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    }
248
249    @Override
250    public void unregisterResourceBundle(ResourceBundle bundle) {
251        log.info(String.format("Removing resource bundle '%s'", bundle.getName()));
252        resourceBundles.removeContribution(bundle);
253        log.info(String.format("Done removing resource bundle '%s'", bundle.getName()));
254    }
255
256    @Override
257    public void registerResource(Resource resource) {
258        log.info(String.format("Register resource '%s'", resource.getName()));
259        resources.addContribution(resource);
260        log.info(String.format("Done registering resource '%s'", resource.getName()));
261    }
262
263    @Override
264    public void unregisterResource(Resource resource) {
265        log.info(String.format("Removing resource '%s'", resource.getName()));
266        resources.removeContribution(resource);
267        log.info(String.format("Done removing resource '%s'", resource.getName()));
268    }
269
270}