Uploaded image for project: 'Alfresco'
  1. Alfresco
  2. ALF-21875

NullPointerException in TagScopePropertyMethodInterceptor due to Script API conversion of null ContentData

    Details

    • Type: Bug
    • Status: New (View Workflow)
    • Priority: Critical
    • Resolution: Unresolved
    • Affects Version/s: Community Edition 201605 GA
    • Fix Version/s: None
    • Component/s: JavaScript API
    • Security Level: external (External user)
    • Labels:
      None
    • Security Severity:
      None
    • Triage:
      ACE

      Description

      Due to how tag scope caches are processed and content properties are converted in the Script API layer, the TagScopePropertyMethodInterceptor may trigger a NullPointerException when the info about a site is retrieved AFTER the node representing the site has been saved via the Script API.
      This is a rather obscure issue that involves multiple participants / steps to occur. This is in part due to the asynchronous nature of tag scope cache processing.

      Steps to reproduce:
      1) Create a site "tagscopebugtest"
      2) Upload a document
      3) Apply a tag to the document
      4) Delete the document
      5) Wait until the job "taggingStartupJobDetail" has run (60 minute interval) or trigger it manually via the Support Tools addon "Scheduled Tasks" tool in the Repository-tier Admin Console
      6) Execute the following script (as a web script, action or via the JavaScript Console addon):

      var site, siteNode;
      
      site = siteService.getSite('tagscopebugtest');
      siteNode = site.node;
      siteNode.properties.title = 'Test';
      siteNode.save();
      
      site = siteService.getSite('tagscopebugtest');
      

      Expectation: The script executes successfully - the title of the site is updated
      Observation: The script fails with an exception - the stacktrace of the exception includes the following cause

      Caused by: java.lang.NullPointerException at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187) at com.google.common.cache.LocalCache.getIfPresent(LocalCache.java:3953) at com.google.common.cache.LocalCache$LocalManualCache.getIfPresent(LocalCache.java:4758) at org.alfresco.repo.cache.DefaultSimpleCache.get(DefaultSimpleCache.java:133) at org.alfresco.repo.cache.TransactionalCache.getSharedCacheValue(TransactionalCache.java:454) at org.alfresco.repo.cache.TransactionalCache.get(TransactionalCache.java:641) at org.alfresco.repo.tagging.TagScopePropertyMethodInterceptor.getTagSummary(TagScopePropertyMethodInterceptor.java:186) at org.alfresco.repo.tagging.TagScopePropertyMethodInterceptor.invoke(TagScopePropertyMethodInterceptor.java:127) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.alfresco.repo.node.MLPropertyInterceptor.invoke(MLPropertyInterceptor.java:181) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.alfresco.repo.node.NodeRefPropertyMethodInterceptor.invoke(NodeRefPropertyMethodInterceptor.java:219) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) at com.sun.proxy.$Proxy20.getProperties(Unknown Source) at sun.reflect.GeneratedMethodAccessor242.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:198) at com.sun.proxy.$Proxy20.getProperties(Unknown Source) at org.alfresco.repo.site.SiteServiceImpl.createSiteInfo(SiteServiceImpl.java:1251) at org.alfresco.repo.site.SiteServiceImpl.getSiteImpl(SiteServiceImpl.java:1359) at org.alfresco.repo.site.SiteServiceImpl.getSite(SiteServiceImpl.java:1340) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) at net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:80) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.alfresco.repo.security.permissions.impl.ExceptionTranslatorMethodInterceptor.invoke(ExceptionTranslatorMethodInterceptor.java:53) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.alfresco.repo.audit.AuditMethodInterceptor.invoke(AuditMethodInterceptor.java:166) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.alfresco.repo.transaction.CheckTransactionAdvice.invoke(CheckTransactionAdvice.java:54) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.alfresco.repo.transaction.RetryingTransactionAdvice$1.execute(RetryingTransactionAdvice.java:71) at org.alfresco.repo.transaction.RetryingTransactionHelper.doInTransaction(RetryingTransactionHelper.java:464) at org.alfresco.repo.transaction.RetryingTransactionAdvice.invoke(RetryingTransactionAdvice.java:74) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) at com.sun.proxy.$Proxy111.getSite(Unknown Source) at org.alfresco.repo.site.script.ScriptSiteService$1.doWork(ScriptSiteService.java:354) at org.alfresco.repo.site.script.ScriptSiteService$1.doWork(ScriptSiteService.java:1) at org.alfresco.repo.security.authentication.AuthenticationUtil.runAs(AuthenticationUtil.java:555) at org.alfresco.repo.site.script.ScriptSiteService.getSite(ScriptSiteService.java:350)
      

      My analysis as to the cause of the issue:

      • the class UpdateTagScopesActionExecuter (triggered by the job) explicitly sets the value of cm:tagScopeCache to null when no tags are present in the scope (instead of setting it to null, it would be more appropriate just to remove the property)
      • the ScriptNode.save() operation iterates over all set properties on the scriptable properties map and collects them for a call to NodeService.setProperties()
      • the ContentAwareScriptableQNameMap.get() operation will convert a null value for any d:content property to a dummy ContentData object with a contentUrl set as null
      • NodeService.setProperties() will result in an update to the nodeProperties cache with the dummy ContentData as the property value of cm:tagScopeCache
      • TagScopePropertyMethodInterceptor.getTagSummary() only checks for a null value of the cm:tagScopeCache property, but not for a null value of the contentUrl inside of any ContentData instance, resulting in null to be passed on to the cache lookup which subsequently fails

      In my opinion there are multiple issues here:

      • in order to remove a property, the NodeService.removeProperty operation must be used instead of setting the property to null (honestly, a null value should never be persisted in the alf_node_properties table in any way, but unfortunately Alfresco NodeService has always allowed that and it is hard to change now)
      • the Script API must not leak pre-emptively created value objects (provided for better developer experience) into the persistence layer unless actually used/modified

        Attachments

          Issue Links

            Activity

            Hide
            afaust Axel Faust added a comment -

            Note: The call to getSite() isn't actually necessary to trigger the issue. Further testing has shown that the IncompleteNodeTagger will call NodeService.getProperties() later in the transaction and cause the same issue in TagScopePropertyMethodInterceptor.

            It is very likely that Enterprise Edition will not show this behaviour due to the difference in cache implementations. The Hazelcast IMap instance may be ignorant of null cache keys and just not throw a NullPointerException.

            Show
            afaust Axel Faust added a comment - Note: The call to getSite() isn't actually necessary to trigger the issue. Further testing has shown that the IncompleteNodeTagger will call NodeService.getProperties() later in the transaction and cause the same issue in TagScopePropertyMethodInterceptor. It is very likely that Enterprise Edition will not show this behaviour due to the difference in cache implementations. The Hazelcast IMap instance may be ignorant of null cache keys and just not throw a NullPointerException.
            Hide
            idwright Ian Wright added a comment -

            In this situation it is not possible to edit site details - see https://github.com/malariagen/alfresco-discussions/wiki/Troubleshooting

            Show
            idwright Ian Wright added a comment - In this situation it is not possible to edit site details - see https://github.com/malariagen/alfresco-discussions/wiki/Troubleshooting

              People

              • Assignee:
                repositoryteam Repository Team
                Reporter:
                afaust Axel Faust
              • Votes:
                0 Vote for this issue
                Watchers:
                2 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Date of First Response: