View Javadoc
1   /**
2    * This file Copyright (c) 2016 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.resourceloader.classpath.service.impl.base;
35  
36  import info.magnolia.resourceloader.classpath.hierarchy.ClasspathDirectory;
37  import info.magnolia.resourceloader.classpath.hierarchy.ClasspathEntry;
38  import info.magnolia.resourceloader.classpath.service.ClasspathServiceConfiguration;
39  
40  import java.net.URL;
41  import java.util.Map;
42  
43  import org.apache.commons.lang3.StringUtils;
44  import org.reflections.vfs.Vfs;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import com.google.common.base.Predicate;
49  import com.google.common.collect.Maps;
50  
51  /**
52   * Produces a set of {@link ClasspathEntry classpath entries} mapped to their absolute paths on the basis of {@link ClasspathServiceConfiguration classpath configuration}
53   * and some classpath traversal strategy.
54   *
55   * <p>Allows to specify the strategy of {@link info.magnolia.resourceloader.classpath.hierarchy.ClasspathFile classpath file entries} construction via
56   * {@link ClasspathFileConstructionStrategy}</p>
57   *
58   * <p>Provides a {@link ReflectionsVfsBased default implementation} based on <a href="https://github.com/ronmamo/reflections">Reflections library's VFS</a>.</p>
59   *
60   * @see ClasspathServiceBase
61   * @see ClasspathFileConstructionStrategy
62   */
63  public abstract class ClasspathEntriesResolver {
64  
65      private final ClasspathFileConstructionStrategy classpathFileConstructionStrategy;
66  
67      public ClasspathEntriesResolver(ClasspathFileConstructionStrategy classpathFileConstructionStrategy) {
68          this.classpathFileConstructionStrategy = classpathFileConstructionStrategy;
69      }
70  
71      /**
72       * Resolve {@link ClasspathEntry classpath enty} mappings bound to the {@link ClasspathServiceConfiguration classpath configuration}.
73       * @param configuration classpath restrictions for resource search locations, patterns etc
74       * @return unique classpath entries mapped to their absolute paths
75       */
76      public abstract Map<String, ClasspathEntry> resolveClasspathEntries(ClasspathServiceConfiguration configuration);
77  
78      protected ClasspathFileConstructionStrategy getClasspathFileConstructionStrategy() {
79          return classpathFileConstructionStrategy;
80      }
81  
82      /**
83       * Default implementation of {@link ClasspathEntriesResolver} based <a href="https://github.com/ronmamo/reflections">Reflections library's VFS</a>.
84       */
85      public static class ReflectionsVfsBased extends ClasspathEntriesResolver {
86  
87          private static final Logger log = LoggerFactory.getLogger(ReflectionsVfsBased.class);
88  
89          public ReflectionsVfsBased(ClasspathFileConstructionStrategy classpathFileConstructionStrategy) {
90              super(classpathFileConstructionStrategy);
91          }
92  
93          @Override
94          public Map<String, ClasspathEntry> resolveClasspathEntries(ClasspathServiceConfiguration configuration) {
95              final Predicate<String> resourcesFilter = configuration.getResourceFilter();
96              final Iterable<URL> classpathLocations = configuration.getClasspathLocations();
97  
98              final Map<String, ClasspathEntry> entries = Maps.newHashMap();
99              ClasspathFileConstructionStrategy strategy = getClasspathFileConstructionStrategy();
100 
101             for (final URL url : classpathLocations) {
102                 try {
103                     for (final Vfs.File file : Vfs.fromURL(url).getFiles()) {
104                         if (resourcesFilter.apply(file.getRelativePath())) {
105                             final ClasspathEntry resource = strategy.buildFile(file, url);
106                             attachAndIndex(resource, entries);
107                         }
108                     }
109                 } catch (Exception e) {
110                     log.debug("Failed to populate resources from {} due to: ", url, e);
111                 }
112             }
113             return entries;
114         }
115 
116         /**
117          * Attaches resources to the virtual file tree structure, creating virtual directories for ancestors as needed.
118          * Also indexes all classpath entries into the given map.
119          */
120         protected void attachAndIndex(ClasspathEntry resource, Map<String, ClasspathEntry> entries) {
121             entries.put(resource.getAbsolutePath(), resource);
122 
123             // Add parent directories
124             String parentPath = getParentPath(resource.getAbsolutePath());
125             while (parentPath != null) {
126                 ClasspathDirectory parent;
127                 if (!entries.containsKey(parentPath)) {
128                     // parent doesn't exist, create it, set relationship and put it in the map
129                     parent = new ClasspathDirectory(parentPath);
130                     parent.getEntries().add(resource);
131                     entries.put(parentPath, parent);
132                 } else {
133                     // parent exists, set relationship and stop creating ancestor paths
134                     parent = (ClasspathDirectory) entries.get(parentPath);
135                     parent.getEntries().add(resource);
136                     break;
137                 }
138                 parentPath = getParentPath(parentPath);
139                 resource = parent;
140             }
141         }
142 
143         private String getParentPath(String resourcePath) {
144             if ("/".equals(resourcePath)) {
145                 // root has no parent
146                 return null;
147             }
148             String parentPath = StringUtils.substringBeforeLast(resourcePath, "/");
149             return StringUtils.defaultIfEmpty(parentPath, "/");
150         }
151     }
152 }