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            String tags = extractText(document, "/project/properties/tags");
125            if (tags != null && tags.length() > 0)
126            {
127                info.setTags(Arrays.asList(tags.split(",")));
128            }
129            
130        }
131        
132        return info;
133        
134    }
135
136    private String extractText(Document document, String xpathExpression)
137    {
138        XPath xpath = XPathFactory.newInstance().newXPath();
139        String text;
140        try
141        {
142            XPathExpression expression = xpath.compile(xpathExpression);
143            text = (String) expression.evaluate(document, XPathConstants.STRING);
144        }
145        catch (XPathExpressionException e)
146        {
147            throw new RuntimeException(e);
148        }
149        if ("".equals(text)) 
150        {
151            text = null;
152        }
153        return text;
154    }
155
156    private String getPomPath(LibraryMapping libraryMapping)
157    {
158        final String rootPackageConverted = libraryMapping.getRootPackage().replace('.', '/');
159        final URL rootPackageUrl = getClass().getClassLoader().getResource(rootPackageConverted);
160        String path = rootPackageUrl.toString();
161        String url = null;
162        if (path.contains("!/"))
163        {
164            path = path.substring(0, path.indexOf("!/"));
165        }
166        for (String pomPath : pomPaths)
167        {
168            if (path.equals(getPomPathUrl(pomPath))) {
169                url = pomPath;
170                break;
171            }
172        }
173        return url;
174    }
175    
176    private String getPomPathUrl(String pomPath)
177    {
178        String url = pomPathToRootUrl.get(pomPath);
179        if (url == null)
180        {
181            for (String path : pomPaths)
182            {
183                final URL resource = getClass().getResource("/" + path);
184                String resourcePath = null;
185                if (resource != null && resource.toString().contains("!/")) 
186                {
187                    resourcePath = resource.toString();
188                    resourcePath = resourcePath.substring(0, resourcePath.indexOf("!/"));
189                }
190                pomPathToRootUrl.put(path, resourcePath);
191                url = resourcePath;
192            }
193        }
194        return url;
195    }
196
197    private static Set<String> findPomPaths(ClasspathScanner classpathScanner)
198    {
199        final ClasspathMatcher classpathMatcher = new ClasspathMatcher()
200        {
201            @Override
202            public boolean matches(String packagePath, String fileName)
203            {
204                return fileName.equals("pom.xml");
205            }
206        };
207        try
208        {
209            return classpathScanner.scan("META-INF/maven/", classpathMatcher);
210        }
211        catch (IOException e)
212        {
213            throw new RuntimeException("Exception while finding pom.xml files in the classpath", e);
214        }
215    }
216
217}