Java tutorial
/* * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl-2.1.html * * This library 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. * * Contributors: * Thibaud Arguillere */ package org.nuxeo.binary.metadata.test; import static org.junit.Assert.*; import java.io.File; import java.io.StringReader; import java.util.HashMap; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.nuxeo.binary.metadata.ExternalTools.TOOL; import org.nuxeo.binary.metadata.ExternalTools.ToolAvailability; import org.nuxeo.binary.metadata.operations.ExtractBinaryMetadataInDocumentOp; import org.nuxeo.binary.metadata.operations.ExtractXMPFromBlobOp; import org.nuxeo.binary.metadata.MetadataReader; import org.nuxeo.binary.metadata.XYResolutionDPI; import org.nuxeo.binary.metadata.BinaryMetadataConstants.*; import org.nuxeo.common.utils.FileUtils; import org.nuxeo.ecm.automation.AutomationService; import org.nuxeo.ecm.automation.OperationChain; import org.nuxeo.ecm.automation.OperationContext; import org.nuxeo.ecm.automation.core.util.Properties; import org.nuxeo.ecm.automation.test.EmbeddedAutomationServerFeature; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.impl.blob.FileBlob; import org.nuxeo.ecm.core.test.CoreFeature; import org.nuxeo.ecm.platform.test.PlatformFeature; import org.nuxeo.runtime.test.runner.Deploy; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.w3c.dom.Document; import org.xml.sax.InputSource; import com.google.inject.Inject; @RunWith(FeaturesRunner.class) @Features({ PlatformFeature.class, CoreFeature.class, EmbeddedAutomationServerFeature.class }) @Deploy({ "org.nuxeo.ecm.platform.picture.core", "nuxeo-binary-metadata", "org.nuxeo.ecm.platform.commandline.executor" }) public class MetadataReaderTest { protected static final Log log = LogFactory.getLog(MetadataReaderTest.class); protected static final String IMAGE_GIF = "images/a.gif"; protected static final String IMAGE_JPEG = "images/a.jpg"; protected static final String IMAGE_PNG = "images/a.png"; protected static final String IMAGE_TIF = "images/a.tif"; protected static final String NUXEO_LOGO = "images/Nuxeo.png"; protected static final String WITH_XMP = "images/with-xmp.jpg"; protected File filePNG; protected File fileGIF; protected File fileTIF; protected File fileJPEG; protected DocumentModel parentOfTestDocs; protected DocumentModel docPNG; protected DocumentModel docGIF; protected DocumentModel docTIF; protected DocumentModel docJPEG; protected static int graphicsMagickCheck = -1; protected static int exifToolCheck = -1; @Inject CoreSession coreSession; @Inject AutomationService service; protected void doLog(String what) { System.out.println(what); } // Not sure it's the best way to get the current method name, but at least // it works protected String getCurrentMethodName(RuntimeException e) { StackTraceElement currentElement = e.getStackTrace()[0]; return currentElement.getMethodName(); } @Before public void setUp() { // Setup documents if needed, etc. filePNG = FileUtils.getResourceFileFromContext(IMAGE_PNG); fileGIF = FileUtils.getResourceFileFromContext(IMAGE_GIF); fileTIF = FileUtils.getResourceFileFromContext(IMAGE_TIF); fileJPEG = FileUtils.getResourceFileFromContext(IMAGE_JPEG); // Cleanup repo if needed and create the Picture documents // coreSession.removeChildren(coreSession.getRootDocument().getRef()); parentOfTestDocs = coreSession.createDocumentModel("/", "test-pictures", "Folder"); parentOfTestDocs.setPropertyValue("dc:title", "test-pictures"); parentOfTestDocs = coreSession.createDocument(parentOfTestDocs); parentOfTestDocs = coreSession.saveDocument(parentOfTestDocs); docPNG = createPictureDocument(filePNG); docGIF = createPictureDocument(fileGIF); docTIF = createPictureDocument(fileTIF); docJPEG = createPictureDocument(fileJPEG); coreSession.save(); } @After public void cleanup() { coreSession.removeDocument(parentOfTestDocs.getRef()); coreSession.save(); } protected DocumentModel createPictureDocument(File inFile) { DocumentModel pictDoc = coreSession.createDocumentModel(parentOfTestDocs.getPathAsString(), inFile.getName(), "Picture"); pictDoc.setPropertyValue("dc:title", inFile.getName()); pictDoc.setPropertyValue("file:content", new FileBlob(inFile)); return coreSession.createDocument(pictDoc); } protected void checkImagesValues_ImageMagick(File inWhichOne, String inWidth, String inHeight, String inColorspace, String inResolution, String inUnits, int xDPI, int yDPI) throws Exception { String theAssertMessage = "getMetadata() for " + inWhichOne.getName(); MetadataReader mdr = new MetadataReader(inWhichOne.getAbsolutePath()); String[] keysStr = { KEYS.WIDTH, KEYS.HEIGHT, KEYS.COLORSPACE, KEYS.RESOLUTION, KEYS.UNITS }; HashMap<String, String> result = mdr.readMetadata(keysStr); assertNotNull(theAssertMessage, result); assertEquals(theAssertMessage, inWidth, result.get(KEYS.WIDTH)); assertEquals(theAssertMessage, inHeight, result.get(KEYS.HEIGHT)); assertEquals(theAssertMessage, inColorspace, result.get(KEYS.COLORSPACE)); assertEquals(theAssertMessage, inResolution, result.get(KEYS.RESOLUTION)); assertEquals(theAssertMessage, inUnits, result.get(KEYS.UNITS)); // Resolution needs extra work XYResolutionDPI dpi = new XYResolutionDPI(result.get(KEYS.RESOLUTION), result.get(KEYS.UNITS)); assertEquals(theAssertMessage, xDPI, dpi.getX()); assertEquals(theAssertMessage, yDPI, dpi.getY()); } @Test public void testImages() throws Exception { doLog(getCurrentMethodName(new RuntimeException()) + "..."); checkImagesValues_ImageMagick(filePNG, "100", "100", "sRGB", "37.79x37.79", "PixelsPerCentimeter", 96, 96); checkImagesValues_ImageMagick(fileGIF, "328", "331", "sRGB", "72x72", "Undefined", 72, 72); checkImagesValues_ImageMagick(fileTIF, "456", "180", "sRGB", "72x72", "PixelsPerInch", 72, 72); checkImagesValues_ImageMagick(fileJPEG, "1597", "232", "sRGB", "96x96", "PixelsPerInch", 96, 96); } @Test public void testGetAllMetadata_ImageMagick() throws Exception { doLog(getCurrentMethodName(new RuntimeException()) + "..."); MetadataReader mdr = new MetadataReader(filePNG.getAbsolutePath()); // ================================================== // Test with metadata returned as a String // ================================================== String all = mdr.readAllMetadata(); assertTrue(all != null); assertTrue(!all.isEmpty()); // Just for an example: assertTrue(all.indexOf("Format=PNG (Portable Network Graphics)") > -1); assertTrue(all.indexOf("Channel depth:green=8-bit") > -1); // ================================================== // Test with the result as a hashmap // ================================================== HashMap<String, String> allInHashMap = mdr.readMetadata(null); assertTrue(allInHashMap.containsKey("Format")); assertEquals("PNG (Portable Network Graphics)", allInHashMap.get("Format")); assertTrue(allInHashMap.containsKey("Channel depth:green")); assertEquals("8-bit", allInHashMap.get("Channel depth:green")); } @Test public void testXYResolutionDPI() throws Exception { doLog(getCurrentMethodName(new RuntimeException()) + "..."); XYResolutionDPI xyDPI = new XYResolutionDPI("180x180", RESOLUTION_UNITS.PIXELS_PER_INCH); assertEquals(180, xyDPI.getX()); assertEquals(180, xyDPI.getY()); xyDPI = new XYResolutionDPI("37.89x37.89", RESOLUTION_UNITS.PIXELS_PER_CENTIMETER); assertEquals(96, xyDPI.getX()); assertEquals(96, xyDPI.getY()); xyDPI = new XYResolutionDPI("72x72", RESOLUTION_UNITS.UNDEFINED); assertEquals(72, xyDPI.getX()); assertEquals(72, xyDPI.getY()); xyDPI = new XYResolutionDPI("", RESOLUTION_UNITS.PIXELS_PER_INCH); assertEquals(0, xyDPI.getX()); assertEquals(0, xyDPI.getY()); } @Test public void ExtractBinaryMetadataInDocumentOpShouldFail() throws Exception { doLog(getCurrentMethodName(new RuntimeException()) + "..."); // ==================== Not passing properties OperationContext ctx = new OperationContext(coreSession); assertNotNull(ctx); ctx.setInput(docPNG); OperationChain chain = new OperationChain("testChain"); chain.add(ExtractBinaryMetadataInDocumentOp.ID); try { service.run(ctx, chain); assertTrue("The operation should have fail when no properties are passed", false); } catch (Exception e) { // Possibly, test it's a TraceException } // ==================== Invalid xpath ctx.setInput(docPNG); chain = new OperationChain("testChain"); Properties props = new Properties(); props.put("dc:description", "all"); chain.add(ExtractBinaryMetadataInDocumentOp.ID).set("properties", props).set("xpath", "blahblah:blahblah"); try { service.run(ctx, chain); assertTrue("The operation should have fail when an invalid path is passed", false); } catch (Exception e) { // Possibly, test it's a PropertyNotFoundException } } @Test public void testExtractBinaryMetadataInDocumentOp() throws Exception { doLog(getCurrentMethodName(new RuntimeException()) + "..."); OperationContext ctx = new OperationContext(coreSession); assertNotNull(ctx); // ======================================== // ASK FOR ALL PROPERTIES // ======================================== String changeToken = docPNG.getChangeToken(); ctx.setInput(docPNG); OperationChain chain = new OperationChain("testChain"); // Let xpath and save the default values Properties props = new Properties(); props.put("dc:description", "all"); chain.add(ExtractBinaryMetadataInDocumentOp.ID).set("properties", props); service.run(ctx, chain); // Check the doc was modified assertNotSame(changeToken, docPNG.getChangeToken()); // Check value for this PNG String all = (String) docPNG.getPropertyValue("dc:description"); assertTrue(all != null && !all.isEmpty()); // Possibly, check some values are available assertTrue(all.indexOf("Page geometry") > -1); assertTrue(all.indexOf("Units") > -1); // ======================================== // NO SAVE MUST, WELL, NOT SAVE // ======================================== changeToken = docPNG.getChangeToken(); ctx.setInput(docPNG); chain = new OperationChain("testChain"); props = new Properties(); props.put("dc:description", "all"); chain.add(ExtractBinaryMetadataInDocumentOp.ID).set("properties", props).set("save", false); service.run(ctx, chain); assertEquals(changeToken, docPNG.getChangeToken()); } @Test public void testWhenDocIsNotSaved() throws Exception { doLog(getCurrentMethodName(new RuntimeException()) + "..."); File nuxeoFile = FileUtils.getResourceFileFromContext(NUXEO_LOGO); DocumentModel aPictDoc = coreSession.createDocumentModel(parentOfTestDocs.getPathAsString(), filePNG.getName(), "Picture"); aPictDoc.setPropertyValue("dc:title", filePNG.getName()); aPictDoc.setPropertyValue("file:content", new FileBlob(nuxeoFile)); // We don't coreSession.createDocument(aPictDoc); // because we don't want the blob to be stored in the BinaryStore // (so it's not a StorageBlob) OperationContext ctx = new OperationContext(coreSession); assertNotNull(ctx); String changeToken = aPictDoc.getChangeToken(); ctx.setInput(aPictDoc); OperationChain chain = new OperationChain("testChain"); Properties props = new Properties(); props.put("imd:pixel_xdimension", KEYS.WIDTH); // Here too, we don't save chain.add(ExtractBinaryMetadataInDocumentOp.ID).set("properties", props).set("save", false); service.run(ctx, chain); assertEquals(changeToken, aPictDoc.getChangeToken()); assertEquals((long) 201, aPictDoc.getPropertyValue("imd:pixel_xdimension")); } @Test public void testXMP() throws Exception { String methodName = getCurrentMethodName(new RuntimeException()); doLog(methodName + "..."); if (!ToolAvailability.isExifToolAvailable()) { doLog("[WARN] Cannot run " + methodName + "() because ExifTool is not available"); return; } MetadataReader mdr; String xmp; // ============================== // Test on png file with no xmp // ============================== mdr = new MetadataReader(filePNG.getAbsolutePath()); xmp = mdr.readXMP(); assertTrue(xmp.isEmpty()); // ============================== // Test on file with xmp // ============================== File withXmpFile = FileUtils.getResourceFileFromContext(WITH_XMP); mdr = new MetadataReader(withXmpFile.getAbsolutePath()); xmp = mdr.readXMP(); assertFalse(xmp.isEmpty()); // Check it is a valid, well formed XML DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(xmp)); Document doc = dBuilder.parse(is); assertEquals("x:xmpmeta", doc.getDocumentElement().getNodeName()); // ============================== // Test the operation // ============================== OperationChain chain; OperationContext ctx = new OperationContext(coreSession); assertNotNull(ctx); // Using the file with xmp FileBlob fb = new FileBlob(withXmpFile); ctx.setInput(fb); chain = new OperationChain("testChain"); chain.add(ExtractXMPFromBlobOp.ID).set("varName", "xmp"); service.run(ctx, chain); // Check our "xmp" Context Variable is filled xmp = (String) ctx.get("xmp"); assertFalse(xmp.isEmpty()); // Check it is a valid, well formed XML // (we re-used the variables declared previously) dbFactory = DocumentBuilderFactory.newInstance(); dBuilder = dbFactory.newDocumentBuilder(); is = new InputSource(new StringReader(xmp)); doc = dBuilder.parse(is); assertEquals("x:xmpmeta", doc.getDocumentElement().getNodeName()); } @Test public void testGetMetadataWithExifTool() throws Exception { String methodName = getCurrentMethodName(new RuntimeException()); doLog(methodName + "..."); if (!ToolAvailability.isExifToolAvailable()) { doLog("[WARN] Cannot run " + methodName + "() because ExifTool is not available"); return; } HashMap<String, String> result; MetadataReader mdr; String[] theKeys = { "ImageSize", "FileName", "ImageHeight", "ImageWidth", "FileType", "JFIFVersion", "ProfileVersion", "ProfileDescription" }; mdr = new MetadataReader(filePNG.getAbsolutePath()); result = mdr.readMetadata(theKeys, TOOL.EXIFTOOL); assertNotNull(result); assertEquals("100x100", result.get("ImageSize")); assertEquals(filePNG.getName(), result.get("FileName")); assertEquals("100", result.get("ImageWidth")); assertEquals("100", result.get("ImageHeight")); assertEquals("PNG", result.get("FileType")); // Expected "" values returned because the image does not have these // tags assertEquals("", result.get("JFIFVersion")); assertEquals("", result.get("ProfileVersion")); assertEquals("", result.get("ProfileDescription")); mdr = new MetadataReader(fileJPEG.getAbsolutePath()); result = mdr.readMetadata(theKeys, TOOL.EXIFTOOL); assertNotNull(result); assertEquals("1597x232", result.get("ImageSize")); assertEquals(fileJPEG.getName(), result.get("FileName")); assertEquals("1597", result.get("ImageWidth")); assertEquals("232", result.get("ImageHeight")); assertEquals("JPEG", result.get("FileType")); assertEquals("1.01", result.get("JFIFVersion")); // Expected "" values returned because the image does not have these // tags assertEquals("", result.get("ProfileVersion")); assertEquals("", result.get("ProfileDescription")); mdr = new MetadataReader(fileTIF.getAbsolutePath()); result = mdr.readMetadata(theKeys, TOOL.EXIFTOOL); assertNotNull(result); assertEquals("456x180", result.get("ImageSize")); assertEquals(fileTIF.getName(), result.get("FileName")); assertEquals("456", result.get("ImageWidth")); assertEquals("180", result.get("ImageHeight")); assertEquals("TIFF", result.get("FileType")); assertEquals("", result.get("JFIFVersion")); assertEquals("2.1.0", result.get("ProfileVersion")); assertEquals("Display", result.get("ProfileDescription")); } @Test public void testExtractBinaryMetadataInDocumentOp_ExifTool() throws Exception { String methodName = getCurrentMethodName(new RuntimeException()); doLog(methodName + "..."); OperationContext ctx; OperationChain chain; // ===================================== ExifTool if (!ToolAvailability.isExifToolAvailable()) { doLog("[WARN] Cannot check ExifTool with " + methodName + "() because ExifTool is not available"); return; } else { ctx = new OperationContext(coreSession); assertNotNull(ctx); chain = new OperationChain("testChain"); Properties props = new Properties(); props.put("dc:description", "FileType"); props.put("dc:language", "ImageSize"); props.put("dc:format", "ProfileDescription"); props.put("dc:rights", "Keywords"); props.put("dc:source", "Title"); props.put("dc:coverage", "NOT_VALID_PROPERTY"); chain.add(ExtractBinaryMetadataInDocumentOp.ID).set("tool", "ExifTool").set("properties", props) .set("save", false); ctx.setInput(docTIF); DocumentModel resultDoc = (DocumentModel) service.run(ctx, chain); assertEquals("TIFF", resultDoc.getPropertyValue("dc:description")); assertEquals("456x180", resultDoc.getPropertyValue("dc:language")); assertEquals("Display", resultDoc.getPropertyValue("dc:format")); assertEquals("kw1,kw2", resultDoc.getPropertyValue("dc:rights")); assertEquals("Some Test", resultDoc.getPropertyValue("dc:source")); assertEquals("", resultDoc.getPropertyValue("dc:coverage")); } } /* * See MetadataReader global comment: For PDF, Office, ... files, we use * only ExifTool */ @Test public void testPdfFile() throws Exception { String methodName = getCurrentMethodName(new RuntimeException()); doLog(methodName + "..."); if (!ToolAvailability.isExifToolAvailable()) { doLog("[WARN] Cannot run " + methodName + "() because ExifTool is not available"); return; } HashMap<String, String> result; File f = FileUtils.getResourceFileFromContext("files/a.pdf"); MetadataReader mdr = new MetadataReader(f.getAbsolutePath()); /* * result = mdr.getMetadata(null, TOOL.IMAGEMAGICK); * * result = mdr.getMetadata(null, TOOL.GRAPHICSMAGICK); */ // Wa have less info with ExifTool result = mdr.readMetadata(null, TOOL.EXIFTOOL); assertEquals("PDF", result.get("FileType")); assertEquals("1.4", result.get("PDFVersion")); assertEquals("Mac OS X 10.10 Quartz PDFContext", result.get("Producer")); } /* * [See comments for testPdfFile] */ @Test public void testMicrosoftWordFile() throws Exception { String methodName = getCurrentMethodName(new RuntimeException()); doLog(methodName + "..."); if (!ToolAvailability.isExifToolAvailable()) { doLog("[WARN] Cannot run " + methodName + "() because ExifTool is not available"); return; } File f = FileUtils.getResourceFileFromContext("files/a.docx"); MetadataReader mdr = new MetadataReader(f.getAbsolutePath()); HashMap<String, String> result = mdr.readMetadata(null, TOOL.EXIFTOOL); assertEquals("DOCX", result.get("FileType")); assertEquals("3", result.get("Pages")); assertEquals("628", result.get("Words")); assertEquals("3585", result.get("Characters")); } /* * [See comments for testPdfFile] */ @Test public void testMicrosoftPowerPointFile() throws Exception { String methodName = getCurrentMethodName(new RuntimeException()); doLog(methodName + "..."); if (!ToolAvailability.isExifToolAvailable()) { doLog("[WARN] Cannot run " + methodName + "() because ExifTool is not available"); return; } File f = FileUtils.getResourceFileFromContext("files/a.pptx"); MetadataReader mdr = new MetadataReader(f.getAbsolutePath()); HashMap<String, String> result = mdr.readMetadata(null, TOOL.EXIFTOOL); assertEquals("PPTX", result.get("FileType")); assertEquals("6", result.get("Slides")); assertEquals("0", result.get("HiddenSlides")); assertEquals("Custom", result.get("PresentationFormat")); } /* * [See comments for testPdfFile] */ @Test public void testMp4File() throws Exception { String methodName = getCurrentMethodName(new RuntimeException()); doLog(methodName + "..."); if (!ToolAvailability.isExifToolAvailable()) { doLog("[WARN] Cannot run " + methodName + "() because ExifTool is not available"); return; } File f = FileUtils.getResourceFileFromContext("files/a.mp4"); MetadataReader mdr = new MetadataReader(f.getAbsolutePath()); HashMap<String, String> result = mdr.readMetadata(null, TOOL.EXIFTOOL); assertEquals("MP4", result.get("FileType")); assertEquals("320x180", result.get("ImageSize")); assertEquals("11.85 s", result.get("Duration")); assertEquals("2997", result.get("TimeScale")); } }