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.model.impl; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.Calendar; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.betaconceptframework.astroboa.api.model.BinaryChannel; import org.betaconceptframework.astroboa.api.model.exception.CmsException; import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType; import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder; import org.betaconceptframework.astroboa.context.RepositoryContext; import org.betaconceptframework.astroboa.model.jaxb.adapter.BinaryChannelAdapter; import org.betaconceptframework.astroboa.model.lazy.LazyLoader; import org.betaconceptframework.astroboa.util.CmsConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Repository's resource content * * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * */ @XmlJavaTypeAdapter(value = BinaryChannelAdapter.class) public class BinaryChannelImpl extends CmsRepositoryEntityImpl implements BinaryChannel, Serializable { /** * */ private static final long serialVersionUID = -6485262251238864739L; private String name; private long size; private String encoding; private Calendar modified; /** * Name of the content channel */ private String sourceFilename; /** * MIME type (audio, image, text, video, etc) of resource's content */ private String mimeType; /** * Actual resource's content */ private byte[] newContent; /** * Used when binary content is not managed by the * JCR implementation but rather is managed by the * Astroboa engine. * */ private String relativeFileSystemPath; private String absoluteBinaryChannelContentPath; private String repositoryId; private String contentObjectId; private String contentObjectSystemName; private String binaryPropertyPermanentPath; private boolean unmanaged = false; private boolean binaryPropertyIsMultiValued; /* * This variable represents the external location of the content * of the binary channel. This location can either be a URI or a simple * key which makes sense only in the context of the module (in this * case the PopulateSimpleCmsProperty class) which is * responsible to populate the binary channel (and its content) to the repository. * * The value of this variable is used primarily when saving an object which * contains one or more properties of type Binary, using XML or JSON format. * * According to Astroboa model, the binary channel type defines an attribute named 'url' * which represents the URI location of the content of the binary channel and an * element of type xs:base64Binary, named 'content', which represents the actual content * of the binary channel. * * When importing an object in XML/JSON format, it is practically unrealistic to require that * binary content must exist inside the XML/JSON source under the 'content' element. * A more practical way would be to allow users * to define the URI location of the content so that Astroboa could download it upon save * or to allow users to provide the actual content along with the XML/JSON * source but not inside the source. (see more in * ImportService.importContentObject(String contentSource,boolean version, boolean updateLastModificationTime, boolean save, Map<String, byte[]> binaryContentMap); * * In either cases, users must simply supply a value to the 'url' attribute of the element which represents the binary property * and Astroboa will then discover where to go to get the actual content * * This is an example (in JSON) where user specifies an external location for the content * * "image" : { * "lastModificationDate" : "2008-12-17T16:33:02.474+02:00", * "mimeType" : "image/jpeg", * "sourceFileName" : "image1.jpg", * "url" : "http://myserver/temp/images/image1.jpg" * } * * * and this is an example (in JSON) where user specifies a key as the location of the content * * "image" : { * "lastModificationDate" : "2008-12-17T16:33:02.474+02:00", * "mimeType" : "image/jpeg", * "sourceFileName" : "image1.jpg", * "url" : "image1" * } * * In this case, user must also provide Astroboa with a binary content map * */ private String externalLocationOfTheContent; /** * @return Returns the content. */ public byte[] getContent() { if (newContent != null) { /* * this.newContent = content * * produces the following bug report from FindBugs * Returning a reference to a mutable object value stored in one of the object's fields exposes the internal representation of the object. * If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important * properties, you will need to do something different. Returning a new copy of the object is better approach in many situations. * * The following approach would be more preferable * * byte[] clone = new byte[newContent.length]; System.arraycopy(newContent, 0, clone, 0, newContent.length); return clone; However, we need to examine the performance issue rising from copying the byte array each time user uses method getContent(). Therefore for the moment we return the reference to the provided content. */ return newContent; } //Return content from file system InputStream inputStream = null; try { inputStream = getContentAsStream(); if (inputStream != null) { newContent = IOUtils.toByteArray(inputStream); } else { newContent = new byte[0]; } return newContent; } catch (Exception e) { throw new CmsException(e); } finally { if (inputStream != null) try { inputStream.close(); } catch (IOException e) { throw new CmsException(e); } } } /** * Content is provided only if path for the given binary channel is set * @param content The content to set. */ public void setContent(byte[] content) { /* * this.newContent = content * * produces the following bug report from FindBugs * This code stores a reference to an externally mutable object into the internal representation of the object. * If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security * or other important properties, * you will need to do something different. Storing a copy of the object is better approach in many situations * * The following approach would be more preferable if (content != null){ System.arraycopy(content, 0, newContent, 0, content.length); } else{ newContent = null; } However, we need to examine the performance issue rising from copying the byte array each time user uses method setContent(). Therefore for the moment we create a reference to the provided content. */ newContent = content; if (newContent != null) { setSize(newContent.length); } else { setSize(0); } //Reset paths since a new content has been defined absoluteBinaryChannelContentPath = null; relativeFileSystemPath = null; } /** * * @return Returns the mimeType. */ public String getMimeType() { return mimeType; } /** * @param mimeType The mimeType to set. */ public void setMimeType(String mimeType) { this.mimeType = mimeType; } /** * @return Returns the sourceFilename. */ public String getSourceFilename() { return sourceFilename; } /** * @param sourceFilename The sourceFilename to set. */ public void setSourceFilename(String name) { this.sourceFilename = name; } /** * @return Returns the sourceFilename suffix. */ public String getSourceFilenameSuffix() { return StringUtils.substringAfterLast(sourceFilename, "."); } public long getSize() { return size; } public void setSize(long size) { this.size = size; } public String getCalculatedSize() { Long roundedSize = Math.round(getSize() / (double) 1024); return roundedSize.toString() + "Kb"; } public String getEncoding() { return encoding; } public void setEncoding(String encoding) { this.encoding = encoding; } public Calendar getModified() { return modified; } public void setModified(Calendar modified) { this.modified = modified; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isNewContentLoaded() { return newContent != null; } public String getRelativeFileSystemPath() { return relativeFileSystemPath; } public void setRelativeFileSystemPath(String fileSystemPath) { this.relativeFileSystemPath = fileSystemPath; } public InputStream getContentAsStream() { try { //return new content if any if (newContent != null) return new ByteArrayInputStream(newContent); //Look for file content only if id exists //or binary channel is unmanaged try { if (unmanaged) { if (StringUtils.isNotBlank(absoluteBinaryChannelContentPath)) { return new FileInputStream(absoluteBinaryChannelContentPath); } } else if (StringUtils.isNotBlank(getId())) { //Use binary channel id to load binary value from the content repository LazyLoader lazyLoader = getLazyLoader(); if (lazyLoader != null) { byte[] binaryValue = lazyLoader.lazyLoadBinaryValue(getId(), authenticationToken); return new ByteArrayInputStream(binaryValue); } else { final Logger logger = LoggerFactory.getLogger(getClass()); logger.warn("Could not activate lazy loader for token {}", authenticationToken); } } return null; } catch (Exception e) { final Logger logger = LoggerFactory.getLogger(getClass()); logger.error("", e); return null; } } catch (Exception e) { e.printStackTrace(); return null; } } public void setAbsoluteBinaryChannelContentPath(String absoluteBinaryChannelContentPath) { this.absoluteBinaryChannelContentPath = absoluteBinaryChannelContentPath; } public void setRepositoryId(String repositoryId) { this.repositoryId = repositoryId; } public String getServerURL() { RepositoryContext repositoryContext = AstroboaClientContextHolder .getRepositoryContextForClient(authenticationToken); if (repositoryContext != null && repositoryContext.getCmsRepository() != null && StringUtils.isNotBlank(repositoryContext.getCmsRepository().getServerURL())) { String serverURL = repositoryContext.getCmsRepository().getServerURL().trim(); return serverURL.endsWith("/") ? serverURL.substring(0, serverURL.length() - 1) : serverURL; } return null; } public String getRestfulApiBasePath() { RepositoryContext repositoryContext = AstroboaClientContextHolder .getRepositoryContextForClient(authenticationToken); if (repositoryContext != null && repositoryContext.getCmsRepository() != null && StringUtils.isNotBlank(repositoryContext.getCmsRepository().getRestfulApiBasePath())) { String restfulApiBasePath = repositoryContext.getCmsRepository().getRestfulApiBasePath().trim(); if (!restfulApiBasePath.startsWith("/")) { restfulApiBasePath = "/" + restfulApiBasePath; } return restfulApiBasePath.endsWith("/") ? restfulApiBasePath.substring(0, restfulApiBasePath.length() - 1) : restfulApiBasePath; } return null; } public boolean contentExists() { if (newContent != null) { return true; } if (unmanaged && StringUtils.isNotBlank(absoluteBinaryChannelContentPath)) { return new File(absoluteBinaryChannelContentPath).exists(); } //TODO: Change this code so that binary content should not be loaded getContent(); return newContent != null; } public void setUnmanaged(boolean unmanaged) { this.unmanaged = unmanaged; } public String buildResourceApiURL(Integer width, Integer height, Double aspectRatio, CropPolicy cropPolicy, ContentDispositionType contentDispositionType, boolean friendlyUrl, boolean relative) { if (StringUtils.isBlank(contentObjectId) || StringUtils.isBlank(binaryPropertyPermanentPath)) { return ""; } // Astroboa RESTful API URL pattern for accessing the value of content object properties // http://server/resource-api/ // <reposiotry-id>/objects/<contentObjectId>/<binaryChannelPropertyValuePath> // ?contentDispositionType=<contentDispositionType>&width=<width>&height=<height> StringBuilder resourceApiURLBuilder = new StringBuilder(); if (!relative) { String serverURL = getServerURL(); if (serverURL != null) { resourceApiURLBuilder.append(serverURL); } } resourceApiURLBuilder.append(getRestfulApiBasePath()).append(CmsConstants.FORWARD_SLASH); resourceApiURLBuilder.append((StringUtils.isBlank(repositoryId) ? "no-repository" : repositoryId)); resourceApiURLBuilder.append(CmsConstants.RESOURCE_API_OBJECTS_COLLECTION_URI_PATH); if (friendlyUrl) { resourceApiURLBuilder.append(CmsConstants.FORWARD_SLASH).append(contentObjectSystemName); } else { resourceApiURLBuilder.append(CmsConstants.FORWARD_SLASH).append(contentObjectId); } resourceApiURLBuilder.append(CmsConstants.FORWARD_SLASH).append(binaryPropertyPermanentPath); if (binaryPropertyIsMultiValued) { resourceApiURLBuilder.append(CmsConstants.LEFT_BRACKET).append(getId()) .append(CmsConstants.RIGHT_BRACKET); } StringBuilder urlParametersBuilder = new StringBuilder(); if (contentDispositionType != null) { urlParametersBuilder.append(CmsConstants.AMPERSAND).append("contentDispositionType") .append(CmsConstants.EQUALS_SIGN).append(contentDispositionType.toString().toLowerCase()); } if (isJPGorPNGorGIFImage()) { if (width != null && width > 0) { urlParametersBuilder.append(CmsConstants.AMPERSAND).append("width").append(CmsConstants.EQUALS_SIGN) .append(width); } if (height != null && height > 0) { urlParametersBuilder.append(CmsConstants.AMPERSAND).append("height") .append(CmsConstants.EQUALS_SIGN).append(height); } // we accept to set a new aspect ratio only if if (aspectRatio != null && (width == null || height == null)) { urlParametersBuilder.append(CmsConstants.AMPERSAND).append("aspectRatio") .append(CmsConstants.EQUALS_SIGN).append(aspectRatio); if (cropPolicy != null) { urlParametersBuilder.append(CmsConstants.AMPERSAND).append("cropPolicy") .append(CmsConstants.EQUALS_SIGN).append(cropPolicy.toString()); } } } if (urlParametersBuilder.length() > 0) { urlParametersBuilder.replace(0, 1, CmsConstants.QUESTION_MARK); } return resourceApiURLBuilder.append(urlParametersBuilder).toString(); } // we need this in order to determine if width, height, aspectRatio and cropPolicy url parameters should be appended in the URL private boolean isJPGorPNGorGIFImage() { return StringUtils.isNotBlank(mimeType) && (mimeType.equals("image/jpeg") || mimeType.equals("image/png") || mimeType.equals("image/x-png") || mimeType.equals("image/gif")); } public void setContentObjectId(String contentObjectId) { this.contentObjectId = contentObjectId; } public void setContentObjectSystemName(String systemName) { this.contentObjectSystemName = systemName; } /** * */ public void clean() { getContent(); absoluteBinaryChannelContentPath = null; contentObjectId = null; contentObjectSystemName = null; relativeFileSystemPath = null; setId(null); } @Override public String getResourceApiURL(ResourceRepresentationType<?> resourceRepresentationType, boolean relative, boolean friendlyUrl) { return buildResourceApiURL(null, null, null, null, null, friendlyUrl, relative); } public void binaryPropertyIsMultiValued() { binaryPropertyIsMultiValued = true; } public void setBinaryPropertyPermanentPath(String binaryPropertyPermanentPath) { this.binaryPropertyPermanentPath = binaryPropertyPermanentPath; } public void setExternalLocationOfTheContent(String externalLocationOfTheContent) { this.externalLocationOfTheContent = externalLocationOfTheContent; } public String getExternalLocationOfTheContent() { return externalLocationOfTheContent; } private LazyLoader getLazyLoader() { return AstroboaClientContextHolder.getLazyLoaderForClient(authenticationToken); } }