View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2014-2015 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.wcm.ui.granite.resource;
21  
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.LinkedHashMap;
26  import java.util.Map;
27  
28  import org.apache.sling.api.resource.Resource;
29  import org.apache.sling.api.resource.ResourceMetadata;
30  import org.apache.sling.api.resource.ResourceResolver;
31  import org.apache.sling.api.resource.SyntheticResource;
32  import org.apache.sling.api.resource.ValueMap;
33  import org.apache.sling.api.wrappers.ValueMapDecorator;
34  import org.jetbrains.annotations.NotNull;
35  import org.osgi.annotation.versioning.ProviderType;
36  
37  import com.day.cq.commons.jcr.JcrConstants;
38  
39  /**
40   * Extended version of {@link SyntheticResource} that allows to pass an own value map and optional child resources.
41   * Please note: Accessing child resources does only work when accessing {@link Resource#listChildren()}, and
42   * not when calling the same method on resourceResolver. This breaks the contract of the resource API, but should
43   * work at least for the Granite UI implementation which seems to always use this method.
44   */
45  @ProviderType
46  public final class GraniteUiSyntheticResource extends SyntheticResource {
47  
48    private final ValueMap props;
49    private final Map<String, Resource> children;
50  
51    private GraniteUiSyntheticResource(ResourceResolver resourceResolver,
52        ResourceMetadata resourceMetadata, String resourceType,
53        ValueMap props, Iterable<Resource> children) {
54      super(resourceResolver, resourceMetadata, resourceType);
55      this.props = props;
56      this.children = childrenMap(children);
57    }
58  
59    private GraniteUiSyntheticResource(ResourceResolver resourceResolver,
60        String path,
61        String resourceType,
62        ValueMap props,
63        Iterable<Resource> children) {
64      super(resourceResolver, path, resourceType);
65      this.props = props;
66      this.children = childrenMap(children);
67    }
68  
69    private static Map<String, Resource> childrenMap(Iterable<Resource> children) {
70      Map<String, Resource> result = new LinkedHashMap<>();
71      children.forEach(resource -> result.put(resource.getName(), resource));
72      return result;
73    }
74  
75    @SuppressWarnings({
76        "unchecked", "null"
77    })
78    @Override
79    public <Type> Type adaptTo(Class<Type> type) {
80      if (ValueMap.class.equals(type)) {
81        return (Type)props;
82      }
83      else {
84        return super.adaptTo(type);
85      }
86    }
87  
88    @Override
89    public Iterator<Resource> listChildren() {
90      return children.values().iterator();
91    }
92  
93    @Override
94    public Iterable<Resource> getChildren() {
95      return children.values();
96    }
97  
98    @Override
99    public boolean hasChildren() {
100     return !children.isEmpty();
101   }
102 
103   @Override
104   public Resource getChild(String relPath) {
105     // naive implementation that only covers the simplest-possible case to detect the correct child
106     Resource child = children.get(relPath);
107     if (child != null) {
108       return child;
109     }
110     return super.getChild(relPath);
111   }
112 
113   private void addChild(Resource child) {
114     children.put(child.getName(), child);
115   }
116 
117   /**
118    * Create synthetic resource.
119    * @param resourceResolver Resource resolver
120    * @param valueMap Properties
121    * @return Resource
122    */
123   public static Resource create(@NotNull ResourceResolver resourceResolver, @NotNull ValueMap valueMap) {
124     return create(resourceResolver, "", JcrConstants.NT_UNSTRUCTURED, valueMap);
125   }
126 
127   /**
128    * Create synthetic resource.
129    * @param resourceResolver Resource resolver
130    * @param path Resource path
131    * @param resourceType Resource type
132    * @return Resource
133    */
134   public static Resource create(@NotNull ResourceResolver resourceResolver, @NotNull String path, @NotNull String resourceType) {
135     return create(resourceResolver, path, resourceType, ValueMap.EMPTY);
136   }
137 
138   /**
139    * Create synthetic resource.
140    * @param resourceResolver Resource resolver
141    * @param path Resource path
142    * @param resourceType Resource type
143    * @param valueMap Properties
144    * @return Resource
145    */
146   public static Resource create(@NotNull ResourceResolver resourceResolver, @NotNull String path, @NotNull String resourceType,
147       @NotNull ValueMap valueMap) {
148     return new GraniteUiSyntheticResource(resourceResolver,
149         path,
150         resourceType,
151         valueMap,
152         Collections.emptyList());
153   }
154 
155   /**
156    * Wrap a real resource and create a synthetic resource out of it.
157    * @param resource Real resource
158    * @return Resource
159    */
160   public static Resource wrap(@NotNull Resource resource) {
161     return wrap(resource, resource.getValueMap(), resource.getChildren());
162   }
163 
164   /**
165    * Wrap a real resource and create a synthetic resource out of it.
166    * @param resource Real resource
167    * @param valueMap Properties to use instead of the real properties
168    * @return Resource
169    */
170   public static Resource wrap(@NotNull Resource resource, @NotNull ValueMap valueMap) {
171     return wrap(resource, valueMap, resource.getChildren());
172   }
173 
174   /**
175    * Wrap a real resource and create a synthetic resource out of it.
176    * Merges the given properties with the existing properties of the resource.
177    * @param resource Real resource
178    * @param valueMap Properties to be merged with the real properties
179    * @return Resource
180    */
181   public static Resource wrapMerge(@NotNull Resource resource, @NotNull ValueMap valueMap) {
182     Map<String, Object> mergedProperties = new HashMap<>();
183     mergedProperties.putAll(resource.getValueMap());
184     mergedProperties.putAll(valueMap);
185     return wrap(resource, new ValueMapDecorator(mergedProperties), resource.getChildren());
186   }
187 
188   private static Resource wrap(Resource resource, ValueMap valueMap, Iterable<Resource> children) {
189     return new GraniteUiSyntheticResource(resource.getResourceResolver(),
190         resource.getResourceMetadata(),
191         resource.getResourceType(),
192         valueMap,
193         children);
194   }
195 
196   /**
197    * Create synthetic resource child resource of the given parent resource.
198    * @param parentResource Parent resource (has to be a {@link GraniteUiSyntheticResource} instance)
199    * @param name Child resource name
200    * @param resourceType Resource type
201    * @return Resource
202    */
203   public static Resource child(@NotNull Resource parentResource, @NotNull String name, @NotNull String resourceType) {
204     return child(parentResource, name, resourceType, ValueMap.EMPTY);
205   }
206 
207   /**
208    * Create synthetic resource child resource of the given parent resource.
209    * @param parentResource Parent resource (has to be a {@link GraniteUiSyntheticResource} instance)
210    * @param name Child resource name
211    * @param resourceType Resource type
212    * @param valueMap Properties
213    * @return Resource
214    */
215   public static Resource child(@NotNull Resource parentResource, @NotNull String name, @NotNull String resourceType,
216       @NotNull ValueMap valueMap) {
217     Resource child = new GraniteUiSyntheticResource(parentResource.getResourceResolver(),
218         parentResource.getPath() + "/" + name,
219         resourceType,
220         valueMap,
221         Collections.emptyList());
222     if (parentResource instanceof GraniteUiSyntheticResource) {
223       ((GraniteUiSyntheticResource)parentResource).addChild(child);
224     }
225     else {
226       throw new IllegalArgumentException("Resource is not a GraniteUiSyntheticResource.");
227     }
228     return child;
229   }
230 
231   /**
232    * Copy the given source resource as synthetic child under the target parent resource, including all children.
233    * @param targetParent Target parent resource
234    * @param source Source resource
235    */
236   public static void copySubtree(@NotNull Resource targetParent, @NotNull Resource source) {
237     Resource targetChild = child(targetParent, source.getName(), source.getResourceType(), source.getValueMap());
238     for (Resource sourceChild : source.getChildren()) {
239       copySubtree(targetChild, sourceChild);
240     }
241   }
242 
243 }