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 EXAMPLE_ROOT = "example";
081
082    public static final String TEST_EXAMPLE_ROOT = "testexample";
083
084    public static final String TEMPLATE_ROOT = "template";
085    
086    public static final String TEST_TEMPLATE_ROOT = "testtemplate";
087
088    protected static final String RESOURCES_ROOT = "templatesamples";
089
090    protected static final String RAW_RESOURCES_ROOT = "rawsamples";
091
092    protected static final String DOMAIN_QUERY = "select * from Domain where ecm:isCheckedInVersion=0  AND  ecm:currentLifeCycleState != 'deleted' order by dc:created ASC";
093
094    protected final CoreSession session;
095
096    public ModelImporter(CoreSession session) {
097        this.session = session;
098    }
099
100    protected String getTemplateResourcesRootPath() {
101        return RESOURCES_ROOT;
102    }
103
104    protected String getRawTemplateResourcesRootPath() {
105        return RAW_RESOURCES_ROOT;
106    }
107
108    protected DocumentModel getTargetDomain() {
109        return getTargetDomain(true);
110    }
111
112    protected DocumentModel getTargetDomain(boolean canRetry) {
113        DocumentModelList domains = session.query(DOMAIN_QUERY);
114        if (domains.size() > 0) {
115            return domains.get(0);
116        }
117        // no domain, that's strange
118        // may be a session flush issue
119        if (canRetry) {
120            session.save();
121            return getTargetDomain(false);
122        }
123        return null;
124    }
125
126    protected DocumentModel getOrCreateTemplateContainer() {
127        DocumentModel rootDomain = getTargetDomain();
128
129        if (rootDomain != null) {
130            DocumentModelList roots = session.getChildren(rootDomain.getRef(), "TemplateRoot");
131            if (roots.size() > 0) {
132                return roots.get(0);
133            }
134        }
135        return null;
136    }
137
138    protected DocumentModel getWSRoot(DocumentModel rootDomain) {
139        if (rootDomain != null) {
140            DocumentModelList roots = session.getChildren(rootDomain.getRef(), "WorkspaceRoot");
141            if (roots.size() > 0) {
142                DocumentModel WSRoot = roots.get(0);
143                return WSRoot;
144            }
145        }
146        return null;
147    }
148
149    protected DocumentModel getOrCreateSampleContainer() {
150        DocumentModel rootDomain = getTargetDomain();
151        DocumentModel container = null;
152
153        DocumentModel WSRoot = getWSRoot(rootDomain);
154        if (WSRoot != null) {
155            PathRef targetPath = new PathRef(WSRoot.getPathAsString() + "/" + getTemplateResourcesRootPath());
156            if (!session.exists(targetPath)) {
157                container = session.createDocumentModel(WSRoot.getPathAsString(), getTemplateResourcesRootPath(),
158                        "nxtrSamplesContainer");
159                container.setPropertyValue("dc:title", "Discover Customization Examples");
160                container.setPropertyValue("nxtplsamplescontainer:instructions",
161                        "<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>");
162                container = session.createDocument(container);
163            } else {
164                container = session.getDocument(targetPath);
165            }
166        }
167        return container;
168    }
169
170    private DocumentModel getOrCreateTestSamplesContainer() {
171        DocumentModel container = null;
172        DocumentModel parentContainer = getOrCreateSampleContainer();
173
174        PathRef targetPath = new PathRef(getOrCreateSampleContainer().getPathAsString() + "/" + getRawTemplateResourcesRootPath());
175        if (!session.exists(targetPath)) {
176            container = session.createDocumentModel(parentContainer.getPathAsString(), "rawsamples",
177                    "Workspace");
178            container.setPropertyValue("dc:title", "More (Raw) Examples");
179            container.setPropertyValue("dc:description",
180                    "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.");
181            container = session.createDocument(container);
182        } else {
183            container = session.getDocument(targetPath);
184        }
185        return container;
186    }
187
188    protected boolean isImportAlreadyDone() {
189        if (Framework.isTestModeSet()) {
190            return false;
191        }
192
193        AuditReader reader = Framework.getService(AuditReader.class);
194        List<LogEntry> entries = reader.queryLogs(IMPORT_ALREADY_DONE_EVENTS, null);
195        return !entries.isEmpty();
196    }
197
198    protected void markImportDone() {
199        if (Framework.isTestModeSet()) {
200            return;
201        }
202
203        AuditLogger writer = Framework.getService(AuditLogger.class);
204
205        LogEntry entry = writer.newLogEntry();
206        entry.setEventId(TEMPLATE_SAMPLE_INIT_EVENT);
207        entry.setEventDate(Calendar.getInstance().getTime());
208
209        List<LogEntry> entries = new ArrayList<LogEntry>();
210        entries.add(entry);
211        writer.addLogEntries(entries);
212
213    }
214
215    public int importModels() {
216
217        if (isImportAlreadyDone()) {
218            return 0;
219        }
220
221        int nbImportedDocs = 0;
222        Path path = TemplateBundleActivator.getDataDirPath();
223        path = path.append(getTemplateResourcesRootPath());
224        File root = new File(path.toString());
225        if (root.exists()) {
226            File[] modelRoots = root.listFiles(new FileFilter() {
227                @Override
228                public boolean accept(File pathname) {
229                    if (!pathname.isDirectory()) {
230                        return false;
231                    }
232                    return true;
233                }
234            });
235
236            if (modelRoots != null && modelRoots.length > 0) {
237                for (File modelRoot : modelRoots) {
238                    log.info("Importing template from " + modelRoot.getAbsolutePath());
239                    try {
240                        nbImportedDocs += importModelAndExamples(modelRoot);
241                    } catch (IOException e) {
242                        throw new NuxeoException("Failed to import from template: " + modelRoot.getAbsolutePath(), e);
243                    }
244                }
245                markImportDone();
246            }
247        }
248
249        return nbImportedDocs;
250    }
251
252    public int importModelAndExamples(File root) throws IOException {
253
254        int nbImportedDocs = 0;
255        final Map<String, File> roots = new HashMap<String, File>();
256        root.listFiles(new FileFilter() {
257            @Override
258            public boolean accept(File file) {
259                if (!file.isDirectory()) {
260                    return false;
261                }
262                if (file.getName().equals(TEMPLATE_ROOT)) {
263                    roots.put(TEMPLATE_ROOT, file);
264                    return true;
265                } else if (file.getName().equals(TEST_TEMPLATE_ROOT) && Framework.isTestModeSet()) {
266                        roots.put(TEMPLATE_ROOT, file);
267                    return true;
268                } else if (file.getName().equals(EXAMPLE_ROOT)) {
269                    roots.put(EXAMPLE_ROOT, file);
270                    return true;
271                } else if (file.getName().equals(TEST_EXAMPLE_ROOT) && Framework.isTestModeSet()) {
272                    roots.put(TEST_EXAMPLE_ROOT, file);
273                    return true;
274                }
275
276                return false;
277            }
278        });
279
280        if (roots.size() >= 1) {
281            if (roots.get(TEMPLATE_ROOT) != null) {
282                DocumentModel templatesContainer = getOrCreateTemplateContainer();
283                DocumentModel samplesContainer = getOrCreateSampleContainer();
284                DocumentModel testSamplesContainer = null;
285                if(Framework.isTestModeSet()){
286                        testSamplesContainer = getOrCreateTestSamplesContainer();
287                }
288                if (templatesContainer != null) {
289                    DocumentRef modelRef = importModel(root.getName(), roots.get(TEMPLATE_ROOT), templatesContainer);
290                    nbImportedDocs++;
291                    if (samplesContainer != null) {
292                        if (roots.get(EXAMPLE_ROOT) != null) {
293                            nbImportedDocs = nbImportedDocs
294                                    + importSamples(roots.get(EXAMPLE_ROOT), modelRef, samplesContainer);
295                        }
296                        if (roots.get(TEST_EXAMPLE_ROOT) != null 
297                                && Framework.isTestModeSet()) {
298                            nbImportedDocs = nbImportedDocs
299                                    + importSamples(roots.get(TEST_EXAMPLE_ROOT), modelRef, testSamplesContainer);
300                        }
301                    }
302                }
303            }
304        }
305
306        return nbImportedDocs;
307
308    }
309
310    protected DocumentRef importModel(String modelName, File source, DocumentModel root) throws IOException {
311
312        // import
313        DocumentReader reader = new XMLModelReader(source, modelName);
314        DocumentWriter writer = new DocumentModelWriter(session, root.getPathAsString());
315
316        DocumentPipe pipe = new DocumentPipeImpl(10);
317        pipe.setReader(reader);
318        pipe.setWriter(writer);
319        DocumentTranslationMap map = pipe.run();
320
321        DocumentRef ref = map.getDocRefMap().values().iterator().next();
322        session.save();
323
324        return ref;
325    }
326
327    protected int importSamples(File root, DocumentRef modelRef, DocumentModel rootDoc) throws IOException {
328
329        int nbImportedDocs = 0;
330        for (File exampleDir : root.listFiles()) {
331            if (!exampleDir.isDirectory()) {
332                continue;
333            }
334
335            // import
336            DocumentReader reader = new XMLModelReader(exampleDir, exampleDir.getName());
337            DocumentWriter writer = new DocumentModelWriter(session, rootDoc.getPathAsString());
338
339            DocumentPipe pipe = new DocumentPipeImpl(10);
340
341            final String targetUUID = modelRef.toString();
342
343            pipe.addTransformer(new DocumentTransformer() {
344
345                @Override
346                public boolean transform(ExportedDocument xdoc) throws IOException {
347                    xdoc.getDocument().accept(new Visitor() {
348
349                        @Override
350                        public void visit(Text node) {
351                        }
352
353                        @Override
354                        public void visit(ProcessingInstruction node) {
355                        }
356
357                        @Override
358                        public void visit(Namespace namespace) {
359                        }
360
361                        @Override
362                        public void visit(Entity node) {
363                        }
364
365                        @Override
366                        public void visit(Comment node) {
367                        }
368
369                        @Override
370                        public void visit(CDATA node) {
371                        }
372
373                        @Override
374                        public void visit(Attribute node) {
375                        }
376
377                        @Override
378                        public void visit(Element node) {
379                            if ("templateId".equalsIgnoreCase(node.getName())
380                                    && "templateEntry".equalsIgnoreCase(node.getParent().getName())) {
381                                log.debug("Translating uuid to " + targetUUID);
382                                node.setText(targetUUID);
383                            }
384                        }
385
386                        @Override
387                        public void visit(DocumentType documentType) {
388                        }
389
390                        @Override
391                        public void visit(Document document) {
392                        }
393                    });
394                    return true;
395                }
396            });
397            pipe.setReader(reader);
398            pipe.setWriter(writer);
399            pipe.run();
400            nbImportedDocs++;
401
402        }
403        session.save();
404        return nbImportedDocs;
405    }
406}