001/* 002 * (C) Copyright 2015-2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Anahide Tchertchian 018 */ 019package org.nuxeo.ecm.web.resources.core.service; 020 021import java.net.URL; 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import org.apache.commons.lang.StringUtils; 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.codehaus.plexus.util.dag.CycleDetectedException; 031import org.codehaus.plexus.util.dag.DAG; 032import org.codehaus.plexus.util.dag.TopologicalSorter; 033import org.nuxeo.ecm.web.resources.api.Processor; 034import org.nuxeo.ecm.web.resources.api.Resource; 035import org.nuxeo.ecm.web.resources.api.ResourceBundle; 036import org.nuxeo.ecm.web.resources.api.ResourceContext; 037import org.nuxeo.ecm.web.resources.api.ResourceType; 038import org.nuxeo.ecm.web.resources.api.service.WebResourceManager; 039import org.nuxeo.ecm.web.resources.core.ProcessorDescriptor; 040import org.nuxeo.ecm.web.resources.core.ResourceDescriptor; 041import org.nuxeo.runtime.model.ComponentContext; 042import org.nuxeo.runtime.model.ComponentInstance; 043import org.nuxeo.runtime.model.DefaultComponent; 044 045/** 046 * @since 7.3 047 */ 048public class WebResourceManagerImpl extends DefaultComponent implements WebResourceManager { 049 050 private static final Log log = LogFactory.getLog(WebResourceManagerImpl.class); 051 052 protected static final String RESOURCES_ENDPOINT = "resources"; 053 054 protected ResourceRegistry resources; 055 056 protected static final String RESOURCE_BUNDLES_ENDPOINT = "bundles"; 057 058 protected ResourceBundleRegistry resourceBundles; 059 060 protected static final String PROCESSORS_ENDPOINT = "processors"; 061 062 protected ProcessorRegistry processors; 063 064 // Runtime Component API 065 066 @Override 067 public void activate(ComponentContext context) { 068 super.activate(context); 069 resources = new ResourceRegistry(); 070 resourceBundles = new ResourceBundleRegistry(); 071 processors = new ProcessorRegistry(); 072 } 073 074 @Override 075 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 076 if (RESOURCES_ENDPOINT.equals(extensionPoint)) { 077 ResourceDescriptor resource = (ResourceDescriptor) contribution; 078 computeResourceUri(resource, contributor); 079 registerResource(resource); 080 } else if (RESOURCE_BUNDLES_ENDPOINT.equals(extensionPoint)) { 081 ResourceBundle bundle = (ResourceBundle) contribution; 082 registerResourceBundle(bundle); 083 } else if (PROCESSORS_ENDPOINT.equals(extensionPoint)) { 084 ProcessorDescriptor p = (ProcessorDescriptor) contribution; 085 log.info(String.format("Register processor '%s'", p.getName())); 086 processors.addContribution(p); 087 log.info(String.format("Done registering processor '%s'", p.getName())); 088 } else { 089 log.error(String.format("Unknown contribution to the service, extension point '%s': '%s", extensionPoint, 090 contribution)); 091 } 092 } 093 094 @Override 095 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 096 if (RESOURCES_ENDPOINT.equals(extensionPoint)) { 097 Resource resource = (Resource) contribution; 098 unregisterResource(resource); 099 } else if (RESOURCE_BUNDLES_ENDPOINT.equals(extensionPoint)) { 100 ResourceBundle bundle = (ResourceBundle) contribution; 101 unregisterResourceBundle(bundle); 102 } else if (PROCESSORS_ENDPOINT.equals(extensionPoint)) { 103 ProcessorDescriptor p = (ProcessorDescriptor) contribution; 104 log.info(String.format("Removing processor '%s'", p.getName())); 105 processors.removeContribution(p); 106 log.info(String.format("Done removing processor '%s'", p.getName())); 107 } else { 108 log.error(String.format("Unknown contribution to the service, extension point '%s': '%s", extensionPoint, 109 contribution)); 110 } 111 } 112 113 // service API 114 115 protected void computeResourceUri(ResourceDescriptor resource, ComponentInstance contributor) { 116 String uri = resource.getURI(); 117 if (uri == null) { 118 // build it from local classpath 119 // XXX: hacky wildcard support 120 String path = resource.getPath(); 121 if (path != null) { 122 boolean hasWildcard = false; 123 if (path.endsWith("*")) { 124 hasWildcard = true; 125 path = path.substring(0, path.length() - 1); 126 } 127 URL url = contributor.getContext().getLocalResource(path); 128 if (url == null) { 129 log.error("Cannot resolve local URL for resource '" + resource.getName() + "' with path '" 130 + resource.getPath() + "'"); 131 } else { 132 String builtUri = url.toString(); 133 if (hasWildcard) { 134 builtUri += "*"; 135 } 136 resource.setURI(builtUri); 137 } 138 } 139 } 140 } 141 142 @Override 143 public Resource getResource(String name) { 144 return resources.getResource(name); 145 } 146 147 @Override 148 public ResourceBundle getResourceBundle(String name) { 149 return resourceBundles.getResourceBundle(name); 150 } 151 152 @Override 153 public List<ResourceBundle> getResourceBundles() { 154 return resourceBundles.getResourceBundles(); 155 } 156 157 @Override 158 public Processor getProcessor(String name) { 159 return processors.getProcessor(name); 160 } 161 162 @Override 163 public List<Processor> getProcessors() { 164 return processors.getProcessors(); 165 } 166 167 @Override 168 public List<Processor> getProcessors(String type) { 169 return processors.getProcessors(type); 170 } 171 172 @Override 173 public List<Resource> getResources(ResourceContext context, String bundleName, String type) { 174 List<Resource> res = new ArrayList<>(); 175 ResourceBundle rb = resourceBundles.getResourceBundle(bundleName); 176 if (rb == null) { 177 if (log.isDebugEnabled()) { 178 log.debug(String.format("Unknown bundle named '%s'", bundleName)); 179 } 180 return res; 181 } 182 183 Map<String, Resource> all = new HashMap<>(); 184 // retrieve deps + filter depending on type + detect cycles 185 DAG graph = new DAG(); 186 for (String rn : rb.getResources()) { 187 Resource r = getResource(rn); 188 if (r == null) { 189 log.error(String.format("Could not resolve resource '%s' on bundle '%s'", rn, bundleName)); 190 continue; 191 } 192 // resolve sub resources of given type before filtering 193 Map<String, Resource> subRes = getSubResources(graph, r, type); 194 if (ResourceType.matches(type, r) || !subRes.isEmpty()) { 195 graph.addVertex(rn); 196 all.put(rn, r); 197 all.putAll(subRes); 198 } 199 } 200 201 for (Object rn : TopologicalSorter.sort(graph)) { 202 Resource r = all.get(rn); 203 if (ResourceType.matches(type, r)) { 204 res.add(r); 205 } 206 } 207 208 return res; 209 } 210 211 protected Map<String, Resource> getSubResources(DAG graph, Resource r, String type) { 212 Map<String, Resource> res = new HashMap<String, Resource>(); 213 List<String> deps = r.getDependencies(); 214 if (deps != null) { 215 for (String dn : deps) { 216 Resource d = getResource(dn); 217 if (d == null) { 218 log.error("Unknown resource dependency named '" + dn + "'"); 219 continue; 220 } 221 if (!ResourceType.matches(type, d)) { 222 continue; 223 } 224 res.put(dn, d); 225 try { 226 graph.addEdge(r.getName(), dn); 227 } catch (CycleDetectedException e) { 228 log.error("Cycle detected in resource dependencies: ", e); 229 break; 230 } 231 res.putAll(getSubResources(graph, d, type)); 232 } 233 } 234 return res; 235 } 236 237 @Override 238 public void registerResourceBundle(ResourceBundle bundle) { 239 log.info(String.format("Register resource bundle '%s'", bundle.getName())); 240 if (bundle.getResources().removeIf(StringUtils::isBlank)) { 241 log.error("Some resources references were null or blank while setting " + bundle.getName() 242 + " and have been supressed. This probably happened because some <resource> tags were empty in " 243 + "the xml declaration. The correct form is <resource>resource name</resource>."); 244 } 245 resourceBundles.addContribution(bundle); 246 log.info(String.format("Done registering resource bundle '%s'", bundle.getName())); 247 setModifiedNow(); 248 } 249 250 @Override 251 public void unregisterResourceBundle(ResourceBundle bundle) { 252 log.info(String.format("Removing resource bundle '%s'", bundle.getName())); 253 resourceBundles.removeContribution(bundle); 254 log.info(String.format("Done removing resource bundle '%s'", bundle.getName())); 255 setModifiedNow(); 256 } 257 258 @Override 259 public void registerResource(Resource resource) { 260 log.info(String.format("Register resource '%s'", resource.getName())); 261 resources.addContribution(resource); 262 log.info(String.format("Done registering resource '%s'", resource.getName())); 263 setModifiedNow(); 264 } 265 266 @Override 267 public void unregisterResource(Resource resource) { 268 log.info(String.format("Removing resource '%s'", resource.getName())); 269 resources.removeContribution(resource); 270 log.info(String.format("Done removing resource '%s'", resource.getName())); 271 setModifiedNow(); 272 } 273 274}