001/* 002 * (C) Copyright 2013-2015 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 * Olivier Grisel <ogrisel@nuxeo.com> 018 * Antoine Taillefer <ataillefer@nuxeo.com> 019 */ 020package org.nuxeo.drive.seam; 021 022import java.io.Serializable; 023import java.security.Principal; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.Set; 027 028import javax.faces.context.FacesContext; 029import javax.servlet.ServletRequest; 030 031import org.apache.commons.lang.ObjectUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.jboss.seam.Component; 035import org.jboss.seam.ScopeType; 036import org.jboss.seam.annotations.Factory; 037import org.jboss.seam.annotations.In; 038import org.jboss.seam.annotations.Install; 039import org.jboss.seam.annotations.Name; 040import org.jboss.seam.annotations.Scope; 041import org.jboss.seam.contexts.Context; 042import org.jboss.seam.contexts.Contexts; 043import org.nuxeo.common.Environment; 044import org.nuxeo.common.utils.URIUtils; 045import org.nuxeo.drive.NuxeoDriveConstants; 046import org.nuxeo.drive.adapter.FileSystemItem; 047import org.nuxeo.drive.hierarchy.userworkspace.adapter.UserWorkspaceHelper; 048import org.nuxeo.drive.service.FileSystemItemAdapterService; 049import org.nuxeo.drive.service.NuxeoDriveManager; 050import org.nuxeo.ecm.core.api.Blob; 051import org.nuxeo.ecm.core.api.CoreSession; 052import org.nuxeo.ecm.core.api.DocumentModel; 053import org.nuxeo.ecm.core.api.DocumentModelList; 054import org.nuxeo.ecm.core.api.DocumentRef; 055import org.nuxeo.ecm.core.api.IdRef; 056import org.nuxeo.ecm.core.api.LifeCycleConstants; 057import org.nuxeo.ecm.core.api.NuxeoException; 058import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 059import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 060import org.nuxeo.ecm.core.api.security.SecurityConstants; 061import org.nuxeo.ecm.core.io.download.DownloadService; 062import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 063import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 064import org.nuxeo.ecm.tokenauth.service.TokenAuthenticationService; 065import org.nuxeo.ecm.user.center.UserCenterViewManager; 066import org.nuxeo.ecm.webapp.base.InputController; 067import org.nuxeo.ecm.webapp.contentbrowser.DocumentActions; 068import org.nuxeo.ecm.webapp.security.AbstractUserGroupManagement; 069import org.nuxeo.runtime.api.Framework; 070 071/** 072 * @since 5.7 073 */ 074@Name("nuxeoDriveActions") 075@Scope(ScopeType.PAGE) 076@Install(precedence = Install.FRAMEWORK) 077public class NuxeoDriveActions extends InputController implements Serializable { 078 079 private static final long serialVersionUID = 1L; 080 081 private static final Log log = LogFactory.getLog(NuxeoDriveActions.class); 082 083 protected static final String IS_UNDER_SYNCHRONIZATION_ROOT = "nuxeoDriveIsUnderSynchronizationRoot"; 084 085 protected static final String CURRENT_SYNCHRONIZATION_ROOT = "nuxeoDriveCurrentSynchronizationRoot"; 086 087 public static final String NXDRIVE_PROTOCOL = "nxdrive"; 088 089 public static final String PROTOCOL_COMMAND_EDIT = "edit"; 090 091 /** 092 * @deprecated Use {@link NuxeoDriveConstants#UPDATE_SITE_URL_PROP_KEY} instead 093 */ 094 @Deprecated 095 public static final String UPDATE_SITE_URL_PROP_KEY = NuxeoDriveConstants.UPDATE_SITE_URL_PROP_KEY; 096 097 /** 098 * @deprecated Since 7.10. Use {@link Environment} properties 099 */ 100 @Deprecated 101 public static final String SERVER_VERSION_PROP_KEY = Environment.PRODUCT_VERSION; 102 103 public static final String NEW_DRIVE_EDIT_URL_PROP_KEY = "org.nuxeo.drive.new.edit.url"; 104 105 public static final String DESKTOP_PACKAGE_URL_LATEST_SEGMENT = "latest"; 106 107 public static final String DESKTOP_PACKAGE_PREFIX = "nuxeo-drive."; 108 109 public static final String MSI_EXTENSION = "msi"; 110 111 public static final String DMG_EXTENSION = "dmg"; 112 113 public static final String WINDOWS_PLATFORM = "windows"; 114 115 public static final String OSX_PLATFORM = "osx"; 116 117 private static final String DRIVE_METADATA_VIEW = "view_drive_metadata"; 118 119 @SuppressWarnings("hiding") 120 @In(create = true, required = false) 121 protected transient NavigationContext navigationContext; 122 123 @In(create = true, required = false) 124 protected transient CoreSession documentManager; 125 126 @In(create = true, required = false) 127 protected transient UserCenterViewManager userCenterViews; 128 129 @In(create = true) 130 protected transient DocumentActions documentActions; 131 132 @Factory(value = CURRENT_SYNCHRONIZATION_ROOT, scope = ScopeType.EVENT) 133 public DocumentModel getCurrentSynchronizationRoot() { 134 // Use the event context as request cache 135 Context cache = Contexts.getEventContext(); 136 Boolean isUnderSync = (Boolean) cache.get(IS_UNDER_SYNCHRONIZATION_ROOT); 137 if (isUnderSync == null) { 138 NuxeoDriveManager driveManager = Framework.getLocalService(NuxeoDriveManager.class); 139 Set<IdRef> references = driveManager.getSynchronizationRootReferences(documentManager); 140 DocumentModelList path = navigationContext.getCurrentPath(); 141 DocumentModel root = null; 142 // list is ordered such as closest synchronized ancestor is 143 // considered the current synchronization root 144 for (DocumentModel parent : path) { 145 if (references.contains(parent.getRef())) { 146 root = parent; 147 break; 148 } 149 } 150 cache.set(CURRENT_SYNCHRONIZATION_ROOT, root); 151 cache.set(IS_UNDER_SYNCHRONIZATION_ROOT, root != null); 152 } 153 return (DocumentModel) cache.get(CURRENT_SYNCHRONIZATION_ROOT); 154 } 155 156 public boolean canEditDocument(DocumentModel doc) { 157 if (doc == null || !documentManager.exists(doc.getRef())) { 158 return false; 159 } 160 if (doc.isFolder() || doc.isProxy()) { 161 return false; 162 } 163 if (!documentManager.hasPermission(doc.getRef(), SecurityConstants.WRITE)) { 164 return false; 165 } 166 // Check if current document can be adapted as a FileSystemItem 167 return getFileSystemItem(doc) != null; 168 } 169 170 public boolean hasOneDriveToken(Principal user) { 171 TokenAuthenticationService tokenService = Framework.getLocalService(TokenAuthenticationService.class); 172 for (DocumentModel token : tokenService.getTokenBindings(user.getName())) { 173 if ("Nuxeo Drive".equals(token.getPropertyValue("authtoken:applicationName"))) { 174 return true; 175 } 176 } 177 return false; 178 } 179 180 /** 181 * Returns the Drive edit URL for the current document. 182 * 183 * @see #getDriveEditURL(DocumentModel) 184 */ 185 public String getDriveEditURL() { 186 @SuppressWarnings("hiding") 187 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 188 return getDriveEditURL(currentDocument); 189 } 190 191 /** 192 * Returns the Drive edit URL for the given document. 193 * <p> 194 * {@link #NXDRIVE_PROTOCOL} must be handled by a protocol handler configured on the client side (either on the 195 * browser, or on the OS). 196 * 197 * @since 7.4 198 * @return Drive edit URL in the form "{@link #NXDRIVE_PROTOCOL}:// {@link #PROTOCOL_COMMAND_EDIT} 199 * /protocol/server[:port]/webappName/[user/userName/]repo/repoName/nxdocid/docId/filename/fileName[/ 200 * downloadUrl/downloadUrl]" 201 */ 202 public String getDriveEditURL(@SuppressWarnings("hiding") DocumentModel currentDocument) { 203 if (currentDocument == null) { 204 return null; 205 } 206 // TODO NXP-15397: handle Drive not started exception 207 BlobHolder bh = currentDocument.getAdapter(BlobHolder.class); 208 if (bh == null) { 209 throw new NuxeoException(String.format("Document %s (%s) is not a BlobHolder, cannot get Drive Edit URL.", 210 currentDocument.getPathAsString(), currentDocument.getId())); 211 } 212 Blob blob = bh.getBlob(); 213 if (blob == null) { 214 throw new NuxeoException(String.format("Document %s (%s) has no blob, cannot get Drive Edit URL.", 215 currentDocument.getPathAsString(), currentDocument.getId())); 216 } 217 String fileName = blob.getFilename(); 218 ServletRequest servletRequest = (ServletRequest) FacesContext.getCurrentInstance() 219 .getExternalContext() 220 .getRequest(); 221 String baseURL = VirtualHostHelper.getBaseURL(servletRequest); 222 StringBuffer sb = new StringBuffer(); 223 sb.append(NXDRIVE_PROTOCOL).append("://"); 224 sb.append(PROTOCOL_COMMAND_EDIT).append("/"); 225 sb.append(baseURL.replaceFirst("://", "/")); 226 if (Boolean.valueOf(Framework.getProperty(NEW_DRIVE_EDIT_URL_PROP_KEY))) { 227 sb.append("user/"); 228 sb.append(documentManager.getPrincipal().getName()); 229 sb.append("/"); 230 } 231 sb.append("repo/"); 232 sb.append(documentManager.getRepositoryName()); 233 sb.append("/nxdocid/"); 234 sb.append(currentDocument.getId()); 235 sb.append("/filename/"); 236 String escapedFilename = fileName.replaceAll("(/|\\\\|\\*|<|>|\\?|\"|:|\\|)", "-"); 237 sb.append(URIUtils.quoteURIPathComponent(escapedFilename, true)); 238 if (Boolean.valueOf(Framework.getProperty(NEW_DRIVE_EDIT_URL_PROP_KEY))) { 239 sb.append("/downloadUrl/"); 240 DownloadService downloadService = Framework.getService(DownloadService.class); 241 String downloadUrl = downloadService.getDownloadUrl(currentDocument, DownloadService.BLOBHOLDER_0, ""); 242 sb.append(downloadUrl); 243 } 244 return sb.toString(); 245 } 246 247 public String navigateToUserCenterNuxeoDrive() { 248 return getUserCenterNuxeoDriveView(); 249 } 250 251 @Factory(value = "canSynchronizeCurrentDocument") 252 public boolean canSynchronizeCurrentDocument() { 253 @SuppressWarnings("hiding") 254 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 255 if (currentDocument == null) { 256 return false; 257 } 258 return isSyncRootCandidate(currentDocument) && getCurrentSynchronizationRoot() == null; 259 } 260 261 @Factory(value = "canUnSynchronizeCurrentDocument") 262 public boolean canUnSynchronizeCurrentDocument() { 263 @SuppressWarnings("hiding") 264 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 265 if (currentDocument == null) { 266 return false; 267 } 268 if (!isSyncRootCandidate(currentDocument)) { 269 return false; 270 } 271 DocumentRef currentDocRef = currentDocument.getRef(); 272 DocumentModel currentSyncRoot = getCurrentSynchronizationRoot(); 273 if (currentSyncRoot == null) { 274 return false; 275 } 276 return currentDocRef.equals(currentSyncRoot.getRef()); 277 } 278 279 @Factory(value = "canNavigateToCurrentSynchronizationRoot") 280 public boolean canNavigateToCurrentSynchronizationRoot() { 281 @SuppressWarnings("hiding") 282 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 283 if (currentDocument == null) { 284 return false; 285 } 286 if (LifeCycleConstants.DELETED_STATE.equals(currentDocument.getCurrentLifeCycleState())) { 287 return false; 288 } 289 DocumentRef currentDocRef = currentDocument.getRef(); 290 DocumentModel currentSyncRoot = getCurrentSynchronizationRoot(); 291 if (currentSyncRoot == null) { 292 return false; 293 } 294 return !currentDocRef.equals(currentSyncRoot.getRef()); 295 } 296 297 @Factory(value = "currentDocumentUserWorkspace", scope = ScopeType.PAGE) 298 public boolean isCurrentDocumentUserWorkspace() { 299 @SuppressWarnings("hiding") 300 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 301 if (currentDocument == null) { 302 return false; 303 } 304 return UserWorkspaceHelper.isUserWorkspace(currentDocument); 305 } 306 307 public String synchronizeCurrentDocument() { 308 NuxeoDriveManager driveManager = Framework.getLocalService(NuxeoDriveManager.class); 309 Principal principal = documentManager.getPrincipal(); 310 DocumentModel newSyncRoot = navigationContext.getCurrentDocument(); 311 driveManager.registerSynchronizationRoot(principal, newSyncRoot, documentManager); 312 boolean hasOneNuxeoDriveToken = hasOneDriveToken(principal); 313 if (hasOneNuxeoDriveToken) { 314 return null; 315 } else { 316 // redirect to user center 317 return getUserCenterNuxeoDriveView(); 318 } 319 } 320 321 public void unsynchronizeCurrentDocument() { 322 NuxeoDriveManager driveManager = Framework.getLocalService(NuxeoDriveManager.class); 323 Principal principal = documentManager.getPrincipal(); 324 DocumentModel syncRoot = navigationContext.getCurrentDocument(); 325 driveManager.unregisterSynchronizationRoot(principal, syncRoot, documentManager); 326 } 327 328 public String navigateToCurrentSynchronizationRoot() { 329 DocumentModel currentRoot = getCurrentSynchronizationRoot(); 330 if (currentRoot == null) { 331 return ""; 332 } 333 return navigationContext.navigateToDocument(currentRoot); 334 } 335 336 public DocumentModelList getSynchronizationRoots() { 337 DocumentModelList syncRoots = new DocumentModelListImpl(); 338 NuxeoDriveManager driveManager = Framework.getLocalService(NuxeoDriveManager.class); 339 Set<IdRef> syncRootRefs = driveManager.getSynchronizationRootReferences(documentManager); 340 for (IdRef syncRootRef : syncRootRefs) { 341 syncRoots.add(documentManager.getDocument(syncRootRef)); 342 } 343 return syncRoots; 344 } 345 346 public void unsynchronizeRoot(DocumentModel syncRoot) { 347 NuxeoDriveManager driveManager = Framework.getLocalService(NuxeoDriveManager.class); 348 Principal principal = documentManager.getPrincipal(); 349 driveManager.unregisterSynchronizationRoot(principal, syncRoot, documentManager); 350 } 351 352 @Factory(value = "nuxeoDriveClientPackages", scope = ScopeType.CONVERSATION) 353 public List<DesktopPackageDefinition> getClientPackages() { 354 List<DesktopPackageDefinition> packages = new ArrayList<>(); 355 Object desktopPackageBaseURL = Component.getInstance("desktopPackageBaseURL", ScopeType.APPLICATION); 356 // Add link to packages from the update site 357 if (desktopPackageBaseURL != ObjectUtils.NULL) { 358 // Mac OS X 359 String packageName = DESKTOP_PACKAGE_PREFIX + DMG_EXTENSION; 360 String packageURL = desktopPackageBaseURL + packageName; 361 packages.add(new DesktopPackageDefinition(packageURL, packageName, OSX_PLATFORM)); 362 if (log.isDebugEnabled()) { 363 log.debug(String.format("Added %s to the list of desktop packages available for download.", packageURL)); 364 } 365 // Windows 366 packageName = DESKTOP_PACKAGE_PREFIX + MSI_EXTENSION; 367 packageURL = desktopPackageBaseURL + packageName; 368 packages.add(new DesktopPackageDefinition(packageURL, packageName, WINDOWS_PLATFORM)); 369 if (log.isDebugEnabled()) { 370 log.debug(String.format("Added %s to the list of desktop packages available for download.", packageURL)); 371 } 372 } 373 // Debian / Ubuntu 374 // TODO: remove when Debian package is available 375 packages.add(new DesktopPackageDefinition( 376 "https://github.com/nuxeo/nuxeo-drive#ubuntudebian-and-other-linux-variants-client", 377 "user.center.nuxeoDrive.platform.ubuntu.docLinkTitle", "ubuntu")); 378 return packages; 379 } 380 381 @Factory(value = "desktopPackageBaseURL", scope = ScopeType.APPLICATION) 382 public Object getDesktopPackageBaseURL() { 383 String URL = Framework.getProperty(NuxeoDriveConstants.UPDATE_SITE_URL_PROP_KEY); 384 if (URL == null) { 385 return ObjectUtils.NULL; 386 } 387 StringBuilder sb = new StringBuilder(URL); 388 if (!URL.endsWith("/")) { 389 sb.append("/"); 390 } 391 sb.append(DESKTOP_PACKAGE_URL_LATEST_SEGMENT); 392 sb.append("/"); 393 String platformVersion = Framework.getProperty(Environment.DISTRIBUTION_VERSION); 394 int indexOfHFSuffix = platformVersion.indexOf("-HF"); 395 if (indexOfHFSuffix > -1) { 396 platformVersion = platformVersion.substring(0, indexOfHFSuffix); 397 } 398 sb.append(platformVersion); 399 sb.append("/"); 400 return sb.toString(); 401 } 402 403 protected boolean isSyncRootCandidate(DocumentModel doc) { 404 if (!doc.isFolder()) { 405 return false; 406 } 407 if (LifeCycleConstants.DELETED_STATE.equals(doc.getCurrentLifeCycleState())) { 408 return false; 409 } 410 return true; 411 } 412 413 protected FileSystemItem getFileSystemItem(DocumentModel doc) { 414 // Force parentItem to null to avoid computing ancestors 415 // NXP-19442: Avoid useless and costly call to DocumentModel#getLockInfo 416 FileSystemItem fileSystemItem = Framework.getLocalService(FileSystemItemAdapterService.class) 417 .getFileSystemItem(doc, null, false, false, false); 418 if (fileSystemItem == null) { 419 if (log.isDebugEnabled()) { 420 log.debug(String.format("Document %s (%s) is not adaptable as a FileSystemItem.", 421 doc.getPathAsString(), doc.getId())); 422 } 423 } 424 return fileSystemItem; 425 } 426 427 protected String getUserCenterNuxeoDriveView() { 428 userCenterViews.setCurrentViewId("userCenterNuxeoDrive"); 429 return AbstractUserGroupManagement.VIEW_HOME; 430 } 431 432 /** 433 * Update document model and redirect to drive view. 434 */ 435 public String updateCurrentDocument() { 436 documentActions.updateCurrentDocument(); 437 return DRIVE_METADATA_VIEW; 438 } 439 440}