1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.handler.url.impl.clientlib;
21
22 import static javax.jcr.observation.Event.NODE_ADDED;
23 import static javax.jcr.observation.Event.NODE_MOVED;
24 import static javax.jcr.observation.Event.NODE_REMOVED;
25 import static javax.jcr.observation.Event.PROPERTY_ADDED;
26 import static javax.jcr.observation.Event.PROPERTY_CHANGED;
27 import static javax.jcr.observation.Event.PROPERTY_REMOVED;
28
29 import java.util.Collections;
30
31 import javax.jcr.Node;
32 import javax.jcr.RepositoryException;
33 import javax.jcr.Session;
34 import javax.jcr.observation.EventIterator;
35 import javax.jcr.observation.EventListener;
36
37 import org.apache.jackrabbit.api.observation.JackrabbitEventFilter;
38 import org.apache.jackrabbit.api.observation.JackrabbitObservationManager;
39 import org.apache.sling.api.resource.LoginException;
40 import org.apache.sling.api.resource.Resource;
41 import org.apache.sling.api.resource.ResourceResolver;
42 import org.apache.sling.api.resource.ResourceResolverFactory;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import com.github.benmanes.caffeine.cache.Caffeine;
47 import com.github.benmanes.caffeine.cache.LoadingCache;
48
49
50
51
52
53
54 final class ClientlibPathCache implements EventListener, AutoCloseable {
55
56 private final ResourceResolverFactory resourceResolverFactory;
57 private ResourceResolver listenerServiceResourceResolver;
58
59 private static final String NT_CLIENTLIBRARY = "cq:ClientLibraryFolder";
60 private static final String PN_ALLOWPROXY = "allowProxy";
61
62
63
64
65 private static final String CLIENTLIBS_SERVICE = "clientlibs-service";
66
67 private static final String SERVICE_USER_MAPPING_WARNING = "Missing service user mapping for "
68 + "'io.wcm.handler.url:" + CLIENTLIBS_SERVICE + "' - see https://wcm.io/handler/url/configuration.html";
69
70 private final LoadingCache<String, ClientlibPathCacheEntry> cache = Caffeine.newBuilder()
71 .maximumSize(10000)
72 .build(path -> {
73 try (ResourceResolver resourceResolver = getServiceResourceResolver()) {
74 Resource resource = resourceResolver.getResource(path);
75 if (resource != null) {
76 Node node = resource.adaptTo(Node.class);
77 if (node != null && node.isNodeType(NT_CLIENTLIBRARY)) {
78 boolean isAllowProxy = resource.getValueMap().get(PN_ALLOWPROXY, false);
79 ClientlibPathCacheEntry entry = new ClientlibPathCacheEntry(path, true, isAllowProxy);
80 log.debug("Detected client library: {}", entry);
81 return entry;
82 }
83 }
84 }
85 catch (LoginException ex) {
86 log.warn(SERVICE_USER_MAPPING_WARNING);
87 }
88 return new ClientlibPathCacheEntry(path, false, false);
89 });
90
91 private static final Logger log = LoggerFactory.getLogger(ClientlibPathCache.class);
92
93 ClientlibPathCache(ResourceResolverFactory resourceResolverFactory) {
94 this.resourceResolverFactory = resourceResolverFactory;
95 try {
96 this.listenerServiceResourceResolver = getServiceResourceResolver();
97 enableObservationForClientLibraries();
98 }
99 catch (LoginException ex) {
100 log.warn(SERVICE_USER_MAPPING_WARNING);
101 }
102 }
103
104 private ResourceResolver getServiceResourceResolver() throws LoginException {
105 return resourceResolverFactory.getServiceResourceResolver(
106 Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object)CLIENTLIBS_SERVICE));
107 }
108
109
110
111
112 private void enableObservationForClientLibraries() {
113 Session session = this.listenerServiceResourceResolver.adaptTo(Session.class);
114 if (session != null) {
115 try {
116 log.debug("Enable observation for client libraries.");
117 JackrabbitObservationManager observationManager = (JackrabbitObservationManager)session.getWorkspace().getObservationManager();
118 JackrabbitEventFilter eventFilter = new JackrabbitEventFilter()
119 .setEventTypes(NODE_ADDED | NODE_MOVED | NODE_REMOVED | PROPERTY_ADDED | PROPERTY_CHANGED | PROPERTY_REMOVED)
120 .setAbsPath("/apps")
121 .setAdditionalPaths("/apps", "/libs")
122 .setIsDeep(true)
123 .setNodeTypes(new String[] { NT_CLIENTLIBRARY });
124 observationManager.addEventListener(this, eventFilter);
125 }
126 catch (RepositoryException ex) {
127 log.warn("Unable to register obervation for client libraries.");
128 }
129 }
130 }
131
132
133
134
135 @Override
136 public void onEvent(EventIterator events) {
137 log.debug("Clear client library path cache.");
138 cache.invalidateAll();
139 }
140
141
142
143
144
145
146 public boolean isClientlibWithAllowProxy(String path) {
147 ClientlibPathCacheEntry entry = cache.get(path);
148 return entry.isClientLibrary() && entry.isAllowProxy();
149 }
150
151 @Override
152 public void close() {
153 if (this.listenerServiceResourceResolver != null) {
154 log.debug("End observation for client libraries.");
155 this.listenerServiceResourceResolver.close();
156 }
157 }
158
159 }