001/*
002 * (C) Copyright 2006-2017 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
018 */
019package org.nuxeo.ecm.platform.publisher.impl.service;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.nuxeo.common.utils.Path;
029import org.nuxeo.ecm.core.api.CoreSession;
030import org.nuxeo.ecm.core.api.DocumentModel;
031import org.nuxeo.ecm.core.api.NuxeoException;
032import org.nuxeo.ecm.core.api.repository.RepositoryManager;
033import org.nuxeo.ecm.core.repository.RepositoryService;
034import org.nuxeo.ecm.platform.publisher.api.PublicationNode;
035import org.nuxeo.ecm.platform.publisher.api.PublicationTree;
036import org.nuxeo.ecm.platform.publisher.api.PublishedDocument;
037import org.nuxeo.ecm.platform.publisher.api.PublishedDocumentFactory;
038import org.nuxeo.ecm.platform.publisher.api.PublisherService;
039import org.nuxeo.ecm.platform.publisher.descriptors.PublicationTreeConfigDescriptor;
040import org.nuxeo.ecm.platform.publisher.descriptors.PublicationTreeDescriptor;
041import org.nuxeo.ecm.platform.publisher.descriptors.PublishedDocumentFactoryDescriptor;
042import org.nuxeo.ecm.platform.publisher.descriptors.RootSectionFinderFactoryDescriptor;
043import org.nuxeo.ecm.platform.publisher.helper.PublicationRelationHelper;
044import org.nuxeo.ecm.platform.publisher.helper.RootSectionFinder;
045import org.nuxeo.ecm.platform.publisher.helper.RootSectionFinderFactory;
046import org.nuxeo.ecm.platform.publisher.impl.finder.DefaultRootSectionsFinder;
047import org.nuxeo.ecm.platform.publisher.rules.ValidatorsRule;
048import org.nuxeo.ecm.platform.publisher.rules.ValidatorsRuleDescriptor;
049import org.nuxeo.runtime.api.Framework;
050import org.nuxeo.runtime.model.ComponentContext;
051import org.nuxeo.runtime.model.ComponentInstance;
052import org.nuxeo.runtime.model.DefaultComponent;
053import org.nuxeo.runtime.transaction.TransactionHelper;
054
055/**
056 * POJO implementation of the publisher service.
057 *
058 * @author tiry
059 */
060public class PublisherServiceImpl extends DefaultComponent implements PublisherService {
061
062    private final Log log = LogFactory.getLog(PublisherServiceImpl.class);
063
064    protected Map<String, PublicationTreeDescriptor> treeDescriptors = new HashMap<>();
065
066    protected Map<String, PublishedDocumentFactoryDescriptor> factoryDescriptors = new HashMap<>();
067
068    protected Map<String, PublicationTreeConfigDescriptor> treeConfigDescriptors = new HashMap<>();
069
070    protected Map<String, ValidatorsRuleDescriptor> validatorsRuleDescriptors = new HashMap<>();
071
072    protected Map<String, PublicationTreeConfigDescriptor> pendingDescriptors = new HashMap<>();
073
074    protected RootSectionFinderFactory rootSectionFinderFactory = null;
075
076    public static final String TREE_EP = "tree";
077
078    public static final String TREE_CONFIG_EP = "treeInstance";
079
080    public static final String VALIDATORS_RULE_EP = "validatorsRule";
081
082    public static final String FACTORY_EP = "factory";
083
084    public static final String ROOT_SECTION_FINDER_FACTORY_EP = "rootSectionFinderFactory";
085
086    protected static final String ROOT_PATH_KEY = "RootPath";
087
088    protected static final String RELATIVE_ROOT_PATH_KEY = "RelativeRootPath";
089
090    @Override
091    public void start(ComponentContext context) {
092        RepositoryService repositoryService = Framework.getService(RepositoryService.class);
093        if (repositoryService == null) {
094            // RepositoryService failed to start, no need to go further
095            return;
096        }
097        boolean txWasStartedOutsideComponent = TransactionHelper.isTransactionActiveOrMarkedRollback();
098
099        if (txWasStartedOutsideComponent || TransactionHelper.startTransaction()) {
100            boolean completedAbruptly = true;
101            try {
102                doApplicationStarted();
103                completedAbruptly = false;
104            } finally {
105                if (completedAbruptly) {
106                    TransactionHelper.setTransactionRollbackOnly();
107                }
108                if (!txWasStartedOutsideComponent) {
109                    TransactionHelper.commitOrRollbackTransaction();
110                }
111            }
112        } else {
113            doApplicationStarted();
114        }
115    }
116
117    protected void doApplicationStarted() {
118        ClassLoader jbossCL = Thread.currentThread().getContextClassLoader();
119        ClassLoader nuxeoCL = PublisherServiceImpl.class.getClassLoader();
120        try {
121            Thread.currentThread().setContextClassLoader(nuxeoCL);
122            log.info("Publisher Service initialization");
123            registerPendingDescriptors();
124        } finally {
125            Thread.currentThread().setContextClassLoader(jbossCL);
126            log.debug("JBoss ClassLoader restored");
127        }
128    }
129
130    @Override
131    public void activate(ComponentContext context) {
132        treeDescriptors = new HashMap<>();
133        factoryDescriptors = new HashMap<>();
134        treeConfigDescriptors = new HashMap<>();
135        validatorsRuleDescriptors = new HashMap<>();
136        pendingDescriptors = new HashMap<>();
137    }
138
139    @Override
140    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
141
142        log.debug("Registry contribution for EP " + extensionPoint);
143
144        if (TREE_EP.equals(extensionPoint)) {
145            PublicationTreeDescriptor desc = (PublicationTreeDescriptor) contribution;
146            treeDescriptors.put(desc.getName(), desc);
147        } else if (TREE_CONFIG_EP.equals(extensionPoint)) {
148            PublicationTreeConfigDescriptor desc = (PublicationTreeConfigDescriptor) contribution;
149            registerTreeConfig(desc);
150        } else if (FACTORY_EP.equals(extensionPoint)) {
151            PublishedDocumentFactoryDescriptor desc = (PublishedDocumentFactoryDescriptor) contribution;
152            factoryDescriptors.put(desc.getName(), desc);
153        } else if (VALIDATORS_RULE_EP.equals(extensionPoint)) {
154            ValidatorsRuleDescriptor desc = (ValidatorsRuleDescriptor) contribution;
155            validatorsRuleDescriptors.put(desc.getName(), desc);
156        } else if (ROOT_SECTION_FINDER_FACTORY_EP.equals(extensionPoint)) {
157            RootSectionFinderFactoryDescriptor desc = (RootSectionFinderFactoryDescriptor) contribution;
158            try {
159                rootSectionFinderFactory = desc.getFactory().newInstance();
160            } catch (ReflectiveOperationException t) {
161                log.error("Unable to load custom RootSectionFinderFactory", t);
162            }
163        }
164    }
165
166    protected void registerTreeConfig(PublicationTreeConfigDescriptor desc) {
167        if (desc.getParameters().get("RelativeRootPath") != null) {
168            pendingDescriptors.put(desc.getName(), desc);
169        } else {
170            treeConfigDescriptors.put(desc.getName(), desc);
171        }
172    }
173
174    @Override
175    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
176        if (contribution instanceof PublicationTreeDescriptor) {
177            treeDescriptors.remove(((PublicationTreeDescriptor) contribution).getName());
178        } else if (contribution instanceof PublicationTreeConfigDescriptor) {
179            String name = ((PublicationTreeConfigDescriptor) contribution).getName();
180            pendingDescriptors.remove(name);
181            treeConfigDescriptors.remove(name);
182        } else if (contribution instanceof ValidatorsRuleDescriptor) {
183            validatorsRuleDescriptors.remove(((ValidatorsRuleDescriptor) contribution).getName());
184        } else if (contribution instanceof RootSectionFinderFactoryDescriptor) {
185            rootSectionFinderFactory = null;
186        }
187    }
188
189    @Override
190    public List<String> getAvailablePublicationTree() {
191        List<String> treeConfigs = new ArrayList<>();
192        treeConfigs.addAll(treeConfigDescriptors.keySet());
193        return treeConfigs;
194    }
195
196    @Override
197    public Map<String, String> getAvailablePublicationTrees() {
198        Map<String, String> trees = new HashMap<>();
199        for (PublicationTreeConfigDescriptor desc : treeConfigDescriptors.values()) {
200            String title = desc.getTitle() == null ? desc.getName() : desc.getTitle();
201            trees.put(desc.getName(), title);
202        }
203        return trees;
204    }
205
206    @Override
207    public PublicationTree getPublicationTree(String treeName, CoreSession coreSession, Map<String, String> params) {
208        return getPublicationTree(treeName, coreSession, params, null);
209    }
210
211    @Override
212    public PublicationTree getPublicationTree(String treeName, CoreSession coreSession, Map<String, String> params,
213            DocumentModel currentDocument) {
214        PublicationTree tree = buildTree(treeName, coreSession, params);
215        if (tree == null) {
216            return null;
217        }
218        if (currentDocument != null) {
219            tree.setCurrentDocument(currentDocument);
220        }
221        return tree;
222    }
223
224    protected PublicationTree buildTree(String treeConfigName, CoreSession coreSession, Map<String, String> params) {
225        PublicationTreeConfigDescriptor config = getPublicationTreeConfigDescriptor(treeConfigName);
226        Map<String, String> allParameters = computeAllParameters(config, params);
227        PublicationTreeDescriptor treeDescriptor = getPublicationTreeDescriptor(config);
228        PublishedDocumentFactory publishedDocumentFactory = getPublishedDocumentFactory(config, treeDescriptor,
229                coreSession, allParameters);
230        return getPublicationTree(treeDescriptor, coreSession, allParameters, publishedDocumentFactory,
231                config.getName(), config.getTitle());
232    }
233
234    protected Map<String, String> computeAllParameters(PublicationTreeConfigDescriptor config,
235            Map<String, String> params) {
236        final Map<String, String> allParameters = config.getParameters();
237        if (params != null) {
238            allParameters.putAll(params);
239        }
240        return allParameters;
241    }
242
243    protected PublishedDocumentFactory getPublishedDocumentFactory(PublicationTreeConfigDescriptor config,
244            PublicationTreeDescriptor treeDescriptor, CoreSession coreSession, Map<String, String> params) {
245        PublishedDocumentFactoryDescriptor factoryDesc = getPublishedDocumentFactoryDescriptor(config, treeDescriptor);
246        ValidatorsRule validatorsRule = getValidatorsRule(factoryDesc);
247
248        PublishedDocumentFactory factory;
249        try {
250            factory = factoryDesc.getKlass().newInstance();
251        } catch (ReflectiveOperationException e) {
252            throw new NuxeoException("Error while creating factory " + factoryDesc.getName(), e);
253        }
254        factory.init(coreSession, validatorsRule, params);
255        return factory;
256    }
257
258    protected ValidatorsRule getValidatorsRule(PublishedDocumentFactoryDescriptor factoryDesc) {
259        String validatorsRuleName = factoryDesc.getValidatorsRuleName();
260        ValidatorsRule validatorsRule = null;
261        if (validatorsRuleName != null) {
262            ValidatorsRuleDescriptor validatorsRuleDesc = validatorsRuleDescriptors.get(validatorsRuleName);
263            if (validatorsRuleDesc == null) {
264                throw new NuxeoException("Unable to find validatorsRule" + validatorsRuleName);
265            }
266            try {
267                validatorsRule = validatorsRuleDesc.getKlass().newInstance();
268            } catch (ReflectiveOperationException e) {
269                throw new NuxeoException("Error while creating validatorsRule " + validatorsRuleName, e);
270            }
271        }
272        return validatorsRule;
273    }
274
275    protected PublishedDocumentFactoryDescriptor getPublishedDocumentFactoryDescriptor(
276            PublicationTreeConfigDescriptor config, PublicationTreeDescriptor treeDescriptor) {
277        String factoryName = config.getFactory();
278        if (factoryName == null) {
279            factoryName = treeDescriptor.getFactory();
280        }
281
282        PublishedDocumentFactoryDescriptor factoryDesc = factoryDescriptors.get(factoryName);
283        if (factoryDesc == null) {
284            throw new NuxeoException("Unable to find factory" + factoryName);
285        }
286        return factoryDesc;
287    }
288
289    protected PublicationTreeConfigDescriptor getPublicationTreeConfigDescriptor(String treeConfigName) {
290        if (!treeConfigDescriptors.containsKey(treeConfigName)) {
291            throw new NuxeoException("Unknow treeConfig :" + treeConfigName);
292        }
293        return treeConfigDescriptors.get(treeConfigName);
294    }
295
296    protected PublicationTreeDescriptor getPublicationTreeDescriptor(PublicationTreeConfigDescriptor config) {
297        String treeImplName = config.getTree();
298        if (!treeDescriptors.containsKey(treeImplName)) {
299            throw new NuxeoException("Unknow treeImplementation :" + treeImplName);
300        }
301        return treeDescriptors.get(treeImplName);
302    }
303
304    protected PublicationTree getPublicationTree(PublicationTreeDescriptor treeDescriptor, CoreSession coreSession,
305            Map<String, String> parameters, PublishedDocumentFactory factory, String configName, String treeTitle) {
306        PublicationTree treeImpl;
307        try {
308            treeImpl = treeDescriptor.getKlass().newInstance();
309        } catch (ReflectiveOperationException e) {
310            throw new NuxeoException("Error while creating tree implementation", e);
311        }
312        treeImpl.initTree(coreSession, parameters, factory, configName, treeTitle);
313        return treeImpl;
314    }
315
316    @Override
317    public PublishedDocument publish(DocumentModel doc, PublicationNode targetNode) {
318        return publish(doc, targetNode, null);
319    }
320
321    @Override
322    public PublishedDocument publish(DocumentModel doc, PublicationNode targetNode, Map<String, String> params) {
323        return targetNode.getTree().publish(doc, targetNode, params);
324    }
325
326    @Override
327    public void unpublish(DocumentModel doc, PublicationNode targetNode) {
328        targetNode.getTree().unpublish(doc, targetNode);
329    }
330
331    @Override
332    public boolean isPublishedDocument(DocumentModel documentModel) {
333        return PublicationRelationHelper.isPublished(documentModel);
334    }
335
336    @Override
337    public PublicationTree getPublicationTreeFor(DocumentModel doc, CoreSession coreSession) {
338        PublicationTree tree = null;
339        try {
340            tree = PublicationRelationHelper.getPublicationTreeUsedForPublishing(doc, coreSession);
341        } catch (NuxeoException e) {
342            // TODO catch proper exception
343            log.error("Unable to get PublicationTree for " + doc.getPathAsString()
344                    + ". Fallback on first PublicationTree accepting this document.", e);
345            for (String treeName : treeConfigDescriptors.keySet()) {
346                tree = getPublicationTree(treeName, coreSession, null);
347                if (tree.isPublicationNode(doc)) {
348                    break;
349                }
350            }
351        }
352        return tree;
353    }
354
355    @Override
356    public PublicationNode wrapToPublicationNode(DocumentModel documentModel, CoreSession coreSession) {
357        for (String name : getAvailablePublicationTree()) {
358            PublicationTree tree = getPublicationTree(name, coreSession, null);
359            if (tree.isPublicationNode(documentModel)) {
360                return tree.wrapToPublicationNode(documentModel);
361            }
362        }
363        return null;
364    }
365
366    protected void registerPendingDescriptors() {
367        // TODO what to do with multiple repositories?
368        RepositoryManager repositoryManager = Framework.getService(RepositoryManager.class);
369        String repositoryName = repositoryManager.getDefaultRepositoryName();
370        List<DocumentModel> domains = new DomainsFinder(repositoryName).getDomains();
371        for (DocumentModel domain : domains) {
372            registerTreeConfigFor(domain);
373        }
374    }
375
376    public void registerTreeConfigFor(DocumentModel domain) {
377        for (PublicationTreeConfigDescriptor desc : pendingDescriptors.values()) {
378            PublicationTreeConfigDescriptor newDesc = new PublicationTreeConfigDescriptor(desc);
379            String newTreeName = desc.getName() + "-" + domain.getName();
380            newDesc.setName(newTreeName);
381            Path newPath = domain.getPath();
382            Map<String, String> parameters = newDesc.getParameters();
383            newPath = newPath.append(parameters.remove(RELATIVE_ROOT_PATH_KEY));
384            parameters.put(ROOT_PATH_KEY, newPath.toString());
385            parameters.put(PublisherService.DOMAIN_NAME_KEY, domain.getTitle());
386            treeConfigDescriptors.put(newDesc.getName(), newDesc);
387        }
388    }
389
390    public void unRegisterTreeConfigFor(DocumentModel domain) {
391        unRegisterTreeConfigFor(domain.getName());
392    }
393
394    /**
395     * @since 7.3
396     */
397    public void unRegisterTreeConfigFor(String domainName) {
398        for (PublicationTreeConfigDescriptor desc : pendingDescriptors.values()) {
399            String treeName = desc.getName() + "-" + domainName;
400            treeConfigDescriptors.remove(treeName);
401        }
402    }
403
404    @Override
405    public Map<String, String> getParametersFor(String treeConfigName) {
406        PublicationTreeConfigDescriptor desc = treeConfigDescriptors.get(treeConfigName);
407        Map<String, String> parameters = new HashMap<>();
408        if (desc != null) {
409            parameters.putAll(desc.getParameters());
410        }
411        return parameters;
412    }
413
414    @Override
415    public RootSectionFinder getRootSectionFinder(CoreSession session) {
416        if (rootSectionFinderFactory != null) {
417            return rootSectionFinderFactory.getRootSectionFinder(session);
418        }
419        return new DefaultRootSectionsFinder(session);
420    }
421}