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