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