From a55a18670556a0bf78f6d40fb8b504a7e58d2129 Mon Sep 17 00:00:00 2001 From: Kevin Roast Date: Wed, 23 May 2007 12:25:04 +0000 Subject: [PATCH] The Import dialog now allows ZIP file structure import into the repo from the web-client by any user with Write access to a folder - this is nice quick way for any user to import bulk data without using an FTP client or CIFS folder access. Import action moved to top-level in the main browse screen now it is useful on a day-to-day basis. The 'import' repository action (ImporterActionExecuter) extended to support import of any zip flavour file. More zip flavour filetypes added to mimetype map. Improved debugging output added to BasePathResultsMap for xpaths executed in templates. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5760 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/action-services-context.xml | 62 ++--- config/alfresco/mimetype/mimetype-map.xml | 5 +- .../executer/ImporterActionExecuter.java | 237 +++++++++++++++++- .../repo/template/BasePathResultsMap.java | 13 +- 4 files changed, 283 insertions(+), 34 deletions(-) diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index 349ff953ab..6a55930f53 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -230,15 +230,15 @@ - - - - - - - - - + + + + + + + + + @@ -300,9 +300,9 @@ ${mail.header} - - ${mail.from.default} - + + ${mail.from.default} + @@ -344,6 +344,12 @@ + + + + + + @@ -482,22 +488,22 @@ false - - - - - - - - - - - - - - false - - + + + + + + + + + + + + + + false + + diff --git a/config/alfresco/mimetype/mimetype-map.xml b/config/alfresco/mimetype/mimetype-map.xml index 8b25d1972e..9e4926bac2 100644 --- a/config/alfresco/mimetype/mimetype-map.xml +++ b/config/alfresco/mimetype/mimetype-map.xml @@ -292,7 +292,10 @@ z - zip + zip + war + jar + ear dwg diff --git a/source/java/org/alfresco/repo/action/executer/ImporterActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ImporterActionExecuter.java index cdcb3445b3..ac3032f8e9 100644 --- a/source/java/org/alfresco/repo/action/executer/ImporterActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ImporterActionExecuter.java @@ -24,9 +24,24 @@ */ package org.alfresco.repo.action.executer; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Enumeration; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.zip.ZipException; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ApplicationModel; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.repo.content.MimetypeMap; @@ -34,13 +49,21 @@ import org.alfresco.repo.importer.ACPImportPackageHandler; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.view.ImporterService; import org.alfresco.service.cmr.view.Location; +import org.alfresco.service.namespace.QName; import org.alfresco.util.TempFileProvider; +import org.apache.tools.zip.ZipEntry; +import org.apache.tools.zip.ZipFile; /** * Importer action executor @@ -53,8 +76,10 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase public static final String PARAM_ENCODING = "encoding"; public static final String PARAM_DESTINATION_FOLDER = "destination"; + private static final int BUFFER_SIZE = 16384; private static final String TEMP_FILE_PREFIX = "alf"; - private static final String TEMP_FILE_SUFFIX = ".acp"; + private static final String TEMP_FILE_SUFFIX_ACP = ".acp"; + private static final String TEMP_FILE_SUFFIX_ZIP = ".zip"; /** * The importer service @@ -71,6 +96,16 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase */ private ContentService contentService; + /** + * The mimetype service + */ + private MimetypeService mimetypeService; + + /** + * The file folder service + */ + private FileFolderService fileFolderService; + /** * Sets the ImporterService to use * @@ -100,6 +135,26 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase { this.contentService = contentService; } + + /** + * Sets the MimetypeService to use + * + * @param mimetypeService The MimetypeService + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * Sets the FileFolderService to use + * + * @param fileFolderService The FileFolderService + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } /** * @see org.alfresco.repo.action.executer.ActionExecuter#execute(org.alfresco.repo.ref.NodeRef, org.alfresco.repo.ref.NodeRef) @@ -112,19 +167,20 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase ContentReader reader = this.contentService.getReader(actionedUponNodeRef, ContentModel.PROP_CONTENT); if (reader != null) { + NodeRef importDest = (NodeRef)ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER); if (MimetypeMap.MIMETYPE_ACP.equals(reader.getMimetype())) { + // perform an import of an Alfresco ACP file (special format ZIP structure) File zipFile = null; try { // unfortunately a ZIP file can not be read directly from an input stream so we have to create // a temporary file first - zipFile = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX); + zipFile = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX_ACP); reader.getContent(zipFile); - + ACPImportPackageHandler importHandler = new ACPImportPackageHandler(zipFile, (String)ruleAction.getParameterValue(PARAM_ENCODING)); - NodeRef importDest = (NodeRef)ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER); this.importerService.importView(importHandler, new Location(importDest), null, null); } @@ -137,10 +193,108 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase } } } + else if (MimetypeMap.MIMETYPE_ZIP.equals(reader.getMimetype())) + { + // perform an import of a standard ZIP file + ZipFile zipFile = null; + File tempFile = null; + try + { + tempFile = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX_ACP); + reader.getContent(tempFile); + // NOTE: This encoding allows us to workaround bug: + // http://bugs.sun.com/bugdatabase/view_bug.do;:WuuT?bug_id=4820807 + zipFile = new ZipFile(tempFile, "Cp437"); + + // build a temp dir name based on the ID of the noderef we are importing + File alfTempDir = TempFileProvider.getTempDir(); + File tempDir = new File(alfTempDir.getPath() + File.separatorChar + actionedUponNodeRef.getId()); + try + { + // TODO: improve this code to directly pipe the zip stream output into the repo objects - + // to remove the need to expand to the filesystem first? + extractFile(zipFile, tempDir.getPath()); + importDirectory(tempDir.getPath(), importDest); + } + finally + { + deleteDir(tempDir); + } + } + catch (IOException ioErr) + { + throw new AlfrescoRuntimeException("Failed to import ZIP file.", ioErr); + } + finally + { + // now the import is done, delete the temporary file + if (tempFile != null) + { + tempFile.delete(); + } + } + } } } } + /** + * 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); + for (File file : topdir.listFiles()) + { + try + { + if (file.isFile()) + { + String fileName = file.getName(); + + // create content node based on the file name + FileInfo fileInfo = this.fileFolderService.create(root, fileName, ContentModel.TYPE_CONTENT); + NodeRef fileRef = fileInfo.getNodeRef(); + + // add titled aspect for the read/edit properties screens + Map titledProps = new HashMap(1, 1.0f); + titledProps.put(ContentModel.PROP_TITLE, fileName); + this.nodeService.addAspect(fileRef, ContentModel.ASPECT_TITLED, titledProps); + + // push the content of the file into the node + InputStream contentStream = new BufferedInputStream(new FileInputStream(file), BUFFER_SIZE); + ContentWriter writer = this.contentService.getWriter(fileRef, ContentModel.PROP_CONTENT, true); + writer.setMimetype(this.mimetypeService.guessMimetype(fileName)); + writer.putContent(contentStream); + } + else + { + // create a folder based on the folder name + FileInfo folderInfo = this.fileFolderService.create(root, file.getName(), ContentModel.TYPE_FOLDER); + NodeRef folderRef = folderInfo.getNodeRef(); + + // add the uifacets aspect for the read/edit properties screens + this.nodeService.addAspect(folderRef, ApplicationModel.ASPECT_UIFACETS, null); + + 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); + } + } + } + /** * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) */ @@ -151,5 +305,80 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase paramList.add(new ParameterDefinitionImpl(PARAM_ENCODING, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_ENCODING))); } + + /** + * Extract the file and folder structure of a ZIP file into the specified directory + * + * @param archive The ZIP archive to extract + * @param extractDir The directory to extract into + */ + public static void extractFile(ZipFile archive, String extractDir) + { + String fileName; + String destFileName; + byte[] buffer = new byte[BUFFER_SIZE]; + extractDir = extractDir + File.separator; + try + { + for (Enumeration e = archive.getEntries(); e.hasMoreElements();) + { + ZipEntry entry = (ZipEntry) e.nextElement(); + if (!entry.isDirectory()) + { + fileName = entry.getName(); + fileName = fileName.replace('/', File.separatorChar); + destFileName = extractDir + fileName; + File destFile = new File(destFileName); + String parent = destFile.getParent(); + if (parent != null) + { + File parentFile = new File(parent); + if (!parentFile.exists()) parentFile.mkdirs(); + } + InputStream in = new BufferedInputStream(archive.getInputStream(entry), BUFFER_SIZE); + OutputStream out = new BufferedOutputStream(new FileOutputStream(destFileName), BUFFER_SIZE); + int count; + while ((count = in.read(buffer)) != -1) + { + out.write(buffer, 0, count); + } + in.close(); + out.close(); + } + else + { + File newdir = new File(extractDir + entry.getName()); + newdir.mkdir(); + } + } + } + catch (ZipException e) + { + throw new AlfrescoRuntimeException("Failed to process ZIP file.", e); + } + catch (FileNotFoundException e) + { + throw new AlfrescoRuntimeException("Failed to process ZIP file.", e); + } + catch (IOException e) + { + throw new AlfrescoRuntimeException("Failed to process ZIP file.", e); + } + } + /** + * Recursively delete a dir of files and directories + * + * @param dir directory to delete + */ + public static void deleteDir(File dir) + { + File elenco = new File(dir.getPath()); + for (File file : elenco.listFiles()) + { + if (file.isFile()) file.delete(); + else deleteDir(file); + } + dir.delete(); + } } diff --git a/source/java/org/alfresco/repo/template/BasePathResultsMap.java b/source/java/org/alfresco/repo/template/BasePathResultsMap.java index ada739ce32..2ec47f1f8c 100644 --- a/source/java/org/alfresco/repo/template/BasePathResultsMap.java +++ b/source/java/org/alfresco/repo/template/BasePathResultsMap.java @@ -70,7 +70,18 @@ public abstract class BasePathResultsMap extends BaseTemplateMap if (xpath.length() != 0) { if (logger.isDebugEnabled()) - logger.debug("Executing xpath: " + xpath); + { + String out = "Executing xpath: " + xpath; + if (params != null) + { + out += " with params:"; + for (QueryParameterDefinition p : params) + { + out += " " + p.getDefault(); + } + } + logger.debug(out); + } List nodes = this.services.getSearchService().selectNodes( this.parent.getNodeRef(),