1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.resourceloader.layered;
35
36 import static info.magnolia.resourceloader.ResourceOriginChange.Type.*;
37 import static info.magnolia.resourceloader.ResourceOriginChange.resourceChange;
38 import static java.util.Comparator.comparing;
39 import static java.util.stream.Collectors.toList;
40
41 import info.magnolia.resourceloader.AbstractResourceOrigin;
42 import info.magnolia.resourceloader.Resource;
43 import info.magnolia.resourceloader.ResourceChangeHandler;
44 import info.magnolia.resourceloader.ResourceChangeHandlerRegistration;
45 import info.magnolia.resourceloader.ResourceOrigin;
46 import info.magnolia.resourceloader.ResourceOriginChange;
47 import info.magnolia.resourceloader.ResourceOriginFactory;
48 import info.magnolia.resourceloader.util.Functions;
49
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.Reader;
53 import java.nio.charset.Charset;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.List;
57 import java.util.Optional;
58 import java.util.function.Predicate;
59
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import com.google.auto.factory.AutoFactory;
64 import com.google.common.collect.Lists;
65 import com.google.common.collect.Ordering;
66
67
68
69
70
71
72 @AutoFactory(implementing = ResourceOriginFactory.class)
73 public class LayeredResourceOrigin extends AbstractResourceOrigin<LayeredResource> {
74
75 private static final Logger log = LoggerFactory.getLogger(LayeredResourceOrigin.class);
76
77 private final List<ResourceOrigin<?>> origins;
78
79
80 LayeredResourceOrigin(String name, ResourceOrigin<?>... origins) {
81 super(name);
82 this.origins = Arrays.asList(origins);
83 }
84
85 @Override
86 public LayeredResource getRoot() {
87 final List<Resource> roots = origins.stream()
88 .map(ResourceOrigin::getRoot)
89 .collect(toList());
90 return newLayeredResource(roots);
91 }
92
93 @Override
94 public ResourceChangeHandlerRegistration registerResourceChangeHandler(final ResourceChangeHandler changeHandler) {
95 final ResourceChangeHandler relayingChangeHandler = new RelayingChangeHandler(changeHandler);
96
97 final List<ResourceChangeHandlerRegistration> registrations = Lists.newArrayListWithCapacity(origins.size() + 1);
98 registrations.add(super.registerResourceChangeHandler(relayingChangeHandler));
99
100 for (final ResourceOrigin origin : origins) {
101 registrations.add(origin.registerResourceChangeHandler(relayingChangeHandler));
102 }
103
104 return new AggregateChangeHandlerRegistration(registrations);
105 }
106
107 @Override
108 public LayeredResource getByPath(String path) {
109 List<Resource> matchingResources = origins.stream()
110 .filter(origin -> origin.hasPath(path))
111 .map(origin -> origin.getByPath(path))
112 .collect(toList());
113 if (matchingResources.isEmpty()) {
114 throw new ResourceNotFoundException(this, path);
115 }
116 return newLayeredResource(matchingResources);
117 }
118
119 @Override
120 public boolean hasPath(String path) {
121 return origins.stream().anyMatch(origin -> origin.hasPath(path));
122 }
123
124 @Override
125 protected boolean isFile(LayeredResource resource) {
126 return resource.getFirst().isFile();
127 }
128
129 @Override
130 protected boolean isDirectory(LayeredResource resource) {
131 return resource.getFirst().isDirectory();
132 }
133
134 @Override
135 protected boolean isEditable(LayeredResource resource) {
136 return resource.getFirst().isEditable();
137 }
138
139 @Override
140 protected String getPath(LayeredResource resource) {
141 return resource.getFirst().getPath();
142 }
143
144 @Override
145 protected String getName(LayeredResource resource) {
146 return resource.getFirst().getName();
147 }
148
149 @Override
150 protected long getLastModified(LayeredResource resource) {
151 return resource.getFirst().getLastModified();
152 }
153
154 @Override
155 protected List<LayeredResource> listChildren(LayeredResource resource) {
156 List<LayeredResource> layeredResources = new ArrayList<>();
157
158
159 final List<Resource> layers = resource.getLayers();
160 for (Resource layer : layers) {
161 List<Resource> layerChildren = layer.listChildren();
162
163
164 if (layeredResources.isEmpty()) {
165 layerChildren.stream()
166 .map(input -> new LayeredResource(LayeredResourceOrigin.this, input.getPath(), Lists.newArrayList(input)))
167 .forEachOrdered(layeredResources::add);
168 continue;
169 }
170
171 for (final Resource r : layerChildren) {
172 Optional<LayeredResource> matchingResource = layeredResources.stream()
173 .filter(input -> r.getPath().equals(input.getPath()) && r.isFile() == input.isFile())
174 .findFirst();
175 if (matchingResource.isPresent()) {
176 LayeredResource l = matchingResource.get();
177 l.getLayers().add(r);
178 } else {
179 LayeredResource l = new LayeredResource(this, r.getPath(), Lists.newArrayList(r));
180 layeredResources.add(l);
181 }
182 }
183 }
184 return Ordering.from(comparing(Resource::getPath)).immutableSortedCopy(layeredResources);
185 }
186
187 @Override
188 protected LayeredResource getParent(LayeredResource resource) {
189
190
191
192
193 Resource parent = resource.getFirst().getParent();
194 return parent != null ? getByPath(parent.getPath()) : null;
195 }
196
197 @Override
198 protected InputStream doOpenStream(LayeredResource resource) throws IOException {
199 return resource.getFirst().openStream();
200 }
201
202 @Override
203 protected Reader openReader(LayeredResource resource) throws IOException {
204 return resource.getFirst().openReader();
205 }
206
207
208
209
210
211
212 @Override
213 protected Charset getCharsetFor(LayeredResource resource) {
214 throw new IllegalStateException("This method should not be called");
215 }
216
217 protected LayeredResource newLayeredResource(List<Resource> resources) {
218
219 final String path = resources.get(0).getPath();
220 final boolean isDirectory = resources.get(0).isDirectory();
221 if (!resources.stream().allMatch(Functions.pathEquals(path))) {
222 throw new IllegalStateException("Given resources don't match path [" + path + "]: " + resources);
223 }
224 Predicate<Resource> dirOrFilePredicate = isDirectory ? Resource::isDirectory : Resource::isFile;
225 if (!resources.stream().allMatch(dirOrFilePredicate)) {
226 log.warn("Resources at {} are not all directory/file: {}", path, resources);
227 resources = resources.stream()
228 .filter(dirOrFilePredicate)
229 .collect(toList());
230 }
231 return new LayeredResource(this, path, resources);
232 }
233
234
235
236
237
238 private static class AggregateChangeHandlerRegistration implements ResourceChangeHandlerRegistration {
239
240 private final List<ResourceChangeHandlerRegistration> registrations;
241
242 public AggregateChangeHandlerRegistration(List<ResourceChangeHandlerRegistration> registrations) {
243 this.registrations = registrations;
244 }
245
246 @Override
247 public void unRegister() {
248 for (final ResourceChangeHandlerRegistration registration : registrations) {
249 registration.unRegister();
250 }
251
252 }
253 }
254
255
256
257
258
259
260
261
262
263
264 private class RelayingChangeHandler implements ResourceChangeHandler {
265
266 private final ResourceChangeHandler delegate;
267
268 RelayingChangeHandler(ResourceChangeHandler delegate) {
269 this.delegate = delegate;
270 }
271
272 @Override
273 public void onResourceChanged(ResourceOriginChange change) {
274 final Optional<LayeredResource> resource = hasPath(change.getRelatedResourcePath()) ? Optional.of(getByPath(change.getRelatedResourcePath())) : Optional.empty();
275 final ResourceOriginChange.Builder relayedChange =
276 resourceChange().
277 inOrigin(LayeredResourceOrigin.this).
278 at(change.getRelatedResourcePath());
279
280 switch (change.getType()) {
281 case MODIFIED:
282
283 if (resource.isPresent() && resource.get().getFirst().getOrigin().equals(change.getRelatedOrigin())) {
284 delegate.onResourceChanged(relayedChange.ofType(MODIFIED).build());
285 }
286 break;
287 case ADDED:
288
289 if (resource.isPresent() && resource.get().getFirst().getOrigin().equals(change.getRelatedOrigin())) {
290 delegate.onResourceChanged(relayedChange.ofType(ADDED).build());
291 }
292 break;
293 case REMOVED:
294
295 if (!resource.isPresent()) {
296 delegate.onResourceChanged(relayedChange.ofType(REMOVED).build());
297 } else {
298
299 delegate.onResourceChanged(relayedChange.ofType(MODIFIED).build());
300 }
301 break;
302 }
303 }
304 }
305 }