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