org.betaconceptframework.astroboa.resourceapi.utility.ContentApiUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.betaconceptframework.astroboa.resourceapi.utility.ContentApiUtils.java

Source

/*
 * Copyright (C) 2005-2012 BetaCONCEPT Limited
 *
 * This file is part of Astroboa.
 *
 * Astroboa is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Astroboa 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Astroboa.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.betaconceptframework.astroboa.resourceapi.utility;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.activation.MimetypesFileTypeMap;
import javax.security.auth.Subject;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.EntityTag;
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 org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.betaconceptframework.astroboa.api.model.BinaryChannel.ContentDispositionType;
import org.betaconceptframework.astroboa.api.model.CmsRepositoryEntity;
import org.betaconceptframework.astroboa.api.model.Taxonomy;
import org.betaconceptframework.astroboa.api.model.Topic;
import org.betaconceptframework.astroboa.api.model.io.FetchLevel;
import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType;
import org.betaconceptframework.astroboa.api.security.AstroboaPrincipalName;
import org.betaconceptframework.astroboa.api.security.CmsRole;
import org.betaconceptframework.astroboa.api.security.IdentityPrincipal;
import org.betaconceptframework.astroboa.client.AstroboaClient;
import org.betaconceptframework.astroboa.resourceapi.resource.Output;
import org.betaconceptframework.astroboa.security.CmsGroup;
import org.betaconceptframework.astroboa.security.CmsPrincipal;
import org.betaconceptframework.astroboa.security.CmsRoleAffiliationFactory;
import org.betaconceptframework.astroboa.util.ResourceApiURLUtils;
import org.betaconceptframework.astroboa.util.UrlProperties;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Gregory Chomatas (gchomatas@betaconcept.com)
 * @author Savvas Triantafyllou (striantafyllou@betaconcept.com)
 * 
 */
public class ContentApiUtils {

    private final static Logger logger = LoggerFactory.getLogger(ContentApiUtils.class);

    private final static MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();

    private final static String MANAGED_FILE_ACCESS_INFO_REG_EXP = "^" + "([A-Za-z0-9_\\-]+)" + // repository Id (group 1)
            "/" + "(datastore/[0-9abcdef]{2})" + // Resource path ( group 2)
            "/" + "([0-9abcdef]{2})" + // Resource path ( group 3)
            "/" + "([0-9abcdef]{2})" + // Resource path (group 4 )
            "/" + "([0-9abcdef]{40})" + // Resource path (group 5)
            "/" + "([A-Za-z0-9_\\-\\.]+/[A-Za-z0-9_\\-\\.]+)" + // mime-type must contain '/': image/jpeg, application/x-texinfo, etc    ( group 6 )
            "(?:/width/([0-9]+{1,3}))?" + // width ( group 7 )
            "(?:/height/([0-9]+{1,3}))?" + // height  ( group 8 )
            "(?:/contentDispositionType/(attachment|inline))?" + // content disposition type ( group 9 )
            "/" + "([^/]+{0,200})"; // filename  ( group 10 )

    private final static String UNMANAGED_FILE_ACCESS_INFO_REG_EXP = "^" + "([A-Za-z0-9_\\-]+)" + // repository Id (group 1)
            "/" + "(.+?)" + // Resource path ( group 2)
            "(?:/width/([0-9]+{1,3}))?" + // width ( group 3 )
            "(?:/height/([0-9]+{1,3}))?" + // height  ( group 4 )
            "(?:/contentDispositionType/(attachment|inline))?"; // content disposition type ( group 5 )

    public final static Pattern MANAGED_FILE_ACCESS_INFO_PATTERN = Pattern
            .compile(MANAGED_FILE_ACCESS_INFO_REG_EXP);
    public final static Pattern UNMANAGED_FILE_ACCESS_INFO_PATTERN = Pattern
            .compile(UNMANAGED_FILE_ACCESS_INFO_REG_EXP);

    public static ObjectMapper objectMapper;

    static {
        objectMapper = new ObjectMapper();
    }

    public static String createETag(long lastModified, long contentLength) {
        return new StringBuilder().append("\"").append(contentLength).append("-")
                .append(String.valueOf(lastModified)).append("\"").toString();
    }

