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.logging.Log;
041import org.apache.commons.logging.LogFactory;
042
043import org.nuxeo.common.utils.FileUtils;
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:at@nuxeo.com">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        FileOutputStream outDocs = new FileOutputStream(temp);
223        try {
224            FileUtils.copy(zip, outDocs);
225        } finally {
226            outDocs.close();
227        }
228        zip.closeEntry();
229
230        InputStream tempIn = new FileInputStream(temp.getPath());
231        DocumentTranslationMap map = docsImporter.importDocs(tempIn);
232        tempIn.close();
233        temp.delete();
234
235        while ((zentry = zip.getNextEntry()) != null) {
236            String entryName = zentry.getName();
237            if (entryName.endsWith(".xml")) {
238                String ioAdapterName = entryName.substring(0, entryName.length() - 4);
239                IOResourceAdapter adapter = getAdapter(ioAdapterName);
240                if (adapter == null) {
241                    log.warn("Adapter " + ioAdapterName + " not available. Unable to import associated resources.");
242                    continue;
243                }
244                IOResources resources = adapter.loadResourcesFromXML(zip);
245                IOResources newResources = adapter.translateResources(repo, resources, map);
246                log.info("store resources with adapter " + ioAdapterName);
247                adapter.storeResources(newResources);
248            } else {
249                log.warn("skip entry: " + entryName);
250            }
251            try {
252                // we might have an undesired stream close in the client
253                zip.closeEntry();
254            } catch (IOException e) {
255                log.error("Please check code handling entry " + entryName, e);
256            }
257        }
258        zip.close();
259    }
260
261    @Override
262    public Collection<DocumentRef> copyDocumentsAndResources(String repo, Collection<DocumentRef> sources,
263            DocumentLocation targetLocation, Collection<String> ioAdapters) {
264        if (sources == null || sources.isEmpty()) {
265            return null;
266        }
267
268        String newRepo = targetLocation.getServerName();
269        if (!repo.equals(newRepo)) {
270            // TODO: maybe import and export (?), assume copy is recursive.
271            throw new NuxeoException("Cannot copy to different server");
272        }
273
274        List<DocumentRef> roots = new ArrayList<>();
275        try (CoreSession session = CoreInstance.openCoreSession(repo)) {
276            for (DocumentRef source : sources) {
277                DocumentTranslationMap map = new DocumentTranslationMapImpl(repo, repo);
278                DocumentModel sourceDoc = session.getDocument(source);
279                DocumentModel destDoc = session.copy(source, targetLocation.getDocRef(), null);
280                roots.add(destDoc.getRef());
281                // iterate on each tree to build translation map
282                DocumentTreeIterator sourceIt = new DocumentTreeIterator(session, sourceDoc);
283                DocumentTreeIterator destIt = new DocumentTreeIterator(session, destDoc);
284                while (sourceIt.hasNext()) {
285                    DocumentModel sourceItem = sourceIt.next();
286                    DocumentRef sourceRef = sourceItem.getRef();
287                    if (!destIt.hasNext()) {
288                        map.put(sourceRef, null);
289                    } else {
290                        DocumentModel destItem = destIt.next();
291                        DocumentRef destRef = destItem.getRef();
292                        map.put(sourceRef, destRef);
293                    }
294                }
295                Collection<DocumentRef> allSources = map.getDocRefMap().keySet();
296                if (ioAdapters != null && !ioAdapters.isEmpty()) {
297                    for (String adapterName : ioAdapters) {
298                        IOResourceAdapter adapter = getAdapter(adapterName);
299                        if (adapter == null) {
300                            log.warn("Adapter " + adapterName + " not found");
301                            continue;
302                        }
303                        IOResources resources = adapter.extractResources(repo, allSources);
304                        IOResources newResources = adapter.translateResources(repo, resources, map);
305                        adapter.storeResources(newResources);
306                    }
307                }
308                session.save();
309            }
310        }
311        return roots;
312    }
313
314    private static DocumentWriter createDocWriter(String docWriterFactoryName, Map<String, Object> factoryParams) {
315        // create a custom writer using factory instance
316        Object factoryObj;
317        try {
318            Class<?> clazz = Class.forName(docWriterFactoryName);
319            factoryObj = clazz.newInstance();
320        } catch (ReflectiveOperationException e) {
321            throw new NuxeoException("cannot instantiate factory " + docWriterFactoryName, e);
322        }
323
324        DocumentWriter customDocWriter;
325        if (factoryObj instanceof DocumentWriterFactory) {
326            customDocWriter = ((DocumentWriterFactory) factoryObj).createDocWriter(factoryParams);
327        } else {
328            throw new NuxeoException("bad class type: " + factoryObj);
329        }
330
331        if (customDocWriter == null) {
332            throw new NuxeoException("null DocumentWriter created by " + docWriterFactoryName);
333        }
334
335        return customDocWriter;
336    }
337
338    private static DocumentReader createDocReader(String docReaderFactoryName, Map<String, Object> factoryParams) {
339        // create a custom reader using factory instance
340        Object factoryObj;
341        try {
342            Class<?> clazz = Class.forName(docReaderFactoryName);
343            factoryObj = clazz.newInstance();
344        } catch (ReflectiveOperationException e) {
345            throw new NuxeoException("cannot instantiate factory " + docReaderFactoryName, e);
346        }
347
348        DocumentReader customDocReader;
349        if (factoryObj instanceof DocumentReaderFactory) {
350            customDocReader = ((DocumentReaderFactory) factoryObj).createDocReader(factoryParams);
351        } else {
352            throw new NuxeoException("bad class type: " + factoryObj);
353        }
354
355        if (customDocReader == null) {
356            throw new NuxeoException("null DocumentReader created by " + docReaderFactoryName);
357        }
358
359        return customDocReader;
360    }
361
362    @Override
363    public void importFromStream(InputStream in, DocumentLocation targetLocation, String docReaderFactoryClassName,
364            Map<String, Object> rFactoryParams, String docWriterFactoryClassName, Map<String, Object> wFactoryParams) {
365        DocumentWriter customDocWriter = createDocWriter(docWriterFactoryClassName, wFactoryParams);
366        DocumentReader customDocReader = null;
367
368        try {
369            if (rFactoryParams == null) {
370                rFactoryParams = new HashMap<>();
371            }
372            rFactoryParams.put("source_stream", in);
373            customDocReader = createDocReader(docReaderFactoryClassName, rFactoryParams);
374
375            IODocumentManager docManager = new IODocumentManagerImpl();
376            DocumentTranslationMap map = docManager.importDocuments(customDocReader, customDocWriter);
377        } finally {
378            if (customDocReader != null) {
379                customDocReader.close();
380            }
381            customDocWriter.close();
382
383            if (in != null) {
384                try {
385                    in.close();
386                } catch (IOException e) {
387                    log.error(e);
388                }
389            }
390        }
391
392    }
393
394}