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