/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ package org.alfresco.wcm.asset; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.mbeans.VirtServerRegistry; import org.alfresco.model.ApplicationModel; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.executer.ImporterActionExecuter; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.avm.util.AVMUtil; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.locking.AVMLockingService; import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; import org.alfresco.util.TempFileProvider; import org.alfresco.wcm.sandbox.SandboxConstants; import org.alfresco.wcm.util.WCMUtil; import org.apache.commons.compress.archivers.zip.ZipFile; import org.springframework.extensions.surf.util.ParameterCheck; /** * Asset Service fundamental API. *

* This service API is designed to support the public facing Asset APIs. * * @author janv */ public class AssetServiceImpl implements AssetService { private static char PATH_SEPARATOR = '/'; private static final int BUFFER_SIZE = 16384; private AVMService avmService; private AVMLockingService avmLockingService; private NodeService avmNodeService; // AVM node service (ML-aware) private VirtServerRegistry virtServerRegistry; public void setAvmService(AVMService avmService) { this.avmService = avmService; } public void setAvmLockingService(AVMLockingService avmLockingService) { this.avmLockingService = avmLockingService; } public void setNodeService(NodeService avmNodeService) { this.avmNodeService = avmNodeService; } public void setVirtServerRegistry(VirtServerRegistry virtServerRegistry) { this.virtServerRegistry = virtServerRegistry; } private void checkMandatoryPath(String path) { ParameterCheck.mandatoryString("path", path); if (path.indexOf(AVMUtil.AVM_STORE_SEPARATOR_CHAR) != -1) { throw new IllegalArgumentException("Unexpected path '"+path+"' - should not contain '"+WCMUtil.AVM_STORE_SEPARATOR+"'"); } } private boolean isWebProjectStagingSandbox(String sbStoreId) { PropertyValue propVal = avmService.getStoreProperty(sbStoreId, SandboxConstants.PROP_WEB_PROJECT_NODE_REF); return ((propVal != null) && (WCMUtil.isStagingStore(sbStoreId))); } public void createFolderWebApp(String sbStoreId, String webApp, String parentFolderPathRelativeToWebApp, String name) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("webApp", webApp); checkMandatoryPath(parentFolderPathRelativeToWebApp); ParameterCheck.mandatoryString("name", name); if (! isWebProjectStagingSandbox(sbStoreId)) { parentFolderPathRelativeToWebApp = AVMUtil.addLeadingSlash(parentFolderPathRelativeToWebApp); String avmParentPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp) + parentFolderPathRelativeToWebApp; createFolderAVM(avmParentPath, name, null); } else { throw new AccessDeniedException("Not allowed to write in: " + sbStoreId); } } public void createFolder(String sbStoreId, String parentFolderPath, String name, Map properties) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("parentFolderPath", parentFolderPath); ParameterCheck.mandatoryString("name", name); String avmParentPath = AVMUtil.buildAVMPath(sbStoreId, parentFolderPath); createFolderAVM(avmParentPath, name, properties); } private void createFolderAVM(String avmParentPath, String name, Map properties) { ParameterCheck.mandatoryString("avmParentPath", avmParentPath); ParameterCheck.mandatoryString("name", name); String sbStoreId = WCMUtil.getSandboxStoreId(avmParentPath); if (! isWebProjectStagingSandbox(sbStoreId)) { avmService.createDirectory(avmParentPath, name); String avmPath = avmParentPath + PATH_SEPARATOR + name; // for WCM Web Client (Alfresco Explorer) avmService.addAspect(avmPath, ApplicationModel.ASPECT_UIFACETS); if ((properties != null) && (properties.size() > 0)) { setProperties(avmPath, properties); } } else { throw new AccessDeniedException("Not allowed to write in: " + sbStoreId); } } public ContentWriter createFileWebApp(String sbStoreId, String webApp, String parentFolderPathRelativeToWebApp, String name) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("webApp", webApp); ParameterCheck.mandatoryString("parentFolderPathRelativeToWebApp", parentFolderPathRelativeToWebApp); ParameterCheck.mandatoryString("name", name); parentFolderPathRelativeToWebApp = AVMUtil.addLeadingSlash(parentFolderPathRelativeToWebApp); String avmParentPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp) + parentFolderPathRelativeToWebApp; createFileAVM(avmParentPath, name); String avmPath = avmParentPath + PATH_SEPARATOR + name; return avmService.getContentWriter(avmPath, true); } public ContentWriter createFile(String sbStoreId, String parentFolderPath, String name, Map properties) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("parentFolderPath", parentFolderPath); ParameterCheck.mandatoryString("name", name); String avmParentPath = AVMUtil.buildAVMPath(sbStoreId, parentFolderPath); createFileAVM(avmParentPath, name); String avmPath = avmParentPath + PATH_SEPARATOR + name; if ((properties != null) && (properties.size() > 0)) { setProperties(avmPath, properties); } return avmService.getContentWriter(avmPath, true); } private void createFileAVM(String avmParentPath, String name) { ParameterCheck.mandatoryString("avmParentPath", avmParentPath); ParameterCheck.mandatoryString("name", name); String sbStoreId = WCMUtil.getSandboxStoreId(avmParentPath); if (! isWebProjectStagingSandbox(sbStoreId)) { try { avmService.createFile(avmParentPath, name).close(); } catch (IOException e) { throw new AlfrescoRuntimeException("I/O Error.", e); } } else { throw new AccessDeniedException("Not allowed to write in: " + sbStoreId); } } private void createFileAVM(String avmParentPath, String name, InputStream in) { ParameterCheck.mandatoryString("avmParentPath", avmParentPath); String sbStoreId = WCMUtil.getSandboxStoreId(avmParentPath); if (! isWebProjectStagingSandbox(sbStoreId)) { avmService.createFile(avmParentPath, name, in, null, null); } else { throw new AccessDeniedException("Not allowed to write in: " + sbStoreId); } } public ContentWriter getContentWriter(AssetInfo asset) { ParameterCheck.mandatory("asset", asset); if (! isWebProjectStagingSandbox(asset.getSandboxId())) { return avmService.getContentWriter(asset.getAvmPath(), true); } else { throw new AccessDeniedException("Not allowed to write in: " + asset.getSandboxId()); } } public ContentReader getContentReader(AssetInfo asset) { ParameterCheck.mandatory("asset", asset); return avmService.getContentReader(asset.getSandboxVersion(), asset.getAvmPath()); } public AssetInfo getAssetWebApp(String sbStoreId, String webApp, String pathRelativeToWebApp) { return getAssetWebApp(sbStoreId, webApp, pathRelativeToWebApp, false); } public AssetInfo getAssetWebApp(String sbStoreId, String webApp, String pathRelativeToWebApp, boolean includeDeleted) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("webApp", webApp); ParameterCheck.mandatoryString("pathRelativeToWebApp", pathRelativeToWebApp); pathRelativeToWebApp = AVMUtil.addLeadingSlash(pathRelativeToWebApp); String avmPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp) + pathRelativeToWebApp; return getAssetAVM(-1, avmPath, includeDeleted); } public AssetInfo getAsset(String sbStoreId, String path) { return getAsset(sbStoreId, -1, path, false); } public AssetInfo getAsset(String sbStoreId, int version, String path, boolean includeDeleted) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("path", path); String avmPath = AVMUtil.buildAVMPath(sbStoreId, path); return getAssetAVM(version, avmPath, includeDeleted); } private AssetInfo getAssetAVM(int version, String avmPath, boolean includeDeleted) { ParameterCheck.mandatoryString("avmPath", avmPath); AVMNodeDescriptor node = avmService.lookup(version, avmPath, includeDeleted); AssetInfo asset = null; if (node != null) { String lockOwner = null; if (avmLockingService != null) { String wpStoreId = WCMUtil.getWebProjectStoreIdFromPath(avmPath); String[] parts = WCMUtil.splitPath(avmPath); lockOwner = getLockOwner(wpStoreId, parts[1]); } asset = new AssetInfoImpl(version, node, lockOwner); } return asset; } public String getLockOwner(AssetInfo asset) { ParameterCheck.mandatory("asset", asset); return getLockOwner(WCMUtil.getWebProjectStoreId(asset.getSandboxId()), asset.getPath()); } private String getLockOwner(String wpStoreId, String filePath) { return avmLockingService.getLockOwner(wpStoreId, filePath); } public boolean hasLockAccess(AssetInfo asset) { ParameterCheck.mandatory("asset", asset); return avmLockingService.hasAccess( WCMUtil.getWebProjectStoreId(asset.getSandboxId()), asset.getAvmPath(), AuthenticationUtil.getFullyAuthenticatedUser()); } public void updateAssetProperties(AssetInfo asset, Map properties) { ParameterCheck.mandatory("asset", asset); ParameterCheck.mandatory("properties", properties); NodeRef avmNodeRef = AVMNodeConverter.ToNodeRef(-1, asset.getAvmPath()); for (Map.Entry prop : properties.entrySet()) { avmNodeService.setProperty(avmNodeRef, prop.getKey(), prop.getValue()); } } public void setAssetProperties(AssetInfo asset, Map properties) { ParameterCheck.mandatory("asset", asset); ParameterCheck.mandatory("properties", properties); setProperties(asset.getAvmPath(), properties); } private void setProperties(String avmPath, Map properties) { NodeRef avmNodeRef = AVMNodeConverter.ToNodeRef(-1, avmPath); avmNodeService.setProperties(avmNodeRef, properties); } public void addAspect(AssetInfo asset, QName aspectName, Map properties) { addAspect(asset.getAvmPath(), aspectName, properties); } private void addAspect(String avmPath, QName aspect, Map properties) { NodeRef avmNodeRef = AVMNodeConverter.ToNodeRef(-1, avmPath); avmNodeService.addAspect(avmNodeRef, aspect, properties); } public void removeAspect(AssetInfo asset, QName aspectName) { ParameterCheck.mandatory("asset", asset); NodeRef avmNodeRef = AVMNodeConverter.ToNodeRef(-1, asset.getAvmPath()); avmNodeService.removeAspect(avmNodeRef, aspectName); } public Set getAspects(AssetInfo asset) { ParameterCheck.mandatory("asset", asset); NodeRef avmNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath()); return avmNodeService.getAspects(avmNodeRef); } public boolean hasAspect(AssetInfo asset, QName aspectName) { ParameterCheck.mandatory("asset", asset); NodeRef avmNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath()); return avmNodeService.hasAspect(avmNodeRef, aspectName); } public Map getAssetProperties(AssetInfo asset) { ParameterCheck.mandatory("asset", asset); return getProperties(asset.getSandboxVersion(), asset.getAvmPath()); } private Map getProperties(int version, String avmPath) { NodeRef avmNodeRef = AVMNodeConverter.ToNodeRef(version, avmPath); return avmNodeService.getProperties(avmNodeRef); // note: includes built-in properties } public List listAssetsWebApp(String sbStoreId, String webApp, String parentFolderPathRelativeToWebApp, boolean includeDeleted) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("webApp", webApp); ParameterCheck.mandatoryString("parentFolderPathRelativeToWebApp", parentFolderPathRelativeToWebApp); parentFolderPathRelativeToWebApp = AVMUtil.addLeadingSlash(parentFolderPathRelativeToWebApp); String avmPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp) + parentFolderPathRelativeToWebApp; return listAssetsAVM(-1, avmPath, includeDeleted); } public List listAssets(String sbStoreId, String parentFolderPath, boolean includeDeleted) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("parentFolderPath", parentFolderPath); String avmPath = AVMUtil.buildAVMPath(sbStoreId, parentFolderPath); return listAssetsAVM(-1, avmPath, includeDeleted); } public List listAssets(String sbStoreId, int version, String parentFolderPath, boolean includeDeleted) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("parentFolderPath", parentFolderPath); String avmPath = AVMUtil.buildAVMPath(sbStoreId, parentFolderPath); return listAssetsAVM(version, avmPath, includeDeleted); } private List listAssetsAVM(int version, String avmPath, boolean includeDeleted) { ParameterCheck.mandatoryString("avmPath", avmPath); Map nodes = avmService.getDirectoryListing(version, avmPath, includeDeleted); List assets = new ArrayList(nodes.size()); for (AVMNodeDescriptor node : nodes.values()) { String lockOwner = null; if (avmLockingService != null) { String wpStoreId = WCMUtil.getWebProjectStoreIdFromPath(avmPath); String[] parts = WCMUtil.splitPath(avmPath); lockOwner = getLockOwner(wpStoreId, parts[1]); } assets.add(new AssetInfoImpl(version, node, lockOwner)); } return assets; } public void deleteAsset(AssetInfo asset) { ParameterCheck.mandatory("asset", asset); if (! isWebProjectStagingSandbox(asset.getSandboxId())) { avmService.removeNode(asset.getAvmPath()); } else { throw new AccessDeniedException("Not allowed to write in: " + asset.getSandboxId()); } } public AssetInfo renameAsset(AssetInfo asset, String newName) { ParameterCheck.mandatory("asset", asset); if (! isWebProjectStagingSandbox(asset.getSandboxId())) { String avmParentPath = AVMUtil.splitBase(asset.getAvmPath())[0]; String oldName = asset.getName(); avmService.rename(avmParentPath, oldName, avmParentPath, newName); return getAsset(asset.getSandboxId(), WCMUtil.getStoreRelativePath(avmParentPath)+"/"+newName); } else { throw new AccessDeniedException("Not allowed to write in: " + asset.getSandboxId()); } } public AssetInfo moveAsset(AssetInfo asset, String parentFolderPath) { ParameterCheck.mandatory("asset", asset); if (! isWebProjectStagingSandbox(asset.getSandboxId())) { String avmDstPath = AVMUtil.buildAVMPath(asset.getSandboxId(), parentFolderPath); String avmSrcPath = AVMUtil.splitBase(asset.getAvmPath())[0]; String name = asset.getName(); avmService.rename(avmSrcPath, name, avmDstPath, name); return getAsset(asset.getSandboxId(), WCMUtil.getStoreRelativePath(avmDstPath)+"/"+name); } else { throw new AccessDeniedException("Not allowed to write in: " + asset.getSandboxId()); } } public AssetInfo copyAsset(AssetInfo asset, String parentFolderPath) { ParameterCheck.mandatory("asset", asset); if (! isWebProjectStagingSandbox(asset.getSandboxId())) { String avmDstParentPath = AVMUtil.buildAVMPath(asset.getSandboxId(), parentFolderPath); String avmSrcPath = asset.getAvmPath(); String name = asset.getName(); avmService.copy(-1, avmSrcPath, avmDstParentPath, name); return getAsset(asset.getSandboxId(), WCMUtil.getStoreRelativePath(avmDstParentPath+"/"+name)); } else { throw new AccessDeniedException("Not allowed to write in: " + asset.getSandboxId()); } } // TODO should this be in sandbox service ? public void bulkImport(String sbStoreId, String parentFolderPath, File zipFile, boolean isHighByteZip) { if (! isWebProjectStagingSandbox(sbStoreId)) { String avmDstPath = AVMUtil.buildAVMPath(sbStoreId, parentFolderPath); // convert the AVM path to a NodeRef so we can use the NodeService to perform import NodeRef importRef = AVMNodeConverter.ToNodeRef(-1, avmDstPath); processZipImport(zipFile, isHighByteZip, importRef); // After a bulk import, snapshot the store avmService.createSnapshot(sbStoreId, "Import of file: " + zipFile.getName(), null); // Bind the post-commit transaction listener with data required for virtualization server notification UpdateSandboxTransactionListener tl = new UpdateSandboxTransactionListener(avmDstPath); AlfrescoTransactionSupport.bindListener(tl); } else { throw new AccessDeniedException("Not allowed to write in: " + sbStoreId); } } /** * Process ZIP file for import into an AVM repository store location * * @param file ZIP format file * @param rootRef Root reference of the AVM location to import into */ private void processZipImport(File file, boolean isHighByteZip, NodeRef rootRef) { try { // NOTE: This encoding allows us to workaround bug: // http://bugs.sun.com/bugdatabase/view_bug.do;:WuuT?bug_id=4820807 // We also try to use the extra encoding information if present ZipFile zipFile = new ZipFile(file, isHighByteZip ? "Cp437" : null, true); File alfTempDir = TempFileProvider.getTempDir(); // build a temp dir name based on the name of the file we are importing File tempDir = new File(alfTempDir.getPath() + File.separatorChar + file.getName() + "_unpack"); try { ImporterActionExecuter.extractFile(zipFile, tempDir.getPath()); importDirectory(tempDir.getPath(), rootRef); } finally { if (tempDir.exists()) { ImporterActionExecuter.deleteDir(tempDir); } } } catch (IOException e) { throw new AlfrescoRuntimeException("Unable to process Zip file. File may not be of the expected format.", e); } } /** * Recursively import a directory structure into the specified root node * * @param dir The directory of files and folders to import * @param root The root node to import into */ private void importDirectory(String dir, NodeRef root) { File topdir = new File(dir); if (!topdir.exists()) return; for (File file : topdir.listFiles()) { try { if (file.isFile()) { // Create a file in the AVM store String avmPath = AVMNodeConverter.ToAVMVersionPath(root).getSecond(); String fileName = file.getName(); Map titledProps = new HashMap(); titledProps.put(ContentModel.PROP_TITLE, fileName); createFileAVM(avmPath, fileName, new BufferedInputStream(new FileInputStream(file), BUFFER_SIZE)); String filePath = avmPath + '/' + fileName; addAspect(filePath, ContentModel.ASPECT_TITLED, titledProps); } else { // Create a directory in the AVM store String avmPath = AVMNodeConverter.ToAVMVersionPath(root).getSecond(); createFolderAVM(avmPath, file.getName(), null); String folderPath = avmPath + '/' + file.getName(); NodeRef folderRef = AVMNodeConverter.ToNodeRef(-1, folderPath); importDirectory(file.getPath(), folderRef); } } catch (FileNotFoundException e) { // TODO: add failed file info to status message? throw new AlfrescoRuntimeException("Failed to process ZIP file.", e); } catch (FileExistsException e) { // TODO: add failed file info to status message? throw new AlfrescoRuntimeException("Failed to process ZIP file.", e); } } } /** * Update Sandbox Transaction listener - invoked after bulk import */ private class UpdateSandboxTransactionListener extends TransactionListenerAdapter { private String virtUpdatePath; public UpdateSandboxTransactionListener(String virtUpdatePath) { this.virtUpdatePath = virtUpdatePath; } /** * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() */ @Override public void afterCommit() { // Reload virtualisation server as required if (this.virtUpdatePath != null) { WCMUtil.updateVServerWebapp(virtServerRegistry, this.virtUpdatePath, true); } } } }