diff --git a/source/java/org/alfresco/repo/content/metadata/xml/XPathMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/xml/XPathMetadataExtracter.java index 17a23b5d29..f657bc76a4 100644 --- a/source/java/org/alfresco/repo/content/metadata/xml/XPathMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/xml/XPathMetadataExtracter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Jesper Steen Møller + * Copyright (C) 2005-2007 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -42,6 +42,7 @@ import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; @@ -80,12 +81,12 @@ import org.w3c.dom.Document; * * *

- * The mapping of document properties to XPaths must look as follows: - *

- *    # Get the author
- *    author=/root/author@name
- * 
+ * All values are extracted as text values and therefore all XPath statements must evaluate to a node + * that can be rendered as text. * + * @see AbstractMappingMetadataExtracter#setMappingProperties(Properties) + * @see #setXpathMappingProperties(Properties) + * @since 2.1 * @author Derek Hulley */ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter implements NamespaceContext @@ -105,13 +106,27 @@ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter imp public XPathMetadataExtracter() { super(new HashSet(Arrays.asList(SUPPORTED_MIMETYPES))); + try + { + documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + xpathFactory = XPathFactory.newInstance(); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Failed to initialize XML metadata extractor", e); + } } /** {@inheritDoc} */ public String getNamespaceURI(String prefix) { ParameterCheck.mandatoryString("prefix", prefix); - return namespacesByPrefix.get(prefix); + String namespace = namespacesByPrefix.get(prefix); + if (namespace == null) + { + throw new AlfrescoRuntimeException("Prefix '" + prefix + "' is not associated with a namespace."); + } + return namespace; } /** {@inheritDoc} */ @@ -150,12 +165,11 @@ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter imp * The Xpath mapping is of the form: *
      * # Namespaces prefixes
-     * namespace.prefix.cm=http://www.alfresco.org/model/content/1.0
      * namespace.prefix.my=http://www....com/alfresco/1.0
      * 
      * # Mapping
-     * editor=/cm:some-xpath-1
-     * title=/my:some-xpath-2
+     * editor=/my:example-element/@cm:editor
+     * title=/my:example-element/text()
      * 
