/* * (C) Copyright 2015-2016 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Anahide Tchertchian */ package org.nuxeo.ecm.web.resources.core.service; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.plexus.util.dag.CycleDetectedException; import org.codehaus.plexus.util.dag.DAG; import org.codehaus.plexus.util.dag.TopologicalSorter; import org.nuxeo.ecm.web.resources.api.Processor; import org.nuxeo.ecm.web.resources.api.Resource; import org.nuxeo.ecm.web.resources.api.ResourceBundle; import org.nuxeo.ecm.web.resources.api.ResourceContext; import org.nuxeo.ecm.web.resources.api.ResourceType; import org.nuxeo.ecm.web.resources.api.service.WebResourceManager; import org.nuxeo.ecm.web.resources.core.ProcessorDescriptor; import org.nuxeo.ecm.web.resources.core.ResourceDescriptor; import org.nuxeo.runtime.model.ComponentContext; import org.nuxeo.runtime.model.ComponentInstance; import org.nuxeo.runtime.model.DefaultComponent; /** * @since 7.3 */ public class WebResourceManagerImpl extends DefaultComponent implements WebResourceManager { private static final Log log = LogFactory.getLog(WebResourceManagerImpl.class); protected static final String RESOURCES_ENDPOINT = "resources"; protected ResourceRegistry resources; protected static final String RESOURCE_BUNDLES_ENDPOINT = "bundles"; protected ResourceBundleRegistry resourceBundles; protected static final String PROCESSORS_ENDPOINT = "processors"; protected ProcessorRegistry processors; // Runtime Component API @Override public void activate(ComponentContext context) { super.activate(context); resources = new ResourceRegistry(); resourceBundles = new ResourceBundleRegistry(); processors = new ProcessorRegistry(); } @Override public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { if (RESOURCES_ENDPOINT.equals(extensionPoint)) { ResourceDescriptor resource = (ResourceDescriptor) contribution; computeResourceUri(resource, contributor); registerResource(resource); } else if (RESOURCE_BUNDLES_ENDPOINT.equals(extensionPoint)) { ResourceBundle bundle = (ResourceBundle) contribution; registerResourceBundle(bundle); } else if (PROCESSORS_ENDPOINT.equals(extensionPoint)) { ProcessorDescriptor p = (ProcessorDescriptor) contribution; log.info(String.format("Register processor '%s'", p.getName())); processors.addContribution(p); log.info(String.format("Done registering processor '%s'", p.getName())); } else { log.error(String.format("Unknown contribution to the service, extension point '%s': '%s", extensionPoint, contribution)); } } @Override public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { if (RESOURCES_ENDPOINT.equals(extensionPoint)) { Resource resource = (Resource) contribution; unregisterResource(resource); } else if (RESOURCE_BUNDLES_ENDPOINT.equals(extensionPoint)) { ResourceBundle bundle = (ResourceBundle) contribution; unregisterResourceBundle(bundle); } else if (PROCESSORS_ENDPOINT.equals(extensionPoint)) { ProcessorDescriptor p = (ProcessorDescriptor) contribution; log.info(String.format("Removing processor '%s'", p.getName())); processors.removeContribution(p); log.info(String.format("Done removing processor '%s'", p.getName())); } else { log.error(String.format("Unknown contribution to the service, extension point '%s': '%s", extensionPoint, contribution)); } } // service API protected void computeResourceUri(ResourceDescriptor resource, ComponentInstance contributor) { String uri = resource.getURI(); if (uri == null) { // build it from local classpath // XXX: hacky wildcard support String path = resource.getPath(); if (path != null) { boolean hasWildcard = false; if (path.endsWith("*")) { hasWildcard = true; path = path.substring(0, path.length() - 1); } URL url = contributor.getContext().getLocalResource(path); if (url == null) { log.error("Cannot resolve local URL for resource '" + resource.getName() + "' with path '" + resource.getPath() + "'"); } else { String builtUri = url.toString(); if (hasWildcard) { builtUri += "*"; } resource.setURI(builtUri); } } } } @Override public Resource getResource(String name) { return resources.getResource(name); } @Override public ResourceBundle getResourceBundle(String name) { return resourceBundles.getResourceBundle(name); } @Override public List<ResourceBundle> getResourceBundles() { return resourceBundles.getResourceBundles(); } @Override public Processor getProcessor(String name) { return processors.getProcessor(name); } @Override public List<Processor> getProcessors() { return processors.getProcessors(); } @Override public List<Processor> getProcessors(String type) { return processors.getProcessors(type); } @Override public List<Resource> getResources(ResourceContext context, String bundleName, String type) { List<Resource> res = new ArrayList<>(); ResourceBundle rb = resourceBundles.getResourceBundle(bundleName); if (rb == null) { if (log.isDebugEnabled()) { log.debug(String.format("Unknown bundle named '%s'", bundleName)); } return res; } Map<String, Resource> all = new HashMap<>(); // retrieve deps + filter depending on type + detect cycles DAG graph = new DAG(); for (String rn : rb.getResources()) { Resource r = getResource(rn); if (r == null) { log.error(String.format("Could not resolve resource '%s' on bundle '%s'", rn, bundleName)); continue; } // resolve sub resources of given type before filtering Map<String, Resource> subRes = getSubResources(graph, r, type); if (ResourceType.matches(type, r) || !subRes.isEmpty()) { graph.addVertex(rn); all.put(rn, r); all.putAll(subRes); } } for (Object rn : TopologicalSorter.sort(graph)) { Resource r = all.get(rn); if (ResourceType.matches(type, r)) { res.add(r); } } return res; } protected Map<String, Resource> getSubResources(DAG graph, Resource r, String type) { Map<String, Resource> res = new HashMap<String, Resource>(); List<String> deps = r.getDependencies(); if (deps != null) { for (String dn : deps) { Resource d = getResource(dn); if (d == null) { log.error("Unknown resource dependency named '" + dn + "'"); continue; } if (!ResourceType.matches(type, d)) { continue; } res.put(dn, d); try { graph.addEdge(r.getName(), dn); } catch (CycleDetectedException e) { log.error("Cycle detected in resource dependencies: ", e); break; } res.putAll(getSubResources(graph, d, type)); } } return res; } @Override public void registerResourceBundle(ResourceBundle bundle) { log.info(String.format("Register resource bundle '%s'", bundle.getName())); if (bundle.getResources().removeIf(StringUtils::isBlank)) { log.error("Some resources references were null or blank while setting " + bundle.getName() + " and have been supressed. This probably happened because some <resource> tags were empty in " + "the xml declaration. The correct form is <resource>resource name</resource>."); } resourceBundles.addContribution(bundle); log.info(String.format("Done registering resource bundle '%s'", bundle.getName())); setModifiedNow(); } @Override public void unregisterResourceBundle(ResourceBundle bundle) { log.info(String.format("Removing resource bundle '%s'", bundle.getName())); resourceBundles.removeContribution(bundle); log.info(String.format("Done removing resource bundle '%s'", bundle.getName())); setModifiedNow(); } @Override public void registerResource(Resource resource) { log.info(String.format("Register resource '%s'", resource.getName())); resources.addContribution(resource); log.info(String.format("Done registering resource '%s'", resource.getName())); setModifiedNow(); } @Override public void unregisterResource(Resource resource) { log.info(String.format("Removing resource '%s'", resource.getName())); resources.removeContribution(resource); log.info(String.format("Done removing resource '%s'", resource.getName())); setModifiedNow(); } }