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