001/*
002 * (C) Copyright 2006-20012 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 *     Nuxeo - initial API and implementation
016 *
017 */
018
019package org.nuxeo.template.samples.importer;
020
021import java.io.File;
022import java.io.FileFilter;
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.Calendar;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.dom4j.Attribute;
033import org.dom4j.CDATA;
034import org.dom4j.Comment;
035import org.dom4j.Document;
036import org.dom4j.DocumentType;
037import org.dom4j.Element;
038import org.dom4j.Entity;
039import org.dom4j.Namespace;
040import org.dom4j.ProcessingInstruction;
041import org.dom4j.Text;
042import org.dom4j.Visitor;
043import org.nuxeo.common.utils.Path;
044import org.nuxeo.ecm.core.api.CoreSession;
045import org.nuxeo.ecm.core.api.DocumentModel;
046import org.nuxeo.ecm.core.api.DocumentModelList;
047import org.nuxeo.ecm.core.api.DocumentRef;
048import org.nuxeo.ecm.core.api.NuxeoException;
049import org.nuxeo.ecm.core.api.PathRef;
050import org.nuxeo.ecm.core.io.DocumentPipe;
051import org.nuxeo.ecm.core.io.DocumentReader;
052import org.nuxeo.ecm.core.io.DocumentTransformer;
053import org.nuxeo.ecm.core.io.DocumentTranslationMap;
054import org.nuxeo.ecm.core.io.DocumentWriter;
055import org.nuxeo.ecm.core.io.ExportedDocument;
056import org.nuxeo.ecm.core.io.impl.DocumentPipeImpl;
057import org.nuxeo.ecm.core.io.impl.plugins.DocumentModelWriter;
058import org.nuxeo.ecm.platform.audit.api.AuditLogger;
059import org.nuxeo.ecm.platform.audit.api.AuditReader;
060import org.nuxeo.ecm.platform.audit.api.LogEntry;
061import org.nuxeo.runtime.api.Framework;
062
063/**
064 * Imports models and samples from resources or filesystem via CoreIO.
065 * <p>
066 * The association between template and document is translated during the IO import (because UUIDs change).
067 *
068 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a>
069 */
070public class ModelImporter {
071
072    protected final static Log log = LogFactory.getLog(ModelImporter.class);
073
074    private static final String TEMPLATE_SAMPLE_INIT_EVENT = "TemplateSampleInit";
075
076    private static final String[] IMPORT_ALREADY_DONE_EVENTS = { TEMPLATE_SAMPLE_INIT_EVENT };
077
078    public static final String EXAMPLES_ROOT = "examples";
079
080    public static final String RAW_EXAMPLES_ROOT = "rawexamples";
081
082    public static final String TEMPLATE_ROOT = "template";
083
084    protected static final String RESOURCES_ROOT = "templatesamples";
085
086    protected static final String RAW_RESOURCES_ROOT = "rawsamples";
087
088    protected static final String DOMAIN_QUERY = "select * from Domain where ecm:isCheckedInVersion=0  AND  ecm:currentLifeCycleState != 'deleted' order by dc:created ASC";
089
090    protected final CoreSession session;
091
092    public ModelImporter(CoreSession session) {
093        this.session = session;
094    }
095
096    protected String getTemplateResourcesRootPath() {
097        return RESOURCES_ROOT;
098    }
099
100    protected String getRawTemplateResourcesRootPath() {
101        return RAW_RESOURCES_ROOT;
102    }
103
104    protected DocumentModel getTargetDomain() {
105        return getTargetDomain(true);
106    }
107
108    protected DocumentModel getTargetDomain(boolean canRetry) {
109        DocumentModelList domains = session.query(DOMAIN_QUERY);
110        if (domains.size() > 0) {
111            return domains.get(0);
112        }
113        // no domain, that's strange
114        // may be a session flush issue
115        if (canRetry) {
116            session.save();
117            return getTargetDomain(false);
118        }
119        return null;
120    }
121
122    protected DocumentModel getOrCreateTemplateContainer() {
123        DocumentModel rootDomain = getTargetDomain();
124
125        if (rootDomain != null) {
126            DocumentModelList roots = session.getChildren(rootDomain.getRef(), "TemplateRoot");
127            if (roots.size() > 0) {
128                return roots.get(0);
129            }
130        }
131        return null;
132    }
133
134    protected DocumentModel getWSRoot(DocumentModel rootDomain) {
135        if (rootDomain != null) {
136            DocumentModelList roots = session.getChildren(rootDomain.getRef(), "WorkspaceRoot");
137            if (roots.size() > 0) {
138                DocumentModel WSRoot = roots.get(0);
139                return WSRoot;
140            }
141        }
142        return null;
143    }
144
145    protected DocumentModel getOrCreateSampleContainer() {
146        DocumentModel rootDomain = getTargetDomain();
147        DocumentModel container = null;
148
149        DocumentModel WSRoot = getWSRoot(rootDomain);
150        if (WSRoot != null) {
151            PathRef targetPath = new PathRef(WSRoot.getPathAsString() + "/" + getTemplateResourcesRootPath());
152            if (!session.exists(targetPath)) {
153                container = session.createDocumentModel(WSRoot.getPathAsString(), getTemplateResourcesRootPath(),
154                        "nxtrSamplesContainer");
155                container.setPropertyValue("dc:title", "Discover Customization Examples");
156                container.setPropertyValue("nxtplsamplescontainer:instructions",
157                        "<span class=\"nxtrExplanations\">The BigCorp company uses Nuxeo Studio and template rendering to generate custom project portfolios that showcase relevant expertise to potential new clients.<br /><br /><strong>It's your turn now! Open the \"BigCorp Transforms GreatBank Customer Service\" project</strong> and follow the instructions.</span>");
158                container = session.createDocument(container);
159            } else {
160                container = session.getDocument(targetPath);
161            }
162        }
163        return container;
164    }
165
166    private DocumentModel getOrCreateRawSampleContainer() {
167        DocumentModel container = null;
168        DocumentModel parentContainer = getOrCreateSampleContainer();
169
170        PathRef targetPath = new PathRef(getOrCreateSampleContainer().getPathAsString() + "/" + getRawTemplateResourcesRootPath());
171        if (!session.exists(targetPath)) {
172            container = session.createDocumentModel(parentContainer.getPathAsString(), "rawsamples",
173                    "Workspace");
174            container.setPropertyValue("dc:title", "More (Raw) Examples");
175            container.setPropertyValue("dc:description",
176                    "This space contains raw examples to demonstrate the Nuxeo template rendering add-on's advanced possibilities. Go to the \"Discover Customization Samples\" folder first if you did not follow its instructions yet.");
177            container = session.createDocument(container);
178        } else {
179            container = session.getDocument(targetPath);
180        }
181        return container;
182    }
183
184    protected boolean isImportAlreadyDone() {
185        if (Framework.isTestModeSet()) {
186            return false;
187        }
188
189        AuditReader reader = Framework.getService(AuditReader.class);
190        List<LogEntry> entries = reader.queryLogs(IMPORT_ALREADY_DONE_EVENTS, null);
191        return !entries.isEmpty();
192    }
193
194    protected void markImportDone() {
195        if (Framework.isTestModeSet()) {
196            return;
197        }
198
199        AuditLogger writer = Framework.getLocalService(AuditLogger.class);
200
201        LogEntry entry = writer.newLogEntry();
202        entry.setEventId(TEMPLATE_SAMPLE_INIT_EVENT);
203        entry.setEventDate(Calendar.getInstance().getTime());
204
205        List<LogEntry> entries = new ArrayList<LogEntry>();
206        entries.add(entry);
207        writer.addLogEntries(entries);
208
209    }
210
211    public int importModels() {
212
213        if (isImportAlreadyDone()) {
214            return 0;
215        }
216
217        int nbImportedDocs = 0;
218        Path path = TemplateBundleActivator.getDataDirPath();
219        path = path.append(getTemplateResourcesRootPath());
220        File root = new File(path.toString());
221        if (root.exists()) {
222            File[] modelRoots = root.listFiles(new FileFilter() {
223                @Override
224                public boolean accept(File pathname) {
225                    if (!pathname.isDirectory()) {
226                        return false;
227                    }
228                    return true;
229                }
230            });
231
232            if (modelRoots != null && modelRoots.length > 0) {
233                for (File modelRoot : modelRoots) {
234                    log.info("Importing template from " + modelRoot.getAbsolutePath());
235                    try {
236                        nbImportedDocs += importModelAndExamples(modelRoot);
237                    } catch (IOException e) {
238                        throw new NuxeoException("Failed to import from template: " + modelRoot.getAbsolutePath(), e);
239                    }
240                }
241                markImportDone();
242            }
243        }
244
245        return nbImportedDocs;
246    }
247
248    public int importModelAndExamples(File root) throws IOException {
249
250        int nbImportedDocs = 0;
251        final Map<String, File> roots = new HashMap<String, File>();
252        root.listFiles(new FileFilter() {
253            @Override
254            public boolean accept(File file) {
255                if (!file.isDirectory()) {
256                    return false;
257                }
258                if (file.getName().equals(TEMPLATE_ROOT)) {
259                    roots.put(TEMPLATE_ROOT, file);
260                    return true;
261                } else if (file.getName().equals(EXAMPLES_ROOT)) {
262                    roots.put(EXAMPLES_ROOT, file);
263                    return true;
264                } else if (file.getName().equals(RAW_EXAMPLES_ROOT)) {
265                    roots.put(RAW_EXAMPLES_ROOT, file);
266                    return true;
267                }
268
269                return false;
270            }
271        });
272
273        if (roots.size() >= 1) {
274            if (roots.get(TEMPLATE_ROOT) != null) {
275                DocumentModel templatesContainer = getOrCreateTemplateContainer();
276                DocumentModel samplesContainer = getOrCreateSampleContainer();
277                DocumentModel rawSamplesContainer = getOrCreateRawSampleContainer();
278                if (templatesContainer != null) {
279                    DocumentRef modelRef = importModel(root.getName(), roots.get(TEMPLATE_ROOT), templatesContainer);
280                    nbImportedDocs++;
281                    if (samplesContainer != null) {
282                        if (roots.get(EXAMPLES_ROOT) != null) {
283                            nbImportedDocs = nbImportedDocs
284                                    + importSamples(roots.get(EXAMPLES_ROOT), modelRef, samplesContainer);
285                        }
286                        if (roots.get(RAW_EXAMPLES_ROOT) != null) {
287                            nbImportedDocs = nbImportedDocs
288                                    + importSamples(roots.get(RAW_EXAMPLES_ROOT), modelRef, rawSamplesContainer);
289                        }
290                    }
291                }
292            }
293        }
294
295        return nbImportedDocs;
296
297    }
298
299    protected DocumentRef importModel(String modelName, File source, DocumentModel root) throws IOException {
300
301        // import
302        DocumentReader reader = new XMLModelReader(source, modelName);
303        DocumentWriter writer = new DocumentModelWriter(session, root.getPathAsString());
304
305        DocumentPipe pipe = new DocumentPipeImpl(10);
306        pipe.setReader(reader);
307        pipe.setWriter(writer);
308        DocumentTranslationMap map = pipe.run();
309
310        DocumentRef ref = map.getDocRefMap().values().iterator().next();
311        session.save();
312
313        return ref;
314    }
315
316    protected int importSamples(File root, DocumentRef modelRef, DocumentModel rootDoc) throws IOException {
317
318        int nbImportedDocs = 0;
319        for (File exampleDir : root.listFiles()) {
320            if (!exampleDir.isDirectory()) {
321                continue;
322            }
323
324            // import
325            DocumentReader reader = new XMLModelReader(exampleDir, exampleDir.getName());
326            DocumentWriter writer = new DocumentModelWriter(session, rootDoc.getPathAsString());
327
328            DocumentPipe pipe = new DocumentPipeImpl(10);
329
330            final String targetUUID = modelRef.toString();
331
332            pipe.addTransformer(new DocumentTransformer() {
333
334                @Override
335                public boolean transform(ExportedDocument xdoc) throws IOException {
336                    xdoc.getDocument().accept(new Visitor() {
337
338                        @Override
339                        public void visit(Text node) {
340                        }
341
342                        @Override
343                        public void visit(ProcessingInstruction node) {
344                        }
345
346                        @Override
347                        public void visit(Namespace namespace) {
348                        }
349
350                        @Override
351                        public void visit(Entity node) {
352                        }
353
354                        @Override
355                        public void visit(Comment node) {
356                        }
357
358                        @Override
359                        public void visit(CDATA node) {
360                        }
361
362                        @Override
363                        public void visit(Attribute node) {
364                        }
365
366                        @Override
367                        public void visit(Element node) {
368                            if ("templateId".equalsIgnoreCase(node.getName())
369                                    && "templateEntry".equalsIgnoreCase(node.getParent().getName())) {
370                                log.debug("Translating uuid to " + targetUUID);
371                                node.setText(targetUUID);
372                            }
373                        }
374
375                        @Override
376                        public void visit(DocumentType documentType) {
377                        }
378
379                        @Override
380                        public void visit(Document document) {
381                        }
382                    });
383                    return true;
384                }
385            });
386            pipe.setReader(reader);
387            pipe.setWriter(writer);
388            pipe.run();
389            nbImportedDocs++;
390
391        }
392        session.save();
393        return nbImportedDocs;
394    }
395}