AemObjectInjector.java

  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. import java.lang.reflect.AnnotatedElement;
  22. import java.lang.reflect.Type;
  23. import java.util.Locale;

  24. import org.apache.commons.lang3.StringUtils;
  25. import org.apache.sling.api.SlingHttpServletRequest;
  26. import org.apache.sling.api.resource.Resource;
  27. import org.apache.sling.api.resource.ResourceResolver;
  28. import org.apache.sling.api.scripting.SlingBindings;
  29. import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
  30. import org.apache.sling.models.spi.AcceptsNullName;
  31. import org.apache.sling.models.spi.DisposalCallbackRegistry;
  32. import org.apache.sling.models.spi.Injector;
  33. import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor2;
  34. import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor2;
  35. import org.apache.sling.models.spi.injectorspecific.StaticInjectAnnotationProcessorFactory;
  36. import org.apache.sling.xss.XSSAPI;
  37. import org.jetbrains.annotations.NotNull;
  38. import org.jetbrains.annotations.Nullable;
  39. import org.osgi.framework.Constants;
  40. import org.osgi.service.component.annotations.Component;
  41. import org.osgi.service.component.annotations.Reference;

  42. import com.adobe.cq.sightly.WCMBindings;
  43. import com.adobe.granite.workflow.WorkflowSession;
  44. import com.day.cq.i18n.I18n;
  45. import com.day.cq.tagging.TagManager;
  46. import com.day.cq.wcm.api.AuthoringUIMode;
  47. import com.day.cq.wcm.api.Page;
  48. import com.day.cq.wcm.api.PageManager;
  49. import com.day.cq.wcm.api.WCMMode;
  50. import com.day.cq.wcm.api.components.ComponentContext;
  51. import com.day.cq.wcm.api.designer.Design;
  52. import com.day.cq.wcm.api.designer.Designer;
  53. import com.day.cq.wcm.api.designer.Style;
  54. import com.day.cq.wcm.commons.WCMUtils;

  55. import io.wcm.sling.commons.request.RequestContext;
  56. import io.wcm.sling.models.annotations.AemObject;

  57. /**
  58.  * Injects common AEM objects that can be derived from a SlingHttpServletRequest.
  59.  * Documentation see {@link AemObject}.
  60.  */
  61. @Component(service = { Injector.class, StaticInjectAnnotationProcessorFactory.class }, property = {
  62.     /*
  63.      * SERVICE_RANKING of this service should be lower than the ranking of the OsgiServiceInjector (5000),
  64.      * otherwise the generic XSSAPI service would be injected from the OSGi Service Registry instead of the
  65.      * pre-configured from the current request.
  66.      * Additionally it should be lower than the ACS commons AemObjectInjector (4500).
  67.      */
  68.     Constants.SERVICE_RANKING + ":Integer=" + 4400
  69. })
  70. public final class AemObjectInjector implements Injector, StaticInjectAnnotationProcessorFactory, AcceptsNullName {

  71.   /**
  72.    * Injector name
  73.    */
  74.   public static final @NotNull String NAME = "wcm-io-aem-object";

  75.   static final String RESOURCE_PAGE = "resourcePage";
  76.   static final String USER_I18N = "userI18n";

  77.   @Reference
  78.   private RequestContext requestContext;
  79.   @Reference
  80.   private ModelsImplConfiguration modelsImplConfiguration;

  81.   @Override
  82.   public @NotNull String getName() {
  83.     return NAME;
  84.   }

  85.   @Override
  86.   @SuppressWarnings("java:S3776") // complexity
  87.   public Object getValue(@NotNull final Object adaptable, final String name, @NotNull final Type type,
  88.       @NotNull final AnnotatedElement element, @NotNull final DisposalCallbackRegistry callbackRegistry) {

  89.     // only class types are supported
  90.     if (!(type instanceof Class<?>)) {
  91.       return null;
  92.     }
  93.     Class<?> requestedClass = (Class<?>)type;

  94.     SlingHttpServletRequest request = getRequest(adaptable);
  95.     if (request != null) {
  96.       if (requestedClass.equals(WCMMode.class)) {
  97.         return getWcmMode(request);
  98.       }
  99.       if (requestedClass.equals(AuthoringUIMode.class)) {
  100.         return getAuthoringUiMode(request);
  101.       }
  102.       if (requestedClass.equals(ComponentContext.class)) {
  103.         return getComponentContext(request);
  104.       }
  105.       if (requestedClass.equals(Style.class)) {
  106.         return getStyle(request);
  107.       }
  108.       if (requestedClass.equals(XSSAPI.class)) {
  109.         return getXssApi(request);
  110.       }
  111.       if (requestedClass.equals(I18n.class)) {
  112.         if (StringUtils.equals(name, USER_I18N)) {
  113.           return getUserI18n(request);
  114.         }
  115.         else {
  116.           return getResourceI18n(request);
  117.         }
  118.       }
  119.     }

  120.     if (requestedClass.equals(PageManager.class)) {
  121.       return getPageManager(adaptable);
  122.     }
  123.     else if (requestedClass.equals(Page.class)) {
  124.       if (StringUtils.equals(name, RESOURCE_PAGE)) {
  125.         return getResourcePage(adaptable);
  126.       }
  127.       else {
  128.         return getCurrentPage(adaptable);
  129.       }
  130.     }
  131.     else if (requestedClass.equals(Designer.class)) {
  132.       return getDesigner(adaptable);
  133.     }
  134.     else if (requestedClass.equals(Design.class)) {
  135.       return getCurrentDesign(adaptable);
  136.     }
  137.     else if (requestedClass.equals(TagManager.class)) {
  138.       return getTagManager(adaptable);
  139.     }
  140.     else if (requestedClass.equals(WorkflowSession.class)) {
  141.       return getWorkflowSession(adaptable);
  142.     }

  143.     return null;
  144.   }

  145.   private @Nullable SlingHttpServletRequest getRequest(@NotNull final Object adaptable) {
  146.     if (adaptable instanceof SlingHttpServletRequest) {
  147.       return (SlingHttpServletRequest)adaptable;
  148.     }
  149.     else if (modelsImplConfiguration.isRequestThreadLocal()) {
  150.       return requestContext.getThreadRequest();
  151.     }
  152.     else {
  153.       return null;
  154.     }
  155.   }

  156.   private @Nullable ResourceResolver getResourceResolver(@NotNull final Object adaptable) {
  157.     if (adaptable instanceof ResourceResolver) {
  158.       return (ResourceResolver)adaptable;
  159.     }
  160.     if (adaptable instanceof Resource) {
  161.       return ((Resource)adaptable).getResourceResolver();
  162.     }
  163.     if (adaptable instanceof Page) {
  164.       Resource resource = ((Page)adaptable).adaptTo(Resource.class);
  165.       if (resource != null) {
  166.         return resource.getResourceResolver();
  167.       }
  168.     }
  169.     SlingHttpServletRequest request = getRequest(adaptable);
  170.     if (request != null) {
  171.       return request.getResourceResolver();
  172.     }
  173.     return null;
  174.   }

  175.   private @Nullable Resource getResource(@NotNull final Object adaptable) {
  176.     if (adaptable instanceof Resource) {
  177.       return (Resource)adaptable;
  178.     }
  179.     if (adaptable instanceof Page) {
  180.       return ((Page)adaptable).adaptTo(Resource.class);
  181.     }
  182.     SlingHttpServletRequest request = getRequest(adaptable);
  183.     if (request != null) {
  184.       return request.getResource();
  185.     }
  186.     return null;
  187.   }

  188.   private @Nullable PageManager getPageManager(@NotNull final Object adaptable) {
  189.     ResourceResolver resolver = getResourceResolver(adaptable);
  190.     if (resolver != null) {
  191.       return resolver.adaptTo(PageManager.class);
  192.     }
  193.     return null;
  194.   }

  195.   private @Nullable Designer getDesigner(@NotNull final Object adaptable) {
  196.     ResourceResolver resolver = getResourceResolver(adaptable);
  197.     if (resolver != null) {
  198.       return resolver.adaptTo(Designer.class);
  199.     }
  200.     return null;
  201.   }

  202.   private @Nullable Page getCurrentPage(@NotNull final Object adaptable) {
  203.     SlingHttpServletRequest request = getRequest(adaptable);
  204.     if (request != null) {
  205.       ComponentContext context = getComponentContext(request);
  206.       if (context != null) {
  207.         return context.getPage();
  208.       }
  209.     }
  210.     return getResourcePage(adaptable);
  211.   }

  212.   private @Nullable Page getResourcePage(@NotNull final Object adaptable) {
  213.     PageManager pageManager = getPageManager(adaptable);
  214.     Resource resource = getResource(adaptable);
  215.     if (pageManager != null && resource != null) {
  216.       return pageManager.getContainingPage(resource);
  217.     }
  218.     return null;
  219.   }

  220.   private @NotNull WCMMode getWcmMode(@NotNull final SlingHttpServletRequest request) {
  221.     return WCMMode.fromRequest(request);
  222.   }

  223.   private @NotNull AuthoringUIMode getAuthoringUiMode(@NotNull final SlingHttpServletRequest request) {
  224.     AuthoringUIMode mode = AuthoringUIMode.fromRequest(request);
  225.     if (mode == null) {
  226.       // if no mode is set (e.g. if WCMMode is disabled) default to Touch UI
  227.       mode = AuthoringUIMode.TOUCH;
  228.     }
  229.     return mode;
  230.   }

  231.   private @Nullable ComponentContext getComponentContext(@NotNull final SlingHttpServletRequest request) {
  232.     return WCMUtils.getComponentContext(request);
  233.   }

  234.   private @Nullable Design getCurrentDesign(final Object adaptable) {
  235.     Page currentPage = getCurrentPage(adaptable);
  236.     Designer designer = getDesigner(adaptable);
  237.     if (currentPage != null && designer != null) {
  238.       return designer.getDesign(currentPage);
  239.     }
  240.     return null;
  241.   }

  242.   @SuppressWarnings("deprecation")
  243.   private @Nullable Style getStyle(@NotNull final SlingHttpServletRequest request) {
  244.     Style style = null;
  245.     // first try to get from sling bindings
  246.     SlingBindings slingBindings = getSlingBindings(request);
  247.     if (slingBindings != null) {
  248.       style = (Style)slingBindings.get(WCMBindings.CURRENT_STYLE);
  249.     }
  250.     if (style == null) {
  251.       // fallback to current design
  252.       Design currentDesign = getCurrentDesign(request);
  253.       ComponentContext componentContext = getComponentContext(request);
  254.       if (currentDesign != null && componentContext != null) {
  255.         style = currentDesign.getStyle(componentContext.getCell());
  256.       }
  257.     }
  258.     return style;
  259.   }

  260.   private @Nullable XSSAPI getXssApi(@NotNull final SlingHttpServletRequest request) {
  261.     return request.adaptTo(XSSAPI.class);
  262.   }

  263.   private @Nullable I18n getResourceI18n(@NotNull final SlingHttpServletRequest request) {
  264.     Page currentPage = getCurrentPage(request);
  265.     if (currentPage != null) {
  266.       Locale currentLocale = currentPage.getLanguage(false);
  267.       return new I18n(getI18nEnabledRequest(request).getResourceBundle(currentLocale));
  268.     }
  269.     return null;
  270.   }

  271.   private @NotNull I18n getUserI18n(@NotNull final SlingHttpServletRequest request) {
  272.     return new I18n(getI18nEnabledRequest(request));
  273.   }

  274.   private @Nullable TagManager getTagManager(@NotNull final Object adaptable) {
  275.     ResourceResolver resolver = getResourceResolver(adaptable);
  276.     if (resolver != null) {
  277.       return resolver.adaptTo(TagManager.class);
  278.     }
  279.     return null;
  280.   }

  281.   private @Nullable WorkflowSession getWorkflowSession(@NotNull final Object adaptable) {
  282.     ResourceResolver resolver = getResourceResolver(adaptable);
  283.     if (resolver != null) {
  284.       return resolver.adaptTo(WorkflowSession.class);
  285.     }
  286.     return null;
  287.   }

  288.   /**
  289.    * Returns a sling request that has a resource bundle set. Due to several wrappings inside Sling
  290.    * this is not always the request that is available in the script or java code initiating the injection.
  291.    * If a SlingBindings object is available the request from this is returned.
  292.    * If not it is checked if the current request that was recorded in a ThreadLocal can be used.
  293.    * As a last resort a fallback to the request that was used for the adaption is returned, but this
  294.    * is likely to not be i18n-enabled.
  295.    * @param request Original request
  296.    * @return Request from sling bindings
  297.    */
  298.   private @NotNull SlingHttpServletRequest getI18nEnabledRequest(@NotNull SlingHttpServletRequest request) {
  299.     SlingBindings bindings = getSlingBindings(request);
  300.     if (bindings != null) {
  301.       SlingHttpServletRequest bindingsRequest = bindings.getRequest();
  302.       if (bindingsRequest != null) {
  303.         return bindingsRequest;
  304.       }
  305.     }
  306.     if (modelsImplConfiguration.isRequestThreadLocal()) {
  307.       SlingHttpServletRequest threadLocalRequest = requestContext.getThreadRequest();
  308.       if (threadLocalRequest != null) {
  309.         return threadLocalRequest;
  310.       }
  311.     }
  312.     return request;
  313.   }

  314.   private @Nullable SlingBindings getSlingBindings(@NotNull SlingHttpServletRequest request) {
  315.     return (SlingBindings)request.getAttribute(SlingBindings.class.getName());
  316.   }

  317.   @SuppressWarnings({ "null", "unused" })
  318.   @Override
  319.   public InjectAnnotationProcessor2 createAnnotationProcessor(final AnnotatedElement element) {
  320.     // check if the element has the expected annotation
  321.     AemObject annotation = element.getAnnotation(AemObject.class);
  322.     if (annotation != null) {
  323.       return new AemObjectAnnotationProcessor(annotation);
  324.     }
  325.     return null;
  326.   }

  327.   private static class AemObjectAnnotationProcessor extends AbstractInjectAnnotationProcessor2 {

  328.     private final AemObject annotation;

  329.     AemObjectAnnotationProcessor(final AemObject annotation) {
  330.       this.annotation = annotation;
  331.     }

  332.     @Override
  333.     public InjectionStrategy getInjectionStrategy() {
  334.       return annotation.injectionStrategy();
  335.     }

  336.     @Override
  337.     @SuppressWarnings("deprecation")
  338.     public Boolean isOptional() {
  339.       return annotation.optional();
  340.     }
  341.   }

  342. }