Java tutorial
/* * 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; } } }