Java tutorial
/** * NuxeoImageUtils.java * * {Purpose of This Class} * * {Other Notes Relating to This Class (Optional)} * * $LastChangedBy: $ * $LastChangedRevision: $ * $LastChangedDate: $ * * This document is a part of the source code and related artifacts * for CollectionSpace, an open source collections management system * for museums and related institutions: * * http://www.collectionspace.org * http://wiki.collectionspace.org * * Copyright 2009 {Contributing Institution}. * * Licensed under the Educational Community License (ECL), Version 2.0. * You may not use this file except in compliance with this License. * * You may obtain a copy of the ECL 2.0 License at * https://source.collectionspace.org/collection-space/LICENSE.txt */ package org.collectionspace.services.common.imaging.nuxeo; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; import java.util.List; import java.util.Map; import org.nuxeo.runtime.api.Framework; import org.nuxeo.ecm.platform.picture.api.ImageInfo; import org.nuxeo.ecm.platform.picture.api.ImagingDocumentConstants; import org.nuxeo.ecm.platform.picture.api.ImagingService; import org.nuxeo.ecm.platform.picture.api.PictureView; import org.nuxeo.ecm.platform.mimetype.MimetypeDetectionException; import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry; import org.nuxeo.ecm.platform.picture.api.adapters.PictureBlobHolder; import org.nuxeo.ecm.platform.filemanager.api.FileManager; import org.nuxeo.ecm.platform.filemanager.service.FileManagerService; import org.nuxeo.ecm.platform.filemanager.service.extension.FileImporter; import org.nuxeo.ecm.platform.filemanager.utils.FileManagerUtils; import org.nuxeo.ecm.platform.types.TypeManager; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; import org.nuxeo.ecm.core.api.blobholder.DocumentBlobHolder; import org.nuxeo.ecm.core.api.impl.blob.FileBlob; import org.nuxeo.ecm.core.api.impl.blob.InputStreamBlob; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentRef; import org.nuxeo.ecm.core.api.VersioningOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.commons.io.IOUtils; import org.collectionspace.services.client.PoxPayloadIn; import org.collectionspace.services.client.PoxPayloadOut; import org.collectionspace.services.common.FileUtilities; import org.collectionspace.services.common.ServiceMain; import org.collectionspace.services.common.blob.BlobInput; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.document.TransactionException; import org.collectionspace.services.common.repository.RepositoryClient; import org.collectionspace.services.common.api.CommonAPI; import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils; import org.collectionspace.services.common.blob.BlobOutput; import org.collectionspace.services.blob.BlobsCommon; import org.collectionspace.services.blob.DimensionSubGroup; import org.collectionspace.services.blob.DimensionSubGroupList; import org.collectionspace.services.blob.MeasuredPartGroup; import org.collectionspace.services.blob.MeasuredPartGroupList; import org.collectionspace.services.jaxb.BlobJAXBSchema; import org.collectionspace.services.nuxeo.client.java.CommonList; import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl; import org.collectionspace.services.nuxeo.extension.thumbnail.ThumbnailConstants; import org.collectionspace.services.nuxeo.util.NuxeoUtils; import org.collectionspace.services.config.service.ListResultField; /** * The Class NuxeoBlobUtils. */ public class NuxeoBlobUtils { /** The Constant logger. */ private static final Logger logger = LoggerFactory.getLogger(NuxeoBlobUtils.class); // // A maximum byte size for the byte array used to hold an image. Images larger than this will // be returned as FileInputStreams rather than ByteArrayInputStreams // private static final int MAX_IMAGE_BUFFER = 256 * 1024; // REM: 11/26/2013 - This should be set in a config/property file. // // File name constants // private static final String NUXEO_FILENAME_BAD_CHARS = "[^a-zA-Z_0-9-.%:/\\ ]"; private static final String NUXEO_FILENAME_VALID_STRING = "[a-zA-Z_0-9-.%:/\\ ]+"; public static final String DOCUMENT_PLACEHOLDER_IMAGE = "documentImage.jpg"; public static final String DOCUMENT_MISSING_PLACEHOLDER_IMAGE = "documentImageMissing.jpg"; public static final String DOCUMENT_PLACEHOLDER_CSV = "documentCSV.jpg"; public static final String DOCUMENT_PLACEHOLDER_DOC = "documentDOC.jpg"; public static final String DOCUMENT_PLACEHOLDER_DOCX = "documentDOC.jpg"; public static final String DOCUMENT_PLACEHOLDER_MP3 = "documentMP3.jpg"; public static final String DOCUMENT_PLACEHOLDER_PDF = "documentPDF.jpg"; public static final String DOCUMENT_PLACEHOLDER_PPT = "documentPPT.jpg"; public static final String DOCUMENT_PLACEHOLDER_PPTX = "documentPPT.jpg"; public static final String DOCUMENT_PLACEHOLDER_RTF = "documentRTF.jpg"; public static final String DOCUMENT_PLACEHOLDER_XLS = "documentXLS.jpg"; public static final String DOCUMENT_PLACEHOLDER_XLSX = "documentXLS.jpg"; public static final String DOCUMENT_PLACEHOLDER_ZIP = "documentZIP.jpg"; public static final String MIME_CSV = "text/csv"; public static final String MIME_DOC = "application/msword"; public static final String MIME_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; public static final String MIME_JPEG = "image/jpeg"; public static final String MIME_MP3 = "audio/mpeg"; public static final String MIME_PDF = "application/pdf"; public static final String MIME_PPT = "application/vnd.ms-powerpoint"; public static final String MIME_PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; public static final String MIME_RTF = "text/rtf"; public static final String MIME_XLS = "application/vnd.ms-excel"; public static final String MIME_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; public static final String MIME_ZIP = "application/zip"; /* * FIXME: REM - These constants should be coming from configuration and NOT * hard coded. */ public static final String DERIVATIVE_ORIGINAL = "Original"; public static final String DERIVATIVE_ORIGINAL_TAG = DERIVATIVE_ORIGINAL + "_"; public static final String DERIVATIVE_ORIGINAL_JPEG = "OriginalJpeg"; public static final String DERIVATIVE_ORIGINAL_JPEG_TAG = DERIVATIVE_ORIGINAL_JPEG + "_"; public static final String DERIVATIVE_MEDIUM = "Medium"; public static final String DERIVATIVE_MEDIUM_TAG = DERIVATIVE_MEDIUM + "_"; public static final String DERIVATIVE_SMALL = "Small"; public static final String DERIVATIVE_SMALL_TAG = DERIVATIVE_SMALL + "_"; public static final String DERIVATIVE_THUMBNAIL = "Thumbnail"; public static final String DERIVATIVE_THUMBNAIL_TAG = DERIVATIVE_THUMBNAIL + "_"; public static final String DERIVATIVE_UNKNOWN = "_UNKNOWN_DERIVATIVE_NAME_"; // // Image Dimension fields // public static final String PART_IMAGE = "digitalImage"; public static final String PART_SUMMARY = "The dimensions of a digital image -width, height, and pixel depth."; public static final String WIDTH = "width"; public static final String HEIGHT = "height"; public static final String DEPTH = "depth"; public static final String UNIT_PIXELS = "pixels"; public static final String UNIT_BITS = "bits"; // // Image Metadata schemas - These are Nuxeo defined schemas // public static final String SCHEMA_IPTC = "iptc"; public static final String SCHEMA_IMAGE_METADATA = "image_metadata"; /** * Instantiates a new nuxeo image utils. */ NuxeoBlobUtils() { // empty constructor } /* * Use this for debugging Nuxeo's PictureView class */ private static String toStringPictureView(PictureView pictureView) { StringBuffer strBuffer = new StringBuffer(); strBuffer.append("Description: " + pictureView.getDescription() + '\n'); strBuffer.append("FileName: " + pictureView.getFilename() + '\n'); strBuffer.append("Height: " + pictureView.getHeight() + '\n'); strBuffer.append("Width: " + pictureView.getWidth() + '\n'); strBuffer.append("Tag: " + pictureView.getTag() + '\n'); strBuffer.append("Title: " + pictureView.getTitle() + '\n'); return strBuffer.toString(); } // FIXME: REM - This needs to be configuration-bases and NOT hard coded! // FIXME: REM - Use MultiviewPicture adapter to get some of this information static private String getDerivativeUri(String uri, String derivativeName) { String result = DERIVATIVE_UNKNOWN; if (derivativeName.startsWith(DERIVATIVE_ORIGINAL_TAG) == true) { result = DERIVATIVE_ORIGINAL; } else if (derivativeName.startsWith(DERIVATIVE_ORIGINAL_JPEG_TAG) == true) { result = DERIVATIVE_ORIGINAL_JPEG; } else if (derivativeName.startsWith(DERIVATIVE_MEDIUM_TAG) == true) { result = DERIVATIVE_MEDIUM; } else if (derivativeName.startsWith(DERIVATIVE_SMALL_TAG) == true) { result = DERIVATIVE_SMALL; } else if (derivativeName.startsWith(DERIVATIVE_THUMBNAIL_TAG) == true) { result = DERIVATIVE_THUMBNAIL; } return uri + result + "/" + BlobInput.URI_CONTENT_PATH; } static private HashMap<String, Object> createBlobListItem(Blob blob, String uri) { HashMap<String, Object> item = new HashMap<String, Object>(); String value = blob.getEncoding(); if (value != null && !value.trim().isEmpty()) { item.put(BlobJAXBSchema.encoding, value); } value = Long.toString(blob.getLength()); if (value != null && !value.trim().isEmpty()) { item.put(BlobJAXBSchema.length, value); } value = blob.getMimeType(); if (value != null && !value.trim().isEmpty()) { item.put(BlobJAXBSchema.mimeType, value); } value = blob.getFilename(); if (value != null && !value.trim().isEmpty()) { item.put(BlobJAXBSchema.name, value); } value = getDerivativeUri(uri, blob.getFilename()); if (value != null && !value.trim().isEmpty()) { item.put(BlobJAXBSchema.uri, value); } return item; } static public String getSanizitedFilename(File srcFile) throws Exception { return getSanizitedFilename(srcFile.getName()); } /* * Valid Nuxeo file names are a subset of *nix and Windows filenames, so we need to check. */ static public String getSanizitedFilename(String fileName) throws Exception { String result = fileName; if (fileName != null && fileName.matches(NUXEO_FILENAME_VALID_STRING) == false) { String fixedString = fileName.replaceAll(NUXEO_FILENAME_BAD_CHARS, "_"); // Replace "bad" chars with underscore character if (fixedString.matches(NUXEO_FILENAME_VALID_STRING) == true) { result = fixedString; } else { String errMsg = String.format("\tSorry, the sanizited string '%s' is still bad.", fixedString); throw new Exception(errMsg); } } if (result != null && logger.isDebugEnabled() == true) { if (result.equals(fileName) == false) { logger.debug(String.format("The file name '%s' was sanizitized to '%s'.", fileName, result)); } } return result; } static public CommonList getBlobDerivatives(CoreSessionInterface repoSession, String repositoryId, List<ListResultField> resultsFields, String uri) throws Exception { CommonList commonList = new CommonList(); int nFields = resultsFields.size() + 2; String fields[] = new String[nFields];// FIXME: REM - Patrick needs to fix this hack. It is a "common list" issue fields[0] = "csid"; fields[1] = "uri"; for (int i = 2; i < nFields; i++) { ListResultField field = resultsFields.get(i - 2); fields[i] = field.getElement(); } commonList.setFieldsReturned(fields); IdRef documentRef = new IdRef(repositoryId); DocumentModel documentModel = repoSession.getDocument(documentRef); DocumentBlobHolder docBlobHolder = (DocumentBlobHolder) documentModel.getAdapter(BlobHolder.class); List<Blob> docBlobs = docBlobHolder.getBlobs(); // List<BlobListItem> blobListItems = result.getBlobListItem(); HashMap<String, Object> item = null; for (Blob blob : docBlobs) { if (blob != null) { item = createBlobListItem(blob, uri); commonList.addItem(item); } } return commonList; } /* * [dublincore, uid, picture, iptc, common, image_metadata] */ static private Map<String, Object> getMetadata(Blob nuxeoBlob) throws Exception { ImagingService service = Framework.getService(ImagingService.class); Map<String, Object> metadataMap = service.getImageMetadata(nuxeoBlob); return metadataMap; } private static String[] imageTypes = { "jpeg", "bmp", "gif", "png", "tiff", "octet-stream" }; static private boolean isImageMedia(Blob nuxeoBlob) { boolean result = false; String mimeType = nuxeoBlob.getMimeType(); if (mimeType != null) { mimeType = mimeType.toLowerCase().trim(); String[] parts = mimeType.split("/"); // split strings like "application/xml" into an array of two strings if (parts.length == 2) { for (String type : imageTypes) { if (parts[1].equalsIgnoreCase(type)) { result = true; break; } } } } return result; } static private MeasuredPartGroupList getDimensions(DocumentModel documentModel, Blob nuxeoBlob) { MeasuredPartGroupList result = null; if (isImageMedia(nuxeoBlob) == true) try { ImagingService service = Framework.getService(ImagingService.class); ImageInfo imageInfo = service.getImageInfo(nuxeoBlob); Map<String, Object> metadataMap = getMetadata(nuxeoBlob); if (imageInfo != null) { // // Create a timestamp to add to all the image's dimensions // String valueDate = GregorianCalendarDateTimeUtils.timestampUTC(); result = new MeasuredPartGroupList(); List<MeasuredPartGroup> measuredPartGroupList = (result).getMeasuredPartGroup(); // // Create a new measured part for the "image" // MeasuredPartGroup mpGroup = new MeasuredPartGroup(); mpGroup.setMeasuredPart(PART_IMAGE); mpGroup.setDimensionSummary(PART_SUMMARY); mpGroup.setDimensionSubGroupList(new DimensionSubGroupList()); List<DimensionSubGroup> dimensionSubGroupList = mpGroup.getDimensionSubGroupList() .getDimensionSubGroup(); // // Set the width // DimensionSubGroup widthDimension = new DimensionSubGroup(); widthDimension.setDimension(WIDTH); widthDimension.setMeasurementUnit(UNIT_PIXELS); widthDimension.setValue(intToBigDecimal(imageInfo.getWidth())); widthDimension.setValueDate(valueDate); dimensionSubGroupList.add(widthDimension); // // Set the height // DimensionSubGroup heightDimension = new DimensionSubGroup(); heightDimension.setDimension(HEIGHT); heightDimension.setMeasurementUnit(UNIT_PIXELS); heightDimension.setValue(intToBigDecimal(imageInfo.getHeight())); heightDimension.setValueDate(valueDate); dimensionSubGroupList.add(heightDimension); // // Set the depth // DimensionSubGroup depthDimension = new DimensionSubGroup(); depthDimension.setDimension(DEPTH); depthDimension.setMeasurementUnit(UNIT_BITS); depthDimension.setValue(intToBigDecimal(imageInfo.getDepth())); depthDimension.setValueDate(valueDate); dimensionSubGroupList.add(depthDimension); // // Now set out result // measuredPartGroupList.add(mpGroup); } else { if (logger.isWarnEnabled() == true) { logger.warn( "Could not synthesize a dimension list of the blob: " + documentModel.getName()); } } } catch (Exception e) { logger.warn("Could not extract image information for blob: " + documentModel.getName(), e); } return result; } // FIXME: Add error checking here, as none of these calls return an // Exception static private BigDecimal intToBigDecimal(int i) { BigInteger bigint = BigInteger.valueOf(i); BigDecimal bigdec = new BigDecimal(bigint); return bigdec; } static private BlobsCommon createBlobsCommon(DocumentModel documentModel, Blob nuxeoBlob) { return createBlobsCommon(documentModel, nuxeoBlob, false); } static private BlobsCommon createBlobsCommon(DocumentModel documentModel, Blob nuxeoBlob, Boolean getContentFlag) { BlobsCommon result = new BlobsCommon(); if (documentModel != null && nuxeoBlob != null) { result.setMimeType(nuxeoBlob.getMimeType()); result.setName(nuxeoBlob.getFilename()); result.setLength(Long.toString(nuxeoBlob.getLength())); result.setRepositoryId(documentModel.getId()); // // If getContentFlag is true then we're being asked for the blob's content, so we don't // need the measurement info. Getting the measurement info requires a call to Nuxeo which in turn // calls ImageMagick. // if (getContentFlag.booleanValue() == false) { MeasuredPartGroupList measuredPartGroupList = getDimensions(documentModel, nuxeoBlob); if (measuredPartGroupList != null) { result.setMeasuredPartGroupList(measuredPartGroupList); } } // Check to see if a thumbnail preview was created by Nuxeo if (documentModel.hasFacet(ThumbnailConstants.THUMBNAIL_FACET)) { String errorMsg = null; String thumbnailName = null; try { thumbnailName = (String) documentModel.getProperty(ThumbnailConstants.THUMBNAIL_SCHEMA_NAME, ThumbnailConstants.THUMBNAIL_FILENAME_PROPERTY_NAME); Blob thumbnailBlob = (Blob) documentModel.getProperty(ThumbnailConstants.THUMBNAIL_SCHEMA_NAME, ThumbnailConstants.THUMBNAIL_PROPERTY_NAME); } catch (ClientException e) { errorMsg = "Could not extract the name of the thumbnail preview image file."; if (logger.isDebugEnabled()) { logger.debug(errorMsg, e); } } if (errorMsg == null) { logger.info("A thumbnail preview was created for this document blob: " + thumbnailName); } else { logger.warn(errorMsg); } } } return result; } static private Blob checkMimeType(Blob blob, String fullname) throws ClientException { final String mimeType = blob.getMimeType(); if (mimeType != null && !mimeType.equals("application/octet-stream") && !mimeType.equals("application/octetstream")) { return blob; } String filename = FileManagerUtils.fetchFileName(fullname); try { blob = getMimeService().updateMimetype(blob, filename); } catch (MimetypeDetectionException e) { throw new ClientException(e); } return blob; } /** * Gets the type service. Not in use, but please keep for future reference * * @return the type service * @throws ClientException * the client exception */ private static TypeManager getTypeService() throws ClientException { TypeManager typeService = null; try { typeService = Framework.getService(TypeManager.class); } catch (Exception e) { throw new ClientException(e); } return typeService; } private static Blob createNuxeoFileBasedBlob(File file) throws Exception { return new FileBlob(file); } /** * Gets Nuxeo's file manager service. * * @return the file manager service * @throws ClientException * the client exception */ private static FileManager getFileManager() throws ClientException { FileManager result = null; try { result = Framework.getService(FileManager.class); } catch (Exception e) { String msg = "Unable to get Nuxeo's FileManager service."; logger.error(msg, e); throw new ClientException("msg", e); } return result; } /** * Gets Nuxeo's file manager service. * * @return the file manager service * @throws ClientException * the client exception */ private static FileManagerService getFileManagerService() throws ClientException { FileManagerService result = null; try { result = (FileManagerService) getFileManager(); } catch (Exception e) { String msg = "Unable to get Nuxeo's FileManager service."; logger.error(msg, e); throw new ClientException("msg", e); } return result; } static private CoreSessionInterface getRepositorySession(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, RepositoryClient<PoxPayloadIn, PoxPayloadOut> repositoryClient) { CoreSessionInterface result = null; RepositoryJavaClientImpl nuxeoClient = (RepositoryJavaClientImpl) repositoryClient; try { result = nuxeoClient.getRepositorySession(ctx); } catch (Exception e) { logger.error("Could not get a repository session to the Nuxeo repository", e); } return result; } static private void releaseRepositorySession(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, RepositoryClient<PoxPayloadIn, PoxPayloadOut> repositoryClient, CoreSessionInterface repoSession) throws TransactionException { RepositoryJavaClientImpl nuxeoClient = (RepositoryJavaClientImpl) repositoryClient; nuxeoClient.releaseRepositorySession(ctx, repoSession); } static private MimetypeRegistry getMimeService() throws ClientException { MimetypeRegistry result = null; try { result = Framework.getService(MimetypeRegistry.class); } catch (Exception e) { throw new ClientException(e); } return result; } private static DocumentModel createDocumentFromBlob(CoreSessionInterface repoSession, Blob inputStreamBlob, String blobLocation, boolean overwrite, String blobName, boolean useNuxeoAdaptors) throws Exception { DocumentModel result = null; if (useNuxeoAdaptors == true) { // // Use Nuxeo's high-level create method which looks for plugin adapters that match the MIME type. For example, // for image blobs, Nuxeo's file manager will pick a special image plugin that will automatically generate // image derivatives. // result = getFileManager().createDocumentFromBlob(repoSession.getCoreSession(), inputStreamBlob, blobLocation, overwrite, blobName); } else { // // User Nuxeo's default file importer/adapter explicitly. This avoids specialized functionality from happening like // image derivative creation. // String digestAlgorithm = getFileManager().getDigestAlgorithm(); // Only call this because we seem to need some way of initializing Nuxeo's FileManager with a call. FileManagerService fileManagerService = getFileManagerService(); inputStreamBlob = checkMimeType(inputStreamBlob, blobName); FileImporter defaultFileImporter = fileManagerService.getPluginByName("DefaultFileImporter"); result = defaultFileImporter.create(repoSession.getCoreSession(), inputStreamBlob, blobLocation, overwrite, blobName, getTypeService()); } return result; } static public BlobsCommon createBlobInRepository(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, RepositoryClient<PoxPayloadIn, PoxPayloadOut> repositoryClient, InputStream inputStream, String blobName, boolean useNuxeoAdaptors) throws TransactionException { BlobsCommon result = null; boolean repoSessionCleanup = false; CoreSessionInterface repoSession = (CoreSessionInterface) ctx.getCurrentRepositorySession(); if (repoSession == null) { repoSession = getRepositorySession(ctx, repositoryClient); repoSessionCleanup = true; } try { // We'll store the blob inside the workspace directory of the calling service String nuxeoWspaceId = ctx.getRepositoryWorkspaceId(); DocumentRef nuxeoWspace = new IdRef(nuxeoWspaceId); DocumentModel blobLocation = repoSession.getDocument(nuxeoWspace); Blob inputStreamBlob = new InputStreamBlob(inputStream); DocumentModel documentModel = createDocumentFromBlob(repoSession, inputStreamBlob, blobLocation.getPathAsString(), false, blobName, useNuxeoAdaptors); result = createBlobsCommon(documentModel, inputStreamBlob); // Now create the metadata about the Nuxeo blob document } catch (Exception e) { result = null; logger.error("Could not create new Nuxeo blob document.", e); //FIXME: REM - This should probably be re-throwing the exception? } finally { if (repoSessionCleanup == true) { releaseRepositorySession(ctx, repositoryClient, repoSession); } } return result; } /** * Creates the picture. * * @param ctx * the ctx * @param repoSession * the repo session * @param filePath * the file path * @return the string * @throws Exception */ public static BlobsCommon createBlobInRepository(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, CoreSessionInterface repoSession, BlobInput blobInput, boolean purgeOriginal, boolean useNuxeoAdaptors) throws Exception { BlobsCommon result = null; File originalFile = blobInput.getBlobFile(); File targetFile = originalFile; try { // We'll store the blob inside the workspace directory of the calling service String nuxeoWspaceId = ctx.getRepositoryWorkspaceId(); DocumentRef nuxeoWspace = new IdRef(nuxeoWspaceId); DocumentModel wspaceDoc = repoSession.getDocument(nuxeoWspace); // // If the original file's name contains "illegal" characters, then we create a copy of the file to give Nuxeo. // String sanitizedName = NuxeoBlobUtils.getSanizitedFilename(originalFile); if (sanitizedName.equals(originalFile.getName()) == false) { targetFile = FileUtilities.createTmpFile(originalFile, sanitizedName); if (logger.isDebugEnabled() == true) { logger.debug(String.format( "The file '%s''s name has characters that Nuxeo can't deal with. Rather than renaming the file, we created a new temp file at '%s'", originalFile.getName(), targetFile.getAbsolutePath())); } } result = createBlobInRepository(repoSession, wspaceDoc, purgeOriginal, targetFile, null, // MIME type useNuxeoAdaptors); // // Make sure we're using the original file name in our BlobsCommon instance. If the original file's name // contained illegal characters, then we created and handed a copy of the file to Nuxeo. We don't want the // copy's file name stored in the BlobsCommon instance, we want the original file name instead. // if (targetFile.equals(originalFile) == false) { result.setName(originalFile.getName()); } } catch (Exception e) { logger.error("Could not create image blob.", e); throw e; } finally { // // If we created a temp file then we should delete it. // if (targetFile.equals(originalFile) == false) { if (targetFile.delete() == false) { logger.warn(String.format("Attempt to delete temporary file '%s' failed.", targetFile.getAbsolutePath())); } } } return result; } /* * Find out if this document's blob/file-contents are allowed to be purged. For instance, we currently * only want to allow the purging the contents of Nuxeo "Picture" documents. */ static private boolean isPurgeAllowed(DocumentModel docModel) { boolean result = false; if (docModel.hasFacet(ImagingDocumentConstants.PICTURE_FACET) == true) { result = true; // Yes, delete/purge the original content } return result; } /** * Creates the image blob. * * @param nuxeoSession * the nuxeo session * @param blobLocation * the blob location * @param file * the file * @param fileName * the file name * @param mimeType * the mime type * @return the string */ static private BlobsCommon createBlobInRepository(CoreSessionInterface nuxeoSession, DocumentModel blobLocation, boolean purgeOriginal, File file, String mimeType, boolean useNuxeoAdaptors) { BlobsCommon result = null; try { Blob fileBlob = createNuxeoFileBasedBlob(file); DocumentModel documentModel = createDocumentFromBlob(nuxeoSession, fileBlob, blobLocation.getPathAsString(), false, file.getName(), useNuxeoAdaptors); result = createBlobsCommon(documentModel, fileBlob); // Now create our metadata resource document // If the requester wants us to generate only derivatives, we need to purge/clear the original image file if (purgeOriginal == true && isPurgeAllowed(documentModel) == true) { // Empty the document model's "content" property -this does not delete the actual file/blob //documentModel.setPropertyValue("file:content", (Serializable) null); if (documentModel.hasFacet(ImagingDocumentConstants.PICTURE_FACET)) { // // We're going to use the "source" property field of the Dublin Core schema as a way of indicating to // our event listener (See UpdateImageDerivatives.java) that the original image needs to be // purged. The "source" property does not seem to be used by Nuxeo for Picture documents as of v6.0. However, this might // break in future releases of Nuxeo, so we'll emit a warning to the logs if we find a value in this // property. // String source = (String) documentModel.getProperty(CommonAPI.NUXEO_DUBLINCORE_SCHEMANAME, CommonAPI.NUXEO_DUBLINCORE_SOURCE); if (source != null) { logger.warn(String.format( "The Nuxeo dublin core property '%s' is set to '%s'. We expected it to be empty. See JIRA issue CSPACE-6679 for details.", CommonAPI.NUXEO_DUBLINCORE_SOURCE, source)); } documentModel.setProperty(CommonAPI.NUXEO_DUBLINCORE_SCHEMANAME, CommonAPI.NUXEO_DUBLINCORE_SOURCE, CommonAPI.URL_SOURCED_PICTURE); // Now with no content, the derivative listener wants to update the derivatives. So to // prevent the listener, we remove the "Picture" facet from the document //NuxeoUtils.removeFacet(documentModel, ImagingDocumentConstants.PICTURE_FACET); // Removing this facet ensures the original derivatives are unchanged. // Now that we've emptied the document model's content field, we can add back the Picture facet //NuxeoUtils.addFacet(documentModel, ImagingDocumentConstants.PICTURE_FACET); } //nuxeoSession.saveDocument(documentModel); // Next, we need to remove the actual file from Nuxeo's data directory // Blob blob = docBlobHolder.getBlob(); // if(blob == null) { // logger.error("Could not get blob for original image. Trying to delete original for: {}", // file.getName()); // } else { // boolean deleteSuccess = NuxeoUtils.deleteFileOfBlob(docBlobHolder.getBlob()); // } } // // Persist/save any changes. // nuxeoSession.saveDocument(documentModel); nuxeoSession.save(); } catch (Exception e) { result = null; logger.error("Could not create new Nuxeo blob document.", e); //FIXME: REM - This should probably be re-throwing the exception? } return result; } // /* // * This is an alternate approach to getting information about an image // * and its corresponding derivatives. // */ // // MultiviewPictureAdapter multiviewPictureAdapter = // documentModel.getAdapter(MultiviewPictureAdapter.class); // MultiviewPictureAdapterFactory multiviewPictureAdapterFactory = new // MultiviewPictureAdapterFactory(); // MultiviewPictureAdapter multiviewPictureAdapter = // (MultiviewPictureAdapter)multiviewPictureAdapterFactory.getAdapter(documentModel, // null); // if (multiviewPictureAdapter != null) { // PictureView[] pictureViewArray = multiviewPictureAdapter.getViews(); // for (PictureView pictureView : pictureViewArray) { // if (logger.isDebugEnabled() == true) { // logger.debug("-------------------------------------"); // logger.debug(toStringPictureView(pictureView)); // } // } // } public static InputStream getResource(String resourceName) { InputStream result = null; try { result = ServiceMain.getInstance().getResourceAsStream(resourceName); } catch (FileNotFoundException e) { logger.error("Missing Services resource: " + resourceName, e); } return result; } static public BlobOutput getBlobOutput(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, RepositoryClient<PoxPayloadIn, PoxPayloadOut> repositoryClient, String repositoryId, StringBuffer outMimeType) throws TransactionException { BlobOutput result = null; boolean repoSessionCleanup = false; CoreSessionInterface repoSession = (CoreSessionInterface) ctx.getCurrentRepositorySession(); if (repoSession == null) { repoSession = getRepositorySession(ctx, repositoryClient); repoSessionCleanup = true; } try { result = getBlobOutput(ctx, repoSession, repositoryId, null, true, outMimeType); if (outMimeType.length() == 0) { BlobsCommon blobsCommon = result.getBlobsCommon(); String mimeType = blobsCommon.getMimeType(); outMimeType.append(mimeType); } } finally { if (repoSessionCleanup == true) { releaseRepositorySession(ctx, repositoryClient, repoSession); } } return result; } // // If the blob is not too big, we return a ByteArrayInputStream. Otherwise, we return Nuxeo's InputStream // which is usually a FileInputStream. // static private InputStream getInputStream(BlobsCommon blobsCommon, Blob blob) { InputStream result = null; if (blob != null) { try { InputStream blobStream = blob.getStream(); // By default, the result will be whatever stream Nuxeo returns to us. int blobSize = blobsCommon.getLength() != null ? Integer.parseInt(blobsCommon.getLength()) : 0; if (blobSize > 0 && blobSize < MAX_IMAGE_BUFFER) { byte[] bytes = IOUtils.toByteArray(blobStream); blobStream.close(); // Close the InputStream that we got from Nuxeo since it's usually a FileInputStream -we definitely want FileInputStreams closed. result = new ByteArrayInputStream(bytes); } else { result = blobStream; // The blob is too large to put into a ByteArrayStream. } } catch (Exception e) { logger.error( String.format("Error getting the InputStream content for file %s.", blobsCommon.getName()), e); if (result != null) { try { result.close(); result = null; } catch (Exception x) { logger.debug(String.format("Exception encountered during InputStream cleanup of file %s", blobsCommon.getName()), x); } } } } return result; } /** * Gets the image. * * @param repoSession * the repo session * @param repositoryId * the repository id * @param derivativeTerm * the derivative term * @return the image */ static public BlobOutput getBlobOutput(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, CoreSessionInterface repoSession, String repositoryId, String derivativeTerm, Boolean getContentFlag, StringBuffer outMimeType) { BlobOutput result = new BlobOutput(); boolean isNonImageDerivative = false; if (repositoryId != null && repositoryId.isEmpty() == false) try { IdRef documentRef = new IdRef(repositoryId); DocumentModel documentModel = repoSession.getDocument(documentRef); Blob docBlob = null; DocumentBlobHolder docBlobHolder = (DocumentBlobHolder) documentModel.getAdapter(BlobHolder.class); if (docBlobHolder instanceof PictureBlobHolder) { // if it is a PictureDocument then it has these // Nuxeo schemas: [dublincore, uid, picture, iptc, common, image_metadata] // // Need to add the "MultiviewPictureAdapter" support here to // get the view data, see above. // PictureBlobHolder pictureBlobHolder = (PictureBlobHolder) docBlobHolder; if (derivativeTerm != null) { docBlob = pictureBlobHolder.getBlob(derivativeTerm); // Nuxeo derivatives are all JPEG outMimeType.append(MIME_JPEG); // All Nuxeo image derivatives are JPEG images. } else { docBlob = pictureBlobHolder.getBlob(); } } else { docBlob = docBlobHolder.getBlob(); if (derivativeTerm != null) { // If its a derivative request on a non-image blob, then return just a document image thumbnail isNonImageDerivative = true; } } if (logger.isWarnEnabled()) { if (docBlob == null) { String msg = String.format( "Could not retrieve document blob for Nuxeo document ID = '%s' CSID = '%s'", repositoryId, NuxeoUtils.getCsid(documentModel)); logger.warn(msg); } } // // Create the result instance that will contain the blob metadata // and an InputStream with the bits if the 'getContentFlag' is // set. // BlobsCommon blobsCommon = createBlobsCommon(documentModel, docBlob, getContentFlag); result.setBlobsCommon(blobsCommon); if (getContentFlag == true) { InputStream remoteStream = null; if (isNonImageDerivative == false) { //remoteStream = docBlob.getStream(); remoteStream = getInputStream(blobsCommon, docBlob); // CSPACE-6110 - For small files, return a byte array instead of a file stream } else { // If its a derivative request on a non-image blob, then return just a document image thumbnail String docBlobMimetype = docBlob.getMimeType(); switch (docBlobMimetype) { case MIME_CSV: remoteStream = getResource(DOCUMENT_PLACEHOLDER_CSV); break; case MIME_DOC: remoteStream = getResource(DOCUMENT_PLACEHOLDER_DOC); break; case MIME_DOCX: remoteStream = getResource(DOCUMENT_PLACEHOLDER_DOCX); break; case MIME_MP3: remoteStream = getResource(DOCUMENT_PLACEHOLDER_MP3); break; case MIME_PDF: remoteStream = getResource(DOCUMENT_PLACEHOLDER_PDF); break; case MIME_PPT: remoteStream = getResource(DOCUMENT_PLACEHOLDER_PPT); break; case MIME_PPTX: remoteStream = getResource(DOCUMENT_PLACEHOLDER_PPTX); break; case MIME_RTF: remoteStream = getResource(DOCUMENT_PLACEHOLDER_RTF); break; case MIME_XLS: remoteStream = getResource(DOCUMENT_PLACEHOLDER_XLS); break; case MIME_XLSX: remoteStream = getResource(DOCUMENT_PLACEHOLDER_XLSX); break; case MIME_ZIP: remoteStream = getResource(DOCUMENT_PLACEHOLDER_ZIP); break; default: remoteStream = getResource(DOCUMENT_PLACEHOLDER_IMAGE); } outMimeType.append(MIME_JPEG); } // BufferedInputStream bufferedInputStream = new BufferedInputStream( // remoteStream); // result.setBlobInputStream(bufferedInputStream); result.setBlobInputStream(remoteStream); } } catch (Exception e) { if (logger.isErrorEnabled() == true) { logger.error(e.getMessage(), e); } result = null; } return result; } } /* * Notes and code snippets about Nuxeo's support for binaries and image * documents. */ /* * * * MultiviewPictureAdapter org.nuxeo.ecm.platform.picture.api.adapters * PictureResourceAdapter pictureResourceAdapter = (PictureResourceAdapter) * documentModel.getAdapter(PictureResourceAdapter.class); String thumbnailPath * = pictureResourceAdapter.getViewXPath("Thumbnail"); * * Map<String,Serializable> blobHolderProps = docBlobHolder.getProperties(); * String filePath = docBlobHolder.getFilePath(); List<Blob> docBlobs = * docBlobHolder.getBlobs(); * * stream = new FileInputStream(fileUploadHolder.getTempFile()); * * public String addFile(InputStream fileUpload, String fileName) fileName = * FileUtils.getCleanFileName(fileName); DocumentModel currentDocument = * navigationContext.getCurrentDocument(); String path = * currentDocument.getPathAsString(); Blob blob = * FileUtils.createSerializableBlob(fileUpload, fileName, null); * * DocumentModel createdDoc = getFileManagerService().createDocumentFromBlob( * documentManager, blob, path, true, fileName); * eventManager.raiseEventsOnDocumentSelected(createdDoc); * * protected FileManager fileManager; * * protected FileManager getFileManagerService() throws ClientException { if * (fileManager == null) { try { fileManager = * Framework.getService(FileManager.class); } catch (Exception e) { * log.error("Unable to get FileManager service ", e); throw new * ClientException("Unable to get FileManager service ", e); } } return * fileManager; } */ /* * RepositoryService repositoryService = (RepositoryService) * Framework.getRuntime().getComponent( RepositoryService.NAME); * RepositoryManager repositoryManager = * repositoryService.getRepositoryManager(); RepositoryDescriptor descriptor = * repositoryManager.getDescriptor(repositoryName); DefaultBinaryManager * binaryManager = new DefaultBinaryManager( * SQLRepository.getDescriptor(descriptor))); * * File storageDir = binaryManager.getStorageDir(); SQLBlob blob = (SQLBlob) * doc.getPropertyValue("schema:blobField"); File file = * binaryManager.getFileForDigest( blob.getBinary().getDigest(), false); */ /* * RepositoryInstance.getStreamURI() * * String getStreamURI(String blobPropertyId) throws ClientException * * Returns an URI identifying the stream given the blob property id. This method * should be used by a client to download the data of a blob property. * * The blob is fetched from the repository and the blob stream is registered * against the streaming service so the stream will be available remotely * through stream service API. * * After the client has called this method, it will be able to download the * stream using streaming server API. * * Returns: an URI identifying the remote stream Throws: ClientException */ /* * A blob contains usually large data. * * Document fields holding Blob data are by default fetched in a lazy manner. * * A Blob object hides the data source and it also describes data properties * like the encoding or mime-type. * * The encoding is used to decode Unicode text content that was stored in an * encoded form. If not encoding is specified, the default java encoding is * used. The encoding is ignored for binary content. * * When retrieving the content from a document, it will be returned as source * content instead of returning the content bytes. * * The same is true when setting the content for a document: you set a content * source and not directly the content bytes. Ex: * * File file = new File("/tmp/index.html"); FileBlob fb = new FileBlob(file); * fb.setMimeType("text/html"); fb.setEncoding("UTF-8"); // this specifies that * content bytes will be stored as UTF-8 document.setProperty("file", "content", * fb); * * * Then you may want to retrieve the content as follow: * * Blob blob = document.getProperty("file:content"); htmlDoc = blob.getString(); * // the content is decoded from UTF-8 into a java string */