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