/** * Copyright 2012 Manning Publications Co. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.manning.cmis.theblend.install; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.chemistry.opencmis.client.api.ObjectId; import org.apache.chemistry.opencmis.client.api.ObjectType; import org.apache.chemistry.opencmis.client.api.OperationContext; import org.apache.chemistry.opencmis.client.api.Session; import org.apache.chemistry.opencmis.client.runtime.OperationContextImpl; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.ContentStream; import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; import org.apache.chemistry.opencmis.commons.enums.Cardinality; import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; import org.apache.chemistry.opencmis.commons.enums.Updatability; import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; /** * Imports the content of a Zip file into the repository. */ public class CMISImporter { private static final OperationContext IMPORT_OPERATION_CONTEXT = new OperationContextImpl(); static { IMPORT_OPERATION_CONTEXT .setFilterString("cmis:objectId,cmis:objectTypeId,cmis:name"); IMPORT_OPERATION_CONTEXT.setIncludeAcls(false); IMPORT_OPERATION_CONTEXT.setIncludeAllowableActions(false); IMPORT_OPERATION_CONTEXT.setIncludePolicies(false); IMPORT_OPERATION_CONTEXT .setIncludeRelationships(IncludeRelationships.NONE); IMPORT_OPERATION_CONTEXT.setRenditionFilter(null); IMPORT_OPERATION_CONTEXT.setIncludePathSegments(true); IMPORT_OPERATION_CONTEXT.setOrderBy(null); IMPORT_OPERATION_CONTEXT.setCacheEnabled(true); } private Session session; private ZipFile zipFile; private String applicationRoot; /** * CMIS Importer * * @param session * Valid OpenCMIS session * @param zipFile * the Zip file * @param applicationRoot * the application root folder path */ public CMISImporter(Session session, ZipFile zipFile, String applicationRoot) { if (session == null) { throw new IllegalArgumentException("Session not set!"); } if (zipFile == null) { throw new IllegalArgumentException("ZipFile not set!"); } this.session = session; this.zipFile = zipFile; if (applicationRoot == null) { this.applicationRoot = ""; } else if (applicationRoot.endsWith("/")) { this.applicationRoot = applicationRoot.substring(0, applicationRoot.length() - 1); } else { this.applicationRoot = applicationRoot; } } /** * Runs the import. */ public void runImport(ImportProgress progress) { progress.startImport(); Set<String> processed = new HashSet<String>(); try { // create the application root createApplicationRoot(); // iterate over the Zip file @SuppressWarnings("unchecked") Enumeration<ZipEntry> entryEnumeration = (Enumeration<ZipEntry>) zipFile .entries(); while (entryEnumeration.hasMoreElements()) { ZipEntry entry = entryEnumeration.nextElement(); try { // don't try to re-process folders if (processed.contains(entry.getName())) { continue; } processed.add(entry.getName()); // extract path String[] path = entry.getName().split("/"); // ignore files starting with "." if (path[path.length - 1].startsWith(".")) { continue; } // ignore files ending with ".properties" if (path[path.length - 1].endsWith(".properties")) { continue; } progress.startFile(entry.getName()); // don't touch existing objects if (exists(path)) { progress.message("already existed"); progress.endFile(entry.getName()); continue; } // check the parent and create it, if necessary ObjectId parent = checkAndCreateParentFolder(path); // create object if (entry.isDirectory()) { createFolder(parent, path[path.length - 1]); progress.message("folder created"); } else { createDocument(parent, path[path.length - 1], entry); progress.message("document created"); } } catch (Exception e) { progress.message("Exception: " + e.toString()); } progress.endFile(entry.getName()); } } finally { try { zipFile.close(); } catch (Exception ie) { // ignore } progress.endImport(); } } /** * Creates the application root folder. */ private void createApplicationRoot() { String[] path = applicationRoot.split("/"); if (path.length < 2) { return; } ObjectId parent = session.getRootFolder(); StringBuilder sb = new StringBuilder(); for (int i = 1; i < path.length; i++) { sb.append('/'); sb.append(path[i]); try { parent = session.getObjectByPath(sb.toString(), IMPORT_OPERATION_CONTEXT); } catch (CmisObjectNotFoundException notFound) { Map<String, Object> properties = new HashMap<String, Object>(); properties.put(PropertyIds.NAME, path[i]); properties.put(PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_FOLDER.value()); parent = session.createFolder(properties, parent); } } } /** * Checks the parent folder of the object that should be created. If the * folder doesn't exits, it creates the folder. */ private ObjectId checkAndCreateParentFolder(String[] path) { // check for the application root folder if (path.length < 2) { return session.getObjectByPath((applicationRoot.length() == 0 ? "/" : applicationRoot), IMPORT_OPERATION_CONTEXT); } // prepare parent path String[] parentPath = new String[path.length - 1]; System.arraycopy(path, 0, parentPath, 0, parentPath.length); try { // if parent folder exits, return its object id return session.getObjectByPath(buildRepositoryPath(parentPath), IMPORT_OPERATION_CONTEXT); } catch (CmisObjectNotFoundException notFound) { // parent folder doesn't exit -> create it ObjectId parent = checkAndCreateParentFolder(parentPath); return createFolder(parent, parentPath[parentPath.length - 1]); } } /** * Creates a folder. */ private ObjectId createFolder(ObjectId parent, String name) { // set up properties Map<String, Object> properties = new HashMap<String, Object>(); properties.put(PropertyIds.NAME, name); properties.put(PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_FOLDER.value()); return session.createFolder(properties, parent); } /** * Creates a document. */ private ObjectId createDocument(ObjectId parent, String name, ZipEntry entry) throws Exception { // set up properties Map<String, Object> properties = new HashMap<String, Object>(); properties.put(PropertyIds.NAME, name); properties.put(PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value()); // copy stream to temp file File contentFile = File.createTempFile("theblend", "tmp"); OutputStream out = new BufferedOutputStream(new FileOutputStream( contentFile)); InputStream stream = zipFile.getInputStream(entry); try { byte[] buffer = new byte[64 * 1024]; int b; while ((b = stream.read(buffer)) > -1) { out.write(buffer, 0, b); } } catch (Exception e) { try { contentFile.delete(); } catch (Exception ie) { // ignore } throw e; } finally { try { out.close(); } catch (Exception ie) { // ignore } } TikaProperties tikaProperties = new TikaProperties(contentFile); properties.put(PropertyIds.OBJECT_TYPE_ID, tikaProperties .findDocumentType(session).getId()); tikaProperties.enrichProperties(session, properties); String mimetype = tikaProperties.getMIMEType(); addAdditionalProperties(entry, properties); // create document InputStream tmpStream = new BufferedInputStream(new FileInputStream( contentFile)); try { String filename = (String) properties.get(PropertyIds.NAME); long filesize = contentFile.length(); ContentStream contentStream = session.getObjectFactory() .createContentStream(filename, filesize, mimetype, tmpStream); return session.createDocument(properties, parent, contentStream, null); } catch (Exception e) { throw e; } finally { try { tmpStream.close(); } catch (Exception ie) { // ignore } try { contentFile.delete(); } catch (Exception ie) { // ignore } } } /** * Adds additional properties. */ protected void addAdditionalProperties(ZipEntry parentEntry, Map<String, Object> properties) { ZipEntry entry = zipFile .getEntry(parentEntry.getName() + ".properties"); if (entry == null) { return; } try { java.util.Properties props = new java.util.Properties(); props.load(zipFile.getInputStream(entry)); String typeId = props.getProperty(PropertyIds.OBJECT_TYPE_ID); if (typeId == null) { typeId = (String) properties.get(PropertyIds.OBJECT_TYPE_ID); } ObjectType typeDef = session.getTypeDefinition(typeId); SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd'T'hh:mm:ssZ"); for (Object key : props.keySet()) { String propId = key.toString(); PropertyDefinition<?> propDef = typeDef .getPropertyDefinitions().get(propId); if (propDef == null || propDef.getUpdatability() == Updatability.READONLY) { continue; } String[] values = props.getProperty(propId).split("\n"); switch (propDef.getPropertyType()) { case INTEGER: List<BigInteger> propValueInt = new ArrayList<BigInteger>(); for (String value : values) { propValueInt.add(new BigInteger(value)); } if (propDef.getCardinality() == Cardinality.SINGLE) { properties.put(propId, propValueInt.get(0)); } else { properties.put(propId, propValueInt); } break; case DECIMAL: List<BigDecimal> propValueDec = new ArrayList<BigDecimal>(); for (String value : values) { propValueDec.add(new BigDecimal(value)); } if (propDef.getCardinality() == Cardinality.SINGLE) { properties.put(propId, propValueDec.get(0)); } else { properties.put(propId, propValueDec); } break; case BOOLEAN: List<Boolean> propValueBool = new ArrayList<Boolean>(); for (String value : values) { propValueBool.add(Boolean.valueOf(value)); } if (propDef.getCardinality() == Cardinality.SINGLE) { properties.put(propId, propValueBool.get(0)); } else { properties.put(propId, propValueBool); } break; case DATETIME: List<GregorianCalendar> propValueDateTime = new ArrayList<GregorianCalendar>(); for (String value : values) { GregorianCalendar cal = new GregorianCalendar(); cal.setTime(sdf.parse(value)); propValueDateTime.add(cal); } if (propDef.getCardinality() == Cardinality.SINGLE) { properties.put(propId, propValueDateTime.get(0)); } else { properties.put(propId, propValueDateTime); } break; case STRING: if (propDef.getCardinality() == Cardinality.SINGLE) { properties.put(propId, values[0]); } else { properties.put(propId, Arrays.asList(values)); } break; case ID: if (propDef.getCardinality() == Cardinality.SINGLE) { properties.put(propId, values[0]); } else { properties.put(propId, Arrays.asList(values)); } break; case HTML: if (propDef.getCardinality() == Cardinality.SINGLE) { properties.put(propId, values[0]); } else { properties.put(propId, Arrays.asList(values)); } break; case URI: if (propDef.getCardinality() == Cardinality.SINGLE) { properties.put(propId, values[0]); } else { properties.put(propId, Arrays.asList(values)); } break; } } } catch (Exception e) { e.printStackTrace(); } } /** * Checks if the object exits. */ private boolean exists(String[] path) { try { session.getObjectByPath(buildRepositoryPath(path), IMPORT_OPERATION_CONTEXT); } catch (CmisObjectNotFoundException notFound) { return false; } return true; } /** * Return the repository path from the given path fragments. */ private String buildRepositoryPath(String[] path) { StringBuilder parentPath = new StringBuilder(applicationRoot); for (int i = 0; i < path.length; i++) { parentPath.append('/'); parentPath.append(path[i]); } return parentPath.toString(); } public static interface ImportProgress { void startImport(); void endImport(); void startFile(String name); void endFile(String name); void message(String msg); } }