
com.adobe.granite.rest.converter.siren.AbstractSirenConverter Maven / Gradle / Ivy
/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2014 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package com.adobe.granite.rest.converter.siren;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.ArrayUtils;
import org.apache.jackrabbit.util.ISO8601;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.adobe.granite.rest.Constants;
import com.adobe.granite.rest.converter.ResourceConverter;
import com.adobe.granite.rest.converter.ResourceConverterContext;
import com.adobe.granite.rest.converter.ResourceConverterException;
import com.adobe.granite.rest.filter.Filter;
import com.adobe.granite.rest.utils.Resources;
import com.adobe.granite.rest.utils.URIUtils;
import com.adobe.reef.siren.Action;
import com.adobe.reef.siren.Entity;
import com.adobe.reef.siren.Link;
import com.adobe.reef.siren.builder.BuilderException;
import com.adobe.reef.siren.builder.EntityBuilder;
import com.adobe.reef.siren.builder.LinkBuilder;
/**
* {@code AbtractConverter} is a base implementation of
* {@link ResourceConverter}. ResourceConverter implementations are encouraged
* to extend from this abstract base class.
*/
public abstract class AbstractSirenConverter implements ResourceConverter {
/**
* Allowed property prefixes for short version
*/
// TODO: make this list configurable?
protected static final String[] PREFIX_ALLOWED_SHORT = { "dc" };
/**
* Allowed property prefixes for long version
*/
// TODO: make this list configurable?
protected static final String[] PREFIX_ALLOWED = { "dc", "cq", "crs", "xmp" };
/**
* SIREN properties prefix
*/
public static final String PREFIX_SRN = "srn:";
protected Logger log = LoggerFactory.getLogger(getClass());
protected Resource resource;
private Collection children;
/**
* Relation attribute name "self".
*/
public static final String REL_SELF = "self";
/**
* Relation attribute name "child".
*/
public static final String REL_CHILD = "child";
/**
* Relation attribute name "content".
*/
public static final String REL_CONTENT = "content";
/**
* Relation attribute name "next".
*/
public static final String REL_NEXT = "next";
/**
* Relation attribute name "prev".
*/
public static final String REL_PREV = "prev";
/**
* Relation attribute name "parent".
*/
public static final String REL_PARENT = "parent";
public AbstractSirenConverter(Resource resource) {
this.resource = resource;
}
/**
* Returns the child resources.
*
* @return A collection of child resources
*/
protected Collection listChildren() {
if (children == null) {
children = new LinkedList();
for (Iterator it = resource.listChildren(); it.hasNext(); ) {
Resource resource = it.next();
children.add(resource);
}
}
return children;
}
/**
* Checks a {@code key} against a set of {@code allowedPrefixes}.
*
* @param key Key to check
* @param allowedPrefixes Array of allowed prefixes
* @return {@code true} if key is allowed, {@code false} otherwise
*/
protected boolean isAllowedPrefix(String key, String[] allowedPrefixes) {
return isAllowedPrefix(key, null, allowedPrefixes);
}
/**
* Checks a {@code key} against a set of {@code allowedPrefixes}.
*
* @param key Key to check
* @param context Converter context
* @param allowedPrefixes Array of allowed prefixes
* @return {@code true} if key is allowed, {@code false} otherwise
*/
protected boolean isAllowedPrefix(String key, ResourceConverterContext context, String[] allowedPrefixes) {
// Ignore props that start with an underscore as those are used internally
if (key.charAt(0) == '_') {
return false;
}
if (context != null && context.getShowProperties() != null && context.getShowProperties().length > 0) {
boolean allowed = ArrayUtils.contains(context.getShowProperties(), key);
if (allowed) {
return allowed;
}
}
String prefix = "";
int pos = key.indexOf(':');
// keys with no prefix are always allowed.
if (pos < 0) {
return true;
}
prefix = key.substring(0, pos);
for (String p : allowedPrefixes) {
if (p.equals(prefix)) {
return true;
}
}
return false;
}
/**
* Returns an array representation of the "class" property.
*
* @return Siren class property values
*/
protected abstract String[] getClazz();
/**
* Returns an object representation of properties. By default all properties
* of the underlying resource matching the {@link #PREFIX_ALLOWED_SHORT} or
* if {@code showAllProperties} is set to {@code true} the
* {@link #PREFIX_ALLOWED} are returned.
*
* @param context Converter context
* @return Siren properties map
*/
protected Map getProperties(ResourceConverterContext context) {
return getProperties(context, false);
}
/**
* Returns an object representation of properties. By default all properties
* of the underlying resource matching the {@link #PREFIX_ALLOWED_SHORT} or
* if {@code showAllProperties} is set to {@code true} the
* {@link #PREFIX_ALLOWED} are returned.
*
* @param context Converter context
* @param isChild Indicator for child entity properties
* @return Siren properties map
*/
protected Map getProperties(ResourceConverterContext context, boolean isChild) {
ValueMap valueMap = resource.adaptTo(ValueMap.class);
Map props = getProperties(valueMap, context, isChild);
// overwrite name
props.put("name", resource.getName());
return props;
}
/**
* Returns a set of properties after applying the property prefix rules to
* the specified {@code properties}.
*
* @param properties Resource properties
* @param context Converter context
* @param isChild Indicator if resource is a child resource
* @return
*/
private Map getProperties(Map properties, ResourceConverterContext context, boolean isChild) {
Map props = new HashMap();
if (properties != null) {
for (String key : properties.keySet()) {
// show all allowed
boolean isAllowed = isAllowedPrefix(key, context, PREFIX_ALLOWED);
// show short allowed unless showAllProperties is set
if (isChild) {
if (!context.isShowAllProperties()) {
isAllowed = isAllowedPrefix(key, context, PREFIX_ALLOWED_SHORT);
}
}
if (!isAllowed) {
continue;
}
Object value = properties.get(key);
if(value instanceof Calendar) {
value = ISO8601.format((Calendar)value);
}
if (value instanceof ValueMap) {
value = getProperties((ValueMap)value, context, isChild);
}
props.put(key, value);
}
}
return props;
}
/**
* Returns a list of entities. By default returns an empty list.
*
* @param context ResourceConverterContext
* @param children Children resources to get entities from
* @return List of Siren sub-entities
* @throws BuilderException If an error occurs during the build of the Entity
*/
protected List getEntities(ResourceConverterContext context, Iterator children) throws BuilderException {
return new LinkedList();
}
/**
* Returns a list of entities. By default returns an empty list.
*
* @param context ResourceConverterContext
* @return List of Siren sub-entities
* @throws BuilderException If an error occurs during the build of the Entity
*/
protected List getEntities(ResourceConverterContext context) throws BuilderException {
return getEntities(context, listChildren().iterator());
}
/**
* Returns an entity object.
*
* @param clazz Class attribute.
* @param title Title attribute. Optional.
* @param actions Actions collection. Optional.
* @param entities Entities collection. Optional.
* @param links Links collection. Optional.
* @param properties Properties collection. Optional.
* @return Siren main entity
* @throws BuilderException If an error occurs during the build of the Entity
*/
protected Entity getEntity(String[] clazz, String title, List actions, List entities,
List links, Map properties) throws BuilderException {
Entity entity = new EntityBuilder()
.setClass(clazz)
.setTitle(title)
.setActions(actions)
.setEntities(entities)
.setLinks(links)
.setProperties(properties)
.build();
return entity;
}
/**
* Returns a list of links. By default this method returns a list containing
* one link with rel attribute 'self' and the href pointing to itself.
*
* @param context Converter context
* @return List of Siren links
* @throws BuilderException is an error occurs during building the link
* @throws ResourceConverterException if general error occurs
*/
protected List getLinks(ResourceConverterContext context) throws BuilderException, ResourceConverterException {
Map pagingParameters = new HashMap();
pagingParameters.putAll(context.getParameters());
List links = new LinkedList();
links.add(getLink(AbstractSirenConverter.REL_SELF, buildURL(context, resource.getPath(), Constants.EXT_JSON, pagingParameters), null));
return links;
}
/**
* Returns a link representation.
*
* @param rel rel attribute
* @param href href attribute
* @param type type attribute
* @return A Siren link
* @throws BuilderException If an error occurs during the build of the Link
*/
protected Link getLink(String[] rel, String href, String type) throws BuilderException {
Link link = new LinkBuilder()
.setRel(rel)
.setHref(href)
.setType(type)
.build();
return link;
}
/**
* Returns a link representation.
*
* @param rel rel attributes
* @param href href attribute
* @param type type attribute
* @return A Siren link
* @throws BuilderException If an error occurs during the build of the Link
*/
protected Link getLink(String rel, String href, String type) throws BuilderException {
return getLink(new String[]{rel}, href, type);
}
/**
* Returns a list of actions. By default this method returns an empty list.
*
* @param context Converter context
* @return A list of Siren actions
* @throws BuilderException If an error occurs during the build of the Actions
*/
protected List getActions(ResourceConverterContext context) throws BuilderException, ResourceConverterException {
return new LinkedList();
}
/**
* {@inheritDoc}
*/
@Override
public Entity toEntity(ResourceConverterContext context) throws ResourceConverterException {
try {
ResourceConverterContext ctx = (ResourceConverterContext) context;
Entity entity = new EntityBuilder()
.setClass(getClazz())
.setProperties(getProperties(ctx))
.setEntities(getEntities(ctx))
.setLinks(getLinks(ctx))
.setActions(getActions(ctx))
.build();
return entity;
} catch (BuilderException e) {
throw new ResourceConverterException(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public Entity toSubEntity(ResourceConverterContext context) throws ResourceConverterException {
try {
List links = new LinkedList();
links.add(getLink(AbstractSirenConverter.REL_SELF, buildURL(context, resource.getPath(), Constants.EXT_JSON), null));
Entity entity = new EntityBuilder()
.setClass(getClazz())
.setRel(new String[]{AbstractSirenConverter.REL_CHILD})
.setProperties(getProperties(context,true))
.setLinks(links)
.build();
return entity;
} catch (BuilderException e) {
throw new ResourceConverterException(e);
}
}
/**
* Builds an URL from the provided {@code resourcePath} and
* {@code extension} parameters.
*
* @param context Converter context. Cannot be null.
* @param resourcePath Resource path to build a URL from. Cannot be null.
* @param extension Resource extension with leading dot or {@code null}
* @return A string representation of an URL
* @throws ResourceConverterException if a general error occurs
*/
protected String buildURL(ResourceConverterContext context, String resourcePath, String extension) throws ResourceConverterException {
return buildURL(context, resourcePath, extension, null);
}
/**
* Builds an URL pointing to the 'next' results for paging.
*
* @param context
* Converter context
* @return A string representation of an URL or {@code null} if there is
* nothing more to show
* @throws ResourceConverterException
* if an error occurs
*/
protected String getNextPageURL(ResourceConverterContext context) throws ResourceConverterException {
int offset = context.getOffset();
int limit = context.getLimit();
Filter filter = context.getFilter();
if (offset + limit >= Resources.getSize(listChildren().iterator(), filter)) {
return null;
}
offset += limit;
return buildPagingURL(context, offset);
}
/**
* Builds an URL pointing to the 'previous' results for paging.
*
* @param context Converter context
* @return A string representation of an URL
* @throws ResourceConverterException if a general error occurs
*/
protected String getPrevPageURL(ResourceConverterContext context) throws ResourceConverterException {
int offset = context.getOffset();
if (offset == 0) {
return null;
}
int limit = context.getLimit();
offset -= limit;
if (offset < 0) {
offset = 0;
}
return buildPagingURL(context, offset);
}
private String buildPagingURL(ResourceConverterContext context, int offset) throws ResourceConverterException {
Map pagingParameters = new LinkedHashMap();
pagingParameters.putAll(context.getParameters());
pagingParameters.put(Constants.PARAM_OFFSET, new String[]{ Integer.toString(offset) } );
pagingParameters.put(Constants.PARAM_LIMIT, new String[]{ Integer.toString(context.getLimit())});
return buildURL(context, resource.getPath(), Constants.EXT_JSON, pagingParameters);
}
private String buildURL(ResourceConverterContext context, String resourcePath, String extension, Map additionalParameters)
throws ResourceConverterException {
String authority = context.getServerName();
// we won't expose default ports
if (context.getServerPort() != 80
&& context.getServerPort() != 443) {
authority += ":" + context.getServerPort();
}
String path = resourcePath;
if (!context.isAbsolutURI()) {
path = URIUtils.relativize(context.getRequestPathInfo(), resourcePath);
}
if (extension != null) {
path += extension;
}
String query = null;
if (additionalParameters != null) {
boolean isFirst = true;
for (String parameter : additionalParameters.keySet()) {
for (String value : additionalParameters.get(parameter)) {
if (!isFirst) {
query += "&";
} else {
query = "";
isFirst = false;
}
query += parameter;
query += "=";
query += value;
}
}
}
try {
if (!context.isAbsolutURI()) {
return new URI( null,
null,
path,
query,
null
).toASCIIString();
} else {
return new URI( context.getScheme(),
authority,
path,
query,
null
).toASCIIString();
}
} catch (URISyntaxException e) {
throw new ResourceConverterException(e.getMessage(), e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy