001/*
002 * (C) Copyright 2006-2018 Nuxeo (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 *     Nuxeo - initial API and implementation
018 *
019 * $Id: DocumentViewCodecService.java 22535 2007-07-13 14:57:58Z atchertchian $
020 */
021
022package org.nuxeo.ecm.platform.url.service;
023
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.List;
027import java.util.Map;
028import java.util.concurrent.ConcurrentHashMap;
029
030import org.apache.commons.lang3.StringUtils;
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.ecm.platform.url.api.DocumentView;
034import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager;
035import org.nuxeo.ecm.platform.url.codec.api.DocumentViewCodec;
036import org.nuxeo.ecm.platform.url.codec.descriptor.DocumentViewCodecDescriptor;
037import org.nuxeo.runtime.model.ComponentContext;
038import org.nuxeo.runtime.model.ComponentInstance;
039import org.nuxeo.runtime.model.DefaultComponent;
040
041public class DocumentViewCodecService extends DefaultComponent implements DocumentViewCodecManager {
042
043    private static final long serialVersionUID = -4521897334653742494L;
044
045    private static final Log log = LogFactory.getLog(DocumentViewCodecService.class);
046
047    public static final String NAME = DocumentViewCodecService.class.getName();
048
049    public static final String CODECS_EXTENSION_POINT = "codecs";
050
051    protected String defaultCodecName;
052
053    protected final Map<String, DocumentViewCodecDescriptor> descriptors;
054
055    protected final Map<String, DocumentViewCodec> codecs;
056
057    public DocumentViewCodecService() {
058        descriptors = new ConcurrentHashMap<>();
059        codecs = new ConcurrentHashMap<>();
060    }
061
062    @Override
063    public void deactivate(ComponentContext context) {
064        descriptors.clear();
065        codecs.clear();
066    }
067
068    @Override
069    @SuppressWarnings("unchecked")
070    public <T> T getAdapter(Class<T> adapter) {
071        if (adapter.isAssignableFrom(DocumentViewCodecManager.class)) {
072            return (T) this;
073        }
074        return null;
075    }
076
077    @Override
078    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
079        if (CODECS_EXTENSION_POINT.equals(extensionPoint)) {
080            DocumentViewCodecDescriptor desc = (DocumentViewCodecDescriptor) contribution;
081            String codecName = desc.getName();
082            if (desc.getDefaultCodec()) {
083                defaultCodecName = codecName;
084            }
085            // try to instantiate it
086            String className = desc.getClassName();
087            if (className == null) {
088                throw new IllegalArgumentException(
089                        String.format("Invalid class for codec '%s': check ERROR logs" + " at startup", codecName));
090            }
091            DocumentViewCodec codec;
092            try {
093                // Thread context loader is not working in isolated EARs
094                codec = (DocumentViewCodec) DocumentViewCodecManager.class.getClassLoader()
095                                                                          .loadClass(className)
096                                                                          .getDeclaredConstructor()
097                                                                          .newInstance();
098            } catch (ReflectiveOperationException e) {
099                String msg = String.format("Caught error when instantiating codec '%s' with " + "class '%s' ",
100                        codecName, className);
101                throw new IllegalArgumentException(msg, e);
102            }
103            String prefix = desc.getPrefix();
104            if (prefix != null) {
105                codec.setPrefix(prefix);
106            }
107            if (!descriptors.containsKey(codecName) || desc.getPriority() > descriptors.get(codecName).getPriority()) {
108                descriptors.put(codecName, desc);
109                codecs.put(codecName, codec);
110            }
111            log.debug("Added URL codec: " + codecName);
112        }
113    }
114
115    @Override
116    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
117        if (CODECS_EXTENSION_POINT.equals(extensionPoint)) {
118            DocumentViewCodecDescriptor codecDesc = (DocumentViewCodecDescriptor) contribution;
119            String codecName = codecDesc.getName();
120            descriptors.remove(codecName);
121            codecs.remove(codecName);
122            log.debug("Removed URL codec: " + codecName);
123        }
124    }
125
126    public List<String> getDocumentViewCodecDescriptorNames() {
127        List<String> lst = new ArrayList<>();
128        for (String k : descriptors.keySet()) {
129            if (descriptors.get(k).getEnabled()) {
130                lst.add(k);
131            }
132        }
133        return lst;
134    }
135
136    @Override
137    public String getDefaultCodecName() {
138        String name = defaultCodecName;
139        if (name == null) {
140            // take first one
141            List<String> descs = getDocumentViewCodecDescriptorNames();
142            if (descs != null && !descs.isEmpty()) {
143                Collections.sort(descs);
144                name = descs.get(0);
145            }
146        }
147        return name;
148    }
149
150    public DocumentViewCodec getCodec() {
151        return getCodec(defaultCodecName);
152    }
153
154    @Override
155    public DocumentViewCodec getCodec(String codecName) {
156        if (StringUtils.isBlank(codecName)) {
157            return null;
158        }
159        return codecs.get(codecName);
160    }
161
162    @Override
163    public String getUrlFromDocumentView(DocumentView docView, boolean needBaseUrl, String baseUrl) {
164        String url = null;
165        DocumentViewCodec codec = getCodec(getDefaultCodecName());
166        if (codec != null && codec.handleDocumentView(docView)) {
167            url = getUrlFromDocumentView(codec, docView, needBaseUrl, baseUrl);
168        }
169        if (url == null) {
170            for (String codecName : descriptors.keySet()) {
171                if (!codecName.equals(defaultCodecName)) {
172                    codec = getCodec(codecName);
173                    if (codec != null && codec.handleDocumentView(docView)) {
174                        url = getUrlFromDocumentView(codec, docView, needBaseUrl, baseUrl);
175                        if (url != null) {
176                            break;
177                        }
178                    }
179                }
180            }
181        }
182        return url;
183    }
184
185    @Override
186    public String getUrlFromDocumentView(String codecName, DocumentView docView, boolean needBaseUrl, String baseUrl) {
187        DocumentViewCodec codec = getCodec(codecName);
188        return getUrlFromDocumentView(codec, docView, needBaseUrl, baseUrl);
189    }
190
191    protected String getUrlFromDocumentView(DocumentViewCodec codec, DocumentView docView, boolean needBaseUrl,
192            String baseUrl) {
193        if (codec != null) {
194            String partialUrl = codec.getUrlFromDocumentView(docView);
195            if (partialUrl != null) {
196                if (needBaseUrl && !StringUtils.isBlank(baseUrl)) {
197                    if (baseUrl.endsWith("/") || partialUrl.startsWith("/")) {
198                        return baseUrl + partialUrl;
199                    } else {
200                        return baseUrl + "/" + partialUrl;
201                    }
202                } else {
203                    return partialUrl;
204                }
205            }
206        }
207        return null;
208    }
209
210    @Override
211    public DocumentView getDocumentViewFromUrl(String url, boolean hasBaseUrl, String baseUrl) {
212        DocumentView docView = null;
213        String finalUrl = getUrlWithoutBase(url, hasBaseUrl, baseUrl);
214        DocumentViewCodec codec = getCodec(getDefaultCodecName());
215        if (codec != null && codec.handleUrl(finalUrl)) {
216            docView = getDocumentViewFromUrl(codec, finalUrl);
217        }
218        if (docView == null) {
219            for (String codecName : descriptors.keySet()) {
220                if (!codecName.equals(defaultCodecName)) {
221                    codec = getCodec(codecName);
222                    if (codec != null && codec.handleUrl(finalUrl)) {
223                        docView = getDocumentViewFromUrl(codec, finalUrl);
224                        if (docView != null) {
225                            break;
226                        }
227                    }
228                }
229            }
230        }
231        return docView;
232    }
233
234    @Override
235    public DocumentView getDocumentViewFromUrl(String codecName, String url, boolean hasBaseUrl, String baseUrl) {
236        DocumentViewCodec codec = getCodec(codecName);
237        String finalUrl = getUrlWithoutBase(url, hasBaseUrl, baseUrl);
238        return getDocumentViewFromUrl(codec, finalUrl);
239    }
240
241    protected String getUrlWithoutBase(String url, boolean hasBaseUrl, String baseUrl) {
242        if (hasBaseUrl && baseUrl != null) {
243            if (url.startsWith(baseUrl)) {
244                url = url.substring(baseUrl.length());
245            }
246        }
247        return url;
248    }
249
250    protected DocumentView getDocumentViewFromUrl(DocumentViewCodec codec, String finalUrl) {
251        if (codec != null) {
252            DocumentView docView = codec.getDocumentViewFromUrl(finalUrl);
253            return docView;
254        }
255        return null;
256    }
257
258}