    001// Copyright 2014 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014package org.apache.tapestry5.internal.services;
    015
    016import java.io.IOException;
    017import java.io.InputStream;
    018import java.net.URL;
    019import java.util.Arrays;
    020import java.util.Collections;
    021import java.util.HashMap;
    022import java.util.Map;
    023import java.util.Set;
    024import java.util.WeakHashMap;
    025
    026import javax.xml.parsers.DocumentBuilder;
    027import javax.xml.parsers.DocumentBuilderFactory;
    028import javax.xml.xpath.XPath;
    029import javax.xml.xpath.XPathConstants;
    030import javax.xml.xpath.XPathExpression;
    031import javax.xml.xpath.XPathExpressionException;
    032import javax.xml.xpath.XPathFactory;
    033
    034import org.apache.tapestry5.ioc.services.ClasspathMatcher;
    035import org.apache.tapestry5.ioc.services.ClasspathScanner;
    036import org.apache.tapestry5.services.ComponentLibraryInfo;
    037import org.apache.tapestry5.services.ComponentLibraryInfoSource;
    038import org.apache.tapestry5.services.LibraryMapping;
    039import org.slf4j.Logger;
    040import org.w3c.dom.Document;
    041
    042/**
    043 * {@link ComponentLibraryInfoSource} implementation based on the pom.xml and pom.properties files
    044 * Maven places in the /META-INF/maven/[groupId]/[artifactId] folder.
    045 */
    046public class MavenComponentLibraryInfoSource implements ComponentLibraryInfoSource
    047{
    048
    049    final private Logger logger;
    050
    051    final private Set<String> pomPaths;
    052
    053    final private Map<String, ComponentLibraryInfo> cache = new HashMap<String, ComponentLibraryInfo>();
    054
    055    final private Map<String, String> pomPathToRootUrl;
    056
    057    final private DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    058
    059    public MavenComponentLibraryInfoSource(Logger logger, ClasspathScanner classpathScanner)
    060    {
    061        super();
    062        this.logger = logger;
    063        this.pomPaths = Collections.unmodifiableSet(findPomPaths(classpathScanner));
    064        pomPathToRootUrl = new WeakHashMap<String, String>(pomPaths.size());
    065    }
    066
    067    @Override
    068    public ComponentLibraryInfo find(LibraryMapping libraryMapping)
    069    {
    070        ComponentLibraryInfo info = null;
    071        if (cache.containsKey(libraryMapping.libraryName))
    072        {
    073            info = cache.get(libraryMapping.libraryName);
    074        }
    075        else
    076        {
    077            final String pomPath = getPomPath(libraryMapping);
    078            if (pomPath != null)
    079            {
    080                InputStream inputStream = getClass().getResourceAsStream("/" + pomPath);
    081                info = parse(inputStream);
    082                info.setLibraryMapping(libraryMapping);
    083                cache.put(libraryMapping.libraryName, info);
    084            }
    085            else
    086            {
    087                cache.put(libraryMapping.libraryName, null);
    088            }
    089        }
    090        return info;
    091    }
    092
    093    private ComponentLibraryInfo parse(InputStream inputStream)
    094    {
    095        ComponentLibraryInfo info = null;
    096        if (inputStream != null)
    097        {
    098
    099            Document document;
    100
    101            try
    102            {
    103                DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
    104                document = documentBuilder.parse(inputStream);
    105            }
    106            catch (Exception e)
    107            {
    108                logger.warn("Exception while parsing pom.xml", e);
    109                return null;
    110            }
    111
    112            info = new ComponentLibraryInfo();
    113            info.setGroupId(extractText(document, "(/project/groupId | /project/parent/groupId)[1]"));
    114            info.setArtifactId(extractText(document, "/project/artifactId"));
    115            info.setVersion(extractText(document, "/project/version"));
    116            info.setName(extractText(document, "/project/name"));
    117            info.setDescription(extractText(document, "/project/description"));
    118            info.setDocumentationUrl(extractText(document, "/project/properties/documentationUrl"));
    119            info.setHomepageUrl(extractText(document, "/project/properties/homepageUrl"));
    120            info.setIssueTrackerUrl(extractText(document, "/project/issueManagement/url"));
    121            info.setJavadocUrl(extractText(document, "/project/properties/javadocUrl"));
    122            info.setSourceBrowseUrl(extractText(document, "/project/scm/url"));
    123            info.setSourceRootUrl(extractText(document, "/project/properties/sourceRootUrl"));
    124            info.setTapestryVersion(extractText(document, "(/project/dependencies/dependency[./groupId='org.apache.tapestry'][./artifactId='tapestry-core']/version | /project/properties/tapestryVersion)[1]"));
    125            String tags = extractText(document, "/project/properties/tags");
    126            if (tags != null && tags.length() > 0)
    127            {
    128                info.setTags(Arrays.asList(tags.split(",")));
    129            }
    130
    131        }
    132
    133        return info;
    134
    135    }
    136
    137    private String extractText(Document document, String xpathExpression)
    138    {
    139        XPath xpath = XPathFactory.newInstance().newXPath();
    140        String text;
    141        try
    142        {
    143            XPathExpression expression = xpath.compile(xpathExpression);
    144            text = (String) expression.evaluate(document, XPathConstants.STRING);
    145        }
    146        catch (XPathExpressionException e)
    147        {
    148            throw new RuntimeException(e);
    149        }
    150        if ("".equals(text))
    151        {
    152            text = null;
    153        }
    154        return text;
    155    }
    156
    157    private String getPomPath(LibraryMapping libraryMapping)
    158    {
    159        final String rootPackageConverted = libraryMapping.getRootPackage().replace('.', '/');
    160        final URL rootPackageUrl = getClass().getClassLoader().getResource(rootPackageConverted);
    161        String path = rootPackageUrl.toString();
    162        String url = null;
    163        if (path.contains("!/"))
    164        {
    165            path = path.substring(0, path.indexOf("!/"));
    166        }
    167        for (String pomPath : pomPaths)
    168        {
    169            if (path.equals(getPomPathUrl(pomPath))) {
    170                url = pomPath;
    171                break;
    172            }
    173        }
    174        return url;
    175    }
    176
    177    private String getPomPathUrl(String pomPath)
    178    {
    179        String url = pomPathToRootUrl.get(pomPath);
    180        if (url == null)
    181        {
    182            for (String path : pomPaths)
    183            {
    184                final URL resource = getClass().getResource("/" + path);
    185                String resourcePath = null;
    186                if (resource != null && resource.toString().contains("!/"))
    187                {
    188                    resourcePath = resource.toString();
    189                    resourcePath = resourcePath.substring(0, resourcePath.indexOf("!/"));
    190                }
    191                pomPathToRootUrl.put(path, resourcePath);
    192                url = resourcePath;
    193            }
    194        }
    195        return url;
    196    }
    197
    198    private static Set<String> findPomPaths(ClasspathScanner classpathScanner)
    199    {
    200        final ClasspathMatcher classpathMatcher = new ClasspathMatcher()
    201        {
    202            @Override
    203            public boolean matches(String packagePath, String fileName)
    204            {
    205                return fileName.equals("pom.xml");
    206            }
    207        };
    208        try
    209        {
    210            return classpathScanner.scan("META-INF/maven/", classpathMatcher);
    211        }
    212        catch (IOException e)
    213        {
    214            throw new RuntimeException("Exception while finding pom.xml files in the classpath", e);
    215        }
    216    }
    217
    218}