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