    public static Subject createSubjectForSystemUserAndItsRoles(String cmsRepositoryId) {

        Subject subject = new Subject();

        //System identity
        subject.getPrincipals().add(new IdentityPrincipal(IdentityPrincipal.SYSTEM));

        //Load default roles for SYSTEM USER
        //Must return at list one group named "Roles" in order to be 
        Group rolesPrincipal = new CmsGroup(AstroboaPrincipalName.Roles.toString());

        for (CmsRole cmsRole : CmsRole.values()) {
            rolesPrincipal.addMember(new CmsPrincipal(CmsRoleAffiliationFactory.INSTANCE
                    .getCmsRoleAffiliationForRepository(cmsRole, cmsRepositoryId)));
        }

        subject.getPrincipals().add(rolesPrincipal);

        return subject;
    }

    public static void generateXMLP(StringBuilder resourceAsXMLP, String resourceAsXML, String callback) {
        String escapedResourceAsXML = StringEscapeUtils.escapeJavaScript(resourceAsXML);
        resourceAsXMLP.append(callback).append("(").append("'").append(escapedResourceAsXML).append("'").append(")")
                .append(";");
    }

    public static void generateJSONP(StringBuilder resourceAsJSONP, String resourceAsJSON, String callback) {
        resourceAsJSONP.append(callback).append("(").append(resourceAsJSON).append(")").append(";");
    }

    public static Response createResponse(StringBuilder resourceRepresentation, Output output, String callback,
            Date lastModified) {
        ResponseBuilder responseBuilder = null;
        responseBuilder = Response.ok(resourceRepresentation.toString());
        responseBuilder.header("Content-Disposition", "inline");

        if (callback != null) {
            responseBuilder.type("application/javascript; charset=utf-8");
        } else if (output == null) {
            responseBuilder.type(MediaType.APPLICATION_XML + "; charset=utf-8");
        } else {
            switch (output) {
            case XML:
            case XSD:
                responseBuilder.type(MediaType.APPLICATION_XML + "; charset=utf-8");
                break;
            case JSON:
                responseBuilder.type(MediaType.APPLICATION_JSON + "; charset=utf-8");
                break;
            case XHTML:
                responseBuilder.type(MediaType.APPLICATION_XHTML_XML + "; charset=utf-8");
                break;
            }
        }

        if (lastModified == null) {
            lastModified = Calendar.getInstance().getTime();
        }
        addLastModifiedAndETagHeaderToResponse(responseBuilder, lastModified, resourceRepresentation.length());

        return responseBuilder.build();
    }

    public List<Topic> getTaxonomyRootTopics(AstroboaClient astroboaClient, String taxonomyName) {

        if (StringUtils.isNotBlank(taxonomyName)) {

            Taxonomy taxonomy = astroboaClient.getTaxonomyService().getTaxonomy(taxonomyName,
                    ResourceRepresentationType.TAXONOMY_INSTANCE, FetchLevel.ENTITY_AND_CHILDREN, false);

            if (taxonomy != null) {
                List<Topic> rootTopics = taxonomy.getRootTopics();
                if (CollectionUtils.isNotEmpty(rootTopics)) {
                    return rootTopics;
                }
            }
        }

        return new ArrayList<Topic>();
    }

    public static Response createBinaryResponse(byte[] binaryResource, String mimeType,
            ContentDispositionType contentDispositionType, String filename, Date lastModified) {
        ResponseBuilder responseBuilder = null;
        responseBuilder = Response.ok(binaryResource, mimeType);
        addContentDispositionHeaderToResponse(responseBuilder, contentDispositionType, filename);
        if (lastModified == null) {
            lastModified = Calendar.getInstance().getTime();
        }
        addLastModifiedAndETagHeaderToResponse(responseBuilder, lastModified, binaryResource.length);
        return responseBuilder.build();
    }

    public static Response createBinaryResponseFromStream(InputStream resourceStream, String mimeType,
            ContentDispositionType contentDispositionType, String filename, Date lastModified, long contentLength) {
        ResponseBuilder responseBuilder = null;
        responseBuilder = Response.ok(resourceStream, mimeType);
        addContentDispositionHeaderToResponse(responseBuilder, contentDispositionType, filename);
        if (lastModified == null) {
            lastModified = Calendar.getInstance().getTime();
        }
        addLastModifiedAndETagHeaderToResponse(responseBuilder, lastModified, contentLength);
        return responseBuilder.build();
    }

    public static void addContentDispositionHeaderToResponse(ResponseBuilder responseBuilder,
            ContentDispositionType contentDispositionType, String fileName) {
        responseBuilder.header("Content-Disposition", contentDispositionType + ";filename=" + fileName);
    }

    public static void addLastModifiedAndETagHeaderToResponse(ResponseBuilder response, Date lastModified,
            long contentLength) {
        response.lastModified(lastModified);
        response.tag(EntityTag.valueOf(createETag(lastModified.getTime(), contentLength)));

    }

