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 = (NuxeoPrincipal) 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     * @param routeStructureDocType
127     * @param id
128     * @param session
129     * @return
130     */
131    protected DocumentModel createModelsRoutesStructure(String routeStructureDocType, String id, CoreSession session)
132            {
133        DocumentModel rootModels = session.createDocumentModel("/", id, routeStructureDocType);
134        rootModels.setPropertyValue(DC_TITLE, routeStructureDocType);
135        rootModels = session.createDocument(rootModels);
136        ACP acp = session.getACP(rootModels.getRef());
137        ACL acl = acp.getOrCreateACL(ACL.LOCAL_ACL);
138        acl.add(new ACE(SecurityConstants.EVERYONE, SecurityConstants.READ, true));
139        session.setACP(rootModels.getRef(), acp, true);
140        return rootModels;
141    }
142
143    /**
144     * @return
145     */
146    protected List<ACE> getACEs() {
147        List<ACE> aces = new ArrayList<ACE>();
148        for (String group : getUserManager().getAdministratorsGroups()) {
149            aces.add(new ACE(group, SecurityConstants.EVERYTHING, true));
150        }
151        aces.add(new ACE(DocumentRoutingConstants.ROUTE_MANAGERS_GROUP_NAME, SecurityConstants.READ_WRITE, true));
152        aces.add(new ACE(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false));
153        return aces;
154    }
155
156    protected UserManager getUserManager() {
157        return Framework.getService(UserManager.class);
158    }
159
160    protected DocumentModel getDocumentRoutesStructure(String type, CoreSession session) {
161        DocumentModelList res = session.query(String.format("SELECT * from %s", type));
162        if (res == null || res.isEmpty()) {
163            return null;
164        }
165        if (res.size() > 1) {
166            if (log.isWarnEnabled()) {
167                log.warn("More han one DocumentRouteInstanceRoot found:");
168                for (DocumentModel model : res) {
169                    log.warn(" - " + model.getName() + ", " + model.getPathAsString());
170                }
171            }
172        }
173        return res.get(0);
174    }
175
176    @Override
177    public DocumentModel getParentFolderForNewModel(CoreSession session, DocumentModel instance) {
178        UserWorkspaceService service = Framework.getService(UserWorkspaceService.class);
179        return service.getCurrentUserPersonalWorkspace(session, instance);
180    }
181
182    @Override
183    public String getNewModelName(DocumentModel instance) {
184        return "(COPY) " + instance.getPropertyValue("dc:title");
185    }
186
187    protected DocumentModel undoReadOnlySecurityPolicy(DocumentModel instance, CoreSession session)
188            {
189        UndoReadOnlySecurityPolicy runner = new UndoReadOnlySecurityPolicy(session, instance.getRef());
190        runner.runUnrestricted();
191        return session.getDocument(runner.getInstanceRef());
192    }
193
194    class UndoReadOnlySecurityPolicy extends UnrestrictedSessionRunner {
195
196        DocumentRef documentRef;
197
198        public UndoReadOnlySecurityPolicy(CoreSession session, DocumentRef documentRef) {
199            super(session);
200            this.documentRef = documentRef;
201        }
202
203        @Override
204        public void run() {
205            DocumentModel instance = session.getDocument(documentRef);
206            if (instance == null) {
207                return;
208            }
209            ACP acp = instance.getACP();
210            // remove READ for everyone
211            ACL routingACL = acp.getOrCreateACL(DocumentRoutingConstants.DOCUMENT_ROUTING_ACL);
212            routingACL.remove(new ACE(SecurityConstants.EVERYONE, SecurityConstants.READ, true));
213            // unblock rights inheritance
214            ACL localACL = acp.getOrCreateACL(ACL.LOCAL_ACL);
215            localACL.remove(new ACE(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false));
216            instance.setACP(acp, true);
217        }
218
219        DocumentRef getInstanceRef() {
220            return documentRef;
221        }
222    }
223
224    @Override
225    public DocumentModel getParentFolderForDocumentRouteModels(CoreSession session) {
226        DocumentModel root = getDocumentRoutesStructure(
227                DocumentRoutingConstants.DOCUMENT_ROUTE_MODELS_ROOT_DOCUMENT_TYPE, session);
228        if (root == null) {
229            root = createModelsRoutesStructure(DocumentRoutingConstants.DOCUMENT_ROUTE_MODELS_ROOT_DOCUMENT_TYPE,
230                    DocumentRoutingConstants.DOCUMENT_ROUTE_MODELS_ROOT_ID, session);
231        }
232        return root;
233    }
234}