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}