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 */ 020package org.nuxeo.template.adapters.doc; 021 022import java.io.IOException; 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.List; 026 027import org.dom4j.DocumentException; 028import org.nuxeo.ecm.automation.AutomationService; 029import org.nuxeo.ecm.automation.OperationChain; 030import org.nuxeo.ecm.automation.OperationContext; 031import org.nuxeo.ecm.automation.OperationException; 032import org.nuxeo.ecm.automation.core.operations.blob.ConvertBlob; 033import org.nuxeo.ecm.core.api.Blob; 034import org.nuxeo.ecm.core.api.DocumentModel; 035import org.nuxeo.ecm.core.api.DocumentRef; 036import org.nuxeo.ecm.core.api.IdRef; 037import org.nuxeo.ecm.core.api.NuxeoException; 038import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 039import org.nuxeo.runtime.api.Framework; 040import org.nuxeo.template.XMLSerializer; 041import org.nuxeo.template.adapters.AbstractTemplateDocument; 042import org.nuxeo.template.api.TemplateInput; 043import org.nuxeo.template.api.TemplateProcessor; 044import org.nuxeo.template.api.TemplateProcessorService; 045import org.nuxeo.template.api.adapters.TemplateBasedDocument; 046import org.nuxeo.template.api.adapters.TemplateSourceDocument; 047import org.nuxeo.template.api.descriptor.OutputFormatDescriptor; 048 049/** 050 * Default implementation of {@link TemplateBasedDocument} adapter. This adapter mainly expect from the underlying 051 * {@link DocumentModel} to have the "TemplateBased" facet 052 * 053 * @author Tiry (tdelprat@nuxeo.com) 054 */ 055public class TemplateBasedDocumentAdapterImpl extends AbstractTemplateDocument implements Serializable, 056 TemplateBasedDocument { 057 058 private static final long serialVersionUID = 1L; 059 060 public static final String TEMPLATEBASED_FACET = "TemplateBased"; 061 062 protected final TemplateBindings bindings; 063 064 public TemplateBasedDocumentAdapterImpl(DocumentModel doc) { 065 this.adaptedDoc = doc; 066 bindings = new TemplateBindings(doc); 067 } 068 069 public DocumentModel setTemplate(DocumentModel template, boolean save) { 070 071 TemplateSourceDocument source = template.getAdapter(TemplateSourceDocument.class); 072 if (source == null) { 073 throw new NuxeoException("Can not bind to an non template document"); 074 } 075 String tid = source.getId(); 076 String templateName = source.getName(); 077 if (!bindings.containsTemplateId(tid)) { 078 if (templateName == null) { 079 templateName = TemplateBindings.DEFAULT_BINDING; 080 } 081 TemplateBinding tb = new TemplateBinding(); 082 tb.setTemplateId(tid); 083 tb.setName(templateName); 084 bindings.add(tb); 085 initializeFromTemplate(templateName, false); 086 bindings.save(adaptedDoc); 087 if (save) { 088 doSave(); 089 } 090 091 } 092 return adaptedDoc; 093 } 094 095 public DocumentModel removeTemplateBinding(String templateName, boolean save) { 096 if (bindings.containsTemplateName(templateName)) { 097 bindings.removeByName(templateName); 098 bindings.save(adaptedDoc); 099 if (save) { 100 doSave(); 101 } 102 } 103 return adaptedDoc; 104 } 105 106 public TemplateSourceDocument getSourceTemplate(String templateName) { 107 DocumentModel template = getSourceTemplateDoc(templateName); 108 if (template != null) { 109 return template.getAdapter(TemplateSourceDocument.class); 110 } 111 return null; 112 } 113 114 @Override 115 public DocumentRef getSourceTemplateDocRef(String templateName) { 116 TemplateBinding binding = null; 117 if (templateName == null) { 118 binding = bindings.get(); 119 } else { 120 binding = bindings.get(templateName); 121 } 122 if (binding == null) { 123 return null; 124 } 125 return new IdRef(binding.getTemplateId()); 126 } 127 128 public DocumentModel getSourceTemplateDoc(String templateName) { 129 TemplateBinding binding = null; 130 if (templateName == null) { 131 binding = bindings.get(); 132 } else { 133 binding = bindings.get(templateName); 134 } 135 if (binding == null) { 136 return null; 137 } 138 DocumentRef tRef = getSourceTemplateDocRef(templateName); 139 if (tRef == null) { 140 return null; 141 } 142 return getSession().getDocument(tRef); 143 } 144 145 public List<TemplateSourceDocument> getSourceTemplates() { 146 List<TemplateSourceDocument> result = new ArrayList<TemplateSourceDocument>(); 147 for (TemplateBinding binding : bindings) { 148 result.add(getSourceTemplate(binding.getName())); 149 } 150 return result; 151 } 152 153 public String getTemplateType(String templateName) { 154 TemplateSourceDocument source = getSourceTemplate(templateName); 155 if (source != null) { 156 return source.getTemplateType(); 157 } 158 return null; 159 } 160 161 public DocumentModel initializeFromTemplate(boolean save) { 162 return initializeFromTemplate(TemplateBindings.DEFAULT_BINDING, save); 163 } 164 165 public DocumentModel initializeFromTemplate(String templateName, boolean save) { 166 167 TemplateSourceDocument tmpl = getSourceTemplate(templateName); 168 if (tmpl == null) { 169 throw new NuxeoException("No associated template for name " + templateName); 170 } 171 172 // copy Params but set as readonly all params set in template 173 List<TemplateInput> params = tmpl.getParams(); 174 List<TemplateInput> myParams = new ArrayList<TemplateInput>(); 175 for (TemplateInput param : params) { 176 boolean readOnly = param.isSet() && !tmpl.allowInstanceOverride(); 177 TemplateInput myParam = param.getCopy(readOnly); 178 myParams.add(myParam); 179 } 180 181 bindings.get(templateName).setData(myParams); 182 183 if (tmpl.useAsMainContent()) { 184 // copy the template as main blob 185 BlobHolder bh = adaptedDoc.getAdapter(BlobHolder.class); 186 if (bh != null) { 187 bh.setBlob(tmpl.getTemplateBlob()); 188 } 189 bindings.get(templateName).setUseMainContentAsTemplate(true); 190 } 191 192 if (save) { 193 doSave(); 194 } 195 return adaptedDoc; 196 } 197 198 @Override 199 protected void doSave() { 200 bindings.save(adaptedDoc); 201 super.doSave(); 202 } 203 204 protected void setBlob(Blob blob) { 205 adaptedDoc.getAdapter(BlobHolder.class).setBlob(blob); 206 } 207 208 public Blob renderWithTemplate(String templateName) { 209 TemplateProcessor processor = getTemplateProcessor(templateName); 210 if (processor != null) { 211 Blob blob; 212 try { 213 blob = processor.renderTemplate(this, templateName); 214 } catch (IOException e) { 215 throw new NuxeoException("Failed to render template: " + templateName, e); 216 } 217 String format = getSourceTemplate(templateName).getOutputFormat(); 218 if (blob != null && format != null && !format.isEmpty()) { 219 try { 220 return convertBlob(templateName, blob, format); 221 } catch (OperationException e) { 222 throw new NuxeoException(e); 223 } 224 } else { 225 return blob; 226 } 227 } else { 228 String templateType = getTemplateType(templateName); 229 if (templateType == null) { 230 throw new NuxeoException( 231 "Template type is null : if you don't set it explicitly, your template file should have an extension or a mimetype so that it can be automatically determined"); 232 } else { 233 throw new NuxeoException("No template processor found for template type=" + templateType); 234 } 235 } 236 } 237 238 private Blob convertBlob(String templateName, Blob blob, String outputFormat) throws OperationException { 239 OutputFormatDescriptor outFormat = getOutputFormatDescriptor(outputFormat); 240 String chainId = outFormat.getChainId(); 241 String mimeType = outFormat.getMimeType(); 242 AutomationService automationService = Framework.getLocalService(AutomationService.class); 243 OperationContext ctx = initOperationContext(blob, templateName); 244 Object result = null; 245 if (chainId != null) { 246 ctx.put("templateSourceDocument", getSourceTemplateDoc(templateName)); 247 ctx.put("templateBasedDocument", adaptedDoc); 248 result = automationService.run(ctx, chainId); 249 } else if (mimeType != null) { 250 OperationChain chain = new OperationChain("convertToMimeType"); 251 chain.add(ConvertBlob.ID).set("mimeType", mimeType); 252 result = automationService.run(ctx, chain); 253 } 254 if (result != null && result instanceof Blob) { 255 return (Blob) result; 256 } else { 257 return blob; 258 } 259 } 260 261 protected OperationContext initOperationContext(Blob blob, String templateName) { 262 OperationContext ctx = new OperationContext(); 263 ctx.put("templateName", templateName); 264 ctx.setInput(blob); 265 ctx.setCommit(false); 266 ctx.setCoreSession(getSession()); 267 return ctx; 268 } 269 270 public Blob renderAndStoreAsAttachment(String templateName, boolean save) { 271 Blob blob = renderWithTemplate(templateName); 272 setBlob(blob); 273 if (save) { 274 adaptedDoc = getSession().saveDocument(adaptedDoc); 275 } 276 return blob; 277 } 278 279 public boolean isBidirectional() { 280 /* 281 * TemplateProcessor processor = getTemplateProcessor(); if (processor != null) { return processor instanceof 282 * BidirectionalTemplateProcessor; } 283 */ 284 return false; 285 } 286 287 public Blob getTemplateBlob(String templateName) { 288 TemplateSourceDocument source = getSourceTemplate(templateName); 289 if (source != null) { 290 if (source.useAsMainContent()) { 291 BlobHolder bh = getAdaptedDoc().getAdapter(BlobHolder.class); 292 if (bh != null) { 293 Blob blob = bh.getBlob(); 294 if (blob != null) { 295 return blob; 296 } 297 } 298 } 299 // get the template from the source 300 Blob blob = source.getTemplateBlob(); 301 return blob; 302 } 303 // fall back 304 BlobHolder bh = getAdaptedDoc().getAdapter(BlobHolder.class); 305 if (bh == null) { 306 return null; 307 } else { 308 return bh.getBlob(); 309 } 310 } 311 312 public boolean hasParams(String templateName) { 313 return getParams(templateName).size() > 0; 314 } 315 316 public List<TemplateInput> getParams(String templateName) { 317 318 TemplateBinding binding = bindings.get(templateName); 319 if (binding != null) { 320 String xml = binding.getData(); 321 try { 322 return XMLSerializer.readFromXml(xml); 323 } catch (DocumentException e) { 324 log.error("Unable to parse parameters", e); 325 return new ArrayList<TemplateInput>(); 326 } 327 } 328 return new ArrayList<TemplateInput>(); 329 } 330 331 public DocumentModel saveParams(String templateName, List<TemplateInput> params, boolean save) { 332 TemplateBinding binding = bindings.get(templateName); 333 if (binding != null) { 334 binding.setData(params); 335 bindings.save(adaptedDoc); 336 } 337 if (save) { 338 doSave(); 339 } 340 return adaptedDoc; 341 } 342 343 protected TemplateProcessor getTemplateProcessor(String templateName) { 344 TemplateProcessorService tps = Framework.getLocalService(TemplateProcessorService.class); 345 return tps.getProcessor(getTemplateType(templateName)); 346 } 347 348 protected OutputFormatDescriptor getOutputFormatDescriptor(String outputFormat) { 349 TemplateProcessorService tps = Framework.getLocalService(TemplateProcessorService.class); 350 return tps.getOutputFormatDescriptor(outputFormat); 351 } 352 353 public boolean hasEditableParams(String templateName) { 354 for (TemplateInput param : getParams(templateName)) { 355 if (!param.isReadOnly()) { 356 return true; 357 } 358 } 359 return false; 360 } 361 362 public String getTemplateNameForRendition(String renditionName) { 363 for (TemplateBinding binding : bindings) { 364 if (renditionName.equals(getSourceTemplate(binding.getName()).getTargetRenditionName())) { 365 return binding.getName(); 366 } 367 } 368 return null; 369 } 370 371 public List<String> getTemplateNames() { 372 return bindings.getNames(); 373 } 374 375}