001/*
002 * (C) Copyright 2009 Nuxeo SA (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 *     arussel
016 */
017package org.nuxeo.ecm.platform.routing.core.impl;
018
019import java.util.ArrayList;
020import java.util.Calendar;
021import java.util.Date;
022import java.util.List;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.nuxeo.ecm.core.api.CoreSession;
027import org.nuxeo.ecm.core.api.DocumentModel;
028import org.nuxeo.ecm.core.api.DocumentModelList;
029import org.nuxeo.ecm.core.api.DocumentRef;
030import org.nuxeo.ecm.core.api.NuxeoPrincipal;
031import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
032import org.nuxeo.ecm.core.api.security.ACE;
033import org.nuxeo.ecm.core.api.security.ACL;
034import org.nuxeo.ecm.core.api.security.ACP;
035import org.nuxeo.ecm.core.api.security.SecurityConstants;
036import org.nuxeo.ecm.platform.routing.api.DocumentRoute;
037import org.nuxeo.ecm.platform.routing.api.DocumentRoutingConstants;
038import org.nuxeo.ecm.platform.routing.api.DocumentRoutingPersister;
039import org.nuxeo.ecm.platform.routing.core.persistence.TreeHelper;
040import org.nuxeo.ecm.platform.usermanager.UserManager;
041import org.nuxeo.ecm.platform.userworkspace.api.UserWorkspaceService;
042import org.nuxeo.runtime.api.Framework;
043
044/**
045 * The default persister. It persists the {@link DocumentRoute} in a tree hierarchy ressembling the current date. New
046 * model created from instance are stored in the personal workspace of the user.
047 *
048 * @author arussel
049 */
050public class DocumentRoutingTreePersister implements DocumentRoutingPersister {
051
052    private static final String DC_TITLE = "dc:title";
053
054    protected static final Log log = LogFactory.getLog(DocumentRoutingTreePersister.class);
055
056    @Override
057    public DocumentModel getParentFolderForDocumentRouteInstance(DocumentModel document, CoreSession session) {
058        return TreeHelper.getOrCreateDateTreeFolder(session, getOrCreateRootOfDocumentRouteInstanceStructure(session),
059                new Date(), "HiddenFolder");
060    }
061
062    @Override
063    public DocumentModel createDocumentRouteInstanceFromDocumentRouteModel(DocumentModel model, CoreSession session) {
064        DocumentModel parent = getParentFolderForDocumentRouteInstance(model, session);
065        DocumentModel result = session.copy(model.getRef(), parent.getRef(), null);
066        // copy now copies all the acls, and we don't need the readOnly
067        // policy applied on the model
068        // on the instance, too => removing acls
069        result = undoReadOnlySecurityPolicy(result, session);
070        // set initiator
071        NuxeoPrincipal principal = (NuxeoPrincipal) session.getPrincipal();
072        String initiator = principal.getActingUser();
073        result.setPropertyValue(DocumentRoutingConstants.INITIATOR, initiator);
074        // using the ref, the value of the attached document might not been
075        // saved on the model
076        result.setPropertyValue(DocumentRoutingConstants.ATTACHED_DOCUMENTS_PROPERTY_NAME,
077                model.getPropertyValue(DocumentRoutingConstants.ATTACHED_DOCUMENTS_PROPERTY_NAME));
078        // reset creation date, used for workflow start time
079        result.setPropertyValue("dc:created", Calendar.getInstance());
080        result.setPropertyValue(DocumentRoutingConstants.DOCUMENT_ROUTE_INSTANCE_MODEL_ID, model.getId());
081        session.saveDocument(result);
082        return result;
083    }
084
085    @Override
086    public DocumentModel saveDocumentRouteInstanceAsNewModel(DocumentModel routeInstance, DocumentModel parentFolder,
087            String newName, CoreSession session) {
088        DocumentModel result = session.copy(routeInstance.getRef(), parentFolder.getRef(), newName);
089        return undoReadOnlySecurityPolicy(result, session);
090    }
091
092    @Override
093    public DocumentModel getOrCreateRootOfDocumentRouteInstanceStructure(CoreSession session) {
094        DocumentModel root = getDocumentRoutesStructure(
095                DocumentRoutingConstants.DOCUMENT_ROUTE_INSTANCES_ROOT_DOCUMENT_TYPE, session);
096        if (root == null) {
097            root = createDocumentRoutesStructure(DocumentRoutingConstants.DOCUMENT_ROUTE_INSTANCES_ROOT_DOCUMENT_TYPE,
098                    DocumentRoutingConstants.DOCUMENT_ROUTE_INSTANCES_ROOT_ID, session);
099        }
100        return root;
101    }
102
103    /**
104     * Finds the first domain by name, and creates under it the root container for the structure containing the route
105     * instances.
106     */
107    protected DocumentModel createDocumentRoutesStructure(String routeStructureDocType, String id, CoreSession session)
108            {
109        DocumentModel root = session.createDocumentModel(session.getRootDocument().getPathAsString(), id,
110                routeStructureDocType);
111        root.setPropertyValue(DC_TITLE, routeStructureDocType);
112        root = session.createDocument(root);
113        ACP acp = session.getACP(root.getRef());
114        ACL acl = acp.getOrCreateACL(ACL.LOCAL_ACL);
115        acl.addAll(getACEs());
116        session.setACP(root.getRef(), acp, true);
117        return root;
118    }
119
120    /**
121     * Create the rootModels under to root document. Grant READ to everyone on the root models ; workflow availability
122     * is specified on each route
123     *
124     * @param routeStructureDocType
125     * @param id
126     * @param session
127     * @return
128     */
129    protected DocumentModel createModelsRoutesStructure(String routeStructureDocType, String id, CoreSession session)
130            {
131        DocumentModel rootModels = session.createDocumentModel("/", id, routeStructureDocType);
132        rootModels.setPropertyValue(DC_TITLE, routeStructureDocType);
133        rootModels = session.createDocument(rootModels);
134        ACP acp = session.getACP(rootModels.getRef());
135        ACL acl = acp.getOrCreateACL(ACL.LOCAL_ACL);
136        acl.add(new ACE(SecurityConstants.EVERYONE, SecurityConstants.READ, true));
137        session.setACP(rootModels.getRef(), acp, true);
138        return rootModels;
139    }
140
141    /**
142     * @return
143     */
144    protected List<ACE> getACEs() {
145        List<ACE> aces = new ArrayList<ACE>();
146        for (String group : getUserManager().getAdministratorsGroups()) {
147            aces.add(new ACE(group, SecurityConstants.EVERYTHING, true));
148        }
149        aces.add(new ACE(DocumentRoutingConstants.ROUTE_MANAGERS_GROUP_NAME, SecurityConstants.READ_WRITE, true));
150        aces.add(new ACE(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false));
151        return aces;
152    }
153
154    protected UserManager getUserManager() {
155        return Framework.getService(UserManager.class);
156    }
157
158    protected DocumentModel getDocumentRoutesStructure(String type, CoreSession session) {
159        DocumentModelList res = session.query(String.format("SELECT * from %s", type));
160        if (res == null || res.isEmpty()) {
161            return null;
162        }
163        if (res.size() > 1) {
164            if (log.isWarnEnabled()) {
165                log.warn("More han one DocumentRouteInstanceRoot found:");
166                for (DocumentModel model : res) {
167                    log.warn(" - " + model.getName() + ", " + model.getPathAsString());
168                }
169            }
170        }
171        return res.get(0);
172    }
173
174    @Override
175    public DocumentModel getParentFolderForNewModel(CoreSession session, DocumentModel instance) {
176        UserWorkspaceService service = Framework.getService(UserWorkspaceService.class);
177        return service.getCurrentUserPersonalWorkspace(session, instance);
178    }
179
180    @Override
181    public String getNewModelName(DocumentModel instance) {
182        return "(COPY) " + instance.getPropertyValue("dc:title");
183    }
184
185    protected DocumentModel undoReadOnlySecurityPolicy(DocumentModel instance, CoreSession session)
186            {
187        UndoReadOnlySecurityPolicy runner = new UndoReadOnlySecurityPolicy(session, instance.getRef());
188        runner.runUnrestricted();
189        return session.getDocument(runner.getInstanceRef());
190    }
191
192    class UndoReadOnlySecurityPolicy extends UnrestrictedSessionRunner {
193
194        DocumentRef documentRef;
195
196        public UndoReadOnlySecurityPolicy(CoreSession session, DocumentRef documentRef) {
197            super(session);
198            this.documentRef = documentRef;
199        }
200
201        @Override
202        public void run() {
203            DocumentModel instance = session.getDocument(documentRef);
204            if (instance == null) {
205                return;
206            }
207            ACP acp = instance.getACP();
208            // remove READ for everyone
209            ACL routingACL = acp.getOrCreateACL(DocumentRoutingConstants.DOCUMENT_ROUTING_ACL);
210            routingACL.remove(new ACE(SecurityConstants.EVERYONE, SecurityConstants.READ, true));
211            // unblock rights inheritance
212            ACL localACL = acp.getOrCreateACL(ACL.LOCAL_ACL);
213            localACL.remove(new ACE(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false));
214            instance.setACP(acp, true);
215        }
216
217        DocumentRef getInstanceRef() {
218            return documentRef;
219        }
220    }
221
222    @Override
223    public DocumentModel getParentFolderForDocumentRouteModels(CoreSession session) {
224        DocumentModel root = getDocumentRoutesStructure(
225                DocumentRoutingConstants.DOCUMENT_ROUTE_MODELS_ROOT_DOCUMENT_TYPE, session);
226        if (root == null) {
227            root = createModelsRoutesStructure(DocumentRoutingConstants.DOCUMENT_ROUTE_MODELS_ROOT_DOCUMENT_TYPE,
228                    DocumentRoutingConstants.DOCUMENT_ROUTE_MODELS_ROOT_ID, session);
229        }
230        return root;
231    }
232}