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