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    public void handleEvent(Event event) {
095        EventContext ctx = event.getContext();
096
097        if (ctx instanceof DocumentEventContext) {
098            DocumentEventContext docCtx = (DocumentEventContext) ctx;
099            DocumentModel publishedDoc = docCtx.getSourceDocument();
100            if (!publishedDoc.isProxy()) {
101                // we are only interested in the publication of proxy documents
102                return;
103            }
104            CoreSession session = ctx.getCoreSession();
105            RelationManager rmanager = getRelationManager();
106
107            Resource publishedResource = rmanager.getResource(RelationConstants.DOCUMENT_NAMESPACE, publishedDoc, null);
108            Resource sourceResource = null;
109
110            // Copy relations from working copy if not a rendition proxy
111            if (!RENDITION_PROXY_PUBLISHED.equals(event.getName())) {
112                try {
113                    // fetch the archived version the proxy is pointing to
114                    DocumentModel sourceDoc = session.getSourceDocument(publishedDoc.getRef());
115
116                    // fetch the working version the archived version is coming
117                    // from
118                    sourceDoc = session.getSourceDocument(sourceDoc.getRef());
119
120                    sourceResource = rmanager.getResource(RelationConstants.DOCUMENT_NAMESPACE, sourceDoc, null);
121
122                    // copy the relations from the working copy (the source
123                    // document getting published)
124                    copyRelationsFromWorkingCopy(rmanager, sourceResource, publishedResource);
125                } catch (DocumentSecurityException e) {
126                    log.warn("working copy of the proxy is no longer available or not readable by the current user, cannot copy the source relations");
127                }
128            }
129
130            // Copy relations from replaced proxies
131            @SuppressWarnings("unchecked")
132            List<String> replacedProxyIds = (List<String>) ctx.getProperties().get(
133                    CoreEventConstants.REPLACED_PROXY_IDS);
134            if (replacedProxyIds != null) {
135                for (String replacedProxyId : replacedProxyIds) {
136                    DocumentLocationImpl docLoc = new DocumentLocationImpl(ctx.getRepositoryName(), new IdRef(
137                            replacedProxyId), null);
138                    Resource replacedResource = rmanager.getResource(RelationConstants.DOCUMENT_NAMESPACE, docLoc, null);
139                    copyRelationsFromReplacedProxy(rmanager, replacedResource, publishedResource, sourceResource);
140                }
141            }
142        }
143    }
144
145    protected void copyRelationsFromReplacedProxy(RelationManager rmanager, Resource replacedResource,
146            Resource publishedResource, Resource sourceResource) {
147        for (String graphName : getGraphNamesForCopyFromReplacedProxy()) {
148            Graph graph = rmanager.getGraphByName(graphName);
149
150            // collect existing relations to or from the source resource
151            List<Statement> newStatements = new ArrayList<Statement>();
152            for (Statement stmt : graph.getStatements(replacedResource, null, null)) {
153                if (!isCopyFromSource(stmt, sourceResource)) {
154                    // do not copy previous relations that come from a
155                    // source
156                    // copy
157                    stmt.setSubject(publishedResource);
158                    newStatements.add(stmt);
159                }
160            }
161            for (Statement stmt : graph.getStatements(null, null, replacedResource)) {
162                if (!isCopyFromSource(stmt, sourceResource)) {
163                    // do not copy previous relations that come from a
164                    // source
165                    // copy
166                    stmt.setObject(publishedResource);
167                    newStatements.add(stmt);
168                }
169            }
170            if (!newStatements.isEmpty()) {
171                // add the rewritten statements on the proxy
172                graph.add(newStatements);
173            }
174        }
175    }
176
177    protected boolean isCopyFromSource(Statement stmt, Resource sourceResource) {
178        Node[] values = stmt.getProperties(RelationConstants.COPY_FROM_WORK_VERSION);
179        if (values == null) {
180            return false;
181        } else {
182            return Arrays.asList(values).contains(sourceResource);
183        }
184    }
185
186    protected void copyRelationsFromWorkingCopy(RelationManager rmanager, Resource sourceResource,
187            Resource publishedResource) {
188        for (String graphName : getGraphNamesForCopyFromWork()) {
189            Graph graph = rmanager.getGraphByName(graphName);
190
191            // collect existing relations to or from the source document
192            List<Statement> newStatements = new ArrayList<Statement>();
193            for (Statement stmt : graph.getStatements(sourceResource, null, null)) {
194                stmt.setSubject(publishedResource);
195                stmt.addProperty(RelationConstants.COPY_FROM_WORK_VERSION, sourceResource);
196                newStatements.add(stmt);
197            }
198            for (Statement stmt : graph.getStatements(null, null, sourceResource)) {
199                stmt.setObject(publishedResource);
200                stmt.addProperty(RelationConstants.COPY_FROM_WORK_VERSION, sourceResource);
201                newStatements.add(stmt);
202            }
203
204            if (!newStatements.isEmpty()) {
205                // add the rewritten statements on the proxy
206                graph.add(newStatements);
207            }
208        }
209    }
210}