*/ public void setXpathMappingProperties(Properties xpathMappingProperties) @@ -169,16 +183,18 @@ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter imp protected void init() { PropertyCheck.mandatory(this, "xpathMappingProperties", xpathExpressionMapping); - try - { - documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - xpathFactory = XPathFactory.newInstance(); - } - catch (Throwable e) - { - throw new AlfrescoRuntimeException("Failed to initialize XML metadata extractor", e); - } + // Get the base class to set up its mappings super.init(); + // Remove all XPath expressions that aren't going to be used + Map> mapping = getMapping(); + Set xpathExpressionMappingKeys = new HashSet(xpathExpressionMapping.keySet()); + for (String xpathMappingKey : xpathExpressionMappingKeys) + { + if (!mapping.containsKey(xpathMappingKey)) + { + xpathExpressionMapping.remove(xpathMappingKey); + } + } } /** @@ -232,7 +248,7 @@ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter imp String documentProperty = element.getKey(); XPathExpression xpathExpression = element.getValue(); // Execute it - String value = xpathExpression.evaluate(document); + String value = (String) xpathExpression.evaluate(document, XPathConstants.STRING); // Put the value rawProperties.put(documentProperty, value); } @@ -258,8 +274,6 @@ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter imp namespacesByPrefix.put(prefix, namespace); } } - // Get the mapping that will be applied by the base class - Map> finalMapping = getMapping(); // Create the mapping for (Map.Entry entry : xpathMappingProperties.entrySet()) { @@ -270,11 +284,6 @@ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter imp // Ignore these now continue; } - // If the property is not going to be mapped, then just ignore it too - if (!finalMapping.containsKey(documentProperty)) - { - continue; - } // Construct the XPath XPath xpath = xpathFactory.newXPath(); xpath.setNamespaceContext(this); @@ -285,10 +294,12 @@ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter imp } catch (XPathExpressionException e) { - throw new AlfrescoRuntimeException( - "Failed to path XPath expression: \n" + + throw new AlfrescoRuntimeException("\n" + + "Failed to create XPath expression: \n" + " Document property: " + documentProperty + "\n" + - " XPath: " + xpathStr); + " XPath: " + xpathStr + "\n" + + " Error: " + e.getMessage(), + e); } // Persist it xpathExpressionMapping.put(documentProperty, xpathExpression); diff --git a/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracterTest.java new file mode 100644 index 0000000000..767e8b4b84 --- /dev/null +++ b/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracterTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.content.metadata.xml; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.URL; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.repo.content.transform.AbstractContentTransformerTest; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.util.PropertyMap; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Tests various aspects of XML metadata extraction. + * + * @see XPathMetadataExtracter + * + * @author Derek Hulley + */ +public class XmlMetadataExtracterTest extends TestCase +{ + private static final String FILE_ALFRESCO_MODEL = "xml-metadata/alfresco-model-sample.xml"; + private static final String FILE_ECLIPSE_PROJECT = "xml-metadata/eclipse-project-sample.xml"; + + private static final String CTX_LOCATION = "classpath:xml-metadata/xml-metadata-test-context.xml"; + private static final ApplicationContext ctx = new ClassPathXmlApplicationContext(CTX_LOCATION); + + private XPathMetadataExtracter alfrescoModelMetadataExtractor; + private XPathMetadataExtracter eclipseProjectMetadataExtractor; + + /** + * Get a reader for a file that should be on the classpath. + */ + private static final ContentReader getReader(String fileName) throws FileNotFoundException + { + URL url = AbstractContentTransformerTest.class.getClassLoader().getResource(fileName); + if (url == null) + { + throw new FileNotFoundException("Could not find file on classpath: " + fileName); + } + File file = new File(url.getFile()); + if (!file.exists()) + { + throw new FileNotFoundException("Could not find file on classpath: " + fileName); + } + ContentReader reader = new FileContentReader(file); + reader.setMimetype(MimetypeMap.MIMETYPE_XML); + return reader; + } + + @Override + public void setUp() throws Exception + { + alfrescoModelMetadataExtractor = (XPathMetadataExtracter) ctx.getBean("extracter.xml.AlfrescoModelMetadataExtracter"); + eclipseProjectMetadataExtractor = (XPathMetadataExtracter) ctx.getBean("extracter.xml.EclipseProjectMetadataExtracter"); + } + + public void testSetUp() + { + assertNotNull(alfrescoModelMetadataExtractor); + assertNotNull(eclipseProjectMetadataExtractor); + } + + public void testExtractAlfresocModel() throws Exception + { + // Load the example file + ContentReader reader = getReader(FILE_ALFRESCO_MODEL); + assertTrue(reader.exists()); + + // Pass it to the extracter + PropertyMap checkProperties = new PropertyMap(); + alfrescoModelMetadataExtractor.extract(reader, checkProperties); + + // Check the values + assertEquals("Gavin Cornwell", checkProperties.get(ContentModel.PROP_AUTHOR)); + assertEquals("fm:forummodel", checkProperties.get(ContentModel.PROP_TITLE)); + assertEquals("Forum Model", checkProperties.get(ContentModel.PROP_DESCRIPTION)); + } + + public void testExtractEclipseProject() throws Exception + { + // Load the example file + ContentReader reader = getReader(FILE_ECLIPSE_PROJECT); + assertTrue(reader.exists()); + + // Pass it to the extracter + PropertyMap checkProperties = new PropertyMap(); + eclipseProjectMetadataExtractor.extract(reader, checkProperties); + + // Check the values + assertEquals("Repository", checkProperties.get(ContentModel.PROP_TITLE)); + assertEquals("JavaCC Nature", checkProperties.get(ContentModel.PROP_DESCRIPTION)); + } +} diff --git a/source/test-resources/xml-metadata/alfresco-model-mappings.properties b/source/test-resources/xml-metadata/alfresco-model-mappings.properties new file mode 100644 index 0000000000..6e7178fdf2 --- /dev/null +++ b/source/test-resources/xml-metadata/alfresco-model-mappings.properties @@ -0,0 +1,12 @@ +# +# XmlMetadataExtracter - mapping for Alfresco model properties +# +# author: Derek Hulley + +# Namespaces +namespace.prefix.cm=http://www.alfresco.org/model/content/1.0 + +# Mappings +author=cm:author +title=cm:title +description=cm:description diff --git a/source/test-resources/xml-metadata/alfresco-model-sample.xml b/source/test-resources/xml-metadata/alfresco-model-sample.xml new file mode 100644 index 0000000000..e018ef1522 --- /dev/null +++ b/source/test-resources/xml-metadata/alfresco-model-sample.xml @@ -0,0 +1,25 @@ + + + + + Forum Model + Gavin Cornwell + 1.0 + + + + + + + + + + + + + cm:folder + + + + + \ No newline at end of file diff --git a/source/test-resources/xml-metadata/alfresco-model-xpath-mappings.properties b/source/test-resources/xml-metadata/alfresco-model-xpath-mappings.properties new file mode 100644 index 0000000000..f236169d1b --- /dev/null +++ b/source/test-resources/xml-metadata/alfresco-model-xpath-mappings.properties @@ -0,0 +1,14 @@ +# +# Maps document properties to the required XPaths for Alfresco model files +# +# author: Derek Hulley + +# Namespaces +namespace.prefix.fm=http://www.alfresco.org/model/forum/1.0 + +# Mappings +author=/model/author/text() +title=/model/@name +description=/model/description/text() +version=/model/version/text() +ignored-with-ns=/model/fm:ignored \ No newline at end of file diff --git a/source/test-resources/xml-metadata/eclipse-project-mappings.properties b/source/test-resources/xml-metadata/eclipse-project-mappings.properties new file mode 100644 index 0000000000..c68df82456 --- /dev/null +++ b/source/test-resources/xml-metadata/eclipse-project-mappings.properties @@ -0,0 +1,11 @@ +# +# XmlMetadataExtracter - mapping for Eclipse project files +# +# author: Derek Hulley + +# Namespaces +namespace.prefix.cm=http://www.alfresco.org/model/content/1.0 + +# Mappings +name=cm:title +description=cm:description diff --git a/source/test-resources/xml-metadata/eclipse-project-sample.xml b/source/test-resources/xml-metadata/eclipse-project-sample.xml new file mode 100644 index 0000000000..7e072e9002 --- /dev/null +++ b/source/test-resources/xml-metadata/eclipse-project-sample.xml @@ -0,0 +1,28 @@ + + + Repository + JavaCC Nature + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.ui.externaltools.ExternalToolBuilder + auto,incremental, + + + LaunchConfigHandle + <project>/.externalToolBuilders/JibX.launch + + + + + + org.eclipse.jdt.core.javanature + rk.eclipse.javacc.javaccnature + + diff --git a/source/test-resources/xml-metadata/eclipse-project-xpath-mappings.properties b/source/test-resources/xml-metadata/eclipse-project-xpath-mappings.properties new file mode 100644 index 0000000000..3299d1ffea --- /dev/null +++ b/source/test-resources/xml-metadata/eclipse-project-xpath-mappings.properties @@ -0,0 +1,10 @@ +# +# Maps document properties to the required XPaths for Eclipse project files +# +# author: Derek Hulley + +# Namespaces + +# Mappings +name=/projectDescription/name/text() +description=/projectDescription/comment/text() diff --git a/source/test-resources/xml-metadata/xml-metadata-test-context.xml b/source/test-resources/xml-metadata/xml-metadata-test-context.xml new file mode 100644 index 0000000000..0ad3a5b047 --- /dev/null +++ b/source/test-resources/xml-metadata/xml-metadata-test-context.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + classpath:xml-metadata/alfresco-model-mappings.properties + + + + + + + classpath:xml-metadata/alfresco-model-xpath-mappings.properties + + + + + + + + + + + classpath:xml-metadata/eclipse-project-mappings.properties + + + + + + + classpath:xml-metadata/eclipse-project-xpath-mappings.properties + + + + + + \ No newline at end of file