001/* 002 * (C) Copyright 2006-2016 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 * <a href="mailto:[email protected]">Anahide Tchertchian</a> 018 * 019 */ 020 021package org.nuxeo.ecm.platform.io.impl; 022 023import java.io.ByteArrayOutputStream; 024import java.io.File; 025import java.io.FileInputStream; 026import java.io.FileOutputStream; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.OutputStream; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.zip.ZipEntry; 036import java.util.zip.ZipException; 037import java.util.zip.ZipInputStream; 038import java.util.zip.ZipOutputStream; 039 040import org.apache.commons.io.IOUtils; 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.ecm.core.api.CloseableCoreSession; 044import org.nuxeo.ecm.core.api.CoreInstance; 045import org.nuxeo.ecm.core.api.CoreSession; 046import org.nuxeo.ecm.core.api.DocumentLocation; 047import org.nuxeo.ecm.core.api.DocumentModel; 048import org.nuxeo.ecm.core.api.DocumentRef; 049import org.nuxeo.ecm.core.api.DocumentTreeIterator; 050import org.nuxeo.ecm.core.api.NuxeoException; 051import org.nuxeo.ecm.core.io.DocumentReader; 052import org.nuxeo.ecm.core.io.DocumentReaderFactory; 053import org.nuxeo.ecm.core.io.DocumentTranslationMap; 054import org.nuxeo.ecm.core.io.DocumentWriter; 055import org.nuxeo.ecm.core.io.DocumentWriterFactory; 056import org.nuxeo.ecm.core.io.DocumentsExporter; 057import org.nuxeo.ecm.core.io.DocumentsImporter; 058import org.nuxeo.ecm.core.io.IODocumentManager; 059import org.nuxeo.ecm.core.io.impl.DocumentTranslationMapImpl; 060import org.nuxeo.ecm.core.io.impl.IODocumentManagerImpl; 061import org.nuxeo.ecm.platform.io.api.IOManager; 062import org.nuxeo.ecm.platform.io.api.IOResourceAdapter; 063import org.nuxeo.ecm.platform.io.api.IOResources; 064import org.nuxeo.runtime.api.Framework; 065 066/** 067 * IOManager implementation 068 * 069 * @author <a href="mailto:[email protected]">Anahide Tchertchian</a> 070 */ 071public class IOManagerImpl implements IOManager { 072 073 private static final long serialVersionUID = 5789086884484295921L; 074 075 private static final Log log = LogFactory.getLog(IOManagerImpl.class); 076 077 protected final Map<String, IOResourceAdapter> adaptersRegistry; 078 079 public IOManagerImpl() { 080 adaptersRegistry = new HashMap<>(); 081 } 082 083 @Override 084 public IOResourceAdapter getAdapter(String name) { 085 return adaptersRegistry.get(name); 086 } 087 088 @Override 089 public void addAdapter(String name, IOResourceAdapter adapter) { 090 if (DOCUMENTS_ADAPTER_NAME.equals(name)) { 091 log.error("Cannot register adapter with name " + DOCUMENTS_ADAPTER_NAME); 092 return; 093 } 094 adaptersRegistry.put(name, adapter); 095 } 096 097 @Override 098 public void removeAdapter(String name) { 099 adaptersRegistry.remove(name); 100 } 101 102 public void exportDocumentsAndResources(OutputStream out, String repo, final String format, 103 Collection<String> ioAdapters, final DocumentReader customDocReader) throws IOException { 104 DocumentsExporter docsExporter = new DocumentsExporter() { 105 @Override 106 public DocumentTranslationMap exportDocs(OutputStream out) throws IOException { 107 IODocumentManager docManager = new IODocumentManagerImpl(); 108 DocumentTranslationMap map = docManager.exportDocuments(out, customDocReader, format); 109 return map; 110 } 111 }; 112 exportDocumentsAndResources(out, repo, docsExporter, ioAdapters); 113 } 114 115 @Override 116 public void exportDocumentsAndResources(OutputStream out, final String repo, final Collection<DocumentRef> sources, 117 final boolean recurse, final String format, final Collection<String> ioAdapters) throws IOException { 118 DocumentsExporter docsExporter = new DocumentsExporter() { 119 @Override 120 public DocumentTranslationMap exportDocs(OutputStream out) throws IOException { 121 IODocumentManager docManager = new IODocumentManagerImpl(); 122 DocumentTranslationMap map = docManager.exportDocuments(out, repo, sources, recurse, format); 123 return map; 124 } 125 }; 126 exportDocumentsAndResources(out, repo, docsExporter, ioAdapters); 127 } 128 129 void exportDocumentsAndResources(OutputStream out, String repo, DocumentsExporter docsExporter, 130 Collection<String> ioAdapters) throws IOException { 131 List<String> doneAdapters = new ArrayList<>(); 132 133 ZipOutputStream zip = new ZipOutputStream(out); 134 zip.setMethod(ZipOutputStream.DEFLATED); 135 zip.setLevel(9); 136 137 ByteArrayOutputStream docsZip = new ByteArrayOutputStream(); 138 DocumentTranslationMap map = docsExporter.exportDocs(docsZip); 139 140 ZipEntry docsEntry = new ZipEntry(DOCUMENTS_ADAPTER_NAME + ".zip"); 141 zip.putNextEntry(docsEntry); 142 zip.write(docsZip.toByteArray()); 143 zip.closeEntry(); 144 docsZip.close(); 145 doneAdapters.add(DOCUMENTS_ADAPTER_NAME); 146 147 Collection<DocumentRef> allSources = map.getDocRefMap().keySet(); 148 149 if (ioAdapters != null && !ioAdapters.isEmpty()) { 150 for (String adapterName : ioAdapters) { 151 String filename = adapterName + ".xml"; 152 IOResourceAdapter adapter = getAdapter(adapterName); 153 if (adapter == null) { 154 log.warn("Adapter " + adapterName + " not found"); 155 continue; 156 } 157 if (doneAdapters.contains(adapterName)) { 158 log.warn("Export for adapter " + adapterName + " already done"); 159 continue; 160 } 161 IOResources resources = adapter.extractResources(repo, allSources); 162 resources = adapter.translateResources(repo, resources, map); 163 ByteArrayOutputStream adapterOut = new ByteArrayOutputStream(); 164 adapter.getResourcesAsXML(adapterOut, resources); 165 ZipEntry adapterEntry = new ZipEntry(filename); 166 zip.putNextEntry(adapterEntry); 167 zip.write(adapterOut.toByteArray()); 168 zip.closeEntry(); 169 doneAdapters.add(adapterName); 170 adapterOut.close(); 171 } 172 } 173 try { 174 zip.close(); 175 } catch (ZipException e) { 176 // empty zip file, do nothing 177 } 178 } 179 180 @Override 181 public void importDocumentsAndResources(InputStream in, final String repo, final DocumentRef root) 182 throws IOException { 183 DocumentsImporter docsImporter = new DocumentsImporter() { 184 185 @Override 186 public DocumentTranslationMap importDocs(InputStream sourceStream) throws IOException { 187 IODocumentManager docManager = new IODocumentManagerImpl(); 188 return docManager.importDocuments(sourceStream, repo, root); 189 } 190 191 }; 192 importDocumentsAndResources(docsImporter, in, repo); 193 } 194 195 public void importDocumentsAndResources(InputStream in, final String repo, final DocumentRef root, 196 final DocumentWriter customDocWriter) throws IOException { 197 DocumentsImporter docsImporter = new DocumentsImporter() { 198 199 @Override 200 public DocumentTranslationMap importDocs(InputStream sourceStream) throws IOException { 201 IODocumentManager docManager = new IODocumentManagerImpl(); 202 return docManager.importDocuments(sourceStream, customDocWriter); 203 } 204 205 }; 206 importDocumentsAndResources(docsImporter, in, repo); 207 } 208 209 void importDocumentsAndResources(DocumentsImporter docsImporter, InputStream in, String repo) throws IOException { 210 ZipInputStream zip = new ZipInputStream(in); 211 212 // first entry will be documents 213 ZipEntry zentry = zip.getNextEntry(); 214 String docZipFilename = DOCUMENTS_ADAPTER_NAME + ".zip"; 215 if (zentry == null || !docZipFilename.equals(zentry.getName())) { 216 zip.close(); 217 throw new NuxeoException("Invalid archive"); 218 } 219 220 // fill in a new stream 221 File temp = Framework.createTempFile("nuxeo-import-adapters-", ".zip"); 222 try (FileOutputStream outDocs = new FileOutputStream(temp)) { 223 IOUtils.copy(zip, outDocs); 224 } 225 zip.closeEntry(); 226 227 InputStream tempIn = new FileInputStream(temp.getPath()); 228 DocumentTranslationMap map = docsImporter.importDocs(tempIn); 229 tempIn.close(); 230 temp.delete(); 231 232 while ((zentry = zip.getNextEntry()) != null) { 233 String entryName = zentry.getName(); 234 if (entryName.endsWith(".xml")) { 235 String ioAdapterName = entryName.substring(0, entryName.length() - 4); 236 IOResourceAdapter adapter = getAdapter(ioAdapterName); 237 if (adapter == null) { 238 log.warn("Adapter " + ioAdapterName + " not available. Unable to import associated resources."); 239 continue; 240 } 241 IOResources resources = adapter.loadResourcesFromXML(zip); 242 IOResources newResources = adapter.translateResources(repo, resources, map); 243 log.info("store resources with adapter " + ioAdapterName); 244 adapter.storeResources(newResources); 245 } else { 246 log.warn("skip entry: " + entryName); 247 } 248 try { 249 // we might have an undesired stream close in the client 250 zip.closeEntry(); 251 } catch (IOException e) { 252 log.error("Please check code handling entry " + entryName, e); 253 } 254 } 255 zip.close(); 256 } 257 258 @Override 259 public Collection<DocumentRef> copyDocumentsAndResources(String repo, Collection<DocumentRef> sources, 260 DocumentLocation targetLocation, Collection<String> ioAdapters) { 261 if (sources == null || sources.isEmpty()) { 262 return null; 263 } 264 265 String newRepo = targetLocation.getServerName(); 266 if (!repo.equals(newRepo)) { 267 // TODO: maybe import and export (?), assume copy is recursive. 268 throw new NuxeoException("Cannot copy to different server"); 269 } 270 271 List<DocumentRef> roots = new ArrayList<>(); 272 try (CloseableCoreSession session = CoreInstance.openCoreSession(repo)) { 273 for (DocumentRef source : sources) { 274 DocumentTranslationMap map = new DocumentTranslationMapImpl(repo, repo); 275 DocumentModel sourceDoc = session.getDocument(source); 276 DocumentModel destDoc = session.copy(source, targetLocation.getDocRef(), null); 277 roots.add(destDoc.getRef()); 278 // iterate on each tree to build translation map 279 DocumentTreeIterator sourceIt = new DocumentTreeIterator(session, sourceDoc); 280 DocumentTreeIterator destIt = new DocumentTreeIterator(session, destDoc); 281 while (sourceIt.hasNext()) { 282 DocumentModel sourceItem = sourceIt.next(); 283 DocumentRef sourceRef = sourceItem.getRef(); 284 if (!destIt.hasNext()) { 285 map.put(sourceRef, null); 286 } else { 287 DocumentModel destItem = destIt.next(); 288 DocumentRef destRef = destItem.getRef(); 289 map.put(sourceRef, destRef); 290 } 291 } 292 Collection<DocumentRef> allSources = map.getDocRefMap().keySet(); 293 if (ioAdapters != null && !ioAdapters.isEmpty()) { 294 for (String adapterName : ioAdapters) { 295 IOResourceAdapter adapter = getAdapter(adapterName); 296 if (adapter == null) { 297 log.warn("Adapter " + adapterName + " not found"); 298 continue; 299 } 300 IOResources resources = adapter.extractResources(repo, allSources); 301 IOResources newResources = adapter.translateResources(repo, resources, map); 302 adapter.storeResources(newResources); 303 } 304 } 305 session.save(); 306 } 307 } 308 return roots; 309 } 310 311 private static DocumentWriter createDocWriter(String docWriterFactoryName, Map<String, Object> factoryParams) { 312 // create a custom writer using factory instance 313 Object factoryObj; 314 try { 315 Class<?> clazz = Class.forName(docWriterFactoryName); 316 factoryObj = clazz.newInstance(); 317 } catch (ReflectiveOperationException e) { 318 throw new NuxeoException("cannot instantiate factory " + docWriterFactoryName, e); 319 } 320 321 DocumentWriter customDocWriter; 322 if (factoryObj instanceof DocumentWriterFactory) { 323 customDocWriter = ((DocumentWriterFactory) factoryObj).createDocWriter(factoryParams); 324 } else { 325 throw new NuxeoException("bad class type: " + factoryObj); 326 } 327 328 if (customDocWriter == null) { 329 throw new NuxeoException("null DocumentWriter created by " + docWriterFactoryName); 330 } 331 332 return customDocWriter; 333 } 334 335 private static DocumentReader createDocReader(String docReaderFactoryName, Map<String, Object> factoryParams) { 336 // create a custom reader using factory instance 337 Object factoryObj; 338 try { 339 Class<?> clazz = Class.forName(docReaderFactoryName); 340 factoryObj = clazz.newInstance(); 341 } catch (ReflectiveOperationException e) { 342 throw new NuxeoException("cannot instantiate factory " + docReaderFactoryName, e); 343 } 344 345 DocumentReader customDocReader; 346 if (factoryObj instanceof DocumentReaderFactory) { 347 customDocReader = ((DocumentReaderFactory) factoryObj).createDocReader(factoryParams); 348 } else { 349 throw new NuxeoException("bad class type: " + factoryObj); 350 } 351 352 if (customDocReader == null) { 353 throw new NuxeoException("null DocumentReader created by " + docReaderFactoryName); 354 } 355 356 return customDocReader; 357 } 358 359 @Override 360 public void importFromStream(InputStream in, DocumentLocation targetLocation, String docReaderFactoryClassName, 361 Map<String, Object> rFactoryParams, String docWriterFactoryClassName, Map<String, Object> wFactoryParams) { 362 DocumentWriter customDocWriter = createDocWriter(docWriterFactoryClassName, wFactoryParams); 363 DocumentReader customDocReader = null; 364 365 try { 366 if (rFactoryParams == null) { 367 rFactoryParams = new HashMap<>(); 368 } 369 rFactoryParams.put("source_stream", in); 370 customDocReader = createDocReader(docReaderFactoryClassName, rFactoryParams); 371 372 IODocumentManager docManager = new IODocumentManagerImpl(); 373 DocumentTranslationMap map = docManager.importDocuments(customDocReader, customDocWriter); 374 } finally { 375 if (customDocReader != null) { 376 customDocReader.close(); 377 } 378 customDocWriter.close(); 379 380 if (in != null) { 381 try { 382 in.close(); 383 } catch (IOException e) { 384 log.error(e); 385 } 386 } 387 } 388 389 } 390 391}