package org.betaconceptframework.astroboa.resourceapi.resource; import java.io.InputStream; import java.net.HttpURLConnection; import java.util.Calendar; import javax.activation.MimetypesFileTypeMap; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import org.apache.commons.lang.StringUtils; import org.betaconceptframework.astroboa.api.model.BinaryChannel; import org.betaconceptframework.astroboa.api.model.BinaryChannel.ContentDispositionType; import org.betaconceptframework.astroboa.api.model.BinaryProperty; import org.betaconceptframework.astroboa.api.model.ContentObject; import org.betaconceptframework.astroboa.api.model.io.FetchLevel; import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType; import org.betaconceptframework.astroboa.api.model.query.CacheRegion; import org.betaconceptframework.astroboa.client.AstroboaClient; import org.betaconceptframework.astroboa.resourceapi.utility.ContentApiUtils; import org.betaconceptframework.astroboa.resourceapi.utility.IndexExtractor; import org.betaconceptframework.astroboa.util.CmsConstants; import org.betaconceptframework.utility.FilenameUtils; import org.betaconceptframework.utility.ImageUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BinaryChannelResource extends AstroboaResource{ private final Logger logger = LoggerFactory.getLogger(getClass()); private BinaryProperty binaryProperty; private int binaryPropertyValueIndex; private ContentObject contentObject; private String binaryPropertyValueIdentifier; public BinaryChannelResource(AstroboaClient astroboaClient) { super(astroboaClient); } public BinaryChannelResource( AstroboaClient astroboaClient, ContentObject contentObject, BinaryProperty binaryProperty, String binaryPropertyValueIdentifier) { super(astroboaClient); this.contentObject = contentObject; this.binaryProperty = binaryProperty; this.binaryPropertyValueIdentifier = binaryPropertyValueIdentifier; } public BinaryChannelResource( AstroboaClient astroboaClient, ContentObject contentObject, BinaryProperty binaryProperty, int binaryPropertyValueIndex) { super(astroboaClient); this.contentObject = contentObject; this.binaryProperty = binaryProperty; this.binaryPropertyValueIndex = binaryPropertyValueIndex; } // This will be called when the API call is through the content object resource and contentObjectIdOrName, propertyPath have been already consumed // This may also be called if an old style API call is received with empty content object id and empty property path, i.e. /resource/binaryChannel // In the latter case an exception is thrown. // The API call always returns a single binary file even if the binary property is multivalue. // This means that for multivalue binary properties you should provide a value index (between brackets) at the end of the path, // e.g. myfiles.file[0] or mySlideshow[1].image[0] //If the value index is omitted, the first value (i.e. the first file) is always returned @GET @Path("/") public Response getContentObjectBinaryChannelPropertyValue( @QueryParam("contentDispositionType") String contentDispositionType, @QueryParam("width") String width, @QueryParam("height") String height, @QueryParam("aspectRatio") String aspectRatio, @QueryParam("cropPolicy") String cropPolicy) { if (contentObject == null || binaryProperty == null) { // an old style API call with empty content object id and empty property path have been received, i.e. /resource/binaryChannel logger.warn("an old style API call with empty content object id and empty property path have been received"); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } if (StringUtils.isBlank(contentDispositionType) || ! ContentDispositionType.ATTACHMENT.toString().equals(contentDispositionType.toUpperCase())){ contentDispositionType = ContentDispositionType.INLINE.toString(); } else { contentDispositionType = contentDispositionType.toUpperCase(); } if (binaryPropertyValueIdentifier != null){ return getBinaryFileInBinaryChannelPropertyUsingBinaryChannelId(binaryProperty, binaryPropertyValueIdentifier, contentDispositionType, width, height, aspectRatio, cropPolicy); } else{ return getBinaryFileInBinaryChannelProperty(binaryProperty, binaryPropertyValueIndex, contentDispositionType, width, height, aspectRatio, cropPolicy); } } @GET @Path("/{contentObjectIdOrName: " + CmsConstants.UUID_OR_SYSTEM_NAME_REG_EXP_FOR_RESTEASY + "}" + "/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}") public Response getBinaryFileInBinaryChannelProperty( @PathParam("contentObjectIdOrName") String contentObjectIdOrName, @PathParam("propertyPath") String binaryChannelPropertyValuePath ) { return getBinaryFileInBinaryChannelProperty(contentObjectIdOrName, binaryChannelPropertyValuePath, ContentDispositionType.INLINE.toString()); } /* @PUT @Path("/{contentObjectIdOrSystemName}/{binaryChannelPropertyValuePath}") public Response setBinaryFileInBinaryChannelProperty( @PathParam("contentObjectIdOrSystemName") String contentObjectIdOrSystemName, @PathParam("binaryChannelPropertyValuePath") String binaryChannelPropertyValuePath ) { return putBinaryFileInBinaryChannelProperty(contentObjectIdOrSystemName, binaryChannelPropertyValuePath); } */ //binaryChannelPropertyValuePath is the full path to a property value //for example myfiles.file[0] or mySlideshow[1].image[0] // The API call always returns a single binary file even if the binary property is multivalue. //This means that for multivalue binary properties you should provide a value index (between brackets) at the end of the path //If the value index is omitted, the first value (i.e. the first file) is always returned @GET @Path("/{contentObjectIdOrName: " + CmsConstants.UUID_OR_SYSTEM_NAME_REG_EXP_FOR_RESTEASY + "}" + "/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}" + "/{contentDispositionType}") public Response getBinaryFileInBinaryChannelProperty( @PathParam("contentObjectIdOrName") String contentObjectIdOrName, @PathParam("propertyPath") String binaryChannelPropertyValuePath, @PathParam("contentDispositionType") String contentDispositionType) { return getBinaryFileInBinaryChannelProperty(contentObjectIdOrName, binaryChannelPropertyValuePath, contentDispositionType, "-1", "-1"); } @GET @Path("/{contentObjectIdOrName: " + CmsConstants.UUID_OR_SYSTEM_NAME_REG_EXP_FOR_RESTEASY + "}" + "/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}" + "/{contentDispositionType}/{width}") public Response getBinaryFileInBinaryChannelPropertyWithContentDispositionAndWidth( @PathParam("contentObjectIdOrName") String contentObjectIdOrName, @PathParam("propertyPath") String binaryChannelPropertyValuePath, @PathParam("contentDispositionType") String contentDispositionType, @PathParam("width") String width) { return getBinaryFileInBinaryChannelProperty(contentObjectIdOrName, binaryChannelPropertyValuePath, contentDispositionType, width, "-1"); } @GET @Path("/{contentObjectIdOrName: " + CmsConstants.UUID_OR_SYSTEM_NAME_REG_EXP_FOR_RESTEASY + "}" + "/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}" + "/{contentDispositionType}/{height}") public Response getBinaryFileInBinaryChannelPropertyWithContentDispositionAndHeight( @PathParam("contentObjectIdOrName") String contentObjectIdOrName, @PathParam("propertyPath") String binaryChannelPropertyValuePath, @PathParam("contentDispositionType") String contentDispositionType, @PathParam("height") String height) { return getBinaryFileInBinaryChannelProperty(contentObjectIdOrName, binaryChannelPropertyValuePath, contentDispositionType, "-1", height); } @GET @Path("/{contentObjectIdOrName: " + CmsConstants.UUID_OR_SYSTEM_NAME_REG_EXP_FOR_RESTEASY + "}" + "/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}" + "/{height}") public Response getBinaryFileInBinaryChannelPropertyWithHeight( @PathParam("contentObjectIdOrName") String contentObjectIdOrName, @PathParam("propertyPath") String binaryChannelPropertyValuePath, @PathParam("height") String height) { return getBinaryFileInBinaryChannelProperty(contentObjectIdOrName, binaryChannelPropertyValuePath, null, "-1", height); } @GET @Path("/{contentObjectIdOrName: " + CmsConstants.UUID_OR_SYSTEM_NAME_REG_EXP_FOR_RESTEASY + "}" + "/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}" + "/{width}") public Response getBinaryFileInBinaryChannelPropertyWithWidth( @PathParam("contentObjectIdOrName") String contentObjectIdOrName, @PathParam("propertyPath") String binaryChannelPropertyValuePath, @PathParam("width") String width) { return getBinaryFileInBinaryChannelProperty(contentObjectIdOrName, binaryChannelPropertyValuePath, null, width, "-1"); } @GET @Path("/{contentObjectIdOrName: " + CmsConstants.UUID_OR_SYSTEM_NAME_REG_EXP_FOR_RESTEASY + "}" + "/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}" + "/{width}/{height}") public Response getBinaryFileInBinaryChannelPropertyWithWidthAndHeight( @PathParam("contentObjectIdOrName") String contentObjectIdOrName, @PathParam("propertyPath") String binaryChannelPropertyValuePath, @PathParam("width") String width, @PathParam("height") String height) { return getBinaryFileInBinaryChannelProperty(contentObjectIdOrName, binaryChannelPropertyValuePath, null, width, height); } @GET @Path("/{contentObjectIdOrName: " + CmsConstants.UUID_OR_SYSTEM_NAME_REG_EXP_FOR_RESTEASY + "}" + "/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}" + "/{contentDispositionType}/{width}/{height}") public Response getBinaryFileInBinaryChannelProperty( @PathParam("contentObjectIdOrName") String contentObjectIdOrName, @PathParam("propertyPath") String binaryChannelPropertyValuePath, @PathParam("contentDispositionType") String contentDispositionType, @PathParam("width") String width, @PathParam("height") String height) { try { if (StringUtils.isBlank(contentObjectIdOrName)){ logger.warn("The provided content object id {} is invalid ", contentObjectIdOrName); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } ContentObject contentObject = astroboaClient.getContentService().getContentObject(contentObjectIdOrName, ResourceRepresentationType.CONTENT_OBJECT_INSTANCE, FetchLevel.ENTITY, CacheRegion.NONE, null, false); if (contentObject == null) { logger.warn("The provided content object id {} does not correspond to a content object or you do not have permission to access the requested object", contentObjectIdOrName); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } //ContentDisposition Type could either be attachment or inline if (StringUtils.isBlank(contentDispositionType) || ! ContentDispositionType.ATTACHMENT.toString().equals(contentDispositionType.toUpperCase())){ contentDispositionType = ContentDispositionType.INLINE.toString(); } //Extract Index from path IndexExtractor indexExtractor = new IndexExtractor(binaryChannelPropertyValuePath); String binaryChannelPropertyPath = indexExtractor.getPropertyPathWithoutIndex(); int valueIndex = indexExtractor.getIndex(); //Load Property according to property path BinaryProperty binaryChannelProperty = null; try{ binaryChannelProperty = (BinaryProperty) contentObject.getCmsProperty(binaryChannelPropertyPath); } catch(Exception e){ logger.warn("Could not load provided property using path "+ binaryChannelPropertyPath+" from contentObject "+contentObjectIdOrName, e); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } if (binaryChannelProperty == null) { logger.warn("The provided property {} does not exist",binaryChannelPropertyPath); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } return getBinaryFileInBinaryChannelProperty(binaryChannelProperty, valueIndex, contentDispositionType, width, height, null, null); } catch (WebApplicationException e) { throw e; } catch (Exception e) { logger.error("A problem occured while connecting repository client to Astroboa Repository", e); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } } private Response getBinaryFileInBinaryChannelProperty( BinaryProperty binaryProperty, int binaryPropertyValueIndex, String contentDispositionType, String width, String height, String aspectRatio, String cropPolicy) { try { //Load resource if ((binaryProperty).hasNoValues()){ logger.warn("Content Object name: {}. The provided property {} does not have any values", contentObject.getSystemName(), binaryProperty.getPath()); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } BinaryChannel binaryChannel = null; if (binaryProperty.getPropertyDefinition().isMultiple()){ try{ binaryChannel = binaryProperty.getSimpleTypeValues().get(binaryPropertyValueIndex); } catch(Exception e){ logger.warn("Content Object name: " + contentObject.getSystemName() + ". Unable to retrieve value from binary channel property "+binaryProperty.getPath()+ " for index "+ binaryPropertyValueIndex, e); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } } else{ if (binaryPropertyValueIndex != 0){ logger.warn("Content Object name: {}. You have provided a value index for the single value property {}. You will get back the value of the property but avoid to use indexes for single value properties", contentObject.getSystemName(), binaryProperty.getPath()); } binaryChannel = binaryProperty.getSimpleTypeValue(); } if (binaryChannel == null) { logger.warn("Content Object name: {}. BinaryChannel does not exist in path {}", contentObject.getSystemName(), binaryProperty.getPath()); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } String mimeType = binaryChannel.getMimeType(); if (StringUtils.isBlank(mimeType) && StringUtils.isNotBlank(binaryChannel.getSourceFilename())){ mimeType = new MimetypesFileTypeMap().getContentType(binaryChannel.getSourceFilename()); } if (StringUtils.isBlank(mimeType) ){ mimeType = "application/octet-stream"; } String binaryFileName = FilenameUtils.convertFilenameGreekCharactersToEnglishAndReplaceInvalidCharacters(binaryChannel.getSourceFilename()); if (StringUtils.isNotBlank(mimeType) && mimeType.startsWith("image/") && (! StringUtils.equals(width, "-1") || ! StringUtils.equals(height, "-1")) ){ byte[] resourceByteArray = binaryChannel.getContent(); resourceByteArray = resizeImageResource(resourceByteArray, mimeType, width, height, aspectRatio, cropPolicy); return ContentApiUtils.createBinaryResponse( resourceByteArray, mimeType, ContentDispositionType.valueOf(contentDispositionType), (width != null ? "W"+width : "") +(height != null ?"H"+height: "") +(width != null || height != null ? "-":"") +binaryFileName, binaryChannel.getModified() != null ? binaryChannel.getModified().getTime() : Calendar.getInstance().getTime()); } else{ InputStream resourceStream = binaryChannel.getContentAsStream(); if (resourceStream == null) { logger.warn("No content found for binaryChannel in path "+ binaryProperty.getPath()); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } return ContentApiUtils.createBinaryResponseFromStream( resourceStream, mimeType, ContentDispositionType.valueOf(contentDispositionType), binaryFileName, binaryChannel.getModified() != null ? binaryChannel.getModified().getTime() : Calendar.getInstance().getTime(), binaryChannel.getSize()); } } catch (WebApplicationException e) { throw e; } catch (Exception e) { logger.error("Content Object name: " + contentObject.getSystemName() + "binary channel property: " + binaryProperty.getPath() + " property index "+ binaryPropertyValueIndex + ". A problem occured while retrieving binary channel", e); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } } private byte[] resizeImageResource(byte[] resourceByteArray, String mimeType, String width, String height, String aspectRatio, String cropPolicy) throws NumberFormatException, Exception { int imageWidth = 0; int imageHeight = 0; if (StringUtils.isNotBlank(width)){ imageWidth = Integer.valueOf(width); } if (StringUtils.isNotBlank(height)){ imageHeight = Integer.valueOf(height); } if (imageWidth != 0 && imageHeight != 0) { return ImageUtils.bufferedImageToByteArray(ImageUtils.resize(resourceByteArray, imageWidth, imageHeight), mimeType); } if (imageWidth != 0 && imageHeight == 0) { if (StringUtils.isNotBlank(aspectRatio)){ return ImageUtils.changeAspectRatioAndResize(resourceByteArray, mimeType, imageWidth, imageHeight, Double.valueOf(aspectRatio), cropPolicy); } return ImageUtils.bufferedImageToByteArray(ImageUtils.scaleToWidth(resourceByteArray, imageWidth), mimeType); } if (imageWidth == 0 && imageHeight != 0) { if (StringUtils.isNotBlank(aspectRatio)){ return ImageUtils.changeAspectRatioAndResize(resourceByteArray, mimeType, imageWidth, imageHeight, Double.valueOf(aspectRatio), cropPolicy); } return ImageUtils.bufferedImageToByteArray(ImageUtils.scaleToHeight(resourceByteArray, imageHeight), mimeType); } if (imageWidth == 0 && imageHeight == 0 && StringUtils.isNotBlank(aspectRatio)) { return ImageUtils.bufferedImageToByteArray(ImageUtils.changeAspectRatio(resourceByteArray, Double.valueOf(aspectRatio), cropPolicy), mimeType); } return resourceByteArray; } private Response getBinaryFileInBinaryChannelPropertyUsingBinaryChannelId( BinaryProperty binaryProperty, String binaryChannelId, String contentDispositionType, String width, String height, String aspectRatio, String cropPolicy) { try { //Load resource if ((binaryProperty).hasNoValues()){ logger.warn("Content Object name: {}. The provided property {} does not have any values", contentObject.getSystemName(), binaryProperty.getPath()); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } if (StringUtils.isBlank(binaryChannelId)){ logger.warn("Content Object name: {}. The provided binary channel id {} is blank", contentObject.getSystemName(), binaryChannelId); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } BinaryChannel binaryChannel = null; if (binaryProperty.getPropertyDefinition().isMultiple()){ try{ for (BinaryChannel binaryPropertyValue : binaryProperty.getSimpleTypeValues()){ if (StringUtils.equals(binaryPropertyValue.getId(),binaryChannelId)){ binaryChannel = binaryPropertyValue; break; } } } catch(Exception e){ logger.warn("Content Object name: " + contentObject.getSystemName() + ". Unable to retrieve value from binary channel property "+binaryProperty.getPath()+ " for index "+ binaryPropertyValueIndex, e); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } } else{ if (binaryPropertyValueIndex != 0){ logger.warn("Content Object name: {}. You have provided a value index for the single value property {}. You will get back the value of the property but avoid to use indexes for single value properties", contentObject.getSystemName(), binaryProperty.getPath()); } if (StringUtils.equals(binaryProperty.getSimpleTypeValue().getId(), binaryChannelId)){ binaryChannel = binaryProperty.getSimpleTypeValue(); } } if (binaryChannel == null) { logger.warn("Content Object name: {}. BinaryChannel with id {} is not a value of the property in in path {}", new Object[]{contentObject.getSystemName(), binaryChannelId, binaryProperty.getPath()}); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } String mimeType = binaryChannel.getMimeType(); if (StringUtils.isBlank(mimeType) && StringUtils.isNotBlank(binaryChannel.getSourceFilename())){ mimeType = new MimetypesFileTypeMap().getContentType(binaryChannel.getSourceFilename()); } if (StringUtils.isBlank(mimeType) ){ mimeType = "application/octet-stream"; } String binaryFileName = FilenameUtils.convertFilenameGreekCharactersToEnglishAndReplaceInvalidCharacters(binaryChannel.getSourceFilename()); if (StringUtils.isNotBlank(mimeType) && mimeType.startsWith("image/") && (! StringUtils.equals(width, "-1") || ! StringUtils.equals(height, "-1")) ){ byte[] resourceByteArray = binaryChannel.getContent(); resourceByteArray = resizeImageResource(resourceByteArray, mimeType, width, height, aspectRatio, cropPolicy); return ContentApiUtils.createBinaryResponse( resourceByteArray, mimeType, ContentDispositionType.valueOf(contentDispositionType), (width != null ? "W"+width : "") +(height != null ?"H"+height: "") +(width != null || height != null ? "-":"") +binaryFileName, binaryChannel.getModified() != null ? binaryChannel.getModified().getTime() : Calendar.getInstance().getTime()); } else{ InputStream resourceStream = binaryChannel.getContentAsStream(); if (resourceStream == null) { logger.warn("No content found for binaryChannel in path "+ binaryProperty.getPath()); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } return ContentApiUtils.createBinaryResponseFromStream( resourceStream, mimeType, ContentDispositionType.valueOf(contentDispositionType), binaryFileName, binaryChannel.getModified() != null ? binaryChannel.getModified().getTime() : Calendar.getInstance().getTime(), binaryChannel.getSize()); } } catch (WebApplicationException e) { throw e; } catch (Exception e) { logger.error("Content Object name: " + contentObject.getSystemName() + "binary channel property: " + binaryProperty.getPath() + " property index "+ binaryPropertyValueIndex + " and binary channel id "+ binaryChannelId+ ". A problem occured while retrieving binary channel", e); throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); } } }