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({
66        "unchecked", "null"
67    })
68    @Override
69    public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
70      if (type == UrlHandlerConfig.class) {
71        return (AdapterType)serviceResolver.resolve(UrlHandlerConfig.class, (Adaptable)adaptable);
72      }
73      if (type == SiteConfig.class) {
74        return (AdapterType)getSiteConfigForSiteRoot(getContextResource(adaptable));
75      }
76      return null;
77    }
78  
79    private Resource getContextResource(Object adaptable) {
80      if (adaptable instanceof Resource) {
81        return (Resource)adaptable;
82      }
83      else if (adaptable instanceof SlingHttpServletRequest) {
84        return ((SlingHttpServletRequest)adaptable).getResource();
85      }
86      return null;
87    }
88  
89    private SiteConfig getSiteConfigForSiteRoot(Resource contextResource) {
90      if (contextResource == null) {
91        return null;
92      }
93      String contextRootPath = configurationResourceResolver.getContextPath(contextResource);
94  
95      // site root cannot be detected? then get SiteConfig directly from resource without any caching
96      if (StringUtils.isBlank(contextRootPath)) {
97        return getSiteConfigForResource(contextResource);
98      }
99  
100     // get site config for site root resource and cache the result (for a short time)
101     return siteConfigCache.get(contextRootPath, path -> {
102       Resource siteRootResource = contextResource.getResourceResolver().getResource(contextRootPath);
103       return getSiteConfigForResourceCacheable(siteRootResource);
104     });
105   }
106 
107   /**
108    * Converts the SiteConfig instance to a newly created instance, because the original implementation
109    * implements lazy property reading and is connected to the original resource resolver implementation.
110    * @param contextResource Context resource
111    * @return Cacheable site configuration
112    */
113   private SiteConfig getSiteConfigForResourceCacheable(Resource contextResource) {
114     final SiteConfig siteConfig = getSiteConfigForResource(contextResource);
115     return new SiteConfig() {
116 
117       @Override
118       public Class<? extends Annotation> annotationType() {
119         return SiteConfig.class;
120       }
121 
122       @Override
123       public String siteUrl() {
124         return siteConfig.siteUrl();
125       }
126 
127       @Override
128       public String siteUrlSecure() {
129         return siteConfig.siteUrlSecure();
130       }
131 
132       @Override
133       public String siteUrlAuthor() {
134         return siteConfig.siteUrlAuthor();
135       }
136 
137     };
138   }
139 
140   @SuppressWarnings("java:S112") // allow runtime exception
141   private SiteConfig getSiteConfigForResource(Resource contextResource) {
142     ConfigurationBuilder configurationBuilder = contextResource.adaptTo(ConfigurationBuilder.class);
143     if (configurationBuilder == null) {
144       throw new RuntimeException("No configuration builder.");
145     }
146     return configurationBuilder.as(SiteConfig.class);
147   }
148 
149 }