001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id: LiveEditBootstrapHelper.java 30586 2008-02-26 14:30:17Z ogrisel $ 018 */ 019 020package org.nuxeo.ecm.webapp.liveedit; 021 022import static org.jboss.seam.ScopeType.EVENT; 023 024import java.io.IOException; 025import java.io.Serializable; 026import java.util.Calendar; 027import java.util.Enumeration; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031 032import javax.faces.context.FacesContext; 033import javax.servlet.http.Cookie; 034import javax.servlet.http.HttpServletRequest; 035import javax.servlet.http.HttpServletResponse; 036 037import org.apache.commons.logging.Log; 038import org.apache.commons.logging.LogFactory; 039import org.dom4j.Document; 040import org.dom4j.DocumentFactory; 041import org.dom4j.Element; 042import org.dom4j.QName; 043import org.dom4j.io.OutputFormat; 044import org.dom4j.io.XMLWriter; 045import org.jboss.seam.ScopeType; 046import org.jboss.seam.annotations.Factory; 047import org.jboss.seam.annotations.In; 048import org.jboss.seam.annotations.Name; 049import org.jboss.seam.annotations.web.RequestParameter; 050import org.jboss.seam.annotations.Scope; 051import org.nuxeo.ecm.core.api.Blob; 052import org.nuxeo.ecm.core.api.CoreInstance; 053import org.nuxeo.ecm.core.api.CoreSession; 054import org.nuxeo.ecm.core.api.DocumentModel; 055import org.nuxeo.ecm.core.api.DocumentNotFoundException; 056import org.nuxeo.ecm.core.api.IdRef; 057import org.nuxeo.ecm.core.api.LifeCycleConstants; 058import org.nuxeo.ecm.core.api.NuxeoException; 059import org.nuxeo.ecm.core.api.PropertyException; 060import org.nuxeo.ecm.core.api.security.SecurityConstants; 061import org.nuxeo.ecm.core.schema.FacetNames; 062import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeEntry; 063import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry; 064import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 065import org.nuxeo.ecm.platform.ui.web.tag.fn.LiveEditConstants; 066import org.nuxeo.ecm.platform.ui.web.util.BaseURL; 067import org.nuxeo.runtime.api.Framework; 068 069/** 070 * The LiveEdit bootstrap procedure works as follows: 071 * <ul> 072 * <li>browsed page calls a JSF function from the DocumentModelFunctions class (edit a document, create new document, 073 * etc.) to generate;</li> 074 * <li>composing a specific URL as result, triggering the bootstrap addon to popup;</li> 075 * <li>the addon come back with the URL composed allowing the present seam component to create the bootstrap file. The 076 * file contains various data as requested in the URL;</li> 077 * <li>the XML file is now available to addon which presents it to the client plugin.</li> 078 * </ul> 079 * Please refer to the nuxeo book chapter on desktop integration for details on the format of the nxedit URLs and the 080 * XML bootstrap file. 081 * 082 * @author Thierry Delprat NXP-1959 the bootstrap file is managing the 'create new document [from template]' case too. 083 * The URL is containing an action identifier. 084 * @author Rux rdarlea@nuxeo.com 085 * @author Olivier Grisel ogrisel@nuxeo.com (split url functions into JSF DocumentModelFunctions module) 086 */ 087@Scope(EVENT) 088@Name("liveEditHelper") 089public class LiveEditBootstrapHelper implements Serializable, LiveEditConstants { 090 091 protected static final String MODIFIED_FIELD = "modified"; 092 093 protected static final String DUBLINCORE_SCHEMA = "dublincore"; 094 095 private static final Log log = LogFactory.getLog(LiveEditBootstrapHelper.class); 096 097 private static final long serialVersionUID = 876879071L; 098 099 @In(create = true) 100 protected transient NavigationContext navigationContext; 101 102 @In(create = true, required = false) 103 protected transient CoreSession documentManager; 104 105 @RequestParameter 106 protected String action; 107 108 @RequestParameter 109 protected String repoID; 110 111 @RequestParameter 112 protected String templateRepoID; 113 114 @RequestParameter 115 protected String docRef; 116 117 @RequestParameter 118 protected String templateDocRef; 119 120 @In(create = true) 121 protected LiveEditClientConfig liveEditClientConfig; 122 123 /** 124 * @deprecated use blobPropertyField and filenamePropertyField instead 125 */ 126 @Deprecated 127 @RequestParameter 128 protected String schema; 129 130 @RequestParameter 131 protected String templateSchema; 132 133 /** 134 * @deprecated use blobPropertyField instead 135 */ 136 @Deprecated 137 @RequestParameter 138 protected String blobField; 139 140 @RequestParameter 141 protected String blobPropertyName; 142 143 @RequestParameter 144 protected String templateBlobField; 145 146 // TODO: to be deprecated once all filenames are stored in the blob itself 147 /** 148 * @deprecated use filenamePropertyField instead 149 */ 150 @Deprecated 151 @RequestParameter 152 protected String filenameField; 153 154 // TODO: to be deprecated once all filenames are stored in the blob itself 155 @RequestParameter 156 protected String filenamePropertyName; 157 158 @RequestParameter 159 protected String mimetype; 160 161 @RequestParameter 162 protected String docType; 163 164 protected MimetypeRegistry mimetypeRegistry; 165 166 // Event-long cache for mimetype lookups - no invalidation required 167 protected final Map<String, Boolean> cachedEditableStates = new HashMap<String, Boolean>(); 168 169 // Event-long cache for document field lookups - no invalidation required 170 protected final Map<String, Boolean> cachedEditableBlobs = new HashMap<String, Boolean>(); 171 172 /** 173 * Creates the bootstrap file. It is called from the browser's addon. The URL composition tells the case and what to 174 * create. The structure is depicted in the NXP-1881. Rux NXP-1959: add new tag on root level describing the action: 175 * actionEdit, actionNew or actionFromTemplate. 176 * 177 * @return the bootstrap file content 178 */ 179 public void getBootstrap() throws IOException { 180 String currentRepoID = documentManager.getRepositoryName(); 181 182 CoreSession session = documentManager; 183 CoreSession templateSession = documentManager; 184 try { 185 if (repoID != null && !currentRepoID.equals(repoID)) { 186 session = CoreInstance.openCoreSession(repoID); 187 } 188 189 if (templateRepoID != null && !currentRepoID.equals(templateRepoID)) { 190 templateSession = CoreInstance.openCoreSession(templateRepoID); 191 } 192 193 DocumentModel doc = null; 194 DocumentModel templateDoc = null; 195 String filename = null; 196 if (ACTION_EDIT_DOCUMENT.equals(action)) { 197 // fetch the document to edit to get its mimetype and document 198 // type 199 doc = session.getDocument(new IdRef(docRef)); 200 docType = doc.getType(); 201 Blob blob = null; 202 if (blobPropertyName != null) { 203 blob = (Blob) doc.getPropertyValue(blobPropertyName); 204 if (blob == null) { 205 throw new NuxeoException(String.format("could not find blob to edit with property '%s'", 206 blobPropertyName)); 207 } 208 } else { 209 blob = (Blob) doc.getProperty(schema, blobField); 210 if (blob == null) { 211 throw new NuxeoException(String.format( 212 "could not find blob to edit with schema '%s' and field '%s'", schema, blobField)); 213 } 214 } 215 mimetype = blob.getMimeType(); 216 if (filenamePropertyName != null) { 217 filename = (String) doc.getPropertyValue(filenamePropertyName); 218 } else { 219 filename = (String) doc.getProperty(schema, filenameField); 220 } 221 } else if (ACTION_CREATE_DOCUMENT.equals(action)) { 222 // creating a new document all parameters are read from the 223 // request parameters 224 } else if (ACTION_CREATE_DOCUMENT_FROM_TEMPLATE.equals(action)) { 225 // fetch the template blob to get its mimetype 226 templateDoc = templateSession.getDocument(new IdRef(templateDocRef)); 227 Blob blob = (Blob) templateDoc.getProperty(templateSchema, templateBlobField); 228 if (blob == null) { 229 throw new NuxeoException(String.format( 230 "could not find template blob with schema '%s' and field '%s'", templateSchema, 231 templateBlobField)); 232 } 233 mimetype = blob.getMimeType(); 234 // leave docType from the request query parameter 235 } else { 236 throw new NuxeoException(String.format( 237 "action '%s' is not a valid LiveEdit action: should be one of '%s', '%s' or '%s'", action, 238 ACTION_CREATE_DOCUMENT, ACTION_CREATE_DOCUMENT_FROM_TEMPLATE, ACTION_EDIT_DOCUMENT)); 239 } 240 241 FacesContext context = FacesContext.getCurrentInstance(); 242 HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); 243 HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); 244 245 Element root = DocumentFactory.getInstance().createElement(liveEditTag); 246 root.addNamespace("", XML_LE_NAMESPACE); 247 // RUX NXP-1959: action id 248 Element actionInfo = root.addElement(actionSelectorTag); 249 actionInfo.setText(action); 250 251 // Document related informations 252 Element docInfo = root.addElement(documentTag); 253 addTextElement(docInfo, docRefTag, docRef); 254 Element docPathT = docInfo.addElement(docPathTag); 255 Element docTitleT = docInfo.addElement(docTitleTag); 256 if (doc != null) { 257 docPathT.setText(doc.getPathAsString()); 258 docTitleT.setText(doc.getTitle()); 259 } 260 addTextElement(docInfo, docRepositoryTag, repoID); 261 262 addTextElement(docInfo, docSchemaNameTag, schema); 263 addTextElement(docInfo, docFieldNameTag, blobField); 264 addTextElement(docInfo, docBlobFieldNameTag, blobField); 265 Element docFieldPathT = docInfo.addElement(docfieldPathTag); 266 Element docBlobFieldPathT = docInfo.addElement(docBlobFieldPathTag); 267 if (blobPropertyName != null) { 268 // FIXME AT: NXP-2306: send blobPropertyName correctly (?) 269 docFieldPathT.setText(blobPropertyName); 270 docBlobFieldPathT.setText(blobPropertyName); 271 } else { 272 if (schema != null && blobField != null) { 273 docFieldPathT.setText(schema + ':' + blobField); 274 docBlobFieldPathT.setText(schema + ':' + blobField); 275 } 276 } 277 addTextElement(docInfo, docFilenameFieldNameTag, filenameField); 278 Element docFilenameFieldPathT = docInfo.addElement(docFilenameFieldPathTag); 279 if (filenamePropertyName != null) { 280 docFilenameFieldPathT.setText(filenamePropertyName); 281 } else { 282 if (schema != null && blobField != null) { 283 docFilenameFieldPathT.setText(schema + ':' + filenameField); 284 } 285 } 286 287 addTextElement(docInfo, docfileNameTag, filename); 288 addTextElement(docInfo, docTypeTag, docType); 289 addTextElement(docInfo, docMimetypeTag, mimetype); 290 addTextElement(docInfo, docFileExtensionTag, getFileExtension(mimetype)); 291 292 Element docFileAuthorizedExtensions = docInfo.addElement(docFileAuthorizedExtensionsTag); 293 List<String> authorizedExtensions = getFileExtensions(mimetype); 294 if (authorizedExtensions != null) { 295 for (String extension : authorizedExtensions) { 296 addTextElement(docFileAuthorizedExtensions, docFileAuthorizedExtensionTag, extension); 297 } 298 } 299 300 Element docIsVersionT = docInfo.addElement(docIsVersionTag); 301 Element docIsLockedT = docInfo.addElement(docIsLockedTag); 302 if (ACTION_EDIT_DOCUMENT.equals(action)) { 303 docIsVersionT.setText(Boolean.toString(doc.isVersion())); 304 docIsLockedT.setText(Boolean.toString(doc.isLocked())); 305 } 306 307 // template information for ACTION_CREATE_DOCUMENT_FROM_TEMPLATE 308 309 Element templateDocInfo = root.addElement(templateDocumentTag); 310 addTextElement(templateDocInfo, docRefTag, templateDocRef); 311 docPathT = templateDocInfo.addElement(docPathTag); 312 docTitleT = templateDocInfo.addElement(docTitleTag); 313 if (templateDoc != null) { 314 docPathT.setText(templateDoc.getPathAsString()); 315 docTitleT.setText(templateDoc.getTitle()); 316 } 317 addTextElement(templateDocInfo, docRepositoryTag, templateRepoID); 318 addTextElement(templateDocInfo, docSchemaNameTag, templateSchema); 319 addTextElement(templateDocInfo, docFieldNameTag, templateBlobField); 320 addTextElement(templateDocInfo, docBlobFieldNameTag, templateBlobField); 321 docFieldPathT = templateDocInfo.addElement(docfieldPathTag); 322 docBlobFieldPathT = templateDocInfo.addElement(docBlobFieldPathTag); 323 if (templateSchema != null && templateBlobField != null) { 324 docFieldPathT.setText(templateSchema + ":" + templateBlobField); 325 docBlobFieldPathT.setText(templateSchema + ":" + templateBlobField); 326 } 327 addTextElement(templateDocInfo, docMimetypeTag, mimetype); 328 addTextElement(templateDocInfo, docFileExtensionTag, getFileExtension(mimetype)); 329 330 Element templateFileAuthorizedExtensions = templateDocInfo.addElement(docFileAuthorizedExtensionsTag); 331 if (authorizedExtensions != null) { 332 for (String extension : authorizedExtensions) { 333 addTextElement(templateFileAuthorizedExtensions, docFileAuthorizedExtensionTag, extension); 334 } 335 } 336 337 // Browser request related informations 338 Element requestInfo = root.addElement(requestInfoTag); 339 Cookie[] cookies = request.getCookies(); 340 Element cookiesT = requestInfo.addElement(requestCookiesTag); 341 for (Cookie cookie : cookies) { 342 Element cookieT = cookiesT.addElement(requestCookieTag); 343 cookieT.addAttribute("name", cookie.getName()); 344 cookieT.setText(cookie.getValue()); 345 } 346 Element headersT = requestInfo.addElement(requestHeadersTag); 347 Enumeration hEnum = request.getHeaderNames(); 348 while (hEnum.hasMoreElements()) { 349 String hName = (String) hEnum.nextElement(); 350 if (!hName.equalsIgnoreCase("cookie")) { 351 Element headerT = headersT.addElement(requestHeaderTag); 352 headerT.addAttribute("name", hName); 353 headerT.setText(request.getHeader(hName)); 354 } 355 } 356 addTextElement(requestInfo, requestBaseURLTag, BaseURL.getBaseURL(request)); 357 358 // User related informations 359 String username = context.getExternalContext().getUserPrincipal().getName(); 360 Element userInfo = root.addElement(userInfoTag); 361 addTextElement(userInfo, userNameTag, username); 362 addTextElement(userInfo, userPasswordTag, ""); 363 addTextElement(userInfo, userTokenTag, ""); 364 addTextElement(userInfo, userLocaleTag, context.getViewRoot().getLocale().toString()); 365 // Rux NXP-1882: the wsdl locations 366 String baseUrl = BaseURL.getBaseURL(request); 367 Element wsdlLocations = root.addElement(wsdlLocationsTag); 368 Element wsdlAccessWST = wsdlLocations.addElement(wsdlAccessWebServiceTag); 369 wsdlAccessWST.setText(baseUrl + "webservices/nuxeoAccess?wsdl"); 370 Element wsdlEEWST = wsdlLocations.addElement(wsdlLEWebServiceTag); 371 wsdlEEWST.setText(baseUrl + "webservices/nuxeoLEWS?wsdl"); 372 373 // Server related informations 374 Element serverInfo = root.addElement(serverInfoTag); 375 Element serverVersionT = serverInfo.addElement(serverVersionTag); 376 serverVersionT.setText("5.1"); // TODO: use a buildtime generated 377 // version tag instead 378 379 // Client related informations 380 Element editId = root.addElement(editIdTag); 381 editId.setText(getEditId(doc, session, username)); 382 383 // serialize bootstrap XML document in the response 384 Document xmlDoc = DocumentFactory.getInstance().createDocument(); 385 xmlDoc.setRootElement(root); 386 response.setContentType("text/xml; charset=UTF-8"); 387 388 // use a formatter to make it easier to debug live edit client 389 // implementations 390 OutputFormat format = OutputFormat.createPrettyPrint(); 391 format.setEncoding("UTF-8"); 392 XMLWriter writer = new XMLWriter(response.getOutputStream(), format); 393 writer.write(xmlDoc); 394 395 response.flushBuffer(); 396 context.responseComplete(); 397 } finally { 398 if (session != null && session != documentManager) { 399 session.close(); 400 } 401 if (templateSession != null && templateSession != documentManager) { 402 templateSession.close(); 403 } 404 } 405 } 406 407 protected String getFileExtension(String mimetype) { 408 if (mimetype == null) { 409 return null; 410 } 411 MimetypeRegistry mimetypeRegistry = Framework.getService(MimetypeRegistry.class); 412 List<String> extensions = mimetypeRegistry.getExtensionsFromMimetypeName(mimetype); 413 if (extensions != null && !extensions.isEmpty()) { 414 return extensions.get(0); 415 } else { 416 return null; 417 } 418 } 419 420 protected List<String> getFileExtensions(String mimetype) { 421 if (mimetype == null) { 422 return null; 423 } 424 MimetypeRegistry mimetypeRegistry = Framework.getService(MimetypeRegistry.class); 425 List<String> extensions = mimetypeRegistry.getExtensionsFromMimetypeName(mimetype); 426 return extensions; 427 } 428 429 protected static Element addTextElement(Element parent, QName newElementName, String value) { 430 Element element = parent.addElement(newElementName); 431 if (value != null) { 432 element.setText(value); 433 } 434 return element; 435 } 436 437 // TODO: please explain what is the use of the "editId" tag here 438 protected static String getEditId(DocumentModel doc, CoreSession session, String userName) { 439 StringBuilder sb = new StringBuilder(); 440 441 if (doc != null) { 442 sb.append(doc.getId()); 443 } else { 444 sb.append("NewDocument"); 445 } 446 sb.append('-'); 447 sb.append(session.getRepositoryName()); 448 sb.append('-'); 449 sb.append(userName); 450 Calendar modified = null; 451 if (doc != null) { 452 try { 453 modified = (Calendar) doc.getProperty(DUBLINCORE_SCHEMA, MODIFIED_FIELD); 454 } catch (PropertyException e) { 455 modified = null; 456 } 457 } 458 if (modified == null) { 459 modified = Calendar.getInstance(); 460 } 461 sb.append('-'); 462 sb.append(modified.getTimeInMillis()); 463 return sb.toString(); 464 } 465 466 // 467 // Methods to check whether or not to display live edit links 468 // 469 470 /** 471 * @deprecated use {@link #isLiveEditable(DocumentModel doc, String blobXpath)} 472 */ 473 @Deprecated 474 public boolean isLiveEditable(Blob blob) { 475 if (blob == null) { 476 return false; 477 } 478 String mimetype = blob.getMimeType(); 479 return isMimeTypeLiveEditable(mimetype); 480 } 481 482 /** 483 * @param document the document to edit. 484 * @param blobXPath XPath to the blob property 485 * @return true if the document is immutable and the blob's mime type is supported, false otherwise. 486 * @since 5.4 487 */ 488 public boolean isLiveEditable(DocumentModel document, Blob blob) { 489 if (document.isImmutable()) { 490 return false; 491 } 492 // NXP-14476: Testing lifecycle state is part of the "mutable_document" filter 493 if (document.getCurrentLifeCycleState().equals(LifeCycleConstants.DELETED_STATE)) { 494 return false; 495 } 496 if (blob == null) { 497 return false; 498 } 499 String mimetype = blob.getMimeType(); 500 return isMimeTypeLiveEditable(mimetype); 501 } 502 503 public boolean isMimeTypeLiveEditable(Blob blob) { 504 if (blob == null) { 505 return false; 506 } 507 String mimetype = blob.getMimeType(); 508 return isMimeTypeLiveEditable(mimetype); 509 } 510 511 public boolean isMimeTypeLiveEditable(String mimetype) { 512 513 Boolean isEditable = cachedEditableStates.get(mimetype); 514 if (isEditable == null) { 515 516 if (liveEditClientConfig.getLiveEditConfigurationPolicy().equals(LiveEditClientConfig.LE_CONFIG_CLIENTSIDE)) { 517 // only trust client config 518 isEditable = liveEditClientConfig.isMimeTypeLiveEditable(mimetype); 519 cachedEditableStates.put(mimetype, isEditable); 520 return isEditable; 521 } 522 523 MimetypeEntry mimetypeEntry = getMimetypeRegistry().getMimetypeEntryByMimeType(mimetype); 524 if (mimetypeEntry == null) { 525 isEditable = Boolean.FALSE; 526 } else { 527 isEditable = mimetypeEntry.isOnlineEditable(); 528 } 529 530 if (liveEditClientConfig.getLiveEditConfigurationPolicy().equals(LiveEditClientConfig.LE_CONFIG_BOTHSIDES)) { 531 boolean isEditableOnClient = liveEditClientConfig.isMimeTypeLiveEditable(mimetype); 532 isEditable = isEditable && isEditableOnClient; 533 } 534 cachedEditableStates.put(mimetype, isEditable); 535 } 536 return isEditable; 537 } 538 539 @Factory(value = "msword_liveeditable", scope = ScopeType.SESSION) 540 public boolean isMSWordLiveEdititable() { 541 return isMimeTypeLiveEditable("application/msword"); 542 } 543 544 @Factory(value = "msexcel_liveeditable", scope = ScopeType.SESSION) 545 public boolean isMSExcelLiveEdititable() { 546 return isMimeTypeLiveEditable("application/vnd.ms-excel"); 547 } 548 549 @Factory(value = "mspowerpoint_liveeditable", scope = ScopeType.SESSION) 550 public boolean isMSPowerpointLiveEdititable() { 551 return isMimeTypeLiveEditable("application/vnd.ms-powerpoint"); 552 } 553 554 @Factory(value = "ootext_liveeditable", scope = ScopeType.SESSION) 555 public boolean isOOTextLiveEdititable() { 556 return isMimeTypeLiveEditable("application/vnd.oasis.opendocument.text"); 557 } 558 559 @Factory(value = "oocalc_liveeditable", scope = ScopeType.SESSION) 560 public boolean isOOCalcLiveEdititable() { 561 return isMimeTypeLiveEditable("application/vnd.oasis.opendocument.spreadsheet"); 562 } 563 564 @Factory(value = "oopresentation_liveeditable", scope = ScopeType.SESSION) 565 public boolean isOOPresentationLiveEdititable() { 566 return isMimeTypeLiveEditable("application/vnd.oasis.opendocument.presentation"); 567 } 568 569 public boolean isCurrentDocumentLiveEditable() { 570 return isDocumentLiveEditable(navigationContext.getCurrentDocument(), DEFAULT_SCHEMA, DEFAULT_BLOB_FIELD); 571 } 572 573 public boolean isCurrentDocumentLiveEditable(String schemaName, String fieldName) { 574 return isDocumentLiveEditable(navigationContext.getCurrentDocument(), schemaName, fieldName); 575 } 576 577 public boolean isCurrentDocumentLiveEditable(String propertyName) { 578 return isDocumentLiveEditable(navigationContext.getCurrentDocument(), propertyName); 579 } 580 581 public boolean isDocumentLiveEditable(DocumentModel documentModel, String schemaName, String fieldName) 582 { 583 return isDocumentLiveEditable(documentModel, schemaName + ":" + fieldName); 584 } 585 586 public boolean isDocumentLiveEditable(DocumentModel documentModel, String propertyName) { 587 if (documentModel == null) { 588 log.warn("cannot check live editable state of null DocumentModel"); 589 return false; 590 } 591 592 // NXP-14476: Testing lifecycle state is part of the "mutable_document" filter 593 if (documentModel.getCurrentLifeCycleState().equals(LifeCycleConstants.DELETED_STATE)) { 594 return false; 595 } 596 597 // check Client browser config 598 if (!liveEditClientConfig.isLiveEditInstalled()) { 599 return false; 600 } 601 602 String cacheKey = documentModel.getRef() + "__" + propertyName; 603 Boolean cachedEditableBlob = cachedEditableBlobs.get(cacheKey); 604 if (cachedEditableBlob == null) { 605 606 if (documentModel.hasFacet(FacetNames.IMMUTABLE)) { 607 return cacheBlobToFalse(cacheKey); 608 } 609 610 if (!documentManager.hasPermission(documentModel.getRef(), SecurityConstants.WRITE_PROPERTIES)) { 611 // the lock state is check as a extension to the 612 // SecurityPolicyManager 613 return cacheBlobToFalse(cacheKey); 614 } 615 616 Blob blob; 617 try { 618 blob = documentModel.getProperty(propertyName).getValue(Blob.class); 619 } catch (PropertyException e) { 620 // this document cannot host a live editable blob is the 621 // requested property, ignore 622 return cacheBlobToFalse(cacheKey); 623 } 624 cachedEditableBlob = isLiveEditable(blob); 625 cachedEditableBlobs.put(cacheKey, cachedEditableBlob); 626 } 627 return cachedEditableBlob; 628 } 629 630 protected boolean cacheBlobToFalse(String cacheKey) { 631 cachedEditableBlobs.put(cacheKey, Boolean.FALSE); 632 return false; 633 } 634 635 protected MimetypeRegistry getMimetypeRegistry() { 636 if (mimetypeRegistry == null) { 637 mimetypeRegistry = Framework.getService(MimetypeRegistry.class); 638 } 639 return mimetypeRegistry; 640 } 641 642}