AemObjectInjector.java
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2014 wcm.io
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package io.wcm.sling.models.injectors.impl;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Type;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.scripting.SlingBindings;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.spi.AcceptsNullName;
import org.apache.sling.models.spi.DisposalCallbackRegistry;
import org.apache.sling.models.spi.Injector;
import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor2;
import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor2;
import org.apache.sling.models.spi.injectorspecific.StaticInjectAnnotationProcessorFactory;
import org.apache.sling.xss.XSSAPI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import com.adobe.cq.sightly.WCMBindings;
import com.adobe.granite.workflow.WorkflowSession;
import com.day.cq.i18n.I18n;
import com.day.cq.tagging.TagManager;
import com.day.cq.wcm.api.AuthoringUIMode;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.WCMMode;
import com.day.cq.wcm.api.components.ComponentContext;
import com.day.cq.wcm.api.designer.Design;
import com.day.cq.wcm.api.designer.Designer;
import com.day.cq.wcm.api.designer.Style;
import com.day.cq.wcm.commons.WCMUtils;
import io.wcm.sling.commons.request.RequestContext;
import io.wcm.sling.models.annotations.AemObject;
/**
* Injects common AEM objects that can be derived from a SlingHttpServletRequest.
* Documentation see {@link AemObject}.
*/
@Component(service = { Injector.class, StaticInjectAnnotationProcessorFactory.class }, property = {
/*
* SERVICE_RANKING of this service should be lower than the ranking of the OsgiServiceInjector (5000),
* otherwise the generic XSSAPI service would be injected from the OSGi Service Registry instead of the
* pre-configured from the current request.
* Additionally it should be lower than the ACS commons AemObjectInjector (4500).
*/
Constants.SERVICE_RANKING + ":Integer=" + 4400
})
public final class AemObjectInjector implements Injector, StaticInjectAnnotationProcessorFactory, AcceptsNullName {
/**
* Injector name
*/
public static final @NotNull String NAME = "wcm-io-aem-object";
static final String RESOURCE_PAGE = "resourcePage";
static final String USER_I18N = "userI18n";
@Reference
private RequestContext requestContext;
@Reference
private ModelsImplConfiguration modelsImplConfiguration;
@Override
public @NotNull String getName() {
return NAME;
}
@Override
@SuppressWarnings("java:S3776") // complexity
public Object getValue(@NotNull final Object adaptable, final String name, @NotNull final Type type,
@NotNull final AnnotatedElement element, @NotNull final DisposalCallbackRegistry callbackRegistry) {
// only class types are supported
if (!(type instanceof Class<?>)) {
return null;
}
Class<?> requestedClass = (Class<?>)type;
SlingHttpServletRequest request = getRequest(adaptable);
if (request != null) {
if (requestedClass.equals(WCMMode.class)) {
return getWcmMode(request);
}
if (requestedClass.equals(AuthoringUIMode.class)) {
return getAuthoringUiMode(request);
}
if (requestedClass.equals(ComponentContext.class)) {
return getComponentContext(request);
}
if (requestedClass.equals(Style.class)) {
return getStyle(request);
}
if (requestedClass.equals(XSSAPI.class)) {
return getXssApi(request);
}
if (requestedClass.equals(I18n.class)) {
if (StringUtils.equals(name, USER_I18N)) {
return getUserI18n(request);
}
else {
return getResourceI18n(request);
}
}
}
if (requestedClass.equals(PageManager.class)) {
return getPageManager(adaptable);
}
else if (requestedClass.equals(Page.class)) {
if (StringUtils.equals(name, RESOURCE_PAGE)) {
return getResourcePage(adaptable);
}
else {
return getCurrentPage(adaptable);
}
}
else if (requestedClass.equals(Designer.class)) {
return getDesigner(adaptable);
}
else if (requestedClass.equals(Design.class)) {
return getCurrentDesign(adaptable);
}
else if (requestedClass.equals(TagManager.class)) {
return getTagManager(adaptable);
}
else if (requestedClass.equals(WorkflowSession.class)) {
return getWorkflowSession(adaptable);
}
return null;
}
private @Nullable SlingHttpServletRequest getRequest(@NotNull final Object adaptable) {
if (adaptable instanceof SlingHttpServletRequest) {
return (SlingHttpServletRequest)adaptable;
}
else if (modelsImplConfiguration.isRequestThreadLocal()) {
return requestContext.getThreadRequest();
}
else {
return null;
}
}
private @Nullable ResourceResolver getResourceResolver(@NotNull final Object adaptable) {
if (adaptable instanceof ResourceResolver) {
return (ResourceResolver)adaptable;
}
if (adaptable instanceof Resource) {
return ((Resource)adaptable).getResourceResolver();
}
if (adaptable instanceof Page) {
Resource resource = ((Page)adaptable).adaptTo(Resource.class);
if (resource != null) {
return resource.getResourceResolver();
}
}
SlingHttpServletRequest request = getRequest(adaptable);
if (request != null) {
return request.getResourceResolver();
}
return null;
}
private @Nullable Resource getResource(@NotNull final Object adaptable) {
if (adaptable instanceof Resource) {
return (Resource)adaptable;
}
if (adaptable instanceof Page) {
return ((Page)adaptable).adaptTo(Resource.class);
}
SlingHttpServletRequest request = getRequest(adaptable);
if (request != null) {
return request.getResource();
}
return null;
}
private @Nullable PageManager getPageManager(@NotNull final Object adaptable) {
ResourceResolver resolver = getResourceResolver(adaptable);
if (resolver != null) {
return resolver.adaptTo(PageManager.class);
}
return null;
}
private @Nullable Designer getDesigner(@NotNull final Object adaptable) {
ResourceResolver resolver = getResourceResolver(adaptable);
if (resolver != null) {
return resolver.adaptTo(Designer.class);
}
return null;
}
private @Nullable Page getCurrentPage(@NotNull final Object adaptable) {
SlingHttpServletRequest request = getRequest(adaptable);
if (request != null) {
ComponentContext context = getComponentContext(request);
if (context != null) {
return context.getPage();
}
}
return getResourcePage(adaptable);
}
private @Nullable Page getResourcePage(@NotNull final Object adaptable) {
PageManager pageManager = getPageManager(adaptable);
Resource resource = getResource(adaptable);
if (pageManager != null && resource != null) {
return pageManager.getContainingPage(resource);
}
return null;
}
private @NotNull WCMMode getWcmMode(@NotNull final SlingHttpServletRequest request) {
return WCMMode.fromRequest(request);
}
private @NotNull AuthoringUIMode getAuthoringUiMode(@NotNull final SlingHttpServletRequest request) {
AuthoringUIMode mode = AuthoringUIMode.fromRequest(request);
if (mode == null) {
// if no mode is set (e.g. if WCMMode is disabled) default to Touch UI
mode = AuthoringUIMode.TOUCH;
}
return mode;
}
private @Nullable ComponentContext getComponentContext(@NotNull final SlingHttpServletRequest request) {
return WCMUtils.getComponentContext(request);
}
private @Nullable Design getCurrentDesign(final Object adaptable) {
Page currentPage = getCurrentPage(adaptable);
Designer designer = getDesigner(adaptable);
if (currentPage != null && designer != null) {
return designer.getDesign(currentPage);
}
return null;
}
@SuppressWarnings("deprecation")
private @Nullable Style getStyle(@NotNull final SlingHttpServletRequest request) {
Style style = null;
// first try to get from sling bindings
SlingBindings slingBindings = getSlingBindings(request);
if (slingBindings != null) {
style = (Style)slingBindings.get(WCMBindings.CURRENT_STYLE);
}
if (style == null) {
// fallback to current design
Design currentDesign = getCurrentDesign(request);
ComponentContext componentContext = getComponentContext(request);
if (currentDesign != null && componentContext != null) {
style = currentDesign.getStyle(componentContext.getCell());
}
}
return style;
}
private @Nullable XSSAPI getXssApi(@NotNull final SlingHttpServletRequest request) {
return request.adaptTo(XSSAPI.class);
}
private @Nullable I18n getResourceI18n(@NotNull final SlingHttpServletRequest request) {
Page currentPage = getCurrentPage(request);
if (currentPage != null) {
Locale currentLocale = currentPage.getLanguage(false);
return new I18n(getI18nEnabledRequest(request).getResourceBundle(currentLocale));
}
return null;
}
private @NotNull I18n getUserI18n(@NotNull final SlingHttpServletRequest request) {
return new I18n(getI18nEnabledRequest(request));
}
private @Nullable TagManager getTagManager(@NotNull final Object adaptable) {
ResourceResolver resolver = getResourceResolver(adaptable);
if (resolver != null) {
return resolver.adaptTo(TagManager.class);
}
return null;
}
private @Nullable WorkflowSession getWorkflowSession(@NotNull final Object adaptable) {
ResourceResolver resolver = getResourceResolver(adaptable);
if (resolver != null) {
return resolver.adaptTo(WorkflowSession.class);
}
return null;
}
/**
* Returns a sling request that has a resource bundle set. Due to several wrappings inside Sling
* this is not always the request that is available in the script or java code initiating the injection.
* If a SlingBindings object is available the request from this is returned.
* If not it is checked if the current request that was recorded in a ThreadLocal can be used.
* As a last resort a fallback to the request that was used for the adaption is returned, but this
* is likely to not be i18n-enabled.
* @param request Original request
* @return Request from sling bindings
*/
private @NotNull SlingHttpServletRequest getI18nEnabledRequest(@NotNull SlingHttpServletRequest request) {
SlingBindings bindings = getSlingBindings(request);
if (bindings != null) {
SlingHttpServletRequest bindingsRequest = bindings.getRequest();
if (bindingsRequest != null) {
return bindingsRequest;
}
}
if (modelsImplConfiguration.isRequestThreadLocal()) {
SlingHttpServletRequest threadLocalRequest = requestContext.getThreadRequest();
if (threadLocalRequest != null) {
return threadLocalRequest;
}
}
return request;
}
private @Nullable SlingBindings getSlingBindings(@NotNull SlingHttpServletRequest request) {
return (SlingBindings)request.getAttribute(SlingBindings.class.getName());
}
@SuppressWarnings({ "null", "unused" })
@Override
public InjectAnnotationProcessor2 createAnnotationProcessor(final AnnotatedElement element) {
// check if the element has the expected annotation
AemObject annotation = element.getAnnotation(AemObject.class);
if (annotation != null) {
return new AemObjectAnnotationProcessor(annotation);
}
return null;
}
private static class AemObjectAnnotationProcessor extends AbstractInjectAnnotationProcessor2 {
private final AemObject annotation;
AemObjectAnnotationProcessor(final AemObject annotation) {
this.annotation = annotation;
}
@Override
public InjectionStrategy getInjectionStrategy() {
return annotation.injectionStrategy();
}
@Override
@SuppressWarnings("deprecation")
public Boolean isOptional() {
return annotation.optional();
}
}
}