001/*
002 * (C) Copyright 2006-2007 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 *     Nuxeo - initial API and implementation
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.platform.relations.core.listener;
023
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.List;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.ecm.core.api.CoreSession;
031import org.nuxeo.ecm.core.api.DocumentModel;
032import org.nuxeo.ecm.core.api.DocumentSecurityException;
033import org.nuxeo.ecm.core.api.IdRef;
034import org.nuxeo.ecm.core.api.event.CoreEventConstants;
035import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
036import org.nuxeo.ecm.core.event.Event;
037import org.nuxeo.ecm.core.event.EventContext;
038import org.nuxeo.ecm.core.event.EventListener;
039import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
040import org.nuxeo.ecm.platform.relations.api.Graph;
041import org.nuxeo.ecm.platform.relations.api.Node;
042import org.nuxeo.ecm.platform.relations.api.RelationManager;
043import org.nuxeo.ecm.platform.relations.api.Resource;
044import org.nuxeo.ecm.platform.relations.api.Statement;
045import org.nuxeo.ecm.platform.relations.api.util.RelationConstants;
046import org.nuxeo.runtime.api.Framework;
047
048/**
049 * Core Event listener to copy relations affecting the source document to the proxy upon publication events and the
050 * relations that were present on the replaced proxies if any. If this core event listener is used in combination with
051 * another core event listener that cleans relation on deleted documents, it should be executed before the cleaning
052 * listener so as to be able to copy relations from the deleted proxies. This core event listener cannot work in
053 * asynchronous or post commit mode.
054 *
055 * @author ogrisel
056 */
057public class PublishRelationsListener implements EventListener {
058
059    private static final Log log = LogFactory.getLog(PublishRelationsListener.class);
060
061    public static final String RENDITION_PROXY_PUBLISHED = "renditionProxyPublished";
062
063    protected RelationManager rmanager;
064
065    // Override to change the list of graphs to copy relations when a document
066    // is published, set to null to copy relations from all graphs
067
068    protected List<String> graphNamesForCopyFromWork = Arrays.asList(RelationConstants.GRAPH_NAME);
069
070    protected List<String> graphNamesForCopyFromReplacedProxy = Arrays.asList(RelationConstants.GRAPH_NAME,
071            "documentComments");
072
073    public RelationManager getRelationManager() {
074        if (rmanager == null) {
075            rmanager = Framework.getService(RelationManager.class);
076        }
077        return rmanager;
078    }
079
080    public List<String> getGraphNamesForCopyFromWork() {
081        if (graphNamesForCopyFromWork == null) {
082            return getRelationManager().getGraphNames();
083        }
084        return graphNamesForCopyFromWork;
085    }
086
087    public List<String> getGraphNamesForCopyFromReplacedProxy() {
088        if (graphNamesForCopyFromReplacedProxy == null) {
089            return getRelationManager().getGraphNames();
090        }
091        return graphNamesForCopyFromReplacedProxy;
092    }
093
094    @Override
095    public void handleEvent(Event event) {
096        EventContext ctx = event.getContext();
097
098        if (ctx instanceof DocumentEventContext) {
099            DocumentEventContext docCtx = (DocumentEventContext) ctx;
100            DocumentModel publishedDoc = docCtx.getSourceDocument();
101            if (!publishedDoc.isProxy()) {
102                // we are only interested in the publication of proxy documents
103                return;
104            }
105            CoreSession session = ctx.getCoreSession();
106            RelationManager rmanager = getRelationManager();
107
108            Resource publishedResource = rmanager.getResource(RelationConstants.DOCUMENT_NAMESPACE, publishedDoc, null);
109            Resource sourceResource = null;
110
111            // Copy relations from working copy if not a rendition proxy
112            if (!RENDITION_PROXY_PUBLISHED.equals(event.getName())) {
113                try {
114                    // fetch the archived version the proxy is pointing to
115                    DocumentModel sourceDoc = session.getSourceDocument(publishedDoc.getRef());
116
117                    // fetch the working version the archived version is coming
118                    // from
119                    sourceDoc = session.getSourceDocument(sourceDoc.getRef());
120
121                    sourceResource = rmanager.getResource(RelationConstants.DOCUMENT_NAMESPACE, sourceDoc, null);
122
123                    // copy the relations from the working copy (the source
124                    // document getting published)
125                    copyRelationsFromWorkingCopy(rmanager, sourceResource, publishedResource);
126                } catch (DocumentSecurityException e) {
127                    log.warn("working copy of the proxy is no longer available or not readable by the current user, cannot copy the source relations");
128                }
129            }
130
131            // Copy relations from replaced proxies
132            @SuppressWarnings("unchecked")
133            List<String> replacedProxyIds = (List<String>) ctx.getProperties().get(
134                    CoreEventConstants.REPLACED_PROXY_IDS);
135            if (replacedProxyIds != null) {
136                for (String replacedProxyId : replacedProxyIds) {
137                    DocumentLocationImpl docLoc = new DocumentLocationImpl(ctx.getRepositoryName(), new IdRef(
138                            replacedProxyId), null);
139                    Resource replacedResource = rmanager.getResource(RelationConstants.DOCUMENT_NAMESPACE, docLoc, null);
140                    copyRelationsFromReplacedProxy(rmanager, replacedResource, publishedResource, sourceResource);
141                }
142            }
143        }
144    }
145
146    protected void copyRelationsFromReplacedProxy(RelationManager rmanager, Resource replacedResource,
147            Resource publishedResource, Resource sourceResource) {
148        for (String graphName : getGraphNamesForCopyFromReplacedProxy()) {
149            Graph graph = rmanager.getGraphByName(graphName);
150
151            // collect existing relations to or from the source resource
152            List<Statement> newStatements = new ArrayList<>();
153            for (Statement stmt : graph.getStatements(replacedResource, null, null)) {
154                if (!isCopyFromSource(stmt, sourceResource)) {
155                    // do not copy previous relations that come from a
156                    // source
157                    // copy
158                    stmt.setSubject(publishedResource);
159                    newStatements.add(stmt);
160                }
161            }
162            for (Statement stmt : graph.getStatements(null, null, replacedResource)) {
163                if (!isCopyFromSource(stmt, sourceResource)) {
164                    // do not copy previous relations that come from a
165                    // source
166                    // copy
167                    stmt.setObject(publishedResource);
168                    newStatements.add(stmt);
169                }
170            }
171            if (!newStatements.isEmpty()) {
172                // add the rewritten statements on the proxy
173                graph.add(newStatements);
174            }
175        }
176    }
177
178    protected boolean isCopyFromSource(Statement stmt, Resource sourceResource) {
179        Node[] values = stmt.getProperties(RelationConstants.COPY_FROM_WORK_VERSION);
180        if (values == null) {
181            return false;
182        } else {
183            return Arrays.asList(values).contains(sourceResource);
184        }
185    }
186
187    protected void copyRelationsFromWorkingCopy(RelationManager rmanager, Resource sourceResource,
188            Resource publishedResource) {
189        for (String graphName : getGraphNamesForCopyFromWork()) {
190            Graph graph = rmanager.getGraphByName(graphName);
191
192            // collect existing relations to or from the source document
193            List<Statement> newStatements = new ArrayList<>();
194            for (Statement stmt : graph.getStatements(sourceResource, null, null)) {
195                stmt.setSubject(publishedResource);
196                stmt.addProperty(RelationConstants.COPY_FROM_WORK_VERSION, sourceResource);
197                newStatements.add(stmt);
198            }
199            for (Statement stmt : graph.getStatements(null, null, sourceResource)) {
200                stmt.setObject(publishedResource);
201                stmt.addProperty(RelationConstants.COPY_FROM_WORK_VERSION, sourceResource);
202                newStatements.add(stmt);
203            }
204
205            if (!newStatements.isEmpty()) {
206                // add the rewritten statements on the proxy
207                graph.add(newStatements);
208            }
209        }
210    }
211}