    public static boolean isKnownMimeTypeSuffix(String candidateMimeTypeSuffix) {

        if ("application/octet-stream".equals(mimetypesFileTypeMap.getContentType("." + candidateMimeTypeSuffix))) {
            return false;
        }

        return true;

    }

    /*
     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
     * 
     * PUT
     * 
     * The PUT method requests that the enclosed entity be stored 
     * under the supplied Request-URI. If the Request-URI refers 
     * to an already existing resource, the enclosed entity SHOULD 
     * be considered as a modified version of the one residing on 
     * the origin server. 
     * 
     * If the Request-URI does not point to an existing resource, 
     * and that URI is capable of being defined as a new resource 
     * by the requesting user agent, the origin server can create 
     * the resource with that URI. If a new resource is created,
     * the origin server MUST inform the user agent via the 201 (Created) response. 
     * 
     * If an existing resource is modified, either the 200 (OK) or 204 (No Content)
     * response codes SHOULD be sent to indicate successful completion of the request.
     * 
     * POST
     * 
     *  The POST method is used to request that the origin server accept the entity enclosed 
     *  in the request as a new subordinate of the resource identified by the Request-URI in 
     *  the Request-Line.
     *   
     * The actual function performed by the POST method is determined by the server 
     * and is usually dependent on the Request-URI. The posted entity is subordinate 
     * to that URI in the same way that a file is subordinate to a directory containing it, 
     * a news article is subordinate to a newsgroup to which it is posted, or a record is 
     * subordinate to a database.
     *
     * The action performed by the POST method might not result in a resource 
     * that can be identified by a URI. In this case, either 200 (OK) or 204 (No Content) 
     * is the appropriate response status, depending on whether or not the response 
     * includes an entity that describes the result. 
     */
    public static Response createSuccessfulResponseForPUTOrPOST(CmsRepositoryEntity entity, String httpMethod,
            ResourceRepresentationType<?> resourceRepresentationType, boolean entityIsNew) {

        ResponseBuilder responseBuilder = null;

        if (httpMethod == null || httpMethod.equals(HttpMethod.POST)) {
            //Entity is a new one. Send CREATED (201) status with location header
            responseBuilder = Response.status(Status.CREATED);

            UrlProperties urlProperties = new UrlProperties();
            urlProperties.setResourceRepresentationType(resourceRepresentationType);
            urlProperties.setFriendly(false);
            urlProperties.setRelative(false);
            urlProperties.setIdentifier(entity.getId());

            responseBuilder.location(URI.create(ResourceApiURLUtils.generateUrlForEntity(entity, urlProperties)));

        } else if (httpMethod.equals(HttpMethod.PUT)) {

            if (entityIsNew) {
                //Entity is a new one. Send CREATED (201) status
                responseBuilder = Response.status(Status.CREATED);

            } else {
                responseBuilder = Response.status(Status.OK);
            }
        } else {
            logger.warn("Expected to have either HTTP PUT or HTTP POST but the provided HTTP method is "
                    + httpMethod + " Will send OK status nevertheless");
            responseBuilder = Response.status(Status.OK);
        }

        responseBuilder.header("Content-Disposition", "inline");
        responseBuilder.type(MediaType.TEXT_PLAIN + "; charset=utf-8");

        //TODO: It should clarified whether entity identifier or entity's system name is provided 
        if (entity != null) {
            responseBuilder.entity(entity.getId());
        }

        return responseBuilder.build();
    }

    public static Response createResponseForPutOrPostOfACmsEntity(CmsRepositoryEntity cmsRepositoryEntity,
            String httpMethod, String requestContent, boolean entityIsNew) {

        if (cmsRepositoryEntity != null && cmsRepositoryEntity.getId() != null) {

            ResourceRepresentationType<?> resourceRepresentationType = contentIsXML(requestContent)
                    ? ResourceRepresentationType.XML
                    : ResourceRepresentationType.JSON;

            return createSuccessfulResponseForPUTOrPOST(cmsRepositoryEntity, httpMethod, resourceRepresentationType,
                    entityIsNew);
        } else {
            logger.error(
                    "{} request was not successfull. The entity created when importing the following content {}. \n {} ",
                    new Object[] { httpMethod, (cmsRepositoryEntity == null ? " was null" : " had no identifier"),
                            requestContent });

            throw new WebApplicationException(HttpURLConnection.HTTP_BAD_REQUEST);
        }
    }

    public static Output getOutputType(String output, Output defaultOutput) {

        if (output == null) {
            return defaultOutput;
        }

        try {
            return Output.valueOf(output.toUpperCase());
        } catch (Exception e) {
            logger.warn("Invalid value '{}' for output parameter. Output.XML will be used");
            return defaultOutput;
        }
    }

