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