View Javadoc
1   /**
2    * This file Copyright (c) 2015-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.layered;
35  
36  import static com.google.common.collect.Iterables.*;
37  
38  import info.magnolia.resourceloader.AbstractResourceOrigin;
39  import info.magnolia.resourceloader.Resource;
40  import info.magnolia.resourceloader.ResourceOrigin;
41  import info.magnolia.resourceloader.ResourceOriginFactory;
42  import info.magnolia.resourceloader.ResourceVisitor;
43  import info.magnolia.resourceloader.util.Functions;
44  
45  import java.io.IOException;
46  import java.io.InputStream;
47  import java.io.Reader;
48  import java.nio.charset.Charset;
49  import java.util.ArrayList;
50  import java.util.Arrays;
51  import java.util.List;
52  
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  import com.google.auto.factory.AutoFactory;
57  import com.google.common.base.Function;
58  import com.google.common.base.Optional;
59  import com.google.common.base.Predicate;
60  import com.google.common.collect.Iterables;
61  import com.google.common.collect.Lists;
62  import com.google.common.collect.Ordering;
63  
64  /**
65   * An implementation of {@link ResourceOrigin} which aggregates
66   * other instances of {@link ResourceOrigin}; resources located on the same path will
67   * be served from the foremost ResourceOrigin, but resources that exist only on one path will always be served.
68   */
69  @AutoFactory(implementing = ResourceOriginFactory.class)
70  public class LayeredResourceOrigin extends AbstractResourceOrigin<LayeredResource> {
71  
72      private static final Logger log = LoggerFactory.getLogger(LayeredResourceOrigin.class);
73  
74      private final List<ResourceOrigin> origins;
75  
76      // No injected arguments so far, which is fine. Just keeping a factory for consistency.
77      LayeredResourceOrigin(String name, ResourceOrigin... origins) {
78          super(name);
79          this.origins = Arrays.asList(origins);
80      }
81  
82      @Override
83      public LayeredResource getRoot() {
84          final List<Resource> roots = Lists.transform(origins, Functions.getRoot());
85          return newLayeredResource(roots);
86      }
87  
88      @Override
89      public void watchForChanges(ResourceVisitor visitor) {
90          for (ResourceOrigin origin : origins) {
91              origin.watchForChanges(new RelayerResourceVisitor(this, visitor));
92          }
93      }
94  
95      @Override
96      public LayeredResource getByPath(String path) {
97          final Iterable<ResourceOrigin> matchingOrigins = filter(this.origins, Functions.hasPath(path));
98          final List<Resource> matchingResources = Lists.newArrayList(transform(matchingOrigins, Functions.getByPath(path)));
99          if (matchingResources.isEmpty()) {
100             throw new ResourceNotFoundException(this, path);
101         }
102         return newLayeredResource(matchingResources);
103     }
104 
105     @Override
106     public boolean hasPath(String path) {
107         return any(origins, Functions.hasPath(path));
108     }
109 
110     @Override
111     protected boolean isFile(LayeredResource resource) {
112         return resource.getFirst().isFile();
113     }
114 
115     @Override
116     protected boolean isDirectory(LayeredResource resource) {
117         return resource.getFirst().isDirectory();
118     }
119 
120     @Override
121     protected boolean isEditable(LayeredResource resource) {
122         return resource.getFirst().isEditable();
123     }
124 
125     @Override
126     protected String getPath(LayeredResource resource) {
127         return resource.getFirst().getPath();
128     }
129 
130     @Override
131     protected String getName(LayeredResource resource) {
132         return resource.getFirst().getName();
133     }
134 
135     @Override
136     protected long getLastModified(LayeredResource resource) {
137         return resource.getFirst().getLastModified();
138     }
139 
140     @Override
141     protected List<LayeredResource> listChildren(LayeredResource resource) {
142         List<LayeredResource> layeredResources = new ArrayList<>();
143 
144         // expecting layers to be correctly ordered
145         final List<Resource> layers = resource.getLayers();
146         for (Resource layer : layers) {
147             List<Resource> layerChildren = layer.listChildren();
148 
149             // optimizing first round, just transform whole first layer into layered resources
150             if (layeredResources.isEmpty()) {
151                 layeredResources.addAll(Lists.transform(layerChildren, new Function<Resource, LayeredResource>() {
152                     @Override
153                     public LayeredResource apply(Resource input) {
154                         return new LayeredResource(LayeredResourceOrigin.this, input.getPath(), Lists.newArrayList(input));
155                     }
156                 }));
157                 continue;
158             }
159 
160             for (final Resource r : layerChildren) {
161                 Optional<LayeredResource> matchingResource = Iterables.tryFind(layeredResources, new Predicate<LayeredResource>() {
162                     @Override
163                     public boolean apply(LayeredResource input) {
164                         return r.getPath().equals(input.getPath()) && r.isFile() == input.isFile();
165                     }
166                 });
167                 if (matchingResource.isPresent()) {
168                     LayeredResource l = matchingResource.get();
169                     l.getLayers().add(r);
170                 } else {
171                     LayeredResource l = new LayeredResource(this, r.getPath(), Lists.newArrayList(r));
172                     layeredResources.add(l);
173                 }
174             }
175         }
176         return Ordering.from(Functions.compareByHandle()).immutableSortedCopy(layeredResources);
177     }
178 
179     @Override
180     protected LayeredResource getParent(LayeredResource resource) {
181         // This would only get the parents from the layers in the given resource, but that parent might exist in other resources too
182         // final List<Resource> parents = Lists.transform(resource.getLayers(), Functions.getParent());
183 
184         // So instead, we pick the parent of the first layer, and do a getByPath, which feels a bit underwhelming.
185         Resource parent = resource.getFirst().getParent();
186         return parent != null ? getByPath(parent.getPath()) : null;
187     }
188 
189     @Override
190     protected InputStream doOpenStream(LayeredResource resource) throws IOException {
191         return resource.getFirst().openStream();
192     }
193 
194     @Override
195     protected Reader openReader(LayeredResource resource) throws IOException {
196         return resource.getFirst().openReader();
197     }
198 
199     /**
200      * Overridden to throw an exception; since we override {@link #openReader(LayeredResource)},
201      * this method used in {@link AbstractResourceOrigin#openReader(info.magnolia.resourceloader.AbstractResource)}
202      * should never be called.
203      */
204     @Override
205     protected Charset getCharsetFor(LayeredResource resource) {
206         throw new IllegalStateException("This method should not be called");
207     }
208 
209     protected LayeredResource newLayeredResource(List<Resource> resources) {
210         // Sanity checks -- are all the resources on the same path and are they all dir/files ?
211         final String path = resources.get(0).getPath();
212         final boolean isDirectory = resources.get(0).isDirectory();
213         if (!all(resources, Functions.pathEquals(path))) {
214             throw new IllegalStateException("Given resources don't match path [" + path + "]: " + resources);
215         }
216         final Predicate<Resource> dirOrFilePredicate = isDirectory ? Functions.isDirectory() : Functions.isFile();
217         if (!all(resources, dirOrFilePredicate)) {
218             log.warn("Resources at {} are not all directory/file: {}", path, resources);
219             resources = Lists.newArrayList(Iterables.filter(resources, dirOrFilePredicate));
220         }
221         return new LayeredResource(this, path, resources);
222     }
223 
224 }