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