    public static boolean isPrettyPrintEnabled(String prettyPrint) {

        if (prettyPrint == null) {
            return false;
        }

        try {
            return BooleanUtils.isTrue(Boolean.valueOf(prettyPrint));
        } catch (Exception e) {
            logger.warn("Invalid value '{}' for prettyPrint parameter. Pretty Print will be disabled");
            return false;
        }
    }

    /*
     * This is a very basic way to determine whether content provided 
     * with HTTP PUT or POST is an XML or JSON, without the need to check the 
     * headers. 
     * 
     * A more elegant way must be determined. 
     * 
     * Currently the need to know the type of the content is needed 
     * when we need to generate the url which represents the entity or the property saved.
     * 
     */
    public static boolean contentIsXML(String content) {
        return content != null && content.startsWith("<?xml version=\"1.0\"");
    }

    public static Response createResponseForExcelWorkbook(HSSFWorkbook workbook,
            ContentDispositionType contentDispositionType, String filename, Date lastModified) {
        ResponseBuilder responseBuilder = null;

        responseBuilder = Response.ok(workbook, "application/vnd.ms-excel");

        addContentDispositionHeaderToResponse(responseBuilder, contentDispositionType, filename);
        if (lastModified == null) {
            lastModified = Calendar.getInstance().getTime();
        }
        addLastModifiedAndETagHeaderToResponse(responseBuilder, lastModified, -1);

        return responseBuilder.build();
    }

    public static Map<String, Object> parse(String json) throws JsonParseException, IOException {
        return objectMapper.readValue(json, Map.class);
    }

    /*
     * A successful response SHOULD be 200 (OK) if the response includes an entity describing the status, 
     * 202 (Accepted) if the action has not yet been enacted, 
     * or 204 (No Content) if the action has been enacted but the response does not include an entity. 
     */
    public static Response createResponseForHTTPDelete(boolean entityHasBeenDeleted, String entityIdOrName) {

        ResponseBuilder responseBuilder = null;

        if (entityHasBeenDeleted) {
            responseBuilder = Response.status(Status.OK);
        } else {
            responseBuilder = Response.status(Status.BAD_REQUEST);
        }

        responseBuilder.header("Content-Disposition", "inline");
        responseBuilder.type(MediaType.TEXT_PLAIN + "; charset=utf-8");

        responseBuilder.entity(entityIdOrName);

        return responseBuilder.build();
    }

    /*
     * This method creates a response with the provided status and the provided exception message.
     * 
     * User may log this exception (on the server) as well. Log level is ERROR
     * 
     * This response can be returned as is or can be used as a parameter of a WebApplicationException, like
     * 
     * throw new WebApplicationException(ContentApiUtils.createResponseForException(Status.BAD_REQUEST, t, true, "Additional Message"));
     * 
     */
    public static Response createResponseForException(Status status, Throwable t, boolean logException,
            String additionalMessage) {

        ResponseBuilder response = Response.status(status);
        response.header("Content-Type", "text/plain; charset=UTF-8");

        StringBuilder message = new StringBuilder();

        if (additionalMessage != null) {
            message.append(additionalMessage);
        }

        if (t != null) {

            if (t.getMessage() != null) {
                message.append("-").append(t.getMessage());
            }

            if (logException) {
                logger.error("", t);
            }
        }

        response.entity(message.toString());

        return response.build();

    }

    public static FetchLevel getFetchLevel(String depth, FetchLevel defaultValue) {

        if (StringUtils.isBlank(depth)) {
            return defaultValue;
        }

        if (StringUtils.equalsIgnoreCase(depth, "0")) {
            return FetchLevel.ENTITY;
        }

        if (StringUtils.equalsIgnoreCase(depth, "1")) {
            return FetchLevel.ENTITY_AND_CHILDREN;
        }

        if (StringUtils.equalsIgnoreCase(depth, "-1")) {
            return FetchLevel.FULL;
        }

        return defaultValue;
    }

    public static boolean shouldUpdateLastModificationTime(String updateLastModificationTime) {

        //Default value is true
        if (updateLastModificationTime == null || updateLastModificationTime.trim().length() == 0) {
            return true;
        }

        try {
            return BooleanUtils.isTrue(Boolean.valueOf(updateLastModificationTime));
        } catch (Exception e) {
            logger.warn(
                    "Invalid value '{}' for updateLastModificationTime parameter. Last modification time will be updated");
            return false;
        }
    }
}