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}