org.betaconceptframework.astroboa.model.impl.BinaryChannelImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.betaconceptframework.astroboa.model.impl.BinaryChannelImpl.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.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);
    }

}