View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2017 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.handler.url.impl;
21  
22  import java.lang.annotation.Annotation;
23  import java.util.concurrent.TimeUnit;
24  
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.sling.api.SlingHttpServletRequest;
27  import org.apache.sling.api.adapter.Adaptable;
28  import org.apache.sling.api.adapter.AdapterFactory;
29  import org.apache.sling.api.resource.Resource;
30  import org.apache.sling.caconfig.ConfigurationBuilder;
31  import org.apache.sling.caconfig.resource.ConfigurationResourceResolver;
32  import org.osgi.service.component.annotations.Component;
33  import org.osgi.service.component.annotations.Reference;
34  
35  import com.github.benmanes.caffeine.cache.Cache;
36  import com.github.benmanes.caffeine.cache.Caffeine;
37  
38  import io.wcm.handler.url.SiteConfig;
39  import io.wcm.handler.url.spi.UrlHandlerConfig;
40  import io.wcm.sling.commons.caservice.ContextAwareServiceResolver;
41  
42  /**
43   * Adapts resources or requests to {@link UrlHandlerConfig} and {@link SiteConfig}.
44   */
45  @Component(service = AdapterFactory.class,
46      property = {
47          AdapterFactory.ADAPTABLE_CLASSES + "=org.apache.sling.api.resource.Resource",
48          AdapterFactory.ADAPTABLE_CLASSES + "=org.apache.sling.api.SlingHttpServletRequest",
49          AdapterFactory.ADAPTER_CLASSES + "=io.wcm.handler.url.spi.UrlHandlerConfig",
50          AdapterFactory.ADAPTER_CLASSES + "=io.wcm.handler.url.SiteConfig"
51      })
52  public class UrlHandlerAdapterFactory implements AdapterFactory {
53  
54    @Reference
55    private ContextAwareServiceResolver serviceResolver;
56    @Reference
57    private ConfigurationResourceResolver configurationResourceResolver;
58  
59    // cache resolving of site root level per resource path
60    private final Cache<String, SiteConfig> siteConfigCache = Caffeine.newBuilder()
61        .expireAfterWrite(5, TimeUnit.SECONDS)
62        .maximumSize(10000)
63        .build();
64  
65    @SuppressWarnings({ "unchecked", "null" })
66    @Override
67    public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
68      if (type == UrlHandlerConfig.class) {
69        return (AdapterType)serviceResolver.resolve(UrlHandlerConfig.class, (Adaptable)adaptable);
70      }
71      if (type == SiteConfig.class) {
72        return (AdapterType)getSiteConfigForSiteRoot(getContextResource(adaptable));
73      }
74      return null;
75    }
76  
77    private Resource getContextResource(Object adaptable) {
78      if (adaptable instanceof Resource) {
79        return (Resource)adaptable;
80      }
81      else if (adaptable instanceof SlingHttpServletRequest) {
82        return ((SlingHttpServletRequest)adaptable).getResource();
83      }
84      return null;
85    }
86  
87    private SiteConfig getSiteConfigForSiteRoot(Resource contextResource) {
88      if (contextResource == null) {
89        return null;
90      }
91      String contextRootPath = configurationResourceResolver.getContextPath(contextResource);
92  
93      // site root cannot be detected? then get SiteConfig directly from resource without any caching
94      if (StringUtils.isBlank(contextRootPath)) {
95        return getSiteConfigForResource(contextResource);
96      }
97  
98      // get site config for site root resource and cache the result (for a short time)
99      return siteConfigCache.get(contextRootPath, path -> {
100       Resource siteRootResource = contextResource.getResourceResolver().getResource(contextRootPath);
101       return getSiteConfigForResourceCacheable(siteRootResource);
102     });
103   }
104 
105   /**
106    * Converts the SiteConfig instance to a newly created instance, because the original implementation
107    * implements lazy property reading and is connected to the original resource resolver implementation.
108    * @param contextResource Context resource
109    * @return Cacheable site configuration
110    */
111   private SiteConfig getSiteConfigForResourceCacheable(Resource contextResource) {
112     final SiteConfig siteConfig = getSiteConfigForResource(contextResource);
113     return new SiteConfig() {
114       @Override
115       public Class<? extends Annotation> annotationType() {
116         return SiteConfig.class;
117       }
118       @Override
119       public String siteUrl() {
120         return siteConfig.siteUrl();
121       }
122       @Override
123       public String siteUrlSecure() {
124         return siteConfig.siteUrlSecure();
125       }
126       @Override
127       public String siteUrlAuthor() {
128         return siteConfig.siteUrlAuthor();
129       }
130 
131     };
132   }
133 
134   @SuppressWarnings("java:S112") // allow runtime exception
135   private SiteConfig getSiteConfigForResource(Resource contextResource) {
136     ConfigurationBuilder configurationBuilder = contextResource.adaptTo(ConfigurationBuilder.class);
137     if (configurationBuilder == null) {
138       throw new RuntimeException("No configuration builder.");
139     }
140     return configurationBuilder.as(SiteConfig.class);
141   }
142 
143 }