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}