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