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}