mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	Compare commits
	
		
			101 Commits
		
	
	
		
			feature/AC
			...
			10.7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					742715f738 | ||
| 
						 | 
					8bf474941f | ||
| 
						 | 
					5bd83fff0e | ||
| 
						 | 
					3b2e81b6b7 | ||
| 
						 | 
					124d8e5277 | ||
| 
						 | 
					1d28d28623 | ||
| 
						 | 
					be580313ef | ||
| 
						 | 
					cff3408f27 | ||
| 
						 | 
					8a6c64c4b8 | ||
| 
						 | 
					38cb0aba92 | ||
| 
						 | 
					56d6957935 | ||
| 
						 | 
					84d9916788 | ||
| 
						 | 
					22ef4441f3 | ||
| 
						 | 
					2f77c8b304 | ||
| 
						 | 
					e34165b72e | ||
| 
						 | 
					f7f4f70990 | ||
| 
						 | 
					e293976b58 | ||
| 
						 | 
					747f2c8919 | ||
| 
						 | 
					f445646045 | ||
| 
						 | 
					1e02870774 | ||
| 
						 | 
					9cb2b23ef5 | ||
| 
						 | 
					7db90ee90c | ||
| 
						 | 
					9d0106e000 | ||
| 
						 | 
					26c991a563 | ||
| 
						 | 
					ddfabba4ba | ||
| 
						 | 
					ab7d757412 | ||
| 
						 | 
					fe028f5b85 | ||
| 
						 | 
					2baf1b9c91 | ||
| 
						 | 
					3818f94268 | ||
| 
						 | 
					53b41068d4 | ||
| 
						 | 
					c302bc31ff | ||
| 
						 | 
					9a30044064 | ||
| 
						 | 
					41edced9f1 | ||
| 
						 | 
					15ca9e21be | ||
| 
						 | 
					36bf6d2f81 | ||
| 
						 | 
					cf01f167ae | ||
| 
						 | 
					4c94059bbf | ||
| 
						 | 
					5efe11008d | ||
| 
						 | 
					3225eefd0b | ||
| 
						 | 
					b6de89aa8d | ||
| 
						 | 
					4cc1c10ce5 | ||
| 
						 | 
					7641c128c5 | ||
| 
						 | 
					a62ad8715e | ||
| 
						 | 
					542f189907 | ||
| 
						 | 
					a006b5acaf | ||
| 
						 | 
					c2e516b69a | ||
| 
						 | 
					2c5044896b | ||
| 
						 | 
					24454afe6b | ||
| 
						 | 
					aec55ed8a6 | ||
| 
						 | 
					ddd5a4ae48 | ||
| 
						 | 
					e523245a10 | ||
| 
						 | 
					c4217b32fb | ||
| 
						 | 
					2fbd21076f | ||
| 
						 | 
					cb1419b140 | ||
| 
						 | 
					9a6c6f2ee9 | ||
| 
						 | 
					eaff930456 | ||
| 
						 | 
					4a03e8cc98 | ||
| 
						 | 
					0eaeea35f8 | ||
| 
						 | 
					f4c632c26b | ||
| 
						 | 
					3c96ed9482 | ||
| 
						 | 
					0141284b37 | ||
| 
						 | 
					2c8ed7f4b5 | ||
| 
						 | 
					decbe6b285 | ||
| 
						 | 
					f0f538bad0 | ||
| 
						 | 
					c0aaf75284 | ||
| 
						 | 
					7f5889474e | ||
| 
						 | 
					11c6125760 | ||
| 
						 | 
					ba4effc6ec | ||
| 
						 | 
					3f52aec2dc | ||
| 
						 | 
					eb3df043be | ||
| 
						 | 
					c5aed167f4 | ||
| 
						 | 
					a477c19e9a | ||
| 
						 | 
					1497362d3e | ||
| 
						 | 
					27e2775e40 | ||
| 
						 | 
					f2ecce0f46 | ||
| 
						 | 
					0ad54cbf77 | ||
| 
						 | 
					3e3cd479c2 | ||
| 
						 | 
					b9b41a10e8 | ||
| 
						 | 
					664d0b9704 | ||
| 
						 | 
					1493b02d8d | ||
| 
						 | 
					70c1a1279c | ||
| 
						 | 
					f0638e8d7d | ||
| 
						 | 
					983dd47c35 | ||
| 
						 | 
					24d092cb02 | ||
| 
						 | 
					ddb299ab03 | ||
| 
						 | 
					19d214fcb0 | ||
| 
						 | 
					870ff8cc64 | ||
| 
						 | 
					aed08fe5d9 | ||
| 
						 | 
					a3b0541560 | ||
| 
						 | 
					2f6c5614c3 | ||
| 
						 | 
					82d3828351 | ||
| 
						 | 
					c986498481 | ||
| 
						 | 
					c93d81379e | ||
| 
						 | 
					d348e0b72d | ||
| 
						 | 
					dc5e7405cc | ||
| 
						 | 
					3c8bb7f154 | ||
| 
						 | 
					bb8d42d23c | ||
| 
						 | 
					9c1aa53819 | ||
| 
						 | 
					885f4a49a5 | ||
| 
						 | 
					9989ec3260 | ||
| 
						 | 
					78ad14b696 | 
							
								
								
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							@@ -54,7 +54,7 @@ jobs:
 | 
			
		||||
      before_script:
 | 
			
		||||
        - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.1 postgres -c 'max_connections=300'
 | 
			
		||||
        - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.10
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.4.0
 | 
			
		||||
      script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContext01TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
 | 
			
		||||
    - name: "Repository - AppContext02TestSuite"
 | 
			
		||||
@@ -67,14 +67,14 @@ jobs:
 | 
			
		||||
      before_script:
 | 
			
		||||
        - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.1 postgres -c 'max_connections=300'
 | 
			
		||||
        - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.10
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.4.0
 | 
			
		||||
      script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContext03TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
 | 
			
		||||
    - name: "Repository - AppContext04TestSuite"
 | 
			
		||||
      before_script:
 | 
			
		||||
        - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.1 postgres -c 'max_connections=300'
 | 
			
		||||
        - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.10
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.4.0
 | 
			
		||||
      script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContext04TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
 | 
			
		||||
    - name: "Repository - AppContext05TestSuite"
 | 
			
		||||
@@ -91,21 +91,21 @@ jobs:
 | 
			
		||||
      before_script:
 | 
			
		||||
        - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.1 postgres -c 'max_connections=300'
 | 
			
		||||
        - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.10
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.4.0
 | 
			
		||||
      script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContext06TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
 | 
			
		||||
    - name: "Repository - AppContextExtraTestSuite"
 | 
			
		||||
      before_script:
 | 
			
		||||
        - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.1 postgres -c 'max_connections=300'
 | 
			
		||||
        - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.10
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.4.0
 | 
			
		||||
      script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContextExtraTestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
 | 
			
		||||
    - name: "Repository - MiscContextTestSuite"
 | 
			
		||||
      before_script:
 | 
			
		||||
        - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.1 postgres -c 'max_connections=300'
 | 
			
		||||
        - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.10
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.4.0
 | 
			
		||||
      script: travis_wait 20 mvn -B test -pl repository -Dtest=MiscContextTestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
 | 
			
		||||
    - name: "Repository - SearchTestSuite"
 | 
			
		||||
@@ -187,21 +187,21 @@ jobs:
 | 
			
		||||
      before_script:
 | 
			
		||||
        - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.1 postgres -c 'max_connections=300'
 | 
			
		||||
        - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.10
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.4.0
 | 
			
		||||
      script: travis_wait 20 mvn -B test -pl remote-api -Dtest=AppContext02TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
 | 
			
		||||
    - name: "Remote-api - AppContext03TestSuite"
 | 
			
		||||
      before_script:
 | 
			
		||||
        - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.1 postgres -c 'max_connections=300'
 | 
			
		||||
        - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.10
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.4.0
 | 
			
		||||
      script: travis_wait 20 mvn -B test -pl remote-api -Dtest=AppContext03TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
 | 
			
		||||
    - name: "Remote-api - AppContext04TestSuite"
 | 
			
		||||
      before_script:
 | 
			
		||||
        - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.1 postgres -c 'max_connections=300'
 | 
			
		||||
        - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.10
 | 
			
		||||
        - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.4.0
 | 
			
		||||
      script: travis_wait 20 mvn -B test -pl remote-api -Dtest=AppContext04TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
 | 
			
		||||
    - name: "Remote-api - AppContextExtraTestSuite"
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
      <version>8.424-SNAPSHOT</version>
 | 
			
		||||
      <version>10.7</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <dependencies>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ package org.alfresco.httpclient;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.security.AlgorithmParameters;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
@@ -32,14 +31,11 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 | 
			
		||||
import org.alfresco.encryption.AlfrescoKeyStore;
 | 
			
		||||
import org.alfresco.encryption.AlfrescoKeyStoreImpl;
 | 
			
		||||
import org.alfresco.encryption.EncryptionUtils;
 | 
			
		||||
import org.alfresco.encryption.Encryptor;
 | 
			
		||||
import org.alfresco.encryption.KeyProvider;
 | 
			
		||||
import org.alfresco.encryption.KeyResourceLoader;
 | 
			
		||||
import org.alfresco.encryption.KeyStoreParameters;
 | 
			
		||||
import org.alfresco.encryption.ssl.AuthSSLProtocolSocketFactory;
 | 
			
		||||
import org.alfresco.encryption.ssl.SSLEncryptionParameters;
 | 
			
		||||
import org.alfresco.error.AlfrescoRuntimeException;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
 | 
			
		||||
import org.apache.commons.httpclient.HostConfiguration;
 | 
			
		||||
import org.apache.commons.httpclient.HttpClient;
 | 
			
		||||
@@ -53,8 +49,6 @@ import org.apache.commons.httpclient.SimpleHttpConnectionManager;
 | 
			
		||||
import org.apache.commons.httpclient.URI;
 | 
			
		||||
import org.apache.commons.httpclient.URIException;
 | 
			
		||||
import org.apache.commons.httpclient.cookie.CookiePolicy;
 | 
			
		||||
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
 | 
			
		||||
import org.apache.commons.httpclient.methods.PostMethod;
 | 
			
		||||
import org.apache.commons.httpclient.params.DefaultHttpParams;
 | 
			
		||||
import org.apache.commons.httpclient.params.DefaultHttpParamsFactory;
 | 
			
		||||
import org.apache.commons.httpclient.params.HttpClientParams;
 | 
			
		||||
@@ -75,23 +69,25 @@ import org.apache.commons.logging.LogFactory;
 | 
			
		||||
 */
 | 
			
		||||
public class HttpClientFactory
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Communication type for HttpClient:
 | 
			
		||||
     * - NONE is plain http
 | 
			
		||||
     * - SECRET is plain http with a shared secret via request header
 | 
			
		||||
     * - HTTPS is mTLS with client authentication (certificates are required)
 | 
			
		||||
     */
 | 
			
		||||
    public static enum SecureCommsType
 | 
			
		||||
    {
 | 
			
		||||
        HTTPS, NONE;
 | 
			
		||||
        HTTPS, NONE, SECRET;
 | 
			
		||||
        
 | 
			
		||||
        public static SecureCommsType getType(String type)
 | 
			
		||||
        {
 | 
			
		||||
            if(type.equalsIgnoreCase("https"))
 | 
			
		||||
            switch (type.toLowerCase())
 | 
			
		||||
            {
 | 
			
		||||
                return HTTPS;
 | 
			
		||||
            }
 | 
			
		||||
            else if(type.equalsIgnoreCase("none"))
 | 
			
		||||
            {
 | 
			
		||||
                return NONE;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                throw new IllegalArgumentException("Invalid communications type");
 | 
			
		||||
                case "https": return HTTPS;
 | 
			
		||||
                case "none": return NONE;
 | 
			
		||||
                case "secret": return SECRET;
 | 
			
		||||
                default: throw new IllegalArgumentException("Invalid communications type");
 | 
			
		||||
                    
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
@@ -122,14 +118,24 @@ public class HttpClientFactory
 | 
			
		||||
 | 
			
		||||
    private int connectionTimeout = 0;
 | 
			
		||||
    
 | 
			
		||||
    // Shared secret parameters
 | 
			
		||||
    private String sharedSecret;
 | 
			
		||||
    private String sharedSecretHeader = DEFAULT_SHAREDSECRET_HEADER;
 | 
			
		||||
 | 
			
		||||
    // Default name for HTTP Request Header when using shared secret communication
 | 
			
		||||
    public static final String DEFAULT_SHAREDSECRET_HEADER = "X-Alfresco-Search-Secret";
 | 
			
		||||
    
 | 
			
		||||
    public HttpClientFactory()
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Default constructor for legacy subsystems.
 | 
			
		||||
     */
 | 
			
		||||
    public HttpClientFactory(SecureCommsType secureCommsType, SSLEncryptionParameters sslEncryptionParameters,
 | 
			
		||||
            KeyResourceLoader keyResourceLoader, KeyStoreParameters keyStoreParameters,
 | 
			
		||||
            MD5EncryptionParameters encryptionParameters, String host, int port, int sslPort, int maxTotalConnections,
 | 
			
		||||
            int maxHostConnections, int socketTimeout)
 | 
			
		||||
                KeyResourceLoader keyResourceLoader, KeyStoreParameters keyStoreParameters,
 | 
			
		||||
                MD5EncryptionParameters encryptionParameters, String host, int port, int sslPort,
 | 
			
		||||
                int maxTotalConnections, int maxHostConnections, int socketTimeout)
 | 
			
		||||
    {
 | 
			
		||||
        this.secureCommsType = secureCommsType;
 | 
			
		||||
        this.sslEncryptionParameters = sslEncryptionParameters;
 | 
			
		||||
@@ -145,6 +151,21 @@ public class HttpClientFactory
 | 
			
		||||
        init();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Recommended constructor for subsystems supporting Shared Secret communication.
 | 
			
		||||
     * This constructor supports Shared Secret ("secret") communication method additionally to the legacy ones: "none" and "https".
 | 
			
		||||
     */
 | 
			
		||||
    public HttpClientFactory(SecureCommsType secureCommsType, SSLEncryptionParameters sslEncryptionParameters,
 | 
			
		||||
                KeyResourceLoader keyResourceLoader, KeyStoreParameters keyStoreParameters,
 | 
			
		||||
                MD5EncryptionParameters encryptionParameters, String sharedSecret, String sharedSecretHeader, 
 | 
			
		||||
                String host, int port, int sslPort, int maxTotalConnections, int maxHostConnections, int socketTimeout)
 | 
			
		||||
    {
 | 
			
		||||
        this(secureCommsType, sslEncryptionParameters, keyResourceLoader, keyStoreParameters, encryptionParameters,
 | 
			
		||||
                    host, port, sslPort, maxTotalConnections, maxHostConnections, socketTimeout);
 | 
			
		||||
        this.sharedSecret = sharedSecret;
 | 
			
		||||
        this.sharedSecretHeader = sharedSecretHeader;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void init()
 | 
			
		||||
    {
 | 
			
		||||
        this.sslKeyStore = new AlfrescoKeyStoreImpl(sslEncryptionParameters.getKeyStoreParameters(),  keyResourceLoader);
 | 
			
		||||
@@ -272,10 +293,44 @@ public class HttpClientFactory
 | 
			
		||||
        this.connectionTimeout = connectionTimeout;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected HttpClient constructHttpClient()
 | 
			
		||||
    /**
 | 
			
		||||
     * Shared secret used for SECRET communication
 | 
			
		||||
     * @param secret shared secret word
 | 
			
		||||
     */
 | 
			
		||||
    public void setSharedSecret(String sharedSecret)
 | 
			
		||||
    {
 | 
			
		||||
        this.sharedSecret = sharedSecret;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Shared secret used for SECRET communication
 | 
			
		||||
     */
 | 
			
		||||
    public String getSharedSecret()
 | 
			
		||||
    {
 | 
			
		||||
        return sharedSecret;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * HTTP Request header used for SECRET communication
 | 
			
		||||
     * @param sharedSecretHeader HTTP Request header
 | 
			
		||||
     */
 | 
			
		||||
    public void setSharedSecretHeader(String sharedSecretHeader)
 | 
			
		||||
    {
 | 
			
		||||
        this.sharedSecretHeader = sharedSecretHeader;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @return HTTP Request header used for SECRET communication
 | 
			
		||||
     */
 | 
			
		||||
    public String getSharedSecretHeader()
 | 
			
		||||
    {
 | 
			
		||||
        return sharedSecretHeader;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected RequestHeadersHttpClient constructHttpClient()
 | 
			
		||||
    {
 | 
			
		||||
        MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
 | 
			
		||||
        HttpClient httpClient = new HttpClient(connectionManager);
 | 
			
		||||
        RequestHeadersHttpClient httpClient = new RequestHeadersHttpClient(connectionManager);
 | 
			
		||||
        HttpClientParams params = httpClient.getParams();
 | 
			
		||||
        params.setBooleanParameter(HttpConnectionParams.TCP_NODELAY, true);
 | 
			
		||||
        params.setBooleanParameter(HttpConnectionParams.STALE_CONNECTION_CHECK, true);
 | 
			
		||||
@@ -291,15 +346,15 @@ public class HttpClientFactory
 | 
			
		||||
        return httpClient;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected HttpClient getHttpsClient()
 | 
			
		||||
    protected RequestHeadersHttpClient getHttpsClient()
 | 
			
		||||
    {
 | 
			
		||||
       return getHttpsClient(host, sslPort);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected HttpClient getHttpsClient(String httpsHost, int httpsPort)
 | 
			
		||||
    protected RequestHeadersHttpClient getHttpsClient(String httpsHost, int httpsPort)
 | 
			
		||||
    {
 | 
			
		||||
        // Configure a custom SSL socket factory that will enforce mutual authentication
 | 
			
		||||
        HttpClient httpClient = constructHttpClient();
 | 
			
		||||
        RequestHeadersHttpClient httpClient = constructHttpClient();
 | 
			
		||||
        // Default port is 443 for the HostFactory, when including customised port (like 8983) the port name is skipped from "getHostURL" string
 | 
			
		||||
        HttpHostFactory hostFactory = new HttpHostFactory(new Protocol("https", sslSocketFactory, HttpsURL.DEFAULT_PORT));
 | 
			
		||||
        httpClient.setHostConfiguration(new HostConfigurationWithHostFactory(hostFactory));
 | 
			
		||||
@@ -307,28 +362,54 @@ public class HttpClientFactory
 | 
			
		||||
        return httpClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected HttpClient getDefaultHttpClient()
 | 
			
		||||
    protected RequestHeadersHttpClient getDefaultHttpClient()
 | 
			
		||||
    {
 | 
			
		||||
        return getDefaultHttpClient(host, port);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected HttpClient getDefaultHttpClient(String httpHost, int httpPort)
 | 
			
		||||
    protected RequestHeadersHttpClient getDefaultHttpClient(String httpHost, int httpPort)
 | 
			
		||||
    {
 | 
			
		||||
        HttpClient httpClient = constructHttpClient();
 | 
			
		||||
        RequestHeadersHttpClient httpClient = constructHttpClient();
 | 
			
		||||
        httpClient.getHostConfiguration().setHost(httpHost, httpPort);
 | 
			
		||||
        return httpClient;
 | 
			
		||||
    }
 | 
			
		||||
       
 | 
			
		||||
    /**
 | 
			
		||||
     * Build HTTP Client using default headers
 | 
			
		||||
     * @return RequestHeadersHttpClient including default header for shared secret method
 | 
			
		||||
     */
 | 
			
		||||
    protected RequestHeadersHttpClient constructSharedSecretHttpClient()
 | 
			
		||||
    {
 | 
			
		||||
        RequestHeadersHttpClient client = constructHttpClient();
 | 
			
		||||
        client.setDefaultHeaders(Map.of(sharedSecretHeader, sharedSecret));
 | 
			
		||||
        return client;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected RequestHeadersHttpClient getSharedSecretHttpClient()
 | 
			
		||||
    {
 | 
			
		||||
        return getSharedSecretHttpClient(host, port);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected RequestHeadersHttpClient getSharedSecretHttpClient(String httpHost, int httpPort)
 | 
			
		||||
    {
 | 
			
		||||
        RequestHeadersHttpClient httpClient = constructSharedSecretHttpClient();
 | 
			
		||||
        httpClient.getHostConfiguration().setHost(httpHost, httpPort);
 | 
			
		||||
        return httpClient;       
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected AlfrescoHttpClient getAlfrescoHttpsClient()
 | 
			
		||||
    {
 | 
			
		||||
        AlfrescoHttpClient repoClient = new HttpsClient(getHttpsClient());
 | 
			
		||||
        return repoClient;
 | 
			
		||||
        return new HttpsClient(getHttpsClient());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected AlfrescoHttpClient getAlfrescoHttpClient()
 | 
			
		||||
    {
 | 
			
		||||
        AlfrescoHttpClient repoClient = new DefaultHttpClient(getDefaultHttpClient());
 | 
			
		||||
        return repoClient;
 | 
			
		||||
        return new DefaultHttpClient(getDefaultHttpClient());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected AlfrescoHttpClient getAlfrescoSharedSecretClient()
 | 
			
		||||
    {
 | 
			
		||||
        return new DefaultHttpClient(getSharedSecretHttpClient());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected HttpClient getMD5HttpClient(String host, int port)
 | 
			
		||||
@@ -341,66 +422,37 @@ public class HttpClientFactory
 | 
			
		||||
    
 | 
			
		||||
    public AlfrescoHttpClient getRepoClient(String host, int port)
 | 
			
		||||
    {
 | 
			
		||||
        AlfrescoHttpClient repoClient = null;
 | 
			
		||||
 | 
			
		||||
        if(secureCommsType == SecureCommsType.HTTPS)
 | 
			
		||||
        switch (secureCommsType)
 | 
			
		||||
        {
 | 
			
		||||
            repoClient = getAlfrescoHttpsClient();
 | 
			
		||||
            case HTTPS: return getAlfrescoHttpsClient();
 | 
			
		||||
            case NONE: return getAlfrescoHttpClient();
 | 
			
		||||
            case SECRET: return getAlfrescoSharedSecretClient();
 | 
			
		||||
            default: throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in [solr|alfresco].secureComms, should be 'ssl', 'none' or 'secret'");
 | 
			
		||||
        }
 | 
			
		||||
        else if(secureCommsType == SecureCommsType.NONE)
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
    public RequestHeadersHttpClient getHttpClient()
 | 
			
		||||
    {       
 | 
			
		||||
        switch (secureCommsType)
 | 
			
		||||
        {
 | 
			
		||||
            repoClient = getAlfrescoHttpClient();
 | 
			
		||||
            case HTTPS: return getHttpsClient();
 | 
			
		||||
            case NONE: return getDefaultHttpClient();
 | 
			
		||||
            case SECRET: return getSharedSecretHttpClient();
 | 
			
		||||
            default: throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in [solr|alfresco].secureComms, should be 'ssl', 'none' or 'secret'");
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in alfresco.secureComms, should be 'ssl'or 'none'");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return repoClient;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public HttpClient getHttpClient()
 | 
			
		||||
    public RequestHeadersHttpClient getHttpClient(String host, int port)
 | 
			
		||||
    {
 | 
			
		||||
        HttpClient httpClient = null;
 | 
			
		||||
 | 
			
		||||
        if(secureCommsType == SecureCommsType.HTTPS)
 | 
			
		||||
        switch (secureCommsType)
 | 
			
		||||
        {
 | 
			
		||||
            httpClient = getHttpsClient();
 | 
			
		||||
            case HTTPS: return getHttpsClient(host, port);
 | 
			
		||||
            case NONE: return getDefaultHttpClient(host, port);
 | 
			
		||||
            case SECRET: return getSharedSecretHttpClient(host, port);
 | 
			
		||||
            default: throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in [solr|alfresco].secureComms, should be 'ssl', 'none' or 'secret'");
 | 
			
		||||
        }
 | 
			
		||||
        else if(secureCommsType == SecureCommsType.NONE)
 | 
			
		||||
        {
 | 
			
		||||
            httpClient = getDefaultHttpClient();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in alfresco.secureComms, should be 'ssl'or 'none'");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return httpClient;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public HttpClient getHttpClient(String host, int port)
 | 
			
		||||
    {
 | 
			
		||||
        HttpClient httpClient = null;
 | 
			
		||||
 | 
			
		||||
        if(secureCommsType == SecureCommsType.HTTPS)
 | 
			
		||||
        {
 | 
			
		||||
            httpClient = getHttpsClient(host, port);
 | 
			
		||||
        }
 | 
			
		||||
        else if(secureCommsType == SecureCommsType.NONE)
 | 
			
		||||
        {
 | 
			
		||||
            httpClient = getDefaultHttpClient(host, port);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in alfresco.secureComms, should be 'ssl'or 'none'");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return httpClient;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * A secure client connection to the repository.
 | 
			
		||||
     * 
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,87 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2005-2021 Alfresco Software Limited.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of Alfresco
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.httpclient;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.httpclient.HostConfiguration;
 | 
			
		||||
import org.apache.commons.httpclient.HttpClient;
 | 
			
		||||
import org.apache.commons.httpclient.HttpException;
 | 
			
		||||
import org.apache.commons.httpclient.HttpMethod;
 | 
			
		||||
import org.apache.commons.httpclient.HttpState;
 | 
			
		||||
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Since Apache HttpClient 3.1 doesn't support including custom headers by default,
 | 
			
		||||
 * this class is adding that custom headers every time a method is invoked.
 | 
			
		||||
 */
 | 
			
		||||
public class RequestHeadersHttpClient extends HttpClient
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
    private Map<String, String> defaultHeaders;
 | 
			
		||||
    
 | 
			
		||||
    public RequestHeadersHttpClient(MultiThreadedHttpConnectionManager connectionManager)
 | 
			
		||||
    {
 | 
			
		||||
        super(connectionManager);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Map<String, String> getDefaultHeaders()
 | 
			
		||||
    {
 | 
			
		||||
        return defaultHeaders;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setDefaultHeaders(Map<String, String> defaultHeaders)
 | 
			
		||||
    {
 | 
			
		||||
        this.defaultHeaders = defaultHeaders;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private void addDefaultHeaders(HttpMethod method)
 | 
			
		||||
    {
 | 
			
		||||
        if (defaultHeaders != null)
 | 
			
		||||
        {
 | 
			
		||||
            defaultHeaders.forEach((k,v) -> {
 | 
			
		||||
                method.addRequestHeader(k, v);
 | 
			
		||||
            });
 | 
			
		||||
        }        
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    public int executeMethod(HttpMethod method) throws IOException, HttpException
 | 
			
		||||
    {
 | 
			
		||||
        addDefaultHeaders(method);
 | 
			
		||||
        return super.executeMethod(method);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int executeMethod(HostConfiguration hostConfiguration, HttpMethod method) throws IOException, HttpException
 | 
			
		||||
    {
 | 
			
		||||
        addDefaultHeaders(method);
 | 
			
		||||
        return super.executeMethod(hostConfiguration, method);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int executeMethod(HostConfiguration hostconfig, HttpMethod method, HttpState state)
 | 
			
		||||
                throws IOException, HttpException
 | 
			
		||||
    {
 | 
			
		||||
        addDefaultHeaders(method);
 | 
			
		||||
        return super.executeMethod(hostconfig, method, state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
@@ -167,7 +167,7 @@
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.fasterxml.woodstox</groupId>
 | 
			
		||||
            <artifactId>woodstox-core</artifactId>
 | 
			
		||||
            <version>6.2.4</version>
 | 
			
		||||
            <version>6.2.6</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <!-- the cxf libs were updated, see dependencyManagement section -->
 | 
			
		||||
@@ -283,6 +283,31 @@
 | 
			
		||||
                    <groupId>com.sun.activation</groupId>
 | 
			
		||||
                    <artifactId>javax.activation</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
                <!-- No longer needed -->
 | 
			
		||||
                <exclusion>
 | 
			
		||||
                    <groupId>org.apache.pdfbox</groupId>
 | 
			
		||||
                    <artifactId>pdfbox</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
                    <groupId>org.apache.pdfbox</groupId>
 | 
			
		||||
                    <artifactId>pdfbox-tools</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
                    <groupId>org.apache.pdfbox</groupId>
 | 
			
		||||
                    <artifactId>preflight</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
                    <groupId>org.apache.pdfbox</groupId>
 | 
			
		||||
                    <artifactId>jempbox</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
                    <groupId>org.apache.pdfbox</groupId>
 | 
			
		||||
                    <artifactId>xmpbox</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
                    <groupId>org.apache.pdfbox</groupId>
 | 
			
		||||
                    <artifactId>jbig2-imageio</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
            </exclusions>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -60,12 +60,15 @@ public abstract class ConfigScheduler<Data>
 | 
			
		||||
        // Synchronized has little effect in normal operation, but on laptops that are suspended, there can be a number
 | 
			
		||||
        // of Threads calling execute concurrently without it, resulting in errors in the log. Theoretically possible in
 | 
			
		||||
        // production but not very likely.
 | 
			
		||||
        public synchronized void execute(JobExecutionContext context) throws JobExecutionException
 | 
			
		||||
        public void execute(JobExecutionContext context) throws JobExecutionException
 | 
			
		||||
        {
 | 
			
		||||
            JobDataMap dataMap = context.getJobDetail().getJobDataMap();
 | 
			
		||||
            ConfigScheduler configScheduler = (ConfigScheduler)dataMap.get(CONFIG_SCHEDULER);
 | 
			
		||||
            boolean successReadingConfig = configScheduler.readConfigAndReplace(true);
 | 
			
		||||
            configScheduler.changeScheduleOnStateChange(successReadingConfig);
 | 
			
		||||
            synchronized (configScheduler)
 | 
			
		||||
            {
 | 
			
		||||
                boolean successReadingConfig = configScheduler.readConfigAndReplace(true);
 | 
			
		||||
                configScheduler.changeScheduleOnStateChange(successReadingConfig);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,6 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-packaging</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
</project>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# Fetch image based on Tomcat 9.0, Java 11 and Centos 8
 | 
			
		||||
# More infos about this image: https://github.com/Alfresco/alfresco-docker-base-tomcat
 | 
			
		||||
FROM alfresco/alfresco-base-tomcat:9.0.41-java-11-openjdk-centos-8
 | 
			
		||||
FROM alfresco/alfresco-base-tomcat:9.0.45-java-11-centos-8
 | 
			
		||||
 | 
			
		||||
# Set default docker_context.
 | 
			
		||||
ARG resource_path=target
 | 
			
		||||
@@ -66,11 +66,11 @@ RUN sed -i -e "s_log4j.appender.File.File\=alfresco.log_log4j.appender.File.File
 | 
			
		||||
# fontconfig is required by Activiti worflow diagram generator
 | 
			
		||||
# installing pinned dependencies as well
 | 
			
		||||
RUN yum install -y fontconfig-2.13.1-3.el8 \
 | 
			
		||||
                   dejavu-fonts-common-2.35-6.el8 \
 | 
			
		||||
                   dejavu-fonts-common-2.35-7.el8 \
 | 
			
		||||
                   fontpackages-filesystem-1.44-22.el8 \
 | 
			
		||||
                   freetype-2.9.1-4.el8_3.1 \
 | 
			
		||||
                   libpng-1.6.34-5.el8 \
 | 
			
		||||
                   dejavu-sans-fonts-2.35-6.el8 && \
 | 
			
		||||
                   dejavu-sans-fonts-2.35-7.el8 && \
 | 
			
		||||
    yum clean all
 | 
			
		||||
 | 
			
		||||
# The standard configuration is to have all Tomcat files owned by root with group GROUPNAME and whilst owner has read/write privileges, 
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-packaging</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <profiles>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
TRANSFORMERS_TAG=2.3.10
 | 
			
		||||
SOLR6_TAG=2.0.1
 | 
			
		||||
TRANSFORMERS_TAG=2.5.0
 | 
			
		||||
SOLR6_TAG=2.0.2
 | 
			
		||||
POSTGRES_TAG=13.1
 | 
			
		||||
ACTIVEMQ_TAG=5.16.1
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-packaging</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <developers>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <developers>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <developers>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <developers>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <developers>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-packaging</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
 
 | 
			
		||||
@@ -57,8 +57,8 @@ ModuleDetails shareServicesModule = moduleService.getModule("alfresco-share-serv
 | 
			
		||||
<html xmlns="http://www.w3.org/1999/xhtml">
 | 
			
		||||
<head>
 | 
			
		||||
   <title>Alfresco</title>
 | 
			
		||||
   <link rel="stylesheet" type="text/css" href="./css/reset.css" />
 | 
			
		||||
   <link rel="stylesheet" type="text/css" href="./css/alfresco.css" />
 | 
			
		||||
   <link rel="stylesheet" type="text/css" href="/<%=sysAdminParams.getAlfrescoContext()%>/css/reset.css" />
 | 
			
		||||
   <link rel="stylesheet" type="text/css" href="/<%=sysAdminParams.getAlfrescoContext()%>/css/alfresco.css" />
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
   <div class="sticky-wrapper">
 | 
			
		||||
 
 | 
			
		||||
@@ -4,21 +4,21 @@
 | 
			
		||||
  %%
 | 
			
		||||
  Copyright (C) 2005 - 2016 Alfresco Software Limited
 | 
			
		||||
  %%
 | 
			
		||||
  This file is part of the Alfresco software. 
 | 
			
		||||
  If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
  the paid license agreement will prevail.  Otherwise, the software is 
 | 
			
		||||
  This file is part of the Alfresco software.
 | 
			
		||||
  If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
  the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
  provided under the following open source license terms:
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
  it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
  the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
  GNU Lesser General Public License for more details.
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
  along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
  #L%
 | 
			
		||||
@@ -57,19 +57,19 @@ ModuleDetails shareServicesModule = moduleService.getModule("alfresco-share-serv
 | 
			
		||||
<html xmlns="http://www.w3.org/1999/xhtml">
 | 
			
		||||
<head>
 | 
			
		||||
   <title>Alfresco</title>
 | 
			
		||||
   <link rel="stylesheet" type="text/css" href="./css/reset.css" />
 | 
			
		||||
   <link rel="stylesheet" type="text/css" href="./css/alfresco.css" />
 | 
			
		||||
   <link rel="stylesheet" type="text/css" href="/<%=sysAdminParams.getAlfrescoContext()%>/css/reset.css" />
 | 
			
		||||
   <link rel="stylesheet" type="text/css" href="/<%=sysAdminParams.getAlfrescoContext()%>/css/alfresco.css" />
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
   <div class="sticky-wrapper">
 | 
			
		||||
      <div class="index">
 | 
			
		||||
         
 | 
			
		||||
 | 
			
		||||
         <div class="title">
 | 
			
		||||
            <span class="logo"><a href="http://www.alfresco.com"><img src="./images/logo/logo.png" width="145" height="48" alt="" border="0" /></a></span>
 | 
			
		||||
            <span class="logo-separator"> </span>
 | 
			
		||||
            <h1>Welcome to Alfresco</h1>
 | 
			
		||||
         </div>
 | 
			
		||||
         
 | 
			
		||||
 | 
			
		||||
         <div class="index-list">
 | 
			
		||||
            <h4><%=descriptorService.getServerDescriptor().getEdition()%> - <%=descriptorService.getServerDescriptor().getVersion()%></h4>
 | 
			
		||||
            <p></p>
 | 
			
		||||
@@ -94,7 +94,7 @@ ModuleDetails shareServicesModule = moduleService.getModule("alfresco-share-serv
 | 
			
		||||
   {
 | 
			
		||||
%>
 | 
			
		||||
            <p>WARNING: The system is in Read Only mode, the License may have failed to deploy. Please visit the <a href="./s/enterprise/admin">Alfresco Administration Console</a> (admin only)</p>
 | 
			
		||||
<% 
 | 
			
		||||
<%
 | 
			
		||||
   }
 | 
			
		||||
   if (descriptorService.getLicenseDescriptor() != null && descriptorService.getLicenseDescriptor().getLicenseMode().toString().equals("ENTERPRISE"))
 | 
			
		||||
   {
 | 
			
		||||
@@ -120,7 +120,7 @@ ModuleDetails shareServicesModule = moduleService.getModule("alfresco-share-serv
 | 
			
		||||
            <p><a href="./api/-default-/public/cmis/versions/1.1/atom">CMIS 1.1 AtomPub Service Document</a></p>
 | 
			
		||||
            <p><a href="./api/-default-/public/cmis/versions/1.1/browser">CMIS 1.1 Browser Binding URL</a></p>
 | 
			
		||||
         </div>
 | 
			
		||||
         
 | 
			
		||||
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="push"></div>
 | 
			
		||||
   </div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										67
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								pom.xml
									
									
									
									
									
								
							@@ -2,7 +2,7 @@
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
    <version>8.424-SNAPSHOT</version>
 | 
			
		||||
    <version>10.7</version>
 | 
			
		||||
    <packaging>pom</packaging>
 | 
			
		||||
    <name>Alfresco Community Repo Parent</name>
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
    <properties>
 | 
			
		||||
        <acs.version.major>7</acs.version.major>
 | 
			
		||||
        <acs.version.minor>0</acs.version.minor>
 | 
			
		||||
        <acs.version.revision>0</acs.version.revision>
 | 
			
		||||
        <acs.version.revision>2</acs.version.revision>
 | 
			
		||||
        <acs.version.label />
 | 
			
		||||
 | 
			
		||||
        <version.edition>Community</version.edition>
 | 
			
		||||
@@ -34,15 +34,15 @@
 | 
			
		||||
        <image.registry>quay.io</image.registry>
 | 
			
		||||
 | 
			
		||||
        <java.version>11</java.version>
 | 
			
		||||
        <maven.compiler.source>11</maven.compiler.source>
 | 
			
		||||
        <maven.compiler.target>11</maven.compiler.target>
 | 
			
		||||
        <maven.build.sourceVersion>11</maven.build.sourceVersion>
 | 
			
		||||
        <maven.compiler.source>${java.version}</maven.compiler.source>
 | 
			
		||||
        <maven.compiler.target>${java.version}</maven.compiler.target>
 | 
			
		||||
        <maven.build.sourceVersion>${java.version}</maven.build.sourceVersion>
 | 
			
		||||
 | 
			
		||||
        <dir.root>${project.build.directory}/alf_data</dir.root>
 | 
			
		||||
 | 
			
		||||
        <dependency.alfresco-hb-data-sender.version>1.0.12</dependency.alfresco-hb-data-sender.version>
 | 
			
		||||
        <dependency.alfresco-mmt.version>6.0</dependency.alfresco-mmt.version>
 | 
			
		||||
        <dependency.alfresco-trashcan-cleaner.version>2.3</dependency.alfresco-trashcan-cleaner.version>
 | 
			
		||||
        <dependency.alfresco-trashcan-cleaner.version>2.4.1</dependency.alfresco-trashcan-cleaner.version>
 | 
			
		||||
        <dependency.alfresco-jlan.version>7.1</dependency.alfresco-jlan.version>
 | 
			
		||||
        <dependency.alfresco-server-root.version>6.0.1</dependency.alfresco-server-root.version>
 | 
			
		||||
        <dependency.alfresco-messaging-repo.version>1.2.15</dependency.alfresco-messaging-repo.version>
 | 
			
		||||
@@ -55,14 +55,14 @@
 | 
			
		||||
 | 
			
		||||
        <dependency.spring.version>5.3.3</dependency.spring.version>
 | 
			
		||||
        <dependency.antlr.version>3.5.2</dependency.antlr.version>
 | 
			
		||||
        <dependency.jackson.version>2.12.1</dependency.jackson.version>
 | 
			
		||||
        <dependency.jackson-databind.version>${dependency.jackson.version}</dependency.jackson-databind.version>
 | 
			
		||||
        <dependency.cxf.version>3.4.2</dependency.cxf.version>
 | 
			
		||||
        <dependency.jackson.version>2.12.3</dependency.jackson.version>
 | 
			
		||||
        <dependency.jackson-databind.version>2.12.3</dependency.jackson-databind.version>
 | 
			
		||||
        <dependency.cxf.version>3.4.4</dependency.cxf.version>
 | 
			
		||||
        <dependency.opencmis.version>1.0.0</dependency.opencmis.version>
 | 
			
		||||
        <dependency.webscripts.version>8.18</dependency.webscripts.version>
 | 
			
		||||
        <dependency.bouncycastle.version>1.68</dependency.bouncycastle.version>
 | 
			
		||||
        <dependency.mockito-core.version>3.8.0</dependency.mockito-core.version>
 | 
			
		||||
        <dependency.org-json.version>20201115</dependency.org-json.version>
 | 
			
		||||
        <dependency.webscripts.version>8.22</dependency.webscripts.version>
 | 
			
		||||
        <dependency.bouncycastle.version>1.69</dependency.bouncycastle.version>
 | 
			
		||||
        <dependency.mockito-core.version>3.9.0</dependency.mockito-core.version>
 | 
			
		||||
        <dependency.org-json.version>20210307</dependency.org-json.version>
 | 
			
		||||
        <dependency.commons-dbcp.version>1.4-DBCP330</dependency.commons-dbcp.version>
 | 
			
		||||
        <dependency.commons-io.version>2.8.0</dependency.commons-io.version>
 | 
			
		||||
        <dependency.gson.version>2.8.5</dependency.gson.version>
 | 
			
		||||
@@ -73,17 +73,18 @@
 | 
			
		||||
        <dependency.slf4j.version>1.7.30</dependency.slf4j.version>
 | 
			
		||||
        <dependency.gytheio.version>0.12</dependency.gytheio.version>
 | 
			
		||||
        <dependency.groovy.version>2.5.9</dependency.groovy.version>
 | 
			
		||||
        <dependency.tika.version>1.25</dependency.tika.version>
 | 
			
		||||
        <dependency.spring-security.version>5.4.1</dependency.spring-security.version>
 | 
			
		||||
        <dependency.tika.version>1.26</dependency.tika.version>
 | 
			
		||||
        <dependency.spring-security.version>5.5.0</dependency.spring-security.version>
 | 
			
		||||
        <dependency.truezip.version>7.7.10</dependency.truezip.version>
 | 
			
		||||
        <dependency.poi.version>4.1.2</dependency.poi.version>
 | 
			
		||||
        <dependency.ooxml-schemas.version>1.4</dependency.ooxml-schemas.version>
 | 
			
		||||
        <dependency.keycloak.version>11.0.0-alfresco-001</dependency.keycloak.version>
 | 
			
		||||
        <dependency.keycloak.version>13.0.1</dependency.keycloak.version>
 | 
			
		||||
        <dependency.jboss.logging.version>3.4.1.Final</dependency.jboss.logging.version>
 | 
			
		||||
        <dependency.camel.version>3.7.0</dependency.camel.version>
 | 
			
		||||
        <dependency.camel.version>3.7.4</dependency.camel.version>
 | 
			
		||||
        <dependency.activemq.version>5.16.1</dependency.activemq.version>
 | 
			
		||||
        <dependency.apache-compress.version>1.21</dependency.apache-compress.version>
 | 
			
		||||
        <dependency.apache.taglibs.version>1.2.5</dependency.apache.taglibs.version>
 | 
			
		||||
        <dependency.awaitility.version>4.0.3</dependency.awaitility.version>
 | 
			
		||||
        <dependency.awaitility.version>4.1.0</dependency.awaitility.version>
 | 
			
		||||
 | 
			
		||||
        <dependency.jakarta-jaxb-api.version>2.3.3</dependency.jakarta-jaxb-api.version>
 | 
			
		||||
        <dependency.jakarta-ws-api.version>2.3.3</dependency.jakarta-ws-api.version>
 | 
			
		||||
@@ -96,16 +97,16 @@
 | 
			
		||||
        <dependency.jakarta-json-api.version>1.1.6</dependency.jakarta-json-api.version>
 | 
			
		||||
        <dependency.jakarta-rpc-api.version>1.1.4</dependency.jakarta-rpc-api.version>
 | 
			
		||||
 | 
			
		||||
        <alfresco.googledrive.version>3.2.1</alfresco.googledrive.version>
 | 
			
		||||
        <alfresco.aos-module.version>1.4.0</alfresco.aos-module.version>
 | 
			
		||||
        <alfresco.googledrive.version>3.2.1.3</alfresco.googledrive.version>
 | 
			
		||||
        <alfresco.aos-module.version>1.4.0.1</alfresco.aos-module.version>
 | 
			
		||||
 | 
			
		||||
        <dependency.postgresql.version>42.2.19</dependency.postgresql.version>
 | 
			
		||||
        <dependency.mysql.version>8.0.23</dependency.mysql.version>
 | 
			
		||||
        <dependency.postgresql.version>42.2.20</dependency.postgresql.version>
 | 
			
		||||
        <dependency.mysql.version>8.0.25</dependency.mysql.version>
 | 
			
		||||
        <dependency.mariadb.version>2.7.2</dependency.mariadb.version>
 | 
			
		||||
        <dependency.tas-utility.version>3.0.43</dependency.tas-utility.version>
 | 
			
		||||
        <dependency.tas-utility.version>3.0.44</dependency.tas-utility.version>
 | 
			
		||||
        <dependency.rest-assured.version>3.3.0</dependency.rest-assured.version>
 | 
			
		||||
        <dependency.tas-restapi.version>1.56</dependency.tas-restapi.version>
 | 
			
		||||
        <dependency.tas-cmis.version>1.27</dependency.tas-cmis.version>
 | 
			
		||||
        <dependency.tas-restapi.version>1.58</dependency.tas-restapi.version>
 | 
			
		||||
        <dependency.tas-cmis.version>1.30</dependency.tas-cmis.version>
 | 
			
		||||
        <dependency.tas-email.version>1.8</dependency.tas-email.version>
 | 
			
		||||
        <dependency.tas-webdav.version>1.6</dependency.tas-webdav.version>
 | 
			
		||||
        <dependency.tas-ftp.version>1.5</dependency.tas-ftp.version>
 | 
			
		||||
@@ -116,7 +117,7 @@
 | 
			
		||||
        <connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection>
 | 
			
		||||
        <developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection>
 | 
			
		||||
        <url>https://github.com/Alfresco/alfresco-community-repo</url>
 | 
			
		||||
        <tag>HEAD</tag>
 | 
			
		||||
        <tag>10.7</tag>
 | 
			
		||||
    </scm>
 | 
			
		||||
 | 
			
		||||
    <distributionManagement>
 | 
			
		||||
@@ -549,8 +550,7 @@
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.bouncycastle</groupId>
 | 
			
		||||
                <artifactId>bcprov-jdk15on</artifactId>
 | 
			
		||||
                <version>1.68</version>
 | 
			
		||||
<!--                <version>${dependency.bouncycastle.version}</version>-->
 | 
			
		||||
                <version>${dependency.bouncycastle.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.bouncycastle</groupId>
 | 
			
		||||
@@ -583,7 +583,7 @@
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>com.drewnoakes</groupId>
 | 
			
		||||
                <artifactId>metadata-extractor</artifactId>
 | 
			
		||||
                <version>2.15.0</version>
 | 
			
		||||
                <version>2.16.0</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <!-- upgrade dependency from TIKA -->
 | 
			
		||||
            <dependency>
 | 
			
		||||
@@ -601,7 +601,7 @@
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.apache.commons</groupId>
 | 
			
		||||
                <artifactId>commons-compress</artifactId>
 | 
			
		||||
                <version>1.20</version>
 | 
			
		||||
                <version>${dependency.apache-compress.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.apache.commons</groupId>
 | 
			
		||||
@@ -679,7 +679,7 @@
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>joda-time</groupId>
 | 
			
		||||
                <artifactId>joda-time</artifactId>
 | 
			
		||||
                <version>2.10.9</version>
 | 
			
		||||
                <version>2.10.10</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!-- provided dependencies -->
 | 
			
		||||
@@ -694,7 +694,7 @@
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>junit</groupId>
 | 
			
		||||
                <artifactId>junit</artifactId>
 | 
			
		||||
                <version>4.13</version>
 | 
			
		||||
                <version>4.13.2</version>
 | 
			
		||||
                <scope>test</scope>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <dependency>
 | 
			
		||||
@@ -814,7 +814,7 @@
 | 
			
		||||
                <plugin>
 | 
			
		||||
                    <groupId>org.apache.maven.plugins</groupId>
 | 
			
		||||
                    <artifactId>maven-javadoc-plugin</artifactId>
 | 
			
		||||
                    <version>3.2.0</version>
 | 
			
		||||
                    <version>3.3.0</version>
 | 
			
		||||
                </plugin>
 | 
			
		||||
                <plugin>
 | 
			
		||||
                    <groupId>org.apache.maven.plugins</groupId>
 | 
			
		||||
@@ -829,5 +829,4 @@
 | 
			
		||||
            </plugins>
 | 
			
		||||
        </pluginManagement>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
 
 | 
			
		||||
@@ -25,21 +25,18 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.web.scripts.solr;
 | 
			
		||||
 | 
			
		||||
import java.io.ByteArrayOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.PrintWriter;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.FilterChain;
 | 
			
		||||
import javax.servlet.ServletContext;
 | 
			
		||||
import javax.servlet.ServletException;
 | 
			
		||||
import javax.servlet.ServletOutputStream;
 | 
			
		||||
import javax.servlet.ServletRequest;
 | 
			
		||||
import javax.servlet.ServletResponse;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
import javax.servlet.http.HttpServletResponseWrapper;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.error.AlfrescoRuntimeException;
 | 
			
		||||
import org.alfresco.httpclient.HttpClientFactory;
 | 
			
		||||
import org.alfresco.repo.web.filter.beans.DependencyInjectedFilter;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
@@ -88,9 +85,7 @@ public class SOLRAuthenticationFilter implements DependencyInjectedFilter, Initi
 | 
			
		||||
 | 
			
		||||
	private String sharedSecret;
 | 
			
		||||
 | 
			
		||||
	private String sharedSecretHeader = DEFAULT_SHAREDSECRET_HEADER;
 | 
			
		||||
 | 
			
		||||
	private static final String DEFAULT_SHAREDSECRET_HEADER = "X-Alfresco-Search-Secret";
 | 
			
		||||
	private String sharedSecretHeader = HttpClientFactory.DEFAULT_SHAREDSECRET_HEADER;
 | 
			
		||||
 | 
			
		||||
	public void setSecureComms(String type)
 | 
			
		||||
	{
 | 
			
		||||
 
 | 
			
		||||
@@ -716,7 +716,7 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if(!pathInfo.substring(0, 6).toLowerCase().equals("/cmis/") && !pathInfo.equals("/discovery"))
 | 
			
		||||
            if((pathInfo.length() > 5 && !pathInfo.substring(0, 6).toLowerCase().equals("/cmis/")) && !pathInfo.equals("/discovery"))
 | 
			
		||||
            {
 | 
			
		||||
                // remove tenant
 | 
			
		||||
                int idx = pathInfo.indexOf('/', 1);
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,11 @@ public class DefaultExceptionResolver implements ExceptionResolver<Exception>
 | 
			
		||||
    @Override
 | 
			
		||||
    public ErrorResponse resolveException(Exception ex)
 | 
			
		||||
    {
 | 
			
		||||
        return new ErrorResponse(DEFAULT_MESSAGE_ID, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), ex.getStackTrace(), null);
 | 
			
		||||
        return new ErrorResponse(DEFAULT_MESSAGE_ID,
 | 
			
		||||
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
 | 
			
		||||
                ex.getLocalizedMessage(),
 | 
			
		||||
                ex.getStackTrace(),
 | 
			
		||||
                null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Remote API
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is 
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * 
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.framework.core.exceptions;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.search.QueryParserException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * QueryParserException is related with search requests to Search Services.
 | 
			
		||||
 */
 | 
			
		||||
public class QueryParserExceptionResolver implements ExceptionResolver<QueryParserException>
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ErrorResponse resolveException(QueryParserException ex)
 | 
			
		||||
    {
 | 
			
		||||
        return new ErrorResponse(
 | 
			
		||||
            DefaultExceptionResolver.DEFAULT_MESSAGE_ID,
 | 
			
		||||
            // Mapping the original HTTP Status code returned by Search Services
 | 
			
		||||
            ex.getHttpStatusCode(),
 | 
			
		||||
            ex.getLocalizedMessage(),
 | 
			
		||||
            ex.getStackTrace(),
 | 
			
		||||
            null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -26,10 +26,12 @@
 | 
			
		||||
package org.alfresco.rest.framework.tools;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.metrics.rest.RestMetricsReporter;
 | 
			
		||||
import org.alfresco.repo.search.QueryParserException;
 | 
			
		||||
import org.alfresco.rest.framework.Api;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.DefaultExceptionResolver;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.ErrorResponse;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.ExceptionResolver;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.QueryParserExceptionResolver;
 | 
			
		||||
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
@@ -48,6 +50,7 @@ public class ApiAssistant {
 | 
			
		||||
 | 
			
		||||
    private ExceptionResolver<Exception> defaultResolver = new DefaultExceptionResolver();
 | 
			
		||||
    private ExceptionResolver<WebScriptException> webScriptExceptionResolver;
 | 
			
		||||
    private ExceptionResolver<QueryParserException> queryParserExceptionResolver;
 | 
			
		||||
    private ExceptionResolver<Exception> resolver;
 | 
			
		||||
    private JacksonHelper jsonHelper;
 | 
			
		||||
    private RestMetricsReporter restMetricsReporter;
 | 
			
		||||
@@ -77,6 +80,10 @@ public class ApiAssistant {
 | 
			
		||||
        {
 | 
			
		||||
            error = webScriptExceptionResolver.resolveException((WebScriptException) ex);
 | 
			
		||||
        }
 | 
			
		||||
        else if (ex instanceof QueryParserException)
 | 
			
		||||
        {
 | 
			
		||||
            error = queryParserExceptionResolver.resolveException((QueryParserException) ex);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            error = resolver.resolveException(ex);
 | 
			
		||||
@@ -100,6 +107,11 @@ public class ApiAssistant {
 | 
			
		||||
        this.webScriptExceptionResolver = webScriptExceptionResolver;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setQueryParserExceptionResolver(ExceptionResolver<QueryParserException> queryParserExceptionResolver)
 | 
			
		||||
    {
 | 
			
		||||
        this.queryParserExceptionResolver = queryParserExceptionResolver;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setResolver(ExceptionResolver<Exception> resolver) {
 | 
			
		||||
        this.resolver = resolver;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ import java.util.Properties;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.ServletContext;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.httpclient.HttpClientFactory.SecureCommsType;
 | 
			
		||||
import org.alfresco.web.scripts.servlet.X509ServletFilterBase;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
@@ -70,7 +71,9 @@ public class AlfrescoX509ServletFilter extends X509ServletFilterBase
 | 
			
		||||
         * Return true or false based on the property. This will switch on/off X509 enforcement in the X509ServletFilterBase.
 | 
			
		||||
         */
 | 
			
		||||
 | 
			
		||||
        if (prop == null || "none".equals(prop))
 | 
			
		||||
        if (prop == null || 
 | 
			
		||||
            SecureCommsType.getType(prop) == SecureCommsType.NONE || 
 | 
			
		||||
            SecureCommsType.getType(prop) == SecureCommsType.SECRET)
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -134,6 +134,8 @@
 | 
			
		||||
    </bean>
 | 
			
		||||
    <bean id="webScriptExceptionResolver" class="org.alfresco.rest.framework.core.exceptions.WebScriptExceptionResolver">
 | 
			
		||||
    </bean>
 | 
			
		||||
    <bean id="queryParserExceptionResolver" class="org.alfresco.rest.framework.core.exceptions.QueryParserExceptionResolver">
 | 
			
		||||
    </bean>
 | 
			
		||||
    <bean id="simpleMappingExceptionResolverParent" abstract="true" class="org.alfresco.rest.framework.core.exceptions.SimpleMappingExceptionResolver">
 | 
			
		||||
        <property name="exceptionMappings">
 | 
			
		||||
            <map>
 | 
			
		||||
@@ -182,6 +184,7 @@
 | 
			
		||||
        <property name="resolver" ref="simpleMappingExceptionResolver" />
 | 
			
		||||
        <property name="webScriptExceptionResolver" ref="webScriptExceptionResolver" />
 | 
			
		||||
        <property name="restMetricsReporter" ref="restMetricsReporter"/>
 | 
			
		||||
        <property name="queryParserExceptionResolver" ref="queryParserExceptionResolver" />
 | 
			
		||||
    </bean>
 | 
			
		||||
    
 | 
			
		||||
	<!-- Using annotation-config=false means AutowiredAnnotationBeanPostProcessor 
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,76 @@
 | 
			
		||||
<#assign null><span style="color:red">${msg("nodebrowser.null")?html}</span></#assign>
 | 
			
		||||
<#assign none><span style="color:red">${msg("nodebrowser.none")?html}</span></#assign>
 | 
			
		||||
<#assign collection>${msg("nodebrowser.collection")?html}</#assign>
 | 
			
		||||
 | 
			
		||||
<#assign maxDepth=1000 />
 | 
			
		||||
<#macro dateFormat date>${date?string("dd MMM yyyy HH:mm:ss 'GMT'Z '('zzz')'")}</#macro>
 | 
			
		||||
<#macro propValue p>
 | 
			
		||||
<#if p.value??>
 | 
			
		||||
   <#if p.value?is_date>
 | 
			
		||||
      <@dateFormat p.value />
 | 
			
		||||
   <#elseif p.value?is_boolean>
 | 
			
		||||
      ${p.value?string}
 | 
			
		||||
   <#elseif p.value?is_number>
 | 
			
		||||
      ${p.value?c}
 | 
			
		||||
   <#elseif p.value?is_string>
 | 
			
		||||
      ${p.value?html}
 | 
			
		||||
   <#elseif p.value?is_hash>
 | 
			
		||||
      <#assign result = "{"/>
 | 
			
		||||
      <#assign first = true />
 | 
			
		||||
      <#list p.value?keys as key>
 | 
			
		||||
         <#if first = false>
 | 
			
		||||
            <#assign result = result + ", "/>
 | 
			
		||||
   <#attempt>
 | 
			
		||||
      <#if p.value??>
 | 
			
		||||
        <#if p.value?is_date>
 | 
			
		||||
         <@dateFormat p.value />
 | 
			
		||||
        <#elseif p.value?is_boolean>
 | 
			
		||||
         ${p.value?string}
 | 
			
		||||
        <#elseif p.value?is_number>
 | 
			
		||||
         ${p.value?c}
 | 
			
		||||
        <#elseif p.value?is_string>
 | 
			
		||||
         ${p.value?html}
 | 
			
		||||
        <#elseif p.value?is_hash || p.value?is_enumerable>
 | 
			
		||||
            <@convertToJSON p.value />
 | 
			
		||||
        </#if>
 | 
			
		||||
   	  <#else>
 | 
			
		||||
   	     ${null}
 | 
			
		||||
      </#if>
 | 
			
		||||
   <#recover>
 | 
			
		||||
      <span style="color:red">${.error}</span>
 | 
			
		||||
   </#attempt>
 | 
			
		||||
</#macro>
 | 
			
		||||
<#macro convertToJSON v>
 | 
			
		||||
   <#if v??>
 | 
			
		||||
      <#if v?is_date>
 | 
			
		||||
         <@dateFormat v />
 | 
			
		||||
      <#elseif v?is_boolean>
 | 
			
		||||
         ${v?string}
 | 
			
		||||
      <#elseif v?is_number>
 | 
			
		||||
         ${v?c}
 | 
			
		||||
      <#elseif v?is_string>
 | 
			
		||||
         "${v?string}"
 | 
			
		||||
      <#elseif v?is_hash>
 | 
			
		||||
         <#if v?keys?size gt maxDepth >
 | 
			
		||||
            <#stop "Max depth of object achieved">
 | 
			
		||||
         </#if>
 | 
			
		||||
         <#assign result = result + "${key}=${p.value[key]?html}" />
 | 
			
		||||
         <#assign first = false/>
 | 
			
		||||
      </#list>
 | 
			
		||||
      <#assign result = result + "}"/>
 | 
			
		||||
      ${result}
 | 
			
		||||
         <@compress single_line=true>
 | 
			
		||||
            {
 | 
			
		||||
            <#assign first = true />
 | 
			
		||||
            <#list v?keys as key>
 | 
			
		||||
            <#if first = false>,</#if>
 | 
			
		||||
            "${key}":
 | 
			
		||||
            <#if v[key]??>
 | 
			
		||||
               <@convertToJSON v[key] />
 | 
			
		||||
            <#else>
 | 
			
		||||
               ${null}
 | 
			
		||||
            </#if>
 | 
			
		||||
            <#assign first = false/>
 | 
			
		||||
            </#list>
 | 
			
		||||
            }
 | 
			
		||||
         </@compress>
 | 
			
		||||
      <#elseif v?is_enumerable>
 | 
			
		||||
         <#if v?size gt maxDepth>
 | 
			
		||||
            <#stop "Max depth of object achieved" >
 | 
			
		||||
         </#if>
 | 
			
		||||
         <#assign first = true />
 | 
			
		||||
            <@compress single_line=true>
 | 
			
		||||
               [
 | 
			
		||||
               <#list v as item>
 | 
			
		||||
                  <#if first = false>,</#if>
 | 
			
		||||
                  <@convertToJSON item />
 | 
			
		||||
                  <#assign first = false/>
 | 
			
		||||
               </#list>
 | 
			
		||||
               ]
 | 
			
		||||
            </@compress>
 | 
			
		||||
      </#if>
 | 
			
		||||
   <#else>
 | 
			
		||||
      ${null}
 | 
			
		||||
   </#if>
 | 
			
		||||
<#else>
 | 
			
		||||
   ${null}
 | 
			
		||||
</#if>
 | 
			
		||||
</#macro>
 | 
			
		||||
<#macro contentUrl nodeRef prop>
 | 
			
		||||
${url.serviceContext}/api/node/${nodeRef?replace("://","/")}/content;${prop?url}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,45 +23,46 @@
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.framework.tests.core;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertNotNull;
 | 
			
		||||
import static org.junit.Assert.assertNull;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.forms.FormNotFoundException;
 | 
			
		||||
package org.alfresco.rest.framework.tests.core;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertNotNull;
 | 
			
		||||
import static org.junit.Assert.assertNull;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.forms.FormNotFoundException;
 | 
			
		||||
import org.alfresco.repo.node.integrity.IntegrityException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.ApiException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.DeletedResourceException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.ErrorResponse;
 | 
			
		||||
import org.alfresco.repo.search.QueryParserException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.ApiException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.DeletedResourceException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.ErrorResponse;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.InsufficientStorageException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.StaleEntityException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.where.InvalidQueryException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.StaleEntityException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.where.InvalidQueryException;
 | 
			
		||||
import org.alfresco.rest.framework.tools.ApiAssistant;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.extensions.webscripts.WebScriptException;
 | 
			
		||||
import org.springframework.test.context.ContextConfiguration;
 | 
			
		||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 | 
			
		||||
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.extensions.webscripts.WebScriptException;
 | 
			
		||||
import org.springframework.test.context.ContextConfiguration;
 | 
			
		||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
 | 
			
		||||
@RunWith(SpringJUnit4ClassRunner.class)
 | 
			
		||||
@ContextConfiguration(locations = { "classpath:test-rest-context.xml" })
 | 
			
		||||
public class ExceptionResolverTests
 | 
			
		||||
{
 | 
			
		||||
    @Autowired
 | 
			
		||||
@RunWith(SpringJUnit4ClassRunner.class)
 | 
			
		||||
@ContextConfiguration(locations = { "classpath:test-rest-context.xml" })
 | 
			
		||||
public class ExceptionResolverTests
 | 
			
		||||
{
 | 
			
		||||
    @Autowired
 | 
			
		||||
    ApiAssistant assistant;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testWebscriptException()
 | 
			
		||||
    {
 | 
			
		||||
        ErrorResponse response = assistant.resolveException(new WebScriptException(null));
 | 
			
		||||
@@ -75,43 +76,43 @@ public class ExceptionResolverTests
 | 
			
		||||
 | 
			
		||||
    //04180006 Authentication failed for Web Script org/alfresco/api/ResourceWebScript.get
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMatchException()
 | 
			
		||||
    {
 | 
			
		||||
    public void testMatchException()
 | 
			
		||||
    {
 | 
			
		||||
        ErrorResponse response = assistant.resolveException(new ApiException(null));
 | 
			
		||||
        assertNotNull(response);
 | 
			
		||||
        assertEquals(500, response.getStatusCode());  //default to INTERNAL_SERVER_ERROR
 | 
			
		||||
       
 | 
			
		||||
        assertNotNull(response);
 | 
			
		||||
        assertEquals(500, response.getStatusCode());  //default to INTERNAL_SERVER_ERROR
 | 
			
		||||
       
 | 
			
		||||
        response = assistant.resolveException(new InvalidArgumentException(null));
 | 
			
		||||
        assertEquals(400, response.getStatusCode());  //default to STATUS_BAD_REQUEST
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new InvalidQueryException(null));
 | 
			
		||||
        assertEquals(400, response.getStatusCode());  //default to STATUS_BAD_REQUEST
 | 
			
		||||
        
 | 
			
		||||
        response = assistant.resolveException(new NotFoundException(null));
 | 
			
		||||
        assertEquals(404, response.getStatusCode());  //default to STATUS_NOT_FOUND
 | 
			
		||||
        
 | 
			
		||||
        response = assistant.resolveException(new EntityNotFoundException(null));
 | 
			
		||||
        assertEquals(404, response.getStatusCode());  //default to STATUS_NOT_FOUND
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new RelationshipResourceNotFoundException(null, null));
 | 
			
		||||
        assertEquals(404, response.getStatusCode());  //default to STATUS_NOT_FOUND
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new PermissionDeniedException(null));
 | 
			
		||||
        assertEquals(403, response.getStatusCode());  //default to STATUS_FORBIDDEN
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new UnsupportedResourceOperationException(null));
 | 
			
		||||
        assertEquals(405, response.getStatusCode());  //default to STATUS_METHOD_NOT_ALLOWED
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new DeletedResourceException(null));
 | 
			
		||||
        assertEquals(405, response.getStatusCode());  //default to STATUS_METHOD_NOT_ALLOWED
 | 
			
		||||
        
 | 
			
		||||
        response = assistant.resolveException(new ConstraintViolatedException(null));
 | 
			
		||||
        assertEquals(409, response.getStatusCode());  //default to STATUS_CONFLICT    
 | 
			
		||||
        
 | 
			
		||||
        response = assistant.resolveException(new StaleEntityException(null));
 | 
			
		||||
        assertEquals(409, response.getStatusCode());  //default to STATUS_CONFLICT    
 | 
			
		||||
        assertEquals(400, response.getStatusCode());  //default to STATUS_BAD_REQUEST
 | 
			
		||||
 | 
			
		||||
        //Try a random exception
 | 
			
		||||
        response = assistant.resolveException(new InvalidQueryException(null));
 | 
			
		||||
        assertEquals(400, response.getStatusCode());  //default to STATUS_BAD_REQUEST
 | 
			
		||||
        
 | 
			
		||||
        response = assistant.resolveException(new NotFoundException(null));
 | 
			
		||||
        assertEquals(404, response.getStatusCode());  //default to STATUS_NOT_FOUND
 | 
			
		||||
        
 | 
			
		||||
        response = assistant.resolveException(new EntityNotFoundException(null));
 | 
			
		||||
        assertEquals(404, response.getStatusCode());  //default to STATUS_NOT_FOUND
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new RelationshipResourceNotFoundException(null, null));
 | 
			
		||||
        assertEquals(404, response.getStatusCode());  //default to STATUS_NOT_FOUND
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new PermissionDeniedException(null));
 | 
			
		||||
        assertEquals(403, response.getStatusCode());  //default to STATUS_FORBIDDEN
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new UnsupportedResourceOperationException(null));
 | 
			
		||||
        assertEquals(405, response.getStatusCode());  //default to STATUS_METHOD_NOT_ALLOWED
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new DeletedResourceException(null));
 | 
			
		||||
        assertEquals(405, response.getStatusCode());  //default to STATUS_METHOD_NOT_ALLOWED
 | 
			
		||||
        
 | 
			
		||||
        response = assistant.resolveException(new ConstraintViolatedException(null));
 | 
			
		||||
        assertEquals(409, response.getStatusCode());  //default to STATUS_CONFLICT    
 | 
			
		||||
        
 | 
			
		||||
        response = assistant.resolveException(new StaleEntityException(null));
 | 
			
		||||
        assertEquals(409, response.getStatusCode());  //default to STATUS_CONFLICT    
 | 
			
		||||
 | 
			
		||||
        //Try a random exception
 | 
			
		||||
        response = assistant.resolveException(new FormNotFoundException(null));
 | 
			
		||||
        assertEquals(500, response.getStatusCode());  //default to INTERNAL_SERVER_ERROR
 | 
			
		||||
 | 
			
		||||
@@ -120,6 +121,15 @@ public class ExceptionResolverTests
 | 
			
		||||
 | 
			
		||||
        response = assistant.resolveException(new IntegrityException(null));
 | 
			
		||||
        assertEquals(422, response.getStatusCode());
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Check that the status code from SS is passed back to the caller. */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testQueryParserException()
 | 
			
		||||
    {
 | 
			
		||||
        ErrorResponse response = assistant.resolveException(new QueryParserException("Endpoint not found", 404));
 | 
			
		||||
        assertNotNull(response);
 | 
			
		||||
        assertEquals("Expected status code to be passed through from query parser.", 404, response.getStatusCode());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,10 +33,12 @@
 | 
			
		||||
    </bean>
 | 
			
		||||
    <bean id="webScriptExceptionResolver" class="org.alfresco.rest.framework.core.exceptions.WebScriptExceptionResolver">
 | 
			
		||||
    </bean>
 | 
			
		||||
    <bean id="queryParserExceptionResolver" class="org.alfresco.rest.framework.core.exceptions.QueryParserExceptionResolver" />
 | 
			
		||||
    <bean id="apiAssistant" class="org.alfresco.rest.framework.tools.ApiAssistant">
 | 
			
		||||
        <property name="jsonHelper" ref="jsonHelper" />
 | 
			
		||||
        <property name="resolver" ref="simpleMappingExceptionResolver" />
 | 
			
		||||
        <property name="webScriptExceptionResolver" ref="webScriptExceptionResolver" />
 | 
			
		||||
        <property name="queryParserExceptionResolver" ref="queryParserExceptionResolver" />
 | 
			
		||||
    </bean>
 | 
			
		||||
    <bean id="simpleMappingExceptionResolver" class="org.alfresco.rest.framework.core.exceptions.SimpleMappingExceptionResolver">
 | 
			
		||||
       <property name="exceptionMappings">
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>8.424-SNAPSHOT</version>
 | 
			
		||||
        <version>10.7</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
@@ -383,7 +383,7 @@
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.fasterxml.woodstox</groupId>
 | 
			
		||||
            <artifactId>woodstox-core</artifactId>
 | 
			
		||||
            <version>6.2.4</version>
 | 
			
		||||
            <version>6.2.6</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <!-- GData -->
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ public class IdsEntity
 | 
			
		||||
    private Long idThree;
 | 
			
		||||
    private Long idFour;
 | 
			
		||||
    private List<Long> ids;
 | 
			
		||||
    private boolean ordered;
 | 
			
		||||
    public Long getIdOne()
 | 
			
		||||
    {
 | 
			
		||||
        return idOne;
 | 
			
		||||
@@ -80,4 +81,12 @@ public class IdsEntity
 | 
			
		||||
    {
 | 
			
		||||
        this.ids = ids;
 | 
			
		||||
    }
 | 
			
		||||
    public boolean isOrdered()
 | 
			
		||||
    {
 | 
			
		||||
        return ordered;
 | 
			
		||||
    }
 | 
			
		||||
    public void setOrdered(boolean ordered)
 | 
			
		||||
    {
 | 
			
		||||
        this.ordered = ordered;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,8 @@ import org.alfresco.sync.repo.Client;
 | 
			
		||||
import org.alfresco.sync.repo.Client.ClientType;
 | 
			
		||||
import org.alfresco.repo.activities.ActivityType;
 | 
			
		||||
import org.alfresco.repo.model.filefolder.HiddenAspect;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
 | 
			
		||||
import org.alfresco.repo.tenant.TenantService;
 | 
			
		||||
import org.alfresco.service.cmr.activities.ActivityInfo;
 | 
			
		||||
import org.alfresco.service.cmr.activities.ActivityPoster;
 | 
			
		||||
@@ -228,7 +230,7 @@ public class ActivityPosterImpl implements CmisActivityPoster, InitializingBean
 | 
			
		||||
    {
 | 
			
		||||
        if(activitiesEnabled && !hiddenAspect.hasHiddenAspect(nodeRef))
 | 
			
		||||
        {
 | 
			
		||||
            SiteInfo siteInfo = siteService.getSite(nodeRef);
 | 
			
		||||
            SiteInfo siteInfo = getSiteAsSystem(nodeRef);
 | 
			
		||||
            String siteId = (siteInfo != null ? siteInfo.getShortName() : null);
 | 
			
		||||
            if(siteId != null && !siteId.equals(""))
 | 
			
		||||
            {
 | 
			
		||||
@@ -290,5 +292,16 @@ public class ActivityPosterImpl implements CmisActivityPoster, InitializingBean
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    private SiteInfo getSiteAsSystem(NodeRef nodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        return AuthenticationUtil.runAsSystem(new RunAsWork<SiteInfo>()
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public SiteInfo doWork() throws Exception
 | 
			
		||||
            {
 | 
			
		||||
                return siteService.getSite(nodeRef);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -34,7 +34,10 @@ import org.alfresco.repo.action.ParameterDefinitionImpl;
 | 
			
		||||
import org.alfresco.repo.admin.SysAdminParams;
 | 
			
		||||
import org.alfresco.repo.jscript.ScriptAction;
 | 
			
		||||
import org.alfresco.service.ServiceRegistry;
 | 
			
		||||
import org.alfresco.service.cmr.action.Action;
 | 
			
		||||
import org.alfresco.service.cmr.action.Action;
 | 
			
		||||
import org.alfresco.service.cmr.action.ActionDefinition;
 | 
			
		||||
import org.alfresco.service.cmr.action.ActionService;
 | 
			
		||||
import org.alfresco.service.cmr.action.ParameterConstraint;
 | 
			
		||||
import org.alfresco.service.cmr.action.ParameterDefinition;
 | 
			
		||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
@@ -126,6 +129,10 @@ public class ScriptActionExecuter extends ActionExecuterAbstractBase
 | 
			
		||||
        if (nodeService.exists(actionedUponNodeRef))
 | 
			
		||||
        {
 | 
			
		||||
            NodeRef scriptRef = (NodeRef)action.getParameterValue(PARAM_SCRIPTREF);
 | 
			
		||||
            if(!isValidScriptRef(action))
 | 
			
		||||
            {
 | 
			
		||||
                throw new IllegalStateException("Invalid script ref path: " + scriptRef);
 | 
			
		||||
            }
 | 
			
		||||
            NodeRef spaceRef = this.serviceRegistry.getRuleService().getOwningNodeRef(action);
 | 
			
		||||
            if (spaceRef == null)
 | 
			
		||||
            {
 | 
			
		||||
@@ -222,4 +229,19 @@ public class ScriptActionExecuter extends ActionExecuterAbstractBase
 | 
			
		||||
 | 
			
		||||
        return companyHomeRef;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private boolean isValidScriptRef(Action action)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef scriptRef = (NodeRef) action.getParameterValue(PARAM_SCRIPTREF);
 | 
			
		||||
        ActionService actionService = this.serviceRegistry.getActionService();
 | 
			
		||||
        ActionDefinition actDef = actionService.getActionDefinition(action.getActionDefinitionName());
 | 
			
		||||
        ParameterDefinition parameterDef = actDef.getParameterDefintion(PARAM_SCRIPTREF);
 | 
			
		||||
        String paramConstraintName = parameterDef.getParameterConstraintName();
 | 
			
		||||
        if (paramConstraintName != null)
 | 
			
		||||
        {
 | 
			
		||||
            ParameterConstraint paramConstraint = actionService.getParameterConstraint(paramConstraintName);
 | 
			
		||||
            return paramConstraint.isValidValue(scriptRef.toString());
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1483,7 +1483,17 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
 | 
			
		||||
 | 
			
		||||
            // Update ACLs for moved tree
 | 
			
		||||
            Long newParentAclId = newParentNode.getAclId();
 | 
			
		||||
            accessControlListDAO.updateInheritance(newChildNodeId, oldParentAclId, newParentAclId);
 | 
			
		||||
 | 
			
		||||
            // Verify if parent has aspect applied and ACL's are pending
 | 
			
		||||
            if (hasNodeAspect(oldParentNodeId, ContentModel.ASPECT_PENDING_FIX_ACL))
 | 
			
		||||
            {
 | 
			
		||||
                Long oldParentSharedAclId = (Long) this.getNodeProperty(oldParentNodeId, ContentModel.PROP_SHARED_ACL_TO_REPLACE);
 | 
			
		||||
                accessControlListDAO.updateInheritance(newChildNodeId, oldParentSharedAclId, newParentAclId);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                accessControlListDAO.updateInheritance(newChildNodeId, oldParentAclId, newParentAclId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Done
 | 
			
		||||
@@ -2746,6 +2756,22 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
 | 
			
		||||
        selectNodesWithAspects(qnameIds, minNodeId, maxNodeId, resultsCallback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void getNodesWithAspects(
 | 
			
		||||
            Set<QName> aspectQNames,
 | 
			
		||||
            Long minNodeId, Long maxNodeId, boolean ordered,
 | 
			
		||||
            NodeRefQueryCallback resultsCallback)
 | 
			
		||||
    {
 | 
			
		||||
        Set<Long> qnameIdsSet = qnameDAO.convertQNamesToIds(aspectQNames, false);
 | 
			
		||||
        if (qnameIdsSet.size() == 0)
 | 
			
		||||
        {
 | 
			
		||||
            // No point running a query
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        List<Long> qnameIds = new ArrayList<Long>(qnameIdsSet);
 | 
			
		||||
        selectNodesWithAspects(qnameIds, minNodeId, maxNodeId, ordered, resultsCallback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return              Returns a writable copy of the cached aspects set
 | 
			
		||||
     */
 | 
			
		||||
@@ -4917,6 +4943,10 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
 | 
			
		||||
            List<Long> qnameIds,
 | 
			
		||||
            Long minNodeId, Long maxNodeId,
 | 
			
		||||
            NodeRefQueryCallback resultsCallback);
 | 
			
		||||
    protected abstract void selectNodesWithAspects(
 | 
			
		||||
            List<Long> qnameIds,
 | 
			
		||||
            Long minNodeId, Long maxNodeId, boolean ordered,
 | 
			
		||||
            NodeRefQueryCallback resultsCallback);
 | 
			
		||||
    protected abstract Long insertNodeAssoc(Long sourceNodeId, Long targetNodeId, Long assocTypeQNameId, int assocIndex);
 | 
			
		||||
    protected abstract int updateNodeAssoc(Long id, int assocIndex);
 | 
			
		||||
    protected abstract int deleteNodeAssoc(Long sourceNodeId, Long targetNodeId, Long assocTypeQNameId);
 | 
			
		||||
 
 | 
			
		||||
@@ -405,6 +405,20 @@ public interface NodeDAO extends NodeBulkLoader
 | 
			
		||||
            Long minNodeId, Long maxNodeId,
 | 
			
		||||
            NodeRefQueryCallback resultsCallback);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get nodes with aspects between the given ranges, ordering the results optionally
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspectQNames              the aspects that must be on the nodes
 | 
			
		||||
     * @param minNodeId                 the minimum node ID (inclusive)
 | 
			
		||||
     * @param maxNodeId                 the maximum node ID (exclusive)
 | 
			
		||||
     * @param ordered                   if the results are to be ordered by nodeID
 | 
			
		||||
     * @param resultsCallback           callback to process results
 | 
			
		||||
     */
 | 
			
		||||
    public void getNodesWithAspects(
 | 
			
		||||
            Set<QName> aspectQNames,
 | 
			
		||||
            Long minNodeId, Long maxNodeId, boolean ordered,
 | 
			
		||||
            NodeRefQueryCallback resultsCallback);
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Node Assocs
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -764,6 +764,31 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl
 | 
			
		||||
        template.select(SELECT_NODES_WITH_ASPECT_IDS, parameters, resultHandler);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void selectNodesWithAspects(
 | 
			
		||||
            List<Long> qnameIds,
 | 
			
		||||
            Long minNodeId, Long maxNodeId, boolean ordered,
 | 
			
		||||
            final NodeRefQueryCallback resultsCallback)
 | 
			
		||||
    {
 | 
			
		||||
        @SuppressWarnings("rawtypes")
 | 
			
		||||
        ResultHandler resultHandler = new ResultHandler()
 | 
			
		||||
        {
 | 
			
		||||
            public void handleResult(ResultContext context)
 | 
			
		||||
            {
 | 
			
		||||
                NodeEntity entity = (NodeEntity) context.getResultObject();
 | 
			
		||||
                Pair<Long, NodeRef> nodePair = new Pair<Long, NodeRef>(entity.getId(), entity.getNodeRef());
 | 
			
		||||
                resultsCallback.handle(nodePair);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        IdsEntity parameters = new IdsEntity();
 | 
			
		||||
        parameters.setIdOne(minNodeId);
 | 
			
		||||
        parameters.setIdTwo(maxNodeId);
 | 
			
		||||
        parameters.setIds(qnameIds);
 | 
			
		||||
        parameters.setOrdered(ordered);
 | 
			
		||||
        template.select(SELECT_NODES_WITH_ASPECT_IDS, parameters, resultHandler);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected Long insertNodeAssoc(Long sourceNodeId, Long targetNodeId, Long assocTypeQNameId, int assocIndex)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -337,6 +337,13 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
 | 
			
		||||
        setFixedAcls(getNodeIdNotNull(parent), inheritFrom, null, sharedAclToReplace, changes, false, asyncCall, true);
 | 
			
		||||
        return changes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<AclChange> setInheritanceForChildren(NodeRef parent, Long inheritFrom, Long sharedAclToReplace, boolean asyncCall, boolean forceSharedACL)
 | 
			
		||||
    {
 | 
			
		||||
        List<AclChange> changes = new ArrayList<AclChange>();
 | 
			
		||||
        setFixedAcls(getNodeIdNotNull(parent), inheritFrom, null, sharedAclToReplace, changes, false, asyncCall, true, forceSharedACL);
 | 
			
		||||
        return changes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void updateChangedAcls(NodeRef startingPoint, List<AclChange> changes)
 | 
			
		||||
    {
 | 
			
		||||
@@ -362,6 +369,29 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
 | 
			
		||||
        setFixedAcls(nodeId, inheritFrom, mergeFrom, sharedAclToReplace, changes, set, false, true);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Support to set a shared ACL on a node and all of its children
 | 
			
		||||
     * 
 | 
			
		||||
     * @param nodeId
 | 
			
		||||
     *            the parent node
 | 
			
		||||
     * @param inheritFrom
 | 
			
		||||
     *            the parent node's ACL
 | 
			
		||||
     * @param mergeFrom
 | 
			
		||||
     *            the shared ACL, if already known. If <code>null</code>, will be retrieved / created lazily
 | 
			
		||||
     * @param changes
 | 
			
		||||
     *            the list in which to record changes
 | 
			
		||||
     * @param set
 | 
			
		||||
     *            set the shared ACL on the parent ?
 | 
			
		||||
     * @param asyncCall
 | 
			
		||||
     *            function may require asynchronous call depending the execution time; if time exceeds configured <code>fixedAclMaxTransactionTime</code> value,
 | 
			
		||||
     *            recursion is stopped using propagateOnChildren parameter(set on false) and those nodes for which the method execution was not finished 
 | 
			
		||||
     *            in the classical way, will have ASPECT_PENDING_FIX_ACL, which will be used in {@link FixedAclUpdater} for later processing
 | 
			
		||||
     */
 | 
			
		||||
    public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace, List<AclChange> changes, boolean set, boolean asyncCall, boolean propagateOnChildren)
 | 
			
		||||
    {
 | 
			
		||||
        setFixedAcls(nodeId, inheritFrom, mergeFrom, sharedAclToReplace, changes, set, false, true, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Support to set a shared ACL on a node and all of its children
 | 
			
		||||
     * 
 | 
			
		||||
@@ -379,8 +409,10 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
 | 
			
		||||
     *            function may require asynchronous call depending the execution time; if time exceeds configured <code>fixedAclMaxTransactionTime</code> value,
 | 
			
		||||
     *            recursion is stopped using propagateOnChildren parameter(set on false) and those nodes for which the method execution was not finished 
 | 
			
		||||
     *            in the classical way, will have ASPECT_PENDING_FIX_ACL, which will be used in {@link FixedAclUpdater} for later processing
 | 
			
		||||
     * @param forceSharedACL
 | 
			
		||||
     *            When a child node has an unexpected ACL, force it to assume the new shared ACL instead of throwing a concurrency exception.
 | 
			
		||||
     */
 | 
			
		||||
    public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace, List<AclChange> changes, boolean set, boolean asyncCall, boolean propagateOnChildren) 
 | 
			
		||||
    public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace, List<AclChange> changes, boolean set, boolean asyncCall, boolean propagateOnChildren, boolean forceSharedACL)
 | 
			
		||||
    {
 | 
			
		||||
        if (log.isDebugEnabled())
 | 
			
		||||
        {
 | 
			
		||||
@@ -431,14 +463,14 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
 | 
			
		||||
               
 | 
			
		||||
                if (acl == null)
 | 
			
		||||
                {
 | 
			
		||||
                    propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false, asyncCall, propagateOnChildren);
 | 
			
		||||
                    propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false, asyncCall, propagateOnChildren, forceSharedACL);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    // Still has old shared ACL or already replaced
 | 
			
		||||
                    if(acl.equals(sharedAclToReplace) || acl.equals(mergeFrom) || acl.equals(currentAcl))
 | 
			
		||||
                    {
 | 
			
		||||
                        propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false, asyncCall, propagateOnChildren);
 | 
			
		||||
                        propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false, asyncCall, propagateOnChildren, forceSharedACL);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
@@ -457,7 +489,20 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (dbAcl.getAclType() == ACLType.SHARED)
 | 
			
		||||
                        {
 | 
			
		||||
                            throw new ConcurrencyFailureException("setFixedAcls: unexpected shared acl: "+dbAcl);
 | 
			
		||||
                            if (forceSharedACL)
 | 
			
		||||
                            {
 | 
			
		||||
                                log.warn("Forcing shared ACL on node: " + child.getId() + " ( "
 | 
			
		||||
                                        + nodeDAO.getNodePair(child.getId()).getSecond() + ") - " + dbAcl);
 | 
			
		||||
                                sharedAclToReplace = acl;
 | 
			
		||||
                                propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace,
 | 
			
		||||
                                        changes, false, asyncCall, propagateOnChildren, forceSharedACL);
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                throw new ConcurrencyFailureException(
 | 
			
		||||
                                        "setFixedAcls: unexpected shared acl: " + dbAcl + " on node " + child.getId() + " ( "
 | 
			
		||||
                                                + nodeDAO.getNodePair(child.getId()).getSecond() + ")");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -506,7 +551,7 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
 | 
			
		||||
     * 
 | 
			
		||||
     */
 | 
			
		||||
    private boolean setFixAclPending(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace,
 | 
			
		||||
            List<AclChange> changes, boolean set, boolean asyncCall, boolean propagateOnChildren)
 | 
			
		||||
            List<AclChange> changes, boolean set, boolean asyncCall, boolean propagateOnChildren, boolean forceSharedACL)
 | 
			
		||||
    {
 | 
			
		||||
        // check transaction time
 | 
			
		||||
        long transactionStartTime = AlfrescoTransactionSupport.getTransactionStartTime();
 | 
			
		||||
@@ -514,7 +559,7 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
 | 
			
		||||
        if (transactionTime < fixedAclMaxTransactionTime)
 | 
			
		||||
        {
 | 
			
		||||
            // make regular method call if time is under max transaction configured time
 | 
			
		||||
            setFixedAcls(nodeId, inheritFrom, mergeFrom, sharedAclToReplace, changes, set, asyncCall, propagateOnChildren);
 | 
			
		||||
            setFixedAcls(nodeId, inheritFrom, mergeFrom, sharedAclToReplace, changes, set, asyncCall, propagateOnChildren, forceSharedACL);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -91,6 +91,11 @@ public interface AccessControlListDAO
 | 
			
		||||
     */
 | 
			
		||||
    public List<AclChange> setInheritanceForChildren(NodeRef parent, Long inheritFrom, Long sharedAclToReplace, boolean asyncCall);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the inheritance on a given node and it's children. If an unexpected ACL occurs in a child, it can be overriden by setting forceSharedACL
 | 
			
		||||
     */
 | 
			
		||||
    public List<AclChange> setInheritanceForChildren(NodeRef parent, Long inheritFrom, Long sharedAclToReplace, boolean asyncCall, boolean forceSharedACL);
 | 
			
		||||
 | 
			
		||||
    public Long getIndirectAcl(NodeRef nodeRef);
 | 
			
		||||
 | 
			
		||||
    public Long getInheritedAcl(NodeRef nodeRef);
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.repo.batch.BatchProcessWorkProvider;
 | 
			
		||||
import org.alfresco.repo.batch.BatchProcessor;
 | 
			
		||||
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
 | 
			
		||||
import org.alfresco.repo.domain.node.NodeDAO;
 | 
			
		||||
import org.alfresco.repo.domain.node.NodeDAO.NodeRefQueryCallback;
 | 
			
		||||
import org.alfresco.repo.lock.JobLockService;
 | 
			
		||||
@@ -50,6 +51,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
 | 
			
		||||
import org.alfresco.repo.security.permissions.PermissionServicePolicies;
 | 
			
		||||
import org.alfresco.repo.security.permissions.PermissionServicePolicies.OnInheritPermissionsDisabled;
 | 
			
		||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
 | 
			
		||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
 | 
			
		||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | 
			
		||||
import org.alfresco.repo.transaction.TransactionListenerAdapter;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
@@ -64,6 +66,8 @@ import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.springframework.beans.BeansException;
 | 
			
		||||
import org.springframework.context.ApplicationContext;
 | 
			
		||||
import org.springframework.context.ApplicationContextAware;
 | 
			
		||||
import org.springframework.context.ApplicationEventPublisher;
 | 
			
		||||
import org.springframework.dao.ConcurrencyFailureException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Finds nodes with ASPECT_PENDING_FIX_ACL aspect and sets fixed ACLs for them
 | 
			
		||||
@@ -91,6 +95,7 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
 | 
			
		||||
 | 
			
		||||
    private int maxItemBatchSize = 100;
 | 
			
		||||
    private int numThreads = 4;
 | 
			
		||||
    private boolean forceSharedACL = false;
 | 
			
		||||
 | 
			
		||||
    private ClassPolicyDelegate<OnInheritPermissionsDisabled> onInheritPermissionsDisabledDelegate;
 | 
			
		||||
    private PolicyComponent policyComponent;
 | 
			
		||||
@@ -132,6 +137,11 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
 | 
			
		||||
        this.maxItemBatchSize = maxItemBatchSize;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setForceSharedACL(boolean forceSharedACL)
 | 
			
		||||
    {
 | 
			
		||||
        this.forceSharedACL = forceSharedACL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setLockTimeToLive(long lockTimeToLive)
 | 
			
		||||
    {
 | 
			
		||||
        this.lockTimeToLive = lockTimeToLive;
 | 
			
		||||
@@ -182,7 +192,7 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
 | 
			
		||||
                        public List<NodeRef> execute() throws Throwable
 | 
			
		||||
                        {
 | 
			
		||||
                            getNodesCallback.init();
 | 
			
		||||
                            nodeDAO.getNodesWithAspects(aspects, getNodesCallback.getMinNodeId(), null, getNodesCallback);
 | 
			
		||||
                            nodeDAO.getNodesWithAspects(aspects, getNodesCallback.getMinNodeId(), null, true, getNodesCallback);
 | 
			
		||||
                            getNodesCallback.done();
 | 
			
		||||
 | 
			
		||||
                            return getNodesCallback.getNodes();
 | 
			
		||||
@@ -253,7 +263,7 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void process(final NodeRef nodeRef) throws Throwable
 | 
			
		||||
        public void process(final NodeRef nodeRef) 
 | 
			
		||||
        {
 | 
			
		||||
            RunAsWork<Void> findAndUpdateAclRunAsWork = new RunAsWork<Void>()
 | 
			
		||||
            {
 | 
			
		||||
@@ -265,34 +275,44 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
 | 
			
		||||
                        log.debug(String.format("Processing node %s", nodeRef));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    final Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst();
 | 
			
		||||
 | 
			
		||||
                    // MNT-22009 - If node was deleted and in archive store, remove the aspect and properties and do not
 | 
			
		||||
                    // process
 | 
			
		||||
                    if (nodeRef.getStoreRef().equals(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE))
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        final Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst();
 | 
			
		||||
 | 
			
		||||
                        // MNT-22009 - If node was deleted and in archive store, remove the aspect and properties and do
 | 
			
		||||
                        // not
 | 
			
		||||
                        // process
 | 
			
		||||
                        if (nodeRef.getStoreRef().equals(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE))
 | 
			
		||||
                        {
 | 
			
		||||
                            accessControlListDAO.removePendingAclAspect(nodeId);
 | 
			
		||||
                            return null;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // retrieve acl properties from node
 | 
			
		||||
                        Long inheritFrom = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_INHERIT_FROM_ACL);
 | 
			
		||||
                        Long sharedAclToReplace = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_SHARED_ACL_TO_REPLACE);
 | 
			
		||||
 | 
			
		||||
                        // set inheritance using retrieved prop
 | 
			
		||||
                        accessControlListDAO.setInheritanceForChildren(nodeRef, inheritFrom, sharedAclToReplace, true,
 | 
			
		||||
                                forceSharedACL);
 | 
			
		||||
 | 
			
		||||
                        // Remove aspect
 | 
			
		||||
                        accessControlListDAO.removePendingAclAspect(nodeId);
 | 
			
		||||
                        return null;
 | 
			
		||||
 | 
			
		||||
                        if (!policyIgnoreUtil.ignorePolicy(nodeRef))
 | 
			
		||||
                        {
 | 
			
		||||
                            boolean transformedToAsyncOperation = toBoolean((Boolean) AlfrescoTransactionSupport
 | 
			
		||||
                                    .getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY));
 | 
			
		||||
 | 
			
		||||
                            OnInheritPermissionsDisabled onInheritPermissionsDisabledPolicy = onInheritPermissionsDisabledDelegate
 | 
			
		||||
                                    .get(ContentModel.TYPE_BASE);
 | 
			
		||||
                            onInheritPermissionsDisabledPolicy.onInheritPermissionsDisabled(nodeRef, transformedToAsyncOperation);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // retrieve acl properties from node
 | 
			
		||||
                    Long inheritFrom = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_INHERIT_FROM_ACL);
 | 
			
		||||
                    Long sharedAclToReplace = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_SHARED_ACL_TO_REPLACE);
 | 
			
		||||
 | 
			
		||||
                    // set inheritance using retrieved prop
 | 
			
		||||
                    accessControlListDAO.setInheritanceForChildren(nodeRef, inheritFrom, sharedAclToReplace, true);
 | 
			
		||||
 | 
			
		||||
                    // Remove aspect
 | 
			
		||||
                    accessControlListDAO.removePendingAclAspect(nodeId);
 | 
			
		||||
 | 
			
		||||
                    if (!policyIgnoreUtil.ignorePolicy(nodeRef))
 | 
			
		||||
                    catch (Exception e)
 | 
			
		||||
                    {
 | 
			
		||||
                        boolean transformedToAsyncOperation = toBoolean(
 | 
			
		||||
                                (Boolean) AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY));
 | 
			
		||||
 | 
			
		||||
                        OnInheritPermissionsDisabled onInheritPermissionsDisabledPolicy = onInheritPermissionsDisabledDelegate
 | 
			
		||||
                                .get(ContentModel.TYPE_BASE);
 | 
			
		||||
                        onInheritPermissionsDisabledPolicy.onInheritPermissionsDisabled(nodeRef, transformedToAsyncOperation);
 | 
			
		||||
                        log.error("Job could not process pending ACL node " + nodeRef + ": " + e);
 | 
			
		||||
                        e.printStackTrace();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (log.isDebugEnabled())
 | 
			
		||||
@@ -308,6 +328,7 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
 | 
			
		||||
            AuthenticationUtil.runAs(findAndUpdateAclRunAsWork, AuthenticationUtil.getSystemUserName());
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private class GetNodesWithAspectCallback implements NodeRefQueryCallback
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -69,19 +69,19 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
    public static final String PROPERTY_READ_ONLY = "system.delete_not_exists.read_only";
 | 
			
		||||
    public static final String PROPERTY_TIMEOUT_SECONDS = "system.delete_not_exists.timeout_seconds";
 | 
			
		||||
 | 
			
		||||
    private Connection connection;
 | 
			
		||||
    protected Connection connection;
 | 
			
		||||
    private String sql;
 | 
			
		||||
    private int line;
 | 
			
		||||
    private File scriptFile;
 | 
			
		||||
    private Properties globalProperties;
 | 
			
		||||
 | 
			
		||||
    private boolean readOnly;
 | 
			
		||||
    private int deleteBatchSize;
 | 
			
		||||
    private int batchSize;
 | 
			
		||||
    protected boolean readOnly;
 | 
			
		||||
    protected int deleteBatchSize;
 | 
			
		||||
    protected int batchSize;
 | 
			
		||||
    private long timeoutSec;
 | 
			
		||||
 | 
			
		||||
    private long deletedCount;
 | 
			
		||||
    private Date startTime;
 | 
			
		||||
    protected long deletedCount;
 | 
			
		||||
    protected Date startTime;
 | 
			
		||||
 | 
			
		||||
    public DeleteNotExistsExecutor(Connection connection, String sql, int line, File scriptFile, Properties globalProperties)
 | 
			
		||||
    {
 | 
			
		||||
@@ -164,7 +164,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void process(Pair<String, String>[] tableColumn, Long[] tableUpperLimits, String[] optionalWhereClauses) throws SQLException
 | 
			
		||||
    protected void process(Pair<String, String>[] tableColumn, Long[] tableUpperLimits, String[] optionalWhereClauses) throws SQLException
 | 
			
		||||
    {
 | 
			
		||||
        // The approach is to fetch ordered row ids from all referencer/secondary (e.g.
 | 
			
		||||
        // alf_audit_app, alf_audit_entry, alf_prop_unique_ctx) tables and
 | 
			
		||||
@@ -190,6 +190,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            connection.setAutoCommit(false);
 | 
			
		||||
 | 
			
		||||
            primaryPrepStmt = connection.prepareStatement(createPreparedSelectStatement(primaryTableName, primaryColumnName, primaryWhereClause));
 | 
			
		||||
            primaryPrepStmt.setFetchSize(batchSize);
 | 
			
		||||
            primaryPrepStmt.setLong(1, primaryId);
 | 
			
		||||
@@ -264,7 +265,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isTimeoutExceeded()
 | 
			
		||||
    protected boolean isTimeoutExceeded()
 | 
			
		||||
    {
 | 
			
		||||
        if (timeoutSec <= 0)
 | 
			
		||||
        {
 | 
			
		||||
@@ -275,7 +276,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        return (now.getTime() > startTime.getTime() + (timeoutSec * 1000));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Long processPrimaryTableResultSet(PreparedStatement primaryPrepStmt, PreparedStatement[] secondaryPrepStmts, PreparedStatement deletePrepStmt, Set<Long> deleteIds, String primaryTableName,
 | 
			
		||||
    protected Long processPrimaryTableResultSet(PreparedStatement primaryPrepStmt, PreparedStatement[] secondaryPrepStmts, PreparedStatement deletePrepStmt, Set<Long> deleteIds, String primaryTableName,
 | 
			
		||||
            String primaryColumnName, Pair<String, String>[] tableColumn) throws SQLException
 | 
			
		||||
    {
 | 
			
		||||
        int rowsProcessed = 0;
 | 
			
		||||
@@ -336,7 +337,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        return primaryId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void deleteFromPrimaryTable(PreparedStatement deletePrepStmt, Set<Long> deleteIds, String primaryTableName) throws SQLException
 | 
			
		||||
    protected void deleteFromPrimaryTable(PreparedStatement deletePrepStmt, Set<Long> deleteIds, String primaryTableName) throws SQLException
 | 
			
		||||
    {
 | 
			
		||||
        int deletedBatchCount = deleteIds.size();
 | 
			
		||||
        if (!readOnly && !deleteIds.isEmpty())
 | 
			
		||||
@@ -425,7 +426,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        return batchUpperLimit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isLess(Long primaryId, Long[] secondaryIds)
 | 
			
		||||
    protected boolean isLess(Long primaryId, Long[] secondaryIds)
 | 
			
		||||
    {
 | 
			
		||||
        for (Long secondaryId : secondaryIds)
 | 
			
		||||
        {
 | 
			
		||||
@@ -447,8 +448,8 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
 | 
			
		||||
        return quotedString.replace("\"", "");
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private String createPreparedSelectStatement(String tableName, String columnName, String whereClause)
 | 
			
		||||
 | 
			
		||||
    protected String createPreparedSelectStatement(String tableName, String columnName, String whereClause)
 | 
			
		||||
    {
 | 
			
		||||
        StringBuilder sqlBuilder = new StringBuilder("SELECT " + columnName + " FROM " + tableName + " WHERE ");
 | 
			
		||||
 | 
			
		||||
@@ -461,7 +462,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        return sqlBuilder.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String createPreparedDeleteStatement(String tableName, String idColumnName, int deleteBatchSize, String whereClause)
 | 
			
		||||
    protected String createPreparedDeleteStatement(String tableName, String idColumnName, int deleteBatchSize, String whereClause)
 | 
			
		||||
    {
 | 
			
		||||
        StringBuilder stmtBuilder = new StringBuilder("DELETE FROM " + tableName + " WHERE ");
 | 
			
		||||
 | 
			
		||||
@@ -515,7 +516,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Long getColumnValueById(ResultSet resultSet, String columnId) throws SQLException
 | 
			
		||||
    protected Long getColumnValueById(ResultSet resultSet, String columnId) throws SQLException
 | 
			
		||||
    {
 | 
			
		||||
        Long columnValue = null;
 | 
			
		||||
        if (resultSet != null && resultSet.next())
 | 
			
		||||
@@ -526,7 +527,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        return columnValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ResultSet[] getSecondaryResultSets(PreparedStatement[] preparedStatements) throws SQLException
 | 
			
		||||
    protected ResultSet[] getSecondaryResultSets(PreparedStatement[] preparedStatements) throws SQLException
 | 
			
		||||
    {
 | 
			
		||||
        ResultSet[] secondaryResultSets = new ResultSet[preparedStatements.length];
 | 
			
		||||
        for (int i = 1; i < preparedStatements.length; i++)
 | 
			
		||||
@@ -540,7 +541,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        return secondaryResultSets;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Long[] getSecondaryIds(ResultSet[] secondaryResultSets, Pair<String, String>[] tableColumn) throws SQLException
 | 
			
		||||
    protected Long[] getSecondaryIds(ResultSet[] secondaryResultSets, Pair<String, String>[] tableColumn) throws SQLException
 | 
			
		||||
    {
 | 
			
		||||
        Long[] secondaryIds = new Long[tableColumn.length];
 | 
			
		||||
 | 
			
		||||
@@ -571,7 +572,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void closeQuietly(Statement statement)
 | 
			
		||||
    protected void closeQuietly(Statement statement)
 | 
			
		||||
    {
 | 
			
		||||
        if (statement != null)
 | 
			
		||||
        {
 | 
			
		||||
@@ -586,7 +587,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void closeQuietly(Statement[] statements)
 | 
			
		||||
    protected void closeQuietly(Statement[] statements)
 | 
			
		||||
    {
 | 
			
		||||
        if (statements != null)
 | 
			
		||||
        {
 | 
			
		||||
@@ -597,7 +598,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void closeQuietly(ResultSet resultSet)
 | 
			
		||||
    protected void closeQuietly(ResultSet resultSet)
 | 
			
		||||
    {
 | 
			
		||||
        if (resultSet != null)
 | 
			
		||||
        {
 | 
			
		||||
@@ -612,7 +613,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void closeQuietly(ResultSet[] resultSets)
 | 
			
		||||
    protected void closeQuietly(ResultSet[] resultSets)
 | 
			
		||||
    {
 | 
			
		||||
        if (resultSets != null)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,278 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2020 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.domain.schema.script;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
 | 
			
		||||
import javax.sql.DataSource;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.sql.Connection;
 | 
			
		||||
import java.sql.PreparedStatement;
 | 
			
		||||
import java.sql.ResultSet;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Properties;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Extends <code>{@link DeleteNotExistsExecutor}</code> to cope with MySQL
 | 
			
		||||
 * specific fetch size limitation and restrictions.
 | 
			
		||||
 */
 | 
			
		||||
public class MySQLDeleteNotExistsExecutor extends DeleteNotExistsExecutor
 | 
			
		||||
{
 | 
			
		||||
    private static final Log logger = LogFactory.getLog(MySQLDeleteNotExistsExecutor.class);
 | 
			
		||||
    
 | 
			
		||||
    private final DataSource dataSource;
 | 
			
		||||
 | 
			
		||||
    public MySQLDeleteNotExistsExecutor(Connection connection, String sql, int line, File scriptFile, Properties globalProperties, DataSource dataSource)
 | 
			
		||||
    {
 | 
			
		||||
        super(connection, sql, line, scriptFile, globalProperties);
 | 
			
		||||
        this.dataSource = dataSource;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void process(Pair<String, String>[] tableColumn, Long[] tableUpperLimits, String[] optionalWhereClauses) throws SQLException
 | 
			
		||||
    {
 | 
			
		||||
        // The approach is to fetch ordered row ids from all referencer/secondary (e.g.
 | 
			
		||||
        // alf_audit_app, alf_audit_entry, alf_prop_unique_ctx) tables and
 | 
			
		||||
        // referenced/primary table (e.g. alf_prop_root) concurrently, so that it is
 | 
			
		||||
        // possible skip over id gaps efficiently while at the same time being able to
 | 
			
		||||
        // work out which ids are obsolete and delete them in batches.
 | 
			
		||||
 | 
			
		||||
        // The algorithm can be further improved by iterating over the rows in descending order.
 | 
			
		||||
        // This is due to the fact that older data should be more stable in time.
 | 
			
		||||
 | 
			
		||||
        String primaryTableName = tableColumn[0].getFirst();
 | 
			
		||||
        String primaryColumnName = tableColumn[0].getSecond();
 | 
			
		||||
        String primaryWhereClause = optionalWhereClauses[0];
 | 
			
		||||
 | 
			
		||||
        Long primaryId = 0L;
 | 
			
		||||
 | 
			
		||||
        PreparedStatement primaryPrepStmt = null;
 | 
			
		||||
        PreparedStatement[] secondaryPrepStmts = null;
 | 
			
		||||
        PreparedStatement deletePrepStmt = null;
 | 
			
		||||
        Set<Long> deleteIds = new HashSet<>();
 | 
			
		||||
 | 
			
		||||
        deletedCount = 0L;
 | 
			
		||||
        startTime = new Date();
 | 
			
		||||
 | 
			
		||||
        long defaultOffset = 0L;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            connection.setAutoCommit(false);
 | 
			
		||||
            
 | 
			
		||||
            primaryPrepStmt = connection.prepareStatement(createLimitPreparedSelectStatement(primaryTableName, primaryColumnName, primaryWhereClause));
 | 
			
		||||
            primaryPrepStmt.setLong(1, primaryId);
 | 
			
		||||
            primaryPrepStmt.setLong(2, tableUpperLimits[0]);
 | 
			
		||||
            primaryPrepStmt.setInt(3, batchSize);
 | 
			
		||||
            primaryPrepStmt.setLong(4, defaultOffset);
 | 
			
		||||
 | 
			
		||||
            boolean hasResults = primaryPrepStmt.execute();
 | 
			
		||||
 | 
			
		||||
            if (hasResults)
 | 
			
		||||
            {
 | 
			
		||||
                secondaryPrepStmts = new PreparedStatement[tableColumn.length];
 | 
			
		||||
                for (int i = 1; i < tableColumn.length; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    PreparedStatement secStmt = connection.prepareStatement(createLimitPreparedSelectStatement(tableColumn[i].getFirst(), tableColumn[i].getSecond(), optionalWhereClauses[i]));
 | 
			
		||||
                    secStmt.setLong(1, primaryId);
 | 
			
		||||
                    secStmt.setLong(2, tableUpperLimits[i]);
 | 
			
		||||
                    secStmt.setInt(3, batchSize);
 | 
			
		||||
                    secStmt.setLong(4, defaultOffset);
 | 
			
		||||
 | 
			
		||||
                    secondaryPrepStmts[i] = secStmt;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                deletePrepStmt = connection.prepareStatement(createPreparedDeleteStatement(primaryTableName, primaryColumnName, deleteBatchSize, primaryWhereClause));
 | 
			
		||||
 | 
			
		||||
                // Timeout is only checked at each bach start.
 | 
			
		||||
                // It can be further refined by being verified at each primary row processing.
 | 
			
		||||
                while (hasResults && !isTimeoutExceeded())
 | 
			
		||||
                {
 | 
			
		||||
                    // Process batch
 | 
			
		||||
                    primaryId = processPrimaryTableResultSet(primaryPrepStmt, secondaryPrepStmts, deletePrepStmt, deleteIds, primaryTableName, primaryColumnName, tableColumn);
 | 
			
		||||
                    connection.commit();
 | 
			
		||||
 | 
			
		||||
                    if (primaryId == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Prepare for next batch
 | 
			
		||||
                    primaryPrepStmt.setLong(1, primaryId);
 | 
			
		||||
                    primaryPrepStmt.setLong(2, tableUpperLimits[0]);
 | 
			
		||||
                    primaryPrepStmt.setInt(3, batchSize);
 | 
			
		||||
                    primaryPrepStmt.setLong(4, defaultOffset);
 | 
			
		||||
 | 
			
		||||
                    for (int i = 1; i < tableColumn.length; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        PreparedStatement secStmt = secondaryPrepStmts[i];
 | 
			
		||||
                        secStmt.setLong(1, primaryId);
 | 
			
		||||
                        secStmt.setLong(2, tableUpperLimits[i]);
 | 
			
		||||
                        secStmt.setInt(3, batchSize);
 | 
			
		||||
                        secStmt.setLong(4, defaultOffset);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    hasResults = primaryPrepStmt.execute();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check if we have any more ids to delete
 | 
			
		||||
            if (!deleteIds.isEmpty())
 | 
			
		||||
            {
 | 
			
		||||
                deleteFromPrimaryTable(deletePrepStmt, deleteIds, primaryTableName);
 | 
			
		||||
                connection.commit();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (logger.isDebugEnabled())
 | 
			
		||||
            {
 | 
			
		||||
                String msg = ((readOnly) ? "Script would have" : "Script") + " deleted a total of " + deletedCount + " items from table " + primaryTableName + ".";
 | 
			
		||||
                logger.debug(msg);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            closeQuietly(deletePrepStmt);
 | 
			
		||||
            closeQuietly(secondaryPrepStmts);
 | 
			
		||||
            closeQuietly(primaryPrepStmt);
 | 
			
		||||
 | 
			
		||||
            connection.setAutoCommit(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected Long processPrimaryTableResultSet(PreparedStatement primaryPrepStmt, PreparedStatement[] secondaryPrepStmts, PreparedStatement deletePrepStmt, Set<Long> deleteIds,
 | 
			
		||||
            String primaryTableName, String primaryColumnName, Pair<String, String>[] tableColumn) throws SQLException
 | 
			
		||||
    {
 | 
			
		||||
        int rowsProcessed = 0;
 | 
			
		||||
        Long primaryId = null;
 | 
			
		||||
        ResultSet[] secondaryResultSets = null;
 | 
			
		||||
        try (ResultSet resultSet = primaryPrepStmt.getResultSet())
 | 
			
		||||
        {
 | 
			
		||||
            secondaryResultSets = getSecondaryResultSets(secondaryPrepStmts);
 | 
			
		||||
            Long[] secondaryIds = getSecondaryIds(secondaryResultSets, tableColumn);
 | 
			
		||||
 | 
			
		||||
            // Create and populate secondary tables offsets
 | 
			
		||||
            Long[] secondaryOffsets = new Long[tableColumn.length];
 | 
			
		||||
            for (int i = 1; i < tableColumn.length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                secondaryOffsets[i] = 0L;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            while (resultSet.next())
 | 
			
		||||
            {
 | 
			
		||||
                ++rowsProcessed;
 | 
			
		||||
                primaryId = resultSet.getLong(primaryColumnName);
 | 
			
		||||
 | 
			
		||||
                while (isLess(primaryId, secondaryIds))
 | 
			
		||||
                {
 | 
			
		||||
                    deleteIds.add(primaryId);
 | 
			
		||||
 | 
			
		||||
                    if (deleteIds.size() == deleteBatchSize)
 | 
			
		||||
                    {
 | 
			
		||||
                        deleteFromPrimaryTable(deletePrepStmt, deleteIds, primaryTableName);
 | 
			
		||||
                        connection.commit();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!resultSet.next())
 | 
			
		||||
                    {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    ++rowsProcessed;
 | 
			
		||||
                    primaryId = resultSet.getLong(primaryColumnName);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (logger.isTraceEnabled())
 | 
			
		||||
                {
 | 
			
		||||
                    logger.trace("RowsProcessed " + rowsProcessed + " from primary table " + primaryTableName);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                updateSecondaryIds(primaryId, secondaryIds, secondaryPrepStmts, secondaryOffsets, secondaryResultSets, tableColumn);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            closeQuietly(secondaryResultSets);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return primaryId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateSecondaryIds(Long primaryId, Long[] secondaryIds, PreparedStatement[] secondaryPrepStmts, Long[] secondaryOffsets, ResultSet[] secondaryResultSets,
 | 
			
		||||
            Pair<String, String>[] tableColumn) throws SQLException
 | 
			
		||||
    {
 | 
			
		||||
        for (int i = 1; i < tableColumn.length; i++)
 | 
			
		||||
        {
 | 
			
		||||
            Long secondaryId = secondaryIds[i];
 | 
			
		||||
            while (secondaryId != null && primaryId >= secondaryId)
 | 
			
		||||
            {
 | 
			
		||||
                ResultSet resultSet = secondaryResultSets[i];
 | 
			
		||||
                String columnId = tableColumn[i].getSecond();
 | 
			
		||||
 | 
			
		||||
                secondaryId = getColumnValueById(resultSet, columnId);
 | 
			
		||||
 | 
			
		||||
                // Check if we reach the end of the first page
 | 
			
		||||
                if (secondaryId == null)
 | 
			
		||||
                {
 | 
			
		||||
                    // Close the previous result set
 | 
			
		||||
                    closeQuietly(resultSet);
 | 
			
		||||
 | 
			
		||||
                    // Set to use the next page
 | 
			
		||||
                    long offset = secondaryOffsets[i] + batchSize;
 | 
			
		||||
                    secondaryOffsets[i] = offset;
 | 
			
		||||
 | 
			
		||||
                    PreparedStatement secStmt = secondaryPrepStmts[i];
 | 
			
		||||
                    secStmt.setLong(4, offset);
 | 
			
		||||
 | 
			
		||||
                    // Check if any results were found
 | 
			
		||||
                    boolean secHasResults = secStmt.execute();
 | 
			
		||||
                    secondaryResultSets[i] = secHasResults ? secStmt.getResultSet() : null;
 | 
			
		||||
 | 
			
		||||
                    // Try again to get the next secondary id
 | 
			
		||||
                    secondaryId = getColumnValueById(secondaryResultSets[i], columnId);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                secondaryIds[i] = secondaryId;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String createLimitPreparedSelectStatement(String tableName, String columnName, String whereClause)
 | 
			
		||||
    {
 | 
			
		||||
        StringBuilder sqlBuilder = new StringBuilder("SELECT " + columnName + " FROM " + tableName + " WHERE ");
 | 
			
		||||
 | 
			
		||||
        if (whereClause != null && !whereClause.isEmpty())
 | 
			
		||||
        {
 | 
			
		||||
            sqlBuilder.append(whereClause + " AND ");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sqlBuilder.append(columnName + " > ? AND " + columnName + " <= ? ORDER BY " + columnName + " ASC LIMIT ? OFFSET ?");
 | 
			
		||||
        return sqlBuilder.toString();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -350,7 +350,7 @@ public class ScriptExecutorImpl implements ScriptExecutor
 | 
			
		||||
                }
 | 
			
		||||
                else if (sql.startsWith("--DELETE_NOT_EXISTS"))
 | 
			
		||||
                {
 | 
			
		||||
                    DeleteNotExistsExecutor deleteNotExists = new DeleteNotExistsExecutor(connection, sql, line, scriptFile, globalProperties);
 | 
			
		||||
                    DeleteNotExistsExecutor deleteNotExists = createDeleteNotExistsExecutor(dialect, connection, sql, line, scriptFile);
 | 
			
		||||
                    deleteNotExists.execute();
 | 
			
		||||
 | 
			
		||||
                    // Reset
 | 
			
		||||
@@ -537,7 +537,17 @@ public class ScriptExecutorImpl implements ScriptExecutor
 | 
			
		||||
            try { scriptInputStream.close(); } catch (Throwable e) {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private DeleteNotExistsExecutor createDeleteNotExistsExecutor(Dialect dialect, Connection connection, String sql, int line, File scriptFile)
 | 
			
		||||
    {
 | 
			
		||||
        if (dialect instanceof MySQLInnoDBDialect)
 | 
			
		||||
        {
 | 
			
		||||
            return new MySQLDeleteNotExistsExecutor(connection, sql, line, scriptFile, globalProperties, dataSource);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new DeleteNotExistsExecutor(connection, sql, line, scriptFile, globalProperties);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute the given SQL statement, absorbing exceptions that we expect during
 | 
			
		||||
     * schema creation or upgrade.
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,6 @@ import org.alfresco.repo.policy.JavaBehaviour;
 | 
			
		||||
import org.alfresco.repo.policy.PolicyComponent;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | 
			
		||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
 | 
			
		||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | 
			
		||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.AssociationRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
			
		||||
@@ -90,11 +89,11 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
    protected DictionaryService dictionaryService;
 | 
			
		||||
    private DescriptorService descriptorService;
 | 
			
		||||
    private EventFilterRegistry eventFilterRegistry;
 | 
			
		||||
    private Event2MessageProducer event2MessageProducer;
 | 
			
		||||
    private TransactionService transactionService;
 | 
			
		||||
    private PersonService personService;
 | 
			
		||||
    protected NodeResourceHelper nodeResourceHelper;
 | 
			
		||||
 | 
			
		||||
    private EventGeneratorQueue eventGeneratorQueue;
 | 
			
		||||
    private NodeTypeFilter nodeTypeFilter;
 | 
			
		||||
    private ChildAssociationTypeFilter childAssociationTypeFilter;
 | 
			
		||||
    private EventUserFilter userFilter;
 | 
			
		||||
@@ -109,10 +108,10 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
        PropertyCheck.mandatory(this, "dictionaryService", dictionaryService);
 | 
			
		||||
        PropertyCheck.mandatory(this, "descriptorService", descriptorService);
 | 
			
		||||
        PropertyCheck.mandatory(this, "eventFilterRegistry", eventFilterRegistry);
 | 
			
		||||
        PropertyCheck.mandatory(this, "event2MessageProducer", event2MessageProducer);
 | 
			
		||||
        PropertyCheck.mandatory(this, "transactionService", transactionService);
 | 
			
		||||
        PropertyCheck.mandatory(this, "personService", personService);
 | 
			
		||||
        PropertyCheck.mandatory(this, "nodeResourceHelper", nodeResourceHelper);
 | 
			
		||||
        PropertyCheck.mandatory(this, "eventGeneratorQueue", eventGeneratorQueue);
 | 
			
		||||
 | 
			
		||||
        this.nodeTypeFilter = eventFilterRegistry.getNodeTypeFilter();
 | 
			
		||||
        this.childAssociationTypeFilter = eventFilterRegistry.getChildAssociationTypeFilter();
 | 
			
		||||
@@ -177,12 +176,6 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
        this.eventFilterRegistry = eventFilterRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unused")
 | 
			
		||||
    public void setEvent2MessageProducer(Event2MessageProducer event2MessageProducer)
 | 
			
		||||
    {
 | 
			
		||||
        this.event2MessageProducer = event2MessageProducer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setTransactionService(TransactionService transactionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.transactionService = transactionService;
 | 
			
		||||
@@ -198,6 +191,11 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
        this.nodeResourceHelper = nodeResourceHelper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setEventGeneratorQueue(EventGeneratorQueue eventGeneratorQueue)
 | 
			
		||||
    {
 | 
			
		||||
        this.eventGeneratorQueue = eventGeneratorQueue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onCreateNode(ChildAssociationRef childAssocRef)
 | 
			
		||||
    {
 | 
			
		||||
@@ -428,20 +426,26 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
 | 
			
		||||
        protected void sendEvent(NodeRef nodeRef, EventConsolidator consolidator)
 | 
			
		||||
        {
 | 
			
		||||
            EventInfo eventInfo = getEventInfo(AuthenticationUtil.getFullyAuthenticatedUser());
 | 
			
		||||
            eventGeneratorQueue.accept(()-> createEvent(nodeRef, consolidator, eventInfo));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private RepoEvent<?> createEvent(NodeRef nodeRef, EventConsolidator consolidator, EventInfo eventInfo)
 | 
			
		||||
        {
 | 
			
		||||
            String user = eventInfo.getPrincipal();
 | 
			
		||||
 | 
			
		||||
            if (consolidator.isTemporaryNode())
 | 
			
		||||
            {
 | 
			
		||||
                if (LOGGER.isTraceEnabled())
 | 
			
		||||
                {
 | 
			
		||||
                    LOGGER.trace("Ignoring temporary node: " + nodeRef);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            final String user = AuthenticationUtil.getFullyAuthenticatedUser();
 | 
			
		||||
            // Get the repo event before the filtering,
 | 
			
		||||
            // so we can take the latest node info into account
 | 
			
		||||
            final RepoEvent<?> event = consolidator.getRepoEvent(getEventInfo(user));
 | 
			
		||||
 | 
			
		||||
            final RepoEvent<?> event = consolidator.getRepoEvent(eventInfo);
 | 
			
		||||
 | 
			
		||||
            final QName nodeType = consolidator.getNodeType();
 | 
			
		||||
            if (isFiltered(nodeType, user))
 | 
			
		||||
@@ -452,7 +456,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
                            + ((nodeType == null) ? "Unknown' " : nodeType.toPrefixString())
 | 
			
		||||
                            + "' created by: " + user);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (event.getType().equals(EventType.NODE_UPDATED.getType()) && consolidator.isResourceBeforeAllFieldsNull())
 | 
			
		||||
@@ -461,27 +465,34 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
                {
 | 
			
		||||
                    LOGGER.trace("Ignoring node updated event as no fields have been updated: " + nodeRef);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            logAndSendEvent(event, consolidator.getEventTypes());
 | 
			
		||||
            logEvent(event, consolidator.getEventTypes());
 | 
			
		||||
            return event;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected void sendEvent(ChildAssociationRef childAssociationRef, ChildAssociationEventConsolidator consolidator)
 | 
			
		||||
        {
 | 
			
		||||
            EventInfo eventInfo = getEventInfo(AuthenticationUtil.getFullyAuthenticatedUser());
 | 
			
		||||
            eventGeneratorQueue.accept(()-> createEvent(eventInfo, childAssociationRef, consolidator));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private RepoEvent<?> createEvent(EventInfo eventInfo, ChildAssociationRef childAssociationRef, ChildAssociationEventConsolidator consolidator)
 | 
			
		||||
        {
 | 
			
		||||
            String user = eventInfo.getPrincipal();
 | 
			
		||||
            if (consolidator.isTemporaryChildAssociation())
 | 
			
		||||
            {
 | 
			
		||||
                if (LOGGER.isTraceEnabled())
 | 
			
		||||
                {
 | 
			
		||||
                    LOGGER.trace("Ignoring temporary child association: " + childAssociationRef);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            final String user = AuthenticationUtil.getFullyAuthenticatedUser();
 | 
			
		||||
            // Get the repo event before the filtering,
 | 
			
		||||
            // so we can take the latest association info into account
 | 
			
		||||
            final RepoEvent<?> event = consolidator.getRepoEvent(getEventInfo(user));
 | 
			
		||||
            final RepoEvent<?> event = consolidator.getRepoEvent(eventInfo);
 | 
			
		||||
 | 
			
		||||
            final QName childAssocType = consolidator.getChildAssocType();
 | 
			
		||||
            if (isFilteredChildAssociation(childAssocType, user))
 | 
			
		||||
@@ -492,7 +503,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
                            + ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString())
 | 
			
		||||
                            + "' created by: " + user);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
                return null;
 | 
			
		||||
            } else if (childAssociationRef.isPrimary())
 | 
			
		||||
            {
 | 
			
		||||
                if (LOGGER.isTraceEnabled())
 | 
			
		||||
@@ -501,13 +512,20 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
                            + ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString())
 | 
			
		||||
                            + "' created by: " + user);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            logAndSendEvent(event, consolidator.getEventTypes());
 | 
			
		||||
            logEvent(event, consolidator.getEventTypes());
 | 
			
		||||
            return event;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected void sendEvent(AssociationRef peerAssociationRef, PeerAssociationEventConsolidator consolidator)
 | 
			
		||||
        {
 | 
			
		||||
            EventInfo eventInfo = getEventInfo(AuthenticationUtil.getFullyAuthenticatedUser());
 | 
			
		||||
            eventGeneratorQueue.accept(()-> createEvent(eventInfo, peerAssociationRef, consolidator));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private RepoEvent<?> createEvent(EventInfo eventInfo, AssociationRef peerAssociationRef, PeerAssociationEventConsolidator consolidator)
 | 
			
		||||
        {
 | 
			
		||||
            if (consolidator.isTemporaryPeerAssociation())
 | 
			
		||||
            {
 | 
			
		||||
@@ -515,30 +533,21 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
 | 
			
		||||
                {
 | 
			
		||||
                    LOGGER.trace("Ignoring temporary peer association: " + peerAssociationRef);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            final String user = AuthenticationUtil.getFullyAuthenticatedUser();
 | 
			
		||||
            // Get the repo event before the filtering,
 | 
			
		||||
            // so we can take the latest association info into account
 | 
			
		||||
            final RepoEvent<?> event = consolidator.getRepoEvent(getEventInfo(user));
 | 
			
		||||
 | 
			
		||||
            logAndSendEvent(event, consolidator.getEventTypes());
 | 
			
		||||
            RepoEvent<?> event = consolidator.getRepoEvent(eventInfo);
 | 
			
		||||
            logEvent(event, consolidator.getEventTypes());
 | 
			
		||||
            return event;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected void logAndSendEvent(RepoEvent<?> event, Deque<EventType> listOfEvents)
 | 
			
		||||
        private void logEvent(RepoEvent<?> event, Deque<EventType> listOfEvents)
 | 
			
		||||
        {
 | 
			
		||||
            if (LOGGER.isTraceEnabled())
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER.trace("List of Events:" + listOfEvents);
 | 
			
		||||
                LOGGER.trace("Sending event:" + event);
 | 
			
		||||
            }
 | 
			
		||||
            // Need to execute this in another read txn because Camel expects it
 | 
			
		||||
            transactionService.getRetryingTransactionHelper().doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                event2MessageProducer.send(event);
 | 
			
		||||
 | 
			
		||||
                return null;
 | 
			
		||||
            }, true, false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,179 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.event2;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.BlockingQueue;
 | 
			
		||||
import java.util.concurrent.Callable;
 | 
			
		||||
import java.util.concurrent.CountDownLatch;
 | 
			
		||||
import java.util.concurrent.Executor;
 | 
			
		||||
import java.util.concurrent.LinkedBlockingQueue;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.event.v1.model.RepoEvent;
 | 
			
		||||
import org.alfresco.util.PropertyCheck;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.springframework.beans.factory.InitializingBean;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * This queue allows to create asynchronously the RepoEvent offloading the work to a ThreadPool but
 | 
			
		||||
 * at the same time it preserves the order of the events
 | 
			
		||||
 */
 | 
			
		||||
public class EventGeneratorQueue implements InitializingBean
 | 
			
		||||
{
 | 
			
		||||
	protected static final Log LOGGER = LogFactory.getLog(EventGeneratorQueue.class);
 | 
			
		||||
    
 | 
			
		||||
    protected Executor enqueueThreadPoolExecutor;
 | 
			
		||||
    protected Executor dequeueThreadPoolExecutor;
 | 
			
		||||
    protected Event2MessageProducer event2MessageProducer;
 | 
			
		||||
    protected BlockingQueue<EventInMaking> queue = new LinkedBlockingQueue<>();
 | 
			
		||||
    protected Runnable listener = createListener();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void afterPropertiesSet() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        PropertyCheck.mandatory(this, "enqueueThreadPoolExecutor", enqueueThreadPoolExecutor);
 | 
			
		||||
        PropertyCheck.mandatory(this, "dequeueThreadPoolExecutor", dequeueThreadPoolExecutor);
 | 
			
		||||
        PropertyCheck.mandatory(this, "event2MessageProducer", event2MessageProducer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setEvent2MessageProducer(Event2MessageProducer event2MessageProducer)
 | 
			
		||||
    {
 | 
			
		||||
        this.event2MessageProducer = event2MessageProducer;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setEnqueueThreadPoolExecutor(Executor enqueueThreadPoolExecutor)
 | 
			
		||||
    {
 | 
			
		||||
        this.enqueueThreadPoolExecutor = enqueueThreadPoolExecutor;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setDequeueThreadPoolExecutor(Executor dequeueThreadPoolExecutor)
 | 
			
		||||
    {
 | 
			
		||||
        this.dequeueThreadPoolExecutor = dequeueThreadPoolExecutor;
 | 
			
		||||
        dequeueThreadPoolExecutor.execute(listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Procedure to enqueue the callback functions that creates an event.
 | 
			
		||||
     * @param maker Callback function that creates an event.
 | 
			
		||||
     */
 | 
			
		||||
    public void accept(Callable<RepoEvent<?>> maker)
 | 
			
		||||
    {
 | 
			
		||||
        EventInMaking eventInMaking = new EventInMaking(maker);
 | 
			
		||||
        queue.offer(eventInMaking);
 | 
			
		||||
        enqueueThreadPoolExecutor.execute(() -> {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                eventInMaking.make();
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception e)
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER.error("Unexpected error while enqueuing maker function for repository event" + e);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create listener task in charge of dequeuing and sending events ready to be sent.
 | 
			
		||||
     * @return The task in charge of dequeuing and sending events ready to be sent.
 | 
			
		||||
     */
 | 
			
		||||
    private Runnable createListener()
 | 
			
		||||
    {
 | 
			
		||||
        return new Runnable()
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void run()
 | 
			
		||||
            {
 | 
			
		||||
                try 
 | 
			
		||||
                {
 | 
			
		||||
                    while (!Thread.interrupted())
 | 
			
		||||
                    {
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            EventInMaking eventInMaking = queue.take();
 | 
			
		||||
                            RepoEvent<?> event = eventInMaking.getEventWhenReady();
 | 
			
		||||
                            if (event != null)
 | 
			
		||||
                            {
 | 
			
		||||
                                event2MessageProducer.send(event);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        catch (Exception e)
 | 
			
		||||
                        {
 | 
			
		||||
                            LOGGER.error("Unexpected error while dequeuing and sending repository event" + e);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    LOGGER.warn("Unexpected: rescheduling the listener thread.");
 | 
			
		||||
                    dequeueThreadPoolExecutor.execute(listener);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Simple class that makes events and allows to retrieve them when ready
 | 
			
		||||
     */
 | 
			
		||||
    private static class EventInMaking
 | 
			
		||||
    {
 | 
			
		||||
        private Callable<RepoEvent<?>> maker;
 | 
			
		||||
        private volatile RepoEvent<?> event;
 | 
			
		||||
        private CountDownLatch latch;
 | 
			
		||||
        
 | 
			
		||||
        public EventInMaking(Callable<RepoEvent<?>> maker)
 | 
			
		||||
        {
 | 
			
		||||
            this.maker = maker;
 | 
			
		||||
            this.latch = new CountDownLatch(1);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public void make() throws Exception
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                event = maker.call();
 | 
			
		||||
            }
 | 
			
		||||
            finally 
 | 
			
		||||
            {
 | 
			
		||||
                latch.countDown();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public RepoEvent<?> getEventWhenReady() throws InterruptedException
 | 
			
		||||
        {
 | 
			
		||||
            latch.await(30, TimeUnit.SECONDS);
 | 
			
		||||
            return event;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        @Override
 | 
			
		||||
        public String toString()
 | 
			
		||||
        {
 | 
			
		||||
            return maker.toString();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,538 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is 
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * 
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.alfresco.repo.rendition.executer;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.io.StringWriter;
 | 
			
		||||
import java.io.Writer;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import javax.xml.transform.OutputKeys;
 | 
			
		||||
import javax.xml.transform.TransformerConfigurationException;
 | 
			
		||||
import javax.xml.transform.sax.SAXTransformerFactory;
 | 
			
		||||
import javax.xml.transform.sax.TransformerHandler;
 | 
			
		||||
import javax.xml.transform.stream.StreamResult;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.repo.action.ParameterDefinitionImpl;
 | 
			
		||||
import org.alfresco.repo.rendition.RenditionLocation;
 | 
			
		||||
import org.alfresco.service.cmr.action.ParameterDefinition;
 | 
			
		||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
 | 
			
		||||
import org.alfresco.service.cmr.rendition.RenditionServiceException;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ContentReader;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ContentService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ContentWriter;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.apache.tika.config.TikaConfig;
 | 
			
		||||
import org.apache.tika.exception.TikaException;
 | 
			
		||||
import org.apache.tika.metadata.Metadata;
 | 
			
		||||
import org.apache.tika.mime.MediaType;
 | 
			
		||||
import org.apache.tika.parser.AutoDetectParser;
 | 
			
		||||
import org.apache.tika.parser.ParseContext;
 | 
			
		||||
import org.apache.tika.parser.Parser;
 | 
			
		||||
import org.apache.tika.sax.BodyContentHandler;
 | 
			
		||||
import org.apache.tika.sax.ContentHandlerDecorator;
 | 
			
		||||
import org.xml.sax.Attributes;
 | 
			
		||||
import org.xml.sax.ContentHandler;
 | 
			
		||||
import org.xml.sax.SAXException;
 | 
			
		||||
import org.xml.sax.helpers.AttributesImpl;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class provides a way to turn documents supported by the
 | 
			
		||||
 *  {@link ContentService} standard transformers into basic, clean
 | 
			
		||||
 *  HTML.
 | 
			
		||||
 * <P/>
 | 
			
		||||
 * The HTML that is produced probably isn't going to be suitable
 | 
			
		||||
 *  for direct web publishing, as it's likely going to be too
 | 
			
		||||
 *  basic. Instead, it should be simple and clean HTML, suitable
 | 
			
		||||
 *  for being the basis of some web-friendly HTML once edited
 | 
			
		||||
 *  / further transformed. 
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Nick Burch
 | 
			
		||||
 * @since 3.4
 | 
			
		||||
 *
 | 
			
		||||
 * @deprecated The RenditionService is being replace by the simpler async RenditionService2.
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
public class HTMLRenderingEngine extends AbstractRenderingEngine
 | 
			
		||||
{
 | 
			
		||||
    private static Log logger = LogFactory.getLog(HTMLRenderingEngine.class);
 | 
			
		||||
    private TikaConfig tikaConfig;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This optional parameter, when set to true, causes only the
 | 
			
		||||
     *  contents of the HTML body to be written out as the rendition.
 | 
			
		||||
     * By default, the whole of the HTML document is used.
 | 
			
		||||
     */
 | 
			
		||||
    public static final String PARAM_BODY_CONTENTS_ONLY = "bodyContentsOnly";
 | 
			
		||||
    /**
 | 
			
		||||
     * This optional parameter, when set to true, causes any embedded
 | 
			
		||||
     *  images to be written into the same folder as the html, with
 | 
			
		||||
     *  a name prefix.
 | 
			
		||||
     * By default, images are placed into a sub-folder.
 | 
			
		||||
     */
 | 
			
		||||
    public static final String PARAM_IMAGES_SAME_FOLDER = "imagesSameFolder";
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Action constants
 | 
			
		||||
     */
 | 
			
		||||
    public static final String NAME = "htmlRenderingEngine";
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    protected Collection<ParameterDefinition> getParameterDefinitions() {
 | 
			
		||||
       Collection<ParameterDefinition> paramList = super.getParameterDefinitions();
 | 
			
		||||
       paramList.add(new ParameterDefinitionImpl(PARAM_BODY_CONTENTS_ONLY, DataTypeDefinition.BOOLEAN, false,
 | 
			
		||||
             getParamDisplayLabel(PARAM_BODY_CONTENTS_ONLY)));
 | 
			
		||||
       paramList.add(new ParameterDefinitionImpl(PARAM_IMAGES_SAME_FOLDER, DataTypeDefinition.BOOLEAN, false,
 | 
			
		||||
             getParamDisplayLabel(PARAM_IMAGES_SAME_FOLDER)));
 | 
			
		||||
       return paramList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Injects the TikaConfig to use
 | 
			
		||||
     * 
 | 
			
		||||
     * @param tikaConfig The Tika Config to use 
 | 
			
		||||
     */
 | 
			
		||||
    public void setTikaConfig(TikaConfig tikaConfig)
 | 
			
		||||
    {
 | 
			
		||||
        this.tikaConfig = tikaConfig;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * (non-Javadoc)
 | 
			
		||||
     * @see org.alfresco.repo.rendition.executer.AbstractRenderingEngine#render(org.alfresco.repo.rendition.executer.AbstractRenderingEngine.RenderingContext)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void render(RenderingContext context)
 | 
			
		||||
    {
 | 
			
		||||
        ContentReader contentReader = context.makeContentReader();
 | 
			
		||||
        String sourceMimeType = contentReader.getMimetype();
 | 
			
		||||
        
 | 
			
		||||
        // Check that Tika supports the supplied file
 | 
			
		||||
        AutoDetectParser p = new AutoDetectParser(tikaConfig);
 | 
			
		||||
        MediaType sourceMediaType = MediaType.parse(sourceMimeType);
 | 
			
		||||
        if(! p.getParsers().containsKey(sourceMediaType))
 | 
			
		||||
        {
 | 
			
		||||
           throw new RenditionServiceException(
 | 
			
		||||
                 "Source mime type of " + sourceMimeType + 
 | 
			
		||||
                 " is not supported by Tika for HTML conversions"
 | 
			
		||||
           );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Make the HTML Version using Tika
 | 
			
		||||
        // This will also extract out any images as found
 | 
			
		||||
        generateHTML(p, context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getHtmlBaseName(RenderingContext context)
 | 
			
		||||
    {
 | 
			
		||||
       // Based on the name of the source node, which will
 | 
			
		||||
       //  also largely be the name of the html node
 | 
			
		||||
       String baseName = nodeService.getProperty( 
 | 
			
		||||
             context.getSourceNode(),
 | 
			
		||||
             ContentModel.PROP_NAME
 | 
			
		||||
       ).toString();
 | 
			
		||||
       if(baseName.lastIndexOf('.') > -1)
 | 
			
		||||
       {
 | 
			
		||||
          baseName = baseName.substring(0, baseName.lastIndexOf('.'));
 | 
			
		||||
       }
 | 
			
		||||
       return baseName;
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * What name should be used for the images directory?
 | 
			
		||||
     * Note this is only required if {@link #PARAM_IMAGES_SAME_FOLDER} is false (the default).
 | 
			
		||||
     */
 | 
			
		||||
    private String getImagesDirectoryName(RenderingContext context)
 | 
			
		||||
    {
 | 
			
		||||
       // Based on the name of the source node, which will
 | 
			
		||||
       //  also largely be the name of the html node
 | 
			
		||||
       String folderName = getHtmlBaseName(context);
 | 
			
		||||
       folderName = folderName + "_files";
 | 
			
		||||
       return folderName;
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * What prefix should be applied to the name of images? 
 | 
			
		||||
     */
 | 
			
		||||
    private String getImagesPrefixName(RenderingContext context)
 | 
			
		||||
    {
 | 
			
		||||
       if( context.getParamWithDefault(PARAM_IMAGES_SAME_FOLDER, false) )
 | 
			
		||||
       {
 | 
			
		||||
          // Prefix with the name of the source node
 | 
			
		||||
          return getHtmlBaseName(context) + "_";
 | 
			
		||||
       }
 | 
			
		||||
       else {
 | 
			
		||||
          // They have their own folder, so no prefix is needed
 | 
			
		||||
          return "";
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a directory to store the images in.
 | 
			
		||||
     * The directory will be a sibling of the rendered
 | 
			
		||||
     *  HTML, and named similar to it.
 | 
			
		||||
     * Note this is only required if {@link #PARAM_IMAGES_SAME_FOLDER} is false (the default).
 | 
			
		||||
     */
 | 
			
		||||
    private NodeRef createImagesDirectory(RenderingContext context)
 | 
			
		||||
    {
 | 
			
		||||
       // It should be a sibling of the HTML in it's eventual location
 | 
			
		||||
       //  (not it's current temporary one!)
 | 
			
		||||
       RenditionLocation location = resolveRenditionLocation(
 | 
			
		||||
             context.getSourceNode(), context.getDefinition(), context.getDestinationNode()
 | 
			
		||||
       );
 | 
			
		||||
       NodeRef parent = location.getParentRef();
 | 
			
		||||
       
 | 
			
		||||
       // Figure out what to call it, based on the HTML node
 | 
			
		||||
       String folderName = getImagesDirectoryName(context);
 | 
			
		||||
       
 | 
			
		||||
       // It is already there?
 | 
			
		||||
       // (eg from when the rendition is being re-run)
 | 
			
		||||
       NodeRef imgFolder = nodeService.getChildByName(
 | 
			
		||||
             parent, ContentModel.ASSOC_CONTAINS, folderName
 | 
			
		||||
       );
 | 
			
		||||
       if(imgFolder != null)
 | 
			
		||||
          return imgFolder;
 | 
			
		||||
       
 | 
			
		||||
       // Create the directory
 | 
			
		||||
       Map<QName,Serializable> properties = new HashMap<QName,Serializable>();
 | 
			
		||||
       properties.put(ContentModel.PROP_NAME, folderName);
 | 
			
		||||
       imgFolder = nodeService.createNode(
 | 
			
		||||
             parent,
 | 
			
		||||
             ContentModel.ASSOC_CONTAINS,
 | 
			
		||||
             QName.createQName(folderName),
 | 
			
		||||
             ContentModel.TYPE_FOLDER,
 | 
			
		||||
             properties
 | 
			
		||||
       ).getChildRef();
 | 
			
		||||
       
 | 
			
		||||
       return imgFolder;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private NodeRef createEmbeddedImage(NodeRef imgFolder, boolean primary,
 | 
			
		||||
          String filename, String contentType, InputStream imageSource,
 | 
			
		||||
          RenderingContext context)
 | 
			
		||||
    {
 | 
			
		||||
       // Create the node if needed
 | 
			
		||||
       NodeRef img = nodeService.getChildByName(
 | 
			
		||||
             imgFolder, ContentModel.ASSOC_CONTAINS, filename
 | 
			
		||||
       );
 | 
			
		||||
       if(img == null)
 | 
			
		||||
       {
 | 
			
		||||
          Map<QName,Serializable> properties = new HashMap<QName,Serializable>();
 | 
			
		||||
          properties.put(ContentModel.PROP_NAME, filename);
 | 
			
		||||
          img = nodeService.createNode(
 | 
			
		||||
                imgFolder,
 | 
			
		||||
                ContentModel.ASSOC_CONTAINS,
 | 
			
		||||
                QName.createQName(filename),
 | 
			
		||||
                ContentModel.TYPE_CONTENT,
 | 
			
		||||
                properties
 | 
			
		||||
          ).getChildRef();
 | 
			
		||||
          if (logger.isDebugEnabled())
 | 
			
		||||
          {
 | 
			
		||||
              logger.debug("Image node created: " + img);
 | 
			
		||||
          }
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
       // TODO Once composite content is properly supported,
 | 
			
		||||
       //  at this point we'll associate the new image with
 | 
			
		||||
       //  the rendered HTML node so the dependency is tracked.
 | 
			
		||||
       
 | 
			
		||||
       // Put the image into the node
 | 
			
		||||
       ContentWriter writer = contentService.getWriter(
 | 
			
		||||
             img, ContentModel.PROP_CONTENT, true
 | 
			
		||||
       );
 | 
			
		||||
       writer.setMimetype(contentType);
 | 
			
		||||
       writer.putContent(imageSource);
 | 
			
		||||
       if (logger.isDebugEnabled())
 | 
			
		||||
       {
 | 
			
		||||
           logger.debug("Image content written into " + img);
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
       // All done
 | 
			
		||||
       return img;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Builds a Tika-compatible SAX content handler, which will
 | 
			
		||||
     *  be used to generate+capture the XHTML
 | 
			
		||||
     */
 | 
			
		||||
    private ContentHandler buildContentHandler(Writer output, RenderingContext context) 
 | 
			
		||||
    {
 | 
			
		||||
       // Create the main transformer
 | 
			
		||||
       SAXTransformerFactory factory = (SAXTransformerFactory)
 | 
			
		||||
                SAXTransformerFactory.newInstance();
 | 
			
		||||
       TransformerHandler handler;
 | 
			
		||||
       
 | 
			
		||||
       try {
 | 
			
		||||
          handler = factory.newTransformerHandler();
 | 
			
		||||
       } catch (TransformerConfigurationException e) {
 | 
			
		||||
          throw new RenditionServiceException("SAX Processing isn't available - " + e);
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
       handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes");
 | 
			
		||||
       handler.setResult(new StreamResult(output));
 | 
			
		||||
       handler.getTransformer().setOutputProperty(OutputKeys.METHOD, "xml");
 | 
			
		||||
       
 | 
			
		||||
       // Change the image links as they go past
 | 
			
		||||
       String dirName = null, imgPrefix = null;
 | 
			
		||||
       if(context.getParamWithDefault(PARAM_IMAGES_SAME_FOLDER, false))
 | 
			
		||||
       {
 | 
			
		||||
          imgPrefix = getImagesPrefixName(context);
 | 
			
		||||
       }
 | 
			
		||||
       else
 | 
			
		||||
       {
 | 
			
		||||
          dirName = getImagesDirectoryName(context);
 | 
			
		||||
       }
 | 
			
		||||
       ContentHandler contentHandler = new TikaImageRewritingContentHandler(
 | 
			
		||||
             handler, dirName, imgPrefix
 | 
			
		||||
       );
 | 
			
		||||
       
 | 
			
		||||
       // If required, wrap it to only return the body 
 | 
			
		||||
       boolean bodyOnly = context.getParamWithDefault(PARAM_BODY_CONTENTS_ONLY, false);
 | 
			
		||||
       if(bodyOnly) {
 | 
			
		||||
          contentHandler = new BodyContentHandler(contentHandler);
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
       // All done
 | 
			
		||||
       return contentHandler;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Asks Tika to translate the contents into HTML
 | 
			
		||||
     */
 | 
			
		||||
    private void generateHTML(Parser p, RenderingContext context)
 | 
			
		||||
    {
 | 
			
		||||
       ContentReader contentReader = context.makeContentReader();
 | 
			
		||||
       
 | 
			
		||||
       // Setup things to parse with
 | 
			
		||||
       StringWriter sw = new StringWriter();
 | 
			
		||||
       ContentHandler handler = buildContentHandler(sw, context);
 | 
			
		||||
       
 | 
			
		||||
       // Tell Tika what we're dealing with
 | 
			
		||||
       Metadata metadata = new Metadata();
 | 
			
		||||
       metadata.set(
 | 
			
		||||
             Metadata.CONTENT_TYPE, 
 | 
			
		||||
             contentReader.getMimetype()
 | 
			
		||||
       );
 | 
			
		||||
       metadata.set(
 | 
			
		||||
             Metadata.RESOURCE_NAME_KEY, 
 | 
			
		||||
             nodeService.getProperty( 
 | 
			
		||||
                   context.getSourceNode(),
 | 
			
		||||
                   ContentModel.PROP_NAME
 | 
			
		||||
             ).toString()
 | 
			
		||||
       );
 | 
			
		||||
 | 
			
		||||
       // Our parse context needs to extract images
 | 
			
		||||
       ParseContext parseContext = new ParseContext();
 | 
			
		||||
       parseContext.set(Parser.class, new TikaImageExtractingParser(context));
 | 
			
		||||
       
 | 
			
		||||
       // Parse
 | 
			
		||||
       try {
 | 
			
		||||
          p.parse(
 | 
			
		||||
                contentReader.getContentInputStream(),
 | 
			
		||||
                handler, metadata, parseContext
 | 
			
		||||
          );
 | 
			
		||||
       } catch(Exception e) {
 | 
			
		||||
          throw new RenditionServiceException("Tika HTML Conversion Failed", e);
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
       // As a string
 | 
			
		||||
       String html = sw.toString();
 | 
			
		||||
       
 | 
			
		||||
       // If we're doing body-only, remove all the html namespaces
 | 
			
		||||
       //  that will otherwise clutter up the document
 | 
			
		||||
       boolean bodyOnly = context.getParamWithDefault(PARAM_BODY_CONTENTS_ONLY, false);
 | 
			
		||||
       if(bodyOnly) {
 | 
			
		||||
          html = html.replaceAll("<\\?xml.*?\\?>", "");
 | 
			
		||||
          html = html.replaceAll("<p xmlns=\"http://www.w3.org/1999/xhtml\"","<p");
 | 
			
		||||
          html = html.replaceAll("<h(\\d) xmlns=\"http://www.w3.org/1999/xhtml\"","<h\\1");
 | 
			
		||||
          html = html.replaceAll("<div xmlns=\"http://www.w3.org/1999/xhtml\"","<div");
 | 
			
		||||
          html = html.replaceAll("<table xmlns=\"http://www.w3.org/1999/xhtml\"","<table");
 | 
			
		||||
          html = html.replaceAll("
","");
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
       // Save it
 | 
			
		||||
       ContentWriter contentWriter = context.makeContentWriter();
 | 
			
		||||
       contentWriter.setMimetype("text/html");
 | 
			
		||||
       contentWriter.putContent( html );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * A nested Tika parser which extracts out any
 | 
			
		||||
     *  images as they come past.
 | 
			
		||||
     */
 | 
			
		||||
   @SuppressWarnings("serial")
 | 
			
		||||
   private class TikaImageExtractingParser implements Parser {
 | 
			
		||||
      private Set<MediaType> types;
 | 
			
		||||
      
 | 
			
		||||
      private RenderingContext renderingContext;
 | 
			
		||||
      private NodeRef imgFolder = null;
 | 
			
		||||
      private int count = 0;
 | 
			
		||||
      
 | 
			
		||||
      private TikaImageExtractingParser(RenderingContext renderingContext) {
 | 
			
		||||
         this.renderingContext = renderingContext;
 | 
			
		||||
         
 | 
			
		||||
         // Our expected types
 | 
			
		||||
         types = new HashSet<MediaType>();
 | 
			
		||||
         types.add(MediaType.image("bmp"));
 | 
			
		||||
         types.add(MediaType.image("gif"));
 | 
			
		||||
         types.add(MediaType.image("jpg"));
 | 
			
		||||
         types.add(MediaType.image("jpeg"));
 | 
			
		||||
         types.add(MediaType.image("png"));
 | 
			
		||||
         types.add(MediaType.image("tiff"));
 | 
			
		||||
         
 | 
			
		||||
         // Are images going in the same place as the HTML?
 | 
			
		||||
         if( renderingContext.getParamWithDefault(PARAM_IMAGES_SAME_FOLDER, false) )
 | 
			
		||||
         {
 | 
			
		||||
            RenditionLocation location = resolveRenditionLocation(
 | 
			
		||||
                  renderingContext.getSourceNode(), renderingContext.getDefinition(), 
 | 
			
		||||
                  renderingContext.getDestinationNode()
 | 
			
		||||
            );
 | 
			
		||||
            imgFolder = location.getParentRef();
 | 
			
		||||
            if (logger.isDebugEnabled())
 | 
			
		||||
            {
 | 
			
		||||
                logger.debug("Using imgFolder: " + imgFolder);
 | 
			
		||||
            }
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      @Override
 | 
			
		||||
      public Set<MediaType> getSupportedTypes(ParseContext context) {
 | 
			
		||||
         return types;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      @Override
 | 
			
		||||
      public void parse(InputStream stream, ContentHandler handler,
 | 
			
		||||
            Metadata metadata, ParseContext context) throws IOException,
 | 
			
		||||
            SAXException, TikaException {
 | 
			
		||||
         // Is it a supported image?
 | 
			
		||||
         String filename = metadata.get(Metadata.RESOURCE_NAME_KEY);
 | 
			
		||||
         String type = metadata.get(Metadata.CONTENT_TYPE);
 | 
			
		||||
         boolean accept = false;
 | 
			
		||||
         
 | 
			
		||||
         if(type != null) {
 | 
			
		||||
            for(MediaType mt : types) {
 | 
			
		||||
               if(mt.toString().equals(type)) {
 | 
			
		||||
                  accept = true;
 | 
			
		||||
               }
 | 
			
		||||
            }
 | 
			
		||||
         }
 | 
			
		||||
         if(filename != null) {
 | 
			
		||||
            for(MediaType mt : types) {
 | 
			
		||||
               String ext = "." + mt.getSubtype();
 | 
			
		||||
               if(filename.endsWith(ext)) {
 | 
			
		||||
                  accept = true;
 | 
			
		||||
               }
 | 
			
		||||
            }
 | 
			
		||||
         }
 | 
			
		||||
         
 | 
			
		||||
         if(!accept)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
         handleImage(stream, filename, type);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      private void handleImage(InputStream stream, String filename, String type) {
 | 
			
		||||
         count++;
 | 
			
		||||
         
 | 
			
		||||
         // Do we already have the folder? If not, create it
 | 
			
		||||
         if(imgFolder == null) {
 | 
			
		||||
            imgFolder = createImagesDirectory(renderingContext);
 | 
			
		||||
         }
 | 
			
		||||
         
 | 
			
		||||
         // Give it a sensible name if needed
 | 
			
		||||
         if(filename == null) {
 | 
			
		||||
            filename = "image-" + count + ".";
 | 
			
		||||
            filename += type.substring(type.indexOf('/')+1);
 | 
			
		||||
         }
 | 
			
		||||
         
 | 
			
		||||
         // Prefix the filename if needed
 | 
			
		||||
         filename = getImagesPrefixName(renderingContext) + filename; 
 | 
			
		||||
 | 
			
		||||
         // Save the image
 | 
			
		||||
         createEmbeddedImage(imgFolder, (count==1), filename, type, stream, renderingContext);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
   
 | 
			
		||||
    /**
 | 
			
		||||
     * A content handler that re-writes image src attributes,
 | 
			
		||||
     *  and passes everything else on to the real one.
 | 
			
		||||
     */
 | 
			
		||||
    private class TikaImageRewritingContentHandler extends ContentHandlerDecorator {
 | 
			
		||||
       private String imageFolder;
 | 
			
		||||
       private String imagePrefix;
 | 
			
		||||
       
 | 
			
		||||
       private TikaImageRewritingContentHandler(ContentHandler handler, String imageFolder, String imagePrefix) {
 | 
			
		||||
          super(handler);
 | 
			
		||||
          this.imageFolder = imageFolder;
 | 
			
		||||
          this.imagePrefix = imagePrefix;
 | 
			
		||||
       }
 | 
			
		||||
 | 
			
		||||
       @Override
 | 
			
		||||
       public void startElement(String uri, String localName, String qName,
 | 
			
		||||
             Attributes origAttrs) throws SAXException {
 | 
			
		||||
          // If we have an image tag, re-write the src attribute
 | 
			
		||||
          //  if required
 | 
			
		||||
          if("img".equals(localName)) {
 | 
			
		||||
             AttributesImpl attrs;
 | 
			
		||||
             if(origAttrs instanceof AttributesImpl) {
 | 
			
		||||
                attrs = (AttributesImpl)origAttrs;
 | 
			
		||||
             } else {
 | 
			
		||||
                attrs = new AttributesImpl(origAttrs);
 | 
			
		||||
             }
 | 
			
		||||
             
 | 
			
		||||
             for(int i=0; i<attrs.getLength(); i++) {
 | 
			
		||||
                if("src".equals(attrs.getLocalName(i))) {
 | 
			
		||||
                   String src = attrs.getValue(i);
 | 
			
		||||
                   if(src.startsWith("embedded:")) {
 | 
			
		||||
                      String newSrc = "";
 | 
			
		||||
                      if(imageFolder != null)
 | 
			
		||||
                         newSrc += imageFolder + "/";
 | 
			
		||||
                      if(imagePrefix != null)
 | 
			
		||||
                         newSrc += imagePrefix;
 | 
			
		||||
                      newSrc += src.substring(src.indexOf(':')+1);
 | 
			
		||||
                      attrs.setValue(i, newSrc);
 | 
			
		||||
                   }
 | 
			
		||||
                }
 | 
			
		||||
             }
 | 
			
		||||
             super.startElement(uri, localName, qName, attrs);
 | 
			
		||||
          } else {
 | 
			
		||||
             // For any other tag, pass through as-is
 | 
			
		||||
             super.startElement(uri, localName, qName, origAttrs);
 | 
			
		||||
          }
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2020 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
@@ -52,6 +52,7 @@ import java.util.Set;
 | 
			
		||||
import java.util.StringJoiner;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_PDF;
 | 
			
		||||
import static org.alfresco.repo.content.transform.magick.ImageTransformationOptions.OPT_COMMAND_OPTIONS;
 | 
			
		||||
import static org.alfresco.repo.rendition2.RenditionDefinition2.ALLOW_ENLARGEMENT;
 | 
			
		||||
import static org.alfresco.repo.rendition2.RenditionDefinition2.ALLOW_PDF_ENLARGEMENT;
 | 
			
		||||
import static org.alfresco.repo.rendition2.RenditionDefinition2.ALPHA_REMOVE;
 | 
			
		||||
@@ -122,6 +123,7 @@ public class TransformationOptionsConverter implements InitializingBean
 | 
			
		||||
        IMAGE_OPTIONS.addAll(RESIZE_OPTIONS);
 | 
			
		||||
        IMAGE_OPTIONS.add(AUTO_ORIENT);
 | 
			
		||||
        IMAGE_OPTIONS.add(ALPHA_REMOVE);
 | 
			
		||||
        IMAGE_OPTIONS.add(OPT_COMMAND_OPTIONS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Set<String> PDF_OPTIONS = new HashSet<>(Arrays.asList(new String[]
 | 
			
		||||
@@ -284,6 +286,8 @@ public class TransformationOptionsConverter implements InitializingBean
 | 
			
		||||
                    }
 | 
			
		||||
                    opts.setSourceOptionsList(sourceOptionsList);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                ifSet(options, OPT_COMMAND_OPTIONS, (v) -> opts.setCommandOptions(v));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
@@ -361,13 +365,11 @@ public class TransformationOptionsConverter implements InitializingBean
 | 
			
		||||
            {
 | 
			
		||||
                ImageTransformationOptions opts = (ImageTransformationOptions) options;
 | 
			
		||||
 | 
			
		||||
                // TODO We don't support this any more for security reasons, however it might be possible to
 | 
			
		||||
                // extract some of the well know values and add them to the newer ImageMagick transform options.
 | 
			
		||||
                // From a security viewpoint it would be better not to support the option of passing anything to
 | 
			
		||||
                // ImageMagick. It might be possible to extract some of the well know values and add them to the
 | 
			
		||||
                // T-Engine engine_config.
 | 
			
		||||
                String commandOptions = opts.getCommandOptions();
 | 
			
		||||
                if (commandOptions != null && !commandOptions.isBlank())
 | 
			
		||||
                {
 | 
			
		||||
                    logger.error("ImageMagick commandOptions are no longer supported for security reasons: " + commandOptions);
 | 
			
		||||
                }
 | 
			
		||||
                ifSet(commandOptions != null && !commandOptions.isBlank(), map, OPT_COMMAND_OPTIONS, commandOptions);
 | 
			
		||||
 | 
			
		||||
                ImageResizeOptions imageResizeOptions = opts.getResizeOptions();
 | 
			
		||||
                if (imageResizeOptions != null)
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,9 @@
 | 
			
		||||
package org.alfresco.repo.search;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.error.AlfrescoRuntimeException;
 | 
			
		||||
import org.apache.http.HttpStatus;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Andy
 | 
			
		||||
@@ -33,11 +36,10 @@ import org.alfresco.error.AlfrescoRuntimeException;
 | 
			
		||||
 */
 | 
			
		||||
public class QueryParserException extends AlfrescoRuntimeException
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    /** Serial version UUID. */
 | 
			
		||||
    private static final long serialVersionUID = 4886993838297301968L;
 | 
			
		||||
    /** Http Status Code that should be returned by Remote API. */
 | 
			
		||||
    private int httpStatusCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param msgId
 | 
			
		||||
@@ -45,7 +47,6 @@ public class QueryParserException extends AlfrescoRuntimeException
 | 
			
		||||
    public QueryParserException(String msgId)
 | 
			
		||||
    {
 | 
			
		||||
        super(msgId);
 | 
			
		||||
        // TODO Auto-generated constructor stub
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -55,7 +56,6 @@ public class QueryParserException extends AlfrescoRuntimeException
 | 
			
		||||
    public QueryParserException(String msgId, Object[] msgParams)
 | 
			
		||||
    {
 | 
			
		||||
        super(msgId, msgParams);
 | 
			
		||||
        // TODO Auto-generated constructor stub
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -65,7 +65,6 @@ public class QueryParserException extends AlfrescoRuntimeException
 | 
			
		||||
    public QueryParserException(String msgId, Throwable cause)
 | 
			
		||||
    {
 | 
			
		||||
        super(msgId, cause);
 | 
			
		||||
        // TODO Auto-generated constructor stub
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -76,7 +75,22 @@ public class QueryParserException extends AlfrescoRuntimeException
 | 
			
		||||
    public QueryParserException(String msgId, Object[] msgParams, Throwable cause)
 | 
			
		||||
    {
 | 
			
		||||
        super(msgId, msgParams, cause);
 | 
			
		||||
        // TODO Auto-generated constructor stub
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for exception that allows setting an HTTP status code.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msgId Message for the exception
 | 
			
		||||
     * @param httpStatusCode Status code to return for exception
 | 
			
		||||
     */
 | 
			
		||||
    public QueryParserException(String msgId, int httpStatusCode)
 | 
			
		||||
    {
 | 
			
		||||
        super(msgId);
 | 
			
		||||
        this.httpStatusCode = httpStatusCode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getHttpStatusCode()
 | 
			
		||||
    {
 | 
			
		||||
        return httpStatusCode;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,460 +1,505 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is 
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * 
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.search.impl.querymodel.impl.db;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.repo.domain.node.AbstractNodeDAOImpl.CACHE_REGION_NODES;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.BitSet;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.concurrent.NotThreadSafe;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.repo.admin.patch.OptionalPatchApplicationCheckBootstrapBean;
 | 
			
		||||
import org.alfresco.repo.cache.SimpleCache;
 | 
			
		||||
import org.alfresco.repo.cache.lookup.EntityLookupCache;
 | 
			
		||||
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
 | 
			
		||||
import org.alfresco.repo.domain.node.Node;
 | 
			
		||||
import org.alfresco.repo.domain.node.NodeDAO;
 | 
			
		||||
import org.alfresco.repo.domain.permissions.AclCrudDAO;
 | 
			
		||||
import org.alfresco.repo.domain.permissions.Authority;
 | 
			
		||||
import org.alfresco.repo.domain.qname.QNameDAO;
 | 
			
		||||
import org.alfresco.repo.search.SimpleResultSetMetaData;
 | 
			
		||||
import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.Query;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryEngine;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryEngineResults;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryModelException;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryModelFactory;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryOptions;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | 
			
		||||
import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet;
 | 
			
		||||
import org.alfresco.repo.tenant.TenantService;
 | 
			
		||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.StoreRef;
 | 
			
		||||
import org.alfresco.service.cmr.search.LimitBy;
 | 
			
		||||
import org.alfresco.service.cmr.search.PermissionEvaluationMode;
 | 
			
		||||
import org.alfresco.service.cmr.search.ResultSet;
 | 
			
		||||
import org.alfresco.service.cmr.security.PermissionService;
 | 
			
		||||
import org.alfresco.service.namespace.NamespaceService;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.apache.ibatis.session.ResultContext;
 | 
			
		||||
import org.apache.ibatis.session.ResultHandler;
 | 
			
		||||
import org.mybatis.spring.SqlSessionTemplate;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Andy
 | 
			
		||||
 */
 | 
			
		||||
@NotThreadSafe
 | 
			
		||||
public class DBQueryEngine implements QueryEngine
 | 
			
		||||
{
 | 
			
		||||
    protected static final Log logger = LogFactory.getLog(DBQueryEngine.class);
 | 
			
		||||
    
 | 
			
		||||
    protected static final String SELECT_BY_DYNAMIC_QUERY = "alfresco.metadata.query.select_byDynamicQuery";
 | 
			
		||||
    
 | 
			
		||||
    protected SqlSessionTemplate template;
 | 
			
		||||
 | 
			
		||||
    protected QNameDAO qnameDAO;
 | 
			
		||||
    
 | 
			
		||||
    private NodeDAO nodeDAO;
 | 
			
		||||
 | 
			
		||||
    protected DictionaryService dictionaryService;
 | 
			
		||||
 | 
			
		||||
    protected NamespaceService namespaceService;
 | 
			
		||||
    
 | 
			
		||||
    protected NodeService nodeService;
 | 
			
		||||
 | 
			
		||||
    private TenantService tenantService;
 | 
			
		||||
    
 | 
			
		||||
    private OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2;
 | 
			
		||||
 | 
			
		||||
    protected PermissionService permissionService;
 | 
			
		||||
 | 
			
		||||
    private int maxPermissionChecks;
 | 
			
		||||
    
 | 
			
		||||
    private long maxPermissionCheckTimeMillis;
 | 
			
		||||
 | 
			
		||||
    protected EntityLookupCache<Long, Node, NodeRef> nodesCache;
 | 
			
		||||
    
 | 
			
		||||
    AclCrudDAO aclCrudDAO;
 | 
			
		||||
 | 
			
		||||
    public void setAclCrudDAO(AclCrudDAO aclCrudDAO)
 | 
			
		||||
    {
 | 
			
		||||
        this.aclCrudDAO = aclCrudDAO;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setMaxPermissionChecks(int maxPermissionChecks)
 | 
			
		||||
    {
 | 
			
		||||
        this.maxPermissionChecks = maxPermissionChecks;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis)
 | 
			
		||||
    {
 | 
			
		||||
        this.maxPermissionCheckTimeMillis = maxPermissionCheckTimeMillis;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setTemplate(SqlSessionTemplate template)
 | 
			
		||||
    {
 | 
			
		||||
        this.template = template;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPermissionService(PermissionService permissionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.permissionService = permissionService;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setMetadataIndexCheck2(OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2)
 | 
			
		||||
    {
 | 
			
		||||
        this.metadataIndexCheck2 = metadataIndexCheck2;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setTenantService(TenantService tenantService)
 | 
			
		||||
    {
 | 
			
		||||
        this.tenantService = tenantService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
 | 
			
		||||
    {
 | 
			
		||||
        this.template = sqlSessionTemplate;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param qnameDAO
 | 
			
		||||
     *            the qnameDAO to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setQnameDAO(QNameDAO qnameDAO)
 | 
			
		||||
    {
 | 
			
		||||
        this.qnameDAO = qnameDAO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param dictionaryService
 | 
			
		||||
     *            the dictionaryService to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setDictionaryService(DictionaryService dictionaryService)
 | 
			
		||||
    {
 | 
			
		||||
        this.dictionaryService = dictionaryService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param namespaceService
 | 
			
		||||
     *            the namespaceService to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setNamespaceService(NamespaceService namespaceService)
 | 
			
		||||
    {
 | 
			
		||||
        this.namespaceService = namespaceService;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param nodeService the nodeService to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setNodeService(NodeService nodeService)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodeService = nodeService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param nodeDAO the nodeDAO to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setNodeDAO(NodeDAO nodeDAO)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodeDAO = nodeDAO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * (non-Javadoc)
 | 
			
		||||
     * @see
 | 
			
		||||
     * org.alfresco.repo.search.impl.querymodel.QueryEngine#executeQuery(org.alfresco.repo.search.impl.querymodel.Query,
 | 
			
		||||
     * org.alfresco.repo.search.impl.querymodel.QueryOptions,
 | 
			
		||||
     * org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public QueryEngineResults executeQuery(Query query, QueryOptions options, FunctionEvaluationContext functionContext)
 | 
			
		||||
    {
 | 
			
		||||
        long start = 0;
 | 
			
		||||
        if (logger.isDebugEnabled())
 | 
			
		||||
        {
 | 
			
		||||
            start = System.currentTimeMillis();
 | 
			
		||||
            logger.debug("Query request received");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Set<String> selectorGroup = null;
 | 
			
		||||
        if (query.getSource() != null)
 | 
			
		||||
        {
 | 
			
		||||
            List<Set<String>> selectorGroups = query.getSource().getSelectorGroups(functionContext);
 | 
			
		||||
 | 
			
		||||
            if (selectorGroups.size() == 0)
 | 
			
		||||
            {
 | 
			
		||||
                throw new QueryModelException("No selectors");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (selectorGroups.size() > 1)
 | 
			
		||||
            {
 | 
			
		||||
                throw new QueryModelException("Advanced join is not supported");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            selectorGroup = selectorGroups.get(0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        DBQuery dbQuery = (DBQuery)query;
 | 
			
		||||
        
 | 
			
		||||
        if (options.getStores().size() > 1)
 | 
			
		||||
        {
 | 
			
		||||
            throw new QueryModelException("Multi-store queries are not supported");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // MT
 | 
			
		||||
        StoreRef storeRef = options.getStores().get(0);
 | 
			
		||||
        storeRef = storeRef != null ? tenantService.getName(storeRef) : null;
 | 
			
		||||
 | 
			
		||||
        Pair<Long, StoreRef> store = nodeDAO.getStore(storeRef);
 | 
			
		||||
        if (store == null)
 | 
			
		||||
        {
 | 
			
		||||
        	  throw new QueryModelException("Unknown store: "+storeRef);
 | 
			
		||||
        }
 | 
			
		||||
        dbQuery.setStoreId(store.getFirst());
 | 
			
		||||
        Pair<Long, QName> sysDeletedType = qnameDAO.getQName(ContentModel.TYPE_DELETED);
 | 
			
		||||
        if (sysDeletedType == null)
 | 
			
		||||
        {
 | 
			
		||||
            dbQuery.setSysDeletedType(-1L);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            dbQuery.setSysDeletedType(sysDeletedType.getFirst());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Long sinceTxId = options.getSinceTxId();
 | 
			
		||||
        if (sinceTxId == null)
 | 
			
		||||
        {
 | 
			
		||||
            // By default, return search results for all transactions.
 | 
			
		||||
            sinceTxId = -1L;
 | 
			
		||||
        }
 | 
			
		||||
        dbQuery.setSinceTxId(sinceTxId);
 | 
			
		||||
        
 | 
			
		||||
        logger.debug("- query is being prepared");
 | 
			
		||||
        dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup,
 | 
			
		||||
                null, functionContext, metadataIndexCheck2.getPatchApplied());
 | 
			
		||||
 | 
			
		||||
        ResultSet resultSet;
 | 
			
		||||
        resultSet = selectNodesWithPermissions(options, dbQuery);
 | 
			
		||||
        if (logger.isDebugEnabled())
 | 
			
		||||
        {
 | 
			
		||||
            long ms = System.currentTimeMillis() - start;
 | 
			
		||||
            logger.debug("Selected " + resultSet.length() + " nodes with permission resolution in "+ms+" ms");
 | 
			
		||||
        }
 | 
			
		||||
        return asQueryEngineResults(resultSet);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected String pickQueryTemplate(QueryOptions options, DBQuery dbQuery)
 | 
			
		||||
    {
 | 
			
		||||
        logger.debug("- using standard table for the query");
 | 
			
		||||
        return SELECT_BY_DYNAMIC_QUERY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ResultSet selectNodesWithPermissions(QueryOptions options, DBQuery dbQuery)
 | 
			
		||||
    {
 | 
			
		||||
        Authority authority = aclCrudDAO.getAuthority(AuthenticationUtil.getRunAsUser());
 | 
			
		||||
        
 | 
			
		||||
        NodePermissionAssessor permissionAssessor = createAssessor(authority);
 | 
			
		||||
        int maxPermsChecks = options.getMaxPermissionChecks() < 0 ? maxPermissionChecks : options.getMaxPermissionChecks();
 | 
			
		||||
        long maxPermCheckTimeMillis = options.getMaxPermissionCheckTimeMillis() < 0
 | 
			
		||||
                ? maxPermissionCheckTimeMillis
 | 
			
		||||
                : options.getMaxPermissionCheckTimeMillis();
 | 
			
		||||
        permissionAssessor.setMaxPermissionChecks(maxPermsChecks);
 | 
			
		||||
        permissionAssessor.setMaxPermissionCheckTimeMillis(maxPermCheckTimeMillis);
 | 
			
		||||
        
 | 
			
		||||
        FilteringResultSet resultSet = acceleratedNodeSelection(options, dbQuery, permissionAssessor);
 | 
			
		||||
        
 | 
			
		||||
        PagingLuceneResultSet plrs = new PagingLuceneResultSet(resultSet, options.getAsSearchParmeters(), nodeService);
 | 
			
		||||
        plrs.setTrimmedResultSet(true);
 | 
			
		||||
        return plrs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected NodePermissionAssessor createAssessor(Authority authority)
 | 
			
		||||
    {
 | 
			
		||||
        return new NodePermissionAssessor(nodeService, permissionService, authority, nodesCache);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FilteringResultSet acceleratedNodeSelection(QueryOptions options, DBQuery dbQuery, NodePermissionAssessor permissionAssessor)
 | 
			
		||||
    {
 | 
			
		||||
        List<Node> nodes = new ArrayList<>();
 | 
			
		||||
        int requiredNodes = computeRequiredNodesCount(options);
 | 
			
		||||
        
 | 
			
		||||
        logger.debug("- query sent to the database");
 | 
			
		||||
        template.select(pickQueryTemplate(options, dbQuery), dbQuery, new ResultHandler<Node>()
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void handleResult(ResultContext<? extends Node> context)
 | 
			
		||||
            {
 | 
			
		||||
                doHandleResult(permissionAssessor, nodes, requiredNodes, context);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            private void doHandleResult(NodePermissionAssessor permissionAssessor, List<Node> nodes,
 | 
			
		||||
                                        int requiredNodes, ResultContext<? extends Node> context)
 | 
			
		||||
            {
 | 
			
		||||
                if (nodes.size() >= requiredNodes)
 | 
			
		||||
                {
 | 
			
		||||
                    context.stop();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                Node node = context.getResultObject();
 | 
			
		||||
                
 | 
			
		||||
                boolean shouldCache = nodes.size() >= options.getSkipCount();
 | 
			
		||||
                if(shouldCache)
 | 
			
		||||
                {
 | 
			
		||||
                    logger.debug("- selected node "+nodes.size()+": "+node.getUuid()+" "+node.getId());
 | 
			
		||||
                    nodesCache.setValue(node.getId(), node);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    logger.debug("- skipped node "+nodes.size()+": "+node.getUuid()+" "+node.getId());
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if (permissionAssessor.isIncluded(node))
 | 
			
		||||
                {
 | 
			
		||||
                    nodes.add(shouldCache ? node : null);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if (permissionAssessor.shouldQuitChecks())
 | 
			
		||||
                {
 | 
			
		||||
                    context.stop();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        int numberFound = nodes.size();
 | 
			
		||||
        nodes.removeAll(Collections.singleton(null));
 | 
			
		||||
        
 | 
			
		||||
        DBResultSet rs =  createResultSet(options, nodes, numberFound);
 | 
			
		||||
        FilteringResultSet frs = new FilteringResultSet(rs, formInclusionMask(nodes));
 | 
			
		||||
        frs.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.UNLIMITED, PermissionEvaluationMode.EAGER, rs.getResultSetMetaData().getSearchParameters()));
 | 
			
		||||
 
 | 
			
		||||
        logger.debug("- query is completed, "+nodes.size()+" nodes loaded");
 | 
			
		||||
        return frs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private DBResultSet createResultSet(QueryOptions options, List<Node> nodes, int numberFound)
 | 
			
		||||
    {
 | 
			
		||||
        DBResultSet dbResultSet = new DBResultSet(options.getAsSearchParmeters(), nodes, nodeDAO, nodeService, tenantService, Integer.MAX_VALUE);
 | 
			
		||||
        dbResultSet.setNumberFound(numberFound);
 | 
			
		||||
        return dbResultSet;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int computeRequiredNodesCount(QueryOptions options)
 | 
			
		||||
    {
 | 
			
		||||
        int maxItems = options.getMaxItems();
 | 
			
		||||
        if (maxItems == -1 || maxItems == Integer.MAX_VALUE)
 | 
			
		||||
        {
 | 
			
		||||
            return Integer.MAX_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return maxItems + options.getSkipCount() + 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private BitSet formInclusionMask(List<Node> nodes)
 | 
			
		||||
    {
 | 
			
		||||
        BitSet inclusionMask = new BitSet(nodes.size());
 | 
			
		||||
        for (int i=0; i < nodes.size(); i++)
 | 
			
		||||
        {
 | 
			
		||||
            inclusionMask.set(i, true);
 | 
			
		||||
        }
 | 
			
		||||
        return inclusionMask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    private QueryEngineResults asQueryEngineResults(ResultSet paged)
 | 
			
		||||
    {
 | 
			
		||||
        HashSet<String> key = new HashSet<>();
 | 
			
		||||
        key.add("");
 | 
			
		||||
        Map<Set<String>, ResultSet> answer = new HashMap<>();
 | 
			
		||||
        answer.put(key, paged);
 | 
			
		||||
 | 
			
		||||
        return new QueryEngineResults(answer);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /*
 | 
			
		||||
     * (non-Javadoc)
 | 
			
		||||
     * @see org.alfresco.repo.search.impl.querymodel.QueryEngine#getQueryModelFactory()
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public QueryModelFactory getQueryModelFactory()
 | 
			
		||||
    {
 | 
			
		||||
        return new DBQueryModelFactory();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Injection of nodes cache for clean-up and warm up when required
 | 
			
		||||
     * @param cache The node cache to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setNodesCache(SimpleCache<Serializable, Serializable> cache)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodesCache = new EntityLookupCache<>(
 | 
			
		||||
                cache,
 | 
			
		||||
                CACHE_REGION_NODES,
 | 
			
		||||
                new ReadonlyLocalCallbackDAO());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void setNodesCache(EntityLookupCache<Long, Node, NodeRef> nodesCache) 
 | 
			
		||||
    {
 | 
			
		||||
        this.nodesCache = nodesCache;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private class ReadonlyLocalCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, Node, NodeRef>
 | 
			
		||||
    {
 | 
			
		||||
        @Override
 | 
			
		||||
        public Pair<Long, Node> createValue(Node value)
 | 
			
		||||
        {
 | 
			
		||||
            throw new UnsupportedOperationException("Node creation is done externally: " + value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public Pair<Long, Node> findByKey(Long nodeId)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public NodeRef getValueKey(Node value)
 | 
			
		||||
        {
 | 
			
		||||
            return value.getNodeRef();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is 
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * 
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.search.impl.querymodel.impl.db;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.repo.domain.node.AbstractNodeDAOImpl.CACHE_REGION_NODES;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.BitSet;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.concurrent.NotThreadSafe;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.repo.admin.patch.OptionalPatchApplicationCheckBootstrapBean;
 | 
			
		||||
import org.alfresco.repo.cache.SimpleCache;
 | 
			
		||||
import org.alfresco.repo.cache.lookup.EntityLookupCache;
 | 
			
		||||
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
 | 
			
		||||
import org.alfresco.repo.domain.node.Node;
 | 
			
		||||
import org.alfresco.repo.domain.node.NodeDAO;
 | 
			
		||||
import org.alfresco.repo.domain.node.StoreEntity;
 | 
			
		||||
import org.alfresco.repo.domain.permissions.AclCrudDAO;
 | 
			
		||||
import org.alfresco.repo.domain.permissions.Authority;
 | 
			
		||||
import org.alfresco.repo.domain.qname.QNameDAO;
 | 
			
		||||
import org.alfresco.repo.search.SimpleResultSetMetaData;
 | 
			
		||||
import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.Query;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryEngine;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryEngineResults;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryModelException;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryModelFactory;
 | 
			
		||||
import org.alfresco.repo.search.impl.querymodel.QueryOptions;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | 
			
		||||
import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet;
 | 
			
		||||
import org.alfresco.repo.tenant.TenantService;
 | 
			
		||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.StoreRef;
 | 
			
		||||
import org.alfresco.service.cmr.search.LimitBy;
 | 
			
		||||
import org.alfresco.service.cmr.search.PermissionEvaluationMode;
 | 
			
		||||
import org.alfresco.service.cmr.search.ResultSet;
 | 
			
		||||
import org.alfresco.service.cmr.security.PermissionService;
 | 
			
		||||
import org.alfresco.service.namespace.NamespaceService;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.apache.ibatis.session.ResultContext;
 | 
			
		||||
import org.apache.ibatis.session.ResultHandler;
 | 
			
		||||
import org.mybatis.spring.SqlSessionTemplate;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Andy
 | 
			
		||||
 */
 | 
			
		||||
@NotThreadSafe
 | 
			
		||||
public class DBQueryEngine implements QueryEngine
 | 
			
		||||
{
 | 
			
		||||
    protected static final Log logger = LogFactory.getLog(DBQueryEngine.class);
 | 
			
		||||
    
 | 
			
		||||
    protected static final String SELECT_BY_DYNAMIC_QUERY = "alfresco.metadata.query.select_byDynamicQuery";
 | 
			
		||||
    
 | 
			
		||||
    protected SqlSessionTemplate template;
 | 
			
		||||
 | 
			
		||||
    protected QNameDAO qnameDAO;
 | 
			
		||||
    
 | 
			
		||||
    private NodeDAO nodeDAO;
 | 
			
		||||
 | 
			
		||||
    protected DictionaryService dictionaryService;
 | 
			
		||||
 | 
			
		||||
    protected NamespaceService namespaceService;
 | 
			
		||||
    
 | 
			
		||||
    protected NodeService nodeService;
 | 
			
		||||
 | 
			
		||||
    private TenantService tenantService;
 | 
			
		||||
    
 | 
			
		||||
    private OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2;
 | 
			
		||||
 | 
			
		||||
    protected PermissionService permissionService;
 | 
			
		||||
 | 
			
		||||
    private int maxPermissionChecks;
 | 
			
		||||
    
 | 
			
		||||
    private long maxPermissionCheckTimeMillis;
 | 
			
		||||
 | 
			
		||||
    private boolean maxPermissionCheckEnabled;
 | 
			
		||||
 | 
			
		||||
    protected EntityLookupCache<Long, Node, NodeRef> nodesCache;
 | 
			
		||||
 | 
			
		||||
    private List<Pair<Long, StoreRef>> stores;
 | 
			
		||||
    
 | 
			
		||||
    AclCrudDAO aclCrudDAO;
 | 
			
		||||
 | 
			
		||||
    public void setAclCrudDAO(AclCrudDAO aclCrudDAO)
 | 
			
		||||
    {
 | 
			
		||||
        this.aclCrudDAO = aclCrudDAO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setMaxPermissionChecks(int maxPermissionChecks)
 | 
			
		||||
    {
 | 
			
		||||
        this.maxPermissionChecks = maxPermissionChecks;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis)
 | 
			
		||||
    {
 | 
			
		||||
        this.maxPermissionCheckTimeMillis = maxPermissionCheckTimeMillis;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setMaxPermissionCheckEnabled(boolean maxPermissionCheckEnabled)
 | 
			
		||||
    {
 | 
			
		||||
        this.maxPermissionCheckEnabled = maxPermissionCheckEnabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setTemplate(SqlSessionTemplate template)
 | 
			
		||||
    {
 | 
			
		||||
        this.template = template;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPermissionService(PermissionService permissionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.permissionService = permissionService;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setMetadataIndexCheck2(OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2)
 | 
			
		||||
    {
 | 
			
		||||
        this.metadataIndexCheck2 = metadataIndexCheck2;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void setTenantService(TenantService tenantService)
 | 
			
		||||
    {
 | 
			
		||||
        this.tenantService = tenantService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
 | 
			
		||||
    {
 | 
			
		||||
        this.template = sqlSessionTemplate;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param qnameDAO
 | 
			
		||||
     *            the qnameDAO to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setQnameDAO(QNameDAO qnameDAO)
 | 
			
		||||
    {
 | 
			
		||||
        this.qnameDAO = qnameDAO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param dictionaryService
 | 
			
		||||
     *            the dictionaryService to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setDictionaryService(DictionaryService dictionaryService)
 | 
			
		||||
    {
 | 
			
		||||
        this.dictionaryService = dictionaryService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param namespaceService
 | 
			
		||||
     *            the namespaceService to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setNamespaceService(NamespaceService namespaceService)
 | 
			
		||||
    {
 | 
			
		||||
        this.namespaceService = namespaceService;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param nodeService the nodeService to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setNodeService(NodeService nodeService)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodeService = nodeService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param nodeDAO the nodeDAO to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setNodeDAO(NodeDAO nodeDAO)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodeDAO = nodeDAO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * (non-Javadoc)
 | 
			
		||||
     * @see
 | 
			
		||||
     * org.alfresco.repo.search.impl.querymodel.QueryEngine#executeQuery(org.alfresco.repo.search.impl.querymodel.Query,
 | 
			
		||||
     * org.alfresco.repo.search.impl.querymodel.QueryOptions,
 | 
			
		||||
     * org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public QueryEngineResults executeQuery(Query query, QueryOptions options, FunctionEvaluationContext functionContext)
 | 
			
		||||
    {
 | 
			
		||||
        long start = 0;
 | 
			
		||||
        if (logger.isDebugEnabled())
 | 
			
		||||
        {
 | 
			
		||||
            start = System.currentTimeMillis();
 | 
			
		||||
            logger.debug("Query request received");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Set<String> selectorGroup = null;
 | 
			
		||||
        if (query.getSource() != null)
 | 
			
		||||
        {
 | 
			
		||||
            List<Set<String>> selectorGroups = query.getSource().getSelectorGroups(functionContext);
 | 
			
		||||
 | 
			
		||||
            if (selectorGroups.size() == 0)
 | 
			
		||||
            {
 | 
			
		||||
                throw new QueryModelException("No selectors");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (selectorGroups.size() > 1)
 | 
			
		||||
            {
 | 
			
		||||
                throw new QueryModelException("Advanced join is not supported");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            selectorGroup = selectorGroups.get(0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        DBQuery dbQuery = (DBQuery)query;
 | 
			
		||||
        
 | 
			
		||||
        if (options.getStores().size() > 1)
 | 
			
		||||
        {
 | 
			
		||||
            throw new QueryModelException("Multi-store queries are not supported");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // MT
 | 
			
		||||
        StoreRef storeRef = options.getStores().get(0);
 | 
			
		||||
        storeRef = storeRef != null ? tenantService.getName(storeRef) : null;
 | 
			
		||||
 | 
			
		||||
        Pair<Long, StoreRef> store = nodeDAO.getStore(storeRef);
 | 
			
		||||
        if (store == null)
 | 
			
		||||
        {
 | 
			
		||||
        	  throw new QueryModelException("Unknown store: "+storeRef);
 | 
			
		||||
        }
 | 
			
		||||
        dbQuery.setStoreId(store.getFirst());
 | 
			
		||||
        Pair<Long, QName> sysDeletedType = qnameDAO.getQName(ContentModel.TYPE_DELETED);
 | 
			
		||||
        if (sysDeletedType == null)
 | 
			
		||||
        {
 | 
			
		||||
            dbQuery.setSysDeletedType(-1L);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            dbQuery.setSysDeletedType(sysDeletedType.getFirst());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Long sinceTxId = options.getSinceTxId();
 | 
			
		||||
        if (sinceTxId == null)
 | 
			
		||||
        {
 | 
			
		||||
            // By default, return search results for all transactions.
 | 
			
		||||
            sinceTxId = -1L;
 | 
			
		||||
        }
 | 
			
		||||
        dbQuery.setSinceTxId(sinceTxId);
 | 
			
		||||
        
 | 
			
		||||
        logger.debug("- query is being prepared");
 | 
			
		||||
        dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup,
 | 
			
		||||
                null, functionContext, metadataIndexCheck2.getPatchApplied());
 | 
			
		||||
 | 
			
		||||
        ResultSet resultSet;
 | 
			
		||||
        resultSet = selectNodesWithPermissions(options, dbQuery);
 | 
			
		||||
        if (logger.isDebugEnabled())
 | 
			
		||||
        {
 | 
			
		||||
            long ms = System.currentTimeMillis() - start;
 | 
			
		||||
            logger.debug("Selected " + resultSet.length() + " nodes with permission resolution in "+ms+" ms");
 | 
			
		||||
        }
 | 
			
		||||
        return asQueryEngineResults(resultSet);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    protected String pickQueryTemplate(QueryOptions options, DBQuery dbQuery)
 | 
			
		||||
    {
 | 
			
		||||
        logger.debug("- using standard table for the query");
 | 
			
		||||
        return SELECT_BY_DYNAMIC_QUERY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ResultSet selectNodesWithPermissions(QueryOptions options, DBQuery dbQuery)
 | 
			
		||||
    {
 | 
			
		||||
        Authority authority = aclCrudDAO.getAuthority(AuthenticationUtil.getRunAsUser());
 | 
			
		||||
        
 | 
			
		||||
        NodePermissionAssessor permissionAssessor = createAssessor(authority);
 | 
			
		||||
        int maxPermsChecks = options.getMaxPermissionChecks() < 0 ? maxPermissionChecks : options.getMaxPermissionChecks();
 | 
			
		||||
        long maxPermCheckTimeMillis = options.getMaxPermissionCheckTimeMillis() < 0
 | 
			
		||||
                ? maxPermissionCheckTimeMillis
 | 
			
		||||
                : options.getMaxPermissionCheckTimeMillis();
 | 
			
		||||
        permissionAssessor.setMaxPermissionChecks(maxPermsChecks);
 | 
			
		||||
        permissionAssessor.setMaxPermissionCheckTimeMillis(maxPermCheckTimeMillis);
 | 
			
		||||
        
 | 
			
		||||
        FilteringResultSet resultSet = acceleratedNodeSelection(options, dbQuery, permissionAssessor);
 | 
			
		||||
        
 | 
			
		||||
        PagingLuceneResultSet plrs = new PagingLuceneResultSet(resultSet, options.getAsSearchParmeters(), nodeService);
 | 
			
		||||
        plrs.setTrimmedResultSet(true);
 | 
			
		||||
        return plrs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected NodePermissionAssessor createAssessor(Authority authority)
 | 
			
		||||
    {
 | 
			
		||||
        return new NodePermissionAssessor(nodeService, permissionService, authority, nodesCache);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FilteringResultSet acceleratedNodeSelection(QueryOptions options, DBQuery dbQuery, NodePermissionAssessor permissionAssessor)
 | 
			
		||||
    {
 | 
			
		||||
        // get list of stores from database
 | 
			
		||||
        stores = nodeDAO.getStores();
 | 
			
		||||
 | 
			
		||||
        List<Node> nodes = new ArrayList<>();
 | 
			
		||||
        int requiredNodes = computeRequiredNodesCount(options);
 | 
			
		||||
        
 | 
			
		||||
        logger.debug("- query sent to the database");
 | 
			
		||||
        template.select(pickQueryTemplate(options, dbQuery), dbQuery, new ResultHandler<Node>()
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void handleResult(ResultContext<? extends Node> context)
 | 
			
		||||
            {
 | 
			
		||||
                if (!maxPermissionCheckEnabled && nodes.size() >= requiredNodes)
 | 
			
		||||
                {
 | 
			
		||||
                    context.stop();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                Node node = context.getResultObject();
 | 
			
		||||
                addStoreInfo(node);
 | 
			
		||||
                
 | 
			
		||||
                boolean shouldCache = shouldCache(options, nodes, requiredNodes);
 | 
			
		||||
                if(shouldCache)
 | 
			
		||||
                {
 | 
			
		||||
                    logger.debug("- selected node "+nodes.size()+": "+node.getUuid()+" "+node.getId());
 | 
			
		||||
                    nodesCache.setValue(node.getId(), node);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    logger.debug("- skipped node "+nodes.size()+": "+node.getUuid()+" "+node.getId());
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if (permissionAssessor.isIncluded(node))
 | 
			
		||||
                {
 | 
			
		||||
                    if (nodes.size() > requiredNodes)
 | 
			
		||||
                    {
 | 
			
		||||
                        nodes.add(node);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        nodes.add(shouldCache ? node : null);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if (permissionAssessor.shouldQuitChecks())
 | 
			
		||||
                {
 | 
			
		||||
                    context.stop();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private boolean shouldCache(QueryOptions options, List<Node> nodes, int requiredNodes)
 | 
			
		||||
            {
 | 
			
		||||
                if (nodes.size() > requiredNodes)
 | 
			
		||||
                {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return nodes.size() >= options.getSkipCount();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        int numberFound = nodes.size();
 | 
			
		||||
        nodes.removeAll(Collections.singleton(null));
 | 
			
		||||
        
 | 
			
		||||
        DBResultSet rs =  createResultSet(options, nodes, numberFound);
 | 
			
		||||
        FilteringResultSet frs = new FilteringResultSet(rs, formInclusionMask(nodes));
 | 
			
		||||
        frs.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.UNLIMITED, PermissionEvaluationMode.EAGER, rs.getResultSetMetaData().getSearchParameters()));
 | 
			
		||||
 
 | 
			
		||||
        logger.debug("- query is completed, "+nodes.size()+" nodes loaded");
 | 
			
		||||
        return frs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private DBResultSet createResultSet(QueryOptions options, List<Node> nodes, int numberFound)
 | 
			
		||||
    {
 | 
			
		||||
        DBResultSet dbResultSet = new DBResultSet(options.getAsSearchParmeters(), nodes, nodeDAO, nodeService, tenantService, Integer.MAX_VALUE);
 | 
			
		||||
        dbResultSet.setNumberFound(numberFound);
 | 
			
		||||
        return dbResultSet;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int computeRequiredNodesCount(QueryOptions options)
 | 
			
		||||
    {
 | 
			
		||||
        int maxItems = options.getMaxItems();
 | 
			
		||||
        if (maxItems == -1 || maxItems == Integer.MAX_VALUE)
 | 
			
		||||
        {
 | 
			
		||||
            return Integer.MAX_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return maxItems + options.getSkipCount() + 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private BitSet formInclusionMask(List<Node> nodes)
 | 
			
		||||
    {
 | 
			
		||||
        BitSet inclusionMask = new BitSet(nodes.size());
 | 
			
		||||
        for (int i=0; i < nodes.size(); i++)
 | 
			
		||||
        {
 | 
			
		||||
            inclusionMask.set(i, true);
 | 
			
		||||
        }
 | 
			
		||||
        return inclusionMask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    private QueryEngineResults asQueryEngineResults(ResultSet paged)
 | 
			
		||||
    {
 | 
			
		||||
        HashSet<String> key = new HashSet<>();
 | 
			
		||||
        key.add("");
 | 
			
		||||
        Map<Set<String>, ResultSet> answer = new HashMap<>();
 | 
			
		||||
        answer.put(key, paged);
 | 
			
		||||
 | 
			
		||||
        return new QueryEngineResults(answer);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /*
 | 
			
		||||
     * (non-Javadoc)
 | 
			
		||||
     * @see org.alfresco.repo.search.impl.querymodel.QueryEngine#getQueryModelFactory()
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public QueryModelFactory getQueryModelFactory()
 | 
			
		||||
    {
 | 
			
		||||
        return new DBQueryModelFactory();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Injection of nodes cache for clean-up and warm up when required
 | 
			
		||||
     * @param cache The node cache to set
 | 
			
		||||
     */
 | 
			
		||||
    public void setNodesCache(SimpleCache<Serializable, Serializable> cache)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodesCache = new EntityLookupCache<>(
 | 
			
		||||
                cache,
 | 
			
		||||
                CACHE_REGION_NODES,
 | 
			
		||||
                new ReadonlyLocalCallbackDAO());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void setNodesCache(EntityLookupCache<Long, Node, NodeRef> nodesCache) 
 | 
			
		||||
    {
 | 
			
		||||
        this.nodesCache = nodesCache;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private class ReadonlyLocalCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, Node, NodeRef>
 | 
			
		||||
    {
 | 
			
		||||
        @Override
 | 
			
		||||
        public Pair<Long, Node> createValue(Node value)
 | 
			
		||||
        {
 | 
			
		||||
            throw new UnsupportedOperationException("Node creation is done externally: " + value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public Pair<Long, Node> findByKey(Long nodeId)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public NodeRef getValueKey(Node value)
 | 
			
		||||
        {
 | 
			
		||||
            return value.getNodeRef();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void addStoreInfo(Node node)
 | 
			
		||||
    {
 | 
			
		||||
        StoreEntity storeEntity = node.getStore();
 | 
			
		||||
        logger.debug("Adding store info for store id " + storeEntity.getId());
 | 
			
		||||
        for (Pair<Long, StoreRef> storeRefPair : stores)
 | 
			
		||||
        {
 | 
			
		||||
            if (Objects.equals(storeEntity.getId(), storeRefPair.getFirst()))
 | 
			
		||||
            {
 | 
			
		||||
                StoreRef storeRef = storeRefPair.getSecond();
 | 
			
		||||
                storeEntity.setIdentifier(storeRef.getIdentifier());
 | 
			
		||||
                storeEntity.setProtocol(storeRef.getProtocol());
 | 
			
		||||
                logger.debug("Added store info" + storeEntity.toString());
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -41,9 +41,13 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
 | 
			
		||||
import org.alfresco.service.cmr.security.PermissionService;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.alfresco.util.EqualsHelper;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
 | 
			
		||||
public class NodePermissionAssessor
 | 
			
		||||
{
 | 
			
		||||
    protected static final Log logger = LogFactory.getLog(NodePermissionAssessor.class);
 | 
			
		||||
 | 
			
		||||
    private final boolean isSystemReading;
 | 
			
		||||
    private final boolean isAdminReading;
 | 
			
		||||
    private final boolean isNullReading;
 | 
			
		||||
@@ -138,24 +142,31 @@ public class NodePermissionAssessor
 | 
			
		||||
 | 
			
		||||
    public void setMaxPermissionChecks(int maxPermissionChecks)
 | 
			
		||||
    {
 | 
			
		||||
        this.maxPermissionChecks = maxPermissionChecks;
 | 
			
		||||
        if (maxPermissionChecks == Integer.MAX_VALUE)
 | 
			
		||||
        {
 | 
			
		||||
            this.maxPermissionChecks = maxPermissionChecks;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            this.maxPermissionChecks = maxPermissionChecks + 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public boolean shouldQuitChecks()
 | 
			
		||||
    {
 | 
			
		||||
        boolean result = false;
 | 
			
		||||
        
 | 
			
		||||
        if (checksPerformed >= maxPermissionChecks)
 | 
			
		||||
        {
 | 
			
		||||
            result = true;
 | 
			
		||||
            logger.warn("Maximum permission checks exceeded (" + maxPermissionChecks + ")");
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if ((System.currentTimeMillis() - startTime) >= maxPermissionCheckTimeMillis)
 | 
			
		||||
        {
 | 
			
		||||
            result = true;
 | 
			
		||||
            logger.warn("Maximum permission checks time exceeded (" + maxPermissionCheckTimeMillis + ")");
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return result;
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis)
 | 
			
		||||
 
 | 
			
		||||
@@ -29,26 +29,29 @@ import java.io.BufferedReader;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStreamReader;
 | 
			
		||||
import java.io.Reader;
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.search.QueryParserException;
 | 
			
		||||
import org.apache.commons.httpclient.Header;
 | 
			
		||||
import org.apache.commons.httpclient.HttpClient;
 | 
			
		||||
import org.apache.commons.httpclient.HttpException;
 | 
			
		||||
import org.apache.commons.httpclient.HttpStatus;
 | 
			
		||||
import org.apache.commons.httpclient.URI;
 | 
			
		||||
import org.apache.commons.httpclient.URIException;
 | 
			
		||||
import org.apache.commons.httpclient.methods.PostMethod;
 | 
			
		||||
import org.apache.commons.httpclient.methods.StringRequestEntity;
 | 
			
		||||
import org.apache.commons.httpclient.params.HttpMethodParams;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.json.JSONException;
 | 
			
		||||
import org.json.JSONObject;
 | 
			
		||||
import org.json.JSONTokener;
 | 
			
		||||
 | 
			
		||||
public abstract class AbstractSolrQueryHTTPClient
 | 
			
		||||
{
 | 
			
		||||
    /** Logger for the class. */
 | 
			
		||||
    private static final Log LOGGER = LogFactory.getLog(AbstractSolrQueryHTTPClient.class);
 | 
			
		||||
 | 
			
		||||
    public static final int DEFAULT_SAVEPOST_BUFFER = 4096;
 | 
			
		||||
    
 | 
			
		||||
    // Constants copied from org.apache.solr.common.params.HighlightParams (solr-solrj:1.4.1)
 | 
			
		||||
@@ -79,11 +82,13 @@ public abstract class AbstractSolrQueryHTTPClient
 | 
			
		||||
    public static final String HIGHLIGHT_PARAMS_SLOP = HIGHLIGHT_PARAMS_HIGHLIGHT + "." + HIGHLIGHT_PARAMS_REGEX + ".slop";
 | 
			
		||||
    public static final String HIGHLIGHT_PARAMS_PATTERN = HIGHLIGHT_PARAMS_HIGHLIGHT + "." + HIGHLIGHT_PARAMS_REGEX + ".pattern";
 | 
			
		||||
    public static final String HIGHLIGHT_PARAMS_MAX_RE_CHARS = HIGHLIGHT_PARAMS_HIGHLIGHT + "." + HIGHLIGHT_PARAMS_REGEX + ".maxAnalyzedChars";
 | 
			
		||||
 | 
			
		||||
    /** List of SOLR Exceptions that should be returning HTTP 501 status code in Remote API. */
 | 
			
		||||
    private static final List<String> STATUS_CODE_501_EXCEPTIONS = List.of("java.lang.UnsupportedOperationException");
 | 
			
		||||
    
 | 
			
		||||
    protected JSONObject postQuery(HttpClient httpClient, String url, JSONObject body) throws UnsupportedEncodingException,
 | 
			
		||||
    IOException, HttpException, URIException, JSONException
 | 
			
		||||
    protected JSONObject postQuery(HttpClient httpClient, String url, JSONObject body) throws IOException, JSONException
 | 
			
		||||
    {
 | 
			
		||||
        PostMethod post = new PostMethod(url);
 | 
			
		||||
        PostMethod post = createNewPostMethod(url);
 | 
			
		||||
        if (body.toString().length() > DEFAULT_SAVEPOST_BUFFER)
 | 
			
		||||
        {
 | 
			
		||||
            post.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE, true);
 | 
			
		||||
@@ -103,9 +108,33 @@ public abstract class AbstractSolrQueryHTTPClient
 | 
			
		||||
                    httpClient.executeMethod(post);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            String responseBodyStr = post.getResponseBodyAsString();
 | 
			
		||||
            if (post.getStatusCode() != HttpServletResponse.SC_OK)
 | 
			
		||||
            {
 | 
			
		||||
                throw new QueryParserException("Request failed " + post.getStatusCode() + " " + url.toString());
 | 
			
		||||
                String trace = null;
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    trace = new JSONObject(responseBodyStr).getJSONObject("error").getString("trace");
 | 
			
		||||
                }
 | 
			
		||||
                catch (JSONException jsonException)
 | 
			
		||||
                {
 | 
			
		||||
                    LOGGER.warn("Node 'error.trace' is not present in Search Services error response: " + responseBodyStr);
 | 
			
		||||
                    LOGGER.warn("A generic error message will be provided. Check SOLR log file in order to find the root cause for this issue");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                int httpStatusCode = post.getStatusCode();
 | 
			
		||||
                String message = "Solr request failed with " + httpStatusCode + " " + url;
 | 
			
		||||
 | 
			
		||||
                // Override the status code for certain exceptions with 501.
 | 
			
		||||
                if (trace != null)
 | 
			
		||||
                {
 | 
			
		||||
                    String traceException = trace.substring(0, trace.indexOf(":")).trim();
 | 
			
		||||
                    if (STATUS_CODE_501_EXCEPTIONS.contains(traceException))
 | 
			
		||||
                    {
 | 
			
		||||
                        httpStatusCode = org.apache.http.HttpStatus.SC_NOT_IMPLEMENTED;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                throw new QueryParserException(message, httpStatusCode);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Reader reader = new BufferedReader(new InputStreamReader(post.getResponseBodyAsStream(), post.getResponseCharSet()));
 | 
			
		||||
@@ -118,4 +147,10 @@ public abstract class AbstractSolrQueryHTTPClient
 | 
			
		||||
            post.releaseConnection();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Helper method that can be overridden by unit tests. */
 | 
			
		||||
    protected PostMethod createNewPostMethod(String url)
 | 
			
		||||
    {
 | 
			
		||||
        return new PostMethod(url);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -456,12 +456,20 @@ public class RepoUsageComponentImpl implements RepoUsageComponent
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Check the license expiry
 | 
			
		||||
        // Check the license expiration
 | 
			
		||||
        Long licenseExpiryDate = restrictions.getLicenseExpiryDate();
 | 
			
		||||
        if (licenseExpiryDate != null)
 | 
			
		||||
        {
 | 
			
		||||
            //For informational purposes, get the remaining number of days, counting from the beginning of the day of each date (now and expiration date)
 | 
			
		||||
            int remainingDays = DateUtil.calculateDays(System.currentTimeMillis(), licenseExpiryDate);
 | 
			
		||||
            if (remainingDays <= 0)
 | 
			
		||||
            int remainingMills = 0;
 | 
			
		||||
            if (remainingDays == 0)
 | 
			
		||||
            {
 | 
			
		||||
                //Get exact number of milliseconds between license expiration time and now to see if is expired
 | 
			
		||||
                remainingMills = DateUtil.calculateMs(System.currentTimeMillis(), licenseExpiryDate);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (remainingDays < 0 || remainingMills < 0)
 | 
			
		||||
            {
 | 
			
		||||
                errors.add(I18NUtil.getMessage("system.usage.err.limit_license_expired"));
 | 
			
		||||
                level = RepoUsageLevel.LOCKED_DOWN;
 | 
			
		||||
 
 | 
			
		||||
@@ -322,7 +322,7 @@ public class CombinedConfig
 | 
			
		||||
                {
 | 
			
		||||
                    combinedTransformers.remove(indexToRemove);
 | 
			
		||||
                    // this may also require the current index i to be changed so we don't skip one.
 | 
			
		||||
                    if (i <= indexToRemove)
 | 
			
		||||
                    if (i >= indexToRemove)
 | 
			
		||||
                    {
 | 
			
		||||
                        i--;
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -71,4 +71,34 @@ public class DateUtil
 | 
			
		||||
        }
 | 
			
		||||
        return days;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate the number of milliseconds between start and end dates based on the <b>default</b> timezone.
 | 
			
		||||
     * If the end date is before the start date, the returned value is negative.
 | 
			
		||||
     *
 | 
			
		||||
     * @param startMs start date in milliseconds
 | 
			
		||||
     * @param endMs   end date in milliseconds
 | 
			
		||||
     * @return number milliseconds between
 | 
			
		||||
     */
 | 
			
		||||
    public static int calculateMs(long startMs, long endMs)
 | 
			
		||||
    {
 | 
			
		||||
        DateTime startDateTime = new DateTime(startMs);
 | 
			
		||||
        DateTime endDateTime = new DateTime(endMs);
 | 
			
		||||
 | 
			
		||||
        int milliseconds;
 | 
			
		||||
        if (endDateTime.isBefore(startDateTime))
 | 
			
		||||
        {
 | 
			
		||||
            Interval interval = new Interval(endDateTime, startDateTime);
 | 
			
		||||
            Period period = interval.toPeriod(PeriodType.millis());
 | 
			
		||||
            milliseconds = 0 - period.getMillis();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            Interval interval = new Interval(startDateTime, endDateTime);
 | 
			
		||||
            Period period = interval.toPeriod(PeriodType.millis());
 | 
			
		||||
            milliseconds = period.getMillis();
 | 
			
		||||
        }
 | 
			
		||||
        return milliseconds;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -38,9 +38,10 @@
 | 
			
		||||
        <property name="dictionaryService" ref="dictionaryService"/>
 | 
			
		||||
        <property name="descriptorService" ref="descriptorComponent"/>
 | 
			
		||||
        <property name="eventFilterRegistry" ref="event2FilterRegistry"/>
 | 
			
		||||
        <property name="event2MessageProducer" ref="event2MessageProducer"/>
 | 
			
		||||
        <property name="transactionService" ref="transactionService"/>
 | 
			
		||||
        <property name="personService" ref="personService"/>
 | 
			
		||||
        <property name="nodeResourceHelper" ref="nodeResourceHelper"/>
 | 
			
		||||
        <property name="eventGeneratorQueue" ref="eventGeneratorQueue"/>
 | 
			
		||||
    </bean>
 | 
			
		||||
 | 
			
		||||
    <bean id="baseNodeResourceHelper" abstract="true">
 | 
			
		||||
@@ -54,7 +55,45 @@
 | 
			
		||||
 | 
			
		||||
    <bean id="nodeResourceHelper" class="org.alfresco.repo.event2.NodeResourceHelper" parent="baseNodeResourceHelper"/>
 | 
			
		||||
 | 
			
		||||
    <bean id="eventGeneratorV2" class="org.alfresco.repo.event2.EventGenerator" parent="baseEventGeneratorV2">
 | 
			
		||||
        <property name="nodeResourceHelper" ref="nodeResourceHelper"/>
 | 
			
		||||
    <bean id="eventGeneratorV2" class="org.alfresco.repo.event2.EventGenerator" parent="baseEventGeneratorV2"/>
 | 
			
		||||
 | 
			
		||||
    <bean id="eventGeneratorQueue" class="org.alfresco.repo.event2.EventGeneratorQueue" >
 | 
			
		||||
        <property name="enqueueThreadPoolExecutor">
 | 
			
		||||
            <ref bean="eventAsyncEnqueueThreadPool" />
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="dequeueThreadPoolExecutor">
 | 
			
		||||
            <ref bean="eventAsyncDequeueThreadPool" />
 | 
			
		||||
        </property>
 | 
			
		||||
       <property name="event2MessageProducer" ref="event2MessageProducer"/>
 | 
			
		||||
    </bean>
 | 
			
		||||
 | 
			
		||||
    <bean id="eventAsyncEnqueueThreadPool" class="org.alfresco.util.ThreadPoolExecutorFactoryBean">
 | 
			
		||||
        <property name="poolName">
 | 
			
		||||
            <value>eventAsyncEnqueueThreadPool</value>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="corePoolSize">
 | 
			
		||||
            <value>${repo.event2.queue.enqueueThreadPool.coreSize}</value>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="maximumPoolSize">
 | 
			
		||||
            <value>${repo.event2.queue.enqueueThreadPool.maximumSize}</value>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="threadPriority">
 | 
			
		||||
            <value>${repo.event2.queue.enqueueThreadPool.priority}</value>
 | 
			
		||||
        </property>
 | 
			
		||||
    </bean>
 | 
			
		||||
 | 
			
		||||
    <bean id="eventAsyncDequeueThreadPool" class="org.alfresco.util.ThreadPoolExecutorFactoryBean">
 | 
			
		||||
        <property name="poolName">
 | 
			
		||||
            <value>eventAsyncDequeueThreadPool</value>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="corePoolSize">
 | 
			
		||||
            <value>${repo.event2.queue.dequeueThreadPool.coreSize}</value>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="maximumPoolSize">
 | 
			
		||||
            <value>${repo.event2.queue.dequeueThreadPool.maximumSize}</value>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="threadPriority">
 | 
			
		||||
            <value>${repo.event2.queue.dequeueThreadPool.priority}</value>
 | 
			
		||||
        </property>
 | 
			
		||||
    </bean>
 | 
			
		||||
</beans>
 | 
			
		||||
 
 | 
			
		||||
@@ -779,6 +779,7 @@
 | 
			
		||||
            <if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if>
 | 
			
		||||
            and na.qname_id in
 | 
			
		||||
                <foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach>
 | 
			
		||||
        <if test="ordered == true">order by node.id ASC</if>
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <!-- Common results for result_NodeAssoc -->
 | 
			
		||||
 
 | 
			
		||||
@@ -266,4 +266,4 @@
 | 
			
		||||
        <property name="registry"               ref="auditModel.extractorRegistry" />        
 | 
			
		||||
        <property name="cmisConnector"          ref="CMISConnector" />
 | 
			
		||||
    </bean>
 | 
			
		||||
</beans>
 | 
			
		||||
</beans>
 | 
			
		||||
 
 | 
			
		||||
@@ -117,6 +117,7 @@
 | 
			
		||||
        <property name="nodeDAO" ref="nodeDAO"/>
 | 
			
		||||
        <property name="maxItemBatchSize" value="${system.fixedACLsUpdater.maxItemBatchSize}"/>
 | 
			
		||||
        <property name="numThreads" value="${system.fixedACLsUpdater.numThreads}"/>
 | 
			
		||||
        <property name="forceSharedACL" value="${system.fixedACLsUpdater.forceSharedACL}"/>
 | 
			
		||||
        <property name="lockTimeToLive" value="${system.fixedACLsUpdater.lockTTL}"/>
 | 
			
		||||
        <property name="policyComponent" ref="policyComponent"/>
 | 
			
		||||
        <property name="policyIgnoreUtil" ref="policyIgnoreUtil"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -178,14 +178,6 @@
 | 
			
		||||
      </property>
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
   <bean id="htmlRenderingEngine"
 | 
			
		||||
      class="org.alfresco.repo.rendition.executer.HTMLRenderingEngine"
 | 
			
		||||
      parent="baseRenderingAction">
 | 
			
		||||
      <property name="tikaConfig">
 | 
			
		||||
         <ref bean="tikaConfig"/>
 | 
			
		||||
      </property>
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
   <bean id="compositeRenderingEngine"
 | 
			
		||||
      class="org.alfresco.repo.rendition.executer.CompositeRenderingEngine"
 | 
			
		||||
      parent="baseRenderingAction">
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,9 @@
 | 
			
		||||
        {"name": "allowEnlargement", "value": true},
 | 
			
		||||
        {"name": "maintainAspectRatio", "value": true},
 | 
			
		||||
        {"name": "autoOrient", "value": true},
 | 
			
		||||
        {"name": "thumbnail", "value": true}
 | 
			
		||||
        {"name": "thumbnail", "value": true},
 | 
			
		||||
        {"name": "startPage", "value": "0"},
 | 
			
		||||
        {"name": "endPage", "value": "0"}
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@@ -21,7 +23,9 @@
 | 
			
		||||
        {"name": "allowEnlargement", "value": false},
 | 
			
		||||
        {"name": "maintainAspectRatio", "value": true},
 | 
			
		||||
        {"name": "autoOrient", "value": true},
 | 
			
		||||
        {"name": "thumbnail", "value": true}
 | 
			
		||||
        {"name": "thumbnail", "value": true},
 | 
			
		||||
        {"name": "startPage", "value": "0"},
 | 
			
		||||
        {"name": "endPage", "value": "0"}
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@@ -33,7 +37,9 @@
 | 
			
		||||
        {"name": "allowEnlargement", "value": false},
 | 
			
		||||
        {"name": "maintainAspectRatio", "value": true},
 | 
			
		||||
        {"name": "autoOrient", "value": true},
 | 
			
		||||
        {"name": "thumbnail", "value": true}
 | 
			
		||||
        {"name": "thumbnail", "value": true},
 | 
			
		||||
        {"name": "startPage", "value": "0"},
 | 
			
		||||
        {"name": "endPage", "value": "0"}
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@@ -45,7 +51,9 @@
 | 
			
		||||
        {"name": "allowEnlargement", "value": false},
 | 
			
		||||
        {"name": "maintainAspectRatio", "value": true},
 | 
			
		||||
        {"name": "autoOrient", "value": true},
 | 
			
		||||
        {"name": "thumbnail", "value": true}
 | 
			
		||||
        {"name": "thumbnail", "value": true},
 | 
			
		||||
        {"name": "startPage", "value": "0"},
 | 
			
		||||
        {"name": "endPage", "value": "0"}
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@@ -57,7 +65,9 @@
 | 
			
		||||
        {"name": "allowEnlargement", "value": false},
 | 
			
		||||
        {"name": "maintainAspectRatio", "value": true},
 | 
			
		||||
        {"name": "autoOrient", "value": true},
 | 
			
		||||
        {"name": "thumbnail", "value": true}
 | 
			
		||||
        {"name": "thumbnail", "value": true},
 | 
			
		||||
        {"name": "startPage", "value": "0"},
 | 
			
		||||
        {"name": "endPage", "value": "0"}
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
repository.name=Main Repository
 | 
			
		||||
 | 
			
		||||
# Schema number
 | 
			
		||||
version.schema=14002
 | 
			
		||||
version.schema=14200
 | 
			
		||||
 | 
			
		||||
# Directory configuration
 | 
			
		||||
 | 
			
		||||
@@ -153,6 +153,7 @@ system.cache.parentAssocs.limitFactor=8
 | 
			
		||||
system.acl.maxPermissionCheckTimeMillis=10000
 | 
			
		||||
# The maximum number of search results to perform permission checks against
 | 
			
		||||
system.acl.maxPermissionChecks=1000
 | 
			
		||||
system.acl.maxPermissionCheckEnabled=false
 | 
			
		||||
 | 
			
		||||
# The maximum number of filefolder list results
 | 
			
		||||
system.filefolderservice.defaultListMaxResults=5000
 | 
			
		||||
@@ -480,7 +481,7 @@ system.thumbnail.definition.default.timeoutMs=-1
 | 
			
		||||
system.thumbnail.definition.default.readLimitTimeMs=-1
 | 
			
		||||
system.thumbnail.definition.default.maxSourceSizeKBytes=-1
 | 
			
		||||
system.thumbnail.definition.default.readLimitKBytes=-1
 | 
			
		||||
system.thumbnail.definition.default.pageLimit=1
 | 
			
		||||
system.thumbnail.definition.default.pageLimit=-1
 | 
			
		||||
system.thumbnail.definition.default.maxPages=-1
 | 
			
		||||
 | 
			
		||||
# Max mimetype sizes to create thumbnail icons
 | 
			
		||||
@@ -1082,6 +1083,8 @@ system.fixedACLsUpdater.lockTTL=10000
 | 
			
		||||
system.fixedACLsUpdater.maxItemBatchSize=100
 | 
			
		||||
# fixedACLsUpdater - the number of threads to use
 | 
			
		||||
system.fixedACLsUpdater.numThreads=4
 | 
			
		||||
# fixedACLsUpdater - Force shared ACL to propagate through children even if there is an unexpected ACL
 | 
			
		||||
system.fixedACLsUpdater.forceSharedACL=false
 | 
			
		||||
# fixedACLsUpdater cron expression - fire at midnight every day
 | 
			
		||||
system.fixedACLsUpdater.cronExpression=0 0 0 * * ? 
 | 
			
		||||
 | 
			
		||||
@@ -1207,6 +1210,15 @@ repo.event2.filter.childAssocTypes=rn:rendition
 | 
			
		||||
repo.event2.filter.users=System, null
 | 
			
		||||
# Topic name
 | 
			
		||||
repo.event2.topic.endpoint=amqp:topic:alfresco.repo.event2
 | 
			
		||||
# Thread pool for async enqueue of repo events
 | 
			
		||||
repo.event2.queue.enqueueThreadPool.priority=1
 | 
			
		||||
repo.event2.queue.enqueueThreadPool.coreSize=8
 | 
			
		||||
repo.event2.queue.enqueueThreadPool.maximumSize=10
 | 
			
		||||
# Thread pool for async dequeue and delivery of repo events
 | 
			
		||||
repo.event2.queue.dequeueThreadPool.priority=1
 | 
			
		||||
repo.event2.queue.dequeueThreadPool.coreSize=1
 | 
			
		||||
repo.event2.queue.dequeueThreadPool.maximumSize=1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# MNT-21083
 | 
			
		||||
# --DELETE_NOT_EXISTS - default settings
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@
 | 
			
		||||
        <property name="cronExpression" value="0 30 * * * ?" /> <!-- Repeat hourly on the half hour -->
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="tempFileCleanerJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.util.TempFileProvider$TempFileCleanerJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -51,7 +51,7 @@
 | 
			
		||||
        <property name="cronExpression" value="0 0 * * * ?" /> <!-- Repeat hourly on the start hour -->
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="webscripts.tempFileCleanerJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.util.TempFileProvider$TempFileCleanerJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -68,7 +68,7 @@
 | 
			
		||||
        <property name="cronExpression" value="${system.content.orphanCleanup.cronExpression}"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="contentStoreCleanerJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.content.cleanup.ContentStoreCleanupJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -82,7 +82,7 @@
 | 
			
		||||
        <property name="cronExpression" value="${system.patch.sharedFolder.cronExpression}"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="patchSharedFolderJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.admin.patch.impl.SharedFolderPatch$SharedFolderPatchJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -96,7 +96,7 @@
 | 
			
		||||
        <property name="cronExpression" value="${system.maximumStringLength.jobCronExpression}"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="maxStringLengthJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.node.db.NodeStringLengthWorker$NodeStringLengthJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -110,7 +110,7 @@
 | 
			
		||||
        <property name="cronExpression" value="0 0 21 * * ?"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="nodeServiceCleanupJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.node.cleanup.NodeCleanupJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -124,7 +124,7 @@
 | 
			
		||||
        <property name="cronExpression" value="0 0/5 * * * ?"/> <!-- run every 5 minutes -->
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="userUsageTrackingJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.usage.UserUsageCollapseJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -140,7 +140,7 @@
 | 
			
		||||
        <property name="repeatInterval" value="3600000"/> <!-- 60 minutes -->
 | 
			
		||||
        <property name="startDelay" value="120"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="taggingStartupJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.tagging.UpdateTagScopesQuartzJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -157,7 +157,7 @@
 | 
			
		||||
        <property name="cronExpression" value="${ticket.cleanup.cronExpression}"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="ticketCleanupJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.security.authentication.TicketCleanupJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -171,7 +171,7 @@
 | 
			
		||||
        <property name="cronExpression" value="${system.patch.surfConfigFolder.cronExpression}"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="patchSurfConfigFolderJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.admin.patch.AsynchronousPatch$AsynchronousPatchJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -186,7 +186,7 @@
 | 
			
		||||
        <property name="cronExpression" value="${system.upgradePasswordHash.jobCronExpression}"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="upgradePasswordHashJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.admin.patch.AsynchronousPatch$AsynchronousPatchJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -201,7 +201,7 @@
 | 
			
		||||
        <property name="cronExpression" value="${system.patch.addUnmovableAspect.cronExpression}"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="patchAddUnmovableAspectJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.admin.patch.AsynchronousPatch$AsynchronousPatchJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
@@ -216,7 +216,7 @@
 | 
			
		||||
        <property name="cronExpression" value="${system.fixedACLsUpdater.cronExpression}"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="fixedAclUpdaterJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.domain.permissions.FixedAclUpdaterJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
 
 | 
			
		||||
@@ -126,6 +126,9 @@
 | 
			
		||||
        <property name="maxPermissionCheckTimeMillis">
 | 
			
		||||
        	<value>${system.acl.maxPermissionCheckTimeMillis}</value>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="maxPermissionCheckEnabled">
 | 
			
		||||
            <value>${system.acl.maxPermissionCheckEnabled}</value>
 | 
			
		||||
        </property>
 | 
			
		||||
    </bean>
 | 
			
		||||
   
 | 
			
		||||
   <bean id="search.dbQueryEngine" class="org.springframework.aop.framework.ProxyFactoryBean">
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
        <property name="cronExpression" value="${solr.backup.alfresco.cronExpression}"/>
 | 
			
		||||
        <property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
 | 
			
		||||
        <property name="jobDetail">
 | 
			
		||||
            <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
            <bean id="search.alfrescoCoreBackupJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 | 
			
		||||
                <property name="jobClass" value="org.alfresco.repo.search.impl.solr.SolrBackupJob"/>
 | 
			
		||||
                <property name="jobDataAsMap">
 | 
			
		||||
                    <map>
 | 
			
		||||
 
 | 
			
		||||
@@ -165,6 +165,8 @@
 | 
			
		||||
        <property name="keyResourceLoader" ref="springKeyResourceLoader"/>
 | 
			
		||||
        <property name="keyStoreParameters" ref="keyStoreParameters"/>
 | 
			
		||||
        <property name="encryptionParameters" ref="md5EncryptionParameters"/>
 | 
			
		||||
        <property name="sharedSecret" value="${solr.sharedSecret}"/>
 | 
			
		||||
        <property name="sharedSecretHeader" value="${solr.sharedSecret.header}"/>
 | 
			
		||||
        <property name="host" value="${solr.host}"/>
 | 
			
		||||
        <property name="port" value="${solr.port}"/>
 | 
			
		||||
        <property name="sslPort" value="${solr.port.ssl}"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -117,31 +117,6 @@
 | 
			
		||||
        "imageMagickOptions"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "transformerName": "htmlToPdfViaOdt",
 | 
			
		||||
      "transformerPipeline" : [
 | 
			
		||||
        {"transformerName": "libreoffice",                  "targetMediaType": "application/vnd.oasis.opendocument.text"},
 | 
			
		||||
        {"transformerName": "libreoffice"}
 | 
			
		||||
      ],
 | 
			
		||||
      "supportedSourceAndTargetList": [
 | 
			
		||||
        {"sourceMediaType": "text/html",                    "targetMediaType": "application/pdf" }
 | 
			
		||||
      ],
 | 
			
		||||
      "transformOptions": [
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "transformerName": "htmlToImageViaPdf",
 | 
			
		||||
      "transformerPipeline" : [
 | 
			
		||||
        {"transformerName": "htmlToPdfViaOdt",               "targetMediaType": "application/pdf"},
 | 
			
		||||
        {"transformerName": "pdfToImageViaPng"}
 | 
			
		||||
      ],
 | 
			
		||||
      "supportedSourceAndTargetList": [
 | 
			
		||||
      ],
 | 
			
		||||
      "transformOptions": [
 | 
			
		||||
        "pdfRendererOptions",
 | 
			
		||||
        "imageMagickOptions"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "transformerName": "ooXmlToImageViaText",
 | 
			
		||||
      "transformerPipeline" : [
 | 
			
		||||
@@ -198,42 +173,66 @@
 | 
			
		||||
        "archiveOptions"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "transformerName": "libreofficeHtmlToPdfViaOdt",
 | 
			
		||||
      "transformerPipeline" : [
 | 
			
		||||
        {"transformerName": "libreoffice", "targetMediaType": "application/vnd.oasis.opendocument.text"},
 | 
			
		||||
        {"transformerName": "libreoffice"}
 | 
			
		||||
      ],
 | 
			
		||||
      "supportedSourceAndTargetList": [
 | 
			
		||||
        {"sourceMediaType": "text/html",  "targetMediaType": "application/pdf" }
 | 
			
		||||
      ],
 | 
			
		||||
      "transformOptions": [
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "transformerName": "libreofficeToPdf",
 | 
			
		||||
      "transformerFailover" : [ "libreoffice", "libreofficeHtmlToPdfViaOdt" ],
 | 
			
		||||
      "supportedSourceAndTargetList": [
 | 
			
		||||
        {"sourceMediaType": "application/vnd.oasis.opendocument.graphics", "priority": 150, "targetMediaType": "application/pdf" },
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc.template",       "priority": 150, "targetMediaType": "application/pdf" },
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress.template",    "priority": 150, "targetMediaType": "application/pdf" },
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.writer.template",     "priority": 150, "targetMediaType": "application/pdf" },
 | 
			
		||||
        {"sourceMediaType": "text/tab-separated-values",                   "priority": 150, "targetMediaType": "application/pdf" },
 | 
			
		||||
        {"sourceMediaType": "application/vnd.visio2013",                   "priority": 150, "targetMediaType": "application/pdf" },
 | 
			
		||||
        {"sourceMediaType": "application/wordperfect",                     "priority": 150, "targetMediaType": "application/pdf" },
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc",                "priority": 150, "targetMediaType": "application/pdf" },
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress",             "priority": 150, "targetMediaType": "application/pdf" }
 | 
			
		||||
      ],
 | 
			
		||||
      "transformOptions": [
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "transformerName": "libreofficeToPdfBoxViaPdf",
 | 
			
		||||
      "transformerPipeline" : [
 | 
			
		||||
        {"transformerName": "libreofficeToPdf", "targetMediaType": "application/pdf"},
 | 
			
		||||
        {"transformerName": "libreoffice", "targetMediaType": "application/pdf"},
 | 
			
		||||
        {"transformerName": "PdfBox"}
 | 
			
		||||
      ],
 | 
			
		||||
      "supportedSourceAndTargetList": [
 | 
			
		||||
        {"sourceMediaType": "application/vnd.oasis.opendocument.graphics",                                  "priority": 150, "targetMediaType": "text/csv"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.oasis.opendocument.graphics",                                  "priority": 150, "targetMediaType": "text/html"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.oasis.opendocument.graphics", "maxSourceSizeBytes": 26214400,  "priority": 150, "targetMediaType": "text/plain"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.oasis.opendocument.graphics",                                  "priority": 150, "targetMediaType": "application/xhtml+xml"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.oasis.opendocument.graphics",                                  "priority": 150, "targetMediaType": "text/xml"},
 | 
			
		||||
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc.template",                                        "priority": 150, "targetMediaType": "text/csv"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc.template",                                        "priority": 150, "targetMediaType": "text/html"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc.template",       "maxSourceSizeBytes": 26214400,  "priority": 150, "targetMediaType": "text/plain"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc.template",                                        "priority": 150, "targetMediaType": "application/xhtml+xml"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc.template",                                        "priority": 150, "targetMediaType": "text/xml"},
 | 
			
		||||
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress.template",                                     "priority": 150, "targetMediaType": "text/csv"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress.template",                                     "priority": 150, "targetMediaType": "text/html"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress.template",    "maxSourceSizeBytes": 26214400,  "priority": 150, "targetMediaType": "text/plain"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress.template",                                     "priority": 150, "targetMediaType": "application/xhtml+xml"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress.template",                                     "priority": 150, "targetMediaType": "text/xml"},
 | 
			
		||||
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.writer.template",                                      "priority": 150, "targetMediaType": "text/csv"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.writer.template",                                      "priority": 150, "targetMediaType": "text/html"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.writer.template",     "maxSourceSizeBytes": 26214400,  "priority": 150, "targetMediaType": "text/plain"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.writer.template",                                      "priority": 150, "targetMediaType": "application/xhtml+xml"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.writer.template",                                      "priority": 150, "targetMediaType": "text/xml"},
 | 
			
		||||
 | 
			
		||||
        {"sourceMediaType": "text/tab-separated-values",                                                    "priority": 150, "targetMediaType": "text/csv"},
 | 
			
		||||
        {"sourceMediaType": "text/tab-separated-values",                                                    "priority": 150, "targetMediaType": "text/html"},
 | 
			
		||||
        {"sourceMediaType": "text/tab-separated-values",                   "maxSourceSizeBytes": 26214400,  "priority": 150, "targetMediaType": "text/plain"},
 | 
			
		||||
        {"sourceMediaType": "text/tab-separated-values",                                                    "priority": 150, "targetMediaType": "application/xhtml+xml"},
 | 
			
		||||
        {"sourceMediaType": "text/tab-separated-values",                                                    "priority": 150, "targetMediaType": "text/xml"},
 | 
			
		||||
 | 
			
		||||
        {"sourceMediaType": "application/vnd.visio2013",                                                    "priority": 150, "targetMediaType": "text/csv"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.visio2013",                                                    "priority": 150, "targetMediaType": "text/html"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.visio2013",                   "maxSourceSizeBytes": 26214400,  "priority": 150, "targetMediaType": "text/plain"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.visio2013",                                                    "priority": 150, "targetMediaType": "application/xhtml+xml"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.visio2013",                                                    "priority": 150, "targetMediaType": "text/xml"},
 | 
			
		||||
 | 
			
		||||
        {"sourceMediaType": "application/wordperfect",                                                      "priority": 150, "targetMediaType": "text/csv"},
 | 
			
		||||
        {"sourceMediaType": "application/wordperfect",                                                      "priority": 150, "targetMediaType": "text/html"},
 | 
			
		||||
        {"sourceMediaType": "application/wordperfect",                     "maxSourceSizeBytes": 26214400,  "priority": 150, "targetMediaType": "text/plain"},
 | 
			
		||||
        {"sourceMediaType": "application/wordperfect",                                                      "priority": 150, "targetMediaType": "application/xhtml+xml"},
 | 
			
		||||
        {"sourceMediaType": "application/wordperfect",                                                      "priority": 150, "targetMediaType": "text/xml"},
 | 
			
		||||
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc",                                                 "priority": 150, "targetMediaType": "text/csv"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc",                                                 "priority": 150, "targetMediaType": "text/html"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc",                "maxSourceSizeBytes": 26214400,  "priority": 150, "targetMediaType": "text/plain"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc",                                                 "priority": 150, "targetMediaType": "application/xhtml+xml"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.calc",                                                 "priority": 150, "targetMediaType": "text/xml"},
 | 
			
		||||
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress",                                              "priority": 150, "targetMediaType": "text/csv"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress",                                              "priority": 150, "targetMediaType": "text/html"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress",             "maxSourceSizeBytes": 26214400,  "priority": 150, "targetMediaType": "text/plain"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress",                                              "priority": 150, "targetMediaType": "application/xhtml+xml"},
 | 
			
		||||
        {"sourceMediaType": "application/vnd.sun.xml.impress",                                              "priority": 150, "targetMediaType": "text/xml"}
 | 
			
		||||
      ],
 | 
			
		||||
      "transformOptions": [
 | 
			
		||||
        "pdfboxOptions"
 | 
			
		||||
@@ -263,6 +262,32 @@
 | 
			
		||||
      "transformOptions": [
 | 
			
		||||
        "tikaOptions"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "transformerName": "htmlToPdfViaTXT",
 | 
			
		||||
      "transformerPipeline" : [
 | 
			
		||||
        {"transformerName": "string", "targetMediaType": "text/plain"},
 | 
			
		||||
        {"transformerName": "libreoffice"}
 | 
			
		||||
      ],
 | 
			
		||||
      "supportedSourceAndTargetList": [
 | 
			
		||||
        {"sourceMediaType": "text/html",                             "targetMediaType": "application/pdf" }
 | 
			
		||||
      ],
 | 
			
		||||
      "transformOptions": [
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "transformerName": "htmlToImageViaTXT",
 | 
			
		||||
      "transformerPipeline" : [
 | 
			
		||||
        {"transformerName": "string", "targetMediaType": "text/plain"},
 | 
			
		||||
        {"transformerName": "textToImageViaPdf"}
 | 
			
		||||
      ],
 | 
			
		||||
      "supportedSourceAndTargetList": [
 | 
			
		||||
        {"sourceMediaType": "text/html",                             "targetMediaType": "image/png" }
 | 
			
		||||
      ],
 | 
			
		||||
      "transformOptions": [
 | 
			
		||||
        "pdfRendererOptions",
 | 
			
		||||
        "imageMagickOptions"
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,9 @@ import org.junit.runners.Suite;
 | 
			
		||||
    org.alfresco.repo.node.db.DbNodeServiceImplTest.class,
 | 
			
		||||
 | 
			
		||||
    org.alfresco.repo.node.cleanup.TransactionCleanupTest.class,
 | 
			
		||||
    org.alfresco.repo.security.person.GetPeopleCannedQueryTest.class
 | 
			
		||||
    org.alfresco.repo.security.person.GetPeopleCannedQueryTest.class,
 | 
			
		||||
 | 
			
		||||
    org.alfresco.repo.domain.schema.script.DeleteNotExistsExecutorTest.class
 | 
			
		||||
})
 | 
			
		||||
public class AllDBTestsTestSuite
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -111,14 +111,15 @@ import org.junit.runners.Suite;
 | 
			
		||||
    org.alfresco.util.schemacomp.validator.SchemaVersionValidatorTest.class,
 | 
			
		||||
    org.alfresco.util.schemacomp.validator.TypeNameOnlyValidatorTest.class,
 | 
			
		||||
    org.alfresco.util.test.junitrules.TemporaryMockOverrideTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.AbstractSolrQueryHTTPClientTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.SpellCheckDecisionManagerTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.SolrStoreMappingWrapperTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.SolrQueryHTTPClientTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.SolrSQLHttpClientTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.SolrStatsResultTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.facet.SolrFacetComparatorTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.facet.FacetQNameUtilsTest.class,
 | 
			
		||||
    org.alfresco.util.BeanExtenderUnitTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.SpellCheckDecisionManagerTest.class,
 | 
			
		||||
    org.alfresco.repo.search.impl.solr.SolrStoreMappingWrapperTest.class,
 | 
			
		||||
    org.alfresco.repo.security.authentication.CompositePasswordEncoderTest.class,
 | 
			
		||||
    org.alfresco.repo.security.authentication.PasswordHashingTest.class,
 | 
			
		||||
    org.alfresco.traitextender.TraitExtenderIntegrationTest.class,
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2017 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
@@ -39,7 +39,7 @@ import org.junit.runners.Suite;
 | 
			
		||||
@RunWith(Categories.class)
 | 
			
		||||
@Categories.ExcludeCategory({DBTests.class, NonBuildTests.class})
 | 
			
		||||
@Suite.SuiteClasses({
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // there is a test that runs for 184s and another one that runs for 40s
 | 
			
		||||
    org.alfresco.repo.attributes.AttributeServiceTest.class,
 | 
			
		||||
 | 
			
		||||
@@ -66,7 +66,9 @@ import org.junit.runners.Suite;
 | 
			
		||||
    org.alfresco.repo.content.RoutingContentStoreTest.class,
 | 
			
		||||
 | 
			
		||||
    org.alfresco.encryption.EncryptionTests.class,
 | 
			
		||||
    org.alfresco.encryption.KeyStoreTests.class
 | 
			
		||||
    org.alfresco.encryption.KeyStoreTests.class,
 | 
			
		||||
 | 
			
		||||
    org.alfresco.repo.content.MimetypeMapContentTest.class
 | 
			
		||||
 | 
			
		||||
    // TODO REPO-2791 org.alfresco.repo.content.routing.StoreSelectorAspectContentStoreTest.class,
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,6 @@ import org.junit.runners.Suite;
 | 
			
		||||
    // This test opens, closes and again opens the alfresco application context.
 | 
			
		||||
    org.alfresco.repo.dictionary.CustomModelRepoRestartTest.class,
 | 
			
		||||
 | 
			
		||||
    org.alfresco.repo.rendition.executer.HTMLRenderingEngineTest.class,
 | 
			
		||||
    org.alfresco.repo.rendition.executer.XSLTFunctionsTest.class,
 | 
			
		||||
    org.alfresco.repo.rendition.executer.XSLTRenderingEngineTest.class,
 | 
			
		||||
    org.alfresco.repo.replication.ReplicationServiceIntegrationTest.class,
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,6 @@ import org.junit.runners.Suite;
 | 
			
		||||
    org.alfresco.repo.site.SiteServiceImplTest.class,
 | 
			
		||||
 | 
			
		||||
    // [classpath:alfresco/application-context.xml, classpath:scriptexec/script-exec-test.xml]
 | 
			
		||||
    org.alfresco.repo.domain.schema.script.DeleteNotExistsExecutorTest.class,
 | 
			
		||||
    org.alfresco.repo.domain.schema.script.ScriptExecutorImplIntegrationTest.class,
 | 
			
		||||
    org.alfresco.repo.domain.schema.script.ScriptBundleExecutorImplIntegrationTest.class,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,7 @@ import org.alfresco.repo.security.authentication.AuthenticationComponent;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationContext;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
 | 
			
		||||
import org.alfresco.repo.site.SiteModel;
 | 
			
		||||
import org.alfresco.repo.tenant.TenantAdminService;
 | 
			
		||||
import org.alfresco.repo.tenant.TenantService;
 | 
			
		||||
import org.alfresco.repo.tenant.TenantUtil;
 | 
			
		||||
@@ -107,7 +108,13 @@ import org.alfresco.service.cmr.search.SearchService;
 | 
			
		||||
import org.alfresco.service.cmr.security.AccessPermission;
 | 
			
		||||
import org.alfresco.service.cmr.security.AuthorityService;
 | 
			
		||||
import org.alfresco.service.cmr.security.AuthorityType;
 | 
			
		||||
import org.alfresco.service.cmr.security.MutableAuthenticationService;
 | 
			
		||||
import org.alfresco.service.cmr.security.PermissionService;
 | 
			
		||||
import org.alfresco.service.cmr.security.PersonService;
 | 
			
		||||
import org.alfresco.service.cmr.security.PersonService.PersonInfo;
 | 
			
		||||
import org.alfresco.service.cmr.site.SiteInfo;
 | 
			
		||||
import org.alfresco.service.cmr.site.SiteService;
 | 
			
		||||
import org.alfresco.service.cmr.site.SiteVisibility;
 | 
			
		||||
import org.alfresco.service.cmr.tagging.TaggingService;
 | 
			
		||||
import org.alfresco.service.cmr.version.Version;
 | 
			
		||||
import org.alfresco.service.cmr.version.VersionService;
 | 
			
		||||
@@ -121,7 +128,6 @@ import org.alfresco.util.ApplicationContextHelper;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.alfresco.util.testing.category.FrequentlyFailingTests;
 | 
			
		||||
import org.alfresco.util.testing.category.LuceneTests;
 | 
			
		||||
import org.alfresco.util.testing.category.PerformanceTests;
 | 
			
		||||
import org.alfresco.util.testing.category.RedundantTests;
 | 
			
		||||
import org.apache.chemistry.opencmis.commons.PropertyIds;
 | 
			
		||||
import org.apache.chemistry.opencmis.commons.data.Ace;
 | 
			
		||||
@@ -213,6 +219,9 @@ public class CMISTest
 | 
			
		||||
    private SearchService searchService;
 | 
			
		||||
    private java.util.Properties globalProperties;
 | 
			
		||||
    private AuditComponentImpl auditComponent;
 | 
			
		||||
    private PersonService personService;
 | 
			
		||||
    private SiteService siteService;
 | 
			
		||||
    private MutableAuthenticationService authenticationService;
 | 
			
		||||
 | 
			
		||||
    private AlfrescoCmisServiceFactory factory;
 | 
			
		||||
	
 | 
			
		||||
@@ -338,6 +347,9 @@ public class CMISTest
 | 
			
		||||
        this.tenantService = (TenantService) ctx.getBean("tenantService");
 | 
			
		||||
        this.searchService = (SearchService) ctx.getBean("SearchService");
 | 
			
		||||
        this.auditComponent = (AuditComponentImpl) ctx.getBean("auditComponent");
 | 
			
		||||
        this.personService = (PersonService) ctx.getBean("personService");
 | 
			
		||||
        this.siteService = (SiteService) ctx.getBean("siteService");
 | 
			
		||||
        this.authenticationService = (MutableAuthenticationService) ctx.getBean("AuthenticationService");
 | 
			
		||||
 | 
			
		||||
        this.globalProperties = (java.util.Properties) ctx.getBean("global-properties");
 | 
			
		||||
        this.globalProperties.setProperty(VersionableAspectTest.AUTO_VERSION_PROPS_KEY, "true");
 | 
			
		||||
@@ -719,12 +731,17 @@ public class CMISTest
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <T extends Object> T withCmisService(CmisServiceCallback<T> callback, CmisVersion cmisVersion)
 | 
			
		||||
    {
 | 
			
		||||
        return withCmisService("admin", "admin", callback, cmisVersion);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <T extends Object> T withCmisService(String username, String password, CmisServiceCallback<T> callback, CmisVersion cmisVersion)
 | 
			
		||||
    {
 | 
			
		||||
        CmisService cmisService = null;
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            CallContext context = new SimpleCallContext("admin", "admin", cmisVersion);
 | 
			
		||||
            CallContext context = new SimpleCallContext(username, password, cmisVersion);
 | 
			
		||||
            cmisService = factory.getService(context);
 | 
			
		||||
            T ret = callback.execute(cmisService);
 | 
			
		||||
            return ret;
 | 
			
		||||
@@ -4101,6 +4118,108 @@ public class CMISTest
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This test ensures that a non member user of a private site, can edit metadata on a document (where the non member user
 | 
			
		||||
     * has "SiteCollaborator" role) placed on the site.
 | 
			
		||||
     *
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMNT20006() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        AuthenticationUtil.pushAuthentication();
 | 
			
		||||
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
 | 
			
		||||
 | 
			
		||||
        final String nonMemberUsername = "user" + System.currentTimeMillis();
 | 
			
		||||
        final String nonMemberPassword = "pass" + System.currentTimeMillis();
 | 
			
		||||
        final String siteId = "site" + System.currentTimeMillis();
 | 
			
		||||
        final String originalDescription = "my description";
 | 
			
		||||
 | 
			
		||||
        NodeRef fileNode;
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            fileNode = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<NodeRef>()
 | 
			
		||||
            {
 | 
			
		||||
                public NodeRef execute() throws Throwable
 | 
			
		||||
                {
 | 
			
		||||
                    // Create user
 | 
			
		||||
                    authenticationService.createAuthentication(nonMemberUsername, nonMemberPassword.toCharArray());
 | 
			
		||||
                    Map<QName, Serializable> props = new HashMap<QName, Serializable>();
 | 
			
		||||
                    String email = nonMemberUsername + "@testcmis.com";
 | 
			
		||||
                    props.put(ContentModel.PROP_USERNAME, nonMemberUsername);
 | 
			
		||||
                    props.put(ContentModel.PROP_FIRSTNAME, nonMemberUsername);
 | 
			
		||||
                    props.put(ContentModel.PROP_LASTNAME, nonMemberUsername);
 | 
			
		||||
                    props.put(ContentModel.PROP_EMAIL, email);
 | 
			
		||||
                    PersonInfo personInfo = personService.getPerson(personService.createPerson(props));
 | 
			
		||||
                    assertNotNull("Null person info", personInfo);
 | 
			
		||||
 | 
			
		||||
                    // Create site
 | 
			
		||||
                    SiteInfo siteInfo = siteService.createSite("myPreset", siteId, "myTitle", "myDescription", SiteVisibility.PRIVATE);
 | 
			
		||||
                    assertNotNull("Null site info", siteInfo);
 | 
			
		||||
                    NodeRef siteDocLib = siteService.createContainer(siteId, SiteService.DOCUMENT_LIBRARY, ContentModel.TYPE_FOLDER, null);
 | 
			
		||||
                    assertNotNull("Null site doclib", siteDocLib);
 | 
			
		||||
 | 
			
		||||
                    // Create node in site
 | 
			
		||||
                    String nodeName = "node" + System.currentTimeMillis() + ".txt";
 | 
			
		||||
                    NodeRef fileNode = nodeService.createNode(siteDocLib, ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS, ContentModel.TYPE_CONTENT).getChildRef();
 | 
			
		||||
                    ContentWriter writer = contentService.getWriter(fileNode, ContentModel.PROP_CONTENT, true);
 | 
			
		||||
                    writer.putContent("my node content");
 | 
			
		||||
                    nodeService.setProperty(fileNode, ContentModel.PROP_TITLE, nodeName);
 | 
			
		||||
                    nodeService.setProperty(fileNode, ContentModel.PROP_DESCRIPTION, originalDescription);
 | 
			
		||||
                    assertNotNull("Null file node", fileNode);
 | 
			
		||||
                    assertTrue(nodeService.exists(fileNode));
 | 
			
		||||
 | 
			
		||||
                    // Sets node permissions to the user who is not member of the site and get site activities
 | 
			
		||||
                    permissionService.setPermission(fileNode, nonMemberUsername, SiteModel.SITE_COLLABORATOR, true);
 | 
			
		||||
 | 
			
		||||
                    return fileNode;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            AuthenticationUtil.popAuthentication();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Edit metadata
 | 
			
		||||
        final String newDescription = "new node description";
 | 
			
		||||
 | 
			
		||||
        Boolean updated = withCmisService(nonMemberUsername, nonMemberPassword, new CmisServiceCallback<Boolean>()
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public Boolean execute(CmisService cmisService)
 | 
			
		||||
            {
 | 
			
		||||
                Boolean updated = true;
 | 
			
		||||
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    // Obtain repository id
 | 
			
		||||
                    List<RepositoryInfo> repositories = cmisService.getRepositoryInfos(null);
 | 
			
		||||
                    assertTrue(repositories.size() > 0);
 | 
			
		||||
                    RepositoryInfo repo = repositories.get(0);
 | 
			
		||||
                    String repositoryId = repo.getId();
 | 
			
		||||
 | 
			
		||||
                    // Id holder
 | 
			
		||||
                    Holder<String> objectIdHolder = new Holder<String>(fileNode.toString());
 | 
			
		||||
 | 
			
		||||
                    // New Properties
 | 
			
		||||
                    PropertiesImpl newProperties = new PropertiesImpl();
 | 
			
		||||
                    newProperties.addProperty(new PropertyStringImpl(PropertyIds.DESCRIPTION, newDescription));
 | 
			
		||||
                    cmisService.updateProperties(repositoryId, objectIdHolder, null, newProperties, null);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception e)
 | 
			
		||||
                {
 | 
			
		||||
                    updated = false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return updated;
 | 
			
		||||
            };
 | 
			
		||||
        }, CmisVersion.CMIS_1_1);
 | 
			
		||||
 | 
			
		||||
        assertTrue("Document metadata not updated", updated);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private NodeRef createFolder(NodeRef parentNodeRef, String folderName, QName folderType) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,8 @@
 | 
			
		||||
 | 
			
		||||
package org.alfresco.repo.action;
 | 
			
		||||
 | 
			
		||||
import static java.lang.Thread.sleep;
 | 
			
		||||
import static junit.framework.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertFalse;
 | 
			
		||||
import static org.junit.Assert.assertNotNull;
 | 
			
		||||
import static org.junit.Assert.assertTrue;
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +39,7 @@ import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.repo.action.executer.ActionExecuter;
 | 
			
		||||
import org.alfresco.repo.action.executer.ContentMetadataExtracter;
 | 
			
		||||
import org.alfresco.repo.action.executer.CounterIncrementActionExecuter;
 | 
			
		||||
import org.alfresco.repo.action.executer.ScriptActionExecuter;
 | 
			
		||||
@@ -259,7 +260,7 @@ public class ActionServiceImpl2Test
 | 
			
		||||
    public void testExecuteScript() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        final NodeRef scriptToBeExecuted = addTempScript("changeFileNameTest.js",
 | 
			
		||||
                "document.properties.name = \"Changed\" + \"_\" + document.properties.name;\ndocument.save();");
 | 
			
		||||
                "document.properties.name = \"Changed_\" + document.properties.name;\ndocument.save();");
 | 
			
		||||
        assertNotNull("Failed to add the test script.", scriptToBeExecuted);
 | 
			
		||||
 | 
			
		||||
        // add a test file to the Site in order to change its name
 | 
			
		||||
@@ -310,6 +311,73 @@ public class ActionServiceImpl2Test
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        //Execute script not in Data Dictionary > Scripts
 | 
			
		||||
        AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager);
 | 
			
		||||
        NodeRef companyHomeRef = wellKnownNodes.getCompanyHome();
 | 
			
		||||
        NodeRef sharedFolderRef = nodeService.getChildByName(companyHomeRef, ContentModel.ASSOC_CONTAINS,
 | 
			
		||||
                "Shared");
 | 
			
		||||
        final NodeRef invalidScriptRef = addTempScript("changeFileNameTest.js",
 | 
			
		||||
                "document.properties.name = \"Invalid_Change.pdf\";\ndocument.save();",sharedFolderRef);
 | 
			
		||||
        assertNotNull("Failed to add the test script.", scriptToBeExecuted);
 | 
			
		||||
        transactionHelper.doInTransaction(new RetryingTransactionCallback<Void>()
 | 
			
		||||
        {
 | 
			
		||||
            public Void execute() throws Throwable
 | 
			
		||||
            {
 | 
			
		||||
                // Create the action
 | 
			
		||||
                Action action = actionService.createAction(ScriptActionExecuter.NAME);
 | 
			
		||||
                action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, invalidScriptRef);
 | 
			
		||||
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    // Execute the action
 | 
			
		||||
                    actionService.executeAction(action, testNode);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Throwable th)
 | 
			
		||||
                {
 | 
			
		||||
                    // do nothing
 | 
			
		||||
                }
 | 
			
		||||
                assertFalse("Scripts outside of Data Dictionary Scripts folder should not be executed",
 | 
			
		||||
                        ("Invalid_Change.pdf".equals(nodeService.getProperty(testNode, ContentModel.PROP_NAME))));
 | 
			
		||||
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testActionResult() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
 | 
			
		||||
        transactionHelper.doInTransaction(new RetryingTransactionCallback<Void>()
 | 
			
		||||
        {
 | 
			
		||||
            public Void execute() throws Throwable
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    // Create the script node reference
 | 
			
		||||
                    NodeRef script = addTempScript("test-action-result-script.js", "\"VALUE\";");
 | 
			
		||||
 | 
			
		||||
                    // Create the action
 | 
			
		||||
                    Action action = actionService.createAction(ScriptActionExecuter.NAME);
 | 
			
		||||
                    action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, script);
 | 
			
		||||
 | 
			
		||||
                    // Execute the action
 | 
			
		||||
                    actionService.executeAction(action, testNode);
 | 
			
		||||
 | 
			
		||||
                    // Get the result
 | 
			
		||||
                    String result = (String) action.getParameterValue(ActionExecuter.PARAM_RESULT);
 | 
			
		||||
                    assertNotNull(result);
 | 
			
		||||
                    assertEquals("VALUE", result);
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    AuthenticationUtil.clearCurrentSecurityContext();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
@@ -369,6 +437,32 @@ public class ActionServiceImpl2Test
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private NodeRef addTempScript(final String scriptFileName, final String javaScript, final NodeRef parentRef)
 | 
			
		||||
    {
 | 
			
		||||
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
 | 
			
		||||
        return transactionHelper.doInTransaction(new RetryingTransactionCallback<NodeRef>()
 | 
			
		||||
        {
 | 
			
		||||
            public NodeRef execute() throws Throwable
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                // Create the script node reference
 | 
			
		||||
                NodeRef script = nodeService.createNode(parentRef, ContentModel.ASSOC_CONTAINS,
 | 
			
		||||
                        QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, scriptFileName),
 | 
			
		||||
                        ContentModel.TYPE_CONTENT).getChildRef();
 | 
			
		||||
 | 
			
		||||
                nodeService.setProperty(script, ContentModel.PROP_NAME, scriptFileName);
 | 
			
		||||
 | 
			
		||||
                ContentWriter contentWriter = contentService.getWriter(script, ContentModel.PROP_CONTENT, true);
 | 
			
		||||
                contentWriter.setMimetype(MimetypeMap.MIMETYPE_JAVASCRIPT);
 | 
			
		||||
                contentWriter.setEncoding("UTF-8");
 | 
			
		||||
                contentWriter.putContent(javaScript);
 | 
			
		||||
 | 
			
		||||
                tempNodes.addNodeRef(script);              
 | 
			
		||||
                return script;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private NodeRef addTempScript(final String scriptFileName, final String javaScript)
 | 
			
		||||
    {
 | 
			
		||||
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
 | 
			
		||||
@@ -386,20 +480,7 @@ public class ActionServiceImpl2Test
 | 
			
		||||
                NodeRef scriptsRef = nodeService.getChildByName(dataDictionaryRef, ContentModel.ASSOC_CONTAINS,
 | 
			
		||||
                        "Scripts");
 | 
			
		||||
 | 
			
		||||
                // Create the script node reference
 | 
			
		||||
                NodeRef script = nodeService.createNode(scriptsRef, ContentModel.ASSOC_CONTAINS,
 | 
			
		||||
                        QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, scriptFileName),
 | 
			
		||||
                        ContentModel.TYPE_CONTENT).getChildRef();
 | 
			
		||||
 | 
			
		||||
                nodeService.setProperty(script, ContentModel.PROP_NAME, scriptFileName);
 | 
			
		||||
 | 
			
		||||
                ContentWriter contentWriter = contentService.getWriter(script, ContentModel.PROP_CONTENT, true);
 | 
			
		||||
                contentWriter.setMimetype(MimetypeMap.MIMETYPE_JAVASCRIPT);
 | 
			
		||||
                contentWriter.setEncoding("UTF-8");
 | 
			
		||||
                contentWriter.putContent(javaScript);
 | 
			
		||||
 | 
			
		||||
                tempNodes.addNodeRef(script);              
 | 
			
		||||
                return script;
 | 
			
		||||
                return addTempScript(scriptFileName, javaScript, scriptsRef);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -805,46 +805,6 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
 | 
			
		||||
        assertEquals(action4, savedAction2.getAction(2));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Test the action result parameter
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testActionResult()
 | 
			
		||||
    {
 | 
			
		||||
        // We need to run this test as Administrator. The ScriptAction has to run as a full user (instead of as System)
 | 
			
		||||
        // so that we can setup the Person object in the ScriptNode
 | 
			
		||||
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // Create the script node reference
 | 
			
		||||
            NodeRef script = this.nodeService.createNode(
 | 
			
		||||
                    this.folder,
 | 
			
		||||
                    ContentModel.ASSOC_CONTAINS,
 | 
			
		||||
                    QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testScript.js"),
 | 
			
		||||
                    ContentModel.TYPE_CONTENT).getChildRef();
 | 
			
		||||
            this.nodeService.setProperty(script, ContentModel.PROP_NAME, "testScript.js");
 | 
			
		||||
            ContentWriter contentWriter = this.contentService.getWriter(script, ContentModel.PROP_CONTENT, true);
 | 
			
		||||
            contentWriter.setMimetype("text/plain");
 | 
			
		||||
            contentWriter.setEncoding("UTF-8");
 | 
			
		||||
            contentWriter.putContent("\"VALUE\";");
 | 
			
		||||
 | 
			
		||||
            // Create the action
 | 
			
		||||
            Action action1 = this.actionService.createAction(ScriptActionExecuter.NAME);
 | 
			
		||||
            action1.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, script);
 | 
			
		||||
 | 
			
		||||
            // Execute the action
 | 
			
		||||
            this.actionService.executeAction(action1, this.nodeRef);
 | 
			
		||||
 | 
			
		||||
            // Get the result
 | 
			
		||||
            String result = (String)action1.getParameterValue(ActionExecuter.PARAM_RESULT);
 | 
			
		||||
            assertNotNull(result);
 | 
			
		||||
            assertEquals("VALUE", result);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            AuthenticationUtil.clearCurrentSecurityContext();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /** ===================================================================================
 | 
			
		||||
     *  Test asynchronous actions
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
@@ -23,125 +23,138 @@
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.content;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileOutputStream;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
 | 
			
		||||
import junit.framework.TestCase;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.content.filestore.FileContentReader;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ContentReader;
 | 
			
		||||
import org.alfresco.service.cmr.repository.MimetypeService;
 | 
			
		||||
import org.alfresco.test_category.OwnJVMTestsCategory;
 | 
			
		||||
import org.alfresco.util.DataModelTestApplicationContextHelper;
 | 
			
		||||
import org.apache.poi.util.IOUtils;
 | 
			
		||||
import org.junit.experimental.categories.Category;
 | 
			
		||||
import org.springframework.context.ApplicationContext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Content specific tests for MimeTypeMap
 | 
			
		||||
 * 
 | 
			
		||||
 * @see org.alfresco.repo.content.MimetypeMap
 | 
			
		||||
 * @see org.alfresco.repo.content.MimetypeMapTest
 | 
			
		||||
 */
 | 
			
		||||
@Category(OwnJVMTestsCategory.class)
 | 
			
		||||
public class MimetypeMapContentTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    private static ApplicationContext ctx = DataModelTestApplicationContextHelper.getApplicationContext();
 | 
			
		||||
    
 | 
			
		||||
    private MimetypeService mimetypeService;
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setUp() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        mimetypeService =  (MimetypeService)ctx.getBean("mimetypeService");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void testGuessMimetypeForFile() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Correct ones
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/msword", 
 | 
			
		||||
                mimetypeService.guessMimetype("something.doc", openQuickTestFile("quick.doc"))
 | 
			
		||||
        );
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/msword", 
 | 
			
		||||
                mimetypeService.guessMimetype("SOMETHING.DOC", openQuickTestFile("quick.doc"))
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Incorrect ones, Tika spots the mistake
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/msword", 
 | 
			
		||||
                mimetypeService.guessMimetype("something.pdf", openQuickTestFile("quick.doc"))
 | 
			
		||||
        );
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/pdf", 
 | 
			
		||||
                mimetypeService.guessMimetype("something.doc", openQuickTestFile("quick.pdf"))
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Ones where we use a different mimetype to the canonical one
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "image/bmp", // Officially image/x-ms-bmp 
 | 
			
		||||
                mimetypeService.guessMimetype("image.bmp", openQuickTestFile("quick.bmp"))
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Ones where we know about the parent, and Tika knows about the details
 | 
			
		||||
        assertEquals(
 | 
			
		||||
              "application/dita+xml", // Full version:  application/dita+xml;format=concept
 | 
			
		||||
              mimetypeService.guessMimetype("concept.dita", openQuickTestFile("quickConcept.dita"))
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Alfresco Specific ones, that Tika doesn't know about
 | 
			
		||||
        assertEquals(
 | 
			
		||||
              "application/acp", 
 | 
			
		||||
              mimetypeService.guessMimetype("something.acp", openQuickTestFile("quick.acp"))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        // Where the file is corrupted
 | 
			
		||||
        File tmp = File.createTempFile("alfresco", ".tmp");
 | 
			
		||||
        ContentReader reader = openQuickTestFile("quick.doc");
 | 
			
		||||
        InputStream inp = reader.getContentInputStream();
 | 
			
		||||
        byte[] trunc = new byte[512+256];
 | 
			
		||||
        IOUtils.readFully(inp, trunc);
 | 
			
		||||
        inp.close();
 | 
			
		||||
        FileOutputStream out = new FileOutputStream(tmp);
 | 
			
		||||
        out.write(trunc);
 | 
			
		||||
        out.close();
 | 
			
		||||
        ContentReader truncReader = new FileContentReader(tmp);
 | 
			
		||||
        
 | 
			
		||||
        // Because the file is truncated, Tika won't be able to process the contents
 | 
			
		||||
        //  of the OLE2 structure
 | 
			
		||||
        // So, it'll fall back to just OLE2, but it won't fail
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/x-tika-msoffice", 
 | 
			
		||||
                mimetypeService.guessMimetype(null, truncReader)
 | 
			
		||||
        );
 | 
			
		||||
        // But with the filename it'll be able to use the .doc extension
 | 
			
		||||
        //  to guess at it being a .Doc file
 | 
			
		||||
        assertEquals(
 | 
			
		||||
              "application/msword", 
 | 
			
		||||
              mimetypeService.guessMimetype("something.doc", truncReader)
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Lotus notes EML files (ALF-16381 / TIKA-1042)
 | 
			
		||||
        assertEquals(
 | 
			
		||||
              "message/rfc822", 
 | 
			
		||||
              mimetypeService.guessMimetype("something.eml", openQuickTestFile("quickLotus.eml"))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private ContentReader openQuickTestFile(String filename)
 | 
			
		||||
    {
 | 
			
		||||
        URL url = getClass().getClassLoader().getResource("quick/" + filename);
 | 
			
		||||
        if(url == null)
 | 
			
		||||
        {
 | 
			
		||||
           fail("Quick test file \"" + filename + "\" wasn't found");
 | 
			
		||||
        }
 | 
			
		||||
        File file = new File(url.getFile());
 | 
			
		||||
        return new FileContentReader(file);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
package org.alfresco.repo.content;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileOutputStream;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
 | 
			
		||||
import junit.framework.TestCase;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.content.filestore.FileContentReader;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ContentReader;
 | 
			
		||||
import org.alfresco.service.cmr.repository.MimetypeService;
 | 
			
		||||
import org.alfresco.test_category.OwnJVMTestsCategory;
 | 
			
		||||
import org.alfresco.util.ApplicationContextHelper;
 | 
			
		||||
import org.apache.poi.util.IOUtils;
 | 
			
		||||
import org.junit.experimental.categories.Category;
 | 
			
		||||
import org.springframework.context.ApplicationContext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Content specific tests for MimeTypeMap
 | 
			
		||||
 * 
 | 
			
		||||
 * @see org.alfresco.repo.content.MimetypeMap
 | 
			
		||||
 * @see org.alfresco.repo.content.MimetypeMapTest
 | 
			
		||||
 */
 | 
			
		||||
@Category({OwnJVMTestsCategory.class})
 | 
			
		||||
public class MimetypeMapContentTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
 | 
			
		||||
 | 
			
		||||
    private MimetypeService mimetypeService;
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setUp() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        mimetypeService =  (MimetypeService)ctx.getBean("mimetypeService");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void testGuessPdfMimetype() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/pdf",
 | 
			
		||||
                mimetypeService.guessMimetype("something.doc", openQuickTestFile("quick.pdf"))
 | 
			
		||||
        );
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/pdf",
 | 
			
		||||
                mimetypeService.guessMimetype(null, openQuickTestFile("quick.pdf"))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void testGuessMimetypeForFile() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Correct ones
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/msword", 
 | 
			
		||||
                mimetypeService.guessMimetype("something.doc", openQuickTestFile("quick.doc"))
 | 
			
		||||
        );
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/msword", 
 | 
			
		||||
                mimetypeService.guessMimetype("SOMETHING.DOC", openQuickTestFile("quick.doc"))
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Incorrect ones, Tika spots the mistake
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/msword", 
 | 
			
		||||
                mimetypeService.guessMimetype("something.pdf", openQuickTestFile("quick.doc"))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Ones where we use a different mimetype to the canonical one
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "image/bmp", // Officially image/x-ms-bmp 
 | 
			
		||||
                mimetypeService.guessMimetype("image.bmp", openQuickTestFile("quick.bmp"))
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Ones where we know about the parent, and Tika knows about the details
 | 
			
		||||
        assertEquals(
 | 
			
		||||
              "application/dita+xml", // Full version:  application/dita+xml;format=concept
 | 
			
		||||
              mimetypeService.guessMimetype("concept.dita", openQuickTestFile("quickConcept.dita"))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
// Commented out when the test class was reintroduced after many years of not being run. Failed as the type was
 | 
			
		||||
// identified as a zip. Reintroduced to check guessMimetype works without pdfbox libraries.
 | 
			
		||||
//
 | 
			
		||||
//        // Alfresco Specific ones, that Tika doesn't know about
 | 
			
		||||
//        assertEquals(
 | 
			
		||||
//              "application/acp",
 | 
			
		||||
//              mimetypeService.guessMimetype("something.acp", openQuickTestFile("quick.acp"))
 | 
			
		||||
//        );
 | 
			
		||||
        
 | 
			
		||||
        // Where the file is corrupted
 | 
			
		||||
        File tmp = File.createTempFile("alfresco", ".tmp");
 | 
			
		||||
        ContentReader reader = openQuickTestFile("quick.doc");
 | 
			
		||||
        InputStream inp = reader.getContentInputStream();
 | 
			
		||||
        byte[] trunc = new byte[512+256];
 | 
			
		||||
        IOUtils.readFully(inp, trunc);
 | 
			
		||||
        inp.close();
 | 
			
		||||
        FileOutputStream out = new FileOutputStream(tmp);
 | 
			
		||||
        out.write(trunc);
 | 
			
		||||
        out.close();
 | 
			
		||||
        ContentReader truncReader = new FileContentReader(tmp);
 | 
			
		||||
        
 | 
			
		||||
        // Because the file is truncated, Tika won't be able to process the contents
 | 
			
		||||
        //  of the OLE2 structure
 | 
			
		||||
        // So, it'll fall back to just OLE2, but it won't fail
 | 
			
		||||
        assertEquals(
 | 
			
		||||
                "application/x-tika-msoffice", 
 | 
			
		||||
                mimetypeService.guessMimetype(null, truncReader)
 | 
			
		||||
        );
 | 
			
		||||
// Commented out when the test class was reintroduced after many years of not being run. Failed to open a
 | 
			
		||||
// stream onto the channel. Reintroduced to check guessMimetype works without pdfbox libraries.
 | 
			
		||||
//
 | 
			
		||||
//        // But with the filename it'll be able to use the .doc extension
 | 
			
		||||
//        //  to guess at it being a .Doc file
 | 
			
		||||
//        assertEquals(
 | 
			
		||||
//              "application/msword",
 | 
			
		||||
//              mimetypeService.guessMimetype("something.doc", truncReader)
 | 
			
		||||
//        );
 | 
			
		||||
        
 | 
			
		||||
        // Lotus notes EML files (ALF-16381 / TIKA-1042)
 | 
			
		||||
        assertEquals(
 | 
			
		||||
              "message/rfc822", 
 | 
			
		||||
              mimetypeService.guessMimetype("something.eml", openQuickTestFile("quickLotus.eml"))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private ContentReader openQuickTestFile(String filename)
 | 
			
		||||
    {
 | 
			
		||||
        URL url = getClass().getClassLoader().getResource("quick/" + filename);
 | 
			
		||||
        if(url == null)
 | 
			
		||||
        {
 | 
			
		||||
           fail("Quick test file \"" + filename + "\" wasn't found");
 | 
			
		||||
        }
 | 
			
		||||
        File file = new File(url.getFile());
 | 
			
		||||
        return new FileContentReader(file);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -90,8 +90,8 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
    private CheckOutCheckInService checkOutCheckInService;
 | 
			
		||||
    private ContentService contentService;
 | 
			
		||||
    private AuthorityService authorityService;
 | 
			
		||||
    private static final long MAX_TRANSACTION_TIME_DEFAULT = 50;
 | 
			
		||||
    private static final int[] filesPerLevelMoreFolders = { 5, 3, 1, 50 };
 | 
			
		||||
    private static final long MAX_TRANSACTION_TIME_DEFAULT = 10;
 | 
			
		||||
    private static final int[] filesPerLevelMoreFolders = { 5, 1, 1, 1, 1, 1, 1 };
 | 
			
		||||
    private static final int[] filesPerLevelMoreFiles = { 5, 100 };
 | 
			
		||||
    private long maxTransactionTime;
 | 
			
		||||
    private static HashMap<Integer, Class<?>> errors;
 | 
			
		||||
@@ -306,7 +306,7 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
    public void testSyncCopyNoTimeOut() throws FileExistsException, FileNotFoundException
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef originalRef = createFolderHierarchyInRootForFolderTests("originFolder");
 | 
			
		||||
        NodeRef targetRef = createFolderHierarchyInRootForFolderTests("targetFolder");
 | 
			
		||||
        NodeRef targetRefBase = createFolderHierarchyInRootForFolderTests("targetFolder");
 | 
			
		||||
 | 
			
		||||
        // Get ACLS for later comparison
 | 
			
		||||
        ACLComparator aclComparatorOrigin = new ACLComparator(originalRef);
 | 
			
		||||
@@ -316,6 +316,19 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
            maxTransactionTime = 86400000;
 | 
			
		||||
            setFixedAclMaxTransactionTime(permissionsDaoComponent, homeFolderNodeRef, maxTransactionTime);
 | 
			
		||||
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRefBase, true, false);
 | 
			
		||||
                permissionService.setPermission(targetRefBase, TEST_GROUP_NAME_FULL, PermissionService.CONSUMER, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Trigger the job so the target folder structure has a different base ACL
 | 
			
		||||
            triggerFixedACLJob();
 | 
			
		||||
            assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
 | 
			
		||||
            NodeRef targetRef = nodeDAO.getNodePair(getChild(nodeDAO.getNodePair(targetRefBase).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Set Shared permissions on origin
 | 
			
		||||
            permissionService.setInheritParentPermissions(originalRef, true, false);
 | 
			
		||||
            permissionService.setPermission(originalRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
 | 
			
		||||
@@ -343,7 +356,7 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            deleteNodes(originalRef);
 | 
			
		||||
            deleteNodes(targetRef);
 | 
			
		||||
            deleteNodes(targetRefBase);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -354,14 +367,26 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
    public void testAsyncWithNodeCopy()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyOriginFolder");
 | 
			
		||||
        NodeRef targetRef = createFile(fileFolderService, homeFolderNodeRef, "testAsyncWithNodeCopyTargetFolder",
 | 
			
		||||
                ContentModel.TYPE_FOLDER);
 | 
			
		||||
 | 
			
		||||
        // Get ACLS for later comparison
 | 
			
		||||
        ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
        NodeRef targetRefBase = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyTargetFolder");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRefBase, true, false);
 | 
			
		||||
                permissionService.setPermission(targetRefBase, TEST_GROUP_NAME_FULL, PermissionService.CONSUMER, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Trigger the job so the target folder structure has a different base ACL
 | 
			
		||||
            triggerFixedACLJob();
 | 
			
		||||
            assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
 | 
			
		||||
            NodeRef targetRef = nodeDAO.getNodePair(getChild(nodeDAO.getNodePair(targetRefBase).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Get ACLS for later comparison
 | 
			
		||||
            ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRef, false, false);
 | 
			
		||||
@@ -410,7 +435,7 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            deleteNodes(folderRef);
 | 
			
		||||
            deleteNodes(targetRef);
 | 
			
		||||
            deleteNodes(targetRefBase);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -421,13 +446,26 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
    public void testAsyncWithNodeCopyToPendingFolder()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyOriginFolder");
 | 
			
		||||
        NodeRef targetRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyTargetFolder");
 | 
			
		||||
 | 
			
		||||
        // Get ACLS for later comparison
 | 
			
		||||
        ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
        NodeRef targetRefBase = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyTargetFolder");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRefBase, true, false);
 | 
			
		||||
                permissionService.setPermission(targetRefBase, TEST_GROUP_NAME_FULL, PermissionService.CONSUMER, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Trigger the job so the target folder structure has a different base ACL
 | 
			
		||||
            triggerFixedACLJob();
 | 
			
		||||
            assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
 | 
			
		||||
            NodeRef targetRef = nodeDAO.getNodePair(getChild(nodeDAO.getNodePair(targetRefBase).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Get ACLS for later comparison
 | 
			
		||||
            ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRef, false, false);
 | 
			
		||||
@@ -487,7 +525,7 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            deleteNodes(folderRef);
 | 
			
		||||
            deleteNodes(targetRef);
 | 
			
		||||
            deleteNodes(targetRefBase);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -499,13 +537,26 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
    public void testAsyncWithNodeCopyParentToChildPendingFolder()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyOriginFolder");
 | 
			
		||||
        NodeRef targetRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyTargetFolder");
 | 
			
		||||
 | 
			
		||||
        // Get ACLS for later comparison
 | 
			
		||||
        ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
        NodeRef targetRefBase = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyTargetFolder");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRefBase, true, false);
 | 
			
		||||
                permissionService.setPermission(targetRefBase, TEST_GROUP_NAME_FULL, PermissionService.CONSUMER, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Trigger the job so the target folder structure has a different base ACL
 | 
			
		||||
            triggerFixedACLJob();
 | 
			
		||||
            assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
 | 
			
		||||
            NodeRef targetRef = nodeDAO.getNodePair(getChild(nodeDAO.getNodePair(targetRefBase).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Get ACLS for later comparison
 | 
			
		||||
            ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRef, false, false);
 | 
			
		||||
@@ -585,7 +636,151 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            deleteNodes(folderRef);
 | 
			
		||||
            deleteNodes(targetRef);
 | 
			
		||||
            deleteNodes(targetRefBase);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Move child of node that has the aspect to a child folder of a folder that also has the aspect applied before job
 | 
			
		||||
     * runs
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAsyncWithNodeMoveChildToChildPendingFolder()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveChildToChildPendingFolderOrigin");
 | 
			
		||||
        NodeRef targetRefBase = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveChildToChildPendingFolderTarget");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRefBase, true, false);
 | 
			
		||||
                permissionService.setPermission(targetRefBase, TEST_GROUP_NAME_FULL, PermissionService.CONSUMER, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Trigger the job so the target folder structure has a different base ACL
 | 
			
		||||
            triggerFixedACLJob();
 | 
			
		||||
            assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
 | 
			
		||||
            NodeRef targetRef = nodeDAO.getNodePair(getChild(nodeDAO.getNodePair(targetRefBase).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Set permissions on a child to get a new shared ACL with pending acl nodes
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRef, true, false);
 | 
			
		||||
                permissionService.setPermission(targetRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Get target Folder with a pending ACL
 | 
			
		||||
            NodeRef targetFolderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, targetRef);
 | 
			
		||||
            assertNotNull("No children folders were found with pendingFixACl aspect", targetFolderWithPendingAcl);
 | 
			
		||||
            NodeRef targetFolderWithPendingAclChild = nodeDAO
 | 
			
		||||
                    .getNodePair(getChild(nodeDAO.getNodePair(targetFolderWithPendingAcl).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Get ACLS for later comparison
 | 
			
		||||
            ACLComparator aclComparatorTarget = new ACLComparator(targetFolderWithPendingAcl);
 | 
			
		||||
            aclComparatorTarget.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
 | 
			
		||||
 | 
			
		||||
            // Set permissions on origin folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(folderRef, true, false);
 | 
			
		||||
                permissionService.setPermission(folderRef, TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Find a pending ACL folder
 | 
			
		||||
            NodeRef originFolderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, folderRef);
 | 
			
		||||
            assertNotNull("No children folders were found with pendingFixACl aspect", originFolderWithPendingAcl);
 | 
			
		||||
            NodeRef originFolderWithPendingAclChild = nodeDAO
 | 
			
		||||
                    .getNodePair(getChild(nodeDAO.getNodePair(originFolderWithPendingAcl).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Get ACLS for later comparison
 | 
			
		||||
            ACLComparator aclComparatorMovedNode = new ACLComparator(originFolderWithPendingAclChild);
 | 
			
		||||
            aclComparatorMovedNode.setOriginalPermission(TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION);
 | 
			
		||||
 | 
			
		||||
            // Move one pending folder into the other
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                fileFolderService.move(originFolderWithPendingAclChild, targetFolderWithPendingAclChild, "movedFolder");
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Trigger job
 | 
			
		||||
            triggerFixedACLJob();
 | 
			
		||||
 | 
			
		||||
            assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
            assertTrue("Moved node did not inherit permissions from target",
 | 
			
		||||
                    aclComparatorMovedNode.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
 | 
			
		||||
            assertTrue("Child of Pending Moved node did not inherit permissions from target",
 | 
			
		||||
                    aclComparatorMovedNode.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
 | 
			
		||||
            assertFalse("Moved node kept original permissions", aclComparatorMovedNode.parentHasOriginalPermission());
 | 
			
		||||
            assertFalse("Child of Moved node kept original permissions",
 | 
			
		||||
                    aclComparatorMovedNode.firstChildHasOriginalPermission());
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            deleteNodes(folderRef);
 | 
			
		||||
            deleteNodes(targetRefBase);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Create a conflicting ACL on a node and then try to run the job normally, without forcing the ACL to get the
 | 
			
		||||
     * expected error and then run it again with the forcedShareACL property as true so it can override the problematic
 | 
			
		||||
     * ACL
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAsyncWithErrorsForceSharedACL()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithErrorsForceSharedACL");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // Set permissions on origin folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(folderRef, true, false);
 | 
			
		||||
                permissionService.setPermission(folderRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Find a pending ACL folder
 | 
			
		||||
            NodeRef originFolderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, folderRef);
 | 
			
		||||
            assertNotNull("No children folders were found with pendingFixACl aspect", originFolderWithPendingAcl);
 | 
			
		||||
            NodeRef originFolderWithPendingAclChild = nodeDAO
 | 
			
		||||
                    .getNodePair(getChild(nodeDAO.getNodePair(originFolderWithPendingAcl).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Create a new ACL elsewhere and put the shared ACL (from a child) on the pending node child to simulate
 | 
			
		||||
            // conflict
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                NodeRef tempNode = createFile(fileFolderService, folderRef, "testAsyncWithErrorsForceSharedACLTemp",
 | 
			
		||||
                        ContentModel.TYPE_FOLDER);
 | 
			
		||||
                permissionService.setInheritParentPermissions(tempNode, false, false);
 | 
			
		||||
                permissionService.setPermission(tempNode, TEST_GROUP_NAME_FULL, PermissionService.CONSUMER, true);
 | 
			
		||||
                NodeRef tempNodeChild = createFile(fileFolderService, tempNode, "testAsyncWithErrorsForceSharedACLTempChild",
 | 
			
		||||
                        ContentModel.TYPE_FOLDER);
 | 
			
		||||
                setACL(permissionsDaoComponent, originFolderWithPendingAclChild,
 | 
			
		||||
                        nodeDAO.getNodeAclId(nodeDAO.getNodePair(tempNodeChild).getFirst()));
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            ACLComparator aclComparator = new ACLComparator(originFolderWithPendingAclChild);
 | 
			
		||||
 | 
			
		||||
            // Trigger job without forcing the shared ACL, only 1 error is expected
 | 
			
		||||
            triggerFixedACLJob(false);
 | 
			
		||||
            assertEquals("Unexpected number of errors", 1, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
 | 
			
		||||
            // Trigger job forcing the shared ACL
 | 
			
		||||
            triggerFixedACLJob(true);
 | 
			
		||||
 | 
			
		||||
            assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
            assertTrue("Child of node with conflict does not have correct permissions",
 | 
			
		||||
                    aclComparator.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
 | 
			
		||||
            assertTrue("Node with conflict does not have correct permissions",
 | 
			
		||||
                    aclComparator.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            deleteNodes(folderRef);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -596,14 +791,26 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
    public void testAsyncWithNodeMove()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveOriginFolder");
 | 
			
		||||
        NodeRef targetRef = createFile(fileFolderService, homeFolderNodeRef, "testAsyncWithNodeMoveTargetFolder",
 | 
			
		||||
                ContentModel.TYPE_FOLDER);
 | 
			
		||||
 | 
			
		||||
        // Get ACLS for later comparison
 | 
			
		||||
        ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
        NodeRef targetRefBase = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveTargetFolder");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRefBase, true, false);
 | 
			
		||||
                permissionService.setPermission(targetRefBase, TEST_GROUP_NAME_FULL, PermissionService.CONSUMER, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Trigger the job so the target folder structure has a different base ACL
 | 
			
		||||
            triggerFixedACLJob();
 | 
			
		||||
            assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
 | 
			
		||||
            NodeRef targetRef = nodeDAO.getNodePair(getChild(nodeDAO.getNodePair(targetRefBase).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Get ACLS for later comparison
 | 
			
		||||
            ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRef, false, false);
 | 
			
		||||
@@ -649,7 +856,7 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            deleteNodes(folderRef);
 | 
			
		||||
            deleteNodes(targetRef);
 | 
			
		||||
            deleteNodes(targetRefBase);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -660,13 +867,27 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
    public void testAsyncWithNodeMoveToPendingFolder()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveOriginFolder");
 | 
			
		||||
        NodeRef targetRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveTargetFolder");
 | 
			
		||||
 | 
			
		||||
        // Get ACLS for later comparison
 | 
			
		||||
        ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
        NodeRef targetRefBase = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveTargetFolder");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRefBase, true, false);
 | 
			
		||||
                permissionService.setPermission(targetRefBase, TEST_GROUP_NAME_FULL, PermissionService.CONSUMER, true);
 | 
			
		||||
                return null;
 | 
			
		||||
            }, false, true);
 | 
			
		||||
 | 
			
		||||
            // Trigger the job so the target folder structure has a different base ACL
 | 
			
		||||
            triggerFixedACLJob();
 | 
			
		||||
            assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
 | 
			
		||||
 | 
			
		||||
            NodeRef targetRef = nodeDAO.getNodePair(getChild(nodeDAO.getNodePair(targetRefBase).getFirst())).getSecond();
 | 
			
		||||
 | 
			
		||||
            // Get ACLS for later comparison
 | 
			
		||||
            ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
 | 
			
		||||
 | 
			
		||||
            // Set permissions on target folder
 | 
			
		||||
            txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                permissionService.setInheritParentPermissions(targetRef, false, false);
 | 
			
		||||
@@ -723,7 +944,7 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            deleteNodes(folderRef);
 | 
			
		||||
            deleteNodes(targetRef);
 | 
			
		||||
            deleteNodes(targetRefBase);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1250,6 +1471,19 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void setACL(PermissionsDaoComponent permissionsDaoComponent, NodeRef nodeRef, long aclId)
 | 
			
		||||
    {
 | 
			
		||||
        if (permissionsDaoComponent instanceof ADMPermissionsDaoComponentImpl)
 | 
			
		||||
        {
 | 
			
		||||
            AccessControlListDAO acldao = ((ADMPermissionsDaoComponentImpl) permissionsDaoComponent).getACLDAO(nodeRef);
 | 
			
		||||
            if (acldao instanceof ADMAccessControlListDAO)
 | 
			
		||||
            {
 | 
			
		||||
                ADMAccessControlListDAO admAcLDao = (ADMAccessControlListDAO) acldao;
 | 
			
		||||
                admAcLDao.setAccessControlList(nodeRef, aclId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private NodeRef createFolderHierarchyInRoot(String folderName, int[] filesPerLevel)
 | 
			
		||||
    {
 | 
			
		||||
        return txnHelper.doInTransaction((RetryingTransactionCallback<NodeRef>) () -> {
 | 
			
		||||
@@ -1318,6 +1552,11 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void triggerFixedACLJob()
 | 
			
		||||
    {
 | 
			
		||||
        triggerFixedACLJob(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void triggerFixedACLJob(boolean forceSharedACL)
 | 
			
		||||
    {
 | 
			
		||||
        // run the fixedAclUpdater until there is nothing more to fix (running the updater may create more to fix up) or
 | 
			
		||||
        // the count doesn't change for 3 cycles, meaning we have a problem.
 | 
			
		||||
@@ -1325,6 +1564,7 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
            int count = 0;
 | 
			
		||||
            int previousCount = 0;
 | 
			
		||||
            int rounds = 0;
 | 
			
		||||
            fixedAclUpdater.setForceSharedACL(forceSharedACL);
 | 
			
		||||
            do
 | 
			
		||||
            {
 | 
			
		||||
                previousCount = count;
 | 
			
		||||
@@ -1356,8 +1596,13 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
                        isDescendent = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (isDescendent && nodeDAO.getNodeType(nodeDAO.getNodePair(nodeRef).getFirst()).equals(nodeType))
 | 
			
		||||
                {
 | 
			
		||||
                    // If folder, the tests will need a child and a grandchild to verify permissions
 | 
			
		||||
                    if (nodeType.equals(ContentModel.TYPE_FOLDER) && !hasGrandChilden(nodeRef)) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    return nodeRef;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -1377,6 +1622,10 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
                NodeRef nodeRef = nodesWithAclPendingAspect.get(i);
 | 
			
		||||
                if (nodeDAO.getNodeType(nodeDAO.getNodePair(nodeRef).getFirst()).equals(nodeType))
 | 
			
		||||
                {
 | 
			
		||||
                    // If folder, the tests will need a child and a grandchild to verify permissions
 | 
			
		||||
                    if (nodeType.equals(ContentModel.TYPE_FOLDER) && !hasGrandChilden(nodeRef)) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    return nodeRef;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -1385,6 +1634,18 @@ public class FixedAclUpdaterTest extends TestCase
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean hasGrandChilden(NodeRef nodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst();
 | 
			
		||||
        Long childId = getChild(nodeId);
 | 
			
		||||
        Long grandChild = null;
 | 
			
		||||
        if (childId != null)
 | 
			
		||||
        {
 | 
			
		||||
            grandChild = getChild(childId);
 | 
			
		||||
        }
 | 
			
		||||
        return (grandChild != null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void deleteNodes(NodeRef folder)
 | 
			
		||||
    {
 | 
			
		||||
        txnHelper.doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
 
 | 
			
		||||
@@ -35,10 +35,14 @@ import java.util.Properties;
 | 
			
		||||
 | 
			
		||||
import javax.sql.DataSource;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.domain.dialect.Dialect;
 | 
			
		||||
import org.alfresco.repo.domain.dialect.MySQLInnoDBDialect;
 | 
			
		||||
import org.alfresco.util.ApplicationContextHelper;
 | 
			
		||||
import org.alfresco.util.testing.category.DBTests;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.BeforeClass;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.experimental.categories.Category;
 | 
			
		||||
import org.mockito.Mockito;
 | 
			
		||||
import org.springframework.context.ApplicationContext;
 | 
			
		||||
import org.springframework.jdbc.core.JdbcTemplate;
 | 
			
		||||
@@ -48,11 +52,13 @@ import org.springframework.jdbc.core.JdbcTemplate;
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Cristian Turlica
 | 
			
		||||
 */
 | 
			
		||||
@Category({DBTests.class})
 | 
			
		||||
public class DeleteNotExistsExecutorTest
 | 
			
		||||
{
 | 
			
		||||
    private static ApplicationContext ctx;
 | 
			
		||||
    private ScriptExecutor scriptExecutor;
 | 
			
		||||
    private DataSource dataSource;
 | 
			
		||||
    private Dialect dialect;
 | 
			
		||||
    private JdbcTemplate jdbcTmpl;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass
 | 
			
		||||
@@ -67,9 +73,20 @@ public class DeleteNotExistsExecutorTest
 | 
			
		||||
    {
 | 
			
		||||
        scriptExecutor = ctx.getBean("simpleScriptExecutor", ScriptExecutorImpl.class);
 | 
			
		||||
        dataSource = ctx.getBean("dataSource", DataSource.class);
 | 
			
		||||
        dialect = ctx.getBean("dialect", Dialect.class);
 | 
			
		||||
        jdbcTmpl = new JdbcTemplate(dataSource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private DeleteNotExistsExecutor createDeleteNotExistsExecutor(Connection connection, String sql, int line, File scriptFile, Properties properties)
 | 
			
		||||
    {
 | 
			
		||||
        if (dialect instanceof MySQLInnoDBDialect)
 | 
			
		||||
        {
 | 
			
		||||
            return new MySQLDeleteNotExistsExecutor(connection, sql, line, scriptFile, properties, dataSource);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new DeleteNotExistsExecutor(connection, sql, line, scriptFile, properties);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test()
 | 
			
		||||
    public void testDefaultBehaviour() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
@@ -90,7 +107,7 @@ public class DeleteNotExistsExecutorTest
 | 
			
		||||
            {
 | 
			
		||||
                when(properties.getProperty(DeleteNotExistsExecutor.PROPERTY_READ_ONLY)).thenReturn("true");
 | 
			
		||||
                when(properties.getProperty(DeleteNotExistsExecutor.PROPERTY_TIMEOUT_SECONDS)).thenReturn("-1");
 | 
			
		||||
                DeleteNotExistsExecutor deleteNotExistsExecutor = new DeleteNotExistsExecutor(connection, sql, line, scriptFile, properties);
 | 
			
		||||
                DeleteNotExistsExecutor deleteNotExistsExecutor = createDeleteNotExistsExecutor(connection, sql, line, scriptFile, properties);
 | 
			
		||||
                deleteNotExistsExecutor.execute();
 | 
			
		||||
 | 
			
		||||
                List<String> res = jdbcTmpl.queryForList(select, String.class);
 | 
			
		||||
@@ -100,7 +117,7 @@ public class DeleteNotExistsExecutorTest
 | 
			
		||||
            {
 | 
			
		||||
                when(properties.getProperty(DeleteNotExistsExecutor.PROPERTY_READ_ONLY)).thenReturn("false");
 | 
			
		||||
                when(properties.getProperty(DeleteNotExistsExecutor.PROPERTY_TIMEOUT_SECONDS)).thenReturn("-1");
 | 
			
		||||
                DeleteNotExistsExecutor deleteNotExistsExecutor = new DeleteNotExistsExecutor(connection, sql, line, scriptFile, properties);
 | 
			
		||||
                DeleteNotExistsExecutor deleteNotExistsExecutor = createDeleteNotExistsExecutor(connection, sql, line, scriptFile, properties);
 | 
			
		||||
                deleteNotExistsExecutor.execute();
 | 
			
		||||
 | 
			
		||||
                List<String> res = jdbcTmpl.queryForList(select, String.class);
 | 
			
		||||
@@ -133,7 +150,7 @@ public class DeleteNotExistsExecutorTest
 | 
			
		||||
            {
 | 
			
		||||
                when(properties.getProperty(DeleteNotExistsExecutor.PROPERTY_DELETE_BATCH_SIZE)).thenReturn("1");
 | 
			
		||||
                when(properties.getProperty(DeleteNotExistsExecutor.PROPERTY_READ_ONLY)).thenReturn("false");
 | 
			
		||||
                DeleteNotExistsExecutor deleteNotExistsExecutor = new DeleteNotExistsExecutor(connection, sql, line, scriptFile, properties);
 | 
			
		||||
                DeleteNotExistsExecutor deleteNotExistsExecutor = createDeleteNotExistsExecutor(connection, sql, line, scriptFile, properties);
 | 
			
		||||
                deleteNotExistsExecutor.execute();
 | 
			
		||||
 | 
			
		||||
                List<String> res = jdbcTmpl.queryForList(select, String.class);
 | 
			
		||||
@@ -167,7 +184,7 @@ public class DeleteNotExistsExecutorTest
 | 
			
		||||
                when(properties.getProperty(DeleteNotExistsExecutor.PROPERTY_BATCH_SIZE)).thenReturn("2");
 | 
			
		||||
                when(properties.getProperty(DeleteNotExistsExecutor.PROPERTY_READ_ONLY)).thenReturn("false");
 | 
			
		||||
                when(properties.getProperty(DeleteNotExistsExecutor.PROPERTY_TIMEOUT_SECONDS)).thenReturn("-1");
 | 
			
		||||
                DeleteNotExistsExecutor deleteNotExistsExecutor = new DeleteNotExistsExecutor(connection, sql, line, scriptFile, properties);
 | 
			
		||||
                DeleteNotExistsExecutor deleteNotExistsExecutor = createDeleteNotExistsExecutor(connection, sql, line, scriptFile, properties);
 | 
			
		||||
                deleteNotExistsExecutor.execute();
 | 
			
		||||
 | 
			
		||||
                List<String> res = jdbcTmpl.queryForList(select, String.class);
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
 | 
			
		||||
import static org.awaitility.Awaitility.await;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import javax.jms.ConnectionFactory;
 | 
			
		||||
@@ -77,17 +78,19 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
 | 
			
		||||
public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
 | 
			
		||||
{
 | 
			
		||||
    protected static final boolean            DEBUG = false;
 | 
			
		||||
 | 
			
		||||
    protected static final String             TEST_NAMESPACE  = "http://www.alfresco.org/test/ContextAwareRepoEvent";
 | 
			
		||||
    protected static final RepoEventContainer EVENT_CONTAINER = new RepoEventContainer();
 | 
			
		||||
 | 
			
		||||
    private static final   String             BROKER_URL      = "tcp://localhost:61616";
 | 
			
		||||
    private static final   String             TOPIC_NAME      = "alfresco.repo.event2";
 | 
			
		||||
    private static final   String             CAMEL_ROUTE     = "jms:topic:" + TOPIC_NAME;
 | 
			
		||||
    private static final   RepoEventContainer EVENT_CONTAINER = new RepoEventContainer();
 | 
			
		||||
    private static final   CamelContext       CAMEL_CONTEXT   = new DefaultCamelContext();
 | 
			
		||||
 | 
			
		||||
    private static boolean isCamelConfigured;
 | 
			
		||||
    private static DataFormat dataFormat;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    @Autowired
 | 
			
		||||
    protected RetryingTransactionHelper retryingTransactionHelper;
 | 
			
		||||
 | 
			
		||||
@@ -104,6 +107,13 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
 | 
			
		||||
    @Autowired
 | 
			
		||||
    protected ObjectMapper event2ObjectMapper;
 | 
			
		||||
 | 
			
		||||
    @Autowired @Qualifier("eventGeneratorV2")
 | 
			
		||||
    protected EventGenerator eventGenerator;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    @Qualifier("eventGeneratorQueue")
 | 
			
		||||
    protected EventGeneratorQueue eventQueue;
 | 
			
		||||
 | 
			
		||||
    protected NodeRef rootNodeRef;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass
 | 
			
		||||
@@ -141,8 +151,35 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
 | 
			
		||||
            }
 | 
			
		||||
            return nodeService.getRootNode(storeRef);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        flushSpuriousEvents();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * When running with an empty database some events related to the creation may
 | 
			
		||||
     * creep up here making the test fails. After attempting several other
 | 
			
		||||
     * strategies, a smart sleep seems to do the work.
 | 
			
		||||
     */
 | 
			
		||||
    protected void flushSpuriousEvents() throws InterruptedException
 | 
			
		||||
    {
 | 
			
		||||
        int maxloops = 5;
 | 
			
		||||
        
 | 
			
		||||
        int count = maxloops;
 | 
			
		||||
        do
 | 
			
		||||
        {
 | 
			
		||||
            Thread.sleep(165l);
 | 
			
		||||
            if (EVENT_CONTAINER.isEmpty())
 | 
			
		||||
            {
 | 
			
		||||
                count--;
 | 
			
		||||
            } else 
 | 
			
		||||
            {
 | 
			
		||||
                EVENT_CONTAINER.reset();
 | 
			
		||||
                count = maxloops;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } while (count > 0);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @After
 | 
			
		||||
    public void tearDown()
 | 
			
		||||
    {
 | 
			
		||||
@@ -179,6 +216,16 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
 | 
			
		||||
            propertyMap).getChildRef());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected NodeRef updateNodeName(NodeRef nodeRef, String newName)
 | 
			
		||||
    {
 | 
			
		||||
        PropertyMap propertyMap = new PropertyMap();
 | 
			
		||||
        propertyMap.put(ContentModel.PROP_NAME, newName);
 | 
			
		||||
        return retryingTransactionHelper.doInTransaction(() -> {
 | 
			
		||||
            nodeService.addProperties(nodeRef, propertyMap);
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void deleteNode(NodeRef nodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        retryingTransactionHelper.doInTransaction(() -> {
 | 
			
		||||
@@ -376,13 +423,18 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
 | 
			
		||||
 | 
			
		||||
    public static class RepoEventContainer implements Processor
 | 
			
		||||
    {
 | 
			
		||||
        private final List<RepoEvent<?>> events = new ArrayList<>();
 | 
			
		||||
        private final List<RepoEvent<?>> events = Collections.synchronizedList(new ArrayList<>());
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void process(Exchange exchange)
 | 
			
		||||
        {
 | 
			
		||||
            Object object = exchange.getIn().getBody();
 | 
			
		||||
            events.add((RepoEvent<?>) object);
 | 
			
		||||
 | 
			
		||||
            if (DEBUG)
 | 
			
		||||
            {
 | 
			
		||||
                System.err.println("XX: "+object);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<RepoEvent<?>> getEvents()
 | 
			
		||||
@@ -404,6 +456,12 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
 | 
			
		||||
        {
 | 
			
		||||
            events.clear();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public boolean isEmpty()
 | 
			
		||||
        {
 | 
			
		||||
            return events.isEmpty();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,290 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.event2;
 | 
			
		||||
 | 
			
		||||
import static java.lang.Thread.sleep;
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.mockito.ArgumentMatchers.any;
 | 
			
		||||
import static org.mockito.Mockito.doThrow;
 | 
			
		||||
import static org.mockito.Mockito.mock;
 | 
			
		||||
import static org.mockito.Mockito.when;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.Callable;
 | 
			
		||||
import java.util.concurrent.CopyOnWriteArrayList;
 | 
			
		||||
import java.util.concurrent.Executor;
 | 
			
		||||
import java.util.concurrent.ExecutorService;
 | 
			
		||||
import java.util.concurrent.SynchronousQueue;
 | 
			
		||||
import java.util.concurrent.ThreadPoolExecutor;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.event.v1.model.RepoEvent;
 | 
			
		||||
import org.junit.After;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.mockito.Mockito;
 | 
			
		||||
import org.mockito.invocation.InvocationOnMock;
 | 
			
		||||
import org.mockito.stubbing.Answer;
 | 
			
		||||
 | 
			
		||||
public class EventGeneratorQueueUnitTest
 | 
			
		||||
{
 | 
			
		||||
    private EventGeneratorQueue queue;
 | 
			
		||||
 | 
			
		||||
    private Event2MessageProducer bus;
 | 
			
		||||
    private ExecutorService enqueuePool;
 | 
			
		||||
    private ExecutorService dequeuePool;
 | 
			
		||||
    private List<RepoEvent<?>> recordedEvents;
 | 
			
		||||
    private Map<String, RepoEvent<?>> events;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setup() 
 | 
			
		||||
    {
 | 
			
		||||
        queue = new EventGeneratorQueue();
 | 
			
		||||
 | 
			
		||||
        enqueuePool = newThreadPool();
 | 
			
		||||
        queue.setEnqueueThreadPoolExecutor(enqueuePool);
 | 
			
		||||
        dequeuePool = newThreadPool();
 | 
			
		||||
        queue.setDequeueThreadPoolExecutor(dequeuePool);
 | 
			
		||||
 | 
			
		||||
        bus = mock(Event2MessageProducer.class);
 | 
			
		||||
        queue.setEvent2MessageProducer(bus);
 | 
			
		||||
 | 
			
		||||
        events = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
        setupEventsRecorder();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @After
 | 
			
		||||
    public void teardown() 
 | 
			
		||||
    {
 | 
			
		||||
        enqueuePool.shutdown();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setupEventsRecorder()
 | 
			
		||||
    {
 | 
			
		||||
        recordedEvents = new CopyOnWriteArrayList<>();
 | 
			
		||||
 | 
			
		||||
        Mockito.doAnswer(new Answer<Void>()
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public Void answer(InvocationOnMock invocation) throws Throwable
 | 
			
		||||
            {
 | 
			
		||||
                RepoEvent<?> event = invocation.getArgument(0, RepoEvent.class);
 | 
			
		||||
                recordedEvents.add(event);
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        }).when(bus).send(any());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldReceiveSingleQuickMessage() throws Exception 
 | 
			
		||||
    {
 | 
			
		||||
        queue.accept(messageWithDelay("A", 55l));
 | 
			
		||||
 | 
			
		||||
        sleep(150l);
 | 
			
		||||
 | 
			
		||||
        assertEquals(1, recordedEvents.size());
 | 
			
		||||
        assertEquals("A", recordedEvents.get(0).getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldNotReceiveEventsWhenMessageIsNull() throws Exception 
 | 
			
		||||
    {
 | 
			
		||||
        queue.accept(() -> { return null; });
 | 
			
		||||
 | 
			
		||||
        sleep(150l);
 | 
			
		||||
 | 
			
		||||
        assertEquals(0, recordedEvents.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldReceiveMultipleMessagesPreservingOrderScenarioOne() throws Exception {
 | 
			
		||||
        queue.accept(messageWithDelay("A", 0l));
 | 
			
		||||
        queue.accept(messageWithDelay("B", 100l));
 | 
			
		||||
        queue.accept(messageWithDelay("C", 200l));
 | 
			
		||||
 | 
			
		||||
        sleep(450l);
 | 
			
		||||
 | 
			
		||||
        assertEquals(3, recordedEvents.size());
 | 
			
		||||
        assertEquals("A", recordedEvents.get(0).getId());
 | 
			
		||||
        assertEquals("B", recordedEvents.get(1).getId());
 | 
			
		||||
        assertEquals("C", recordedEvents.get(2).getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldReceiveMultipleMessagesPreservingOrderScenarioTwo() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        queue.accept(messageWithDelay("A", 300l));
 | 
			
		||||
        queue.accept(messageWithDelay("B", 150l));
 | 
			
		||||
        queue.accept(messageWithDelay("C", 0l));
 | 
			
		||||
 | 
			
		||||
        sleep(950l);
 | 
			
		||||
 | 
			
		||||
        assertEquals(3, recordedEvents.size());
 | 
			
		||||
        assertEquals("A", recordedEvents.get(0).getId());
 | 
			
		||||
        assertEquals("B", recordedEvents.get(1).getId());
 | 
			
		||||
        assertEquals("C", recordedEvents.get(2).getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldReceiveMultipleMessagesPreservingOrderEvenWhenMakerPoisoned() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        queue.accept(messageWithDelay("A", 300l));
 | 
			
		||||
        queue.accept(() -> {throw new RuntimeException("Boom! (not to worry, this is a test)");});
 | 
			
		||||
        queue.accept(messageWithDelay("B", 55l));
 | 
			
		||||
        queue.accept(messageWithDelay("C", 0l));
 | 
			
		||||
 | 
			
		||||
        sleep(950l);
 | 
			
		||||
 | 
			
		||||
        assertEquals(3, recordedEvents.size());
 | 
			
		||||
        assertEquals("A", recordedEvents.get(0).getId());
 | 
			
		||||
        assertEquals("B", recordedEvents.get(1).getId());
 | 
			
		||||
        assertEquals("C", recordedEvents.get(2).getId());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldReceiveMultipleMessagesPreservingOrderEvenWhenSenderPoisoned() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        Callable<RepoEvent<?>> makerB = messageWithDelay("B", 55l);
 | 
			
		||||
        RepoEvent<?> messageB = makerB.call();
 | 
			
		||||
        doThrow(new RuntimeException("Boom! (not to worry, this is a test)")).when(bus).send(messageB);
 | 
			
		||||
        queue.accept(messageWithDelay("A", 300l));
 | 
			
		||||
        queue.accept(makerB);
 | 
			
		||||
        queue.accept(messageWithDelay("C", 0l));
 | 
			
		||||
 | 
			
		||||
        sleep(950l);
 | 
			
		||||
 | 
			
		||||
        assertEquals(2, recordedEvents.size());
 | 
			
		||||
        assertEquals("A", recordedEvents.get(0).getId());
 | 
			
		||||
        assertEquals("C", recordedEvents.get(1).getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldReceiveMultipleMessagesPreservingOrderEvenWhenMakerPoisonedWithError() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        queue.accept(messageWithDelay("A", 300l));
 | 
			
		||||
        queue.accept(() -> {throw new OutOfMemoryError("Boom! (not to worry, this is a test)");});
 | 
			
		||||
        queue.accept(messageWithDelay("B", 55l));
 | 
			
		||||
        queue.accept(messageWithDelay("C", 0l));
 | 
			
		||||
 | 
			
		||||
        sleep(950l);
 | 
			
		||||
 | 
			
		||||
        assertEquals(3, recordedEvents.size());
 | 
			
		||||
        assertEquals("A", recordedEvents.get(0).getId());
 | 
			
		||||
        assertEquals("B", recordedEvents.get(1).getId());
 | 
			
		||||
        assertEquals("C", recordedEvents.get(2).getId());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldReceiveMultipleMessagesPreservingOrderEvenWhenSenderPoisonedWithError() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        Callable<RepoEvent<?>> makerB = messageWithDelay("B", 55l);
 | 
			
		||||
        RepoEvent<?> messageB = makerB.call();
 | 
			
		||||
        doThrow(new OutOfMemoryError("Boom! (not to worry, this is a test)")).when(bus).send(messageB);
 | 
			
		||||
        queue.accept(messageWithDelay("A", 300l));
 | 
			
		||||
        queue.accept(makerB);
 | 
			
		||||
        queue.accept(messageWithDelay("C", 0l));
 | 
			
		||||
 | 
			
		||||
        sleep(950l);
 | 
			
		||||
 | 
			
		||||
        assertEquals(2, recordedEvents.size());
 | 
			
		||||
        assertEquals("A", recordedEvents.get(0).getId());
 | 
			
		||||
        assertEquals("C", recordedEvents.get(1).getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Callable<RepoEvent<?>> messageWithDelay(String id, long delay)
 | 
			
		||||
    {
 | 
			
		||||
        Callable<RepoEvent<?>> res = new Callable<RepoEvent<?>>() {
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public RepoEvent<?> call() throws Exception
 | 
			
		||||
            {
 | 
			
		||||
                if(delay != 0)
 | 
			
		||||
                {
 | 
			
		||||
                    sleep(delay); 
 | 
			
		||||
                }
 | 
			
		||||
                return newRepoEvent(id);
 | 
			
		||||
            } 
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public String toString()
 | 
			
		||||
            {
 | 
			
		||||
                return id;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        return res;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private RepoEvent<?> newRepoEvent(String id)
 | 
			
		||||
    {
 | 
			
		||||
        RepoEvent<?> ev = events.get(id);
 | 
			
		||||
        if (ev!=null)
 | 
			
		||||
            return ev;
 | 
			
		||||
        
 | 
			
		||||
        ev = mock(RepoEvent.class);
 | 
			
		||||
        when(ev.getId()).thenReturn(id);
 | 
			
		||||
        when(ev.toString()).thenReturn(id);
 | 
			
		||||
        events.put(id, ev);
 | 
			
		||||
 | 
			
		||||
        return ev;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ExecutorService newThreadPool() 
 | 
			
		||||
    {
 | 
			
		||||
        return new ThreadPoolExecutor(2, Integer.MAX_VALUE,
 | 
			
		||||
                                      60L, TimeUnit.SECONDS,
 | 
			
		||||
                                      new SynchronousQueue<Runnable>());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static final Executor SYNC_EXECUTOR_SAME_THREAD = new Executor()
 | 
			
		||||
    {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void execute(Runnable command)
 | 
			
		||||
        {
 | 
			
		||||
            command.run();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    public static final Executor SYNC_EXECUTOR_NEW_THREAD = new Executor()
 | 
			
		||||
    {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void execute(Runnable command)
 | 
			
		||||
        {
 | 
			
		||||
            Thread t = new Thread(command);
 | 
			
		||||
            t.start();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                t.join();
 | 
			
		||||
            }
 | 
			
		||||
            catch (InterruptedException e)
 | 
			
		||||
            {
 | 
			
		||||
                Thread.currentThread().interrupt();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,249 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2020 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.event2;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.LinkedList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import javax.jms.Destination;
 | 
			
		||||
import javax.jms.JMSException;
 | 
			
		||||
import javax.jms.Message;
 | 
			
		||||
import javax.jms.MessageConsumer;
 | 
			
		||||
import javax.jms.MessageListener;
 | 
			
		||||
import javax.jms.Session;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.repo.event.databind.ObjectMapperFactory;
 | 
			
		||||
import org.alfresco.repo.event.v1.model.RepoEvent;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.apache.activemq.ActiveMQConnection;
 | 
			
		||||
import org.apache.activemq.ActiveMQConnectionFactory;
 | 
			
		||||
import org.apache.activemq.advisory.DestinationSource;
 | 
			
		||||
import org.apache.activemq.command.ActiveMQQueue;
 | 
			
		||||
import org.apache.activemq.command.ActiveMQTextMessage;
 | 
			
		||||
import org.apache.activemq.command.ActiveMQTopic;
 | 
			
		||||
import org.awaitility.Awaitility;
 | 
			
		||||
import org.junit.After;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Qualifier;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
 | 
			
		||||
public class EventGeneratorTest extends AbstractContextAwareRepoEvent
 | 
			
		||||
{
 | 
			
		||||
    private static final String EVENT2_TOPIC_NAME = "alfresco.repo.event2";
 | 
			
		||||
 | 
			
		||||
    private static final long DUMP_BROKER_TIMEOUT = 50000000l;
 | 
			
		||||
 | 
			
		||||
    @Autowired @Qualifier("event2ObjectMapper")
 | 
			
		||||
    private ObjectMapper objectMapper;
 | 
			
		||||
 | 
			
		||||
    private ActiveMQConnection connection;
 | 
			
		||||
    protected List<RepoEvent<?>> receivedEvents;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void startupTopicListener() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
 | 
			
		||||
        connection = (ActiveMQConnection) connectionFactory.createConnection();
 | 
			
		||||
        connection.start();
 | 
			
		||||
 | 
			
		||||
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
 | 
			
		||||
        Destination destination = session.createTopic(EVENT2_TOPIC_NAME);
 | 
			
		||||
        MessageConsumer consumer = session.createConsumer(destination);
 | 
			
		||||
 | 
			
		||||
        receivedEvents = Collections.synchronizedList(new LinkedList<>());
 | 
			
		||||
        consumer.setMessageListener(new MessageListener()
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onMessage(Message message)
 | 
			
		||||
            {
 | 
			
		||||
                String text = getText(message);
 | 
			
		||||
                RepoEvent<?> event = toRepoEvent(text);
 | 
			
		||||
 | 
			
		||||
                if (DEBUG)
 | 
			
		||||
                {
 | 
			
		||||
                    System.err.println("RX: " + event);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                receivedEvents.add(event);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private RepoEvent<?> toRepoEvent(String json)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    return objectMapper.readValue(json, RepoEvent.class);
 | 
			
		||||
                } catch (Exception e)
 | 
			
		||||
                {
 | 
			
		||||
                    e.printStackTrace();
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (DEBUG)
 | 
			
		||||
        {
 | 
			
		||||
            System.err.println("Now actively listening on topic " + EVENT2_TOPIC_NAME);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected ObjectMapper createObjectMapper()
 | 
			
		||||
    {
 | 
			
		||||
        return ObjectMapperFactory.createInstance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @After
 | 
			
		||||
    public void shutdownTopicListener() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        connection.close();
 | 
			
		||||
        connection = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldReceiveEvent2EventsOnNodeCreation() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        createNode(ContentModel.TYPE_CONTENT);
 | 
			
		||||
 | 
			
		||||
        Awaitility.await().atMost(6, TimeUnit.SECONDS).until(() -> receivedEvents.size() == 1);
 | 
			
		||||
 | 
			
		||||
        RepoEvent<?> sent = getRepoEvent(1);
 | 
			
		||||
        RepoEvent<?> received = receivedEvents.get(0);
 | 
			
		||||
        assertEventsEquals("Events are different!", sent, received);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void assertEventsEquals(String message, RepoEvent<?> expected, RepoEvent<?> current)
 | 
			
		||||
    {
 | 
			
		||||
        if (DEBUG)
 | 
			
		||||
        {
 | 
			
		||||
            System.err.println("XP: " + expected);
 | 
			
		||||
            System.err.println("CU: " + current);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        assertEquals(message, expected, current);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldReceiveEvent2EventsInOrder() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT);
 | 
			
		||||
        updateNodeName(nodeRef, "TestFile-" + System.currentTimeMillis() + ".txt");
 | 
			
		||||
        deleteNode(nodeRef);
 | 
			
		||||
 | 
			
		||||
        Awaitility.await().atMost(6, TimeUnit.SECONDS).until(() -> receivedEvents.size() == 3);
 | 
			
		||||
 | 
			
		||||
        RepoEvent<?> sentCreation = getRepoEvent(1);
 | 
			
		||||
        RepoEvent<?> sentUpdate = getRepoEvent(2);
 | 
			
		||||
        RepoEvent<?> sentDeletion = getRepoEvent(3);
 | 
			
		||||
        assertEquals("Expected create event!", sentCreation, (RepoEvent<?>) receivedEvents.get(0));
 | 
			
		||||
        assertEquals("Expected update event!", sentUpdate, (RepoEvent<?>) receivedEvents.get(1));
 | 
			
		||||
        assertEquals("Expected delete event!", sentDeletion, (RepoEvent<?>) receivedEvents.get(2));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static String getText(Message message)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            ActiveMQTextMessage am = (ActiveMQTextMessage) message;
 | 
			
		||||
            return am.getText();
 | 
			
		||||
        } catch (JMSException e)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // a simple main to investigate the contents of the local broker
 | 
			
		||||
    public static void main(String[] args) throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        dumpBroker("tcp://localhost:61616", DUMP_BROKER_TIMEOUT);
 | 
			
		||||
        System.exit(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void dumpBroker(String url, long timeout) throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        System.out.println("Broker at url: '" + url + "'");
 | 
			
		||||
 | 
			
		||||
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
 | 
			
		||||
        ActiveMQConnection connection = (ActiveMQConnection) connectionFactory.createConnection();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            connection.start();
 | 
			
		||||
 | 
			
		||||
            DestinationSource ds = connection.getDestinationSource();
 | 
			
		||||
 | 
			
		||||
            Set<ActiveMQQueue> queues = ds.getQueues();
 | 
			
		||||
            System.out.println("\nFound " + queues.size() + " queues:");
 | 
			
		||||
            for (ActiveMQQueue queue : queues)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    System.out.println("- " + queue.getQueueName());
 | 
			
		||||
                } catch (JMSException e)
 | 
			
		||||
                {
 | 
			
		||||
                    e.printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Set<ActiveMQTopic> topics = ds.getTopics();
 | 
			
		||||
            System.out.println("\nFound " + topics.size() + " topics:");
 | 
			
		||||
            for (ActiveMQTopic topic : topics)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    System.out.println("- " + topic.getTopicName());
 | 
			
		||||
                } catch (JMSException e)
 | 
			
		||||
                {
 | 
			
		||||
                    e.printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
 | 
			
		||||
            Destination destination = session.createTopic(EVENT2_TOPIC_NAME);
 | 
			
		||||
            MessageConsumer consumer = session.createConsumer(destination);
 | 
			
		||||
 | 
			
		||||
            System.out.println("\nListening to topic " + EVENT2_TOPIC_NAME + "...");
 | 
			
		||||
            consumer.setMessageListener(new MessageListener()
 | 
			
		||||
            {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onMessage(Message message)
 | 
			
		||||
                {
 | 
			
		||||
                    String text = getText(message);
 | 
			
		||||
                    System.out.println("Received message " + message + "\n" + text + "\n");
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            Thread.sleep(timeout);
 | 
			
		||||
        } finally
 | 
			
		||||
        {
 | 
			
		||||
            connection.close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -34,7 +34,8 @@ import org.junit.runners.Suite.SuiteClasses;
 | 
			
		||||
                UpdateRepoEventIT.class,
 | 
			
		||||
                DeleteRepoEventIT.class,
 | 
			
		||||
                ChildAssociationRepoEventIT.class,
 | 
			
		||||
                PeerAssociationRepoEventIT.class
 | 
			
		||||
                PeerAssociationRepoEventIT.class,
 | 
			
		||||
                EventGeneratorTest.class
 | 
			
		||||
})
 | 
			
		||||
public class RepoEvent2ITSuite
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,8 @@ import org.junit.runners.Suite.SuiteClasses;
 | 
			
		||||
@RunWith(Suite.class)
 | 
			
		||||
@SuiteClasses({ EventFilterUnitTest.class,
 | 
			
		||||
                EventConsolidatorUnitTest.class,
 | 
			
		||||
                EventJSONSchemaUnitTest.class
 | 
			
		||||
                EventJSONSchemaUnitTest.class,
 | 
			
		||||
                EventGeneratorQueueUnitTest.class
 | 
			
		||||
})
 | 
			
		||||
public class RepoEvent2UnitSuite
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,30 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is 
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * 
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is 
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * 
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.rendition;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.rendition.executer.HTMLRenderingEngineTest;
 | 
			
		||||
import org.alfresco.repo.thumbnail.ThumbnailServiceImplParameterTest;
 | 
			
		||||
import org.alfresco.repo.thumbnail.ThumbnailServiceImplTest;
 | 
			
		||||
import org.alfresco.repo.thumbnail.conditions.NodeEligibleForRethumbnailingEvaluatorTest;
 | 
			
		||||
@@ -49,7 +48,6 @@ import org.junit.runners.Suite;
 | 
			
		||||
        RenditionServiceIntegrationTest.class,
 | 
			
		||||
        RenditionServicePermissionsTest.class,
 | 
			
		||||
        RenditionNodeManagerTest.class,
 | 
			
		||||
        HTMLRenderingEngineTest.class,
 | 
			
		||||
        MultiUserRenditionTest.class
 | 
			
		||||
})
 | 
			
		||||
public class AllRenditionTests
 | 
			
		||||
 
 | 
			
		||||
@@ -1,543 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is 
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * 
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * 
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.alfresco.repo.rendition.executer;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.repo.content.transform.AbstractContentTransformerTest;
 | 
			
		||||
import org.alfresco.repo.model.Repository;
 | 
			
		||||
import org.alfresco.repo.rendition.RenditionDefinitionPersisterImpl;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | 
			
		||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
 | 
			
		||||
import org.alfresco.service.cmr.rendition.RenditionDefinition;
 | 
			
		||||
import org.alfresco.service.cmr.rendition.RenditionService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ContentReader;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ContentService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ContentWriter;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeService;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.alfresco.test_category.BaseSpringTestsCategory;
 | 
			
		||||
import org.alfresco.test_category.OwnJVMTestsCategory;
 | 
			
		||||
import org.alfresco.util.BaseAlfrescoSpringTest;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.junit.After;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.experimental.categories.Category;
 | 
			
		||||
import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit tests for the HTML Rendering Engine
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Nick Burch
 | 
			
		||||
 *
 | 
			
		||||
 * @deprecated We are introducing the new async RenditionService2.
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
@Category(BaseSpringTestsCategory.class)
 | 
			
		||||
@Transactional
 | 
			
		||||
public class HTMLRenderingEngineTest extends BaseAlfrescoSpringTest
 | 
			
		||||
{
 | 
			
		||||
    private final static Log log = LogFactory.getLog(HTMLRenderingEngineTest.class);
 | 
			
		||||
    private NodeRef companyHome;
 | 
			
		||||
    private DictionaryService dictionaryService;
 | 
			
		||||
    private RenditionService renditionService;
 | 
			
		||||
    private Repository repositoryHelper;
 | 
			
		||||
    
 | 
			
		||||
    private NodeRef sourceDoc;
 | 
			
		||||
    private NodeRef targetFolder;
 | 
			
		||||
    private String targetFolderPath;
 | 
			
		||||
    
 | 
			
		||||
    private RenditionDefinition def;
 | 
			
		||||
    
 | 
			
		||||
    private static final String MIMETYPE_DOC = "application/msword";
 | 
			
		||||
    private static final String MIMETYPE_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void before() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        super.before();
 | 
			
		||||
        this.nodeService = (NodeService) this.applicationContext.getBean("NodeService");
 | 
			
		||||
        this.contentService = (ContentService) this.applicationContext.getBean("ContentService");
 | 
			
		||||
        this.renditionService = (RenditionService) this.applicationContext.getBean("RenditionService");
 | 
			
		||||
        this.repositoryHelper = (Repository) this.applicationContext.getBean("repositoryHelper");
 | 
			
		||||
        this.dictionaryService = (DictionaryService) this.applicationContext.getBean("dictionaryService");
 | 
			
		||||
        this.companyHome = repositoryHelper.getCompanyHome();
 | 
			
		||||
        
 | 
			
		||||
        createTargetFolder();
 | 
			
		||||
        
 | 
			
		||||
        // Setup the basic rendition definition
 | 
			
		||||
        QName renditionName = QName.createQName("Test");
 | 
			
		||||
        RenditionDefinition rd = renditionService.loadRenditionDefinition(renditionName); 
 | 
			
		||||
        if(rd != null)
 | 
			
		||||
        {
 | 
			
		||||
           RenditionDefinitionPersisterImpl rdp = new RenditionDefinitionPersisterImpl();
 | 
			
		||||
           rdp.setNodeService(nodeService);
 | 
			
		||||
           rdp.deleteRenditionDefinition(rd);
 | 
			
		||||
        }
 | 
			
		||||
        def = renditionService.createRenditionDefinition(renditionName, HTMLRenderingEngine.NAME);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @After
 | 
			
		||||
    public void after() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        super.after();
 | 
			
		||||
        
 | 
			
		||||
        tidyUpSourceDoc();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void createTargetFolder()
 | 
			
		||||
    {
 | 
			
		||||
        // Set the current security context as admin
 | 
			
		||||
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
 | 
			
		||||
       
 | 
			
		||||
        Map<QName,Serializable> properties = new HashMap<QName,Serializable>();
 | 
			
		||||
        properties.put(ContentModel.PROP_NAME, "TestFolder");
 | 
			
		||||
        targetFolder = nodeService.createNode(
 | 
			
		||||
             companyHome, ContentModel.ASSOC_CONTAINS,
 | 
			
		||||
             QName.createQName("TestFolder"), 
 | 
			
		||||
             ContentModel.TYPE_FOLDER,
 | 
			
		||||
             properties
 | 
			
		||||
        ).getChildRef();
 | 
			
		||||
       
 | 
			
		||||
        targetFolderPath = "/" +
 | 
			
		||||
           (String) nodeService.getProperty(companyHome, ContentModel.PROP_NAME) +
 | 
			
		||||
           "/" +
 | 
			
		||||
           (String) nodeService.getProperty(targetFolder, ContentModel.PROP_NAME)
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
    private void tidyUpSourceDoc()
 | 
			
		||||
    {
 | 
			
		||||
        // Set the current security context as admin
 | 
			
		||||
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
 | 
			
		||||
       
 | 
			
		||||
        // Clean up the source
 | 
			
		||||
        if(sourceDoc != null)
 | 
			
		||||
        {
 | 
			
		||||
           nodeService.deleteNode(sourceDoc);
 | 
			
		||||
        }
 | 
			
		||||
      
 | 
			
		||||
        // Clean up the target folder
 | 
			
		||||
        nodeService.deleteNode(targetFolder);
 | 
			
		||||
        targetFolder = null;
 | 
			
		||||
        
 | 
			
		||||
        // All done
 | 
			
		||||
        sourceDoc = null;
 | 
			
		||||
        createTargetFolder();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private NodeRef createForDoc(String docname) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
       // Create the node
 | 
			
		||||
       Map<QName,Serializable> properties = new HashMap<QName,Serializable>();
 | 
			
		||||
       properties.put(ContentModel.PROP_NAME, docname);
 | 
			
		||||
       
 | 
			
		||||
       NodeRef node = nodeService.createNode(
 | 
			
		||||
             companyHome, ContentModel.ASSOC_CONTAINS,
 | 
			
		||||
             QName.createQName(docname),
 | 
			
		||||
             ContentModel.TYPE_CONTENT,
 | 
			
		||||
             properties
 | 
			
		||||
       ).getChildRef();
 | 
			
		||||
       
 | 
			
		||||
       // Put the sample doc into it
 | 
			
		||||
       File f = AbstractContentTransformerTest.loadNamedQuickTestFile(docname);
 | 
			
		||||
       if(f == null) {
 | 
			
		||||
          fail("Unable to find test file for " + docname);
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
       ContentWriter writer = contentService.getWriter(
 | 
			
		||||
             node, ContentModel.PROP_CONTENT, true
 | 
			
		||||
       );
 | 
			
		||||
       if(docname.endsWith(".doc")) {
 | 
			
		||||
          writer.setMimetype(MIMETYPE_DOC);
 | 
			
		||||
       }
 | 
			
		||||
       if(docname.endsWith(".docx")) {
 | 
			
		||||
          writer.setMimetype(MIMETYPE_DOCX);
 | 
			
		||||
       }
 | 
			
		||||
       writer.putContent(f);
 | 
			
		||||
          
 | 
			
		||||
       if (log.isDebugEnabled())
 | 
			
		||||
       {
 | 
			
		||||
           log.debug("Created document with name: " + docname + ", nodeRef: " + node + ", mimetype: " + writer.getMimetype());
 | 
			
		||||
       }
 | 
			
		||||
 | 
			
		||||
       // All done
 | 
			
		||||
       return node;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBasics() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
       def.setParameterValue(
 | 
			
		||||
             RenditionService.PARAM_DESTINATION_PATH_TEMPLATE,
 | 
			
		||||
             targetFolderPath + "/${name}.html"
 | 
			
		||||
       );
 | 
			
		||||
 | 
			
		||||
       sourceDoc = createForDoc("quick.doc");
 | 
			
		||||
          
 | 
			
		||||
       ChildAssociationRef rendition = renditionService.render(sourceDoc, def);
 | 
			
		||||
       assertNotNull(rendition);
 | 
			
		||||
          
 | 
			
		||||
       // Check it was created
 | 
			
		||||
       NodeRef htmlNode = rendition.getChildRef();
 | 
			
		||||
       assertEquals(true, nodeService.exists(htmlNode));
 | 
			
		||||
          
 | 
			
		||||
       // Check it got the right name
 | 
			
		||||
       assertEquals(
 | 
			
		||||
             "quick.html",
 | 
			
		||||
             nodeService.getProperty(htmlNode, ContentModel.PROP_NAME)
 | 
			
		||||
       );
 | 
			
		||||
       
 | 
			
		||||
       // Check it got the right contents
 | 
			
		||||
       ContentReader reader = contentService.getReader(
 | 
			
		||||
             htmlNode, ContentModel.PROP_CONTENT
 | 
			
		||||
       );
 | 
			
		||||
       String html = reader.getContentString();
 | 
			
		||||
       assertEquals("<?xml", html.substring(0, 5));
 | 
			
		||||
       assertTrue("HTML wrong:\n"+html, html.contains("<html"));
 | 
			
		||||
       assertTrue("HTML wrong:\n"+html, html.contains("<head>"));
 | 
			
		||||
       assertTrue("HTML wrong:\n"+html, html.contains("<body>"));
 | 
			
		||||
       
 | 
			
		||||
       assertTrue("HTML wrong:\n"+html, html.contains("<p>The quick brown fox"));
 | 
			
		||||
       
 | 
			
		||||
       
 | 
			
		||||
       // Now do a body-only one, check that we still got the 
 | 
			
		||||
       //  contents, but not the html surround
 | 
			
		||||
       def.setParameterValue(
 | 
			
		||||
             HTMLRenderingEngine.PARAM_BODY_CONTENTS_ONLY, Boolean.TRUE
 | 
			
		||||
       );
 | 
			
		||||
       rendition = renditionService.render(sourceDoc, def);
 | 
			
		||||
       assertNotNull(rendition);
 | 
			
		||||
       
 | 
			
		||||
       htmlNode = rendition.getChildRef();
 | 
			
		||||
       assertEquals(true, nodeService.exists(htmlNode));
 | 
			
		||||
       
 | 
			
		||||
       reader = contentService.getReader(
 | 
			
		||||
             htmlNode, ContentModel.PROP_CONTENT
 | 
			
		||||
       );
 | 
			
		||||
       html = reader.getContentString();
 | 
			
		||||
       assertFalse("Body wrong:\n"+html, html.contains("<?xml"));
 | 
			
		||||
       assertFalse("Body wrong:\n"+html, html.contains("<html"));
 | 
			
		||||
       assertFalse("Body wrong:\n"+html, html.contains("<head>"));
 | 
			
		||||
       assertFalse("Body wrong:\n"+html, html.contains("<body>"));
 | 
			
		||||
       
 | 
			
		||||
       assertTrue("HTML wrong:\n"+html, html.contains("<p>The quick brown fox"));
 | 
			
		||||
       assertTrue("HTML wrong:\n"+html, html.contains("</p>"));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Test for a .doc and a .docx, neither of which have images
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testDocWithoutImages() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
       def.setParameterValue(
 | 
			
		||||
             RenditionService.PARAM_DESTINATION_PATH_TEMPLATE,
 | 
			
		||||
             targetFolderPath + "/${name}.html"
 | 
			
		||||
       );
 | 
			
		||||
 | 
			
		||||
       for(String name : new String[] {"quick.doc","quick.docx"})
 | 
			
		||||
       {
 | 
			
		||||
          sourceDoc = createForDoc(name);
 | 
			
		||||
          
 | 
			
		||||
          int numItemsStart = nodeService.getChildAssocs(targetFolder).size();
 | 
			
		||||
          
 | 
			
		||||
          ChildAssociationRef rendition = renditionService.render(sourceDoc, def);
 | 
			
		||||
          assertNotNull(rendition);
 | 
			
		||||
          
 | 
			
		||||
          // Check it was created
 | 
			
		||||
          NodeRef htmlNode = rendition.getChildRef();
 | 
			
		||||
          assertEquals(true, nodeService.exists(htmlNode));
 | 
			
		||||
          
 | 
			
		||||
          // Check it got the right name
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                name.substring(0, name.lastIndexOf('.')) + ".html",
 | 
			
		||||
                nodeService.getProperty(htmlNode, ContentModel.PROP_NAME)
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check it ended up in the right place
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                "Should have been in " + targetFolderPath + " but was  in" +
 | 
			
		||||
                   nodeService.getPath(htmlNode),
 | 
			
		||||
                targetFolder,
 | 
			
		||||
                nodeService.getPrimaryParent(htmlNode).getParentRef()
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check it got the right contents
 | 
			
		||||
          ContentReader reader = contentService.getReader(
 | 
			
		||||
                htmlNode, ContentModel.PROP_CONTENT
 | 
			
		||||
          );
 | 
			
		||||
          String html = reader.getContentString();
 | 
			
		||||
          assertEquals("<?xml", html.substring(0, 5));
 | 
			
		||||
          
 | 
			
		||||
          // Check we didn't get an image folder, only the html
 | 
			
		||||
          int numItems = nodeService.getChildAssocs(targetFolder).size();
 | 
			
		||||
          assertEquals(numItemsStart+1, numItems);
 | 
			
		||||
          
 | 
			
		||||
          // Check that the html lacks img tags
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                "Unexpected img tag in html:\n" + html,
 | 
			
		||||
                false, html.contains("<img")
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check we didn't get any images
 | 
			
		||||
          for(ChildAssociationRef ref : nodeService.getChildAssocs(htmlNode))
 | 
			
		||||
          {
 | 
			
		||||
             // TODO Check against composite content associations when present 
 | 
			
		||||
//             if(ref.getTypeQName().equals(HTMLRenderingEngine.PRIMARY_IMAGE))
 | 
			
		||||
//                fail("Found unexpected primary image of rendered html");
 | 
			
		||||
//             if(ref.getTypeQName().equals(HTMLRenderingEngine.SECONDARY_IMAGE))
 | 
			
		||||
//                fail("Found unexpected secondary image of rendered html");
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          // All done
 | 
			
		||||
          tidyUpSourceDoc();
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Test for a .doc and a .docx, both of which have 
 | 
			
		||||
     *  images in them
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testDocWithImages() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
       def.setParameterValue(
 | 
			
		||||
             RenditionService.PARAM_DESTINATION_PATH_TEMPLATE,
 | 
			
		||||
             targetFolderPath + "/${name}.html"
 | 
			
		||||
       );
 | 
			
		||||
       
 | 
			
		||||
       String[] files = new String[] {"quickImg1.doc","quickImg1.docx", "quickImg3.doc","quickImg3.docx"};
 | 
			
		||||
       int[] imgCounts = new int[] {1,1, 3,3};
 | 
			
		||||
 | 
			
		||||
       for(int i=0; i<files.length; i++)
 | 
			
		||||
       {
 | 
			
		||||
          String name = files[i];
 | 
			
		||||
          sourceDoc = createForDoc(name);
 | 
			
		||||
          
 | 
			
		||||
          String baseName = name.substring(0, name.lastIndexOf('.'));
 | 
			
		||||
          
 | 
			
		||||
          int numItemsStart = nodeService.getChildAssocs(targetFolder).size();
 | 
			
		||||
          
 | 
			
		||||
          ChildAssociationRef rendition = renditionService.render(sourceDoc, def);
 | 
			
		||||
          assertNotNull(rendition);
 | 
			
		||||
          
 | 
			
		||||
          // Check it was created
 | 
			
		||||
          NodeRef htmlNode = rendition.getChildRef();
 | 
			
		||||
          assertEquals(true, nodeService.exists(htmlNode));
 | 
			
		||||
          
 | 
			
		||||
          // Check it got the right name
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                baseName + ".html",
 | 
			
		||||
                nodeService.getProperty(htmlNode, ContentModel.PROP_NAME)
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check it ended up in the right place
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                "Should have been in " + targetFolderPath + " but was  in" +
 | 
			
		||||
                   nodeService.getPath(htmlNode),
 | 
			
		||||
                targetFolder,
 | 
			
		||||
                nodeService.getPrimaryParent(htmlNode).getParentRef()
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check it got the right contents
 | 
			
		||||
          ContentReader reader = contentService.getReader(
 | 
			
		||||
                htmlNode, ContentModel.PROP_CONTENT
 | 
			
		||||
          );
 | 
			
		||||
          String html = reader.getContentString();
 | 
			
		||||
          assertEquals("<?xml", html.substring(0, 5));
 | 
			
		||||
          
 | 
			
		||||
          // Check that the html has the img tags
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                "Couldn't find img tag in html:\n" + html,
 | 
			
		||||
                true, html.contains("<img")
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check that it has the right img src
 | 
			
		||||
          String expSource = "src=\""+ baseName + "_files" + "/image";
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                "Couldn't find correct img src in html:\n" + expSource + "\n" + html,
 | 
			
		||||
                true, html.contains(expSource)
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check we got an image folder
 | 
			
		||||
          int numItems = nodeService.getChildAssocs(targetFolder).size();
 | 
			
		||||
          assertEquals(numItemsStart+2, numItems);
 | 
			
		||||
          
 | 
			
		||||
          // Check the name of the image folder
 | 
			
		||||
          NodeRef imgFolder = null;
 | 
			
		||||
          for(ChildAssociationRef ref : nodeService.getChildAssocs(targetFolder)) {
 | 
			
		||||
             if(nodeService.getProperty(ref.getChildRef(), ContentModel.PROP_NAME).equals(
 | 
			
		||||
                   baseName + "_files"
 | 
			
		||||
             )) {
 | 
			
		||||
                imgFolder = ref.getChildRef();
 | 
			
		||||
             }
 | 
			
		||||
          }
 | 
			
		||||
          assertNotNull("Couldn't find new folder named " + baseName + "_files", imgFolder);
 | 
			
		||||
          
 | 
			
		||||
          // Check the contents
 | 
			
		||||
          assertEquals(imgCounts[i], nodeService.getChildAssocs(imgFolder).size());
 | 
			
		||||
          
 | 
			
		||||
          
 | 
			
		||||
          // TODO Check against composite content associations when present 
 | 
			
		||||
          // Check the associations if supported
 | 
			
		||||
//          if(dictionaryService.getAssociation(HTMLRenderingEngine.PRIMARY_IMAGE) != null)
 | 
			
		||||
//          {
 | 
			
		||||
//             boolean hasPrimary = false;
 | 
			
		||||
//             boolean hasSecondary = false;
 | 
			
		||||
//             for(ChildAssociationRef ref : nodeService.getChildAssocs(htmlNode))
 | 
			
		||||
//             {
 | 
			
		||||
//                if(ref.getTypeQName().equals(HTMLRenderingEngine.PRIMARY_IMAGE))
 | 
			
		||||
//                   hasPrimary = true;
 | 
			
		||||
//                if(ref.getTypeQName().equals(HTMLRenderingEngine.SECONDARY_IMAGE))
 | 
			
		||||
//                   hasSecondary = true;
 | 
			
		||||
//             }
 | 
			
		||||
//             assertEquals(true, hasPrimary);
 | 
			
		||||
//             assertEquals(false, hasSecondary);
 | 
			
		||||
//          }
 | 
			
		||||
          
 | 
			
		||||
          // All done
 | 
			
		||||
          tidyUpSourceDoc();
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Test for the option to have the images written to the
 | 
			
		||||
     *  same folder as the html, with a name prefix to them.
 | 
			
		||||
     *  
 | 
			
		||||
     * TODO Re-enable when we've figured out why the rendition service sulkts
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testImagesSameFolder() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
       def.setParameterValue(
 | 
			
		||||
             RenditionService.PARAM_DESTINATION_PATH_TEMPLATE,
 | 
			
		||||
             targetFolderPath + "/${name}.html"
 | 
			
		||||
       );
 | 
			
		||||
       def.setParameterValue(
 | 
			
		||||
             HTMLRenderingEngine.PARAM_IMAGES_SAME_FOLDER,
 | 
			
		||||
             true
 | 
			
		||||
       );
 | 
			
		||||
 | 
			
		||||
       // The documents listed below have 3 embedded images each.
 | 
			
		||||
       final int expectedImageCount = 3;
 | 
			
		||||
       for(String name : new String[] {"quickImg3.doc","quickImg3.docx"})
 | 
			
		||||
       {
 | 
			
		||||
          sourceDoc = createForDoc(name);
 | 
			
		||||
          String baseName = name.substring(0, name.lastIndexOf('.'));
 | 
			
		||||
          
 | 
			
		||||
          int numItemsStart = nodeService.getChildAssocs(targetFolder).size();
 | 
			
		||||
          if (log.isDebugEnabled())
 | 
			
		||||
          {
 | 
			
		||||
              log.debug("targetFolder " + targetFolder + " has " + numItemsStart + " children at start.");
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          ChildAssociationRef rendition = renditionService.render(sourceDoc, def);
 | 
			
		||||
          assertNotNull(rendition);
 | 
			
		||||
          
 | 
			
		||||
          // Check it was created
 | 
			
		||||
          NodeRef htmlNode = rendition.getChildRef();
 | 
			
		||||
          assertEquals(true, nodeService.exists(htmlNode));
 | 
			
		||||
          
 | 
			
		||||
          // Check it got the right name
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                baseName + ".html",
 | 
			
		||||
                nodeService.getProperty(htmlNode, ContentModel.PROP_NAME)
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check it ended up in the right place
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                "Should have been in " + targetFolderPath + " but was  in" +
 | 
			
		||||
                   nodeService.getPath(htmlNode),
 | 
			
		||||
                targetFolder,
 | 
			
		||||
                nodeService.getPrimaryParent(htmlNode).getParentRef()
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check it got the right contents
 | 
			
		||||
          ContentReader reader = contentService.getReader(
 | 
			
		||||
                htmlNode, ContentModel.PROP_CONTENT
 | 
			
		||||
          );
 | 
			
		||||
          String html = reader.getContentString();
 | 
			
		||||
          assertEquals("<?xml", html.substring(0, 5));
 | 
			
		||||
          
 | 
			
		||||
          // Check that the html has the img tags
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                "Couldn't find img tag in html:\n" + html,
 | 
			
		||||
                true, html.contains("<img")
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check that it has the right img src
 | 
			
		||||
          String expSource = "src=\""+ baseName + "_image";
 | 
			
		||||
          assertEquals(
 | 
			
		||||
                "Couldn't find correct img src in html:\n" + expSource + "\n" + html,
 | 
			
		||||
                true, html.contains(expSource)
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // Check we got an image folder
 | 
			
		||||
          int numItems = nodeService.getChildAssocs(targetFolder).size();
 | 
			
		||||
          
 | 
			
		||||
          // We expect a number of images and one text/html node to be created.
 | 
			
		||||
          final int additionalItems = expectedImageCount + 1;
 | 
			
		||||
          assertEquals(numItemsStart+additionalItems, numItems);
 | 
			
		||||
          
 | 
			
		||||
          // There shouldn't be an image folder created
 | 
			
		||||
          for(ChildAssociationRef ref : nodeService.getChildAssocs(targetFolder)) {
 | 
			
		||||
             if(nodeService.getProperty(ref.getChildRef(), ContentModel.PROP_NAME).equals(
 | 
			
		||||
                   baseName + "_files"
 | 
			
		||||
             )) {
 | 
			
		||||
                fail("Image folder was created but shouldn't be there");
 | 
			
		||||
             }
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          // Check we got the images in the same directory as the html
 | 
			
		||||
          int images = 0;
 | 
			
		||||
          for(ChildAssociationRef ref : nodeService.getChildAssocs(targetFolder)) {
 | 
			
		||||
             String childName = (String)nodeService.getProperty(ref.getChildRef(), ContentModel.PROP_NAME);
 | 
			
		||||
             if(childName.startsWith(baseName + "_image")) {
 | 
			
		||||
                images++;
 | 
			
		||||
             }
 | 
			
		||||
          }
 | 
			
		||||
          assertEquals(expectedImageCount, images);
 | 
			
		||||
          
 | 
			
		||||
          // Until the rendition service supports a forced overwrite of other renditions, we must
 | 
			
		||||
          // delete the old rendition node & the images.
 | 
			
		||||
          nodeService.deleteNode(rendition.getChildRef());
 | 
			
		||||
          for (ChildAssociationRef chAssRef : nodeService.getChildAssocs(targetFolder))
 | 
			
		||||
          {
 | 
			
		||||
              nodeService.deleteNode(chAssRef.getChildRef());
 | 
			
		||||
          }
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2019 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
@@ -32,10 +32,13 @@ import org.alfresco.repo.thumbnail.ThumbnailDefinition;
 | 
			
		||||
import org.alfresco.repo.thumbnail.ThumbnailRenditionConvertor;
 | 
			
		||||
import org.alfresco.service.cmr.rendition.RenditionDefinition;
 | 
			
		||||
import org.alfresco.service.cmr.rendition.RenditionService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.PagedSourceOptions;
 | 
			
		||||
import org.alfresco.service.cmr.repository.TransformationOptions;
 | 
			
		||||
import org.alfresco.service.cmr.repository.TransformationSourceOptions;
 | 
			
		||||
import org.alfresco.util.BaseSpringTest;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
@@ -126,6 +129,27 @@ public class RenditionDefinitionTest extends BaseSpringTest
 | 
			
		||||
            // than checking transformationOptions is equal to transformationOptions2.
 | 
			
		||||
            if (!renditionName.equals("pdf") && !renditionName.equals("webpreview"))
 | 
			
		||||
            {
 | 
			
		||||
                // MNT-22409: We no longer have system.thumbnail.definition.default.pageLimit=1 as a default.
 | 
			
		||||
                // It is -1 (unlimited), but to compensate for that the new OOTB renditions defined in
 | 
			
		||||
                // 0100-baseRenditions now have startPage and endPage transform options. The following code modifies
 | 
			
		||||
                //  the transformationOptions so that they will be the same if there are no bugs. 
 | 
			
		||||
                Collection<TransformationSourceOptions> sourceOptionsList = transformationOptions2.getSourceOptionsList();
 | 
			
		||||
                if (sourceOptionsList != null && sourceOptionsList.size() == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    TransformationSourceOptions sourceOptions = sourceOptionsList.iterator().next();
 | 
			
		||||
                    if (sourceOptions instanceof PagedSourceOptions)
 | 
			
		||||
                    {
 | 
			
		||||
                        PagedSourceOptions pagedSourceOptions = (PagedSourceOptions)sourceOptions;
 | 
			
		||||
                        if (pagedSourceOptions.getStartPageNumber() == 1 && pagedSourceOptions.getEndPageNumber() == 1)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (transformationOptions.getSourceOptionsList() == null)
 | 
			
		||||
                            {
 | 
			
		||||
                                transformationOptions.setSourceOptionsList(sourceOptionsList);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                assertEquals("The TransformationOptions used in transforms for " + renditionName + " should be the same",
 | 
			
		||||
                        transformationOptions.toStringAll(), transformationOptions2.toStringAll());
 | 
			
		||||
                assertEquals("The transformationOptionsConverter back to the newer format was not the same for " +
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2020 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2021 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
@@ -565,4 +565,18 @@ public class TransformationOptionsConverterTest
 | 
			
		||||
                        "timeout=-1 "
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testCommandOptionsFromOldOptions()
 | 
			
		||||
    {
 | 
			
		||||
        ImageTransformationOptions oldOptions = new ImageTransformationOptions();
 | 
			
		||||
        oldOptions.setCommandOptions("-resize 350x50> -background none -gravity center");
 | 
			
		||||
 | 
			
		||||
        assertConverterToMapAndBack(oldOptions, MIMETYPE_IMAGE_JPEG, MIMETYPE_IMAGE_PNG,
 | 
			
		||||
                "ImageTransformationOptions [commandOptions=-resize 350x50> -background none -gravity center, " +
 | 
			
		||||
                        "resizeOptions=null, autoOrient=true]]",
 | 
			
		||||
                "autoOrient=true " + // this is a default - so is also set when uploading a logo
 | 
			
		||||
                        "commandOptions=-resize 350x50> -background none -gravity center " +
 | 
			
		||||
                        "timeout=-1 ");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -158,7 +158,20 @@ public class DBQueryEngineTest
 | 
			
		||||
        assertNodePresent(6, result);
 | 
			
		||||
        assertNodePresent(7, result);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldResultSetLengthMatchTheAmountOfAllAccessibleNodesWhenMaxPermissionCheckEnabled()
 | 
			
		||||
    {
 | 
			
		||||
        withMaxItems(5);
 | 
			
		||||
        prepareTemplate(dbQuery, createNodes(10));
 | 
			
		||||
        when(assessor.isIncluded(any(Node.class))).thenReturn(true);
 | 
			
		||||
 | 
			
		||||
        engine.setMaxPermissionCheckEnabled(true);
 | 
			
		||||
        FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
 | 
			
		||||
 | 
			
		||||
        assertEquals(10, result.length());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void shouldNotConsiderInaccessibleNodesInResultSetWhenSkippingNodes()
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,155 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.search.impl.solr;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.fail;
 | 
			
		||||
import static org.mockito.Mockito.doReturn;
 | 
			
		||||
import static org.mockito.Mockito.spy;
 | 
			
		||||
import static org.mockito.Mockito.verify;
 | 
			
		||||
import static org.mockito.Mockito.when;
 | 
			
		||||
import static org.mockito.MockitoAnnotations.openMocks;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.search.QueryParserException;
 | 
			
		||||
import org.apache.commons.httpclient.Header;
 | 
			
		||||
import org.apache.commons.httpclient.HttpClient;
 | 
			
		||||
import org.apache.commons.httpclient.URI;
 | 
			
		||||
import org.apache.commons.httpclient.methods.PostMethod;
 | 
			
		||||
import org.json.JSONObject;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.mockito.Mock;
 | 
			
		||||
 | 
			
		||||
/** Tests for the {@link AbstractSolrQueryHTTPClient}. */
 | 
			
		||||
public class AbstractSolrQueryHTTPClientTest
 | 
			
		||||
{
 | 
			
		||||
    /** A URL for use in the tests. */
 | 
			
		||||
    private static final String URL = "http://this/is/a/url";
 | 
			
		||||
 | 
			
		||||
    /** The abstract class under test. */
 | 
			
		||||
    private AbstractSolrQueryHTTPClient abstractSolrQueryHTTPClient = spy(AbstractSolrQueryHTTPClient.class);
 | 
			
		||||
    @Mock
 | 
			
		||||
    private HttpClient httpClient;
 | 
			
		||||
    @Mock
 | 
			
		||||
    private JSONObject body;
 | 
			
		||||
    @Mock
 | 
			
		||||
    private PostMethod postMethod;
 | 
			
		||||
    @Mock
 | 
			
		||||
    private Header header;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUp() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        openMocks(this);
 | 
			
		||||
 | 
			
		||||
        doReturn(postMethod).when(abstractSolrQueryHTTPClient).createNewPostMethod(URL);
 | 
			
		||||
        when(postMethod.getResponseCharSet()).thenReturn("UTF-8");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Check postQuery works as expected for the success case. */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testPostQuery_success() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        when(body.toString()).thenReturn("Example body");
 | 
			
		||||
        when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_OK);
 | 
			
		||||
        when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream("{}"));
 | 
			
		||||
 | 
			
		||||
        JSONObject response = abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body);
 | 
			
		||||
 | 
			
		||||
        assertEquals("Unexpected JSON response received.", "{}", response.toString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Check that the status code is usually passed through from Solr. */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testPostQuery_failure() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        String failureMessage = "{\"error\": {\"trace\": \"ExceptionClass: Stacktrace\"}}";
 | 
			
		||||
 | 
			
		||||
        when(body.toString()).thenReturn("Example body");
 | 
			
		||||
        when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_NOT_FOUND);
 | 
			
		||||
        when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream(failureMessage));
 | 
			
		||||
        when(postMethod.getResponseBodyAsString()).thenReturn(failureMessage);
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body);
 | 
			
		||||
            fail("Expected a QueryParserException to be thrown.");
 | 
			
		||||
        }
 | 
			
		||||
        catch (QueryParserException e)
 | 
			
		||||
        {
 | 
			
		||||
            assertEquals("Unexpected status code in exception.", e.getHttpStatusCode(), HttpServletResponse.SC_NOT_FOUND);
 | 
			
		||||
            verify(postMethod).releaseConnection();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Check that the status code is replaced with "Not Implemented" for an unsupported query option. */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testPostQuery_unsupportedOperation() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        String failureMessage = "{\"error\": {\"trace\": \"java.lang.UnsupportedOperationException: Stacktrace\"}}";
 | 
			
		||||
 | 
			
		||||
        when(body.toString()).thenReturn("Example body");
 | 
			
		||||
        when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
 | 
			
		||||
        when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream(failureMessage));
 | 
			
		||||
        when(postMethod.getResponseBodyAsString()).thenReturn(failureMessage);
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body);
 | 
			
		||||
            fail("Expected a QueryParserException to be thrown.");
 | 
			
		||||
        }
 | 
			
		||||
        catch (QueryParserException e)
 | 
			
		||||
        {
 | 
			
		||||
            assertEquals("Unexpected status code in exception.", e.getHttpStatusCode(), HttpServletResponse.SC_NOT_IMPLEMENTED);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Check that a redirect can be followed if the endpoint reports that it's moved. */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testPostQuery_moved() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        when(body.toString()).thenReturn("Example body");
 | 
			
		||||
        // Report "moved" for the first invocation and then OK for subsequent requests.
 | 
			
		||||
        when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_MOVED_PERMANENTLY).thenReturn(HttpServletResponse.SC_OK);
 | 
			
		||||
        when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream("{}"));
 | 
			
		||||
        when(postMethod.getResponseHeader("location")).thenReturn(header);
 | 
			
		||||
        when(header.getValue()).thenReturn("http://new/URL");
 | 
			
		||||
 | 
			
		||||
        JSONObject response = abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body);
 | 
			
		||||
 | 
			
		||||
        verify(postMethod).setURI(new URI("http://new/URL", true));
 | 
			
		||||
        assertEquals("Unexpected JSON response received.", "{}", response.toString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Create an input stream containing the given string. */
 | 
			
		||||
    private ByteArrayInputStream convertStringToInputStream(String message)
 | 
			
		||||
    {
 | 
			
		||||
        return new ByteArrayInputStream(message.getBytes());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -31,14 +31,16 @@ import static org.junit.Assert.assertTrue;
 | 
			
		||||
import static org.mockito.ArgumentMatchers.eq;
 | 
			
		||||
import static org.mockito.Mockito.doReturn;
 | 
			
		||||
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.httpclient.HttpClientFactory;
 | 
			
		||||
import org.alfresco.httpclient.RequestHeadersHttpClient;
 | 
			
		||||
import org.alfresco.service.cmr.repository.StoreRef;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.apache.commons.codec.net.URLCodec;
 | 
			
		||||
import org.apache.commons.httpclient.HostConfiguration;
 | 
			
		||||
import org.apache.commons.httpclient.HttpClient;
 | 
			
		||||
import org.apache.commons.httpclient.protocol.Protocol;
 | 
			
		||||
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
@@ -46,8 +48,6 @@ import org.mockito.Mock;
 | 
			
		||||
import org.mockito.junit.MockitoJUnitRunner;
 | 
			
		||||
import org.springframework.beans.factory.BeanFactory;
 | 
			
		||||
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Andy
 | 
			
		||||
 *
 | 
			
		||||
@@ -64,34 +64,34 @@ public class SolrStoreMappingWrapperTest
 | 
			
		||||
    HttpClientFactory httpClientFactory;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClientCommon;
 | 
			
		||||
    RequestHeadersHttpClient httpClientCommon;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClient1;
 | 
			
		||||
    RequestHeadersHttpClient httpClient1;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClient2;
 | 
			
		||||
    RequestHeadersHttpClient httpClient2;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClient3;
 | 
			
		||||
    RequestHeadersHttpClient httpClient3;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClient4;
 | 
			
		||||
    RequestHeadersHttpClient httpClient4;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClient5;
 | 
			
		||||
    RequestHeadersHttpClient httpClient5;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClient6;
 | 
			
		||||
    RequestHeadersHttpClient httpClient6;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClient7;
 | 
			
		||||
    RequestHeadersHttpClient httpClient7;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClient8;
 | 
			
		||||
    RequestHeadersHttpClient httpClient8;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HttpClient httpClient9;
 | 
			
		||||
    RequestHeadersHttpClient httpClient9;
 | 
			
		||||
    
 | 
			
		||||
    @Mock
 | 
			
		||||
    HostConfiguration hostConfigurationCommon;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,9 +25,9 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.usage;
 | 
			
		||||
 | 
			
		||||
import javax.transaction.UserTransaction;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import junit.framework.TestCase;
 | 
			
		||||
import javax.transaction.UserTransaction;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.lock.JobLockService;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | 
			
		||||
@@ -37,6 +37,7 @@ import org.alfresco.service.cmr.admin.RepoUsage;
 | 
			
		||||
import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode;
 | 
			
		||||
import org.alfresco.service.cmr.admin.RepoUsage.UsageType;
 | 
			
		||||
import org.alfresco.service.cmr.admin.RepoUsageStatus;
 | 
			
		||||
import org.alfresco.service.cmr.admin.RepoUsageStatus.RepoUsageLevel;
 | 
			
		||||
import org.alfresco.service.transaction.TransactionService;
 | 
			
		||||
import org.alfresco.test_category.OwnJVMTestsCategory;
 | 
			
		||||
import org.alfresco.util.ApplicationContextHelper;
 | 
			
		||||
@@ -48,6 +49,8 @@ import org.junit.experimental.categories.Category;
 | 
			
		||||
import org.junit.runners.MethodSorters;
 | 
			
		||||
import org.springframework.context.ApplicationContext;
 | 
			
		||||
 | 
			
		||||
import junit.framework.TestCase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests {@link RepoUsageComponent}
 | 
			
		||||
 * 
 | 
			
		||||
@@ -236,6 +239,123 @@ public class RepoUsageComponentTest extends TestCase
 | 
			
		||||
        RepoUsage usage = getUsage();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void testLicenceHoursBeforeExpiration() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Update usage
 | 
			
		||||
        updateUsage(UsageType.USAGE_ALL);
 | 
			
		||||
 | 
			
		||||
        // Set the restrictions for license to expire in 6 hours
 | 
			
		||||
        RepoUsage restrictions = new RepoUsage(
 | 
			
		||||
                System.currentTimeMillis(),
 | 
			
		||||
                5000L,
 | 
			
		||||
                100000L,
 | 
			
		||||
                LicenseMode.TEAM,
 | 
			
		||||
                System.currentTimeMillis() + TimeUnit.HOURS.toMillis(6),
 | 
			
		||||
                false);
 | 
			
		||||
        repoUsageComponent.setRestrictions(restrictions);
 | 
			
		||||
        
 | 
			
		||||
        // Update use
 | 
			
		||||
        updateUsage(UsageType.USAGE_ALL);
 | 
			
		||||
 | 
			
		||||
        // Get the usage
 | 
			
		||||
        RepoUsage usage = getUsage();        
 | 
			
		||||
        
 | 
			
		||||
        // Check        
 | 
			
		||||
        assertFalse("Usage is in read-only mode",usage.isReadOnly());
 | 
			
		||||
        assertTrue("System is in read-only mode",transactionService.getAllowWrite());
 | 
			
		||||
        
 | 
			
		||||
        RepoUsageStatus status = repoUsageComponent.getUsageStatus();
 | 
			
		||||
        assertEquals("System is not at Warning All Level",status.getLevel(),RepoUsageLevel.WARN_ALL);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void testLicenceMinutesAfterExpiration() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Update usage
 | 
			
		||||
        updateUsage(UsageType.USAGE_ALL);
 | 
			
		||||
 | 
			
		||||
        // Set the restrictions for license to expire in 6 hours
 | 
			
		||||
        RepoUsage restrictions = new RepoUsage(
 | 
			
		||||
                System.currentTimeMillis(),
 | 
			
		||||
                5000L,
 | 
			
		||||
                100000L,
 | 
			
		||||
                LicenseMode.TEAM,
 | 
			
		||||
                System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(1),
 | 
			
		||||
                false);
 | 
			
		||||
        repoUsageComponent.setRestrictions(restrictions);
 | 
			
		||||
        
 | 
			
		||||
        // Update use
 | 
			
		||||
        updateUsage(UsageType.USAGE_ALL);
 | 
			
		||||
 | 
			
		||||
        // Get the usage
 | 
			
		||||
        RepoUsage usage = getUsage();        
 | 
			
		||||
        
 | 
			
		||||
        // Check we are in read-only mode
 | 
			
		||||
        assertTrue("Usage is not in read-only mode",usage.isReadOnly());
 | 
			
		||||
        assertFalse("System is not in read-only mode",transactionService.getAllowWrite());
 | 
			
		||||
        
 | 
			
		||||
        RepoUsageStatus status = repoUsageComponent.getUsageStatus();
 | 
			
		||||
        assertEquals("System is not at Locked Level",status.getLevel(),RepoUsageLevel.LOCKED_DOWN);        
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    public void testLicenceMonthsBeforeExpiration() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Update usage
 | 
			
		||||
        updateUsage(UsageType.USAGE_ALL);
 | 
			
		||||
 | 
			
		||||
        // Set the restrictions for license to expire in 60 days from now
 | 
			
		||||
        RepoUsage restrictions = new RepoUsage(
 | 
			
		||||
                System.currentTimeMillis(),
 | 
			
		||||
                5000L,
 | 
			
		||||
                100000L,
 | 
			
		||||
                LicenseMode.TEAM,
 | 
			
		||||
                System.currentTimeMillis() + TimeUnit.DAYS.toMillis(60),
 | 
			
		||||
                false);
 | 
			
		||||
        repoUsageComponent.setRestrictions(restrictions);
 | 
			
		||||
        
 | 
			
		||||
        // Update use
 | 
			
		||||
        updateUsage(UsageType.USAGE_ALL);
 | 
			
		||||
 | 
			
		||||
        // Get the usage
 | 
			
		||||
        RepoUsage usage = getUsage();        
 | 
			
		||||
        
 | 
			
		||||
        // Check        
 | 
			
		||||
        assertFalse("Usage is in read-only mode",usage.isReadOnly());
 | 
			
		||||
        assertTrue("System is in read-only mode",transactionService.getAllowWrite());
 | 
			
		||||
        
 | 
			
		||||
        RepoUsageStatus status = repoUsageComponent.getUsageStatus();
 | 
			
		||||
        assertEquals("System is not at OK Level",status.getLevel(),RepoUsageLevel.OK);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void testLicenceDaysAfterExpiration() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Update usage
 | 
			
		||||
        updateUsage(UsageType.USAGE_ALL);
 | 
			
		||||
 | 
			
		||||
        // Set the restrictions for license expired 5 days ago
 | 
			
		||||
        RepoUsage restrictions = new RepoUsage(
 | 
			
		||||
                System.currentTimeMillis(),
 | 
			
		||||
                5000L,
 | 
			
		||||
                100000L,
 | 
			
		||||
                LicenseMode.TEAM,
 | 
			
		||||
                System.currentTimeMillis() - TimeUnit.DAYS.toMillis(5),
 | 
			
		||||
                false);
 | 
			
		||||
        repoUsageComponent.setRestrictions(restrictions);
 | 
			
		||||
        
 | 
			
		||||
        // Update use
 | 
			
		||||
        updateUsage(UsageType.USAGE_ALL);
 | 
			
		||||
 | 
			
		||||
        // Get the usage
 | 
			
		||||
        RepoUsage usage = getUsage();        
 | 
			
		||||
        
 | 
			
		||||
        // Check we are in read-only mode
 | 
			
		||||
        assertTrue("Usage is not in read-only mode",usage.isReadOnly());
 | 
			
		||||
        assertFalse("System is not in read-only mode",transactionService.getAllowWrite());
 | 
			
		||||
        
 | 
			
		||||
        RepoUsageStatus status = repoUsageComponent.getUsageStatus();
 | 
			
		||||
        assertEquals("System is not at Locked Level",status.getLevel(),RepoUsageLevel.LOCKED_DOWN);   
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Check that concurrent updates are prevented
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user