001/*
002 * (C) Copyright 2006-2014 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 */
019package org.nuxeo.ecm.platform.userworkspace.core.service;
020
021import java.io.Serializable;
022import java.security.Principal;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.apache.commons.lang.StringUtils;
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.nuxeo.common.utils.IdUtils;
030import org.nuxeo.common.utils.Path;
031import org.nuxeo.ecm.core.api.CoreSession;
032import org.nuxeo.ecm.core.api.DocumentModel;
033import org.nuxeo.ecm.core.api.DocumentModelList;
034import org.nuxeo.ecm.core.api.DocumentNotFoundException;
035import org.nuxeo.ecm.core.api.NuxeoException;
036import org.nuxeo.ecm.core.api.NuxeoPrincipal;
037import org.nuxeo.ecm.core.api.PathRef;
038import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
039import org.nuxeo.ecm.core.api.event.CoreEventConstants;
040import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService;
041import org.nuxeo.ecm.core.api.pathsegment.PathSegmentServiceDefault;
042import org.nuxeo.ecm.core.event.Event;
043import org.nuxeo.ecm.core.event.EventContext;
044import org.nuxeo.ecm.core.event.EventProducer;
045import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
046import org.nuxeo.ecm.core.event.impl.EventContextImpl;
047import org.nuxeo.ecm.platform.usermanager.UserAdapter;
048import org.nuxeo.ecm.platform.usermanager.UserManager;
049import org.nuxeo.ecm.platform.userworkspace.api.UserWorkspaceService;
050import org.nuxeo.ecm.platform.userworkspace.constants.UserWorkspaceConstants;
051import org.nuxeo.runtime.api.Framework;
052
053/**
054 * Abstract class holding most of the logic for using {@link UnrestrictedSessionRunner} while creating UserWorkspaces
055 * and associated resources
056 *
057 * @author tiry
058 * @since 5.9.5
059 */
060public abstract class AbstractUserWorkspaceImpl implements UserWorkspaceService {
061
062    private static final Log log = LogFactory.getLog(DefaultUserWorkspaceServiceImpl.class);
063
064    private static final long serialVersionUID = 1L;
065
066    protected String targetDomainName;
067
068    public AbstractUserWorkspaceImpl() {
069        super();
070    }
071
072    protected String getDomainName(CoreSession userCoreSession, DocumentModel currentDocument) {
073        if (targetDomainName == null) {
074            RootDomainFinder finder = new RootDomainFinder(userCoreSession);
075            finder.runUnrestricted();
076            targetDomainName = finder.domaineName;
077        }
078        return targetDomainName;
079    }
080
081    protected String getUserWorkspaceNameForUser(String userName) {
082        PathSegmentService pss = Framework.getLocalService(PathSegmentService.class);
083        return IdUtils.generateId(userName, "-", false, pss.getMaxSize());
084    }
085
086    protected String computePathUserWorkspaceRoot(CoreSession userCoreSession, String usedUsername,
087            DocumentModel currentDocument) {
088        String domainName = getDomainName(userCoreSession, currentDocument);
089        if (domainName == null) {
090            throw new NuxeoException("Unable to find root domain for UserWorkspace");
091        }
092        Path path = new Path("/" + domainName);
093        path = path.append(UserWorkspaceConstants.USERS_PERSONAL_WORKSPACES_ROOT);
094        return path.toString();
095    }
096
097    protected String computePathForUserWorkspace(CoreSession userCoreSession, String userName,
098            DocumentModel currentDocument) {
099        String rootPath = computePathUserWorkspaceRoot(userCoreSession, userName, currentDocument);
100        Path path = new Path(rootPath);
101        path = path.append(getUserWorkspaceNameForUser(userName));
102        return path.toString();
103    }
104
105    protected String computePathForUserWorkspaceCompat(CoreSession userCoreSession, String userName,
106            DocumentModel currentDocument) {
107        String rootPath = computePathUserWorkspaceRoot(userCoreSession, userName, currentDocument);
108        Path path = new Path(rootPath);
109        path = path.append(IdUtils.generateId(userName, "-", false, 30));
110        return path.toString();
111    }
112
113    @Override
114    public DocumentModel getCurrentUserPersonalWorkspace(String userName, DocumentModel currentDocument)
115            {
116        if (currentDocument == null) {
117            return null;
118        }
119        return getCurrentUserPersonalWorkspace(null, userName, currentDocument.getCoreSession(), currentDocument);
120    }
121
122    @Override
123    public DocumentModel getCurrentUserPersonalWorkspace(CoreSession userCoreSession, DocumentModel context)
124            {
125        return getCurrentUserPersonalWorkspace(userCoreSession.getPrincipal(), null, userCoreSession, context);
126    }
127
128    /**
129     * This method handles the UserWorkspace creation with a Principal or a username. At least one should be passed. If
130     * a principal is passed, the username is not taken into account.
131     *
132     * @since 5.7 "userWorkspaceCreated" is triggered
133     */
134    protected DocumentModel getCurrentUserPersonalWorkspace(Principal principal, String userName,
135            CoreSession userCoreSession, DocumentModel context) {
136        if (principal == null && StringUtils.isEmpty(userName)) {
137            throw new NuxeoException("You should pass at least one principal or one username");
138        }
139
140        String usedUsername;
141        if (principal instanceof NuxeoPrincipal) {
142            usedUsername = ((NuxeoPrincipal) principal).getActingUser();
143        } else {
144            usedUsername = userName;
145        }
146
147        PathRef uwsDocRef = getExistingUserWorkspacePathRef(userCoreSession, usedUsername, context);
148
149        if (uwsDocRef == null) {
150            // do the creation
151            uwsDocRef = new PathRef(computePathForUserWorkspace(userCoreSession, usedUsername, context));
152            PathRef rootRef = new PathRef(computePathUserWorkspaceRoot(userCoreSession, usedUsername, context));
153            uwsDocRef = createUserWorkspace(rootRef, uwsDocRef, userCoreSession, principal, usedUsername);
154        }
155
156        // force Session synchro to process invalidation (in non JCA cases)
157        if (userCoreSession.getClass().getSimpleName().equals("LocalSession")) {
158            userCoreSession.save();
159        }
160
161        return userCoreSession.getDocument(uwsDocRef);
162    }
163
164    protected PathRef getExistingUserWorkspacePathRef(CoreSession userCoreSession, String usedUsername,
165            DocumentModel context) {
166        PathRef uwsDocRef = new PathRef(computePathForUserWorkspace(userCoreSession, usedUsername, context));
167        if (userCoreSession.exists(uwsDocRef)) {
168            return uwsDocRef;
169        }
170        // The document does not exist, try with the previous max size (30)
171        uwsDocRef = new PathRef(computePathForUserWorkspaceCompat(userCoreSession, usedUsername, context));
172        if (userCoreSession.exists(uwsDocRef)) {
173            return uwsDocRef;
174        }
175        return null;
176    }
177
178    protected synchronized PathRef createUserWorkspace(PathRef rootRef, PathRef userWSRef, CoreSession userCoreSession,
179            Principal principal, String userName) {
180
181        UnrestrictedUWSCreator creator = new UnrestrictedUWSCreator(rootRef, userWSRef, userCoreSession, principal,
182                userName);
183        creator.runUnrestricted();
184        userWSRef = creator.userWSRef;
185        return userWSRef;
186    }
187
188    @Override
189    public DocumentModel getUserPersonalWorkspace(NuxeoPrincipal principal, DocumentModel context)
190            {
191        return getCurrentUserPersonalWorkspace(principal, null, context.getCoreSession(), context);
192    }
193
194    @Override
195    public DocumentModel getUserPersonalWorkspace(String userName, DocumentModel context) {
196        UnrestrictedUserWorkspaceFinder finder = new UnrestrictedUserWorkspaceFinder(userName, context);
197        finder.runUnrestricted();
198        return finder.getDetachedUserWorkspace();
199    }
200
201    protected String buildUserWorkspaceTitle(Principal principal, String userName) {
202        if (userName == null) {// avoid looking for UserManager for nothing
203            return null;
204        }
205        // get the user service
206        UserManager userManager = Framework.getService(UserManager.class);
207        if (userManager == null) {
208            // for tests
209            return userName;
210        }
211
212        // Adapter userModel to get its fields (firstname, lastname)
213        DocumentModel userModel = userManager.getUserModel(userName);
214        if (userModel == null) {
215            return userName;
216        }
217
218        UserAdapter userAdapter = null;
219        userAdapter = userModel.getAdapter(UserAdapter.class);
220
221        if (userAdapter == null) {
222            return userName;
223        }
224
225        // compute the title
226        StringBuilder title = new StringBuilder();
227        String firstName = userAdapter.getFirstName();
228        if (firstName != null && firstName.trim().length() > 0) {
229            title.append(firstName);
230        }
231
232        String lastName = userAdapter.getLastName();
233        if (lastName != null && lastName.trim().length() > 0) {
234            if (title.length() > 0) {
235                title.append(" ");
236            }
237            title.append(lastName);
238        }
239
240        if (title.length() > 0) {
241            return title.toString();
242        }
243
244        return userName;
245
246    }
247
248    protected void notifyEvent(CoreSession coreSession, DocumentModel document, NuxeoPrincipal principal,
249            String eventId, Map<String, Serializable> properties) {
250        if (properties == null) {
251            properties = new HashMap<String, Serializable>();
252        }
253        EventContext eventContext = null;
254        if (document != null) {
255            properties.put(CoreEventConstants.REPOSITORY_NAME, document.getRepositoryName());
256            properties.put(CoreEventConstants.SESSION_ID, coreSession.getSessionId());
257            properties.put(CoreEventConstants.DOC_LIFE_CYCLE, document.getCurrentLifeCycleState());
258            eventContext = new DocumentEventContext(coreSession, principal, document);
259        } else {
260            eventContext = new EventContextImpl(coreSession, principal);
261        }
262        eventContext.setProperties(properties);
263        Event event = eventContext.newEvent(eventId);
264        Framework.getLocalService(EventProducer.class).fireEvent(event);
265    }
266
267    protected class UnrestrictedUWSCreator extends UnrestrictedSessionRunner {
268
269        PathRef rootRef;
270
271        PathRef userWSRef;
272
273        String userName;
274
275        Principal principal;
276
277        public UnrestrictedUWSCreator(PathRef rootRef, PathRef userWSRef, CoreSession userCoreSession,
278                Principal principal, String userName) {
279            super(userCoreSession);
280            this.rootRef = rootRef;
281            this.userWSRef = userWSRef;
282            this.userName = userName;
283            this.principal = principal;
284        }
285
286        @Override
287        public void run() {
288
289            // create root if needed
290            if (!session.exists(rootRef)) {
291                DocumentModel root;
292                try {
293                    root = doCreateUserWorkspacesRoot(session, rootRef);
294                } catch (DocumentNotFoundException e) {
295                    // domain may have been removed !
296                    targetDomainName = null;
297                    rootRef = new PathRef(computePathUserWorkspaceRoot(session, userName, null));
298                    root = doCreateUserWorkspacesRoot(session, rootRef);
299                    userWSRef = new PathRef(computePathForUserWorkspace(session, userName, null));
300                }
301                assert (root.getPathAsString().equals(rootRef.toString()));
302            }
303
304            // create user WS if needed
305            if (!session.exists(userWSRef)) {
306                DocumentModel uw = doCreateUserWorkspace(session, userWSRef, principal, userName);
307                assert (uw.getPathAsString().equals(userWSRef.toString()));
308            }
309
310            session.save();
311        }
312
313    }
314
315    protected class UnrestrictedUserWorkspaceFinder extends UnrestrictedSessionRunner {
316
317        protected DocumentModel userWorkspace;
318
319        protected String userName;
320
321        protected DocumentModel context;
322
323        protected UnrestrictedUserWorkspaceFinder(String userName, DocumentModel context) {
324            super(context.getCoreSession().getRepositoryName(), userName);
325            this.userName = userName;
326            this.context = context;
327        }
328
329        @Override
330        public void run() {
331            userWorkspace = getCurrentUserPersonalWorkspace(null, userName, session, context);
332            if (userWorkspace != null) {
333                userWorkspace.detach(true);
334            }
335        }
336
337        public DocumentModel getDetachedUserWorkspace() {
338            return userWorkspace;
339        }
340    }
341
342    protected class RootDomainFinder extends UnrestrictedSessionRunner {
343
344        public RootDomainFinder(CoreSession userCoreSession) {
345            super(userCoreSession);
346        }
347
348        protected String domaineName;
349
350        @Override
351        public void run() {
352
353            String targetName = getComponent().getTargetDomainName();
354            PathRef ref = new PathRef("/" + targetName);
355            if (session.exists(ref)) {
356                domaineName = targetName;
357                return;
358            }
359            // configured domain does not exist !!!
360            DocumentModelList domains = session.query("select * from Domain order by dc:created");
361
362            if (!domains.isEmpty()) {
363                domaineName = domains.get(0).getName();
364            }
365        }
366    }
367
368    protected UserWorkspaceServiceImplComponent getComponent() {
369        return (UserWorkspaceServiceImplComponent) Framework.getRuntime().getComponent(
370                UserWorkspaceServiceImplComponent.NAME);
371    }
372
373    protected abstract DocumentModel doCreateUserWorkspacesRoot(CoreSession unrestrictedSession, PathRef rootRef);
374
375    protected abstract DocumentModel doCreateUserWorkspace(CoreSession unrestrictedSession, PathRef wsRef,
376            Principal principal, String userName);
377
378}