View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2014 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.sling.models.injectors.impl;
21  
22  import java.lang.reflect.AnnotatedElement;
23  import java.lang.reflect.Type;
24  import java.util.Locale;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.sling.api.SlingHttpServletRequest;
28  import org.apache.sling.api.resource.Resource;
29  import org.apache.sling.api.resource.ResourceResolver;
30  import org.apache.sling.api.scripting.SlingBindings;
31  import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
32  import org.apache.sling.models.spi.AcceptsNullName;
33  import org.apache.sling.models.spi.DisposalCallbackRegistry;
34  import org.apache.sling.models.spi.Injector;
35  import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor2;
36  import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor2;
37  import org.apache.sling.models.spi.injectorspecific.StaticInjectAnnotationProcessorFactory;
38  import org.apache.sling.xss.XSSAPI;
39  import org.jetbrains.annotations.NotNull;
40  import org.jetbrains.annotations.Nullable;
41  import org.osgi.framework.Constants;
42  import org.osgi.service.component.annotations.Component;
43  import org.osgi.service.component.annotations.Reference;
44  
45  import com.adobe.cq.sightly.WCMBindings;
46  import com.adobe.granite.workflow.WorkflowSession;
47  import com.day.cq.i18n.I18n;
48  import com.day.cq.tagging.TagManager;
49  import com.day.cq.wcm.api.AuthoringUIMode;
50  import com.day.cq.wcm.api.Page;
51  import com.day.cq.wcm.api.PageManager;
52  import com.day.cq.wcm.api.WCMMode;
53  import com.day.cq.wcm.api.components.ComponentContext;
54  import com.day.cq.wcm.api.designer.Design;
55  import com.day.cq.wcm.api.designer.Designer;
56  import com.day.cq.wcm.api.designer.Style;
57  import com.day.cq.wcm.commons.WCMUtils;
58  
59  import io.wcm.sling.commons.request.RequestContext;
60  import io.wcm.sling.models.annotations.AemObject;
61  
62  /**
63   * Injects common AEM objects that can be derived from a SlingHttpServletRequest.
64   * Documentation see {@link AemObject}.
65   */
66  @Component(service = { Injector.class, StaticInjectAnnotationProcessorFactory.class }, property = {
67      /*
68       * SERVICE_RANKING of this service should be lower than the ranking of the OsgiServiceInjector (5000),
69       * otherwise the generic XSSAPI service would be injected from the OSGi Service Registry instead of the
70       * pre-configured from the current request.
71       * Additionally it should be lower than the ACS commons AemObjectInjector (4500).
72       */
73      Constants.SERVICE_RANKING + ":Integer=" + 4400
74  })
75  public final class AemObjectInjector implements Injector, StaticInjectAnnotationProcessorFactory, AcceptsNullName {
76  
77    /**
78     * Injector name
79     */
80    public static final @NotNull String NAME = "wcm-io-aem-object";
81  
82    static final String RESOURCE_PAGE = "resourcePage";
83    static final String USER_I18N = "userI18n";
84  
85    @Reference
86    private RequestContext requestContext;
87    @Reference
88    private ModelsImplConfiguration modelsImplConfiguration;
89  
90    @Override
91    public @NotNull String getName() {
92      return NAME;
93    }
94  
95    @Override
96    @SuppressWarnings("java:S3776") // complexity
97    public Object getValue(@NotNull final Object adaptable, final String name, @NotNull final Type type,
98        @NotNull final AnnotatedElement element, @NotNull final DisposalCallbackRegistry callbackRegistry) {
99  
100     // only class types are supported
101     if (!(type instanceof Class<?>)) {
102       return null;
103     }
104     Class<?> requestedClass = (Class<?>)type;
105 
106     SlingHttpServletRequest request = getRequest(adaptable);
107     if (request != null) {
108       if (requestedClass.equals(WCMMode.class)) {
109         return getWcmMode(request);
110       }
111       if (requestedClass.equals(AuthoringUIMode.class)) {
112         return getAuthoringUiMode(request);
113       }
114       if (requestedClass.equals(ComponentContext.class)) {
115         return getComponentContext(request);
116       }
117       if (requestedClass.equals(Style.class)) {
118         return getStyle(request);
119       }
120       if (requestedClass.equals(XSSAPI.class)) {
121         return getXssApi(request);
122       }
123       if (requestedClass.equals(I18n.class)) {
124         if (StringUtils.equals(name, USER_I18N)) {
125           return getUserI18n(request);
126         }
127         else {
128           return getResourceI18n(request);
129         }
130       }
131     }
132 
133     if (requestedClass.equals(PageManager.class)) {
134       return getPageManager(adaptable);
135     }
136     else if (requestedClass.equals(Page.class)) {
137       if (StringUtils.equals(name, RESOURCE_PAGE)) {
138         return getResourcePage(adaptable);
139       }
140       else {
141         return getCurrentPage(adaptable);
142       }
143     }
144     else if (requestedClass.equals(Designer.class)) {
145       return getDesigner(adaptable);
146     }
147     else if (requestedClass.equals(Design.class)) {
148       return getCurrentDesign(adaptable);
149     }
150     else if (requestedClass.equals(TagManager.class)) {
151       return getTagManager(adaptable);
152     }
153     else if (requestedClass.equals(WorkflowSession.class)) {
154       return getWorkflowSession(adaptable);
155     }
156 
157     return null;
158   }
159 
160   private @Nullable SlingHttpServletRequest getRequest(@NotNull final Object adaptable) {
161     if (adaptable instanceof SlingHttpServletRequest) {
162       return (SlingHttpServletRequest)adaptable;
163     }
164     else if (modelsImplConfiguration.isRequestThreadLocal()) {
165       return requestContext.getThreadRequest();
166     }
167     else {
168       return null;
169     }
170   }
171 
172   private @Nullable ResourceResolver getResourceResolver(@NotNull final Object adaptable) {
173     if (adaptable instanceof ResourceResolver) {
174       return (ResourceResolver)adaptable;
175     }
176     if (adaptable instanceof Resource) {
177       return ((Resource)adaptable).getResourceResolver();
178     }
179     if (adaptable instanceof Page) {
180       Resource resource = ((Page)adaptable).adaptTo(Resource.class);
181       if (resource != null) {
182         return resource.getResourceResolver();
183       }
184     }
185     SlingHttpServletRequest request = getRequest(adaptable);
186     if (request != null) {
187       return request.getResourceResolver();
188     }
189     return null;
190   }
191 
192   private @Nullable Resource getResource(@NotNull final Object adaptable) {
193     if (adaptable instanceof Resource) {
194       return (Resource)adaptable;
195     }
196     if (adaptable instanceof Page) {
197       return ((Page)adaptable).adaptTo(Resource.class);
198     }
199     SlingHttpServletRequest request = getRequest(adaptable);
200     if (request != null) {
201       return request.getResource();
202     }
203     return null;
204   }
205 
206   private @Nullable PageManager getPageManager(@NotNull final Object adaptable) {
207     ResourceResolver resolver = getResourceResolver(adaptable);
208     if (resolver != null) {
209       return resolver.adaptTo(PageManager.class);
210     }
211     return null;
212   }
213 
214   private @Nullable Designer getDesigner(@NotNull final Object adaptable) {
215     ResourceResolver resolver = getResourceResolver(adaptable);
216     if (resolver != null) {
217       return resolver.adaptTo(Designer.class);
218     }
219     return null;
220   }
221 
222   private @Nullable Page getCurrentPage(@NotNull final Object adaptable) {
223     SlingHttpServletRequest request = getRequest(adaptable);
224     if (request != null) {
225       ComponentContext context = getComponentContext(request);
226       if (context != null) {
227         return context.getPage();
228       }
229     }
230     return getResourcePage(adaptable);
231   }
232 
233   private @Nullable Page getResourcePage(@NotNull final Object adaptable) {
234     PageManager pageManager = getPageManager(adaptable);
235     Resource resource = getResource(adaptable);
236     if (pageManager != null && resource != null) {
237       return pageManager.getContainingPage(resource);
238     }
239     return null;
240   }
241 
242   private @NotNull WCMMode getWcmMode(@NotNull final SlingHttpServletRequest request) {
243     return WCMMode.fromRequest(request);
244   }
245 
246   private @NotNull AuthoringUIMode getAuthoringUiMode(@NotNull final SlingHttpServletRequest request) {
247     AuthoringUIMode mode = AuthoringUIMode.fromRequest(request);
248     if (mode == null) {
249       // if no mode is set (e.g. if WCMMode is disabled) default to Touch UI
250       mode = AuthoringUIMode.TOUCH;
251     }
252     return mode;
253   }
254 
255   private @Nullable ComponentContext getComponentContext(@NotNull final SlingHttpServletRequest request) {
256     return WCMUtils.getComponentContext(request);
257   }
258 
259   private @Nullable Design getCurrentDesign(final Object adaptable) {
260     Page currentPage = getCurrentPage(adaptable);
261     Designer designer = getDesigner(adaptable);
262     if (currentPage != null && designer != null) {
263       return designer.getDesign(currentPage);
264     }
265     return null;
266   }
267 
268   @SuppressWarnings("deprecation")
269   private @Nullable Style getStyle(@NotNull final SlingHttpServletRequest request) {
270     Style style = null;
271     // first try to get from sling bindings
272     SlingBindings slingBindings = getSlingBindings(request);
273     if (slingBindings != null) {
274       style = (Style)slingBindings.get(WCMBindings.CURRENT_STYLE);
275     }
276     if (style == null) {
277       // fallback to current design
278       Design currentDesign = getCurrentDesign(request);
279       ComponentContext componentContext = getComponentContext(request);
280       if (currentDesign != null && componentContext != null) {
281         style = currentDesign.getStyle(componentContext.getCell());
282       }
283     }
284     return style;
285   }
286 
287   private @Nullable XSSAPI getXssApi(@NotNull final SlingHttpServletRequest request) {
288     return request.adaptTo(XSSAPI.class);
289   }
290 
291   private @Nullable I18n getResourceI18n(@NotNull final SlingHttpServletRequest request) {
292     Page currentPage = getCurrentPage(request);
293     if (currentPage != null) {
294       Locale currentLocale = currentPage.getLanguage(false);
295       return new I18n(getI18nEnabledRequest(request).getResourceBundle(currentLocale));
296     }
297     return null;
298   }
299 
300   private @NotNull I18n getUserI18n(@NotNull final SlingHttpServletRequest request) {
301     return new I18n(getI18nEnabledRequest(request));
302   }
303 
304   private @Nullable TagManager getTagManager(@NotNull final Object adaptable) {
305     ResourceResolver resolver = getResourceResolver(adaptable);
306     if (resolver != null) {
307       return resolver.adaptTo(TagManager.class);
308     }
309     return null;
310   }
311 
312   private @Nullable WorkflowSession getWorkflowSession(@NotNull final Object adaptable) {
313     ResourceResolver resolver = getResourceResolver(adaptable);
314     if (resolver != null) {
315       return resolver.adaptTo(WorkflowSession.class);
316     }
317     return null;
318   }
319 
320   /**
321    * Returns a sling request that has a resource bundle set. Due to several wrappings inside Sling
322    * this is not always the request that is available in the script or java code initiating the injection.
323    * If a SlingBindings object is available the request from this is returned.
324    * If not it is checked if the current request that was recorded in a ThreadLocal can be used.
325    * As a last resort a fallback to the request that was used for the adaption is returned, but this
326    * is likely to not be i18n-enabled.
327    * @param request Original request
328    * @return Request from sling bindings
329    */
330   private @NotNull SlingHttpServletRequest getI18nEnabledRequest(@NotNull SlingHttpServletRequest request) {
331     SlingBindings bindings = getSlingBindings(request);
332     if (bindings != null) {
333       SlingHttpServletRequest bindingsRequest = bindings.getRequest();
334       if (bindingsRequest != null) {
335         return bindingsRequest;
336       }
337     }
338     if (modelsImplConfiguration.isRequestThreadLocal()) {
339       SlingHttpServletRequest threadLocalRequest = requestContext.getThreadRequest();
340       if (threadLocalRequest != null) {
341         return threadLocalRequest;
342       }
343     }
344     return request;
345   }
346 
347   private @Nullable SlingBindings getSlingBindings(@NotNull SlingHttpServletRequest request) {
348     return (SlingBindings)request.getAttribute(SlingBindings.class.getName());
349   }
350 
351   @SuppressWarnings({ "null", "unused" })
352   @Override
353   public InjectAnnotationProcessor2 createAnnotationProcessor(final AnnotatedElement element) {
354     // check if the element has the expected annotation
355     AemObject annotation = element.getAnnotation(AemObject.class);
356     if (annotation != null) {
357       return new AemObjectAnnotationProcessor(annotation);
358     }
359     return null;
360   }
361 
362   private static class AemObjectAnnotationProcessor extends AbstractInjectAnnotationProcessor2 {
363 
364     private final AemObject annotation;
365 
366     AemObjectAnnotationProcessor(final AemObject annotation) {
367       this.annotation = annotation;
368     }
369 
370     @Override
371     public InjectionStrategy getInjectionStrategy() {
372       return annotation.injectionStrategy();
373     }
374 
375     @Override
376     @SuppressWarnings("deprecation")
377     public Boolean isOptional() {
378       return annotation.optional();
379     }
380   }
381 
382 }