/*
*
* This is a simple Content Management System (CMS)
* Copyright (C) 2010 Imran M Yousuf (imyousuf@smartitengineering.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.smartitengineering.cms.ws.resources.content;
import com.smartitengineering.cms.api.content.BooleanFieldValue;
import com.smartitengineering.cms.api.content.CollectionFieldValue;
import com.smartitengineering.cms.api.content.CompositeFieldValue;
import com.smartitengineering.cms.api.content.Content;
import com.smartitengineering.cms.api.content.ContentFieldValue;
import com.smartitengineering.cms.api.content.ContentId;
import com.smartitengineering.cms.api.content.DateTimeFieldValue;
import com.smartitengineering.cms.api.content.Field;
import com.smartitengineering.cms.api.content.FieldValue;
import com.smartitengineering.cms.api.content.NumberFieldValue;
import com.smartitengineering.cms.api.factory.SmartContentAPI;
import com.smartitengineering.cms.api.factory.content.WriteableContent;
import com.smartitengineering.cms.api.type.CompositeDataType;
import com.smartitengineering.cms.api.type.DataType;
import com.smartitengineering.cms.api.type.FieldDef;
import com.smartitengineering.cms.api.type.FieldValueType;
import com.smartitengineering.cms.api.type.OtherDataType;
import com.smartitengineering.cms.api.type.StringDataType;
import com.smartitengineering.cms.ws.common.domains.FieldImpl;
import com.smartitengineering.cms.ws.common.providers.TextURIListProvider;
import com.smartitengineering.cms.ws.common.utils.Utils;
import com.smartitengineering.cms.ws.resources.ResourcesConfig;
import com.smartitengineering.util.bean.adapter.AbstractAdapterHelper;
import com.smartitengineering.util.bean.adapter.GenericAdapter;
import com.smartitengineering.util.bean.adapter.GenericAdapterImpl;
import com.smartitengineering.util.rest.atom.server.AbstractResource;
import com.smartitengineering.util.rest.server.ServerResourceInjectables;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.Variant;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;
import org.apache.abdera.model.Link;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author imyousuf
*/
public class FieldResource extends AbstractResource {
private final Content content;
private final FieldDef fieldDef;
private final EntityTag entityTag;
private final Field preinitField;
protected final GenericAdapter<Field, com.smartitengineering.cms.ws.common.domains.Field> adapter;
protected final transient Logger logger = LoggerFactory.getLogger(getClass());
public static final String PATH_TO_VAR = "v/{varName}";
public FieldResource(ServerResourceInjectables injectables, Content content, FieldDef fieldDef, EntityTag eTag) {
this(injectables, content, fieldDef, eTag, null);
}
public FieldResource(ServerResourceInjectables injectables, Content content, FieldDef fieldDef, EntityTag eTag,
Field field) {
super(injectables);
if (content == null || fieldDef == null) {
logger.warn("No content or field def", new NullPointerException());
throw new WebApplicationException(Status.NOT_FOUND);
}
this.content = content;
this.fieldDef = fieldDef;
GenericAdapterImpl adapterImpl = new GenericAdapterImpl<Field, com.smartitengineering.cms.ws.common.domains.Field>();
adapterImpl.setHelper(new FieldAdapterHelper());
this.adapter = adapterImpl;
this.entityTag = eTag;
this.preinitField = field;
}
@Path(PATH_TO_VAR)
public VariationResource getVariation(@PathParam("varName") String varName) {
Field field = getCurrentField();
if (field != null) {
return new VariationResource(getInjectables(), content, field, varName);
}
else {
throw new WebApplicationException(Status.NOT_FOUND);
}
}
@GET
@Path("/f/{fieldName}")
public FieldResource getComposedField(@PathParam("fieldName") final String nestedFieldName) {
if (!fieldDef.getValueDef().getType().equals(FieldValueType.COMPOSITE)) {
throw new WebApplicationException(Status.BAD_REQUEST);
}
CompositeDataType compositeFieldDef = (CompositeDataType) fieldDef.getValueDef();
FieldDef newFieldDef = compositeFieldDef.getComposedFieldDefs().get(nestedFieldName);
CompositeFieldValue compositeFieldValue = (CompositeFieldValue) getCurrentField().getValue();
return new FieldResource(getInjectables(), content, newFieldDef, entityTag, compositeFieldValue.getValueAsMap().get(
nestedFieldName));
}
@GET
@Path("/raw/abs")
public Response getAbsoluteRaw() {
ResponseBuilder builder = getContext().getRequest().evaluatePreconditions(content.getLastModifiedDate(), entityTag);
if (builder == null) {
Field field = getCurrentField();
if (field == null) {
builder = Response.status(Status.NOT_FOUND);
}
else {
builder = Response.ok().tag(entityTag);
processDefaultRawContent(builder);
}
CacheControl control = new CacheControl();
control.setMaxAge(ResourcesConfig.getInstance().getFieldHttpCacheControlMaxAge());
builder.cacheControl(control);
}
return builder.build();
}
protected Field getCurrentField() {
if (preinitField != null) {
return preinitField;
}
else {
return content.getField(fieldDef.getName());
}
}
@GET
@Path("/raw")
public Response getRaw() {
boolean useDefault = false;
List<MediaType> accepts = getContext().getRequest().getAcceptableMediaTypes();
if (accepts == null || accepts.isEmpty()) {
useDefault = true;
}
ResponseBuilder builder =
getContext().getRequest().evaluatePreconditions(content.getLastModifiedDate(), entityTag);
if (builder == null) {
Field field = getCurrentField();
if (field == null) {
builder = Response.status(Status.NOT_FOUND);
}
else {
builder = Response.ok().tag(entityTag);
if (useDefault) {
processDefaultRawContent(builder);
}
else {
final MediaType fieldValueDefaultMimeType = getFieldValueDefaultMimeType(fieldDef.getValueDef());
MediaType type = getUserPreferredType(fieldValueDefaultMimeType);
if (type == null) {
final List<Variant> variants = getVariants(MediaType.APPLICATION_ATOM_XML_TYPE, fieldValueDefaultMimeType,
MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE,
MediaType.APPLICATION_XML_TYPE, MediaType.TEXT_XML_TYPE);
return Response.notAcceptable(variants).build();
}
else if (type.equals(MediaType.APPLICATION_ATOM_XML_TYPE) || type.equals(MediaType.TEXT_XML_TYPE) || type.
equals(
MediaType.APPLICATION_XML_TYPE)) {
processAtomFeedAsRawContent(builder);
}
else if (type.toString().equals(MediaType.MEDIA_TYPE_WILDCARD) ||
type.equals(fieldValueDefaultMimeType)) {
processDefaultRawContent(builder);
}
else {
return Response.seeOther(getFieldUri()).build();
}
}
CacheControl control = new CacheControl();
control.setMaxAge(ResourcesConfig.getInstance().getFieldHttpCacheControlMaxAge());
builder.header(HttpHeaders.VARY, HttpHeaders.ACCEPT);
builder.cacheControl(control);
}
}
return builder.build();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response get() {
ResponseBuilder builder = getContext().getRequest().evaluatePreconditions(content.getLastModifiedDate(), entityTag);
if (builder == null) {
Field field = getCurrentField();
if (field == null) {
builder = Response.status(Status.NOT_FOUND);
}
else {
builder = Response.ok(adapter.convert(field)).lastModified(content.getLastModifiedDate()).tag(entityTag);
CacheControl control = new CacheControl();
control.setMaxAge(ResourcesConfig.getInstance().getFieldHttpCacheControlMaxAge());
builder.cacheControl(control);
}
}
return builder.build();
}
@DELETE
public Response delete(@HeaderParam(HttpHeaders.IF_MATCH) EntityTag ifMatchHeader) {
if (!fieldDef.isFieldStandaloneUpdateAble()) {
return Response.status(Status.FORBIDDEN).build();
}
final boolean isAvailable = getCurrentField() != null;
if (!isAvailable) {
return Response.status(Status.NOT_FOUND).build();
}
if (ifMatchHeader == null) {
return Response.status(Status.PRECONDITION_FAILED).build();
}
ResponseBuilder builder =
getContext().getRequest().evaluatePreconditions(content.getLastModifiedDate(), entityTag);
if (builder == null) {
WriteableContent writeableContent = SmartContentAPI.getInstance().getContentLoader().getWritableContent(content);
writeableContent.removeField(fieldDef.getName());
boolean error = false;
try {
writeableContent.put();
}
catch (Exception ex) {
logger.error("Could not update field by updating content!", ex);
builder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage());
error = true;
}
if (!error) {
builder = Response.ok();
}
}
return builder.build();
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response put(com.smartitengineering.cms.ws.common.domains.Field field,
@HeaderParam(HttpHeaders.IF_MATCH) EntityTag ifMatchHeader) {
if (!fieldDef.isFieldStandaloneUpdateAble()) {
return Response.status(Status.FORBIDDEN).build();
}
final boolean isUpdate = content.getField(field.getName()) != null;
if (isUpdate && ifMatchHeader == null) {
return Response.status(Status.PRECONDITION_FAILED).build();
}
ResponseBuilder builder =
getContext().getRequest().evaluatePreconditions(content.getLastModifiedDate(), entityTag);
if (builder == null) {
boolean error = false;
WriteableContent writeableContent = SmartContentAPI.getInstance().getContentLoader().getWritableContent(content);
try {
writeableContent.setField(adapter.convertInversely(field));
}
catch (Exception ex) {
logger.warn("Could not convert to field!", ex);
builder = Response.status(Status.BAD_REQUEST).entity(ex.getMessage());
error = true;
}
try {
writeableContent.put();
}
catch (Exception ex) {
logger.error("Could not update field by updating content!", ex);
builder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage());
error = true;
}
if (!error) {
if (isUpdate) {
builder = Response.status(Status.ACCEPTED);
}
else {
builder = Response.created(UriBuilder.fromUri(ContentResource.getContentUri(getAbsoluteURIBuilder(), content.
getContentId())).path(field.getName()).build());
}
}
}
return builder.build();
}
protected Collection<Entry> getEntries(final FieldValue value, final Date lastModifiedDate, String... id)
throws IllegalArgumentException {
final List<Entry> entries = new ArrayList<Entry>();
final String mimeType = getFieldValueDefaultMimeType(fieldDef.getValueDef()).toString();
switch (fieldDef.getValueDef().getType()) {
case BOOLEAN:
BooleanFieldValue booleanFieldValue = (BooleanFieldValue) value;
Entry entry = getEntry("value", "Value", lastModifiedDate);
entry.setContent(Boolean.toString(booleanFieldValue.getValue()), mimeType);
entries.add(entry);
break;
case COLLECTION:
CollectionFieldValue collectionFieldValue = (CollectionFieldValue) value;
int index = 0;
for (FieldValue fieldValue : collectionFieldValue.getValue()) {
entries.addAll(
getEntries(fieldValue, lastModifiedDate, new StringBuilder("value-").append(index++).toString()));
}
break;
case CONTENT:
ContentFieldValue contentFieldValue = (ContentFieldValue) value;
entry = getEntry(StringUtils.defaultIfEmpty(id[0], "value"), "Value", lastModifiedDate, getLink(ContentResource.
getContentUri(getRelativeURIBuilder(), contentFieldValue.getValue()), Link.REL_ALTERNATE, mimeType));
entries.add(entry);
break;
case DATE_TIME:
DateTimeFieldValue dateTimeFieldValue = (DateTimeFieldValue) value;
entry = getEntry("value", "Value", lastModifiedDate);
entry.setContent(Utils.getFormattedDate(dateTimeFieldValue.getValue()), mimeType);
entries.add(entry);
break;
case DOUBLE:
case INTEGER:
case LONG:
NumberFieldValue numberFieldValue = (NumberFieldValue) value;
entry = getEntry("value", "Value", lastModifiedDate);
entry.setContent(numberFieldValue.getValue().toString(), mimeType);
entries.add(entry);
break;
case STRING:
case OTHER:
OtherDataType otherDataType = (OtherDataType) fieldDef.getValueDef();
entry = getEntry("value", "Value", lastModifiedDate);
entry.setContent(String.valueOf(value), mimeType);
entries.add(entry);
break;
}
return entries;
}
public static MediaType getFieldValueDefaultMimeType(DataType value) {
switch (value.getType()) {
case COLLECTION:
return MediaType.APPLICATION_ATOM_XML_TYPE;
case CONTENT:
return TextURIListProvider.TEXT_URI_LIST_TYPE;
case DATE_TIME:
case DOUBLE:
case INTEGER:
case LONG:
case BOOLEAN:
default:
return MediaType.TEXT_PLAIN_TYPE;
case STRING:
case OTHER:
OtherDataType otherDataType = (OtherDataType) value;
return MediaType.valueOf(otherDataType.getMIMEType());
}
}
private void processDefaultRawContent(ResponseBuilder builder) {
final Field field = getCurrentField();
final FieldValue value = field.getValue();
final MediaType mimeType = getFieldValueDefaultMimeType(fieldDef.getValueDef());
switch (fieldDef.getValueDef().getType()) {
case BOOLEAN:
BooleanFieldValue booleanFieldValue = (BooleanFieldValue) value;
builder.entity(booleanFieldValue.getValue()).type(mimeType);
break;
case COLLECTION:
processAtomFeedAsRawContent(builder);
break;
case CONTENT:
ContentFieldValue contentFieldValue = (ContentFieldValue) value;
builder.entity(Collections.singleton(ContentResource.getContentUri(getRelativeURIBuilder(), contentFieldValue.
getValue()))).type(mimeType);
break;
case DATE_TIME:
DateTimeFieldValue dateTimeFieldValue = (DateTimeFieldValue) value;
builder.entity(Utils.getFormattedDate(dateTimeFieldValue.getValue())).type(mimeType);
break;
case DOUBLE:
case INTEGER:
case LONG:
NumberFieldValue numberFieldValue = (NumberFieldValue) value;
builder.entity(numberFieldValue.getValue().toString()).type(mimeType);
break;
case STRING:
StringDataType stringDataType = (StringDataType) fieldDef.getValueDef();
final String encoding = stringDataType.getEncoding();
if (StringUtils.isNotBlank(encoding)) {
builder.header(HttpHeaders.CONTENT_ENCODING, encoding);
}
case OTHER:
builder.entity(value.getValue());
builder.type(mimeType);
break;
}
}
private void processAtomFeedAsRawContent(ResponseBuilder builder) {
final String toString =
new StringBuilder(content.getContentId().toString()).append(':').append(fieldDef.getName()).toString();
final Date lastModifiedDate = content.getLastModifiedDate();
Feed feed = getFeed(toString, toString, lastModifiedDate);
feed.addLink(getLink(getFieldUri(), Link.REL_EDIT, MediaType.APPLICATION_JSON));
final Field field = getCurrentField();
final FieldValue value = field.getValue();
Collection<Entry> entries = getEntries(value, lastModifiedDate);
for (Entry entry : entries) {
feed.addEntry(entry);
}
if (!value.getDataType().equals(FieldValueType.COLLECTION)) {
feed.addLink(getLink(UriBuilder.fromUri(getFieldUri()).path("raw").build(), Link.REL_ALTERNATE,
getFieldValueDefaultMimeType(fieldDef.getValueDef()).toString()));
}
builder.entity(feed).type(MediaType.APPLICATION_ATOM_XML);
}
private URI getFieldUri() {
return getFieldURI(getAbsoluteURIBuilder(), content, fieldDef);
}
public static URI getFieldURI(UriBuilder builder, Content content, FieldDef fieldDef) {
return getFieldURI(builder, content.getContentId(), fieldDef);
}
public static URI getFieldRawURI(UriBuilder builder, Content content, FieldDef fieldDef) {
return getFieldRawURI(builder, content.getContentId(), fieldDef);
}
public static URI getFieldAbsRawURI(UriBuilder builder, Content content, FieldDef fieldDef) {
return getFieldAbsRawURI(builder, content.getContentId(), fieldDef);
}
public static URI getFieldURI(UriBuilder builder, ContentId contentId, FieldDef fieldDef) {
return UriBuilder.fromUri(ContentResource.getContentUri(builder, contentId)).path("f").path(fieldDef.getName()).
build();
}
public static URI getFieldRawURI(UriBuilder builder, ContentId contentId, FieldDef fieldDef) {
return UriBuilder.fromUri(ContentResource.getContentUri(builder, contentId)).path("f").path(fieldDef.getName()).path(
"raw").build();
}
public static URI getFieldAbsRawURI(UriBuilder builder, ContentId contentId, FieldDef fieldDef) {
return UriBuilder.fromUri(ContentResource.getContentUri(builder, contentId)).path("f").path(fieldDef.getName()).path(
"raw").path("abs").build();
}
@Override
protected String getAuthor() {
return "Smart CMS";
}
private MediaType getUserPreferredType(MediaType defaultType) {
return getContext().getRequest().selectVariant(getVariants(defaultType, MediaType.APPLICATION_ATOM_XML_TYPE,
MediaType.APPLICATION_JSON_TYPE,
MediaType.APPLICATION_XML_TYPE,
MediaType.TEXT_XML_TYPE,
MediaType.WILDCARD_TYPE)).getMediaType();
}
private List<Variant> getVariants(MediaType... mediaTypes) {
return Variant.mediaTypes(mediaTypes).add().build();
}
class FieldAdapterHelper extends AbstractAdapterHelper<Field, com.smartitengineering.cms.ws.common.domains.Field> {
@Override
protected com.smartitengineering.cms.ws.common.domains.Field newTInstance() {
return new FieldImpl();
}
@Override
protected void mergeFromF2T(Field fromBean, com.smartitengineering.cms.ws.common.domains.Field toBean) {
ContentResource.getDomainField(getRelativeURIBuilder(), fromBean, ContentResource.getContentUri(
getRelativeURIBuilder(), content.getContentId()).toASCIIString(), (FieldImpl) toBean);
}
@Override
protected Field convertFromT2F(com.smartitengineering.cms.ws.common.domains.Field toBean) {
return ContentResource.getField(content.getContentId(), fieldDef, toBean, getResourceContext(),
getAbsoluteURIBuilder(), false);
}
}
}