mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			21.2
			...
			feature/AC
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					dc32451c28 | ||
| 
						 | 
					4e354449ab | ||
| 
						 | 
					902fe505fa | ||
| 
						 | 
					14e21eb62d | 
							
								
								
									
										14
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -23,7 +23,6 @@ env:
 | 
			
		||||
  MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }}
 | 
			
		||||
  QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
 | 
			
		||||
  QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
 | 
			
		||||
  CI_WORKSPACE: ${{ github.workspace }}
 | 
			
		||||
  TAS_ENVIRONMENT: ./packaging/tests/environment
 | 
			
		||||
  TAS_SCRIPTS: ../alfresco-community-repo/packaging/tests/scripts
 | 
			
		||||
 | 
			
		||||
@@ -317,8 +316,7 @@ jobs:
 | 
			
		||||
          - testSuite: MTLSTestSuite
 | 
			
		||||
            compose-profile: with-mtls-transform-core-aio
 | 
			
		||||
            mtls: true
 | 
			
		||||
            disabledHostnameVerification: false
 | 
			
		||||
            mvn-options: '-Dencryption.ssl.keystore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.keystore -Dencryption.ssl.truststore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.truststore'
 | 
			
		||||
            mvn-options: '-Dencryption.ssl.keystore.location=${GITHUB_WORKSPACE}/keystores/alfresco/alfresco.keystore -Dencryption.ssl.truststore.location=${GITHUB_WORKSPACE}/keystores/alfresco/alfresco.truststore'
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
 | 
			
		||||
@@ -330,14 +328,8 @@ jobs:
 | 
			
		||||
      - name: "Generate Keystores and Truststores for Mutual TLS configuration"
 | 
			
		||||
        if: ${{ matrix.mtls }}
 | 
			
		||||
        run: |
 | 
			
		||||
          git clone -b "master" --depth=1 "https://${{ secrets.BOT_GITHUB_USERNAME }}:${{ secrets.BOT_GITHUB_TOKEN }}@github.com/Alfresco/alfresco-ssl-generator.git"
 | 
			
		||||
          if ${{ matrix.disabledHostnameVerification }} ; then
 | 
			
		||||
            bash ${{ env.CI_WORKSPACE }}/alfresco-ssl-generator/scripts/ci/generate_keystores_wrong_hostnames.sh
 | 
			
		||||
            echo "HOSTNAME_VERIFICATION_DISABLED=true" >> "$GITHUB_ENV"
 | 
			
		||||
          else
 | 
			
		||||
            bash ${{ env.CI_WORKSPACE }}/alfresco-ssl-generator/scripts/ci/generate_keystores.sh
 | 
			
		||||
            echo "HOSTNAME_VERIFICATION_DISABLED=false" >> "$GITHUB_ENV"
 | 
			
		||||
          fi
 | 
			
		||||
         git clone -b "master" --depth=1 "https://${{ secrets.BOT_GITHUB_USERNAME }}:${{ secrets.BOT_GITHUB_TOKEN }}@github.com/Alfresco/alfresco-ssl-generator.git"
 | 
			
		||||
         bash ./scripts/ci/generate_keystores.sh
 | 
			
		||||
      - name: "Set up the environment"
 | 
			
		||||
        run: |
 | 
			
		||||
          if [ -e ./scripts/ci/tests/${{ matrix.testSuite }}-setup.sh ]; then
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-community-repo-amps</artifactId>
 | 
			
		||||
      <version>21.2</version>
 | 
			
		||||
      <version>20.143-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-governance-services-community-parent</artifactId>
 | 
			
		||||
      <version>21.2</version>
 | 
			
		||||
      <version>20.143-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-governance-services-automation-community-repo</artifactId>
 | 
			
		||||
      <version>21.2</version>
 | 
			
		||||
      <version>20.143-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <build>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-governance-services-community-parent</artifactId>
 | 
			
		||||
      <version>21.2</version>
 | 
			
		||||
      <version>20.143-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
SOLR6_TAG=2.0.7-A5
 | 
			
		||||
SOLR6_TAG=2.0.7-A2
 | 
			
		||||
POSTGRES_TAG=14.4
 | 
			
		||||
ACTIVEMQ_TAG=5.17.1-jre11-rockylinux8
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-governance-services-community-repo-parent</artifactId>
 | 
			
		||||
      <version>21.2</version>
 | 
			
		||||
      <version>20.143-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <properties>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
# Version label
 | 
			
		||||
version.major=7
 | 
			
		||||
version.minor=4
 | 
			
		||||
version.revision=1
 | 
			
		||||
version.revision=0
 | 
			
		||||
version.label=
 | 
			
		||||
 | 
			
		||||
# Edition label
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-governance-services-community-repo-parent</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,9 @@ tags:
 | 
			
		||||
    description: Retrieve and manage unfiled records containers
 | 
			
		||||
  - name: unfiled-record-folders
 | 
			
		||||
    description: Retrieve and manage unfiled record folders
 | 
			
		||||
 | 
			
		||||
  - name: events
 | 
			
		||||
    description: Retrieve and manage retention events
 | 
			
		||||
    
 | 
			
		||||
paths:
 | 
			
		||||
  ## GS sites
 | 
			
		||||
  '/gs-sites':
 | 
			
		||||
@@ -2092,8 +2094,172 @@ paths:
 | 
			
		||||
          description: Unexpected error
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/Error'
 | 
			
		||||
  '/events':
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - events
 | 
			
		||||
      summary: List all available retention events
 | 
			
		||||
      description: |
 | 
			
		||||
        Gets the list of events that can be used by retention steps
 | 
			
		||||
      operationId: getAllEvents
 | 
			
		||||
      produces:
 | 
			
		||||
        - application/json
 | 
			
		||||
      parameters:
 | 
			
		||||
        - $ref: '#/parameters/skipCountParam'
 | 
			
		||||
        - $ref: '#/parameters/maxItemsParam'
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: Successful response
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/EventPaging'
 | 
			
		||||
        '400':
 | 
			
		||||
          description: |
 | 
			
		||||
            Invalid parameter: value of **maxItems** or **skipCount** is invalid
 | 
			
		||||
        '401':
 | 
			
		||||
          description: Authentication failed
 | 
			
		||||
        default:
 | 
			
		||||
          description: Unexpected error
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/Error'
 | 
			
		||||
 | 
			
		||||
    post:
 | 
			
		||||
      tags:
 | 
			
		||||
        - events
 | 
			
		||||
      summary: Create a new retention event
 | 
			
		||||
      description: |
 | 
			
		||||
        Creates a new event that can be used by retention schedules.
 | 
			
		||||
      operationId: createEvent
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: body
 | 
			
		||||
          name: eventBodyCreate
 | 
			
		||||
          description: The new event.
 | 
			
		||||
          required: true
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/EventBody'
 | 
			
		||||
      consumes:
 | 
			
		||||
        - application/json
 | 
			
		||||
      produces:
 | 
			
		||||
        - application/json
 | 
			
		||||
      responses:
 | 
			
		||||
        '201':
 | 
			
		||||
          description: Successful response
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/EventEntry'
 | 
			
		||||
        '400':
 | 
			
		||||
          description: |
 | 
			
		||||
            Invalid parameter: **name** or **type** is invalid
 | 
			
		||||
        '401':
 | 
			
		||||
          description: Authentication failed
 | 
			
		||||
        '403':
 | 
			
		||||
          description: Current user does not have permission to create event
 | 
			
		||||
        '409':
 | 
			
		||||
          description: Cannot create event. An event with the name **name** already exists
 | 
			
		||||
        default:
 | 
			
		||||
          description: Unexpected error
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/Error'
 | 
			
		||||
  
 | 
			
		||||
  '/events/{eventId}':
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - events
 | 
			
		||||
      summary: Return event for given eventId
 | 
			
		||||
      description: |
 | 
			
		||||
        Gets information about the retention event with id **eventId**.
 | 
			
		||||
      operationId: getEvent
 | 
			
		||||
      produces:
 | 
			
		||||
        - application/json
 | 
			
		||||
      parameters:
 | 
			
		||||
        - $ref: '#/parameters/eventIdParam'
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: Successful response
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/EventEntry'
 | 
			
		||||
        '400':
 | 
			
		||||
          description: |
 | 
			
		||||
            Invalid parameter: **eventId** is invalid    
 | 
			
		||||
        '401':
 | 
			
		||||
          description: Authentication failed
 | 
			
		||||
        '404':
 | 
			
		||||
          description: "**eventId** does not exist"
 | 
			
		||||
        default:
 | 
			
		||||
          description: Unexpected error
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/Error'
 | 
			
		||||
    put:
 | 
			
		||||
      tags:
 | 
			
		||||
        - events
 | 
			
		||||
      summary: Update event for given eventId
 | 
			
		||||
      operationId: updateEvent
 | 
			
		||||
      description: |
 | 
			
		||||
        Updates retention event with id **eventId**.
 | 
			
		||||
      produces:
 | 
			
		||||
        - application/json
 | 
			
		||||
      parameters:
 | 
			
		||||
        - $ref: '#/parameters/eventIdParam'
 | 
			
		||||
        - in: body
 | 
			
		||||
          name: eventBodyUpdate
 | 
			
		||||
          description: The event information to update.
 | 
			
		||||
          required: true
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/EventBody'
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: Successful response
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/EventEntry'
 | 
			
		||||
        '400':
 | 
			
		||||
          description: |
 | 
			
		||||
            Invalid parameter: The update request is invalid or **eventId** is not a valid format or **eventBodyUpdate** is invalid
 | 
			
		||||
        '401':
 | 
			
		||||
          description: Authentication failed
 | 
			
		||||
        '403':
 | 
			
		||||
          description: Current user does not have permission to update events
 | 
			
		||||
        '404':
 | 
			
		||||
          description: "**eventId** does not exist"
 | 
			
		||||
        '409':
 | 
			
		||||
          description: Cannot update event. An event with the name **name** already exists
 | 
			
		||||
        default:
 | 
			
		||||
          description: Unexpected error
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/Error'
 | 
			
		||||
  
 | 
			
		||||
  '/event-types':
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - events
 | 
			
		||||
      summary: List all the retention event types
 | 
			
		||||
      description: |
 | 
			
		||||
        Gets a list of all the retention event types.
 | 
			
		||||
      operationId: getAllEventTypes
 | 
			
		||||
      produces:
 | 
			
		||||
        - application/json
 | 
			
		||||
      parameters:
 | 
			
		||||
        - $ref: '#/parameters/skipCountParam'
 | 
			
		||||
        - $ref: '#/parameters/maxItemsParam'
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: Successful response
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/EventTypePaging'
 | 
			
		||||
        '400':
 | 
			
		||||
          description: |
 | 
			
		||||
            Invalid parameter: value of **maxItems** or **skipCount** is invalid
 | 
			
		||||
        '401':
 | 
			
		||||
          description: Authentication failed
 | 
			
		||||
        default:
 | 
			
		||||
          description: Unexpected error
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/Error'          
 | 
			
		||||
parameters:
 | 
			
		||||
  ## event
 | 
			
		||||
  eventIdParam:
 | 
			
		||||
    name: eventId
 | 
			
		||||
    in: path
 | 
			
		||||
    description: The identifier of an event.
 | 
			
		||||
    required: true
 | 
			
		||||
    type: string
 | 
			
		||||
  ## File plans
 | 
			
		||||
  filePlanEntryIncludeParam:
 | 
			
		||||
    name: include
 | 
			
		||||
@@ -3761,4 +3927,92 @@ definitions:
 | 
			
		||||
          - SiteConsumer
 | 
			
		||||
          - SiteCollaborator
 | 
			
		||||
          - SiteContributor
 | 
			
		||||
          - SiteManager
 | 
			
		||||
          - SiteManager
 | 
			
		||||
  EventPaging:
 | 
			
		||||
    type: object
 | 
			
		||||
    properties:
 | 
			
		||||
      list:
 | 
			
		||||
        type: object
 | 
			
		||||
        properties:
 | 
			
		||||
          pagination:
 | 
			
		||||
            $ref: '#/definitions/Pagination'
 | 
			
		||||
          entries:
 | 
			
		||||
            type: array
 | 
			
		||||
            items:
 | 
			
		||||
              $ref: '#/definitions/EventEntry'
 | 
			
		||||
  EventEntry:
 | 
			
		||||
    type: object
 | 
			
		||||
    required:
 | 
			
		||||
      - entry
 | 
			
		||||
    properties:
 | 
			
		||||
      entry:
 | 
			
		||||
        $ref: '#/definitions/Event'
 | 
			
		||||
  Event:
 | 
			
		||||
    type: object
 | 
			
		||||
    required:
 | 
			
		||||
      - id
 | 
			
		||||
      - name
 | 
			
		||||
      - type
 | 
			
		||||
    properties:
 | 
			
		||||
      id:
 | 
			
		||||
        type: string
 | 
			
		||||
        description: this is the id of the event
 | 
			
		||||
      name:
 | 
			
		||||
        type: string
 | 
			
		||||
        description: This is the unique display label of the event
 | 
			
		||||
      type:
 | 
			
		||||
        type: string
 | 
			
		||||
        description: this is event type
 | 
			
		||||
  EventBody:
 | 
			
		||||
    type: object
 | 
			
		||||
    required:
 | 
			
		||||
      - name
 | 
			
		||||
    properties:
 | 
			
		||||
      name:
 | 
			
		||||
        type: string
 | 
			
		||||
        description: This is the unique display label of the event
 | 
			
		||||
      type:
 | 
			
		||||
        type: string
 | 
			
		||||
        description: this is event type
 | 
			
		||||
        default: Simple
 | 
			
		||||
  EventTypePaging:
 | 
			
		||||
    type: object
 | 
			
		||||
    properties:
 | 
			
		||||
      list:
 | 
			
		||||
        type: object
 | 
			
		||||
        properties:
 | 
			
		||||
          pagination:
 | 
			
		||||
            $ref: '#/definitions/Pagination'
 | 
			
		||||
          entries:
 | 
			
		||||
            type: array
 | 
			
		||||
            items:
 | 
			
		||||
              $ref: '#/definitions/EventTypeEntry'
 | 
			
		||||
  EventTypeEntry:
 | 
			
		||||
    type: object
 | 
			
		||||
    required:
 | 
			
		||||
      - entry
 | 
			
		||||
    properties:
 | 
			
		||||
      entry:
 | 
			
		||||
        $ref: '#/definitions/EventType'
 | 
			
		||||
  EventType:
 | 
			
		||||
    type: object
 | 
			
		||||
    required:
 | 
			
		||||
      - id
 | 
			
		||||
      - name
 | 
			
		||||
    properties:
 | 
			
		||||
      id:
 | 
			
		||||
        type: string
 | 
			
		||||
        description: this is the event type id
 | 
			
		||||
      name:
 | 
			
		||||
        type: string
 | 
			
		||||
        description: this is event type name
 | 
			
		||||
      isAutomatic:
 | 
			
		||||
        type: boolean
 | 
			
		||||
        description: Whether events of this type need completing manually or can be completed automatically
 | 
			
		||||
        default: true
 | 
			
		||||
      associationName:
 | 
			
		||||
        type: string
 | 
			
		||||
        description: The association used to determine whether automatic events of this type are complete
 | 
			
		||||
      actionOnAssociatedNode:
 | 
			
		||||
        type: string
 | 
			
		||||
        description: If an association name is set for this event type then it is possible to require an action to be completed on the associated node
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-amps</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
      <version>21.2</version>
 | 
			
		||||
      <version>20.143-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <dependencies>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,6 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-packaging</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
</project>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-packaging</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
SOLR6_TAG=2.0.7-A5
 | 
			
		||||
SOLR6_TAG=2.0.7-A2
 | 
			
		||||
POSTGRES_TAG=14.4
 | 
			
		||||
ACTIVEMQ_TAG=5.17.1-jre11-rockylinux8
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-packaging</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <organization>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <developers>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <developers>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
 
 | 
			
		||||
@@ -52,11 +52,6 @@ This must be unique within the parent category.
 | 
			
		||||
    */
 | 
			
		||||
    private long count;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     The path to this category.
 | 
			
		||||
     */
 | 
			
		||||
    private String path;
 | 
			
		||||
 | 
			
		||||
    public String getId()
 | 
			
		||||
    {
 | 
			
		||||
        return this.id;
 | 
			
		||||
@@ -107,14 +102,6 @@ This must be unique within the parent category.
 | 
			
		||||
        this.count = count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getPath() {
 | 
			
		||||
        return path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPath(String path) {
 | 
			
		||||
        this.path = path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o)
 | 
			
		||||
    {
 | 
			
		||||
@@ -139,7 +126,6 @@ This must be unique within the parent category.
 | 
			
		||||
                ", parentId='" + parentId + '\'' +
 | 
			
		||||
                ", hasChildren=" + hasChildren +
 | 
			
		||||
                ", count=" + count +
 | 
			
		||||
                ", path=" + path +
 | 
			
		||||
                '}';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,223 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Remote API
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2023 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.categories;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomName;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CREATED;
 | 
			
		||||
import static org.springframework.http.HttpStatus.OK;
 | 
			
		||||
import static org.testng.Assert.assertTrue;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.rest.model.RestCategoryLinkBodyModel;
 | 
			
		||||
import org.alfresco.rest.model.RestCategoryModel;
 | 
			
		||||
import org.alfresco.rest.model.RestCategoryModelsCollection;
 | 
			
		||||
import org.alfresco.utility.Utility;
 | 
			
		||||
import org.alfresco.utility.model.FileModel;
 | 
			
		||||
import org.alfresco.utility.model.FolderModel;
 | 
			
		||||
import org.alfresco.utility.model.SiteModel;
 | 
			
		||||
import org.alfresco.utility.model.TestGroup;
 | 
			
		||||
import org.testng.annotations.BeforeClass;
 | 
			
		||||
import org.testng.annotations.Test;
 | 
			
		||||
 | 
			
		||||
public class CategoriesPathTests extends CategoriesRestTest
 | 
			
		||||
{
 | 
			
		||||
    private FileModel file;
 | 
			
		||||
    private RestCategoryModel category;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass(alwaysRun = true)
 | 
			
		||||
    @Override
 | 
			
		||||
    public void dataPreparation() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create user and site");
 | 
			
		||||
        user = dataUser.createRandomTestUser();
 | 
			
		||||
        SiteModel site = dataSite.usingUser(user).createPublicRandomSite();
 | 
			
		||||
 | 
			
		||||
        STEP("Create a folder, file in it and a category");
 | 
			
		||||
        FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
 | 
			
		||||
        file = dataContent.usingUser(user).usingResource(folder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        category = prepareCategoryUnderRoot();
 | 
			
		||||
 | 
			
		||||
        STEP("Wait for indexing to complete");
 | 
			
		||||
        Utility.sleep(1000, 60000, () -> restClient.authenticateUser(user)
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .usingCategory(category)
 | 
			
		||||
                .include(INCLUDE_PATH_PARAM)
 | 
			
		||||
                .getCategory()
 | 
			
		||||
                .assertThat()
 | 
			
		||||
                .field(FIELD_PATH)
 | 
			
		||||
                .isNotNull());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Verify path for a category got by ID.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API })
 | 
			
		||||
    public void testGetCategoryById_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Get category and verify if path is a general path for categories");
 | 
			
		||||
        final RestCategoryModel actualCategory = restClient.authenticateUser(user)
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .usingCategory(category)
 | 
			
		||||
                .include(INCLUDE_PATH_PARAM)
 | 
			
		||||
                .getCategory();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        actualCategory.assertThat().field(FIELD_ID).is(category.getId());
 | 
			
		||||
        actualCategory.assertThat().field(FIELD_PATH).is("/categories/General");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Verify path for category.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API })
 | 
			
		||||
    public void testGetCategories_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Get few categories and verify its paths");
 | 
			
		||||
        final RestCategoryModel parentCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
 | 
			
		||||
        final RestCategoryModelsCollection actualCategories = restClient.authenticateUser(user)
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .usingCategory(parentCategory)
 | 
			
		||||
                .include(INCLUDE_PATH_PARAM)
 | 
			
		||||
                .getCategoryChildren();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        assertTrue(actualCategories.getEntries().stream()
 | 
			
		||||
                .map(RestCategoryModel::onModel)
 | 
			
		||||
                .allMatch(cat -> cat.getPath().equals("/categories/General")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Verify path for child category.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API })
 | 
			
		||||
    public void testGetChildCategory_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create parent and child categories");
 | 
			
		||||
        final RestCategoryModel parentCategory = prepareCategoryUnderRoot();
 | 
			
		||||
        final RestCategoryModel childCategory = prepareCategoryUnder(parentCategory);
 | 
			
		||||
 | 
			
		||||
        STEP("Verify path for created child categories");
 | 
			
		||||
        final RestCategoryModelsCollection actualCategories = restClient.authenticateUser(user)
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .usingCategory(parentCategory)
 | 
			
		||||
                .include(INCLUDE_PATH_PARAM)
 | 
			
		||||
                .getCategoryChildren();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        actualCategories.getEntries().stream()
 | 
			
		||||
                .map(RestCategoryModel::onModel)
 | 
			
		||||
                .forEach(cat -> cat.assertThat().field(FIELD_PATH).is("/categories/General/" + parentCategory.getName()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create category and verify that it has a path.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API })
 | 
			
		||||
    public void testCreateCategory_includingPath()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create a category under root and verify if path is a general path for categories");
 | 
			
		||||
        final String categoryName = getRandomName("Category");
 | 
			
		||||
        final RestCategoryModel rootCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
 | 
			
		||||
        final RestCategoryModel aCategory = createCategoryModelWithName(categoryName);
 | 
			
		||||
        final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .include(INCLUDE_PATH_PARAM)
 | 
			
		||||
                .usingCategory(rootCategory)
 | 
			
		||||
                .createSingleCategory(aCategory);
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(CREATED);
 | 
			
		||||
        createdCategory.assertThat().field(FIELD_NAME).is(categoryName);
 | 
			
		||||
        createdCategory.assertThat().field(FIELD_PATH).is("/categories/General");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update category and verify that it has a path.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API })
 | 
			
		||||
    public void testUpdateCategory_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Update linked category and verify if path is a general path for categories");
 | 
			
		||||
        final String categoryNewName = getRandomName("NewCategoryName");
 | 
			
		||||
        final RestCategoryModel fixedCategoryModel = createCategoryModelWithName(categoryNewName);
 | 
			
		||||
        final RestCategoryModel updatedCategory = restClient.authenticateUser(dataUser.getAdminUser())
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .usingCategory(category)
 | 
			
		||||
                .include(INCLUDE_PATH_PARAM)
 | 
			
		||||
                .updateCategory(fixedCategoryModel);
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        updatedCategory.assertThat().field(FIELD_ID).is(category.getId());
 | 
			
		||||
        updatedCategory.assertThat().field(FIELD_PATH).is("/categories/General");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Link node to categories and verify that they have path.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API })
 | 
			
		||||
    public void testLinkNodeToCategories_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Link node to categories and verify if path is a general path");
 | 
			
		||||
        final RestCategoryLinkBodyModel categoryLinkModel = createCategoryLinkModelWithId(category.getId());
 | 
			
		||||
        final RestCategoryModel linkedCategory = restClient.authenticateUser(dataUser.getAdminUser())
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .usingNode(file)
 | 
			
		||||
                .include(INCLUDE_PATH_PARAM)
 | 
			
		||||
                .linkToCategory(categoryLinkModel);
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(CREATED);
 | 
			
		||||
        linkedCategory.assertThat().field(FIELD_ID).is(category.getId());
 | 
			
		||||
        linkedCategory.assertThat().field(FIELD_PATH).is("/categories/General");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * List categories for given node and verify that they have a path.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API })
 | 
			
		||||
    public void testListCategoriesForNode_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Link file to category");
 | 
			
		||||
        final RestCategoryLinkBodyModel categoryLink = createCategoryLinkModelWithId(category.getId());
 | 
			
		||||
        final RestCategoryModel linkedCategory = restClient.authenticateUser(dataUser.getAdminUser())
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .usingNode(file)
 | 
			
		||||
                .include(INCLUDE_PATH_PARAM)
 | 
			
		||||
                .linkToCategory(categoryLink);
 | 
			
		||||
 | 
			
		||||
        STEP("Get linked category and verify if path is a general path");
 | 
			
		||||
        final RestCategoryModelsCollection linkedCategories = restClient.authenticateUser(dataUser.getAdminUser())
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .usingNode(file)
 | 
			
		||||
                .include(INCLUDE_PATH_PARAM)
 | 
			
		||||
                .getLinkedCategories();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        linkedCategories.assertThat().entriesListCountIs(1);
 | 
			
		||||
        linkedCategories.getEntries().get(0).onModel().assertThat().field(FIELD_ID).is(category.getId());
 | 
			
		||||
        linkedCategories.getEntries().get(0).onModel().assertThat().field(FIELD_PATH).is("/categories/General");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -46,7 +46,6 @@ import org.testng.annotations.BeforeClass;
 | 
			
		||||
abstract class CategoriesRestTest extends RestTest
 | 
			
		||||
{
 | 
			
		||||
    protected static final String INCLUDE_COUNT_PARAM = "count";
 | 
			
		||||
    protected static final String INCLUDE_PATH_PARAM = "path";
 | 
			
		||||
    protected static final String ROOT_CATEGORY_ID = "-root-";
 | 
			
		||||
    protected static final String CATEGORY_NAME_PREFIX = "CategoryName";
 | 
			
		||||
    protected static final String FIELD_NAME = "name";
 | 
			
		||||
@@ -54,7 +53,6 @@ abstract class CategoriesRestTest extends RestTest
 | 
			
		||||
    protected static final String FIELD_PARENT_ID = "parentId";
 | 
			
		||||
    protected static final String FIELD_HAS_CHILDREN = "hasChildren";
 | 
			
		||||
    protected static final String FIELD_COUNT = "count";
 | 
			
		||||
    protected static final String FIELD_PATH = "path";
 | 
			
		||||
 | 
			
		||||
    protected UserModel user;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify admin user gets tag using REST API and status code is OK (200)")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void adminIsAbleToGetTag()
 | 
			
		||||
    public void adminIsAbleToGetTag() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        RestTagModel returnedTag = restClient.authenticateUser(adminUserModel).withCoreAPI().getTag(documentTag);
 | 
			
		||||
        restClient.assertStatusCodeIs(HttpStatus.OK);
 | 
			
		||||
@@ -35,7 +35,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY, description = "Verify user with Manager role gets tag using REST API and status code is OK (200)")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
 | 
			
		||||
    public void userWithManagerRoleIsAbleToGetTag()
 | 
			
		||||
    public void userWithManagerRoleIsAbleToGetTag() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager));
 | 
			
		||||
 | 
			
		||||
@@ -47,7 +47,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify user with Collaborator role gets tag using REST API and status code is OK (200)")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void userWithCollaboratorRoleIsAbleToGetTag()
 | 
			
		||||
    public void userWithCollaboratorRoleIsAbleToGetTag() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteCollaborator));
 | 
			
		||||
        RestTagModel returnedTag = restClient.withCoreAPI().getTag(documentTag);
 | 
			
		||||
@@ -57,7 +57,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify user with Contributor role gets tag using REST API and status code is OK (200)")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void userWithContributorRoleIsAbleToGetTag()
 | 
			
		||||
    public void userWithContributorRoleIsAbleToGetTag() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteContributor));
 | 
			
		||||
        RestTagModel returnedTag = restClient.withCoreAPI().getTag(documentTag);
 | 
			
		||||
@@ -67,7 +67,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
    
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify user with Consumer role gets tag using REST API and status code is OK (200)")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void userWithConsumerRoleIsAbleToGetTag()
 | 
			
		||||
    public void userWithConsumerRoleIsAbleToGetTag() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteConsumer));
 | 
			
		||||
        RestTagModel returnedTag = restClient.withCoreAPI().getTag(documentTag);
 | 
			
		||||
@@ -78,7 +78,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY, description = "Verify Manager user gets status code 401 if authentication call fails")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
 | 
			
		||||
//    @Bug(id="MNT-16904", description = "It fails only on environment with tenants")
 | 
			
		||||
    public void managerIsNotAbleToGetTagIfAuthenticationFails()
 | 
			
		||||
    public void managerIsNotAbleToGetTagIfAuthenticationFails() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        UserModel managerUser = dataUser.usingAdmin().createRandomTestUser();
 | 
			
		||||
        String managerPassword = managerUser.getPassword();
 | 
			
		||||
@@ -92,7 +92,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
 | 
			
		||||
            description = "Verify that if tag id is invalid status code returned is 400")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void invalidTagIdTest()
 | 
			
		||||
    public void invalidTagIdTest() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        String tagId = documentTag.getId();
 | 
			
		||||
        documentTag.setId("random_tag_value");
 | 
			
		||||
@@ -104,7 +104,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
 | 
			
		||||
            executionType = ExecutionType.REGRESSION, description = "Check that properties filter is applied when getting tag using Manager user.")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void checkPropertiesFilterIsApplied()
 | 
			
		||||
    public void checkPropertiesFilterIsApplied() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        RestTagModel returnedTag = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
 | 
			
		||||
                .withParams("properties=id,tag").withCoreAPI().getTag(documentTag);
 | 
			
		||||
@@ -117,7 +117,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
 | 
			
		||||
            executionType = ExecutionType.REGRESSION, description = "Check that Manager user can get tag of a folder.")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void getTagOfAFolder()
 | 
			
		||||
    public void getTagOfAFolder() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        RestTagModel returnedTag = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
 | 
			
		||||
                .withCoreAPI().getTag(folderTag);
 | 
			
		||||
@@ -128,7 +128,7 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
 | 
			
		||||
            executionType = ExecutionType.REGRESSION, description = "Check default error model schema. Use invalid skipCount parameter.")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void checkDefaultErrorModelSchema()
 | 
			
		||||
    public void checkDefaultErrorModelSchema() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
 | 
			
		||||
                .withParams("skipCount=abc").withCoreAPI().getTag(documentTag);
 | 
			
		||||
@@ -140,14 +140,13 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Verify that count field is not present for searched tag when not requested.
 | 
			
		||||
     * Verify that count field is not present for searched tag.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
 | 
			
		||||
    public void testGetTag_notIncludingCount()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create single tag as admin");
 | 
			
		||||
        String tagName = getRandomName(TAG_NAME_PREFIX).toLowerCase();
 | 
			
		||||
        final RestTagModel tagModel = createTagModelWithName(tagName);
 | 
			
		||||
        final RestTagModel tagModel = createTagModelWithName(getRandomName(TAG_NAME_PREFIX).toLowerCase());
 | 
			
		||||
        final RestTagModel createdTag = restClient.authenticateUser(adminUserModel).withCoreAPI().createSingleTag(tagModel);
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(CREATED);
 | 
			
		||||
@@ -156,26 +155,8 @@ public class GetTagTests extends TagsDataPrep
 | 
			
		||||
        final RestTagModel searchedTag = restClient.withCoreAPI().getTag(createdTag);
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        RestTagModel expected = RestTagModel.builder().id(createdTag.getId()).tag(tagName).create();
 | 
			
		||||
        searchedTag.assertThat().isEqualTo(expected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check that the count field can be included.
 | 
			
		||||
     */
 | 
			
		||||
    @Test (groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void testGetTag_includeCount()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create unused tag as admin");
 | 
			
		||||
        String tagName = getRandomName(TAG_NAME_PREFIX).toLowerCase();
 | 
			
		||||
        RestTagModel tagModel = createTagModelWithName(tagName);
 | 
			
		||||
        RestTagModel createdTag = restClient.authenticateUser(adminUserModel).withCoreAPI().createSingleTag(tagModel);
 | 
			
		||||
 | 
			
		||||
        STEP("Get a single tag with the count field");
 | 
			
		||||
        RestTagModel searchedTag = restClient.withCoreAPI().include("count").getTag(createdTag);
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        RestTagModel expected = RestTagModel.builder().id(createdTag.getId()).tag(tagName).count(0).create();
 | 
			
		||||
        searchedTag.assertThat().isEqualTo(expected);
 | 
			
		||||
        searchedTag.assertThat().field(FIELD_TAG).is(tagModel.getTag())
 | 
			
		||||
                .assertThat().field(FIELD_ID).isNotEmpty()
 | 
			
		||||
                .assertThat().field(FIELD_COUNT).isNull();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ package org.alfresco.rest.tags;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomName;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
 | 
			
		||||
import static org.springframework.http.HttpStatus.OK;
 | 
			
		||||
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
@@ -17,11 +16,6 @@ import org.alfresco.utility.testrail.annotation.TestRail;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.testng.annotations.Test;
 | 
			
		||||
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.stream.IntStream;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
 | 
			
		||||
@Test(groups = {TestGroup.REQUIRE_SOLR})
 | 
			
		||||
public class GetTagsTests extends TagsDataPrep
 | 
			
		||||
{
 | 
			
		||||
@@ -78,138 +72,6 @@ public class GetTagsTests extends TagsDataPrep
 | 
			
		||||
            .and().entriesListContains("tag", documentTagValue2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Include count in the query parameters and ensure count is as expected for returned tags.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void testGetTags_withIncludeCount()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Get tags including count filter and ensure count is as expected for returned tags");
 | 
			
		||||
        returnedCollection = restClient.authenticateUser(adminUserModel)
 | 
			
		||||
                .withParams("include=count")
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .getTags();
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
 | 
			
		||||
        returnedCollection.getEntries().stream()
 | 
			
		||||
                .filter(e -> e.onModel().getTag().equals(folderTagValue) || e.onModel().getTag().equals(documentTagValue))
 | 
			
		||||
                .forEach(e -> e.onModel().assertThat().field("count").is(2));
 | 
			
		||||
 | 
			
		||||
        returnedCollection.getEntries().stream()
 | 
			
		||||
                .filter(e -> e.onModel().getTag().equals(documentTagValue2))
 | 
			
		||||
                .forEach(e -> e.onModel().assertThat().field("count").is(1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get tags and order results by count. Default sort order should be ascending
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void testGetTags_withOrderByCountDefaultOrderShouldBeAsc()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Get tags and order results by count. Default sort order should be ascending");
 | 
			
		||||
        returnedCollection = restClient.authenticateUser(adminUserModel)
 | 
			
		||||
                .withParams("include=count&orderBy=count")
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .getTags();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        returnedCollection.assertThat().entriesListIsSortedAscBy("count");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get tags and order results by count in ascending order
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void testGetTags_withOrderByCountAsc()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Get tags and order results by count in ascending order");
 | 
			
		||||
        returnedCollection = restClient.authenticateUser(adminUserModel)
 | 
			
		||||
                .withParams("include=count&orderBy=count ASC")
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .getTags();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        returnedCollection.assertThat().entriesListIsSortedAscBy("count");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get tags and order results by count in descending order
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void testGetTags_withOrderByCountDesc()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Get tags and order results by count in descending order");
 | 
			
		||||
        returnedCollection = restClient.authenticateUser(adminUserModel)
 | 
			
		||||
                .withParams("include=count&orderBy=count DESC")
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .getTags();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        returnedCollection.assertThat().entriesListIsSortedDescBy("count");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get tags and order results by tag name. Default sort order should be ascending
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void testGetTags_withOrderByTagDefaultOrderShouldBeAsc()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Get tags and order results by tag name. Default sort order should be ascending");
 | 
			
		||||
        returnedCollection = restClient.authenticateUser(adminUserModel)
 | 
			
		||||
                .withParams("orderBy=tag")
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .getTags();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        returnedCollection.assertThat().entriesListIsSortedAscBy("tag");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get tags and order results by tag name in ascending order
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void testGetTags_withOrderByTagAsc()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Get tags and order results by tag name in ascending order");
 | 
			
		||||
        returnedCollection = restClient.authenticateUser(adminUserModel)
 | 
			
		||||
                .withParams("orderBy=tag ASC")
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .getTags();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        returnedCollection.assertThat().entriesListIsSortedAscBy("tag");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get tags and order results by tag name in descending order
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void testGetTags_withOrderByTagDesc()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Get tags and order results by tag name in descending order");
 | 
			
		||||
        returnedCollection = restClient.authenticateUser(adminUserModel)
 | 
			
		||||
                .withParams("orderBy=tag DESC")
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .getTags();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(OK);
 | 
			
		||||
        returnedCollection.assertThat().entriesListIsSortedDescBy("tag");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Ensure that we get a 400 error when we request to order by count without also including the tag count.
 | 
			
		||||
     */
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
 | 
			
		||||
    public void testGetTags_orderByCountWithoutIncludeCount()
 | 
			
		||||
    {
 | 
			
		||||
        restClient.authenticateUser(adminUserModel)
 | 
			
		||||
                .withParams("orderBy=count")
 | 
			
		||||
                .withCoreAPI()
 | 
			
		||||
                .getTags();
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(BAD_REQUEST);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY, description = "Failed authentication get tags call returns status code 401 with Manager role")
 | 
			
		||||
    @Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
 | 
			
		||||
//    @Bug(id="MNT-16904", description = "It fails only on environment with tenants")
 | 
			
		||||
@@ -331,7 +193,8 @@ public class GetTagsTests extends TagsDataPrep
 | 
			
		||||
                .getPagination().assertThat().field("maxItems").is(100)
 | 
			
		||||
                .and().field("hasMoreItems").is("false")
 | 
			
		||||
                .and().field("count").is("0")
 | 
			
		||||
                .and().field("skipCount").is(20000);
 | 
			
		||||
                .and().field("skipCount").is(20000)
 | 
			
		||||
                .and().field("totalItems").is(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,7 @@ public class TagsDataPrep extends RestTest
 | 
			
		||||
    protected static ListUserWithRoles usersWithRoles;
 | 
			
		||||
    protected static SiteModel siteModel;
 | 
			
		||||
    protected static FileModel document;
 | 
			
		||||
    protected static FolderModel folder, folder2;
 | 
			
		||||
    protected static RestTagModelsCollection folder2tags;
 | 
			
		||||
    protected static FolderModel folder;
 | 
			
		||||
    protected static String documentTagValue, documentTagValue2, folderTagValue;
 | 
			
		||||
    protected static RestTagModel documentTag, documentTag2, folderTag, orphanTag, returnedModel;
 | 
			
		||||
    protected static RestTagModelsCollection returnedCollection;
 | 
			
		||||
@@ -39,7 +38,6 @@ public class TagsDataPrep extends RestTest
 | 
			
		||||
        usersWithRoles = dataUser.usingAdmin().addUsersWithRolesToSite(siteModel, UserRole.SiteManager, UserRole.SiteCollaborator, UserRole.SiteConsumer, UserRole.SiteContributor);
 | 
			
		||||
        document = dataContent.usingUser(adminUserModel).usingSite(siteModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        folder = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();
 | 
			
		||||
        folder2 = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();
 | 
			
		||||
 | 
			
		||||
        documentTagValue = RandomData.getRandomName("tag").toLowerCase();
 | 
			
		||||
        documentTagValue2 = RandomData.getRandomName("tag").toLowerCase();
 | 
			
		||||
@@ -50,7 +48,6 @@ public class TagsDataPrep extends RestTest
 | 
			
		||||
        documentTag2 = restClient.withCoreAPI().usingResource(document).addTag(documentTagValue2);
 | 
			
		||||
        folderTag = restClient.withCoreAPI().usingResource(folder).addTag(folderTagValue);
 | 
			
		||||
        orphanTag = restClient.withCoreAPI().createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("orphan-tag").toLowerCase()).create());
 | 
			
		||||
        folder2tags = restClient.withCoreAPI().usingResource(folder2).addTags(folderTagValue, documentTagValue);
 | 
			
		||||
 | 
			
		||||
        // Allow indexing to complete.
 | 
			
		||||
        Utility.sleep(500, 60000, () ->
 | 
			
		||||
 
 | 
			
		||||
@@ -305,24 +305,7 @@ public class UpdateTagTests extends TagsDataPrep
 | 
			
		||||
        returnedModel = restClient.authenticateUser(adminUserModel).withCoreAPI().usingTag(orphanTag).update(newTagName);
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(HttpStatus.OK);
 | 
			
		||||
        RestTagModel expected = RestTagModel.builder().id(orphanTag.getId()).tag(newTagName).create();
 | 
			
		||||
        returnedModel.assertThat().isEqualTo(expected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test (groups = { TestGroup.REST_API, TestGroup.TAGS })
 | 
			
		||||
    public void canUpdateTagAndGetCount()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create an orphaned tag");
 | 
			
		||||
        String tagName = RandomData.getRandomName("tag").toLowerCase();
 | 
			
		||||
        RestTagModel createdTag = RestTagModel.builder().tag(tagName).create();
 | 
			
		||||
        RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().createSingleTag(createdTag);
 | 
			
		||||
 | 
			
		||||
        STEP("Update tag and request the count field");
 | 
			
		||||
        String newTagName = RandomData.getRandomName("new").toLowerCase();
 | 
			
		||||
        returnedModel = restClient.authenticateUser(adminUserModel).withCoreAPI().include("count").usingTag(tag).update(newTagName);
 | 
			
		||||
 | 
			
		||||
        restClient.assertStatusCodeIs(HttpStatus.OK);
 | 
			
		||||
        RestTagModel expected = RestTagModel.builder().id(tag.getId()).tag(newTagName).count(0).create();
 | 
			
		||||
        returnedModel.assertThat().isEqualTo(expected);
 | 
			
		||||
        returnedModel.assertThat().field("tag").is(newTagName);
 | 
			
		||||
        returnedModel.assertThat().field("id").isNotNull();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-tests</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <developers>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo-packaging</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								packaging/war/src/main/webapp/images/logo/adw_logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packaging/war/src/main/webapp/images/logo/adw_logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.3 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								packaging/war/src/main/webapp/images/logo/hyland_logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packaging/war/src/main/webapp/images/logo/hyland_logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 7.0 KiB  | 
							
								
								
									
										20
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								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>21.2</version>
 | 
			
		||||
    <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    <packaging>pom</packaging>
 | 
			
		||||
    <name>Alfresco Community Repo Parent</name>
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
    <properties>
 | 
			
		||||
        <acs.version.major>7</acs.version.major>
 | 
			
		||||
        <acs.version.minor>4</acs.version.minor>
 | 
			
		||||
        <acs.version.revision>1</acs.version.revision>
 | 
			
		||||
        <acs.version.revision>0</acs.version.revision>
 | 
			
		||||
        <acs.version.label />
 | 
			
		||||
        <amp.min.version>${acs.version.major}.0.0</amp.min.version>
 | 
			
		||||
 | 
			
		||||
@@ -52,12 +52,12 @@
 | 
			
		||||
        <dependency.alfresco-messaging-repo.version>1.2.20</dependency.alfresco-messaging-repo.version>
 | 
			
		||||
        <dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version>
 | 
			
		||||
        <dependency.activiti.version>5.23.0</dependency.activiti.version>
 | 
			
		||||
        <dependency.alfresco-transform-service.version>2.1.0</dependency.alfresco-transform-service.version>
 | 
			
		||||
        <dependency.alfresco-transform-core.version>3.1.0</dependency.alfresco-transform-core.version>
 | 
			
		||||
        <dependency.alfresco-transform-service.version>2.1.0-A6</dependency.alfresco-transform-service.version>
 | 
			
		||||
        <dependency.alfresco-transform-core.version>3.1.0-A8</dependency.alfresco-transform-core.version>
 | 
			
		||||
        <dependency.alfresco-greenmail.version>6.5</dependency.alfresco-greenmail.version>
 | 
			
		||||
        <dependency.acs-event-model.version>0.0.18</dependency.acs-event-model.version>
 | 
			
		||||
 | 
			
		||||
        <dependency.spring.version>5.3.27</dependency.spring.version>
 | 
			
		||||
        <dependency.spring.version>5.3.26</dependency.spring.version>
 | 
			
		||||
        <dependency.antlr.version>3.5.3</dependency.antlr.version>
 | 
			
		||||
        <dependency.jackson.version>2.15.0-rc1</dependency.jackson.version>
 | 
			
		||||
        <dependency.cxf.version>3.5.5</dependency.cxf.version>
 | 
			
		||||
@@ -79,7 +79,7 @@
 | 
			
		||||
        <dependency.gytheio.version>0.18</dependency.gytheio.version>
 | 
			
		||||
        <dependency.groovy.version>3.0.16</dependency.groovy.version>
 | 
			
		||||
        <dependency.tika.version>2.4.1</dependency.tika.version>
 | 
			
		||||
        <dependency.spring-security.version>5.8.3</dependency.spring-security.version>
 | 
			
		||||
        <dependency.spring-security.version>5.8.2</dependency.spring-security.version>
 | 
			
		||||
        <dependency.truezip.version>7.7.10</dependency.truezip.version>
 | 
			
		||||
        <dependency.poi.version>5.2.2</dependency.poi.version>
 | 
			
		||||
        <dependency.poi-ooxml-lite.version>5.2.3</dependency.poi-ooxml-lite.version>
 | 
			
		||||
@@ -111,8 +111,8 @@
 | 
			
		||||
        <dependency.json-smart.version>2.4.10</dependency.json-smart.version>
 | 
			
		||||
        <dependency.jakarta-rpc-api.version>1.1.4</dependency.jakarta-rpc-api.version>
 | 
			
		||||
 | 
			
		||||
        <alfresco.googledrive.version>3.4.0</alfresco.googledrive.version>
 | 
			
		||||
        <alfresco.aos-module.version>1.6.0</alfresco.aos-module.version>
 | 
			
		||||
        <alfresco.googledrive.version>3.4.0-M1</alfresco.googledrive.version>
 | 
			
		||||
        <alfresco.aos-module.version>1.6.0-A4</alfresco.aos-module.version>
 | 
			
		||||
        <alfresco.api-explorer.version>7.3.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
 | 
			
		||||
 | 
			
		||||
        <alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version>
 | 
			
		||||
@@ -150,7 +150,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>21.2</tag>
 | 
			
		||||
        <tag>HEAD</tag>
 | 
			
		||||
    </scm>
 | 
			
		||||
 | 
			
		||||
    <distributionManagement>
 | 
			
		||||
@@ -936,7 +936,7 @@
 | 
			
		||||
                <plugin>
 | 
			
		||||
                    <groupId>io.fabric8</groupId>
 | 
			
		||||
                    <artifactId>docker-maven-plugin</artifactId>
 | 
			
		||||
                    <version>0.42.1</version>
 | 
			
		||||
                    <version>0.42.0</version>
 | 
			
		||||
                </plugin>
 | 
			
		||||
                <plugin>
 | 
			
		||||
                    <artifactId>maven-surefire-plugin</artifactId>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
 
 | 
			
		||||
@@ -38,10 +38,10 @@ import org.alfresco.service.cmr.repository.StoreRef;
 | 
			
		||||
public interface Tags
 | 
			
		||||
{
 | 
			
		||||
    List<Tag> addTags(String nodeId, List<Tag> tags, Parameters parameters);
 | 
			
		||||
    Tag getTag(StoreRef storeRef, String tagId, Parameters parameters);
 | 
			
		||||
    Tag getTag(StoreRef storeRef, String tagId);
 | 
			
		||||
    void deleteTag(String nodeId, String tagId);
 | 
			
		||||
    CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params);
 | 
			
		||||
    Tag changeTag(StoreRef storeRef, String tagId, Tag tag, Parameters parameters);
 | 
			
		||||
    Tag changeTag(StoreRef storeRef, String tagId, Tag tag);
 | 
			
		||||
    CollectionWithPagingInfo<Tag> getTags(String nodeId, Parameters params);
 | 
			
		||||
 | 
			
		||||
    @Experimental
 | 
			
		||||
 
 | 
			
		||||
@@ -111,11 +111,6 @@ public class CategoriesImpl implements Categories
 | 
			
		||||
            category.setCount(categoriesCount.getOrDefault(category.getId(), 0));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH))
 | 
			
		||||
        {
 | 
			
		||||
            category.setPath(getCategoryPath(category));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return category;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -133,10 +128,6 @@ public class CategoriesImpl implements Categories
 | 
			
		||||
                    {
 | 
			
		||||
                        category.setCount(0);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH))
 | 
			
		||||
                    {
 | 
			
		||||
                        category.setPath(getCategoryPath(category));
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
@@ -145,18 +136,11 @@ public class CategoriesImpl implements Categories
 | 
			
		||||
    public List<Category> getCategoryChildren(final StoreRef storeRef, final String parentCategoryId, final Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        final NodeRef parentNodeRef = getCategoryNodeRef(storeRef, parentCategoryId);
 | 
			
		||||
        final List<Category> categories = nodeService.getChildAssocs(parentNodeRef)
 | 
			
		||||
                .stream()
 | 
			
		||||
                .filter(ca -> ContentModel.ASSOC_SUBCATEGORIES.equals(ca.getTypeQName()))
 | 
			
		||||
                .map(ChildAssociationRef::getChildRef)
 | 
			
		||||
                .map(this::mapToCategory)
 | 
			
		||||
                .peek(category -> {
 | 
			
		||||
                    if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH))
 | 
			
		||||
                    {
 | 
			
		||||
                        category.setPath(getCategoryPath(category));
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
        final List<Category> categories = nodeService.getChildAssocs(parentNodeRef).stream()
 | 
			
		||||
            .filter(ca -> ContentModel.ASSOC_SUBCATEGORIES.equals(ca.getTypeQName()))
 | 
			
		||||
            .map(ChildAssociationRef::getChildRef)
 | 
			
		||||
            .map(this::mapToCategory)
 | 
			
		||||
            .collect(Collectors.toList());
 | 
			
		||||
 | 
			
		||||
        if (parameters.getInclude().contains(INCLUDE_COUNT_PARAM))
 | 
			
		||||
        {
 | 
			
		||||
@@ -186,11 +170,6 @@ public class CategoriesImpl implements Categories
 | 
			
		||||
            category.setCount(categoriesCount.getOrDefault(category.getId(), 0));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH))
 | 
			
		||||
        {
 | 
			
		||||
            category.setPath(getCategoryPath(category));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return category;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -221,16 +200,7 @@ public class CategoriesImpl implements Categories
 | 
			
		||||
        }
 | 
			
		||||
        final Collection<NodeRef> actualCategories = DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, currentCategories);
 | 
			
		||||
 | 
			
		||||
        return actualCategories
 | 
			
		||||
                .stream()
 | 
			
		||||
                .map(this::mapToCategory)
 | 
			
		||||
                .peek(category -> {
 | 
			
		||||
                    if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH))
 | 
			
		||||
                    {
 | 
			
		||||
                        category.setPath(getCategoryPath(category));
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
        return actualCategories.stream().map(this::mapToCategory).collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -260,16 +230,7 @@ public class CategoriesImpl implements Categories
 | 
			
		||||
 | 
			
		||||
        linkNodeToCategories(contentNodeRef, categoryNodeRefs);
 | 
			
		||||
 | 
			
		||||
        return categoryNodeRefs
 | 
			
		||||
                .stream()
 | 
			
		||||
                .map(this::mapToCategory)
 | 
			
		||||
                .peek(category -> {
 | 
			
		||||
                    if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH))
 | 
			
		||||
                    {
 | 
			
		||||
                        category.setPath(getCategoryPath(category));
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
        return categoryNodeRefs.stream().map(this::mapToCategory).collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -514,16 +475,4 @@ public class CategoriesImpl implements Categories
 | 
			
		||||
            .stream()
 | 
			
		||||
            .collect(Collectors.toMap(pair -> pair.getFirst().toString().replace(idPrefix, StringUtils.EMPTY), Pair::getSecond));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get path for a given category in human-readable form.
 | 
			
		||||
     *
 | 
			
		||||
     * @param category Category to provide path for.
 | 
			
		||||
     * @return Path for a category in human-readable form.
 | 
			
		||||
     */
 | 
			
		||||
    private String getCategoryPath(final Category category)
 | 
			
		||||
    {
 | 
			
		||||
        final NodeRef categoryNodeRef = nodes.getNode(category.getId()).getNodeRef();
 | 
			
		||||
        return nodeService.getPath(categoryNodeRef).toDisplayPath(nodeService, permissionService);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,13 +30,11 @@ import static java.util.stream.Collectors.toList;
 | 
			
		||||
import static org.alfresco.rest.antlr.WhereClauseParser.EQUALS;
 | 
			
		||||
import static org.alfresco.rest.antlr.WhereClauseParser.IN;
 | 
			
		||||
import static org.alfresco.rest.antlr.WhereClauseParser.MATCHES;
 | 
			
		||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
 | 
			
		||||
import static org.alfresco.service.cmr.tagging.TaggingService.TAG_ROOT_NODE_REF;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Comparator;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
@@ -45,8 +43,6 @@ import java.util.Objects;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.query.ListBackedPagingResults;
 | 
			
		||||
import org.alfresco.query.PagingResults;
 | 
			
		||||
import org.alfresco.repo.tagging.NonExistentTagException;
 | 
			
		||||
import org.alfresco.repo.tagging.TagExistsException;
 | 
			
		||||
@@ -63,12 +59,10 @@ import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationE
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.Paging;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.SortColumn;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.where.Query;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.where.QueryHelper;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.where.QueryImpl;
 | 
			
		||||
import org.alfresco.service.Experimental;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.StoreRef;
 | 
			
		||||
@@ -86,40 +80,40 @@ import org.apache.commons.collections.CollectionUtils;
 | 
			
		||||
 */
 | 
			
		||||
public class TagsImpl implements Tags
 | 
			
		||||
{
 | 
			
		||||
    public static final String PARAM_INCLUDE_COUNT = "count";
 | 
			
		||||
    private static final String PARAM_WHERE_TAG = "tag";
 | 
			
		||||
    static final String NOT_A_VALID_TAG = "An invalid parameter has been supplied";
 | 
			
		||||
    static final String NO_PERMISSION_TO_MANAGE_A_TAG = "Current user does not have permission to manage a tag";
 | 
			
		||||
	private static final String PARAM_INCLUDE_COUNT = "count";
 | 
			
		||||
	private static final String PARAM_WHERE_TAG = "tag";
 | 
			
		||||
	static final String NOT_A_VALID_TAG = "An invalid parameter has been supplied";
 | 
			
		||||
	static final String NO_PERMISSION_TO_MANAGE_A_TAG = "Current user does not have permission to manage a tag";
 | 
			
		||||
 | 
			
		||||
    private Nodes nodes;
 | 
			
		||||
    private NodeService nodeService;
 | 
			
		||||
    private TaggingService taggingService;
 | 
			
		||||
    private TypeConstraint typeConstraint;
 | 
			
		||||
    private AuthorityService authorityService;
 | 
			
		||||
	private NodeService nodeService;
 | 
			
		||||
	private TaggingService taggingService;
 | 
			
		||||
	private TypeConstraint typeConstraint;
 | 
			
		||||
	private AuthorityService authorityService;
 | 
			
		||||
 | 
			
		||||
    public void setTypeConstraint(TypeConstraint typeConstraint)
 | 
			
		||||
    {
 | 
			
		||||
        this.typeConstraint = typeConstraint;
 | 
			
		||||
    }
 | 
			
		||||
	public void setTypeConstraint(TypeConstraint typeConstraint)
 | 
			
		||||
	{
 | 
			
		||||
		this.typeConstraint = typeConstraint;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    public void setNodes(Nodes nodes)
 | 
			
		||||
	public void setNodes(Nodes nodes)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodes = nodes;
 | 
			
		||||
    }
 | 
			
		||||
    public void setNodeService(NodeService nodeService)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodeService = nodeService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
		this.nodes = nodes;
 | 
			
		||||
	}
 | 
			
		||||
	public void setNodeService(NodeService nodeService)
 | 
			
		||||
	{
 | 
			
		||||
		this.nodeService = nodeService;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
    public void setTaggingService(TaggingService taggingService)
 | 
			
		||||
    {
 | 
			
		||||
        this.taggingService = taggingService;
 | 
			
		||||
    }
 | 
			
		||||
		this.taggingService = taggingService;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    public void setAuthorityService(AuthorityService authorityService)
 | 
			
		||||
    {
 | 
			
		||||
        this.authorityService = authorityService;
 | 
			
		||||
    }
 | 
			
		||||
	public void setAuthorityService(AuthorityService authorityService)
 | 
			
		||||
	{
 | 
			
		||||
		this.authorityService = authorityService;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    public List<Tag> addTags(String nodeId, final List<Tag> tags, final Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
@@ -134,15 +128,14 @@ public class TagsImpl implements Tags
 | 
			
		||||
        {
 | 
			
		||||
            List<Pair<String, NodeRef>> tagNodeRefs = taggingService.addTags(nodeRef, tagValues);
 | 
			
		||||
            List<Tag> ret = new ArrayList<>(tags.size());
 | 
			
		||||
            List<Pair<String, Integer>> tagsCountPairList = taggingService.findTaggedNodesAndCountByTagName(nodeRef.getStoreRef());
 | 
			
		||||
            Map<String, Long> tagsCountMap = tagsCountPairList.stream().collect(Collectors.toMap(Pair::getFirst, pair -> Long.valueOf(pair.getSecond())));
 | 
			
		||||
			List<Pair<String, Integer>> tagsCountPairList = taggingService.findTaggedNodesAndCountByTagName(nodeRef.getStoreRef());
 | 
			
		||||
			Map<String, Integer> tagsCountMap = tagsCountPairList.stream().collect(Collectors.toMap(Pair::getFirst,Pair::getSecond));
 | 
			
		||||
            for (Pair<String, NodeRef> pair : tagNodeRefs)
 | 
			
		||||
            {
 | 
			
		||||
                Tag createdTag = new Tag(pair.getSecond(), pair.getFirst());
 | 
			
		||||
                if (parameters.getInclude().contains(PARAM_INCLUDE_COUNT))
 | 
			
		||||
                {
 | 
			
		||||
                    createdTag.setCount(Optional.ofNullable(tagsCountMap.get(createdTag.getTag())).orElse(0L) + 1);
 | 
			
		||||
                }
 | 
			
		||||
				Tag createdTag = new Tag(pair.getSecond(), pair.getFirst());
 | 
			
		||||
				if (parameters.getInclude().contains(PARAM_INCLUDE_COUNT)) {
 | 
			
		||||
					createdTag.setCount(Optional.ofNullable(tagsCountMap.get(createdTag.getTag())).orElse(0) + 1);
 | 
			
		||||
				}
 | 
			
		||||
                ret.add(createdTag);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
@@ -152,112 +145,105 @@ public class TagsImpl implements Tags
 | 
			
		||||
            throw new InvalidArgumentException(e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
    public void deleteTag(String nodeId, String tagId)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef nodeRef = nodes.validateNode(nodeId);
 | 
			
		||||
        getTag(STORE_REF_WORKSPACE_SPACESSTORE, tagId, null);
 | 
			
		||||
        NodeRef existingTagNodeRef = validateTag(tagId);
 | 
			
		||||
        String tagValue = taggingService.getTagName(existingTagNodeRef);
 | 
			
		||||
        taggingService.removeTag(nodeRef, tagValue);
 | 
			
		||||
		NodeRef nodeRef = nodes.validateNode(nodeId);
 | 
			
		||||
		getTag(tagId);
 | 
			
		||||
    	NodeRef existingTagNodeRef = validateTag(tagId);
 | 
			
		||||
    	String tagValue = taggingService.getTagName(existingTagNodeRef);
 | 
			
		||||
    	taggingService.removeTag(nodeRef, tagValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void deleteTagById(StoreRef storeRef, String tagId) {
 | 
			
		||||
        verifyAdminAuthority();
 | 
			
		||||
	public void deleteTagById(StoreRef storeRef, String tagId) {
 | 
			
		||||
		verifyAdminAuthority();
 | 
			
		||||
 | 
			
		||||
        NodeRef tagNodeRef = validateTag(storeRef, tagId);
 | 
			
		||||
        String tagValue = taggingService.getTagName(tagNodeRef);
 | 
			
		||||
        taggingService.deleteTag(storeRef, tagValue);
 | 
			
		||||
    }
 | 
			
		||||
		NodeRef tagNodeRef = validateTag(storeRef, tagId);
 | 
			
		||||
		String tagValue = taggingService.getTagName(tagNodeRef);
 | 
			
		||||
		taggingService.deleteTag(storeRef, tagValue);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params)
 | 
			
		||||
    {
 | 
			
		||||
        Paging paging = params.getPaging();
 | 
			
		||||
        Pair<String, Boolean> sorting = !params.getSorting().isEmpty() ? new Pair<>(params.getSorting().get(0).column, params.getSorting().get(0).asc) : null;
 | 
			
		||||
        Map<Integer, Collection<String>> namesFilters = resolveTagNamesQuery(params.getQuery());
 | 
			
		||||
        PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging), namesFilters.get(EQUALS), namesFilters.get(MATCHES));
 | 
			
		||||
 | 
			
		||||
        Map<NodeRef, Long> results = taggingService.getTags(storeRef, params.getInclude(), sorting, namesFilters.get(EQUALS), namesFilters.get(MATCHES));
 | 
			
		||||
 | 
			
		||||
        List<Tag> tagsList = results.entrySet().stream().map(entry -> new Tag(entry.getKey(), (String)nodeService.getProperty(entry.getKey(), ContentModel.PROP_NAME))).collect(Collectors.toList());
 | 
			
		||||
 | 
			
		||||
        Integer totalItems = results.getTotalResultCount().getFirst();
 | 
			
		||||
        List<Pair<NodeRef, String>> page = results.getPage();
 | 
			
		||||
        List<Tag> tags = new ArrayList<>(page.size());
 | 
			
		||||
        for (Pair<NodeRef, String> pair : page)
 | 
			
		||||
        {
 | 
			
		||||
            Tag selectedTag = new Tag(pair.getFirst(), pair.getSecond());
 | 
			
		||||
            tags.add(selectedTag);
 | 
			
		||||
        }
 | 
			
		||||
        if (params.getInclude().contains(PARAM_INCLUDE_COUNT))
 | 
			
		||||
        {
 | 
			
		||||
            tagsList.forEach(tag -> tag.setCount(results.get(tag.getNodeRef())));
 | 
			
		||||
            List<Pair<String, Integer>> tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef);
 | 
			
		||||
            Map<String, Integer> tagsByCountMap = new HashMap<>();
 | 
			
		||||
            if (tagsByCount != null)
 | 
			
		||||
            {
 | 
			
		||||
                for (Pair<String, Integer> tagByCountElem : tagsByCount)
 | 
			
		||||
                {
 | 
			
		||||
                    tagsByCountMap.put(tagByCountElem.getFirst(), tagByCountElem.getSecond());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            tags.forEach(tag -> tag.setCount(Optional.ofNullable(tagsByCountMap.get(tag.getTag())).orElse(0)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ListBackedPagingResults listBackedPagingResults = new ListBackedPagingResults(tagsList, Util.getPagingRequest(params.getPaging()));
 | 
			
		||||
 | 
			
		||||
        return CollectionWithPagingInfo.asPaged(paging, listBackedPagingResults.getPage(), listBackedPagingResults.hasMoreItems(), (Integer) listBackedPagingResults.getTotalResultCount().getFirst());
 | 
			
		||||
        return CollectionWithPagingInfo.asPaged(paging, tags, results.hasMoreItems(), totalItems);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public NodeRef validateTag(String tagId)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef tagNodeRef = nodes.validateNode(tagId);
 | 
			
		||||
        return checkTagRootAsNodePrimaryParent(tagId, tagNodeRef);
 | 
			
		||||
    	NodeRef tagNodeRef = nodes.validateNode(tagId);
 | 
			
		||||
		return checkTagRootAsNodePrimaryParent(tagId, tagNodeRef);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public NodeRef validateTag(StoreRef storeRef, String tagId)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef tagNodeRef = nodes.validateNode(storeRef, tagId);
 | 
			
		||||
        return checkTagRootAsNodePrimaryParent(tagId, tagNodeRef);
 | 
			
		||||
    	NodeRef tagNodeRef = nodes.validateNode(storeRef, tagId);
 | 
			
		||||
		return checkTagRootAsNodePrimaryParent(tagId, tagNodeRef);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find the number of times the given tag is used (if requested).
 | 
			
		||||
     *
 | 
			
		||||
     * @param storeRef The store the tag is in.
 | 
			
		||||
     * @param tagName The name of the tag.
 | 
			
		||||
     * @param parameters The request parameters object containing the includes parameter.
 | 
			
		||||
     * @return The number of times the tag is applied, or null if "count" wasn't in the include parameter.
 | 
			
		||||
     */
 | 
			
		||||
    private Long findCountIfRequested(StoreRef storeRef, String tagName, Parameters parameters)
 | 
			
		||||
    public Tag changeTag(StoreRef storeRef, String tagId, Tag tag)
 | 
			
		||||
    {
 | 
			
		||||
        Long count = null;
 | 
			
		||||
        if (parameters != null && parameters.getInclude() != null && parameters.getInclude().contains(PARAM_INCLUDE_COUNT))
 | 
			
		||||
        {
 | 
			
		||||
            count = taggingService.findCountByTagName(storeRef, tagName);
 | 
			
		||||
        }
 | 
			
		||||
        return count;
 | 
			
		||||
    	try
 | 
			
		||||
    	{
 | 
			
		||||
	    	NodeRef existingTagNodeRef = validateTag(storeRef, tagId);
 | 
			
		||||
	    	String existingTagName = taggingService.getTagName(existingTagNodeRef);
 | 
			
		||||
	    	String newTagName = tag.getTag();
 | 
			
		||||
	    	NodeRef newTagNodeRef = taggingService.changeTag(storeRef, existingTagName, newTagName);
 | 
			
		||||
	    	return new Tag(newTagNodeRef, newTagName);
 | 
			
		||||
    	}
 | 
			
		||||
    	catch(NonExistentTagException e)
 | 
			
		||||
    	{
 | 
			
		||||
    		throw new NotFoundException(e.getMessage());
 | 
			
		||||
    	}
 | 
			
		||||
    	catch(TagExistsException e)
 | 
			
		||||
    	{
 | 
			
		||||
    		throw new ConstraintViolatedException(e.getMessage());
 | 
			
		||||
    	}
 | 
			
		||||
    	catch(TaggingException e)
 | 
			
		||||
    	{
 | 
			
		||||
    		throw new InvalidArgumentException(e.getMessage());
 | 
			
		||||
    	}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Tag changeTag(StoreRef storeRef, String tagId, Tag tag, Parameters parameters)
 | 
			
		||||
    public Tag getTag(String tagId)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            NodeRef existingTagNodeRef = validateTag(storeRef, tagId);
 | 
			
		||||
            String existingTagName = taggingService.getTagName(existingTagNodeRef);
 | 
			
		||||
            Long count = findCountIfRequested(storeRef, existingTagName, parameters);
 | 
			
		||||
            String newTagName = tag.getTag();
 | 
			
		||||
            NodeRef newTagNodeRef = taggingService.changeTag(storeRef, existingTagName, newTagName);
 | 
			
		||||
            return Tag.builder().nodeRef(newTagNodeRef).tag(newTagName).count(count).create();
 | 
			
		||||
        }
 | 
			
		||||
        catch(NonExistentTagException e)
 | 
			
		||||
        {
 | 
			
		||||
            throw new NotFoundException(e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
        catch(TagExistsException e)
 | 
			
		||||
        {
 | 
			
		||||
            throw new ConstraintViolatedException(e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
        catch(TaggingException e)
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidArgumentException(e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    	return getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, tagId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Tag getTag(StoreRef storeRef, String tagId, Parameters parameters)
 | 
			
		||||
    public Tag getTag(StoreRef storeRef, String tagId)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef tagNodeRef = validateTag(storeRef, tagId);
 | 
			
		||||
        String tagName = taggingService.getTagName(tagNodeRef);
 | 
			
		||||
        Long count = findCountIfRequested(storeRef, tagName, parameters);
 | 
			
		||||
        return Tag.builder().nodeRef(tagNodeRef).tag(tagName).count(count).create();
 | 
			
		||||
    	NodeRef tagNodeRef = validateTag(storeRef, tagId);
 | 
			
		||||
    	String tagValue = taggingService.getTagName(tagNodeRef);
 | 
			
		||||
    	return new Tag(tagNodeRef, tagValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CollectionWithPagingInfo<Tag> getTags(String nodeId, Parameters params)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef nodeRef = nodes.validateOrLookupNode(nodeId);
 | 
			
		||||
@@ -273,80 +259,80 @@ public class TagsImpl implements Tags
 | 
			
		||||
        return CollectionWithPagingInfo.asPaged(params.getPaging(), tags, results.hasMoreItems(), (totalItems == null ? null : totalItems.intValue()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Experimental
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Tag> createTags(final StoreRef storeRef, final List<Tag> tags, final Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        verifyAdminAuthority();
 | 
			
		||||
        final List<String> tagNames = Optional.ofNullable(tags).orElse(Collections.emptyList()).stream()
 | 
			
		||||
            .filter(Objects::nonNull)
 | 
			
		||||
            .map(Tag::getTag)
 | 
			
		||||
            .distinct()
 | 
			
		||||
            .collect(toList());
 | 
			
		||||
	@Experimental
 | 
			
		||||
	@Override
 | 
			
		||||
	public List<Tag> createTags(final StoreRef storeRef, final List<Tag> tags, final Parameters parameters)
 | 
			
		||||
	{
 | 
			
		||||
		verifyAdminAuthority();
 | 
			
		||||
		final List<String> tagNames = Optional.ofNullable(tags).orElse(Collections.emptyList()).stream()
 | 
			
		||||
			.filter(Objects::nonNull)
 | 
			
		||||
			.map(Tag::getTag)
 | 
			
		||||
			.distinct()
 | 
			
		||||
			.collect(toList());
 | 
			
		||||
 | 
			
		||||
        if (CollectionUtils.isEmpty(tagNames))
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidArgumentException(NOT_A_VALID_TAG);
 | 
			
		||||
        }
 | 
			
		||||
		if (CollectionUtils.isEmpty(tagNames))
 | 
			
		||||
		{
 | 
			
		||||
			throw new InvalidArgumentException(NOT_A_VALID_TAG);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        return taggingService.createTags(storeRef, tagNames).stream()
 | 
			
		||||
            .map(pair -> Tag.builder().tag(pair.getFirst()).nodeRef(pair.getSecond()).create())
 | 
			
		||||
            .peek(tag -> {
 | 
			
		||||
                if (parameters.getInclude().contains(PARAM_INCLUDE_COUNT))
 | 
			
		||||
                {
 | 
			
		||||
                    tag.setCount(0L);
 | 
			
		||||
                }
 | 
			
		||||
            }).collect(toList());
 | 
			
		||||
    }
 | 
			
		||||
		return taggingService.createTags(storeRef, tagNames).stream()
 | 
			
		||||
			.map(pair -> Tag.builder().tag(pair.getFirst()).nodeRef(pair.getSecond()).create())
 | 
			
		||||
			.peek(tag -> {
 | 
			
		||||
				if (parameters.getInclude().contains(PARAM_INCLUDE_COUNT))
 | 
			
		||||
				{
 | 
			
		||||
					tag.setCount(0);
 | 
			
		||||
				}
 | 
			
		||||
			}).collect(toList());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    private void verifyAdminAuthority()
 | 
			
		||||
    {
 | 
			
		||||
        if (!authorityService.hasAdminAuthority())
 | 
			
		||||
        {
 | 
			
		||||
            throw new PermissionDeniedException(NO_PERMISSION_TO_MANAGE_A_TAG);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
	private void verifyAdminAuthority()
 | 
			
		||||
	{
 | 
			
		||||
		if (!authorityService.hasAdminAuthority())
 | 
			
		||||
		{
 | 
			
		||||
			throw new PermissionDeniedException(NO_PERMISSION_TO_MANAGE_A_TAG);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method resolves where query looking for clauses: EQUALS, IN or MATCHES.
 | 
			
		||||
     * Expected values for EQUALS and IN will be merged under EQUALS clause.
 | 
			
		||||
     * @param namesQuery Where query with expected tag name(s).
 | 
			
		||||
     * @return Map of expected exact and alike tag names.
 | 
			
		||||
     */
 | 
			
		||||
    private Map<Integer, Collection<String>> resolveTagNamesQuery(final Query namesQuery)
 | 
			
		||||
    {
 | 
			
		||||
        if (namesQuery == null || namesQuery == QueryImpl.EMPTY)
 | 
			
		||||
        {
 | 
			
		||||
            return Collections.emptyMap();
 | 
			
		||||
        }
 | 
			
		||||
	/**
 | 
			
		||||
	 * Method resolves where query looking for clauses: EQUALS, IN or MATCHES.
 | 
			
		||||
	 * Expected values for EQUALS and IN will be merged under EQUALS clause.
 | 
			
		||||
	 * @param namesQuery Where query with expected tag name(s).
 | 
			
		||||
	 * @return Map of expected exact and alike tag names.
 | 
			
		||||
	 */
 | 
			
		||||
	private Map<Integer, Collection<String>> resolveTagNamesQuery(final Query namesQuery)
 | 
			
		||||
	{
 | 
			
		||||
		if (namesQuery == null || namesQuery == QueryImpl.EMPTY)
 | 
			
		||||
		{
 | 
			
		||||
			return Collections.emptyMap();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        final Map<Integer, Collection<String>> properties = QueryHelper
 | 
			
		||||
            .resolve(namesQuery)
 | 
			
		||||
            .usingOrOperator()
 | 
			
		||||
            .withoutNegations()
 | 
			
		||||
            .getProperty(PARAM_WHERE_TAG)
 | 
			
		||||
            .getExpectedValuesForAnyOf(EQUALS, IN, MATCHES)
 | 
			
		||||
            .skipNegated();
 | 
			
		||||
		final Map<Integer, Collection<String>> properties = QueryHelper
 | 
			
		||||
			.resolve(namesQuery)
 | 
			
		||||
			.usingOrOperator()
 | 
			
		||||
			.withoutNegations()
 | 
			
		||||
			.getProperty(PARAM_WHERE_TAG)
 | 
			
		||||
			.getExpectedValuesForAnyOf(EQUALS, IN, MATCHES)
 | 
			
		||||
			.skipNegated();
 | 
			
		||||
 | 
			
		||||
        return properties.entrySet().stream()
 | 
			
		||||
            .collect(Collectors.groupingBy((entry) -> {
 | 
			
		||||
                if (entry.getKey() == EQUALS || entry.getKey() == IN)
 | 
			
		||||
                {
 | 
			
		||||
                    return EQUALS;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return MATCHES;
 | 
			
		||||
                }
 | 
			
		||||
            }, Collectors.flatMapping((entry) -> entry.getValue().stream().map(String::toLowerCase), Collectors.toCollection(HashSet::new))));
 | 
			
		||||
    }
 | 
			
		||||
		return properties.entrySet().stream()
 | 
			
		||||
			.collect(Collectors.groupingBy((entry) -> {
 | 
			
		||||
				if (entry.getKey() == EQUALS || entry.getKey() == IN)
 | 
			
		||||
				{
 | 
			
		||||
					return EQUALS;
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					return MATCHES;
 | 
			
		||||
				}
 | 
			
		||||
			}, Collectors.flatMapping((entry) -> entry.getValue().stream().map(String::toLowerCase), Collectors.toCollection(HashSet::new))));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    private NodeRef checkTagRootAsNodePrimaryParent(String tagId, NodeRef tagNodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        if ( tagNodeRef == null || !nodeService.getPrimaryParent(tagNodeRef).getParentRef().equals(TAG_ROOT_NODE_REF))
 | 
			
		||||
        {
 | 
			
		||||
            throw new EntityNotFoundException(tagId);
 | 
			
		||||
        }
 | 
			
		||||
        return tagNodeRef;
 | 
			
		||||
    }
 | 
			
		||||
	private NodeRef checkTagRootAsNodePrimaryParent(String tagId, NodeRef tagNodeRef)
 | 
			
		||||
	{
 | 
			
		||||
		if ( tagNodeRef == null || !nodeService.getPrimaryParent(tagNodeRef).getParentRef().equals(TAG_ROOT_NODE_REF))
 | 
			
		||||
		{
 | 
			
		||||
			throw new EntityNotFoundException(tagId);
 | 
			
		||||
		}
 | 
			
		||||
		return tagNodeRef;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,6 @@ public class Category
 | 
			
		||||
    private String parentId;
 | 
			
		||||
    private boolean hasChildren;
 | 
			
		||||
    private Integer count;
 | 
			
		||||
    private String path;
 | 
			
		||||
 | 
			
		||||
    public String getId()
 | 
			
		||||
    {
 | 
			
		||||
@@ -92,14 +91,6 @@ public class Category
 | 
			
		||||
        this.count = count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getPath() {
 | 
			
		||||
        return path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPath(String path) {
 | 
			
		||||
        this.path = path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o)
 | 
			
		||||
    {
 | 
			
		||||
@@ -109,20 +100,19 @@ public class Category
 | 
			
		||||
            return false;
 | 
			
		||||
        Category category = (Category) o;
 | 
			
		||||
        return hasChildren == category.hasChildren && Objects.equals(id, category.id) && Objects.equals(name, category.name) && Objects.equals(parentId, category.parentId)
 | 
			
		||||
            && Objects.equals(count, category.count) && Objects.equals(path, category.path);
 | 
			
		||||
            && Objects.equals(count, category.count);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode()
 | 
			
		||||
    {
 | 
			
		||||
        return Objects.hash(id, name, parentId, hasChildren, count, path);
 | 
			
		||||
        return Objects.hash(id, name, parentId, hasChildren, count);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString()
 | 
			
		||||
    {
 | 
			
		||||
        return "Category{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", parentId='" + parentId + '\'' + ", hasChildren=" + hasChildren
 | 
			
		||||
                + ", count=" + count + ", path=" + path + '}';
 | 
			
		||||
        return "Category{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", parentId='" + parentId + '\'' + ", hasChildren=" + hasChildren + ", count=" + count + '}';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Builder builder()
 | 
			
		||||
@@ -137,7 +127,6 @@ public class Category
 | 
			
		||||
        private String parentId;
 | 
			
		||||
        private boolean hasChildren;
 | 
			
		||||
        private Integer count;
 | 
			
		||||
        private String path;
 | 
			
		||||
 | 
			
		||||
        public Builder id(String id)
 | 
			
		||||
        {
 | 
			
		||||
@@ -169,12 +158,6 @@ public class Category
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Builder path(String path)
 | 
			
		||||
        {
 | 
			
		||||
            this.path = path;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Category create()
 | 
			
		||||
        {
 | 
			
		||||
            final Category category = new Category();
 | 
			
		||||
@@ -183,7 +166,6 @@ public class Category
 | 
			
		||||
            category.setParentId(parentId);
 | 
			
		||||
            category.setHasChildren(hasChildren);
 | 
			
		||||
            category.setCount(count);
 | 
			
		||||
            category.setPath(path);
 | 
			
		||||
            return category;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ public class Tag implements Comparable<Tag>
 | 
			
		||||
{
 | 
			
		||||
	private NodeRef nodeRef;
 | 
			
		||||
	private String tag;
 | 
			
		||||
	private Long count;
 | 
			
		||||
	private Integer count;
 | 
			
		||||
 | 
			
		||||
    public Tag()
 | 
			
		||||
	{
 | 
			
		||||
@@ -76,13 +76,13 @@ public class Tag implements Comparable<Tag>
 | 
			
		||||
		this.tag = Optional.ofNullable(tag).map(String::toLowerCase).orElse(null);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public Long getCount()
 | 
			
		||||
	public Integer getCount()
 | 
			
		||||
	{
 | 
			
		||||
	
 | 
			
		||||
	    return count;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setCount(Long count)
 | 
			
		||||
	public void setCount(Integer count)
 | 
			
		||||
	{
 | 
			
		||||
	    this.count = count;
 | 
			
		||||
	}
 | 
			
		||||
@@ -133,7 +133,7 @@ public class Tag implements Comparable<Tag>
 | 
			
		||||
	{
 | 
			
		||||
		private NodeRef nodeRef;
 | 
			
		||||
		private String tag;
 | 
			
		||||
		private Long count;
 | 
			
		||||
		private Integer count;
 | 
			
		||||
 | 
			
		||||
		public Builder nodeRef(NodeRef nodeRef)
 | 
			
		||||
		{
 | 
			
		||||
@@ -147,7 +147,7 @@ public class Tag implements Comparable<Tag>
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Builder count(Long count)
 | 
			
		||||
		public Builder count(Integer count)
 | 
			
		||||
		{
 | 
			
		||||
			this.count = count;
 | 
			
		||||
			return this;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,8 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.api.tags;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.api.Tags;
 | 
			
		||||
import org.alfresco.rest.api.model.Tag;
 | 
			
		||||
@@ -73,13 +73,13 @@ public class TagsEntityResource implements EntityResourceAction.Read<Tag>,
 | 
			
		||||
    @WebApiDescription(title="Updates a tag by unique Id")
 | 
			
		||||
	public Tag update(String id, Tag entity, Parameters parameters)
 | 
			
		||||
	{
 | 
			
		||||
		return tags.changeTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id, entity, parameters);
 | 
			
		||||
		return tags.changeTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id, entity);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Tag readById(String id, Parameters parameters) throws EntityNotFoundException
 | 
			
		||||
	{
 | 
			
		||||
		return tags.getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id, parameters);
 | 
			
		||||
		return tags.getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,6 @@ import java.util.stream.IntStream;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.repo.transfer.PathHelper;
 | 
			
		||||
import org.alfresco.rest.api.Nodes;
 | 
			
		||||
import org.alfresco.rest.api.model.Category;
 | 
			
		||||
import org.alfresco.rest.api.model.Node;
 | 
			
		||||
@@ -72,7 +71,6 @@ import org.alfresco.rest.framework.resource.parameters.Parameters;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.Path;
 | 
			
		||||
import org.alfresco.service.cmr.repository.StoreRef;
 | 
			
		||||
import org.alfresco.service.cmr.search.CategoryService;
 | 
			
		||||
import org.alfresco.service.cmr.security.AccessStatus;
 | 
			
		||||
@@ -102,9 +100,6 @@ public class CategoriesImplTest
 | 
			
		||||
    private static final Category CATEGORY = createDefaultCategory();
 | 
			
		||||
    private static final String CONTENT_NODE_ID = "content-node-id";
 | 
			
		||||
    private static final NodeRef CONTENT_NODE_REF = createNodeRefWithId(CONTENT_NODE_ID);
 | 
			
		||||
    private static final String MOCK_ROOT_LEVEL = "/{mockRootLevel}";
 | 
			
		||||
    private static final String MOCK_CHILD_LEVEL = "/{mockChild}";
 | 
			
		||||
    private static final String MOCK_CATEGORY_PATH = "//" + MOCK_ROOT_LEVEL + "//" + MOCK_CHILD_LEVEL;
 | 
			
		||||
 | 
			
		||||
    @Mock
 | 
			
		||||
    private Nodes nodesMock;
 | 
			
		||||
@@ -257,27 +252,6 @@ public class CategoriesImplTest
 | 
			
		||||
            .isEqualTo(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetCategoryById_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
 | 
			
		||||
        final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
 | 
			
		||||
        final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
 | 
			
		||||
        given(nodesMock.getNode(any())).willReturn(createNode());
 | 
			
		||||
        given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH));
 | 
			
		||||
        given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath());
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        final Category actualCategory = objectUnderTest.getCategoryById(CATEGORY_ID, parametersMock);
 | 
			
		||||
 | 
			
		||||
        assertThat(actualCategory)
 | 
			
		||||
                .isNotNull()
 | 
			
		||||
                .extracting(Category::getPath)
 | 
			
		||||
                .isNotNull()
 | 
			
		||||
                .isEqualTo(MOCK_CATEGORY_PATH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetCategoryById_notACategory()
 | 
			
		||||
    {
 | 
			
		||||
@@ -505,36 +479,6 @@ public class CategoriesImplTest
 | 
			
		||||
            .isEqualTo(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testCreateCategory_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
 | 
			
		||||
        final NodeRef categoryNodeRef = createNodeRefWithId(CATEGORY_ID);
 | 
			
		||||
        final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
 | 
			
		||||
        final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
 | 
			
		||||
        given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef);
 | 
			
		||||
        given(categoryServiceMock.createCategory(parentCategoryNodeRef, CATEGORY_NAME)).willReturn(categoryNodeRef);
 | 
			
		||||
        given(nodesMock.getNode(any())).willReturn(createNode());
 | 
			
		||||
        given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH));
 | 
			
		||||
        given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath());
 | 
			
		||||
        final List<Category> categoryModels = new ArrayList<>(prepareCategories());
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        final List<Category> actualCreatedCategories = objectUnderTest.createSubcategories(PARENT_ID, categoryModels, parametersMock);
 | 
			
		||||
 | 
			
		||||
        then(categoryServiceMock).should().createCategory(any(), any());
 | 
			
		||||
        then(categoryServiceMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
 | 
			
		||||
        assertThat(actualCreatedCategories)
 | 
			
		||||
                .isNotNull()
 | 
			
		||||
                .hasSize(1)
 | 
			
		||||
                .element(0)
 | 
			
		||||
                .extracting(Category::getPath)
 | 
			
		||||
                .isNotNull()
 | 
			
		||||
                .isEqualTo(MOCK_CATEGORY_PATH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testCreateCategories_noPermissions()
 | 
			
		||||
    {
 | 
			
		||||
@@ -684,30 +628,6 @@ public class CategoriesImplTest
 | 
			
		||||
            .isEqualTo(List.of(0, 2, 0));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetCategoryChildren_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
 | 
			
		||||
        given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef);
 | 
			
		||||
        given(nodesMock.isSubClass(parentCategoryNodeRef, ContentModel.TYPE_CATEGORY, false)).willReturn(true);
 | 
			
		||||
        final int childrenCount = 3;
 | 
			
		||||
        final List<ChildAssociationRef> childAssociationRefMocks = prepareChildAssocMocks(childrenCount, parentCategoryNodeRef);
 | 
			
		||||
        given(nodeServiceMock.getChildAssocs(parentCategoryNodeRef)).willReturn(childAssociationRefMocks);
 | 
			
		||||
        childAssociationRefMocks.forEach(this::prepareCategoryNodeMocks);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH));
 | 
			
		||||
        given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath());
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        final List<Category> actualCategoryChildren = objectUnderTest.getCategoryChildren(PARENT_ID, parametersMock);
 | 
			
		||||
 | 
			
		||||
        assertThat(actualCategoryChildren)
 | 
			
		||||
                .isNotNull()
 | 
			
		||||
                .hasSize(3)
 | 
			
		||||
                .extracting(Category::getPath)
 | 
			
		||||
                .isNotNull()
 | 
			
		||||
                .isEqualTo(List.of(MOCK_CATEGORY_PATH, MOCK_CATEGORY_PATH, MOCK_CATEGORY_PATH));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetCategoryChildren_noChildren()
 | 
			
		||||
    {
 | 
			
		||||
@@ -831,32 +751,6 @@ public class CategoriesImplTest
 | 
			
		||||
            .isEqualTo(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testUpdateCategoryById_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        final String categoryNewName = "categoryNewName";
 | 
			
		||||
        final Category fixedCategory = createCategoryOnlyWithName(categoryNewName);
 | 
			
		||||
        // simulate path provided by client to check if it will be ignored
 | 
			
		||||
        fixedCategory.setPath("/test/TestCat");
 | 
			
		||||
        final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
 | 
			
		||||
        final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
 | 
			
		||||
        final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
 | 
			
		||||
        given(nodesMock.getNode(any())).willReturn(createNode(categoryNewName));
 | 
			
		||||
        given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
 | 
			
		||||
        given(nodeServiceMock.moveNode(any(), any(), any(), any())).willReturn(createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, createCmQNameOf(categoryNewName)));
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH));
 | 
			
		||||
        given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath());
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, fixedCategory, parametersMock);
 | 
			
		||||
 | 
			
		||||
        assertThat(actualCategory)
 | 
			
		||||
                .isNotNull()
 | 
			
		||||
                .extracting(Category::getPath)
 | 
			
		||||
                .isNotNull()
 | 
			
		||||
                .isEqualTo(MOCK_CATEGORY_PATH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testUpdateCategoryById_noPermission()
 | 
			
		||||
    {
 | 
			
		||||
@@ -1024,7 +918,6 @@ public class CategoriesImplTest
 | 
			
		||||
        then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef);
 | 
			
		||||
        then(nodeServiceMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
        final List<Category> expectedLinkedCategories = List.of(CATEGORY);
 | 
			
		||||
        expectedLinkedCategories.get(0).setPath(null);
 | 
			
		||||
        assertThat(actualLinkedCategories)
 | 
			
		||||
            .isNotNull().usingRecursiveComparison()
 | 
			
		||||
            .isEqualTo(expectedLinkedCategories);
 | 
			
		||||
@@ -1091,36 +984,6 @@ public class CategoriesImplTest
 | 
			
		||||
            .isEqualTo(expectedLinkedCategories);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testLinkNodeToCategories_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
 | 
			
		||||
        final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
 | 
			
		||||
        given(nodesMock.getNode(any())).willReturn(createNode());
 | 
			
		||||
        given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
 | 
			
		||||
        given(nodeServiceMock.hasAspect(any(), any())).willReturn(true);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH));
 | 
			
		||||
        given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath());
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY), parametersMock);
 | 
			
		||||
 | 
			
		||||
        then(nodesMock).should(times(2)).getNode(CATEGORY_ID);
 | 
			
		||||
        then(nodeServiceMock).should().getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
 | 
			
		||||
        then(nodeServiceMock).should().getPrimaryParent(CATEGORY_NODE_REF);
 | 
			
		||||
        then(nodeServiceMock).should().getParentAssocs(CATEGORY_NODE_REF);
 | 
			
		||||
        then(nodeServiceMock).should().hasAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE);
 | 
			
		||||
        then(nodeServiceMock).should().getProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES);
 | 
			
		||||
        final Serializable expectedCategories = (Serializable) List.of(CATEGORY_NODE_REF);
 | 
			
		||||
        then(nodeServiceMock).should().setProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES, expectedCategories);
 | 
			
		||||
        then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef);
 | 
			
		||||
        final List<Category> expectedLinkedCategories = List.of(CATEGORY);
 | 
			
		||||
        expectedLinkedCategories.get(0).setPath(MOCK_CATEGORY_PATH);
 | 
			
		||||
        assertThat(actualLinkedCategories)
 | 
			
		||||
                .isNotNull().usingRecursiveComparison()
 | 
			
		||||
                .isEqualTo(expectedLinkedCategories);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testLinkNodeToCategories_withPreviouslyLinkedCategories()
 | 
			
		||||
    {
 | 
			
		||||
@@ -1305,46 +1168,11 @@ public class CategoriesImplTest
 | 
			
		||||
        then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef);
 | 
			
		||||
        then(nodeServiceMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
        final List<Category> expectedCategories = List.of(CATEGORY);
 | 
			
		||||
        expectedCategories.get(0).setPath(null);
 | 
			
		||||
        assertThat(actualCategories)
 | 
			
		||||
            .isNotNull().usingRecursiveComparison()
 | 
			
		||||
            .isEqualTo(expectedCategories);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testListCategoriesForNode_includePath()
 | 
			
		||||
    {
 | 
			
		||||
        final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
 | 
			
		||||
        final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
 | 
			
		||||
        given(nodeServiceMock.getProperty(any(), eq(ContentModel.PROP_CATEGORIES))).willReturn((Serializable) List.of(CATEGORY_NODE_REF));
 | 
			
		||||
        given(nodesMock.getNode(any())).willReturn(createNode());
 | 
			
		||||
        given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH));
 | 
			
		||||
        given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath());
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        final List<Category> actualCategories = objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock);
 | 
			
		||||
 | 
			
		||||
        then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID);
 | 
			
		||||
        then(permissionServiceMock).should().hasReadPermission(CONTENT_NODE_REF);
 | 
			
		||||
        then(permissionServiceMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
        then(typeConstraint).should().matches(CONTENT_NODE_REF);
 | 
			
		||||
        then(typeConstraint).shouldHaveNoMoreInteractions();
 | 
			
		||||
        then(nodesMock).should(times(2)).getNode(CATEGORY_ID);
 | 
			
		||||
        then(nodesMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
        then(nodeServiceMock).should().getProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES);
 | 
			
		||||
        then(nodeServiceMock).should().getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
 | 
			
		||||
        then(nodeServiceMock).should().getPrimaryParent(CATEGORY_NODE_REF);
 | 
			
		||||
        then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef);
 | 
			
		||||
        then(nodeServiceMock).should().getPath(any());
 | 
			
		||||
        then(nodeServiceMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
        final List<Category> expectedCategories = List.of(CATEGORY);
 | 
			
		||||
        expectedCategories.get(0).setPath(MOCK_CATEGORY_PATH);
 | 
			
		||||
        assertThat(actualCategories)
 | 
			
		||||
                .isNotNull().usingRecursiveComparison()
 | 
			
		||||
                .isEqualTo(expectedCategories);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testListCategoriesForNode_withInvalidNodeId()
 | 
			
		||||
    {
 | 
			
		||||
@@ -1501,13 +1329,4 @@ public class CategoriesImplTest
 | 
			
		||||
    {
 | 
			
		||||
        return new ChildAssociationRef(ContentModel.ASSOC_SUBCATEGORIES, parentNode, childNodeName, childNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Path mockCategoryPath()
 | 
			
		||||
    {
 | 
			
		||||
        final Path mockPath = new Path();
 | 
			
		||||
        mockPath.append(PathHelper.stringToPath(MOCK_ROOT_LEVEL));
 | 
			
		||||
        mockPath.append(PathHelper.stringToPath(MOCK_CHILD_LEVEL));
 | 
			
		||||
        mockPath.append(PathHelper.stringToPath("/"));
 | 
			
		||||
        return mockPath;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,14 +40,10 @@ import static org.mockito.ArgumentMatchers.isNull;
 | 
			
		||||
import static org.mockito.BDDMockito.given;
 | 
			
		||||
import static org.mockito.BDDMockito.then;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.query.PagingRequest;
 | 
			
		||||
import org.alfresco.query.PagingResults;
 | 
			
		||||
import org.alfresco.rest.api.Nodes;
 | 
			
		||||
@@ -59,7 +55,6 @@ import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationE
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.Paging;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.SortColumn;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.where.InvalidQueryException;
 | 
			
		||||
import org.alfresco.rest.framework.tools.RecognizedParamsExtractor;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
			
		||||
@@ -88,8 +83,6 @@ public class TagsImplTest
 | 
			
		||||
    private static final NodeRef TAG_PARENT_NODE_REF = new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, PARENT_NODE_ID);
 | 
			
		||||
    private static final String CONTENT_NODE_ID = "content-node-id";
 | 
			
		||||
    private static final NodeRef CONTENT_NODE_REF = new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, CONTENT_NODE_ID);
 | 
			
		||||
    private static final String PARAM_INCLUDE_COUNT = "count";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private final RecognizedParamsExtractor queryExtractor = new RecognizedParamsExtractor() {};
 | 
			
		||||
 | 
			
		||||
@@ -122,23 +115,19 @@ public class TagsImplTest
 | 
			
		||||
        given(nodesMock.validateNode(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID)).willReturn(TAG_NODE_REF);
 | 
			
		||||
        given(taggingServiceMock.getTagName(TAG_NODE_REF)).willReturn(TAG_NAME);
 | 
			
		||||
        given(nodeServiceMock.getPrimaryParent(TAG_NODE_REF)).willReturn(primaryParentMock);
 | 
			
		||||
        given(primaryParentMock.getParentRef()).willReturn(TAG_PARENT_NODE_REF);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetTags()
 | 
			
		||||
    {
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(new ArrayList<>());
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(new ArrayList<>());
 | 
			
		||||
 | 
			
		||||
        //given(taggingServiceMock.getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(), any(), isNull(), isNull())).willReturn(List.of(new Pair<>(TAG_NODE_REF, null)));
 | 
			
		||||
        given(taggingServiceMock.getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(), any(), isNull(), isNull())).willReturn(Map.of(TAG_NODE_REF, 0L));
 | 
			
		||||
        given(nodeServiceMock.getProperty(any(NodeRef.class), eq(ContentModel.PROP_NAME))).willReturn("tag-dummy-name");
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
 | 
			
		||||
        given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
 | 
			
		||||
        given(pagingResultsMock.getPage()).willReturn(List.of(new Pair<>(TAG_NODE_REF, TAG_NAME)));
 | 
			
		||||
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(), any(), isNull(), isNull());
 | 
			
		||||
        then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), isNull(), isNull());
 | 
			
		||||
        then(taggingServiceMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
        final List<Tag> expectedTags = createTagsWithNodeRefs(List.of(TAG_NAME));
 | 
			
		||||
        assertEquals(expectedTags, actualTags.getCollection());
 | 
			
		||||
@@ -148,17 +137,16 @@ public class TagsImplTest
 | 
			
		||||
    public void testGetTags_verifyIfCountIsZero()
 | 
			
		||||
    {
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(new ArrayList<>());
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of(PARAM_INCLUDE_COUNT));
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), any(), any(), any(), any())).willReturn(Map.of(TAG_NODE_REF, 0L));
 | 
			
		||||
 | 
			
		||||
        given(nodeServiceMock.getProperty(any(NodeRef.class), eq(ContentModel.PROP_NAME))).willReturn("tag-dummy-name");
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
 | 
			
		||||
        given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
 | 
			
		||||
        given(pagingResultsMock.getPage()).willReturn(List.of(new Pair<>(TAG_NODE_REF, TAG_NAME)));
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of("count"));
 | 
			
		||||
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        then(taggingServiceMock).should().findTaggedNodesAndCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE);
 | 
			
		||||
        final List<Tag> expectedTags = createTagsWithNodeRefs(List.of(TAG_NAME)).stream()
 | 
			
		||||
            .peek(tag -> tag.setCount(0L))
 | 
			
		||||
            .peek(tag -> tag.setCount(0))
 | 
			
		||||
            .collect(toList());
 | 
			
		||||
        assertEquals(expectedTags, actualTags.getCollection());
 | 
			
		||||
    }
 | 
			
		||||
@@ -169,142 +157,21 @@ public class TagsImplTest
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef tagNodeA = new NodeRef("tag://A/");
 | 
			
		||||
        NodeRef tagNodeB = new NodeRef("tag://B/");
 | 
			
		||||
        List<Pair<NodeRef, String>> tagPairs = List.of(new Pair<>(tagNodeA, "taga"), new Pair<>(tagNodeB, "tagb"));
 | 
			
		||||
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(Collections.emptyList());
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
 | 
			
		||||
        given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
 | 
			
		||||
        given(pagingResultsMock.getPage()).willReturn(tagPairs);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of("count"));
 | 
			
		||||
 | 
			
		||||
        final LinkedHashMap<NodeRef, Long> results = new LinkedHashMap<>();
 | 
			
		||||
        results.put(tagNodeA, 5L);
 | 
			
		||||
        results.put(tagNodeB, 0L);
 | 
			
		||||
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), eq(List.of(PARAM_INCLUDE_COUNT)), isNull(), any(), any())).willReturn(results);
 | 
			
		||||
        given(nodeServiceMock.getProperty(any(NodeRef.class), eq(ContentModel.PROP_NAME))).willReturn("taga", "tagb");
 | 
			
		||||
        // Only taga is included in the returned list since tagb is not in use.
 | 
			
		||||
        given(taggingServiceMock.findTaggedNodesAndCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE)).willReturn(List.of(new Pair<>("taga", 5)));
 | 
			
		||||
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        final List<Tag> expectedTags = List.of(Tag.builder().tag("tagA").nodeRef(tagNodeA).count(5L).create(),
 | 
			
		||||
                                               Tag.builder().tag("tagB").nodeRef(tagNodeB).count(0L).create());
 | 
			
		||||
        assertEquals(expectedTags, actualTags.getCollection());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetTags_orderByCountAscendingOrder()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef tagNodeA = new NodeRef("tag://A/");
 | 
			
		||||
        NodeRef tagNodeB = new NodeRef("tag://B/");
 | 
			
		||||
        NodeRef tagNodeC = new NodeRef("tag://C/");
 | 
			
		||||
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of("count"));
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(List.of(new SortColumn("count", true)));
 | 
			
		||||
 | 
			
		||||
        final LinkedHashMap<NodeRef, Long> results = new LinkedHashMap<>();
 | 
			
		||||
        results.put(tagNodeB, 0L);
 | 
			
		||||
        results.put(tagNodeC, 2L);
 | 
			
		||||
        results.put(tagNodeA, 5L);
 | 
			
		||||
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), eq(List.of(PARAM_INCLUDE_COUNT)), eq(new Pair<>("count", true)), any(), any())).willReturn(results);
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagNodeA, ContentModel.PROP_NAME)).willReturn("taga");
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagNodeB, ContentModel.PROP_NAME)).willReturn("tagb");
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagNodeC, ContentModel.PROP_NAME)).willReturn("tagc");
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        final List<Tag> expectedTags = List.of(Tag.builder().tag("tagb").nodeRef(tagNodeB).count(0L).create(),
 | 
			
		||||
                                               Tag.builder().tag("tagc").nodeRef(tagNodeC).count(2L).create(),
 | 
			
		||||
                                               Tag.builder().tag("taga").nodeRef(tagNodeA).count(5L).create());
 | 
			
		||||
        assertEquals(expectedTags, actualTags.getCollection());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetTags_orderByCountDescendingOrder()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef tagNodeA = new NodeRef("tag://A/");
 | 
			
		||||
        NodeRef tagNodeB = new NodeRef("tag://B/");
 | 
			
		||||
        NodeRef tagNodeC = new NodeRef("tag://C/");
 | 
			
		||||
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of("count"));
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(List.of(new SortColumn("count", false)));
 | 
			
		||||
 | 
			
		||||
        final LinkedHashMap<NodeRef, Long> results = new LinkedHashMap<>();
 | 
			
		||||
        results.put(tagNodeA, 5L);
 | 
			
		||||
        results.put(tagNodeC, 2L);
 | 
			
		||||
        results.put(tagNodeB, 0L);
 | 
			
		||||
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), eq(List.of(PARAM_INCLUDE_COUNT)), eq(new Pair<>("count", false)), any(), any())).willReturn(results);
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagNodeA, ContentModel.PROP_NAME)).willReturn("taga");
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagNodeB, ContentModel.PROP_NAME)).willReturn("tagb");
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagNodeC, ContentModel.PROP_NAME)).willReturn("tagc");
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        final List<Tag> expectedTags = List.of(Tag.builder().tag("taga").nodeRef(tagNodeA).count(5L).create(),
 | 
			
		||||
                                               Tag.builder().tag("tagc").nodeRef(tagNodeC).count(2L).create(),
 | 
			
		||||
                                               Tag.builder().tag("tagb").nodeRef(tagNodeB).count(0L).create());
 | 
			
		||||
        assertEquals(expectedTags, actualTags.getCollection());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetTags_orderByTagAscendingOrder()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef tagApple = new NodeRef("tag://apple/");
 | 
			
		||||
        NodeRef tagBanana = new NodeRef("tag://banana/");
 | 
			
		||||
        NodeRef tagCoconut = new NodeRef("tag://coconut/");
 | 
			
		||||
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(Collections.emptyList());
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(List.of(new SortColumn("tag", true)));
 | 
			
		||||
 | 
			
		||||
        final LinkedHashMap<NodeRef, Long> results = new LinkedHashMap<>();
 | 
			
		||||
        results.put(tagApple, 0L);
 | 
			
		||||
        results.put(tagBanana, 0L);
 | 
			
		||||
        results.put(tagCoconut, 0L);
 | 
			
		||||
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), any(), eq(new Pair<>("tag", true)), any(), any())).willReturn(results);
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagApple, ContentModel.PROP_NAME)).willReturn("apple");
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagBanana, ContentModel.PROP_NAME)).willReturn("banana");
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagCoconut, ContentModel.PROP_NAME)).willReturn("coconut");
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        final List<Tag> expectedTags = List.of(Tag.builder().tag("apple").nodeRef(tagApple).create(),
 | 
			
		||||
                                               Tag.builder().tag("banana").nodeRef(tagBanana).create(),
 | 
			
		||||
                                               Tag.builder().tag("coconut").nodeRef(tagCoconut).create());
 | 
			
		||||
        assertEquals(expectedTags, actualTags.getCollection());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetTags_orderByTagDescendingOrder()
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef tagApple = new NodeRef("tag://apple/");
 | 
			
		||||
        NodeRef tagBanana = new NodeRef("tag://banana/");
 | 
			
		||||
        NodeRef tagCoconut = new NodeRef("tag://coconut/");
 | 
			
		||||
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(Collections.emptyList());
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(List.of(new SortColumn("tag", false)));
 | 
			
		||||
 | 
			
		||||
        final LinkedHashMap<NodeRef, Long> results = new LinkedHashMap<>();
 | 
			
		||||
        results.put(tagCoconut, 0L);
 | 
			
		||||
        results.put(tagBanana, 0L);
 | 
			
		||||
        results.put(tagApple, 0L);
 | 
			
		||||
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), any(), eq(new Pair<>("tag", false)), any(), any())).willReturn(results);
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagApple, ContentModel.PROP_NAME)).willReturn("apple");
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagBanana, ContentModel.PROP_NAME)).willReturn("banana");
 | 
			
		||||
        given(nodeServiceMock.getProperty(tagCoconut, ContentModel.PROP_NAME)).willReturn("coconut");
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        final List<Tag> expectedTags = List.of(Tag.builder().tag("coconut").nodeRef(tagCoconut).create(),
 | 
			
		||||
                                               Tag.builder().tag("banana").nodeRef(tagBanana).create(),
 | 
			
		||||
                                               Tag.builder().tag("apple").nodeRef(tagApple).create());
 | 
			
		||||
        then(taggingServiceMock).should().findTaggedNodesAndCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE);
 | 
			
		||||
        final List<Tag> expectedTags = List.of(Tag.builder().tag("taga").nodeRef(tagNodeA).count(5).create(),
 | 
			
		||||
                                               Tag.builder().tag("tagb").nodeRef(tagNodeB).count(0).create());
 | 
			
		||||
        assertEquals(expectedTags, actualTags.getCollection());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -313,13 +180,13 @@ public class TagsImplTest
 | 
			
		||||
    {
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag=expectedName)"));
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(new ArrayList<>());
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(new ArrayList<>());
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
 | 
			
		||||
        given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), eq(new ArrayList<>()), any(), eq(Set.of("expectedname")), isNull());
 | 
			
		||||
        then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), eq(Set.of("expectedname")), isNull());
 | 
			
		||||
        then(taggingServiceMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
        assertThat(actualTags).isNotNull();
 | 
			
		||||
    }
 | 
			
		||||
@@ -329,13 +196,13 @@ public class TagsImplTest
 | 
			
		||||
    {
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag IN (expectedName1, expectedName2))"));
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(new ArrayList<>());
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(new ArrayList<>());
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
 | 
			
		||||
        given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE),any(), any(), eq(Set.of("expectedname1", "expectedname2")), isNull());
 | 
			
		||||
        then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), eq(Set.of("expectedname1", "expectedname2")), isNull());
 | 
			
		||||
        then(taggingServiceMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
        assertThat(actualTags).isNotNull();
 | 
			
		||||
    }
 | 
			
		||||
@@ -345,13 +212,13 @@ public class TagsImplTest
 | 
			
		||||
    {
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag MATCHES ('expectedName*'))"));
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(new ArrayList<>());
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(new ArrayList<>());
 | 
			
		||||
        given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
 | 
			
		||||
        given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
 | 
			
		||||
 | 
			
		||||
        then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(), any(), isNull(), eq(Set.of("expectedname*")));
 | 
			
		||||
        then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), isNull(), eq(Set.of("expectedname*")));
 | 
			
		||||
        then(taggingServiceMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
        assertThat(actualTags).isNotNull();
 | 
			
		||||
    }
 | 
			
		||||
@@ -361,7 +228,6 @@ public class TagsImplTest
 | 
			
		||||
    {
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag=expectedName AND tag IN (expectedName1, expectedName2))"));
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(new ArrayList<>());
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final Throwable actualException = catchThrowable(() -> objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock));
 | 
			
		||||
@@ -375,7 +241,6 @@ public class TagsImplTest
 | 
			
		||||
    {
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag BETWEEN ('expectedName', 'expectedName2'))"));
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(new ArrayList<>());
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final Throwable actualException = catchThrowable(() -> objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock));
 | 
			
		||||
@@ -389,7 +254,6 @@ public class TagsImplTest
 | 
			
		||||
    {
 | 
			
		||||
        given(parametersMock.getPaging()).willReturn(pagingMock);
 | 
			
		||||
        given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(NOT tag=expectedName)"));
 | 
			
		||||
        given(parametersMock.getSorting()).willReturn(new ArrayList<>());
 | 
			
		||||
 | 
			
		||||
        //when
 | 
			
		||||
        final Throwable actualException = catchThrowable(() -> objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock));
 | 
			
		||||
@@ -402,6 +266,7 @@ public class TagsImplTest
 | 
			
		||||
    public void testDeleteTagById()
 | 
			
		||||
    {
 | 
			
		||||
        //when
 | 
			
		||||
        given(primaryParentMock.getParentRef()).willReturn(TAG_PARENT_NODE_REF);
 | 
			
		||||
        objectUnderTest.deleteTagById(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
 | 
			
		||||
 | 
			
		||||
        then(authorityServiceMock).should().hasAdminAuthority();
 | 
			
		||||
@@ -560,7 +425,7 @@ public class TagsImplTest
 | 
			
		||||
        final List<Tag> actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock);
 | 
			
		||||
 | 
			
		||||
        final List<Tag> expectedTags = createTagsWithNodeRefs(tagNames).stream()
 | 
			
		||||
            .peek(tag -> tag.setCount(0L))
 | 
			
		||||
            .peek(tag -> tag.setCount(0))
 | 
			
		||||
            .collect(toList());
 | 
			
		||||
        assertThat(actualCreatedTags)
 | 
			
		||||
            .isNotNull()
 | 
			
		||||
@@ -570,8 +435,8 @@ public class TagsImplTest
 | 
			
		||||
    @Test(expected = EntityNotFoundException.class)
 | 
			
		||||
    public void testGetTagByIdNotFoundValidation()
 | 
			
		||||
    {
 | 
			
		||||
        given(nodesMock.validateNode(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID)).willThrow(EntityNotFoundException.class);
 | 
			
		||||
        objectUnderTest.getTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID, null);
 | 
			
		||||
        given(primaryParentMock.getParentRef()).willReturn(TAG_NODE_REF);
 | 
			
		||||
        objectUnderTest.getTag(STORE_REF_WORKSPACE_SPACESSTORE,TAG_ID);
 | 
			
		||||
        then(nodeServiceMock).shouldHaveNoInteractions();
 | 
			
		||||
        then(nodesMock).should().validateNode(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
 | 
			
		||||
        then(nodesMock).shouldHaveNoMoreInteractions();
 | 
			
		||||
@@ -594,8 +459,8 @@ public class TagsImplTest
 | 
			
		||||
 | 
			
		||||
        List<Tag> actual = objectUnderTest.addTags(CONTENT_NODE_ID, tags, parametersMock);
 | 
			
		||||
 | 
			
		||||
        final List<Tag> expected = List.of(Tag.builder().tag("taga").nodeRef(tagNodeA).count(5L).create(),
 | 
			
		||||
                Tag.builder().tag("tagb").nodeRef(tagNodeB).count(1L).create());
 | 
			
		||||
        final List<Tag> expected = List.of(Tag.builder().tag("taga").nodeRef(tagNodeA).count(5).create(),
 | 
			
		||||
                Tag.builder().tag("tagb").nodeRef(tagNodeB).count(1).create());
 | 
			
		||||
        assertEquals("Unexpected tags returned.", expected, actual);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -643,53 +508,6 @@ public class TagsImplTest
 | 
			
		||||
        objectUnderTest.getTags(CONTENT_NODE_ID, parametersMock);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testChangeTag()
 | 
			
		||||
    {
 | 
			
		||||
        Tag suppliedTag = Tag.builder().tag("new-name").create();
 | 
			
		||||
        given(taggingServiceMock.changeTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME, "new-name")).willReturn(TAG_NODE_REF);
 | 
			
		||||
 | 
			
		||||
        Tag tag = objectUnderTest.changeTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID, suppliedTag, parametersMock);
 | 
			
		||||
 | 
			
		||||
        Tag expected = Tag.builder().nodeRef(TAG_NODE_REF).tag("new-name").create();
 | 
			
		||||
        assertEquals("Unexpected return value", expected, tag);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testChangeTagAndGetCount()
 | 
			
		||||
    {
 | 
			
		||||
        Tag suppliedTag = Tag.builder().tag("new-name").create();
 | 
			
		||||
        given(taggingServiceMock.changeTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME, "new-name")).willReturn(TAG_NODE_REF);
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of(PARAM_INCLUDE_COUNT));
 | 
			
		||||
        given(taggingServiceMock.findCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME)).willReturn(3L);
 | 
			
		||||
 | 
			
		||||
        Tag tag = objectUnderTest.changeTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID, suppliedTag, parametersMock);
 | 
			
		||||
 | 
			
		||||
        Tag expected = Tag.builder().nodeRef(TAG_NODE_REF).tag("new-name").count(3L).create();
 | 
			
		||||
        assertEquals("Unexpected return value", expected, tag);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetTag()
 | 
			
		||||
    {
 | 
			
		||||
        Tag tag = objectUnderTest.getTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID, parametersMock);
 | 
			
		||||
 | 
			
		||||
        Tag expected = Tag.builder().nodeRef(TAG_NODE_REF).tag(TAG_NAME).create();
 | 
			
		||||
        assertEquals("Unexpected tag returned", expected, tag);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetTagWithCount()
 | 
			
		||||
    {
 | 
			
		||||
        given(parametersMock.getInclude()).willReturn(List.of(PARAM_INCLUDE_COUNT));
 | 
			
		||||
        given(taggingServiceMock.findCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME)).willReturn(0L);
 | 
			
		||||
 | 
			
		||||
        Tag tag = objectUnderTest.getTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID, parametersMock);
 | 
			
		||||
 | 
			
		||||
        Tag expected = Tag.builder().nodeRef(TAG_NODE_REF).tag(TAG_NAME).count(0L).create();
 | 
			
		||||
        assertEquals("Unexpected tag returned", expected, tag);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static List<Pair<String, NodeRef>> createTagAndNodeRefPairs(final List<String> tagNames)
 | 
			
		||||
    {
 | 
			
		||||
        return tagNames.stream()
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.alfresco</groupId>
 | 
			
		||||
        <artifactId>alfresco-community-repo</artifactId>
 | 
			
		||||
        <version>21.2</version>
 | 
			
		||||
        <version>20.143-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2017 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2023 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
@@ -26,10 +26,12 @@
 | 
			
		||||
 | 
			
		||||
package org.alfresco.repo.client.config;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.admin.SysAdminParams;
 | 
			
		||||
import org.alfresco.util.PropertyCheck;
 | 
			
		||||
import org.alfresco.util.UrlUtil;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.context.ApplicationEvent;
 | 
			
		||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +43,7 @@ import java.util.Properties;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.concurrent.ConcurrentMap;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class picks up all the loaded properties passed to it and uses a naming
 | 
			
		||||
@@ -59,25 +62,35 @@ import java.util.concurrent.ConcurrentMap;
 | 
			
		||||
 * for example, <i>sharedLinkBaseUrl</i> and <i>templateAssetsUrl</i> properties, then the following
 | 
			
		||||
 * needs to be put into a properties file.
 | 
			
		||||
 * <ul>
 | 
			
		||||
 * <li>repo.client-app.MyClientName.sharedLinkBaseUrl=http://localhost:8080/MyClientName/s</li>
 | 
			
		||||
 * <li>repo.client-app.MyClientName.templateAssetsUrl=http://localhost:8080/MyClientName/assets</li>
 | 
			
		||||
 * <li>{@code repo.client-app.MyClientName.sharedLinkBaseUrl=http://localhost:8080/MyClientName/s}</li>
 | 
			
		||||
 * <li>{@code repo.client-app.MyClientName.templateAssetsUrl=http://localhost:8080/MyClientName/assets}</li>
 | 
			
		||||
 * </ul>
 | 
			
		||||
 * The default property file is <b>alfresco/client/config/repo-clients-apps.properties</b> which
 | 
			
		||||
 * could be overridden (or add new clients) by <b>alfresco-global</b> properties file.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <b>Note:</b> The {@literal <clientName>Url} is a special property which can be used as a placeholder.
 | 
			
		||||
 * For example,
 | 
			
		||||
 * <ul>
 | 
			
		||||
 * <li>{@code repo.client-app.MyClientName.MyClientNameUrl=${repoBaseUrl}/entrypoint}</li>
 | 
			
		||||
 * <li>{@code repo.client-app.MyClientName.somePropName=${MyClientNameUrl}/some-page}</li>
 | 
			
		||||
 * </ul>
 | 
			
		||||
 * </p>
 | 
			
		||||
 *
 | 
			
		||||
 * @author Jamal Kaabi-Mofrad
 | 
			
		||||
 */
 | 
			
		||||
public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
{
 | 
			
		||||
    private static final Log logger = LogFactory.getLog(ClientAppConfig.class);
 | 
			
		||||
    private static final Logger LOGGER = LoggerFactory.getLogger(ClientAppConfig.class);
 | 
			
		||||
 | 
			
		||||
    public static final String PREFIX = "repo.client-app.";
 | 
			
		||||
    public static final String PROP_TEMPLATE_ASSETS_URL = "templateAssetsUrl";
 | 
			
		||||
 | 
			
		||||
    public static final String SHARE_PLACEHOLDER = "${shareUrl}";
 | 
			
		||||
 | 
			
		||||
    private Properties defaultProperties;
 | 
			
		||||
    private Properties globalProperties;
 | 
			
		||||
 | 
			
		||||
    private ConcurrentMap<String, ClientApp> clients = new ConcurrentHashMap<>();
 | 
			
		||||
    private final ConcurrentMap<String, ClientApp> clients = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
    public ClientAppConfig()
 | 
			
		||||
    {
 | 
			
		||||
@@ -117,6 +130,17 @@ public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
        return clients.get(name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks whether a client with the given name exists or not.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name the name of the client to check
 | 
			
		||||
     * @return true if the client with the given name exists; false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean exists(String name)
 | 
			
		||||
    {
 | 
			
		||||
        return clients.containsKey(name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onBootstrap(ApplicationEvent event)
 | 
			
		||||
    {
 | 
			
		||||
@@ -127,9 +151,9 @@ public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
        processPropertyKeys(mergedProperties, clientsNames, propsNames);
 | 
			
		||||
        clients.putAll(processClients(clientsNames, propsNames, mergedProperties));
 | 
			
		||||
 | 
			
		||||
        if (logger.isDebugEnabled())
 | 
			
		||||
        if (LOGGER.isDebugEnabled())
 | 
			
		||||
        {
 | 
			
		||||
            logger.debug("All bootstrapped repo clients apps: " + clients);
 | 
			
		||||
            LOGGER.debug("All bootstrapped repo clients apps: " + clients);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -232,7 +256,7 @@ public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
            }
 | 
			
		||||
            if (StringUtils.isEmpty(templateAssetsUrl) && config.isEmpty())
 | 
			
		||||
            {
 | 
			
		||||
                logger.warn("Client-app [" + clientName + "] can not be registered as it needs at least one property with a valid value.");
 | 
			
		||||
                LOGGER.warn("Client-app [" + clientName + "] can not be registered as it needs at least one property with a valid value.");
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // As the required values are valid, create the client data
 | 
			
		||||
@@ -275,7 +299,7 @@ public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
    private void logMalformedPropertyKey(String propName, String reason)
 | 
			
		||||
    {
 | 
			
		||||
        reason = (StringUtils.isBlank(reason)) ? "" : " " + reason;
 | 
			
		||||
        logger.warn("Ignoring client app config (malformed property key) [" + propName + "]." + reason);
 | 
			
		||||
        LOGGER.warn("Ignoring client app config (malformed property key) [" + propName + "]." + reason);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void logMalformedPropertyKey(String propName)
 | 
			
		||||
@@ -285,9 +309,9 @@ public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
 | 
			
		||||
    private void logInvalidPropertyValue(String propName, String propValue)
 | 
			
		||||
    {
 | 
			
		||||
        if (logger.isDebugEnabled())
 | 
			
		||||
        if (LOGGER.isDebugEnabled())
 | 
			
		||||
        {
 | 
			
		||||
            logger.debug("Ignoring client app config (invalid value) [" + propValue + "] for the property:" + propName);
 | 
			
		||||
            LOGGER.debug("Ignoring client app config (invalid value) [" + propValue + "] for the property:" + propName);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -298,13 +322,18 @@ public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
 | 
			
		||||
    public static class ClientApp
 | 
			
		||||
    {
 | 
			
		||||
        private static final Logger LOGGER = LoggerFactory.getLogger(ClientApp.class);
 | 
			
		||||
        private final String name;
 | 
			
		||||
        private final String clientUrlPropKey;
 | 
			
		||||
        private final Pattern clientUrlPlaceholderPattern;
 | 
			
		||||
        private final String templateAssetsUrl;
 | 
			
		||||
        private final Map<String, String> properties;
 | 
			
		||||
 | 
			
		||||
        public ClientApp(String name, String templateAssetsUrl, Map<String, String> properties)
 | 
			
		||||
        {
 | 
			
		||||
            this.name = name;
 | 
			
		||||
            this.clientUrlPropKey = name + "Url"; // E.g. 'workspaceUrl' in 'repo.client-app.workspace.workspaceUrl'
 | 
			
		||||
            this.clientUrlPlaceholderPattern = Pattern.compile("\\$\\{" + clientUrlPropKey + '}'); // E.g. ${workspaceUrl}
 | 
			
		||||
            this.templateAssetsUrl = templateAssetsUrl;
 | 
			
		||||
            this.properties = new HashMap<>(properties);
 | 
			
		||||
        }
 | 
			
		||||
@@ -314,11 +343,81 @@ public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
            return name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getClientUrlPropKey()
 | 
			
		||||
        {
 | 
			
		||||
            return clientUrlPropKey;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getClientUrl()
 | 
			
		||||
        {
 | 
			
		||||
            String url = properties.get(getClientUrlPropKey());
 | 
			
		||||
            if (StringUtils.isEmpty(url) && "share".equals(name))
 | 
			
		||||
            {
 | 
			
		||||
                return SHARE_PLACEHOLDER;
 | 
			
		||||
            }
 | 
			
		||||
            LOGGER.info("INSIDE getClientUrl ****--------------------->"+url);
 | 
			
		||||
            return url;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Resolves the client URL that has one of the following defined URL placeholders:
 | 
			
		||||
         * <ul>
 | 
			
		||||
         *     <li>{@literal ${repoBaseUrl}}</li>
 | 
			
		||||
         *     <li>{@literal ${shareUrl}}</li>
 | 
			
		||||
         * </ul>
 | 
			
		||||
         *
 | 
			
		||||
         * @param sysAdminParams SysAdminParams object to retrieve configurable system parameters
 | 
			
		||||
         * @return the resolved property value if applicable
 | 
			
		||||
         */
 | 
			
		||||
        public String getResolvedClientUrl(SysAdminParams sysAdminParams)
 | 
			
		||||
        {
 | 
			
		||||
            System.out.println("Inside getResolvedClientUrl--------------------->");
 | 
			
		||||
            String resolvedUrl;
 | 
			
		||||
            String clientUrl = getClientUrl();
 | 
			
		||||
            System.out.println("clientUrl is ****--------------------->"+clientUrl);
 | 
			
		||||
            if (!StringUtils.isEmpty(clientUrl) && clientUrl.contains(SHARE_PLACEHOLDER))
 | 
			
		||||
            {
 | 
			
		||||
                resolvedUrl = UrlUtil.replaceShareUrlPlaceholder(clientUrl, sysAdminParams);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                resolvedUrl = UrlUtil.replaceRepoBaseUrlPlaceholder(clientUrl, sysAdminParams);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            System.out.println("))))Resolved clientUrl [" + clientUrl + "] to [" + resolvedUrl + "] for the client: " + name);
 | 
			
		||||
            return resolvedUrl;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Pattern getClientUrlPlaceholderPattern()
 | 
			
		||||
        {
 | 
			
		||||
            return clientUrlPlaceholderPattern;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getTemplateAssetsUrl()
 | 
			
		||||
        {
 | 
			
		||||
            return templateAssetsUrl;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Resolves the template assets URL that has the following defined URL placeholder:
 | 
			
		||||
         * <ul>
 | 
			
		||||
         *     <li>{@literal ${<clientName>Url}}</li>
 | 
			
		||||
         * </ul>
 | 
			
		||||
         *
 | 
			
		||||
         * @param sysAdminParams SysAdminParams object to retrieve configurable system parameters
 | 
			
		||||
         * @return the resolved property value if applicable
 | 
			
		||||
         */
 | 
			
		||||
        public String getResolvedTemplateAssetsUrl(SysAdminParams sysAdminParams)
 | 
			
		||||
        {
 | 
			
		||||
            String resolvedUrl = UrlUtil.replaceUrlPlaceholder(getClientUrlPlaceholderPattern(), getTemplateAssetsUrl(),
 | 
			
		||||
                                                               getResolvedClientUrl(sysAdminParams));
 | 
			
		||||
 | 
			
		||||
            LOGGER.debug("Resolved template assets [" + getTemplateAssetsUrl() + "] URL to [" + resolvedUrl
 | 
			
		||||
                                     + "] for the client: " + name);
 | 
			
		||||
 | 
			
		||||
            return UrlUtil.replaceRepoBaseUrlPlaceholder(resolvedUrl, sysAdminParams);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Map<String, String> getProperties()
 | 
			
		||||
        {
 | 
			
		||||
            return Collections.unmodifiableMap(properties);
 | 
			
		||||
@@ -329,6 +428,27 @@ public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
            return properties.get(propName);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Resolves the property that has the following defined URL placeholder:
 | 
			
		||||
         * <ul>
 | 
			
		||||
         *     <li>{@literal ${<clientName>Url}}</li>
 | 
			
		||||
         * </ul>
 | 
			
		||||
         *
 | 
			
		||||
         * @param propName       the property name to search for
 | 
			
		||||
         * @param sysAdminParams SysAdminParams object to retrieve configurable system parameters
 | 
			
		||||
         * @return the resolved property value if applicable
 | 
			
		||||
         */
 | 
			
		||||
        public String getResolvedProperty(String propName, SysAdminParams sysAdminParams)
 | 
			
		||||
        {
 | 
			
		||||
            String property = getProperty(propName);
 | 
			
		||||
            String resolvedProp = UrlUtil.replaceUrlPlaceholder(getClientUrlPlaceholderPattern(), property,
 | 
			
		||||
                                                                getResolvedClientUrl(sysAdminParams));
 | 
			
		||||
 | 
			
		||||
            LOGGER.debug("Resolved [" + property + "] URL to [" + resolvedProp + "] for the [" + propName
 | 
			
		||||
                                     + "] property of the client: " + name);
 | 
			
		||||
            return resolvedProp;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean equals(Object o)
 | 
			
		||||
        {
 | 
			
		||||
@@ -356,6 +476,8 @@ public class ClientAppConfig extends AbstractLifecycleBean
 | 
			
		||||
        {
 | 
			
		||||
            final StringBuilder sb = new StringBuilder(250);
 | 
			
		||||
            sb.append("ClientApp [name=").append(name)
 | 
			
		||||
                        .append(", clientUrlPropKey=").append(clientUrlPropKey)
 | 
			
		||||
                        .append(", clientUrlPlaceholderPattern=").append(clientUrlPlaceholderPattern.pattern())
 | 
			
		||||
                        .append(", templateAssetsUrl=").append(templateAssetsUrl)
 | 
			
		||||
                        .append(", properties=").append(properties)
 | 
			
		||||
                        .append(']');
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2023 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
@@ -1875,7 +1875,6 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli
 | 
			
		||||
     * @param siteName
 | 
			
		||||
     * @param role
 | 
			
		||||
     * @param runAsUser
 | 
			
		||||
     * @param siteService
 | 
			
		||||
     * @param overrideExisting
 | 
			
		||||
     */
 | 
			
		||||
    public void addSiteMembership(final String invitee, final String siteName, final String role, final String runAsUser, final boolean overrideExisting)
 | 
			
		||||
@@ -2111,11 +2110,11 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli
 | 
			
		||||
        workflowProps.put(WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_TYPE, resourceType.toString());
 | 
			
		||||
 | 
			
		||||
        workflowProps.put(WorkflowModelModeratedInvitation.WF_PROP_CLIENT_NAME, clientName);
 | 
			
		||||
        if(clientName != null && clientAppConfig.getClient(clientName) != null)
 | 
			
		||||
        if(clientName != null && clientAppConfig.exists(clientName))
 | 
			
		||||
        {
 | 
			
		||||
            ClientAppConfig.ClientApp client = clientAppConfig.getClient(clientName);
 | 
			
		||||
            workflowProps.put(WorkflowModelModeratedInvitation.WF_TEMPLATE_ASSETS_URL, client.getTemplateAssetsUrl());
 | 
			
		||||
            workflowProps.put(WorkflowModelModeratedInvitation.WF_WORKSPACE_URL,  client.getProperty("workspaceUrl"));
 | 
			
		||||
            workflowProps.put(WorkflowModelModeratedInvitation.WF_TEMPLATE_ASSETS_URL, client.getResolvedTemplateAssetsUrl(sysAdminParams));
 | 
			
		||||
            workflowProps.put(WorkflowModelModeratedInvitation.WF_WORKSPACE_URL,  client.getResolvedClientUrl(sysAdminParams));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // get the moderated workflow
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2023 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software. 
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of 
 | 
			
		||||
@@ -72,7 +72,7 @@ public class SendModeratedInviteDelegate extends AbstractInvitationDelegate
 | 
			
		||||
        Map<String, Object> variables = execution.getVariables();
 | 
			
		||||
        String clientName = (String) variables.get(WorkflowModelModeratedInvitation.wfVarClientName);
 | 
			
		||||
 | 
			
		||||
        if(clientName != null && clientAppConfig.getClient(clientName) != null)
 | 
			
		||||
        if(clientName != null && clientAppConfig.exists(clientName))
 | 
			
		||||
        {
 | 
			
		||||
            ClientAppConfig.ClientApp clientApp = clientAppConfig.getClient(clientName);
 | 
			
		||||
            final String path = clientApp.getProperty("inviteModeratedTemplatePath");
 | 
			
		||||
 
 | 
			
		||||
@@ -23,225 +23,225 @@
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.repo.jscript;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.query.PagingRequest;
 | 
			
		||||
import org.alfresco.service.ServiceRegistry;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.StoreRef;
 | 
			
		||||
import org.alfresco.service.cmr.search.CategoryService;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.mozilla.javascript.Context;
 | 
			
		||||
import org.mozilla.javascript.Scriptable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Support class for finding categories, finding root nodes for categories and creating root categories. 
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Andy Hind
 | 
			
		||||
 */
 | 
			
		||||
public final class Classification extends BaseScopableProcessorExtension
 | 
			
		||||
{
 | 
			
		||||
    private ServiceRegistry services;
 | 
			
		||||
 | 
			
		||||
    private StoreRef storeRef;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the default store reference
 | 
			
		||||
     * 
 | 
			
		||||
     * @param   storeRef the default store reference
 | 
			
		||||
     */
 | 
			
		||||
    public void setStoreUrl(String storeRef)
 | 
			
		||||
    {
 | 
			
		||||
        this.storeRef = new StoreRef(storeRef);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the service registry
 | 
			
		||||
     * 
 | 
			
		||||
     * @param services  the service registry
 | 
			
		||||
     */
 | 
			
		||||
    public void setServiceRegistry(ServiceRegistry services)
 | 
			
		||||
    {
 | 
			
		||||
        this.services = services;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find all the category nodes in a given classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect String
 | 
			
		||||
     * @return Scriptable
 | 
			
		||||
     */
 | 
			
		||||
    public Scriptable getAllCategoryNodes(String aspect)
 | 
			
		||||
    {
 | 
			
		||||
        Object[] cats = buildCategoryNodes(services.getCategoryService().getCategories(
 | 
			
		||||
                            storeRef, createQName(aspect), CategoryService.Depth.ANY));
 | 
			
		||||
        return Context.getCurrentContext().newArray(getScope(), cats);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all the aspects that define a classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @return String[]
 | 
			
		||||
     */
 | 
			
		||||
    public String[] getAllClassificationAspects()
 | 
			
		||||
    {
 | 
			
		||||
        Collection<QName> aspects = services.getCategoryService().getClassificationAspects();
 | 
			
		||||
        String[] answer = new String[aspects.size()];
 | 
			
		||||
        int i = 0;
 | 
			
		||||
        for (QName qname : aspects)
 | 
			
		||||
        {
 | 
			
		||||
            answer[i++] = qname.toPrefixString(this.services.getNamespaceService());
 | 
			
		||||
        }
 | 
			
		||||
        return answer;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a root category in a classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect String
 | 
			
		||||
     * @param name String
 | 
			
		||||
     */
 | 
			
		||||
    public CategoryNode createRootCategory(String aspect, String name)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef categoryNodeRef = services.getCategoryService().createRootCategory(storeRef, createQName(aspect), name);
 | 
			
		||||
        CategoryNode categoryNode = new CategoryNode(categoryNodeRef, this.services, getScope());
 | 
			
		||||
 | 
			
		||||
        return categoryNode;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the category node from the category node reference.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param categoryRef   category node reference
 | 
			
		||||
     * @return {@link CategoryNode} category node 
 | 
			
		||||
     */
 | 
			
		||||
    public CategoryNode getCategory(String categoryRef)
 | 
			
		||||
    {
 | 
			
		||||
        CategoryNode result = null;
 | 
			
		||||
        NodeRef categoryNodeRef = new NodeRef(categoryRef);
 | 
			
		||||
        if (services.getNodeService().exists(categoryNodeRef) == true &&
 | 
			
		||||
            services.getDictionaryService().isSubClass(ContentModel.TYPE_CATEGORY, services.getNodeService().getType(categoryNodeRef)) == true)
 | 
			
		||||
        {
 | 
			
		||||
            result = new CategoryNode(categoryNodeRef, this.services, getScope());
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the root categories in a classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect String
 | 
			
		||||
     * @return Scriptable
 | 
			
		||||
     */
 | 
			
		||||
    public Scriptable getRootCategories(String aspect)
 | 
			
		||||
    {
 | 
			
		||||
        Object[] cats = buildCategoryNodes(services.getCategoryService().getRootCategories(
 | 
			
		||||
                            storeRef, createQName(aspect)));
 | 
			
		||||
        return Context.getCurrentContext().newArray(getScope(), cats);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get ordered, filtered and paged root categories in a classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect
 | 
			
		||||
     * @param filter
 | 
			
		||||
     * @param maxItems
 | 
			
		||||
     * @param skipCount (offset)
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public Scriptable getRootCategories(String aspect, String filter, int maxItems, int skipCount)
 | 
			
		||||
    {
 | 
			
		||||
        PagingRequest pagingRequest = new PagingRequest(skipCount, maxItems);
 | 
			
		||||
        List<ChildAssociationRef> rootCategories = services.getCategoryService().getRootCategories(storeRef, createQName(aspect), pagingRequest, true, filter).getPage();
 | 
			
		||||
        Object[] cats = buildCategoryNodes(rootCategories);
 | 
			
		||||
        return Context.getCurrentContext().newArray(getScope(), cats);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the category usage count.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect String
 | 
			
		||||
     * @param maxCount int
 | 
			
		||||
     * @return Scriptable
 | 
			
		||||
     */
 | 
			
		||||
    public Scriptable getCategoryUsage(String aspect, int maxCount)
 | 
			
		||||
    {
 | 
			
		||||
        List<Pair<NodeRef, Integer>> topCats = services.getCategoryService().getTopCategories(storeRef, createQName(aspect), maxCount);
 | 
			
		||||
        Object[] tags = new Object[topCats.size()];
 | 
			
		||||
        int i = 0;
 | 
			
		||||
        for (Pair<NodeRef, Integer> topCat : topCats)
 | 
			
		||||
        {
 | 
			
		||||
           tags[i++] = new Tag(new CategoryNode(topCat.getFirst(), this.services, getScope()), topCat.getSecond());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return Context.getCurrentContext().newArray(getScope(), tags);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Build category nodes.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param cars  list of associations to category nodes
 | 
			
		||||
     * @return {@link Object}[] array of category nodes
 | 
			
		||||
     */
 | 
			
		||||
    private Object[] buildCategoryNodes(Collection<ChildAssociationRef> cars)
 | 
			
		||||
    {
 | 
			
		||||
        Object[] categoryNodes = new Object[cars.size()];
 | 
			
		||||
        int i = 0;
 | 
			
		||||
        for (ChildAssociationRef car : cars)
 | 
			
		||||
        {
 | 
			
		||||
            categoryNodes[i++] = new CategoryNode(car.getChildRef(), this.services, getScope());
 | 
			
		||||
        }
 | 
			
		||||
        return categoryNodes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create QName from string
 | 
			
		||||
     * 
 | 
			
		||||
     * @param s QName string value
 | 
			
		||||
     * @return {@link QName} qualified name object    
 | 
			
		||||
     */
 | 
			
		||||
    private QName createQName(String s)
 | 
			
		||||
    {
 | 
			
		||||
        QName qname;
 | 
			
		||||
        if (s.indexOf(QName.NAMESPACE_BEGIN) != -1)
 | 
			
		||||
        {
 | 
			
		||||
            qname = QName.createQName(s);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            qname = QName.createQName(s, this.services.getNamespaceService());
 | 
			
		||||
        }
 | 
			
		||||
        return qname;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Tag class returned from getCategoryUsage().
 | 
			
		||||
     */
 | 
			
		||||
    public final class Tag
 | 
			
		||||
    {
 | 
			
		||||
        private CategoryNode categoryNode;
 | 
			
		||||
        private int frequency = 0;
 | 
			
		||||
       
 | 
			
		||||
        public Tag(CategoryNode categoryNode, int frequency)
 | 
			
		||||
        {
 | 
			
		||||
            this.categoryNode = categoryNode;
 | 
			
		||||
            this.frequency = frequency;
 | 
			
		||||
        }
 | 
			
		||||
       
 | 
			
		||||
        public CategoryNode getCategory()
 | 
			
		||||
        {
 | 
			
		||||
            return categoryNode;
 | 
			
		||||
        }
 | 
			
		||||
       
 | 
			
		||||
        public int getFrequency()
 | 
			
		||||
        {
 | 
			
		||||
            return frequency;
 | 
			
		||||
        }
 | 
			
		||||
     }
 | 
			
		||||
}
 | 
			
		||||
package org.alfresco.repo.jscript;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.query.PagingRequest;
 | 
			
		||||
import org.alfresco.service.ServiceRegistry;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.StoreRef;
 | 
			
		||||
import org.alfresco.service.cmr.search.CategoryService;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.mozilla.javascript.Context;
 | 
			
		||||
import org.mozilla.javascript.Scriptable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Support class for finding categories, finding root nodes for categories and creating root categories. 
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Andy Hind
 | 
			
		||||
 */
 | 
			
		||||
public final class Classification extends BaseScopableProcessorExtension
 | 
			
		||||
{
 | 
			
		||||
    private ServiceRegistry services;
 | 
			
		||||
 | 
			
		||||
    private StoreRef storeRef;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the default store reference
 | 
			
		||||
     * 
 | 
			
		||||
     * @param   storeRef the default store reference
 | 
			
		||||
     */
 | 
			
		||||
    public void setStoreUrl(String storeRef)
 | 
			
		||||
    {
 | 
			
		||||
        this.storeRef = new StoreRef(storeRef);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the service registry
 | 
			
		||||
     * 
 | 
			
		||||
     * @param services  the service registry
 | 
			
		||||
     */
 | 
			
		||||
    public void setServiceRegistry(ServiceRegistry services)
 | 
			
		||||
    {
 | 
			
		||||
        this.services = services;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find all the category nodes in a given classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect String
 | 
			
		||||
     * @return Scriptable
 | 
			
		||||
     */
 | 
			
		||||
    public Scriptable getAllCategoryNodes(String aspect)
 | 
			
		||||
    {
 | 
			
		||||
        Object[] cats = buildCategoryNodes(services.getCategoryService().getCategories(
 | 
			
		||||
                            storeRef, createQName(aspect), CategoryService.Depth.ANY));
 | 
			
		||||
        return Context.getCurrentContext().newArray(getScope(), cats);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all the aspects that define a classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @return String[]
 | 
			
		||||
     */
 | 
			
		||||
    public String[] getAllClassificationAspects()
 | 
			
		||||
    {
 | 
			
		||||
        Collection<QName> aspects = services.getCategoryService().getClassificationAspects();
 | 
			
		||||
        String[] answer = new String[aspects.size()];
 | 
			
		||||
        int i = 0;
 | 
			
		||||
        for (QName qname : aspects)
 | 
			
		||||
        {
 | 
			
		||||
            answer[i++] = qname.toPrefixString(this.services.getNamespaceService());
 | 
			
		||||
        }
 | 
			
		||||
        return answer;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a root category in a classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect String
 | 
			
		||||
     * @param name String
 | 
			
		||||
     */
 | 
			
		||||
    public CategoryNode createRootCategory(String aspect, String name)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef categoryNodeRef = services.getCategoryService().createRootCategory(storeRef, createQName(aspect), name);
 | 
			
		||||
        CategoryNode categoryNode = new CategoryNode(categoryNodeRef, this.services, getScope());
 | 
			
		||||
 | 
			
		||||
        return categoryNode;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the category node from the category node reference.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param categoryRef   category node reference
 | 
			
		||||
     * @return {@link CategoryNode} category node 
 | 
			
		||||
     */
 | 
			
		||||
    public CategoryNode getCategory(String categoryRef)
 | 
			
		||||
    {
 | 
			
		||||
        CategoryNode result = null;
 | 
			
		||||
        NodeRef categoryNodeRef = new NodeRef(categoryRef);
 | 
			
		||||
        if (services.getNodeService().exists(categoryNodeRef) == true &&
 | 
			
		||||
            services.getDictionaryService().isSubClass(ContentModel.TYPE_CATEGORY, services.getNodeService().getType(categoryNodeRef)) == true)
 | 
			
		||||
        {
 | 
			
		||||
            result = new CategoryNode(categoryNodeRef, this.services, getScope());
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the root categories in a classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect String
 | 
			
		||||
     * @return Scriptable
 | 
			
		||||
     */
 | 
			
		||||
    public Scriptable getRootCategories(String aspect)
 | 
			
		||||
    {
 | 
			
		||||
        Object[] cats = buildCategoryNodes(services.getCategoryService().getRootCategories(
 | 
			
		||||
                            storeRef, createQName(aspect)));
 | 
			
		||||
        return Context.getCurrentContext().newArray(getScope(), cats);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get ordered, filtered and paged root categories in a classification.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect
 | 
			
		||||
     * @param filter
 | 
			
		||||
     * @param maxItems
 | 
			
		||||
     * @param skipCount (offset)
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public Scriptable getRootCategories(String aspect, String filter, int maxItems, int skipCount)
 | 
			
		||||
    {
 | 
			
		||||
        PagingRequest pagingRequest = new PagingRequest(skipCount, maxItems);
 | 
			
		||||
        List<ChildAssociationRef> rootCategories = services.getCategoryService().getRootCategories(storeRef, createQName(aspect), pagingRequest, true, filter).getPage();
 | 
			
		||||
        Object[] cats = buildCategoryNodes(rootCategories);
 | 
			
		||||
        return Context.getCurrentContext().newArray(getScope(), cats);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the category usage count.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param aspect String
 | 
			
		||||
     * @param maxCount int
 | 
			
		||||
     * @return Scriptable
 | 
			
		||||
     */
 | 
			
		||||
    public Scriptable getCategoryUsage(String aspect, int maxCount)
 | 
			
		||||
    {
 | 
			
		||||
        List<Pair<NodeRef, Integer>> topCats = services.getCategoryService().getTopCategories(storeRef, createQName(aspect), maxCount);
 | 
			
		||||
        Object[] tags = new Object[topCats.size()];
 | 
			
		||||
        int i = 0;
 | 
			
		||||
        for (Pair<NodeRef, Integer> topCat : topCats)
 | 
			
		||||
        {
 | 
			
		||||
           tags[i++] = new Tag(new CategoryNode(topCat.getFirst(), this.services, getScope()), topCat.getSecond());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return Context.getCurrentContext().newArray(getScope(), tags);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Build category nodes.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param cars  list of associations to category nodes
 | 
			
		||||
     * @return {@link Object}[] array of category nodes
 | 
			
		||||
     */
 | 
			
		||||
    private Object[] buildCategoryNodes(Collection<ChildAssociationRef> cars)
 | 
			
		||||
    {
 | 
			
		||||
        Object[] categoryNodes = new Object[cars.size()];
 | 
			
		||||
        int i = 0;
 | 
			
		||||
        for (ChildAssociationRef car : cars)
 | 
			
		||||
        {
 | 
			
		||||
            categoryNodes[i++] = new CategoryNode(car.getChildRef(), this.services, getScope());
 | 
			
		||||
        }
 | 
			
		||||
        return categoryNodes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create QName from string
 | 
			
		||||
     * 
 | 
			
		||||
     * @param s QName string value
 | 
			
		||||
     * @return {@link QName} qualified name object    
 | 
			
		||||
     */
 | 
			
		||||
    private QName createQName(String s)
 | 
			
		||||
    {
 | 
			
		||||
        QName qname;
 | 
			
		||||
        if (s.indexOf(QName.NAMESPACE_BEGIN) != -1)
 | 
			
		||||
        {
 | 
			
		||||
            qname = QName.createQName(s);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            qname = QName.createQName(s, this.services.getNamespaceService());
 | 
			
		||||
        }
 | 
			
		||||
        return qname;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Tag class returned from getCategoryUsage().
 | 
			
		||||
     */
 | 
			
		||||
    public final class Tag
 | 
			
		||||
    {
 | 
			
		||||
        private CategoryNode categoryNode;
 | 
			
		||||
        private int frequency = 0;
 | 
			
		||||
       
 | 
			
		||||
        public Tag(CategoryNode categoryNode, int frequency)
 | 
			
		||||
        {
 | 
			
		||||
            this.categoryNode = categoryNode;
 | 
			
		||||
            this.frequency = frequency;
 | 
			
		||||
        }
 | 
			
		||||
       
 | 
			
		||||
        public CategoryNode getCategory()
 | 
			
		||||
        {
 | 
			
		||||
            return categoryNode;
 | 
			
		||||
        }
 | 
			
		||||
       
 | 
			
		||||
        public int getFrequency()
 | 
			
		||||
        {
 | 
			
		||||
            return frequency;
 | 
			
		||||
        }
 | 
			
		||||
     }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,23 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2017 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2023 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%
 | 
			
		||||
@@ -28,17 +28,15 @@ package org.alfresco.repo.quickshare;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.sync.events.types.ActivityEvent;
 | 
			
		||||
import org.alfresco.sync.events.types.Event;
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.model.QuickShareModel;
 | 
			
		||||
import org.alfresco.sync.repo.Client;
 | 
			
		||||
@@ -51,7 +49,6 @@ import org.alfresco.repo.copy.CopyBehaviourCallback;
 | 
			
		||||
import org.alfresco.repo.copy.CopyDetails;
 | 
			
		||||
import org.alfresco.repo.copy.CopyServicePolicies;
 | 
			
		||||
import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback;
 | 
			
		||||
import org.alfresco.sync.repo.events.EventPreparator;
 | 
			
		||||
import org.alfresco.sync.repo.events.EventPublisher;
 | 
			
		||||
import org.alfresco.repo.node.NodeServicePolicies;
 | 
			
		||||
import org.alfresco.repo.policy.BehaviourFilter;
 | 
			
		||||
@@ -101,7 +98,6 @@ import org.alfresco.util.EqualsHelper;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.alfresco.util.ParameterCheck;
 | 
			
		||||
import org.alfresco.util.PropertyCheck;
 | 
			
		||||
import org.alfresco.util.UrlUtil;
 | 
			
		||||
import org.apache.commons.codec.binary.Base64;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
@@ -113,16 +109,16 @@ import org.safehaus.uuid.UUIDGenerator;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * QuickShare Service implementation.
 | 
			
		||||
 * 
 | 
			
		||||
 * <p>
 | 
			
		||||
 * In addition to the quick share service, this class also provides a BeforeDeleteNodePolicy and
 | 
			
		||||
 * OnCopyNodePolicy for content with the QuickShare aspect.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Alex Miller, janv, Jamal Kaabi-Mofrad
 | 
			
		||||
 */
 | 
			
		||||
public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
            NodeServicePolicies.BeforeDeleteNodePolicy,
 | 
			
		||||
            CopyServicePolicies.OnCopyNodePolicy,
 | 
			
		||||
            NodeServicePolicies.OnRestoreNodePolicy
 | 
			
		||||
                                              NodeServicePolicies.BeforeDeleteNodePolicy,
 | 
			
		||||
                                              CopyServicePolicies.OnCopyNodePolicy,
 | 
			
		||||
                                              NodeServicePolicies.OnRestoreNodePolicy
 | 
			
		||||
{
 | 
			
		||||
    private static final Log logger = LogFactory.getLog(QuickShareServiceImpl.class);
 | 
			
		||||
 | 
			
		||||
@@ -134,7 +130,6 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
    private static final String FTL_SENDER_FIRST_NAME = "sender_first_name";
 | 
			
		||||
    private static final String FTL_SENDER_LAST_NAME = "sender_last_name";
 | 
			
		||||
    private static final String FTL_TEMPLATE_ASSETS_URL = "template_assets_url";
 | 
			
		||||
 | 
			
		||||
    private static final String CONFIG_SHARED_LINK_BASE_URL = "sharedLinkBaseUrl";
 | 
			
		||||
    private static final String DEFAULT_EMAIL_SUBJECT = "quickshare.notifier.email.subject";
 | 
			
		||||
    private static final String EMAIL_TEMPLATE_REF ="alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl";
 | 
			
		||||
@@ -177,9 +172,9 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
     */
 | 
			
		||||
    public void setDictionaryService(DictionaryService dictionaryService)
 | 
			
		||||
    {
 | 
			
		||||
        this.dictionaryService = dictionaryService;	
 | 
			
		||||
        this.dictionaryService = dictionaryService;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the node service
 | 
			
		||||
     */
 | 
			
		||||
@@ -195,15 +190,15 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
    {
 | 
			
		||||
        this.permissionService = permissionService;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the person service 
 | 
			
		||||
     * Set the person service
 | 
			
		||||
     */
 | 
			
		||||
    public void setPersonService(PersonService personService) 
 | 
			
		||||
    public void setPersonService(PersonService personService)
 | 
			
		||||
    {
 | 
			
		||||
        this.personService = personService;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the policy component
 | 
			
		||||
     */
 | 
			
		||||
@@ -215,11 +210,11 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the tenant service
 | 
			
		||||
     */
 | 
			
		||||
    public void setTenantService(TenantService tenantService) 
 | 
			
		||||
    public void setTenantService(TenantService tenantService)
 | 
			
		||||
    {
 | 
			
		||||
        this.tenantService = tenantService;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the thumbnail service
 | 
			
		||||
     */
 | 
			
		||||
@@ -227,7 +222,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
    {
 | 
			
		||||
        this.thumbnailService = thumbnailService;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the eventPublisher
 | 
			
		||||
     */
 | 
			
		||||
@@ -394,14 +389,14 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
 | 
			
		||||
        // Register interest in the beforeDeleteNode policy - note: currently for content only !!
 | 
			
		||||
        policyComponent.bindClassBehaviour(
 | 
			
		||||
                QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
 | 
			
		||||
                ContentModel.TYPE_CONTENT,
 | 
			
		||||
                new JavaBehaviour(this, "beforeDeleteNode"));
 | 
			
		||||
        
 | 
			
		||||
                    QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
 | 
			
		||||
                    ContentModel.TYPE_CONTENT,
 | 
			
		||||
                    new JavaBehaviour(this, "beforeDeleteNode"));
 | 
			
		||||
 | 
			
		||||
        //Register interest in the onCopyNodePolicy to block copying of quick share metadta
 | 
			
		||||
        policyComponent.bindClassBehaviour(
 | 
			
		||||
                    CopyServicePolicies.OnCopyNodePolicy.QNAME, 
 | 
			
		||||
                    QuickShareModel.ASPECT_QSHARE, 
 | 
			
		||||
                    CopyServicePolicies.OnCopyNodePolicy.QNAME,
 | 
			
		||||
                    QuickShareModel.ASPECT_QSHARE,
 | 
			
		||||
                    new JavaBehaviour(this, "getCopyCallback"));
 | 
			
		||||
 | 
			
		||||
        this.policyComponent.bindClassBehaviour(
 | 
			
		||||
@@ -424,7 +419,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
 | 
			
		||||
        //Check the node is the correct type
 | 
			
		||||
        final QName typeQName = nodeService.getType(nodeRef);
 | 
			
		||||
        if (isSharable(typeQName) == false)
 | 
			
		||||
        if (!isSharable(typeQName))
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidNodeRefException(nodeRef);
 | 
			
		||||
        }
 | 
			
		||||
@@ -438,7 +433,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
            UUID uuid = UUIDGenerator.getInstance().generateRandomBasedUUID();
 | 
			
		||||
            sharedId = Base64.encodeBase64URLSafeString(uuid.toByteArray()); // => 22 chars (eg. q3bEKPeDQvmJYgt4hJxOjw)
 | 
			
		||||
 | 
			
		||||
            final Map<QName, Serializable> props = new HashMap<QName, Serializable>(2);
 | 
			
		||||
            final Map<QName, Serializable> props = new HashMap<>(2);
 | 
			
		||||
            props.put(QuickShareModel.PROP_QSHARE_SHAREDID, sharedId);
 | 
			
		||||
            props.put(QuickShareModel.PROP_QSHARE_SHAREDBY, AuthenticationUtil.getRunAsUser());
 | 
			
		||||
 | 
			
		||||
@@ -448,13 +443,9 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                // consumer/contributor should be able to add "shared" aspect (MNT-10366)
 | 
			
		||||
                AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
 | 
			
		||||
                {
 | 
			
		||||
                    public Void doWork()
 | 
			
		||||
                    {
 | 
			
		||||
                        nodeService.addAspect(nodeRef, QuickShareModel.ASPECT_QSHARE, props);
 | 
			
		||||
                        return null;
 | 
			
		||||
                    }
 | 
			
		||||
                AuthenticationUtil.runAsSystem((RunAsWork<Void>) () -> {
 | 
			
		||||
                    nodeService.addAspect(nodeRef, QuickShareModel.ASPECT_QSHARE, props);
 | 
			
		||||
                    return null;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
@@ -464,27 +455,19 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
 | 
			
		||||
            final NodeRef tenantNodeRef = tenantService.getName(nodeRef);
 | 
			
		||||
 | 
			
		||||
            TenantUtil.runAsDefaultTenant(new TenantRunAsWork<Void>()
 | 
			
		||||
            {
 | 
			
		||||
                public Void doWork() throws Exception
 | 
			
		||||
                {
 | 
			
		||||
                    attributeService.setAttribute(tenantNodeRef, ATTR_KEY_SHAREDIDS_ROOT, sharedId);
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            TenantUtil.runAsDefaultTenant((TenantRunAsWork<Void>) () -> {
 | 
			
		||||
                attributeService.setAttribute(tenantNodeRef, ATTR_KEY_SHAREDIDS_ROOT, sharedId);
 | 
			
		||||
                return null;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            final StringBuffer sb = new StringBuffer();
 | 
			
		||||
            sb.append("{").append("\"sharedId\":\"").append(sharedId).append("\"").append("}");
 | 
			
		||||
            String sharedIdStr = "{" + "\"sharedId\":\"" + sharedId + "\"" + "}";
 | 
			
		||||
 | 
			
		||||
            eventPublisher.publishEvent(new EventPreparator(){
 | 
			
		||||
                @Override
 | 
			
		||||
                public Event prepareEvent(String user, String networkId, String transactionId)
 | 
			
		||||
                {
 | 
			
		||||
                    return new ActivityEvent("quickshare", transactionId, networkId, user, nodeRef.getId(),
 | 
			
		||||
                                null, typeQName.toString(),  Client.asType(ClientType.webclient), sb.toString(),
 | 
			
		||||
                                null, null, 0l, null);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            eventPublisher.publishEvent(
 | 
			
		||||
                        (user, networkId, transactionId) -> new ActivityEvent("quickshare", transactionId, networkId,
 | 
			
		||||
                                                                              user, nodeRef.getId(), null,
 | 
			
		||||
                                                                              typeQName.toString(),
 | 
			
		||||
                                                                              Client.asType(ClientType.webclient),
 | 
			
		||||
                                                                              sharedIdStr, null, null, 0L, null));
 | 
			
		||||
 | 
			
		||||
            if (logger.isInfoEnabled())
 | 
			
		||||
            {
 | 
			
		||||
@@ -523,26 +506,25 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Is this service enable? 
 | 
			
		||||
     * Is this service enable?
 | 
			
		||||
     * @throws QuickShareDisabledException if it isn't.
 | 
			
		||||
     */
 | 
			
		||||
    private void checkEnabled()
 | 
			
		||||
    {
 | 
			
		||||
        if (enabled == false) 
 | 
			
		||||
        if (!enabled)
 | 
			
		||||
        {
 | 
			
		||||
            throw new QuickShareDisabledException("QuickShare is disabled system-wide");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    @Override
 | 
			
		||||
    public Map<String, Object> getMetaData(NodeRef nodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO This functionality MUST be available when quickshare is also disabled, therefor refactor it out from the quickshare package to a more common package.
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        Map<QName, Serializable> nodeProps = nodeService.getProperties(nodeRef);
 | 
			
		||||
        ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        String modifierUserName = (String)nodeProps.get(ContentModel.PROP_MODIFIER);
 | 
			
		||||
        Map<QName, Serializable> personProps = null;
 | 
			
		||||
        if (modifierUserName != null)
 | 
			
		||||
@@ -557,21 +539,21 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
            }
 | 
			
		||||
            catch (NoSuchPersonException nspe)
 | 
			
		||||
            {
 | 
			
		||||
                // absorb this exception - eg. System (or maybe the user has been deleted)
 | 
			
		||||
                // absorb this exception - e.g. System (or maybe the user has been deleted)
 | 
			
		||||
                if (logger.isInfoEnabled())
 | 
			
		||||
                {
 | 
			
		||||
                    logger.info("MetaDataGet - no such person: "+modifierUserName);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Map<String, Object> metadata = new HashMap<String, Object>(8);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        Map<String, Object> metadata = new HashMap<>(8);
 | 
			
		||||
 | 
			
		||||
        metadata.put("nodeRef", nodeRef.toString());
 | 
			
		||||
        metadata.put("name", nodeProps.get(ContentModel.PROP_NAME));
 | 
			
		||||
        metadata.put("title", nodeProps.get(ContentModel.PROP_TITLE));
 | 
			
		||||
        metadata.put("description", nodeProps.get(ContentModel.PROP_DESCRIPTION));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (contentData != null)
 | 
			
		||||
        {
 | 
			
		||||
            metadata.put("mimetype", contentData.getMimetype());
 | 
			
		||||
@@ -581,17 +563,17 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        {
 | 
			
		||||
            metadata.put("size", 0L);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        metadata.put("modified", nodeProps.get(ContentModel.PROP_MODIFIED));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (personProps != null)
 | 
			
		||||
        {
 | 
			
		||||
            metadata.put("modifierFirstName", personProps.get(ContentModel.PROP_FIRSTNAME));
 | 
			
		||||
            metadata.put("modifierLastName", personProps.get(ContentModel.PROP_LASTNAME));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // thumbnail defs for this nodeRef
 | 
			
		||||
        List<String> thumbnailDefs = new ArrayList<String>(7);
 | 
			
		||||
        List<String> thumbnailDefs = new ArrayList<>(7);
 | 
			
		||||
        if (contentData != null)
 | 
			
		||||
        {
 | 
			
		||||
            // Note: thumbnail defs only appear in this list if they can produce a thumbnail for the content
 | 
			
		||||
@@ -604,18 +586,18 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        metadata.put("thumbnailDefinitions", thumbnailDefs);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // thumbnail instances for this nodeRef
 | 
			
		||||
        List<NodeRef> thumbnailRefs = thumbnailService.getThumbnails(nodeRef, ContentModel.PROP_CONTENT, null, null);
 | 
			
		||||
        List<String> thumbnailNames = new ArrayList<String>(thumbnailRefs.size());
 | 
			
		||||
        List<String> thumbnailNames = new ArrayList<>(thumbnailRefs.size());
 | 
			
		||||
        for (NodeRef thumbnailRef : thumbnailRefs)
 | 
			
		||||
        {
 | 
			
		||||
            thumbnailNames.add((String)nodeService.getProperty(thumbnailRef, ContentModel.PROP_NAME));
 | 
			
		||||
        }
 | 
			
		||||
        metadata.put("thumbnailNames", thumbnailNames);
 | 
			
		||||
        
 | 
			
		||||
        metadata.put("lastThumbnailModificationData", (List<String>)nodeProps.get(ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        metadata.put("lastThumbnailModificationData", nodeProps.get(ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA));
 | 
			
		||||
 | 
			
		||||
        if (nodeProps.containsKey(QuickShareModel.PROP_QSHARE_SHAREDID))
 | 
			
		||||
        {
 | 
			
		||||
            metadata.put("sharedId", nodeProps.get(QuickShareModel.PROP_QSHARE_SHAREDID));
 | 
			
		||||
@@ -629,7 +611,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
            metadata.put("sharable", sharable);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Map<String, Object> model = new HashMap<String, Object>(2);
 | 
			
		||||
        Map<String, Object> model = new HashMap<>(2);
 | 
			
		||||
        model.put("item", metadata);
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
@@ -637,13 +619,8 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
    @Override
 | 
			
		||||
    public Pair<String, NodeRef> getTenantNodeRefFromSharedId(final String sharedId)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef nodeRef = TenantUtil.runAsDefaultTenant(new TenantRunAsWork<NodeRef>()
 | 
			
		||||
        {
 | 
			
		||||
            public NodeRef doWork() throws Exception
 | 
			
		||||
            {
 | 
			
		||||
                return (NodeRef) attributeService.getAttribute(ATTR_KEY_SHAREDIDS_ROOT, sharedId);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        NodeRef nodeRef = TenantUtil.runAsDefaultTenant(
 | 
			
		||||
                    () -> (NodeRef) attributeService.getAttribute(ATTR_KEY_SHAREDIDS_ROOT, sharedId));
 | 
			
		||||
 | 
			
		||||
        if (nodeRef == null)
 | 
			
		||||
        {
 | 
			
		||||
@@ -659,7 +636,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
            sp.setQuery(query);
 | 
			
		||||
            sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
 | 
			
		||||
 | 
			
		||||
            List<NodeRef> nodeRefs = null;
 | 
			
		||||
            List<NodeRef> nodeRefs;
 | 
			
		||||
            ResultSet results = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -697,23 +674,19 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        Pair<String, NodeRef> pair = getTenantNodeRefFromSharedId(sharedId);
 | 
			
		||||
        final String tenantDomain = pair.getFirst();
 | 
			
		||||
        final NodeRef nodeRef = pair.getSecond();
 | 
			
		||||
        
 | 
			
		||||
        Map<String, Object> model = TenantUtil.runAsSystemTenant(new TenantRunAsWork<Map<String, Object>>()
 | 
			
		||||
        {
 | 
			
		||||
            public Map<String, Object> doWork() throws Exception
 | 
			
		||||
            {
 | 
			
		||||
                checkQuickShareNode(nodeRef);
 | 
			
		||||
                
 | 
			
		||||
                return getMetaData(nodeRef);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        Map<String, Object> model = TenantUtil.runAsSystemTenant(() -> {
 | 
			
		||||
            checkQuickShareNode(nodeRef);
 | 
			
		||||
 | 
			
		||||
            return getMetaData(nodeRef);
 | 
			
		||||
        }, tenantDomain);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (logger.isDebugEnabled())
 | 
			
		||||
        {
 | 
			
		||||
            logger.debug("QuickShare - retrieved metadata: "+sharedId+" ["+nodeRef+"]["+model+"]");
 | 
			
		||||
        }
 | 
			
		||||
        //model.put("nodeRef", nodeRef)
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -729,44 +702,40 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
    // note: will remove "share" even if node is only being archived (ie. moved to trash) => a subsequent restore will *not* restore the "share"
 | 
			
		||||
    public void beforeDeleteNode(final NodeRef beforeDeleteNodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
 | 
			
		||||
        {
 | 
			
		||||
            public Void doWork() throws Exception
 | 
			
		||||
        AuthenticationUtil.runAsSystem((RunAsWork<Void>) () -> {
 | 
			
		||||
            String sharedId = (String)nodeService.getProperty(beforeDeleteNodeRef, QuickShareModel.PROP_QSHARE_SHAREDID);
 | 
			
		||||
            if (sharedId != null)
 | 
			
		||||
            {
 | 
			
		||||
                String sharedId = (String)nodeService.getProperty(beforeDeleteNodeRef, QuickShareModel.PROP_QSHARE_SHAREDID);
 | 
			
		||||
                if (sharedId != null)
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    Pair<String, NodeRef> pair = getTenantNodeRefFromSharedId(sharedId);
 | 
			
		||||
 | 
			
		||||
                    @SuppressWarnings("unused")
 | 
			
		||||
                    final String tenantDomain = pair.getFirst();
 | 
			
		||||
                    final NodeRef nodeRef = pair.getSecond();
 | 
			
		||||
 | 
			
		||||
                    // note: deleted nodeRef might not match, eg. for upload new version -> checkin -> delete working copy
 | 
			
		||||
                    if (nodeRef.equals(beforeDeleteNodeRef))
 | 
			
		||||
                    {
 | 
			
		||||
                        Pair<String, NodeRef> pair = getTenantNodeRefFromSharedId(sharedId);
 | 
			
		||||
 | 
			
		||||
                        @SuppressWarnings("unused")
 | 
			
		||||
                        final String tenantDomain = pair.getFirst();
 | 
			
		||||
                        final NodeRef nodeRef = pair.getSecond();
 | 
			
		||||
 | 
			
		||||
                        // note: deleted nodeRef might not match, eg. for upload new version -> checkin -> delete working copy
 | 
			
		||||
                        if (nodeRef.equals(beforeDeleteNodeRef))
 | 
			
		||||
                        // Disable audit to preserve modifier and modified date
 | 
			
		||||
                        behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            // Disable audit to preserve modifier and modified date
 | 
			
		||||
                            behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                            try
 | 
			
		||||
                            {
 | 
			
		||||
                                nodeService.removeAspect(nodeRef, QuickShareModel.ASPECT_QSHARE);
 | 
			
		||||
                            }
 | 
			
		||||
                            finally
 | 
			
		||||
                            {
 | 
			
		||||
                                behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                            }
 | 
			
		||||
                            removeSharedId(sharedId);
 | 
			
		||||
                            nodeService.removeAspect(nodeRef, QuickShareModel.ASPECT_QSHARE);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (InvalidSharedIdException ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.warn("Couldn't find shareId, " + sharedId + ", attributes for node " + beforeDeleteNodeRef);
 | 
			
		||||
                        finally
 | 
			
		||||
                        {
 | 
			
		||||
                            behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                        }
 | 
			
		||||
                        removeSharedId(sharedId);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
                catch (InvalidSharedIdException ex)
 | 
			
		||||
                {
 | 
			
		||||
                    logger.warn("Couldn't find shareId, " + sharedId + ", attributes for node " + beforeDeleteNodeRef);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -778,37 +747,29 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
    public void onRestoreNode(ChildAssociationRef childAssocRef)
 | 
			
		||||
    {
 | 
			
		||||
        final NodeRef childNodeRef = childAssocRef.getChildRef();
 | 
			
		||||
        AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
 | 
			
		||||
        {
 | 
			
		||||
            public Void doWork() throws Exception
 | 
			
		||||
        AuthenticationUtil.runAsSystem((RunAsWork<Void>) () -> {
 | 
			
		||||
            if (nodeService.hasAspect(childNodeRef, QuickShareModel.ASPECT_QSHARE))
 | 
			
		||||
            {
 | 
			
		||||
                if (nodeService.hasAspect(childNodeRef, QuickShareModel.ASPECT_QSHARE))
 | 
			
		||||
                // Disable audit to preserve modifier and modified date
 | 
			
		||||
                behaviourFilter.disableBehaviour(childNodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    // Disable audit to preserve modifier and modified date
 | 
			
		||||
                    behaviourFilter.disableBehaviour(childNodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        nodeService.removeAspect(childNodeRef, QuickShareModel.ASPECT_QSHARE);
 | 
			
		||||
                    }
 | 
			
		||||
                    finally
 | 
			
		||||
                    {
 | 
			
		||||
                        behaviourFilter.enableBehaviour(childNodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                    }
 | 
			
		||||
                    nodeService.removeAspect(childNodeRef, QuickShareModel.ASPECT_QSHARE);
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    behaviourFilter.enableBehaviour(childNodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void removeSharedId(final String sharedId)
 | 
			
		||||
    {
 | 
			
		||||
        TenantUtil.runAsDefaultTenant(new TenantRunAsWork<Void>()
 | 
			
		||||
        {
 | 
			
		||||
            public Void doWork() throws Exception
 | 
			
		||||
            {
 | 
			
		||||
                attributeService.removeAttribute(ATTR_KEY_SHAREDIDS_ROOT, sharedId);
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        TenantUtil.runAsDefaultTenant((TenantRunAsWork<Void>) () -> {
 | 
			
		||||
            attributeService.removeAttribute(ATTR_KEY_SHAREDIDS_ROOT, sharedId);
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
@@ -832,40 +793,36 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        Pair<String, NodeRef> pair = getTenantNodeRefFromSharedId(sharedId);
 | 
			
		||||
        final String tenantDomain = pair.getFirst();
 | 
			
		||||
        final NodeRef nodeRef = pair.getSecond();
 | 
			
		||||
        
 | 
			
		||||
        TenantUtil.runAsSystemTenant(new TenantRunAsWork<Void>()
 | 
			
		||||
        {
 | 
			
		||||
            public Void doWork() throws Exception
 | 
			
		||||
            {
 | 
			
		||||
                QName typeQName = nodeService.getType(nodeRef);
 | 
			
		||||
                if (! isSharable(typeQName))
 | 
			
		||||
                {
 | 
			
		||||
                    throw new InvalidNodeRefException(nodeRef);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                String nodeSharedId = (String)nodeService.getProperty(nodeRef, QuickShareModel.PROP_QSHARE_SHAREDID);
 | 
			
		||||
                
 | 
			
		||||
                if (! EqualsHelper.nullSafeEquals(nodeSharedId, sharedId))
 | 
			
		||||
                {
 | 
			
		||||
                    logger.warn("SharedId mismatch: expected="+sharedId+",actual="+nodeSharedId);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Disable audit to preserve modifier and modified date
 | 
			
		||||
                // And not to create version
 | 
			
		||||
                // see MNT-15654
 | 
			
		||||
                behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    nodeService.removeAspect(nodeRef, QuickShareModel.ASPECT_QSHARE);
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
        TenantUtil.runAsSystemTenant((TenantRunAsWork<Void>) () -> {
 | 
			
		||||
            QName typeQName = nodeService.getType(nodeRef);
 | 
			
		||||
            if (! isSharable(typeQName))
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidNodeRefException(nodeRef);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            String nodeSharedId = (String)nodeService.getProperty(nodeRef, QuickShareModel.PROP_QSHARE_SHAREDID);
 | 
			
		||||
 | 
			
		||||
            if (! EqualsHelper.nullSafeEquals(nodeSharedId, sharedId))
 | 
			
		||||
            {
 | 
			
		||||
                logger.warn("SharedId mismatch: expected="+sharedId+",actual="+nodeSharedId);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Disable audit to preserve modifier and modified date
 | 
			
		||||
            // And not to create version
 | 
			
		||||
            // see MNT-15654
 | 
			
		||||
            behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                nodeService.removeAspect(nodeRef, QuickShareModel.ASPECT_QSHARE);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }, tenantDomain);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        removeSharedId(sharedId);
 | 
			
		||||
 | 
			
		||||
        if (logger.isInfoEnabled())
 | 
			
		||||
@@ -891,23 +848,19 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        Pair<String, NodeRef> pair = getTenantNodeRefFromSharedId(sharedId);
 | 
			
		||||
        final String tenantDomain = pair.getFirst();
 | 
			
		||||
        final NodeRef nodeRef = pair.getSecond();
 | 
			
		||||
        
 | 
			
		||||
        return TenantUtil.runAsTenant(new TenantRunAsWork<Boolean>()
 | 
			
		||||
        {
 | 
			
		||||
            public Boolean doWork() throws Exception
 | 
			
		||||
 | 
			
		||||
        return TenantUtil.runAsTenant(() -> {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    checkQuickShareNode(nodeRef);
 | 
			
		||||
                    return permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.ALLOWED;
 | 
			
		||||
                }
 | 
			
		||||
                catch (AccessDeniedException ex)
 | 
			
		||||
                {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                checkQuickShareNode(nodeRef);
 | 
			
		||||
                return permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.ALLOWED;
 | 
			
		||||
            }
 | 
			
		||||
            catch (AccessDeniedException ex)
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }, tenantDomain);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -934,18 +887,18 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        Map<String, Serializable> templateModel = new HashMap<>(6);
 | 
			
		||||
        templateModel.put(FTL_SENDER_FIRST_NAME, senderFirstName);
 | 
			
		||||
        templateModel.put(FTL_SENDER_LAST_NAME, senderLastName);
 | 
			
		||||
        final String sharedNodeUrl = getUrl(clientApp.getProperty(CONFIG_SHARED_LINK_BASE_URL), CONFIG_SHARED_LINK_BASE_URL)
 | 
			
		||||
                    + '/' + emailRequest.getSharedId();
 | 
			
		||||
        String resolvedUrl = getUrl(clientApp.getResolvedProperty(CONFIG_SHARED_LINK_BASE_URL, sysAdminParams), CONFIG_SHARED_LINK_BASE_URL);
 | 
			
		||||
        final String sharedNodeUrl = resolvedUrl + '/' + emailRequest.getSharedId();
 | 
			
		||||
 | 
			
		||||
        templateModel.put(FTL_SHARED_NODE_URL, sharedNodeUrl);
 | 
			
		||||
        templateModel.put(FTL_SHARED_NODE_NAME, emailRequest.getSharedNodeName());
 | 
			
		||||
        templateModel.put(FTL_SENDER_MESSAGE, emailRequest.getSenderMessage());
 | 
			
		||||
        final String templateAssetsUrl = getUrl(clientApp.getTemplateAssetsUrl(), ClientAppConfig.PROP_TEMPLATE_ASSETS_URL);
 | 
			
		||||
        final String templateAssetsUrl = getUrl(clientApp.getResolvedTemplateAssetsUrl(sysAdminParams), ClientAppConfig.PROP_TEMPLATE_ASSETS_URL);
 | 
			
		||||
        templateModel.put(FTL_TEMPLATE_ASSETS_URL, templateAssetsUrl);
 | 
			
		||||
 | 
			
		||||
        // Set the email details
 | 
			
		||||
        Map<String, Serializable> actionParams = new HashMap<>();
 | 
			
		||||
        // Email sender. By default the current-user's email address will not be used to send this mail.
 | 
			
		||||
        // Email sender. By default, the current-user's email address will not be used to send this mail.
 | 
			
		||||
        // However, current-user's first and lastname will be used as the personal name.
 | 
			
		||||
        actionParams.put(MailActionExecuter.PARAM_FROM, this.defaultEmailSender);
 | 
			
		||||
        actionParams.put(MailActionExecuter.PARAM_FROM_PERSONAL_NAME, senderFullName);
 | 
			
		||||
@@ -981,21 +934,21 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
 | 
			
		||||
        String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
 | 
			
		||||
        String siteName = getSiteName(nodeRef);
 | 
			
		||||
        boolean isSharedByCurrentUser = currentUser.equals(sharedByUserId);
 | 
			
		||||
        boolean isSharedByCurrentUser = Objects.equals(currentUser, sharedByUserId);
 | 
			
		||||
 | 
			
		||||
        if (siteName != null)
 | 
			
		||||
        {
 | 
			
		||||
            // node belongs to a site - current user must be a manager or collaborator or someone who shared the link
 | 
			
		||||
            String role = siteService.getMembersRole(siteName, currentUser);
 | 
			
		||||
            if (isSharedByCurrentUser || (role != null && (role.equals(SiteModel.SITE_MANAGER) || role.equals(SiteModel.SITE_COLLABORATOR)))
 | 
			
		||||
                    || (authorityService.isAdminAuthority(currentUser)))
 | 
			
		||||
                        || (authorityService.isAdminAuthority(currentUser)))
 | 
			
		||||
            {
 | 
			
		||||
                canDeleteSharedLink = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (isSharedByCurrentUser || (authorityService.isAdminAuthority(currentUser)))
 | 
			
		||||
        {
 | 
			
		||||
            // node does not belongs to a site - current user must be the person who shared the link or an admin
 | 
			
		||||
            // node does not belong to a site - current user must be the person who shared the link or an admin
 | 
			
		||||
            canDeleteSharedLink = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -1015,11 +968,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        while (parent != null && !nodeService.getType(parent).equals(SiteModel.TYPE_SITE))
 | 
			
		||||
        {
 | 
			
		||||
            // check that we can read parent name
 | 
			
		||||
            if (permissionService.hasReadPermission(parent) == AccessStatus.ALLOWED)
 | 
			
		||||
            {
 | 
			
		||||
                String parentName = (String) nodeService.getProperty(parent,ContentModel.PROP_NAME);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            if (!(permissionService.hasReadPermission(parent) == AccessStatus.ALLOWED))
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
@@ -1048,8 +997,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        {
 | 
			
		||||
            url = url.substring(0, url.length() - 1);
 | 
			
		||||
        }
 | 
			
		||||
        // Replace '${shareUrl} placeholder if it does exist.
 | 
			
		||||
        return UrlUtil.replaceShareUrlPlaceholder(url, sysAdminParams);
 | 
			
		||||
        return url;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <T> T getDefaultIfNull(T defaultValue, T newValue)
 | 
			
		||||
@@ -1134,7 +1082,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        {
 | 
			
		||||
            if (toEmails != null)
 | 
			
		||||
            {
 | 
			
		||||
                this.toEmails = Collections.unmodifiableSet(new HashSet<>(toEmails));
 | 
			
		||||
                this.toEmails = Set.copyOf(toEmails);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -1239,7 +1187,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        final NodeRef expiryActionNodeRef = getQuickShareLinkExpiryActionNode(sharedId);
 | 
			
		||||
        // If an expiry action already exists for the specified shared Id, first remove it, before creating a new one.
 | 
			
		||||
        // If an expiry action already exists for the specified sharedId, first remove it, before creating a new one.
 | 
			
		||||
        if (expiryActionNodeRef != null)
 | 
			
		||||
        {
 | 
			
		||||
            deleteQuickShareLinkExpiryAction(expiryActionNodeRef);
 | 
			
		||||
@@ -1247,11 +1195,11 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
 | 
			
		||||
        // Create the expiry action
 | 
			
		||||
        final QuickShareLinkExpiryAction expiryAction = new QuickShareLinkExpiryActionImpl(java.util.UUID.randomUUID().toString(), sharedId,
 | 
			
		||||
                    "QuickShare link expiry action");
 | 
			
		||||
                                                                                           "QuickShare link expiry action");
 | 
			
		||||
        // Create the persisted schedule
 | 
			
		||||
        final ScheduledPersistedAction schedule = scheduledPersistedActionService.createSchedule(expiryAction);
 | 
			
		||||
 | 
			
		||||
        // first set the scheduledAction so we can set the other information
 | 
			
		||||
        // first set the scheduledAction, so we can set the other information
 | 
			
		||||
        expiryAction.setSchedule(schedule);
 | 
			
		||||
        expiryAction.setScheduleStart(expiryDate);
 | 
			
		||||
 | 
			
		||||
@@ -1273,7 +1221,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        if (logger.isDebugEnabled())
 | 
			
		||||
        {
 | 
			
		||||
            logger.debug("Quick share link expiry action is created for sharedId[" + sharedId + "] and it's scheduled to be executed on: "
 | 
			
		||||
                        + expiryDate);
 | 
			
		||||
                                     + expiryDate);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1298,7 +1246,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
            // Delete the expiry action and its related persisted schedule
 | 
			
		||||
            deleteQuickShareLinkExpiryActionImpl(linkExpiryAction);
 | 
			
		||||
 | 
			
		||||
            // As the method is called directly (ie. not via unshareContent method which removes the aspect properties),
 | 
			
		||||
            // As the method is called directly (i.e. not via unshareContent method which removes the aspect properties),
 | 
			
		||||
            // then we have to remove the 'expiryDate' property as well.
 | 
			
		||||
            if (sharedNodeRef != null && nodeService.getProperty(sharedNodeRef, QuickShareModel.PROP_QSHARE_EXPIRY_DATE) != null)
 | 
			
		||||
            {
 | 
			
		||||
@@ -1342,7 +1290,7 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
        quickShareLinkExpiryActionPersister.deleteQuickShareLinkExpiryAction(linkExpiryAction);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private QuickShareLinkExpiryAction attachSchedule(QuickShareLinkExpiryAction quickShareLinkExpiryAction)
 | 
			
		||||
    private void attachSchedule(QuickShareLinkExpiryAction quickShareLinkExpiryAction)
 | 
			
		||||
    {
 | 
			
		||||
        if (quickShareLinkExpiryAction.getSchedule() == null)
 | 
			
		||||
        {
 | 
			
		||||
@@ -1350,7 +1298,6 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
            quickShareLinkExpiryAction.setSchedule(schedule);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        return quickShareLinkExpiryAction;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private NodeRef getQuickShareLinkExpiryActionNode(String sharedId)
 | 
			
		||||
@@ -1381,54 +1328,53 @@ public class QuickShareServiceImpl implements QuickShareService,
 | 
			
		||||
    private enum ExpiryDatePeriod
 | 
			
		||||
    {
 | 
			
		||||
        DAYS
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            int getDuration(DateTime now, DateTime expiryDate)
 | 
			
		||||
            {
 | 
			
		||||
                Interval interval = new Interval(now.withSecondOfMinute(0).withMillisOfSecond(0), expiryDate);
 | 
			
		||||
                return interval.toPeriod(PeriodType.days()).getDays();
 | 
			
		||||
            }
 | 
			
		||||
                    {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        int getDuration(DateTime now, DateTime expiryDate)
 | 
			
		||||
                        {
 | 
			
		||||
                            Interval interval = new Interval(now.withSecondOfMinute(0).withMillisOfSecond(0), expiryDate);
 | 
			
		||||
                            return interval.toPeriod(PeriodType.days()).getDays();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            String getMessage()
 | 
			
		||||
            {
 | 
			
		||||
                return "day (24 hours)";
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
                        @Override
 | 
			
		||||
                        String getMessage()
 | 
			
		||||
                        {
 | 
			
		||||
                            return "day (24 hours)";
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
        HOURS
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            int getDuration(DateTime now, DateTime expiryDate)
 | 
			
		||||
            {
 | 
			
		||||
                Interval interval = new Interval(now.withSecondOfMinute(0).withMillisOfSecond(0), expiryDate);
 | 
			
		||||
                return interval.toPeriod(PeriodType.hours()).getHours();
 | 
			
		||||
            }
 | 
			
		||||
                    {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        int getDuration(DateTime now, DateTime expiryDate)
 | 
			
		||||
                        {
 | 
			
		||||
                            Interval interval = new Interval(now.withSecondOfMinute(0).withMillisOfSecond(0), expiryDate);
 | 
			
		||||
                            return interval.toPeriod(PeriodType.hours()).getHours();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            String getMessage()
 | 
			
		||||
            {
 | 
			
		||||
                return "hour";
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
                        @Override
 | 
			
		||||
                        String getMessage()
 | 
			
		||||
                        {
 | 
			
		||||
                            return "hour";
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
        MINUTES
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public int getDuration(DateTime now, DateTime expiryDate)
 | 
			
		||||
            {
 | 
			
		||||
                Interval interval = new Interval(now.withMillisOfSecond(0), expiryDate);
 | 
			
		||||
                return interval.toPeriod(PeriodType.minutes()).getMinutes();
 | 
			
		||||
            }
 | 
			
		||||
                    {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public int getDuration(DateTime now, DateTime expiryDate)
 | 
			
		||||
                        {
 | 
			
		||||
                            Interval interval = new Interval(now.withMillisOfSecond(0), expiryDate);
 | 
			
		||||
                            return interval.toPeriod(PeriodType.minutes()).getMinutes();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            String getMessage()
 | 
			
		||||
            {
 | 
			
		||||
                return "minute";
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
                        @Override
 | 
			
		||||
                        String getMessage()
 | 
			
		||||
                        {
 | 
			
		||||
                            return "minute";
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
        abstract int getDuration(DateTime now, DateTime expiryDate);
 | 
			
		||||
 | 
			
		||||
        abstract String getMessage();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -514,11 +514,15 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
 | 
			
		||||
                        NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName);
 | 
			
		||||
                        boolean createRenditionNode = renditionNode == null;
 | 
			
		||||
                        boolean sourceHasAspectRenditioned = nodeService.hasAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED);
 | 
			
		||||
                        boolean sourceChanges = !sourceHasAspectRenditioned || createRenditionNode || transformInputStream == null;
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            ruleService.disableRuleType(RuleType.UPDATE);
 | 
			
		||||
                            behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                            behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
 | 
			
		||||
                            if (sourceChanges)
 | 
			
		||||
                            {
 | 
			
		||||
                                ruleService.disableRuleType(RuleType.UPDATE);
 | 
			
		||||
                                behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                                behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // If they do not exist create the rendition association and the rendition node.
 | 
			
		||||
                            if (createRenditionNode)
 | 
			
		||||
@@ -588,9 +592,12 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
 | 
			
		||||
                        }
 | 
			
		||||
                        finally
 | 
			
		||||
                        {
 | 
			
		||||
                            behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                            behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
 | 
			
		||||
                            ruleService.enableRuleType(RuleType.UPDATE);
 | 
			
		||||
                            if (sourceChanges)
 | 
			
		||||
                            {
 | 
			
		||||
                                behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
 | 
			
		||||
                                behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
 | 
			
		||||
                                ruleService.enableRuleType(RuleType.UPDATE);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        return null;
 | 
			
		||||
                    }, false, true));
 | 
			
		||||
 
 | 
			
		||||
@@ -414,7 +414,34 @@ public abstract class AbstractCategoryServiceImpl implements CategoryService
 | 
			
		||||
        int count = 0;
 | 
			
		||||
        boolean moreItems = false;
 | 
			
		||||
 | 
			
		||||
        final Function<NodeRef, Collection<ChildAssociationRef>> childNodesSupplier = getNodeRefCollectionFunction(sortByName, exactNamesFilter, alikeNamesFilter, skipCount, maxItems);
 | 
			
		||||
        final Function<NodeRef, Collection<ChildAssociationRef>> childNodesSupplier = (nodeRef) -> {
 | 
			
		||||
            final Set<ChildAssociationRef> childNodes = new HashSet<>();
 | 
			
		||||
            if (CollectionUtils.isEmpty(exactNamesFilter) && CollectionUtils.isEmpty(alikeNamesFilter))
 | 
			
		||||
            {
 | 
			
		||||
                // lookup in DB without filtering
 | 
			
		||||
                childNodes.addAll(nodeService.getChildAssocs(nodeRef, ContentModel.ASSOC_SUBCATEGORIES, RegexQNamePattern.MATCH_ALL));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if (CollectionUtils.isNotEmpty(exactNamesFilter))
 | 
			
		||||
                {
 | 
			
		||||
                    // lookup in DB filtering by name
 | 
			
		||||
                    childNodes.addAll(nodeService.getChildrenByName(nodeRef, ContentModel.ASSOC_SUBCATEGORIES, exactNamesFilter));
 | 
			
		||||
                }
 | 
			
		||||
                if (CollectionUtils.isNotEmpty(alikeNamesFilter))
 | 
			
		||||
                {
 | 
			
		||||
                    // lookup using search engin filtering by name
 | 
			
		||||
                    childNodes.addAll(getChildren(nodeRef, Mode.SUB_CATEGORIES, Depth.IMMEDIATE, sortByName, alikeNamesFilter, skipCount + maxItems + 1));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Stream<ChildAssociationRef> childNodesStream = childNodes.stream();
 | 
			
		||||
            if (sortByName)
 | 
			
		||||
            {
 | 
			
		||||
                childNodesStream = childNodesStream.sorted(Comparator.comparing(tag -> tag.getQName().getLocalName()));
 | 
			
		||||
            }
 | 
			
		||||
            return childNodesStream.collect(Collectors.toList());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        OUTER_LOOP: for(NodeRef nodeRef : nodeRefs)
 | 
			
		||||
        {
 | 
			
		||||
@@ -441,55 +468,6 @@ public abstract class AbstractCategoryServiceImpl implements CategoryService
 | 
			
		||||
        return new ListBackedPagingResults<>(associations, moreItems);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Collection<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
 | 
			
		||||
    {
 | 
			
		||||
        final Set<NodeRef> nodeRefs = getClassificationNodes(storeRef, aspectName);
 | 
			
		||||
        final List<ChildAssociationRef> associations = new LinkedList<>();
 | 
			
		||||
 | 
			
		||||
        final Function<NodeRef, Collection<ChildAssociationRef>> childNodesSupplier = getNodeRefCollectionFunction(false, exactNamesFilter, alikeNamesFilter, 0, 10000);
 | 
			
		||||
 | 
			
		||||
        for (NodeRef nodeRef : nodeRefs)
 | 
			
		||||
        {
 | 
			
		||||
            Collection<ChildAssociationRef> children = childNodesSupplier.apply(nodeRef);
 | 
			
		||||
            associations.addAll(children);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return associations;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Function<NodeRef, Collection<ChildAssociationRef>> getNodeRefCollectionFunction(boolean sortByName, Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter, int skipCount, int maxItems)
 | 
			
		||||
    {
 | 
			
		||||
        final Function<NodeRef, Collection<ChildAssociationRef>> childNodesSupplier = (nodeRef) -> {
 | 
			
		||||
            final Set<ChildAssociationRef> childNodes = new HashSet<>();
 | 
			
		||||
            if (CollectionUtils.isEmpty(exactNamesFilter) && CollectionUtils.isEmpty(alikeNamesFilter))
 | 
			
		||||
            {
 | 
			
		||||
                // lookup in DB without filtering
 | 
			
		||||
                childNodes.addAll(nodeService.getChildAssocs(nodeRef, ContentModel.ASSOC_SUBCATEGORIES, RegexQNamePattern.MATCH_ALL));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if (CollectionUtils.isNotEmpty(exactNamesFilter))
 | 
			
		||||
                {
 | 
			
		||||
                    // lookup in DB filtering by name
 | 
			
		||||
                    childNodes.addAll(nodeService.getChildrenByName(nodeRef, ContentModel.ASSOC_SUBCATEGORIES, exactNamesFilter));
 | 
			
		||||
                }
 | 
			
		||||
                if (CollectionUtils.isNotEmpty(alikeNamesFilter))
 | 
			
		||||
                {
 | 
			
		||||
                    // lookup using search engine filtering by name
 | 
			
		||||
                    childNodes.addAll(getChildren(nodeRef, Mode.SUB_CATEGORIES, Depth.IMMEDIATE, sortByName, alikeNamesFilter, skipCount + maxItems + 1));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Stream<ChildAssociationRef> childNodesStream = childNodes.stream();
 | 
			
		||||
            if (sortByName)
 | 
			
		||||
            {
 | 
			
		||||
                childNodesStream = childNodesStream.sorted(Comparator.comparing(tag -> tag.getQName().getLocalName()));
 | 
			
		||||
            }
 | 
			
		||||
            return childNodesStream.collect(Collectors.toList());
 | 
			
		||||
        };
 | 
			
		||||
        return childNodesSupplier;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Collection<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName)
 | 
			
		||||
    {
 | 
			
		||||
        return getRootCategories(storeRef, aspectName, null);
 | 
			
		||||
 
 | 
			
		||||
@@ -32,13 +32,11 @@ import org.alfresco.repo.client.config.ClientAppNotFoundException;
 | 
			
		||||
import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.ResetPasswordDetails;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @deprecated from 7.1.0
 | 
			
		||||
 * Reset password service.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Jamal Kaabi-Mofrad
 | 
			
		||||
 * @since 5.2.1
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
public interface ResetPasswordService
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,23 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Remote API
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2017 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2023 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%
 | 
			
		||||
@@ -61,8 +61,8 @@ import org.alfresco.util.ParameterCheck;
 | 
			
		||||
import org.alfresco.util.PropertyCheck;
 | 
			
		||||
import org.alfresco.util.UrlUtil;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.extensions.surf.util.I18NUtil;
 | 
			
		||||
import org.springframework.extensions.webscripts.WebScriptException;
 | 
			
		||||
 | 
			
		||||
@@ -74,17 +74,15 @@ import java.util.Locale;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @deprecated from 7.1.0
 | 
			
		||||
 * *
 | 
			
		||||
 *
 | 
			
		||||
 * Reset password implementation based on workflow.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Jamal Kaabi-Mofrad
 | 
			
		||||
 * @since 5.2.1
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
{
 | 
			
		||||
    private static final Log LOGGER = LogFactory.getLog(ResetPasswordServiceImpl.class);
 | 
			
		||||
    private static final Logger LOGGER = LoggerFactory.getLogger(ResetPasswordServiceImpl.class);
 | 
			
		||||
 | 
			
		||||
    private static final String TIMER_END = "PT1H";
 | 
			
		||||
    private static final String WORKFLOW_DESCRIPTION_KEY = "resetpasswordwf_resetpassword.resetpassword.workflow.description";
 | 
			
		||||
@@ -196,6 +194,7 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
        ParameterCheck.mandatoryString("clientName", clientName);
 | 
			
		||||
 | 
			
		||||
        String userEmail = validateUserAndGetEmail(userId);
 | 
			
		||||
        validateClient(clientName);
 | 
			
		||||
 | 
			
		||||
        // Get the (latest) workflow definition for reset-password.
 | 
			
		||||
        WorkflowDefinition wfDefinition = workflowService.getDefinitionByName(WorkflowModelResetPassword.WORKFLOW_DEFINITION_NAME);
 | 
			
		||||
@@ -342,8 +341,9 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
        }
 | 
			
		||||
        else if (!username.equals(userId))
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidResetPasswordWorkflowException("The given user id [" + userId + "] does not match the person's user id [" + username
 | 
			
		||||
                        + "] who requested the password reset.");
 | 
			
		||||
            throw new InvalidResetPasswordWorkflowException(
 | 
			
		||||
                        "The given user id [" + userId + "] does not match the person's user id [" + username
 | 
			
		||||
                                    + "] who requested the password reset.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -352,12 +352,16 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
    {
 | 
			
		||||
        ParameterCheck.mandatoryString("clientName", clientName);
 | 
			
		||||
 | 
			
		||||
        ClientApp clientApp = clientAppConfig.getClient(clientName);
 | 
			
		||||
        if (clientApp == null)
 | 
			
		||||
        validateClient(clientName);
 | 
			
		||||
        return clientAppConfig.getClient(clientName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void validateClient(String clientName)
 | 
			
		||||
    {
 | 
			
		||||
        if (!clientAppConfig.exists(clientName))
 | 
			
		||||
        {
 | 
			
		||||
            throw new ClientAppNotFoundException("Client was not found [" + clientName + "]");
 | 
			
		||||
        }
 | 
			
		||||
        return clientApp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -383,9 +387,9 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
                    .setUserName(userName)
 | 
			
		||||
                    .setUserEmail(toEmail)
 | 
			
		||||
                    .setTemplatePath(templatePath)
 | 
			
		||||
                    .setTemplateAssetsUrl(clientApp.getTemplateAssetsUrl())
 | 
			
		||||
                    .setEmailSubject(emailSubject)
 | 
			
		||||
                    .setTemplateModel(emailTemplateModel);
 | 
			
		||||
                    .setTemplateModel(emailTemplateModel)
 | 
			
		||||
                    .setClientApp(clientApp);
 | 
			
		||||
 | 
			
		||||
        sendEmail(emailRequest);
 | 
			
		||||
    }
 | 
			
		||||
@@ -400,7 +404,7 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
        final String userName = (String) execution.getVariable(WorkflowModelResetPassword.WF_PROP_USERNAME_ACTIVITI);
 | 
			
		||||
 | 
			
		||||
        // But we cannot get the password from the execution as we have intentionally not stored the password there.
 | 
			
		||||
        // Instead we recover the password from the specific task in which it was set.
 | 
			
		||||
        // Instead, we recover the password from the specific task in which it was set.
 | 
			
		||||
        List<Task> activitiTasks = activitiTaskService.createTaskQuery().taskDefinitionKey(WorkflowModelResetPassword.TASK_RESET_PASSWORD)
 | 
			
		||||
                    .processInstanceId(execution.getProcessInstanceId()).list();
 | 
			
		||||
        if (activitiTasks.size() != 1)
 | 
			
		||||
@@ -448,9 +452,9 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
                    .setUserName(userName)
 | 
			
		||||
                    .setUserEmail(userEmail)
 | 
			
		||||
                    .setTemplatePath(templatePath)
 | 
			
		||||
                    .setTemplateAssetsUrl(clientApp.getTemplateAssetsUrl())
 | 
			
		||||
                    .setEmailSubject(emailSubject)
 | 
			
		||||
                    .setTemplateModel(emailTemplateModel);
 | 
			
		||||
                    .setTemplateModel(emailTemplateModel)
 | 
			
		||||
                    .setClientApp(clientApp);
 | 
			
		||||
 | 
			
		||||
        sendEmail(emailRequest);
 | 
			
		||||
    }
 | 
			
		||||
@@ -460,7 +464,9 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
        // Prepare the email
 | 
			
		||||
        Map<String, Serializable> templateModel = new HashMap<>();
 | 
			
		||||
        // Replace '${shareUrl}' placeholder if it does exist.
 | 
			
		||||
        final String templateAssetsUrl = getUrl(emailRequest.getTemplateAssetsUrl(), ClientAppConfig.PROP_TEMPLATE_ASSETS_URL);
 | 
			
		||||
        ClientApp clientApp = emailRequest.getClientApp();
 | 
			
		||||
        final String templateAssetsUrl = getUrl(clientApp.getResolvedTemplateAssetsUrl(sysAdminParams),
 | 
			
		||||
                                                ClientAppConfig.PROP_TEMPLATE_ASSETS_URL, clientApp.getName());
 | 
			
		||||
        templateModel.put(FTL_TEMPLATE_ASSETS_URL, templateAssetsUrl);
 | 
			
		||||
        if (emailRequest.getTemplateModel() != null)
 | 
			
		||||
        {
 | 
			
		||||
@@ -489,11 +495,11 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
        actionService.executeAction(mailAction, null, false, sendEmailAsynchronously);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getUrl(String url, String propName)
 | 
			
		||||
    private String getUrl(String url, String propName, String clientName)
 | 
			
		||||
    {
 | 
			
		||||
        if (url == null)
 | 
			
		||||
        if (StringUtils.isEmpty(url))
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER.warn("The url for the property [" + propName + "] is not configured.");
 | 
			
		||||
            LOGGER.warn("The url for the property [" + propName + "] is not configured for the client: " + clientName);
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -501,7 +507,7 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
        {
 | 
			
		||||
            url = url.substring(0, url.length() - 1);
 | 
			
		||||
        }
 | 
			
		||||
        return UrlUtil.replaceShareUrlPlaceholder(url, sysAdminParams);
 | 
			
		||||
        return url;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected String getResetPasswordEmailTemplate(ClientApp clientApp)
 | 
			
		||||
@@ -521,22 +527,23 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
    {
 | 
			
		||||
        StringBuilder sb = new StringBuilder(100);
 | 
			
		||||
 | 
			
		||||
        String pageUrl = clientApp.getProperty("resetPasswordPageUrl");
 | 
			
		||||
        String pageUrl = clientApp.getResolvedProperty("resetPasswordPageUrl", sysAdminParams);
 | 
			
		||||
        if (StringUtils.isEmpty(pageUrl))
 | 
			
		||||
        {
 | 
			
		||||
            sb.append(UrlUtil.getShareUrl(sysAdminParams));
 | 
			
		||||
            LOGGER.warn("'resetPasswordPageUrl' property is not set for the client [" + clientApp.getName() + "]. The default base url of Share will be used [" + sb + "]");
 | 
			
		||||
 | 
			
		||||
            LOGGER.warn("'resetPasswordPageUrl' property is not set for the client [" + clientApp.getName()
 | 
			
		||||
                        + "]. The default base url of Share will be used [" + sb.toString() + "]");
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            // We pass an empty string as we know that the pageUrl is not null
 | 
			
		||||
            sb.append(getUrl(pageUrl, ""));
 | 
			
		||||
            sb.append(getUrl(pageUrl, "resetPasswordPageUrl", clientApp.getName()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sb.append("?key=").append(key)
 | 
			
		||||
                    .append("&id=").append(BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, id));
 | 
			
		||||
        sb.append("?key=")
 | 
			
		||||
                    .append(key)
 | 
			
		||||
                    .append("&id=")
 | 
			
		||||
                    .append(BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, id));
 | 
			
		||||
 | 
			
		||||
        return sb.toString();
 | 
			
		||||
    }
 | 
			
		||||
@@ -616,10 +623,10 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
        private String userEmail;
 | 
			
		||||
        private String fromEmail;
 | 
			
		||||
        private String templatePath;
 | 
			
		||||
        private String templateAssetsUrl;
 | 
			
		||||
        private Map<String, Serializable> templateModel;
 | 
			
		||||
        private String emailSubject;
 | 
			
		||||
        private boolean ignoreSendFailure = true;
 | 
			
		||||
        private ClientApp clientApp;
 | 
			
		||||
 | 
			
		||||
        public String getUserName()
 | 
			
		||||
        {
 | 
			
		||||
@@ -665,17 +672,6 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getTemplateAssetsUrl()
 | 
			
		||||
        {
 | 
			
		||||
            return templateAssetsUrl;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ResetPasswordEmailDetails setTemplateAssetsUrl(String templateAssetsUrl)
 | 
			
		||||
        {
 | 
			
		||||
            this.templateAssetsUrl = templateAssetsUrl;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Map<String, Serializable> getTemplateModel()
 | 
			
		||||
        {
 | 
			
		||||
            return templateModel;
 | 
			
		||||
@@ -709,18 +705,29 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ClientApp getClientApp()
 | 
			
		||||
        {
 | 
			
		||||
            return clientApp;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ResetPasswordEmailDetails setClientApp(ClientApp clientApp)
 | 
			
		||||
        {
 | 
			
		||||
            this.clientApp = clientApp;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String toString()
 | 
			
		||||
        {
 | 
			
		||||
            final StringBuilder sb = new StringBuilder(250);
 | 
			
		||||
            final StringBuilder sb = new StringBuilder(300);
 | 
			
		||||
            sb.append("ResetPasswordEmailDetails [userName=").append(userName)
 | 
			
		||||
                        .append(", userEmail=").append(userEmail)
 | 
			
		||||
                        .append(", fromEmail=").append(fromEmail)
 | 
			
		||||
                        .append(", templatePath=").append(templatePath)
 | 
			
		||||
                        .append(", templateAssetsUrl=").append(templateAssetsUrl)
 | 
			
		||||
                        .append(", templateModel=").append(templateModel)
 | 
			
		||||
                        .append(", emailSubject=").append(emailSubject)
 | 
			
		||||
                        .append(", ignoreSendFailure=").append(ignoreSendFailure)
 | 
			
		||||
                        .append(", clientApp=").append(clientApp)
 | 
			
		||||
                        .append(']');
 | 
			
		||||
            return sb.toString();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -357,13 +357,8 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
 | 
			
		||||
 | 
			
		||||
        private ClientRegistration.Builder createBuilder(OIDCProviderMetadata metadata)
 | 
			
		||||
        {
 | 
			
		||||
            final String authUri = Optional.of(metadata)
 | 
			
		||||
                                           .map(OIDCProviderMetadata::getAuthorizationEndpointURI)
 | 
			
		||||
                                           .map(URI::toASCIIString)
 | 
			
		||||
                                           .orElse(null);
 | 
			
		||||
            return ClientRegistration
 | 
			
		||||
                    .withRegistrationId("ids")
 | 
			
		||||
                    .authorizationUri(authUri)
 | 
			
		||||
                    .tokenUri(metadata.getTokenEndpointURI().toASCIIString())
 | 
			
		||||
                    .jwkSetUri(metadata.getJWKSetURI().toASCIIString())
 | 
			
		||||
                    .issuerUri(config.getIssuerUrl())
 | 
			
		||||
 
 | 
			
		||||
@@ -1605,6 +1605,8 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial
 | 
			
		||||
                    this.userSearchCtls = new SearchControls();
 | 
			
		||||
                    this.userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
 | 
			
		||||
                    this.userSearchCtls.setReturningAttributes(LDAPUserRegistry.this.userKeys.getFirst());
 | 
			
		||||
                    // MNT-14001 fix, set search limit to ensure that server will not return more search results then provided by paged result control
 | 
			
		||||
                    this.userSearchCtls.setCountLimit(LDAPUserRegistry.this.queryBatchSize > 0 ? LDAPUserRegistry.this.queryBatchSize : 0);
 | 
			
		||||
 | 
			
		||||
                    this.next = fetchNext();
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,10 +27,8 @@ package org.alfresco.repo.tagging;
 | 
			
		||||
 | 
			
		||||
import static java.util.Collections.emptyMap;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.model.ContentModel.ASPECT_WORKING_COPY;
 | 
			
		||||
import static org.alfresco.model.ContentModel.ASSOC_SUBCATEGORIES;
 | 
			
		||||
import static org.alfresco.model.ContentModel.PROP_NAME;
 | 
			
		||||
import static org.alfresco.service.cmr.search.SearchService.LANGUAGE_LUCENE;
 | 
			
		||||
import static org.alfresco.service.namespace.NamespaceService.CONTENT_MODEL_1_0_URI;
 | 
			
		||||
 | 
			
		||||
import java.io.BufferedReader;
 | 
			
		||||
@@ -46,16 +44,12 @@ import java.util.Comparator;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.TreeMap;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.query.EmptyPagingResults;
 | 
			
		||||
@@ -65,7 +59,6 @@ import org.alfresco.repo.audit.AuditComponent;
 | 
			
		||||
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCheckOut;
 | 
			
		||||
import org.alfresco.repo.copy.CopyServicePolicies.BeforeCopyPolicy;
 | 
			
		||||
import org.alfresco.repo.copy.CopyServicePolicies.OnCopyCompletePolicy;
 | 
			
		||||
import org.alfresco.repo.domain.query.QueryException;
 | 
			
		||||
import org.alfresco.repo.event2.EventGenerator;
 | 
			
		||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
 | 
			
		||||
import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy;
 | 
			
		||||
@@ -126,7 +119,7 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
 | 
			
		||||
    private static Log logger = LogFactory.getLog(TaggingServiceImpl.class);
 | 
			
		||||
    
 | 
			
		||||
    private static Collator collator = Collator.getInstance();
 | 
			
		||||
	private static Collator collator = Collator.getInstance();
 | 
			
		||||
 | 
			
		||||
    private NodeService nodeService;
 | 
			
		||||
    private NodeService nodeServiceInternal;
 | 
			
		||||
@@ -143,9 +136,6 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
    private static final String TAG_DETAILS_DELIMITER = "|";
 | 
			
		||||
    /** Next tag delimiter */
 | 
			
		||||
    private static final String NEXT_TAG_DELIMITER = "\n";
 | 
			
		||||
    /** Parameters Include count */
 | 
			
		||||
    private static final String PARAM_INCLUDE_COUNT = "count";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private static Set<String> FORBIDDEN_TAGS_SEQUENCES = new HashSet<String>(Arrays.asList(new String[] {NEXT_TAG_DELIMITER, TAG_DETAILS_DELIMITER}));
 | 
			
		||||
 | 
			
		||||
@@ -343,7 +333,7 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
    public void beforeDeleteNode(NodeRef nodeRef)
 | 
			
		||||
    {
 | 
			
		||||
       if (this.nodeService.exists(nodeRef) == true &&          
 | 
			
		||||
           this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TAGGABLE) == true && !this.nodeService.hasAspect(nodeRef, ASPECT_WORKING_COPY))
 | 
			
		||||
           this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TAGGABLE) == true && !this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY))
 | 
			
		||||
       {
 | 
			
		||||
           updateAllScopeTags(nodeRef, Boolean.FALSE);
 | 
			
		||||
       }
 | 
			
		||||
@@ -502,8 +492,8 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
    {
 | 
			
		||||
        // Lower the case of the tag
 | 
			
		||||
        tag = tag.toLowerCase();
 | 
			
		||||
 | 
			
		||||
        return getTagNodeRef(storeRef, tag, true);
 | 
			
		||||
		
 | 
			
		||||
		return getTagNodeRef(storeRef, tag, true);
 | 
			
		||||
    }  
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -532,33 +522,33 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
    
 | 
			
		||||
    public NodeRef changeTag(StoreRef storeRef, String existingTag, String newTag)
 | 
			
		||||
    {
 | 
			
		||||
        if (existingTag == null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new TaggingException("Existing tag cannot be null");
 | 
			
		||||
        }
 | 
			
		||||
    	if (existingTag == null)
 | 
			
		||||
    	{
 | 
			
		||||
    		throw new TaggingException("Existing tag cannot be null");
 | 
			
		||||
    	}
 | 
			
		||||
    	
 | 
			
		||||
    	if (newTag == null || StringUtils.isBlank(newTag))
 | 
			
		||||
    	{
 | 
			
		||||
    		throw new TaggingException("New tag cannot be blank");
 | 
			
		||||
    	}
 | 
			
		||||
 | 
			
		||||
        if (newTag == null || StringUtils.isBlank(newTag))
 | 
			
		||||
        {
 | 
			
		||||
            throw new TaggingException("New tag cannot be blank");
 | 
			
		||||
        }
 | 
			
		||||
    	existingTag = existingTag.toLowerCase();
 | 
			
		||||
    	newTag = newTag.toLowerCase();
 | 
			
		||||
 | 
			
		||||
        existingTag = existingTag.toLowerCase();
 | 
			
		||||
        newTag = newTag.toLowerCase();
 | 
			
		||||
 | 
			
		||||
        if (existingTag.equals(newTag))
 | 
			
		||||
        {
 | 
			
		||||
            throw new TaggingException("New and existing tags are the same");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (getTagNodeRef(storeRef, existingTag) == null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new NonExistentTagException("Tag " + existingTag + " not found");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (getTagNodeRef(storeRef, newTag) != null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new TagExistsException("Tag " + newTag + " already exists");
 | 
			
		||||
        }
 | 
			
		||||
    	if (existingTag.equals(newTag))
 | 
			
		||||
    	{
 | 
			
		||||
    		throw new TaggingException("New and existing tags are the same");
 | 
			
		||||
    	}
 | 
			
		||||
    	
 | 
			
		||||
    	if (getTagNodeRef(storeRef, existingTag) == null)
 | 
			
		||||
    	{
 | 
			
		||||
    		throw new NonExistentTagException("Tag " + existingTag + " not found");
 | 
			
		||||
    	}
 | 
			
		||||
    	
 | 
			
		||||
    	if (getTagNodeRef(storeRef, newTag) != null)
 | 
			
		||||
    	{
 | 
			
		||||
    		throw new TagExistsException("Tag " + newTag + " already exists");
 | 
			
		||||
    	}
 | 
			
		||||
 | 
			
		||||
        NodeRef tagNodeRef = getTagNodeRef(storeRef, existingTag);
 | 
			
		||||
        nodeService.setProperty(tagNodeRef, PROP_NAME, newTag);
 | 
			
		||||
@@ -688,21 +678,6 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Map<String, Long> calculateCount(StoreRef storeRef)
 | 
			
		||||
    {
 | 
			
		||||
        List<Pair<String, Integer>> tagsByCount = findTaggedNodesAndCountByTagName(storeRef);
 | 
			
		||||
        Map<String, Long> tagsByCountMap = new HashMap<>();
 | 
			
		||||
        if (tagsByCount != null)
 | 
			
		||||
        {
 | 
			
		||||
            for (Pair<String, Integer> tagByCountElem : tagsByCount)
 | 
			
		||||
            {
 | 
			
		||||
                tagsByCountMap.put(tagByCountElem.getFirst(), Long.valueOf(tagByCountElem.getSecond()));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return tagsByCountMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see TaggingService#hasTag(NodeRef, String)
 | 
			
		||||
     */
 | 
			
		||||
@@ -718,12 +693,12 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    public NodeRef addTag(final NodeRef nodeRef, final String tagName)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef newTagNodeRef = null;
 | 
			
		||||
 | 
			
		||||
        if(tagName == null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new IllegalArgumentException("Must provide a non-null tag");
 | 
			
		||||
        }
 | 
			
		||||
    	NodeRef newTagNodeRef = null;
 | 
			
		||||
    	
 | 
			
		||||
    	if(tagName == null)
 | 
			
		||||
    	{
 | 
			
		||||
    		throw new IllegalArgumentException("Must provide a non-null tag");
 | 
			
		||||
    	}
 | 
			
		||||
 | 
			
		||||
        updateTagBehaviour.disable();
 | 
			
		||||
        createTagBehaviour.disable();
 | 
			
		||||
@@ -773,7 +748,7 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
     */
 | 
			
		||||
    public List<Pair<String, NodeRef>> addTags(NodeRef nodeRef, List<String> tags)
 | 
			
		||||
    {
 | 
			
		||||
        List<Pair<String, NodeRef>> ret = new ArrayList<Pair<String, NodeRef>>();
 | 
			
		||||
    	List<Pair<String, NodeRef>> ret = new ArrayList<Pair<String, NodeRef>>();
 | 
			
		||||
        for (String tag : tags)
 | 
			
		||||
        {
 | 
			
		||||
            NodeRef tagNodeRef = addTag(nodeRef, tag);
 | 
			
		||||
@@ -894,36 +869,36 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
                int skipCount = pagingRequest.getSkipCount();
 | 
			
		||||
                int maxItems = pagingRequest.getMaxItems();
 | 
			
		||||
                int end = maxItems == Integer.MAX_VALUE ? totalItems : skipCount + maxItems;
 | 
			
		||||
                int size = (maxItems == Integer.MAX_VALUE ? totalItems : maxItems);
 | 
			
		||||
            	int size = (maxItems == Integer.MAX_VALUE ? totalItems : maxItems);
 | 
			
		||||
 | 
			
		||||
                final List<Pair<NodeRef, String>> sortedTags = new ArrayList<Pair<NodeRef, String>>(size);
 | 
			
		||||
                // grab all tags and sort (assume fairly low number of tags)
 | 
			
		||||
                for(NodeRef tagNode : currentTagNodes)
 | 
			
		||||
                {
 | 
			
		||||
            	// grab all tags and sort (assume fairly low number of tags)
 | 
			
		||||
            	for(NodeRef tagNode : currentTagNodes)
 | 
			
		||||
            	{
 | 
			
		||||
                    String tag = (String)this.nodeService.getProperty(tagNode, PROP_NAME);
 | 
			
		||||
                    sortedTags.add(new Pair<NodeRef, String>(tagNode, tag));            		
 | 
			
		||||
                }
 | 
			
		||||
            	}
 | 
			
		||||
                Collections.sort(sortedTags, new Comparator<Pair<NodeRef, String>>()
 | 
			
		||||
                {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public int compare(Pair<NodeRef, String> o1, Pair<NodeRef, String> o2)
 | 
			
		||||
                    {
 | 
			
		||||
                        String tag1 = o1.getSecond();
 | 
			
		||||
                        String tag2 = o2.getSecond();
 | 
			
		||||
                        return collator.compare(tag1, tag2);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
					@Override
 | 
			
		||||
					public int compare(Pair<NodeRef, String> o1, Pair<NodeRef, String> o2)
 | 
			
		||||
					{
 | 
			
		||||
						String tag1 = o1.getSecond();
 | 
			
		||||
						String tag2 = o2.getSecond();
 | 
			
		||||
						return collator.compare(tag1, tag2);
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
                final List<Pair<NodeRef, String>> result = new ArrayList<Pair<NodeRef, String>>(size);
 | 
			
		||||
                Iterator<Pair<NodeRef, String>> it = sortedTags.iterator();
 | 
			
		||||
            	Iterator<Pair<NodeRef, String>> it = sortedTags.iterator();
 | 
			
		||||
                for(int count = 0; count < end && it.hasNext(); count++)
 | 
			
		||||
                {
 | 
			
		||||
                    Pair<NodeRef, String> tagPair = it.next();
 | 
			
		||||
                	Pair<NodeRef, String> tagPair = it.next();
 | 
			
		||||
 | 
			
		||||
                    if(count < skipCount)
 | 
			
		||||
                    {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                	if(count < skipCount)
 | 
			
		||||
                	{
 | 
			
		||||
                		continue;
 | 
			
		||||
                	}
 | 
			
		||||
 | 
			
		||||
                    result.add(tagPair);
 | 
			
		||||
                }
 | 
			
		||||
@@ -932,30 +907,30 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
 | 
			
		||||
                return new PagingResults<Pair<NodeRef, String>>()
 | 
			
		||||
                {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public List<Pair<NodeRef, String>> getPage()
 | 
			
		||||
                    {
 | 
			
		||||
                        return result;
 | 
			
		||||
                    }
 | 
			
		||||
        			@Override
 | 
			
		||||
        			public List<Pair<NodeRef, String>> getPage()
 | 
			
		||||
        			{
 | 
			
		||||
        				return result;
 | 
			
		||||
        			}
 | 
			
		||||
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public boolean hasMoreItems()
 | 
			
		||||
                    {
 | 
			
		||||
                        return hasMoreItems;
 | 
			
		||||
                    }
 | 
			
		||||
        			@Override
 | 
			
		||||
        			public boolean hasMoreItems()
 | 
			
		||||
        			{
 | 
			
		||||
        				return hasMoreItems;
 | 
			
		||||
        			}
 | 
			
		||||
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public Pair<Integer, Integer> getTotalResultCount()
 | 
			
		||||
                    {
 | 
			
		||||
                        Integer total = Integer.valueOf(totalItems);
 | 
			
		||||
                        return new Pair<Integer, Integer>(total, total);
 | 
			
		||||
                    }
 | 
			
		||||
        			@Override
 | 
			
		||||
        			public Pair<Integer, Integer> getTotalResultCount()
 | 
			
		||||
        			{
 | 
			
		||||
        				Integer total = Integer.valueOf(totalItems);
 | 
			
		||||
        				return new Pair<Integer, Integer>(total, total);
 | 
			
		||||
        			}
 | 
			
		||||
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public String getQueryExecutionId()
 | 
			
		||||
                    {
 | 
			
		||||
                        return null;
 | 
			
		||||
                    }
 | 
			
		||||
        			@Override
 | 
			
		||||
        			public String getQueryExecutionId()
 | 
			
		||||
        			{
 | 
			
		||||
        				return null;
 | 
			
		||||
        			}
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -979,83 +954,7 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
            exactNamesFilter, alikeNamesFilter);
 | 
			
		||||
 | 
			
		||||
        return mapPagingResult(rootCategories,
 | 
			
		||||
                (childAssociation) -> new Pair<>(childAssociation.getChildRef(), childAssociation.getQName().getLocalName()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Map<NodeRef, Long> getTags(StoreRef storeRef, List<String> parameterIncludes, Pair<String, Boolean> sorting, Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
 | 
			
		||||
    {
 | 
			
		||||
        ParameterCheck.mandatory("storeRef", storeRef);
 | 
			
		||||
        Collection<ChildAssociationRef> rootCategories = categoryService.getRootCategories(storeRef, ContentModel.ASPECT_TAGGABLE, exactNamesFilter, alikeNamesFilter);
 | 
			
		||||
 | 
			
		||||
        Map<String, Long> tagsMap = new TreeMap<>();
 | 
			
		||||
        for (ChildAssociationRef childAssociation : rootCategories)
 | 
			
		||||
        {
 | 
			
		||||
            tagsMap.put(childAssociation.getQName().getLocalName(), 0L);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Map<String, Long> tagsByCountMap = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
        if(parameterIncludes.contains(PARAM_INCLUDE_COUNT))
 | 
			
		||||
        {
 | 
			
		||||
            tagsByCountMap = calculateCount(storeRef);
 | 
			
		||||
 | 
			
		||||
            for (Map.Entry<String, Long> entry : tagsMap.entrySet()) {
 | 
			
		||||
                entry.setValue(Optional.ofNullable(tagsByCountMap.get(entry.getKey())).orElse(0L));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //check if we should sort results. Can only sort by one parameter, default order is ascending
 | 
			
		||||
        if (sorting != null)
 | 
			
		||||
        {
 | 
			
		||||
            if (sorting.getFirst().equals("tag"))
 | 
			
		||||
            {
 | 
			
		||||
                if (!sorting.getSecond())
 | 
			
		||||
                {
 | 
			
		||||
                    Stream<Map.Entry<String,Long>> sortedTags =
 | 
			
		||||
                            tagsMap.entrySet().stream()
 | 
			
		||||
                                    .sorted(Collections.reverseOrder(Map.Entry.comparingByKey()));
 | 
			
		||||
                    tagsMap = sortedTags.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    Stream<Map.Entry<String,Long>> sortedTags =
 | 
			
		||||
                            tagsMap.entrySet().stream()
 | 
			
		||||
                                    .sorted(Map.Entry.comparingByKey());
 | 
			
		||||
                    tagsMap = sortedTags.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else if (sorting.getFirst().equals(PARAM_INCLUDE_COUNT))
 | 
			
		||||
            {
 | 
			
		||||
                if (tagsByCountMap.isEmpty())
 | 
			
		||||
                {
 | 
			
		||||
                    throw new IllegalArgumentException("Tag count should be included when ordering by count");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!sorting.getSecond())
 | 
			
		||||
                {
 | 
			
		||||
                    Stream<Map.Entry<String, Long>> sortedTags =
 | 
			
		||||
                            tagsMap.entrySet().stream()
 | 
			
		||||
                                    .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
 | 
			
		||||
                    tagsMap = sortedTags.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    Stream<Map.Entry<String,Long>> sortedTags =
 | 
			
		||||
                            tagsMap.entrySet().stream()
 | 
			
		||||
                                    .sorted(Map.Entry.comparingByValue());
 | 
			
		||||
                    tagsMap = sortedTags.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Map<NodeRef, Long> tagNodeRefMap = new LinkedHashMap<>();
 | 
			
		||||
 | 
			
		||||
        for (Map.Entry<String, Long> entry : tagsMap.entrySet())
 | 
			
		||||
        {
 | 
			
		||||
            tagNodeRefMap.put(getTagNodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, entry.getKey()), entry.getValue());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return tagNodeRefMap;
 | 
			
		||||
            (childAssociation) -> new Pair<>(childAssociation.getChildRef(), childAssociation.getQName().getLocalName()));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1289,15 +1188,15 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
     */
 | 
			
		||||
    private void getTagScopes(final NodeRef nodeRef, List<NodeRef> tagScopes, boolean firstOnly)
 | 
			
		||||
    {
 | 
			
		||||
        Boolean hasAspect = AuthenticationUtil.runAs(new RunAsWork<Boolean>()
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public Boolean doWork() throws Exception
 | 
			
		||||
            {
 | 
			
		||||
                return new Boolean(nodeService.hasAspect(nodeRef,  ContentModel.ASPECT_TAGSCOPE));
 | 
			
		||||
            }
 | 
			
		||||
        }, AuthenticationUtil.getSystemUserName());
 | 
			
		||||
 | 
			
		||||
    	Boolean hasAspect = AuthenticationUtil.runAs(new RunAsWork<Boolean>()
 | 
			
		||||
    	{
 | 
			
		||||
			@Override
 | 
			
		||||
			public Boolean doWork() throws Exception 
 | 
			
		||||
			{
 | 
			
		||||
				return new Boolean(nodeService.hasAspect(nodeRef,  ContentModel.ASPECT_TAGSCOPE));
 | 
			
		||||
			}
 | 
			
		||||
		}, AuthenticationUtil.getSystemUserName());
 | 
			
		||||
    	
 | 
			
		||||
        if (Boolean.TRUE.equals(hasAspect) == true)
 | 
			
		||||
        {
 | 
			
		||||
            tagScopes.add(nodeRef);
 | 
			
		||||
@@ -1308,23 +1207,23 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        NodeRef parent = AuthenticationUtil.runAs(new RunAsWork<NodeRef>()
 | 
			
		||||
        {
 | 
			
		||||
            @Override
 | 
			
		||||
            public NodeRef doWork() throws Exception
 | 
			
		||||
            {
 | 
			
		||||
                NodeRef result = null;
 | 
			
		||||
                ChildAssociationRef assoc = nodeService.getPrimaryParent(nodeRef);
 | 
			
		||||
                if (assoc != null)
 | 
			
		||||
                {
 | 
			
		||||
                    result = assoc.getParentRef();
 | 
			
		||||
                }
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
        }, AuthenticationUtil.getSystemUserName());
 | 
			
		||||
    	{
 | 
			
		||||
			@Override
 | 
			
		||||
			public NodeRef doWork() throws Exception 
 | 
			
		||||
			{
 | 
			
		||||
				NodeRef result = null;
 | 
			
		||||
				ChildAssociationRef assoc = nodeService.getPrimaryParent(nodeRef);
 | 
			
		||||
		        if (assoc != null)
 | 
			
		||||
		        {
 | 
			
		||||
		            result = assoc.getParentRef();                          
 | 
			
		||||
		        }
 | 
			
		||||
		        return result;
 | 
			
		||||
			}
 | 
			
		||||
		}, AuthenticationUtil.getSystemUserName());
 | 
			
		||||
        
 | 
			
		||||
        if (parent != null)
 | 
			
		||||
        {
 | 
			
		||||
            getTagScopes(parent, tagScopes, firstOnly);
 | 
			
		||||
        	getTagScopes(parent, tagScopes, firstOnly);            
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1342,7 +1241,7 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
            // Do the search for nodes
 | 
			
		||||
            resultSet = this.searchService.query(
 | 
			
		||||
                storeRef, 
 | 
			
		||||
                LANGUAGE_LUCENE,
 | 
			
		||||
                SearchService.LANGUAGE_LUCENE, 
 | 
			
		||||
                "+PATH:\"/cm:taggable/cm:" + ISO9075.encode(tag) + "/member\"");
 | 
			
		||||
            List<NodeRef> nodeRefs = resultSet.getNodeRefs();
 | 
			
		||||
            return nodeRefs;
 | 
			
		||||
@@ -1371,7 +1270,7 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
            // Do query
 | 
			
		||||
            resultSet = this.searchService.query(
 | 
			
		||||
                storeRef, 
 | 
			
		||||
                LANGUAGE_LUCENE,
 | 
			
		||||
                SearchService.LANGUAGE_LUCENE, 
 | 
			
		||||
                "+PATH:\"" + pathString + "//*\" +PATH:\"/cm:taggable/cm:" + ISO9075.encode(tag) + "/member\"");
 | 
			
		||||
            List<NodeRef> nodeRefs = resultSet.getNodeRefs();
 | 
			
		||||
            return nodeRefs;
 | 
			
		||||
@@ -1639,7 +1538,7 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
    public void afterCheckOut(NodeRef workingCopy)
 | 
			
		||||
    {
 | 
			
		||||
        if (this.nodeService.exists(workingCopy) == true && this.nodeService.hasAspect(workingCopy, ContentModel.ASPECT_TAGGABLE) == true
 | 
			
		||||
                && this.nodeService.hasAspect(workingCopy, ASPECT_WORKING_COPY))
 | 
			
		||||
                && this.nodeService.hasAspect(workingCopy, ContentModel.ASPECT_WORKING_COPY))
 | 
			
		||||
        {
 | 
			
		||||
            updateAllScopeTags(workingCopy, Boolean.FALSE);
 | 
			
		||||
        }
 | 
			
		||||
@@ -1651,10 +1550,10 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Pair<String, Integer>> findTaggedNodesAndCountByTagName(StoreRef storeRef)
 | 
			
		||||
    {
 | 
			
		||||
        String queryTaggeble = "ASPECT:\"" + ContentModel.ASPECT_TAGGABLE + "\"" + "-ASPECT:\"" + ASPECT_WORKING_COPY + "\"";
 | 
			
		||||
        String queryTaggeble = "ASPECT:\"" + ContentModel.ASPECT_TAGGABLE + "\"" + "-ASPECT:\"" + ContentModel.ASPECT_WORKING_COPY + "\"";
 | 
			
		||||
        SearchParameters sp = new SearchParameters();
 | 
			
		||||
        sp.setQuery(queryTaggeble);
 | 
			
		||||
        sp.setLanguage(LANGUAGE_LUCENE);
 | 
			
		||||
        sp.setLanguage(SearchService.LANGUAGE_LUCENE);
 | 
			
		||||
        sp.addStore(storeRef);
 | 
			
		||||
        sp.addFieldFacet(new FieldFacet("TAG"));
 | 
			
		||||
 | 
			
		||||
@@ -1674,32 +1573,6 @@ public class TaggingServiceImpl implements TaggingService,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** {@inheritDoc} */
 | 
			
		||||
    @Override
 | 
			
		||||
    public long findCountByTagName(StoreRef storeRef, String name)
 | 
			
		||||
    {
 | 
			
		||||
        String query = "TAG:\"" + name + "\"" + "-ASPECT:\"" + ASPECT_WORKING_COPY + "\"";
 | 
			
		||||
        SearchParameters sp = new SearchParameters();
 | 
			
		||||
        sp.setQuery(query);
 | 
			
		||||
        sp.setLanguage(LANGUAGE_LUCENE);
 | 
			
		||||
        sp.addStore(storeRef);
 | 
			
		||||
 | 
			
		||||
        ResultSet resultSet = null;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // Do the search for nodes
 | 
			
		||||
            resultSet = this.searchService.query(sp);
 | 
			
		||||
            return resultSet.getNumberFound();
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            if (resultSet != null)
 | 
			
		||||
            {
 | 
			
		||||
                resultSet.close();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Experimental
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Pair<String, NodeRef>> createTags(final StoreRef storeRef, final List<String> tagNames)
 | 
			
		||||
 
 | 
			
		||||
@@ -29,12 +29,10 @@ package org.alfresco.repo.workflow;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @deprecated from 7.1.0
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Jamal Kaabi-Mofrad
 | 
			
		||||
 * @since 5.2.1
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
public interface WorkflowModelResetPassword
 | 
			
		||||
{
 | 
			
		||||
    // namespace
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,6 @@
 | 
			
		||||
package org.alfresco.service.cmr.search;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
@@ -151,21 +150,11 @@ public interface CategoryService
 | 
			
		||||
     */
 | 
			
		||||
    @Auditable(parameters = {"storeRef", "aspectName", "pagingRequest", "sortByName", "exactNamesFilter", "alikeNamesFilter"})
 | 
			
		||||
    default PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName,
 | 
			
		||||
                                                              Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
 | 
			
		||||
        Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
 | 
			
		||||
    {
 | 
			
		||||
        return new EmptyPagingResults<>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a collection of the root categories for an aspect/classification supporting multiple name filters.
 | 
			
		||||
     */
 | 
			
		||||
    @Auditable(parameters = {"storeRef", "aspectName", "exactNamesFilter", "alikeNamesFilter"})
 | 
			
		||||
    default Collection<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
 | 
			
		||||
    {
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the root categories for an aspect/classification with names that start with filter
 | 
			
		||||
     * 
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,6 @@ package org.alfresco.service.cmr.tagging;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.api.AlfrescoPublicApi;
 | 
			
		||||
import org.alfresco.query.EmptyPagingResults;
 | 
			
		||||
@@ -95,18 +94,6 @@ public interface TaggingService
 | 
			
		||||
    {
 | 
			
		||||
        return new EmptyPagingResults<>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a map of tag NodeRefs and their respective usage count filtered by name and sorted by tag name or count
 | 
			
		||||
     *
 | 
			
		||||
     * @param storeRef
 | 
			
		||||
     * @param parameterIncludes
 | 
			
		||||
     * @param sorting
 | 
			
		||||
     * @param exactNamesFilter
 | 
			
		||||
     * @param alikeNamesFilter
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    Map<NodeRef, Long> getTags(StoreRef storeRef, List<String>parameterIncludes, Pair<String, Boolean> sorting, Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter);
 | 
			
		||||
    
 | 
			
		||||
    /** 
 | 
			
		||||
     * Get all the tags currently available that match the provided filter.
 | 
			
		||||
@@ -340,7 +327,8 @@ public interface TaggingService
 | 
			
		||||
     */
 | 
			
		||||
    @NotAuditable
 | 
			
		||||
    Pair<List<String>, Integer> getPagedTags(StoreRef storeRef, String filter, int fromTag, int pageSize);
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Get tagged nodes and count of nodes group by tag name
 | 
			
		||||
     * 
 | 
			
		||||
@@ -350,16 +338,6 @@ public interface TaggingService
 | 
			
		||||
    @NotAuditable
 | 
			
		||||
    List<Pair<String, Integer>> findTaggedNodesAndCountByTagName(StoreRef storeRef);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the number of tagged nodes for a given tag.
 | 
			
		||||
     *
 | 
			
		||||
     * @param storeRef The store containing the nodes.
 | 
			
		||||
     * @param name The name of the tag.
 | 
			
		||||
     * @return The number of nodes tagged with the specified tag.
 | 
			
		||||
     */
 | 
			
		||||
    @NotAuditable
 | 
			
		||||
    long findCountByTagName(StoreRef storeRef, String name);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates orphan tags. Tag names case will be lowered.
 | 
			
		||||
     *
 | 
			
		||||
@@ -374,13 +352,6 @@ public interface TaggingService
 | 
			
		||||
    {
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param storeRef
 | 
			
		||||
     * @return a map with each tag name and its usage count
 | 
			
		||||
     */
 | 
			
		||||
    Map<String, Long> calculateCount(StoreRef storeRef);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,23 @@
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2017 Alfresco Software Limited
 | 
			
		||||
 * Copyright (C) 2005 - 2023 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%
 | 
			
		||||
@@ -32,43 +32,62 @@ import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Alfresco URL related utility functions.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @since 3.5
 | 
			
		||||
 */
 | 
			
		||||
public class UrlUtil
 | 
			
		||||
{
 | 
			
		||||
    // ${shareUrl} placeholder
 | 
			
		||||
    public static final Pattern PATTERN = Pattern.compile("\\$\\{shareUrl\\}");
 | 
			
		||||
 | 
			
		||||
    // ${alfrescoUrl} placeholder
 | 
			
		||||
    public static final Pattern REPO_PATTERN = Pattern.compile("\\$\\{alfrescoUrl\\}");
 | 
			
		||||
 | 
			
		||||
    // ${repoBaseUrl} placeholder
 | 
			
		||||
    public static final Pattern REPO_BASE_PATTERN = Pattern.compile("\\$\\{repoBaseUrl\\}");
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Builds up the Url to Alfresco based on the settings in the 
 | 
			
		||||
     *  {@link SysAdminParams}. 
 | 
			
		||||
     * @return Alfresco Url such as https://col.ab.or.ate/alfresco/
 | 
			
		||||
     *  or http://localhost:8080/alfresco/
 | 
			
		||||
     * Builds up the Url to Alfresco root url based on the settings in the
 | 
			
		||||
     *  {@link SysAdminParams}.
 | 
			
		||||
     * @return Alfresco base Url such as {@code https://col.ab.or.ate}
 | 
			
		||||
     *  or {@code http://localhost:8080}
 | 
			
		||||
     */
 | 
			
		||||
    public static String getAlfrescoBaseUrl(SysAdminParams sysAdminParams)
 | 
			
		||||
    {
 | 
			
		||||
        return buildBaseUrl(
 | 
			
		||||
                    sysAdminParams.getAlfrescoProtocol(),
 | 
			
		||||
                    sysAdminParams.getAlfrescoHost(),
 | 
			
		||||
                    sysAdminParams.getAlfrescoPort());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Builds up the Url to Alfresco context based on the settings in the
 | 
			
		||||
     *  {@link SysAdminParams}.
 | 
			
		||||
     * @return Alfresco Url such as {@code https://col.ab.or.ate/alfresco}
 | 
			
		||||
     *  or {@code http://localhost:8080/alfresco}
 | 
			
		||||
     */
 | 
			
		||||
    public static String getAlfrescoUrl(SysAdminParams sysAdminParams)
 | 
			
		||||
    {
 | 
			
		||||
        return buildUrl(
 | 
			
		||||
                sysAdminParams.getAlfrescoProtocol(),
 | 
			
		||||
                sysAdminParams.getAlfrescoHost(),
 | 
			
		||||
                sysAdminParams.getAlfrescoPort(),
 | 
			
		||||
                sysAdminParams.getAlfrescoContext());
 | 
			
		||||
                    sysAdminParams.getAlfrescoProtocol(),
 | 
			
		||||
                    sysAdminParams.getAlfrescoHost(),
 | 
			
		||||
                    sysAdminParams.getAlfrescoPort(),
 | 
			
		||||
                    sysAdminParams.getAlfrescoContext());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Builds up the Url to Share based on the settings in the 
 | 
			
		||||
     *  {@link SysAdminParams}. 
 | 
			
		||||
     * @return Alfresco Url such as https://col.ab.or.ate/share/
 | 
			
		||||
     *  or http://localhost:8081/share/
 | 
			
		||||
     * Builds up the Url to Share based on the settings in the
 | 
			
		||||
     *  {@link SysAdminParams}.
 | 
			
		||||
     * @return Alfresco Url such as {@code https://col.ab.or.ate/share}
 | 
			
		||||
     *  or {@code http://localhost:8081/share}
 | 
			
		||||
     */
 | 
			
		||||
    public static String getShareUrl(SysAdminParams sysAdminParams)
 | 
			
		||||
    {
 | 
			
		||||
        return buildUrl(
 | 
			
		||||
                sysAdminParams.getShareProtocol(),
 | 
			
		||||
                sysAdminParams.getShareHost(),
 | 
			
		||||
                sysAdminParams.getSharePort(),
 | 
			
		||||
                sysAdminParams.getShareContext());
 | 
			
		||||
                    sysAdminParams.getShareProtocol(),
 | 
			
		||||
                    sysAdminParams.getShareHost(),
 | 
			
		||||
                    sysAdminParams.getSharePort(),
 | 
			
		||||
                    sysAdminParams.getShareContext());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -80,8 +99,8 @@ public class UrlUtil
 | 
			
		||||
    /**
 | 
			
		||||
     * Builds URL to Api-Explorer based on the request only if the URL property is not provided
 | 
			
		||||
     *  {@link SysAdminParams}.
 | 
			
		||||
     * @return Rest-Api Url such as https://col.ab.or.ate/api-explorer/
 | 
			
		||||
     *  or http://localhost:8082/api-explorer/
 | 
			
		||||
     * @return Rest-Api Url such as {@code https://col.ab.or.ate/api-explorer}
 | 
			
		||||
     *  or {@code http://localhost:8082/api-explorer}
 | 
			
		||||
     */
 | 
			
		||||
    public static String getApiExplorerUrl(SysAdminParams sysAdminParams, String requestURL, String requestURI)
 | 
			
		||||
    {
 | 
			
		||||
@@ -124,6 +143,12 @@ public class UrlUtil
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static String buildUrl(String protocol, String host, int port, String context)
 | 
			
		||||
    {
 | 
			
		||||
        String baseUrl = buildBaseUrl(protocol, host, port);
 | 
			
		||||
        return baseUrl + '/' + context;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static String buildBaseUrl(String protocol, String host, int port)
 | 
			
		||||
    {
 | 
			
		||||
        StringBuilder url = new StringBuilder();
 | 
			
		||||
        url.append(protocol);
 | 
			
		||||
@@ -142,8 +167,33 @@ public class UrlUtil
 | 
			
		||||
            url.append(':');
 | 
			
		||||
            url.append(port);
 | 
			
		||||
        }
 | 
			
		||||
        url.append('/');
 | 
			
		||||
        url.append(context);
 | 
			
		||||
        return url.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Replaces the repo base url placeholder, namely {@literal ${repoBaseUrl}}, with value based on the settings in the
 | 
			
		||||
     * {@link SysAdminParams}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param value          the string value which contains the repoBase url placeholder
 | 
			
		||||
     * @param sysAdminParams the {@code SysAdminParams} object
 | 
			
		||||
     * @return if the given {@code value} contains repoBase url placeholder,
 | 
			
		||||
     * the placeholder is replaced with repoBase url; otherwise, the given {@code value} is simply returned
 | 
			
		||||
     */
 | 
			
		||||
    public static String replaceRepoBaseUrlPlaceholder(String value, SysAdminParams sysAdminParams)
 | 
			
		||||
    {
 | 
			
		||||
        if (value != null)
 | 
			
		||||
        {
 | 
			
		||||
            return REPO_BASE_PATTERN.matcher(value).replaceAll(getAlfrescoBaseUrl(sysAdminParams));
 | 
			
		||||
        }
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String replaceUrlPlaceholder(Pattern pattern, String value, String replacement)
 | 
			
		||||
    {
 | 
			
		||||
        if (value != null)
 | 
			
		||||
        {
 | 
			
		||||
            return pattern.matcher(value).replaceAll(replacement);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,8 @@
 | 
			
		||||
# a NodeRef of the template
 | 
			
		||||
# a class path of the template
 | 
			
		||||
 | 
			
		||||
# template assets url for share client
 | 
			
		||||
repo.client-app.share.templateAssetsUrl=${shareUrl}/res/components/images/
 | 
			
		||||
# template assets url for share client. i.e. The source url for the images/logo within the email
 | 
			
		||||
repo.client-app.share.templateAssetsUrl=${shareUrl}/res/components/images
 | 
			
		||||
# shared-link (quickShare) base url
 | 
			
		||||
repo.client-app.share.sharedLinkBaseUrl=${shareUrl}/s
 | 
			
		||||
# shared-link email template path
 | 
			
		||||
@@ -20,6 +20,14 @@ repo.client-app.share.resetPasswordPageUrl=${shareUrl}/page/reset-password
 | 
			
		||||
repo.client-app.share.confirmResetPasswordTemplatePath=
 | 
			
		||||
 | 
			
		||||
### Digital workspace template configurations
 | 
			
		||||
repo.client-app.workspace.workspaceUrl=${repoBaseUrl}/workspace
 | 
			
		||||
repo.client-app.workspace.inviteModeratedTemplatePath=
 | 
			
		||||
repo.client-app.workspace.workspaceUrl=workspace
 | 
			
		||||
repo.client-app.workspace.templateAssetsUrl=${alfrescoUrl}/images
 | 
			
		||||
# template assets url for workspace client. i.e. The source url for the images/logo within the email
 | 
			
		||||
repo.client-app.workspace.templateAssetsUrl=${repoBaseUrl}/alfresco/images/logo
 | 
			
		||||
 | 
			
		||||
# reset password request email template path
 | 
			
		||||
repo.client-app.workspace.requestResetPasswordTemplatePath=alfresco/templates/reset-password-email-templates/forgot-password-email-template.ftl
 | 
			
		||||
# reset password UI page url
 | 
			
		||||
repo.client-app.workspace.resetPasswordPageUrl=${workspaceUrl}/reset-password
 | 
			
		||||
# reset password confirmation email template path
 | 
			
		||||
repo.client-app.workspace.confirmResetPasswordTemplatePath=
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
repository.name=Main Repository
 | 
			
		||||
 | 
			
		||||
# Schema number
 | 
			
		||||
version.schema=18100
 | 
			
		||||
version.schema=18000
 | 
			
		||||
 | 
			
		||||
# Directory configuration
 | 
			
		||||
 | 
			
		||||
@@ -752,16 +752,12 @@ encryption.ssl.truststore.keyMetaData.location=
 | 
			
		||||
## HttpClient config for Transform Service
 | 
			
		||||
#enable mtls in HttpClientFactory for Transform Service.
 | 
			
		||||
httpclient.config.transform.mTLSEnabled=false
 | 
			
		||||
httpclient.config.transform.maxTotalConnections=20
 | 
			
		||||
httpclient.config.transform.maxHostConnections=20
 | 
			
		||||
httpclient.config.transform.maxTotalConnections=40
 | 
			
		||||
httpclient.config.transform.maxHostConnections=40
 | 
			
		||||
httpclient.config.transform.socketTimeout=5000
 | 
			
		||||
httpclient.config.transform.connectionRequestTimeout=5000
 | 
			
		||||
httpclient.config.transform.connectionTimeout=5000
 | 
			
		||||
 | 
			
		||||
# Property is disabled by default for security reasons, never enable it on for production environments.
 | 
			
		||||
# It will stop verification of hostnames placed in certificates returned with server responses to Repository requests towards transform services
 | 
			
		||||
httpclient.config.transform.hostnameVerificationDisabled=false
 | 
			
		||||
 | 
			
		||||
# Re-encryptor properties
 | 
			
		||||
encryption.reencryptor.chunkSize=100
 | 
			
		||||
encryption.reencryptor.numThreads=2
 | 
			
		||||
@@ -1349,7 +1345,7 @@ system.remove-alf_server-table-from-db.ignored=true
 | 
			
		||||
allow.unsecure.callback.jsonp=false
 | 
			
		||||
 | 
			
		||||
# pre-configured allow list of media/mime types to allow inline instead of attachment (via Content-Disposition response header)
 | 
			
		||||
content.nonAttach.mimetypes=application/pdf,image/jpeg,image/gif,image/png,image/tiff,image/bmp,application/octet-stream
 | 
			
		||||
content.nonAttach.mimetypes=application/pdf,image/jpeg,image/gif,image/png,image/tiff,image/bmp
 | 
			
		||||
 | 
			
		||||
# Zip file compression ratio threshold as a percentage, above which the zip file will be considered a "zip bomb" and the
 | 
			
		||||
# import extraction process cancelled.
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,428 @@
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
        td {
 | 
			
		||||
            font-family: 'Helvetica Neue', Arial, sans-serif;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        body {
 | 
			
		||||
            -webkit-font-smoothing: antialiased;
 | 
			
		||||
            -webkit-text-size-adjust: none;
 | 
			
		||||
            width: 100% ! important;
 | 
			
		||||
            height: 100% !important;
 | 
			
		||||
            color: #727174;
 | 
			
		||||
            font-weight: 400;
 | 
			
		||||
            font-size: 18px;
 | 
			
		||||
            margin: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        h1 {
 | 
			
		||||
            margin: 10px 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        h2 {
 | 
			
		||||
            color: #727174;
 | 
			
		||||
            font-weight: 600;
 | 
			
		||||
            font-size: 22px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        a {
 | 
			
		||||
            color: #0c79bf;
 | 
			
		||||
            text-decoration: underline;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        a.linkone {
 | 
			
		||||
            color: #0c79bf;
 | 
			
		||||
            text-decoration: underline;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .appleLinks a {
 | 
			
		||||
            color: #0c79bf;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .appleLinksWhite a {
 | 
			
		||||
            color: #0c79bf;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .force-full-width {
 | 
			
		||||
            width: 100% !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .body-padding {
 | 
			
		||||
            padding: 0 75px
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .force-width-80 {
 | 
			
		||||
            width: 80% !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        p {
 | 
			
		||||
            Margin-bottom: 1em;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .button {
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            font-size: 18px;
 | 
			
		||||
            font-family: sans-serif;
 | 
			
		||||
            font-weight: 400;
 | 
			
		||||
            -webkit-font-smoothing: antialiased;
 | 
			
		||||
            -webkit-text-size-adjust: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .button a {
 | 
			
		||||
            color: #ffffff;
 | 
			
		||||
            text-decoration: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        img {
 | 
			
		||||
            max-width: 600px;
 | 
			
		||||
            outline: none;
 | 
			
		||||
            text-decoration: none;
 | 
			
		||||
            -ms-interpolation-mode: bicubic;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        a img {
 | 
			
		||||
            border: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        table {
 | 
			
		||||
            border-collapse: collapse;
 | 
			
		||||
            mso-table-lspace: 0pt;
 | 
			
		||||
            mso-table-rspace: 0pt;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #outlook a {
 | 
			
		||||
            padding: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .ReadMsgBody {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .ExternalClass {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .backgroundTable {
 | 
			
		||||
            margin: 0 auto;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
            width: 100% !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        table td {
 | 
			
		||||
            border-collapse: collapse;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .ExternalClass * {
 | 
			
		||||
            line-height: 115%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .ExternalClass {
 | 
			
		||||
            vertical-align: middle
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /*]]>*/
 | 
			
		||||
    </style>
 | 
			
		||||
    <style type="text/css" media="screen">
 | 
			
		||||
        @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { {
 | 
			
		||||
            @-ms-viewport {
 | 
			
		||||
                width: 320px;
 | 
			
		||||
            }
 | 
			
		||||
            @viewport {
 | 
			
		||||
                width: 320px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
    <style type="text/css" media="screen">
 | 
			
		||||
        @media screen {
 | 
			
		||||
            * {
 | 
			
		||||
                font-family: 'Helvetica Neue', 'Arial', 'sans-serif' !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .w280 {
 | 
			
		||||
                width: 280px !important;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    </style>
 | 
			
		||||
    <style type="text/css" media="only screen and (max-width: 480px)">
 | 
			
		||||
        @media only screen and (max-width: 480px) {
 | 
			
		||||
            .full {
 | 
			
		||||
                display: block;
 | 
			
		||||
                width: 100%;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            table[class*="w320"] {
 | 
			
		||||
                width: 320px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            td[class*="w320"] {
 | 
			
		||||
                width: 280px !important;
 | 
			
		||||
                padding-left: 20px !important;
 | 
			
		||||
                padding-right: 20px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            img[class*="w320"] {
 | 
			
		||||
                width: 250px !important;
 | 
			
		||||
                height: 67px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            td[class*="mobile-spacing"] {
 | 
			
		||||
                padding-top: 10px !important;
 | 
			
		||||
                padding-bottom: 10px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            *[class*="mobile-hide"] {
 | 
			
		||||
                display: none !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            *[class*="mobile-br"] {
 | 
			
		||||
                font-size: 8px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            *[class*="mobile-brh"] {
 | 
			
		||||
                font-size: 1px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            td[class*="mobile-w20"] {
 | 
			
		||||
                width: 20px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            img[class*="mobile-w20"] {
 | 
			
		||||
                width: 20px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            td[class*="mobile-center"] {
 | 
			
		||||
                text-align: center !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            table[class*="w100p"] {
 | 
			
		||||
                width: 100% !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            td[class*="activate-now"] {
 | 
			
		||||
                padding-right: 0 !important;
 | 
			
		||||
                padding-top: 20px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            td[class*="mobile-resize"] {
 | 
			
		||||
                font-size: 22px !important;
 | 
			
		||||
                padding-left: 15px !important;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body offset="0" class="body" style="padding:0; margin:0; display:block; background:#f3f4f4; -webkit-text-size-adjust:none; " bgcolor="#EEEEEE">
 | 
			
		||||
<table align="center" cellpadding="0" cellspacing="0" width="100%" height="100%">
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td align="center" valign="top" style="background-color:#f3f4f4; " width="100%">
 | 
			
		||||
            <table cellspacing="0" cellpadding="0" width="600" class="w320">
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td align="center" valign="top">
 | 
			
		||||
 | 
			
		||||
                        <!--Snippet Block-->
 | 
			
		||||
                        <table border="0" cellspacing="0" cellpadding="0" align="center" width="100%">
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <table border="0" cellspacing="0" cellpadding="10" width="100%" align="center">
 | 
			
		||||
                                        <tbody>
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <td align="left">
 | 
			
		||||
                                                <table align="center" width="100%" border="0" cellspacing="0" cellpadding="0">
 | 
			
		||||
                                                    <tr>
 | 
			
		||||
                                                        <td align="right">
 | 
			
		||||
                                                            <div style="display:none;font-size:1px;color:#333333;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">
 | 
			
		||||
                                                                -
 | 
			
		||||
                                                            </div>
 | 
			
		||||
                                                        </td>
 | 
			
		||||
                                                    </tr>
 | 
			
		||||
                                                </table>
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                        </tbody>
 | 
			
		||||
                                    </table>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </table>
 | 
			
		||||
 | 
			
		||||
                        <!--Header Block-->
 | 
			
		||||
                        <table bgcolor="#FFFFFF" border="0" cellspacing="0" cellpadding="0" align="center" width="100%"
 | 
			
		||||
                               style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; border-bottom:solid 1px #dedee4; border-top:solid 1px #dedee4; width:100%!important; min-width:100%;">
 | 
			
		||||
                            <tr style="width:100%;">
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <table border="0" cellspacing="0" cellpadding="10" align="center" width="100%">
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <td align="left" valign="middle"><a style="color: #ffffff; text-decoration: none;" href=
 | 
			
		||||
                                                "https://www.alfresco.com/" target="_blank"
 | 
			
		||||
                                                ><span style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; color: #727174;"><img
 | 
			
		||||
                                                                style="border:none; display:block; border-collapse:collapse; outline:none; text-decoration:none;"
 | 
			
		||||
                                                                src="${template_assets_url}/hyland_logo.png" border="0"
 | 
			
		||||
                                                                alt="Alfresco" width="122" height="38"></span></a></td>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                    </table>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td align="right" bgcolor="#FFFFFF">
 | 
			
		||||
                                    <table border="0" cellspacing="0" cellpadding="10" align="center" width="100%">
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <td align="right" valign="middle"><span
 | 
			
		||||
                                                        style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; font-weight:200; color: #727174;">Digital Workspace</span>
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                    </table>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </table>
 | 
			
		||||
 | 
			
		||||
                        <!--Break-->
 | 
			
		||||
                        <table width="100%">
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td height="10">
 | 
			
		||||
                                    <div class="mobile-br"> </div>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </table>
 | 
			
		||||
 | 
			
		||||
                        <!--Body Block 1-->
 | 
			
		||||
                        <!--Banner Block-->
 | 
			
		||||
                        <table cellspacing="0" cellpadding="0" width="100%" style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; ">
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td style="border-collapse:collapse; ">
 | 
			
		||||
                                    <table cellspacing="0" cellpadding="0" width="100%" style="background-color:#0c79bf; border:none; ">
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <td style="border-collapse:collapse; ">
 | 
			
		||||
                                                <div class="mktEditable" id="Banner Image 1"><span
 | 
			
		||||
                                                            style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; color: #ffffff;"><img
 | 
			
		||||
                                                                src="${template_assets_url}/adw_logo.png" alt="Alfresco Products" style="width:100%; border:none;
 | 
			
		||||
display:block; border-collapse:collapse; outline:none; text-decoration:none;"></span></div>
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                    </table>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </table>
 | 
			
		||||
                        <!--Copy-->
 | 
			
		||||
                        <table cellspacing="0" cellpadding="0" align="center" bgcolor="#FFFFFF"
 | 
			
		||||
                               style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; border-bottom:solid 1px #dedee4; " width="100%">
 | 
			
		||||
                            <tr align="center">
 | 
			
		||||
                                <td style="background-color:#ffffff; " align="center">
 | 
			
		||||
                                    <table class="force-width-80" cellspacing="0" cellpadding="12" align="center" bgcolor="#FFFFFF" width="80%">
 | 
			
		||||
                                        <tbody>
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <td align="left">
 | 
			
		||||
                                                <!--Headline Text-->
 | 
			
		||||
                                                <div class="mktEditable" id="Headline Text 1" align="left" style="vertical-align:middle; "><span
 | 
			
		||||
                                                            style="color: #727174; margin: 0px; font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 22px; font-weight: 600; text-align: left; text-decoration: none; vertical-align:
 | 
			
		||||
middle;"><p>${message("templates.reset-password-email.ftl.title")}</p></span></div>
 | 
			
		||||
                                                <div class="mktEditable" id="Body Text 1" align="left">
 | 
			
		||||
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:18px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
 | 
			
		||||
<p>${message("templates.reset-password-email.ftl.detail")}</p>
 | 
			
		||||
</span>
 | 
			
		||||
 | 
			
		||||
                                                </div>
 | 
			
		||||
 | 
			
		||||
                                                <!--Break-->
 | 
			
		||||
                                                <table width="100%">
 | 
			
		||||
                                                    <tr>
 | 
			
		||||
                                                        <td height="5">
 | 
			
		||||
                                                            <div class="mobile-br"> </div>
 | 
			
		||||
                                                        </td>
 | 
			
		||||
                                                    </tr>
 | 
			
		||||
                                                </table>
 | 
			
		||||
 | 
			
		||||
                                                <!--Button-->
 | 
			
		||||
                                                <table style="margin:0 auto; " cellspacing="0" cellpadding="0" width="100%">
 | 
			
		||||
                                                    <tr>
 | 
			
		||||
                                                        <td style="text-align:center; margin:0 auto; ">
 | 
			
		||||
                                                            <div><!--[if mso]>
 | 
			
		||||
                                                                <v:rect xmlns:v="urn:schemas-microsoft-com:vml"
 | 
			
		||||
                                                                        xmlns:w="urn:schemas-microsoft-com:office:word"
 | 
			
		||||
                                                                        style="height:45px;v-text-anchor:middle;width:220px;" stroke="f"
 | 
			
		||||
                                                                        fillcolor="#47AA2">
 | 
			
		||||
                                                                    <w:anchorlock/>
 | 
			
		||||
                                                                    <center>
 | 
			
		||||
                                                                <![endif]-->
 | 
			
		||||
                                                                <div class="mktEditable" id="Button Text"><a href="${reset_password_url}" style="background-color:#47aa42; color:#ffffff; display:inline-block; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:18px; font-weight:400; line-height:45px; text-align:center; text-decoration:none; width:220px;
 | 
			
		||||
-webkit-text-size-adjust:none;">${message("templates.reset-password-email.ftl.reset_password_button")}</a></div>
 | 
			
		||||
                                                                <span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:12px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
 | 
			
		||||
<p>${message("templates.reset-password-email.ftl.ignore_message")}</p>
 | 
			
		||||
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:12px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
 | 
			
		||||
<hr>
 | 
			
		||||
<p>${message("templates.reset-password-email.ftl.having_trouble_clicking_button")}</p>
 | 
			
		||||
<p><a href="${reset_password_url}">${reset_password_url}</a></p>
 | 
			
		||||
</span>
 | 
			
		||||
                                                                    <!--[if mso]>
 | 
			
		||||
                                                                    </center>
 | 
			
		||||
                                                                    </v:rect>
 | 
			
		||||
                                                                    <![endif]--></div>
 | 
			
		||||
                                                        </td>
 | 
			
		||||
                                                    </tr>
 | 
			
		||||
                                                </table>
 | 
			
		||||
 | 
			
		||||
                                                <!--Break-->
 | 
			
		||||
                                                <table width="100%">
 | 
			
		||||
                                                    <tr>
 | 
			
		||||
                                                        <td height="5">
 | 
			
		||||
                                                            <div class="mobile-br"> </div>
 | 
			
		||||
                                                        </td>
 | 
			
		||||
                                                    </tr>
 | 
			
		||||
                                                </table>
 | 
			
		||||
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                        </tbody>
 | 
			
		||||
                                    </table>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </table>
 | 
			
		||||
 | 
			
		||||
                        <!--Break-->
 | 
			
		||||
                        <table width="100%">
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td height="10">
 | 
			
		||||
                                    <div class="mobile-br"> </div>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </table>
 | 
			
		||||
 | 
			
		||||
                        <!--Footer Block-->
 | 
			
		||||
                        <table border="0" cellspacing="0" cellpadding="0" align="center" width="100%">
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td align="center">
 | 
			
		||||
                                    <a style="color: #0c79bf; text-decoration: underline;" href="http://www.alfresco.com/company/contact"
 | 
			
		||||
                                       target="_blank"><span
 | 
			
		||||
                                                style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #0c79bf;">${message("templates.generic-email.ftl.contact_us")}</span></a>
 | 
			
		||||
                                    <span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial,
 | 
			
		||||
sans-serif; font-size: 10px; color: #b3b3b8;">© ${date?string["yyyy"]} Alfresco Software, Inc.
 | 
			
		||||
                                    ${message("templates.generic-email.ftl.copy_right")}</span><br>
 | 
			
		||||
                                    <span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #b3b3b8;">Bridge Ave, The Place Maidenhead SL6 1AF United Kingdom</span><br>
 | 
			
		||||
                                    <span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #b3b3b8;">1825 S Grant St, Suite 900 San Mateo, CA 94402 USA</span><br>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </table>
 | 
			
		||||
 | 
			
		||||
                        <!--Break-->
 | 
			
		||||
                        <table width="100%">
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td height="10">
 | 
			
		||||
                                    <div class="mobile-br"> </div>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </table>
 | 
			
		||||
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            </table>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
</table>
 | 
			
		||||
<!--Litmus Tracking-->
 | 
			
		||||
<table style="display: none;">
 | 
			
		||||
    <tbody>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td style="width: 0px; display: none; overflow: hidden; max-height: 0px;">{{my.Litmus_Code}}</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
</table>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@@ -250,7 +250,8 @@ import org.junit.runners.Suite;
 | 
			
		||||
    org.alfresco.repo.event2.RepoEvent2UnitSuite.class,
 | 
			
		||||
 | 
			
		||||
    org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class,
 | 
			
		||||
    org.alfresco.repo.tagging.TaggingServiceImplUnitTest.class
 | 
			
		||||
    org.alfresco.repo.tagging.TaggingServiceImplUnitTest.class,
 | 
			
		||||
    org.alfresco.repo.client.config.ClientAppConfigUnitTest.class
 | 
			
		||||
})
 | 
			
		||||
public class AllUnitTestsSuite
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -31,16 +31,12 @@ import static org.junit.Assert.assertNotNull;
 | 
			
		||||
import static org.junit.Assert.assertNull;
 | 
			
		||||
import static org.junit.Assert.fail;
 | 
			
		||||
import org.alfresco.repo.client.config.ClientAppConfig.ClientApp;
 | 
			
		||||
import org.alfresco.service.cmr.repository.TemporalSourceOptions;
 | 
			
		||||
import org.alfresco.util.ApplicationContextHelper;
 | 
			
		||||
import org.alfresco.util.testing.category.LuceneTests;
 | 
			
		||||
import org.junit.AfterClass;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.BeforeClass;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.experimental.categories.Category;
 | 
			
		||||
import org.springframework.context.ApplicationContext;
 | 
			
		||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
@@ -53,15 +49,15 @@ import java.util.Map;
 | 
			
		||||
@Category(LuceneTests.class)
 | 
			
		||||
public class ClientAppConfigTest
 | 
			
		||||
{
 | 
			
		||||
    private ApplicationContext context;
 | 
			
		||||
    private ClientAppConfig clientAppConfig;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUp() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        ApplicationContextHelper.closeApplicationContext();
 | 
			
		||||
        context = ApplicationContextHelper.getApplicationContext(new String[] { ApplicationContextHelper.CONFIG_LOCATIONS[0],
 | 
			
		||||
                "classpath:org/alfresco/repo/client/config/test-repo-clients-apps-context.xml" });
 | 
			
		||||
        ApplicationContext context = ApplicationContextHelper.getApplicationContext(
 | 
			
		||||
                    new String[] { ApplicationContextHelper.CONFIG_LOCATIONS[0],
 | 
			
		||||
                                "classpath:org/alfresco/repo/client/config/test-repo-clients-apps-context.xml" });
 | 
			
		||||
 | 
			
		||||
        clientAppConfig = context.getBean("clientAppConfigTest", ClientAppConfig.class);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,266 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Repository
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2023 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.client.config;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertFalse;
 | 
			
		||||
import static org.junit.Assert.assertNotNull;
 | 
			
		||||
import static org.junit.Assert.assertNull;
 | 
			
		||||
import static org.junit.Assert.assertTrue;
 | 
			
		||||
import static org.mockito.Mockito.when;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Properties;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.admin.SysAdminParams;
 | 
			
		||||
import org.alfresco.repo.client.config.ClientAppConfig.ClientApp;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.mockito.Mock;
 | 
			
		||||
import org.mockito.junit.MockitoJUnitRunner;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Jamal Kaabi-Mofrad
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(MockitoJUnitRunner.class)
 | 
			
		||||
public class ClientAppConfigUnitTest
 | 
			
		||||
{
 | 
			
		||||
    private ClientAppConfig clientAppConfig;
 | 
			
		||||
    @Mock
 | 
			
		||||
    private SysAdminParams  sysAdminParams;
 | 
			
		||||
 | 
			
		||||
    private AtomicBoolean initialised;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUp() throws Exception
 | 
			
		||||
 | 
			
		||||
    {    // This in not initialised yet. i.e. the properties are not processed yet.
 | 
			
		||||
        // The processing will start when you call the 'onBootstrap()' method
 | 
			
		||||
        clientAppConfig = buildClientAppConfig();
 | 
			
		||||
 | 
			
		||||
        initialised = new AtomicBoolean(false);
 | 
			
		||||
 | 
			
		||||
        when(sysAdminParams.getAlfrescoProtocol()).thenReturn("http");
 | 
			
		||||
        when(sysAdminParams.getAlfrescoHost()).thenReturn("localhost");
 | 
			
		||||
        when(sysAdminParams.getAlfrescoPort()).thenReturn(8080);
 | 
			
		||||
 | 
			
		||||
        when(sysAdminParams.getShareProtocol()).thenReturn("http");
 | 
			
		||||
        when(sysAdminParams.getShareHost()).thenReturn("localhost");
 | 
			
		||||
        when(sysAdminParams.getSharePort()).thenReturn(8081);
 | 
			
		||||
        when(sysAdminParams.getShareContext()).thenReturn("share");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ClientAppConfig buildClientAppConfig()
 | 
			
		||||
    {
 | 
			
		||||
        Properties defaultProps = getWorkspaceAppProperties();
 | 
			
		||||
        defaultProps.putAll(getCoolAppProperties());
 | 
			
		||||
        defaultProps.putAll(getShareProperties());
 | 
			
		||||
        Properties globalProps = new Properties();
 | 
			
		||||
 | 
			
		||||
        ClientAppConfig config = new ClientAppConfig();
 | 
			
		||||
        config.setDefaultProperties(defaultProps);
 | 
			
		||||
        config.setGlobalProperties(globalProps);
 | 
			
		||||
        return config;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testWorkspaceClient()
 | 
			
		||||
    {
 | 
			
		||||
        ClientApp client = getClientApp("workspace");
 | 
			
		||||
        assertEquals("workspace", client.getName());
 | 
			
		||||
        assertEquals("${workspaceUrl}/images", client.getTemplateAssetsUrl());
 | 
			
		||||
        assertEquals("${repoBaseUrl}/workspace", client.getClientUrl());
 | 
			
		||||
        assertEquals("workspaceUrl", client.getClientUrlPropKey());
 | 
			
		||||
        assertEquals("\\$\\{workspaceUrl}", client.getClientUrlPlaceholderPattern()
 | 
			
		||||
                    .pattern());
 | 
			
		||||
 | 
			
		||||
        Map<String, String> properties = client.getProperties();
 | 
			
		||||
        assertNotNull(properties);
 | 
			
		||||
        assertNull("Not Set", properties.get("inviteModeratedTemplatePath"));
 | 
			
		||||
        assertEquals("alfresco/templates/test-email-templates/test-email-template.ftl",
 | 
			
		||||
                     properties.get("requestResetPasswordTemplatePath"));
 | 
			
		||||
        assertEquals("${workspaceUrl}/reset-password", properties.get("resetPasswordPageUrl"));
 | 
			
		||||
        assertEquals("some/path", properties.get("confirmResetPasswordTemplatePath"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testCoolAppClient()
 | 
			
		||||
    {
 | 
			
		||||
        ClientApp client = getClientApp("coolApp");
 | 
			
		||||
        assertEquals("coolApp", client.getName());
 | 
			
		||||
        assertEquals("${coolAppUrl}/images", client.getTemplateAssetsUrl());
 | 
			
		||||
        assertEquals("http://localhost:8090/cool-app", client.getClientUrl());
 | 
			
		||||
        assertEquals("coolAppUrl", client.getClientUrlPropKey());
 | 
			
		||||
        assertEquals("\\$\\{coolAppUrl}", client.getClientUrlPlaceholderPattern()
 | 
			
		||||
                    .pattern());
 | 
			
		||||
 | 
			
		||||
        Map<String, String> properties = client.getProperties();
 | 
			
		||||
        assertNotNull(properties);
 | 
			
		||||
        assertEquals("${coolAppUrl}/page-one/page-two", properties.get("testPropUrl"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void resolveWorkspacePlaceholders()
 | 
			
		||||
    {
 | 
			
		||||
        ClientApp client = getClientApp("workspace");
 | 
			
		||||
        // Raw properties
 | 
			
		||||
        assertEquals("${repoBaseUrl}/workspace", client.getClientUrl());
 | 
			
		||||
        assertEquals("workspaceUrl", client.getClientUrlPropKey());
 | 
			
		||||
        assertEquals("\\$\\{workspaceUrl}", client.getClientUrlPlaceholderPattern()
 | 
			
		||||
                    .pattern());
 | 
			
		||||
        assertEquals("${workspaceUrl}/images", client.getTemplateAssetsUrl());
 | 
			
		||||
        assertEquals("${workspaceUrl}/reset-password", client.getProperty("resetPasswordPageUrl"));
 | 
			
		||||
 | 
			
		||||
        // Resolved properties
 | 
			
		||||
        //  String clientUrl = UrlUtil.replaceRepoBaseUrlPlaceholder(client.getClientUrl(), sysAdminParams);
 | 
			
		||||
        assertEquals("http://localhost:8080/workspace", client.getResolvedClientUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("http://localhost:8080/workspace/images", client.getResolvedTemplateAssetsUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("http://localhost:8080/workspace/reset-password",
 | 
			
		||||
                     client.getResolvedProperty("resetPasswordPageUrl", sysAdminParams));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void resolveCoolAppPlaceholders()
 | 
			
		||||
    {
 | 
			
		||||
        ClientApp client = getClientApp("coolApp");
 | 
			
		||||
        // Resolved properties
 | 
			
		||||
        assertEquals("http://localhost:8090/cool-app", client.getResolvedClientUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("http://localhost:8090/cool-app/images", client.getResolvedTemplateAssetsUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("http://localhost:8090/cool-app/page-one/page-two",
 | 
			
		||||
                     client.getResolvedProperty("testPropUrl", sysAdminParams));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void resolveSharePlaceholders()
 | 
			
		||||
    {
 | 
			
		||||
        ClientApp client = getClientApp("share");
 | 
			
		||||
        // Resolved properties
 | 
			
		||||
        assertEquals("http://localhost:8081/share", client.getResolvedClientUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("http://localhost:8081/share/res/components/images",
 | 
			
		||||
                     client.getResolvedTemplateAssetsUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("http://localhost:8081/share/page/reset-password",
 | 
			
		||||
                     client.getResolvedProperty("resetPasswordPageUrl", sysAdminParams));
 | 
			
		||||
        assertEquals("http://localhost:8081/share/s", client.getResolvedProperty("sharedLinkBaseUrl", sysAdminParams));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testClientsPropertiesOverride()
 | 
			
		||||
    {
 | 
			
		||||
        Properties globalProps = new Properties();
 | 
			
		||||
        globalProps.put("repo.client-app.workspace.workspaceUrl", "https://develop.envalfresco.com/#");
 | 
			
		||||
        globalProps.put("repo.client-app.share.shareUrl", "https://develop.envalfresco.com/share");
 | 
			
		||||
        globalProps.put("repo.client-app.coolApp.coolAppUrl", "https://develop.envalfresco.com/eval/cool-app");
 | 
			
		||||
 | 
			
		||||
        clientAppConfig.setGlobalProperties(globalProps);
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         * Workspace client URL Override
 | 
			
		||||
         */
 | 
			
		||||
        ClientApp workspaceClient = getClientApp("workspace");
 | 
			
		||||
        // Resolved properties
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/#", workspaceClient.getResolvedClientUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/#/images",
 | 
			
		||||
                     workspaceClient.getResolvedTemplateAssetsUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/#/reset-password",
 | 
			
		||||
                     workspaceClient.getResolvedProperty("resetPasswordPageUrl", sysAdminParams));
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         * Share client URL Override
 | 
			
		||||
         */
 | 
			
		||||
        ClientApp shareClient = getClientApp("share");
 | 
			
		||||
        // Resolved properties
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/share", shareClient.getResolvedClientUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/share/res/components/images",
 | 
			
		||||
                     shareClient.getResolvedTemplateAssetsUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/share/page/reset-password",
 | 
			
		||||
                     shareClient.getResolvedProperty("resetPasswordPageUrl", sysAdminParams));
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/share/s",
 | 
			
		||||
                     shareClient.getResolvedProperty("sharedLinkBaseUrl", sysAdminParams));
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         * coolApp client URL Override
 | 
			
		||||
         */
 | 
			
		||||
        ClientApp coolAppClient = getClientApp("coolApp");
 | 
			
		||||
        // Resolved properties
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/eval/cool-app",
 | 
			
		||||
                     coolAppClient.getResolvedClientUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/eval/cool-app/images",
 | 
			
		||||
                     coolAppClient.getResolvedTemplateAssetsUrl(sysAdminParams));
 | 
			
		||||
        assertEquals("https://develop.envalfresco.com/eval/cool-app/page-one/page-two",
 | 
			
		||||
                     coolAppClient.getResolvedProperty("testPropUrl", sysAdminParams));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Properties getWorkspaceAppProperties()
 | 
			
		||||
    {
 | 
			
		||||
        Properties props = new Properties();
 | 
			
		||||
        props.put("repo.client-app.workspace.inviteModeratedTemplatePath", "");
 | 
			
		||||
        props.put("repo.client-app.workspace.workspaceUrl", "${repoBaseUrl}/workspace");
 | 
			
		||||
        props.put("repo.client-app.workspace.templateAssetsUrl", "${workspaceUrl}/images");
 | 
			
		||||
        props.put("repo.client-app.workspace.requestResetPasswordTemplatePath",
 | 
			
		||||
                  "alfresco/templates/test-email-templates/test-email-template.ftl");
 | 
			
		||||
        props.put("repo.client-app.workspace.resetPasswordPageUrl", "${workspaceUrl}/reset-password");
 | 
			
		||||
        props.put("repo.client-app.workspace.confirmResetPasswordTemplatePath", "some/path");
 | 
			
		||||
 | 
			
		||||
        return props;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Properties getShareProperties()
 | 
			
		||||
    {
 | 
			
		||||
        Properties props = new Properties();
 | 
			
		||||
        props.put("repo.client-app.share.templateAssetsUrl", "${shareUrl}/res/components/images");
 | 
			
		||||
        props.put("repo.client-app.share.resetPasswordPageUrl", "${shareUrl}/page/reset-password");
 | 
			
		||||
        props.put("repo.client-app.share.sharedLinkBaseUrl", "${shareUrl}/s");
 | 
			
		||||
 | 
			
		||||
        return props;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Properties getCoolAppProperties()
 | 
			
		||||
    {
 | 
			
		||||
        Properties props = new Properties();
 | 
			
		||||
        props.put("repo.client-app.coolApp.coolAppUrl", "http://localhost:8090/cool-app");
 | 
			
		||||
        props.put("repo.client-app.coolApp.templateAssetsUrl", "${coolAppUrl}/images");
 | 
			
		||||
        props.put("repo.client-app.coolApp.testPropUrl", "${coolAppUrl}/page-one/page-two");
 | 
			
		||||
 | 
			
		||||
        return props;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ClientApp getClientApp(String clientName)
 | 
			
		||||
    {
 | 
			
		||||
        if (!initialised.get())
 | 
			
		||||
        {
 | 
			
		||||
            clientAppConfig.onBootstrap(null);
 | 
			
		||||
            initialised.set(true);
 | 
			
		||||
        }
 | 
			
		||||
        Map<String, ClientApp> clients = clientAppConfig.getClients();
 | 
			
		||||
        assertFalse(clients.isEmpty());
 | 
			
		||||
        assertTrue(clientName + " client is expected.", clientAppConfig.exists(clientName));
 | 
			
		||||
        ClientApp client = clients.get(clientName);
 | 
			
		||||
        assertNotNull(clientName + " client can't be null.", client);
 | 
			
		||||
        return client;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -30,7 +30,6 @@ import static org.junit.Assert.assertNotEquals;
 | 
			
		||||
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
@@ -420,65 +419,6 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
 | 
			
		||||
        assertTrue("New rendition content hash code was not generated", isValidRenditionContentHashCode(contentHashCode3));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testModifierAfterMultipleRenditionRequests() throws InterruptedException
 | 
			
		||||
    {
 | 
			
		||||
        String user1 = createRandomUser();
 | 
			
		||||
        String user2 = createRandomUser();
 | 
			
		||||
        List<NodeRef> nodes = new ArrayList<NodeRef>();
 | 
			
		||||
 | 
			
		||||
        final int TOTAL_NODES = 10;
 | 
			
		||||
 | 
			
		||||
        // Create nodes
 | 
			
		||||
        for (int i = 0; i < TOTAL_NODES; i++)
 | 
			
		||||
        {
 | 
			
		||||
            NodeRef n = createSource(user1, "quick.jpg");
 | 
			
		||||
            render(user1, n, DOC_LIB);
 | 
			
		||||
            nodes.add(n);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Ask rendition multiple times with 'user2'
 | 
			
		||||
        for (int j = 0; j < 10; j++)
 | 
			
		||||
        {
 | 
			
		||||
            for (int i = 0; i < TOTAL_NODES; i++)
 | 
			
		||||
            {
 | 
			
		||||
                render(user2, nodes.get(i), DOC_LIB);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < TOTAL_NODES; i++)
 | 
			
		||||
        {
 | 
			
		||||
            waitForRendition(user1, nodes.get(i), DOC_LIB, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check the modifier is still user1
 | 
			
		||||
        assertEquals(TOTAL_NODES, countModifier(nodes, user1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int countModifier(List<NodeRef> nodes, String user)
 | 
			
		||||
    {
 | 
			
		||||
        int count = 0;
 | 
			
		||||
        for (int i = 0; i < nodes.size(); i++)
 | 
			
		||||
        {
 | 
			
		||||
            count += compareModifier(nodes.get(i), user);
 | 
			
		||||
        }
 | 
			
		||||
        return count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int compareModifier(NodeRef nodeRef, String user)
 | 
			
		||||
    {
 | 
			
		||||
        int res = 0;
 | 
			
		||||
        if (nodeRef != null && user != null)
 | 
			
		||||
        {
 | 
			
		||||
            String modifier = nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIER).toString();
 | 
			
		||||
            if (user.equals(modifier))
 | 
			
		||||
            {
 | 
			
		||||
                res = 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @deprecated can be removed when we remove the original RenditionService
 | 
			
		||||
     */
 | 
			
		||||
@@ -682,4 +622,4 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
 | 
			
		||||
            renditionService2.setEnabled(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -30,6 +30,9 @@ import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertFalse;
 | 
			
		||||
import static org.junit.Assert.assertNotNull;
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.repo.admin.SysAdminParams;
 | 
			
		||||
import org.alfresco.repo.client.config.ClientAppConfig;
 | 
			
		||||
import org.alfresco.repo.client.config.ClientAppConfig.ClientApp;
 | 
			
		||||
import org.alfresco.repo.client.config.ClientAppNotFoundException;
 | 
			
		||||
import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.InvalidResetPasswordWorkflowException;
 | 
			
		||||
import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.ResetPasswordDetails;
 | 
			
		||||
@@ -47,6 +50,7 @@ import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.alfresco.util.GUID;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.alfresco.util.TestHelper;
 | 
			
		||||
import org.alfresco.util.UrlUtil;
 | 
			
		||||
import org.alfresco.util.email.EmailUtil;
 | 
			
		||||
import org.alfresco.util.test.junitrules.ApplicationContextInit;
 | 
			
		||||
import org.alfresco.util.test.junitrules.RunAsFullyAuthenticatedRule;
 | 
			
		||||
@@ -88,9 +92,12 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
    private static PersonService personService;
 | 
			
		||||
    private static Properties globalProperties;
 | 
			
		||||
    private static WorkflowService workflowService;
 | 
			
		||||
    private static ClientAppConfig clientAppConfig;
 | 
			
		||||
    private static SysAdminParams sysAdminParams;
 | 
			
		||||
 | 
			
		||||
    private static TestPerson testPerson;
 | 
			
		||||
    private static EmailUtil emailUtil;
 | 
			
		||||
    private static TestPerson testPerson1;
 | 
			
		||||
    private static TestPerson testPerson2;
 | 
			
		||||
    private static EmailUtil  emailUtil;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass
 | 
			
		||||
    public static void initStaticData() throws Exception
 | 
			
		||||
@@ -103,36 +110,57 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
        personService = APP_CONTEXT_INIT.getApplicationContext().getBean("personService", PersonService.class);
 | 
			
		||||
        globalProperties = APP_CONTEXT_INIT.getApplicationContext().getBean("global-properties", Properties.class);
 | 
			
		||||
        workflowService = APP_CONTEXT_INIT.getApplicationContext().getBean("WorkflowService", WorkflowService.class);
 | 
			
		||||
        clientAppConfig = APP_CONTEXT_INIT.getApplicationContext().getBean("clientAppConfig", ClientAppConfig.class);
 | 
			
		||||
        sysAdminParams = APP_CONTEXT_INIT.getApplicationContext().getBean("sysAdminParams", SysAdminParams.class);
 | 
			
		||||
        emailUtil = new EmailUtil(APP_CONTEXT_INIT.getApplicationContext());
 | 
			
		||||
        emailUtil.reset();
 | 
			
		||||
 | 
			
		||||
        String userName = "jane.doe" + System.currentTimeMillis();
 | 
			
		||||
        testPerson = new TestPerson()
 | 
			
		||||
                    .setUserName(userName)
 | 
			
		||||
        String userName1 = "jane.doe" + System.currentTimeMillis();
 | 
			
		||||
        testPerson1 = new TestPerson()
 | 
			
		||||
                    .setUserName(userName1)
 | 
			
		||||
                    .setFirstName("Jane")
 | 
			
		||||
                    .setLastName("doe")
 | 
			
		||||
                    .setLastName("Doe")
 | 
			
		||||
                    .setPassword("password")
 | 
			
		||||
                    .setEmail(userName + "@example.com");
 | 
			
		||||
                    .setEmail(userName1 + "@example.com");
 | 
			
		||||
 | 
			
		||||
        String userName2 = "sara.blogs" + System.currentTimeMillis();
 | 
			
		||||
        testPerson2 = new TestPerson()
 | 
			
		||||
                    .setUserName(userName2)
 | 
			
		||||
                    .setFirstName("Sara")
 | 
			
		||||
                    .setLastName("Blogs")
 | 
			
		||||
                    .setPassword("password")
 | 
			
		||||
                    .setEmail(userName2 + "@example.com");
 | 
			
		||||
 | 
			
		||||
        AuthenticationUtil.setRunAsUserSystem();
 | 
			
		||||
        transactionHelper.doInTransaction((RetryingTransactionCallback<Void>) () ->
 | 
			
		||||
        {
 | 
			
		||||
            createUser(testPerson);
 | 
			
		||||
            createUser(testPerson1);
 | 
			
		||||
            createUser(testPerson2);
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Restore authentication to pre-test state.
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            AuthenticationUtil.popAuthentication();
 | 
			
		||||
        }
 | 
			
		||||
        catch(EmptyStackException e)
 | 
			
		||||
        {
 | 
			
		||||
            // Nothing to do.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterClass
 | 
			
		||||
    public static void cleanUp()
 | 
			
		||||
    {
 | 
			
		||||
        resetPasswordService.setSendEmailAsynchronously(Boolean.valueOf(
 | 
			
		||||
        resetPasswordService.setSendEmailAsynchronously(Boolean.parseBoolean(
 | 
			
		||||
                    globalProperties.getProperty("system.reset-password.sendEmailAsynchronously")));
 | 
			
		||||
        resetPasswordService.setDefaultEmailSender((String) globalProperties.get("system.email.sender.default"));
 | 
			
		||||
 | 
			
		||||
        AuthenticationUtil.setRunAsUserSystem();
 | 
			
		||||
        transactionHelper.doInTransaction(() ->
 | 
			
		||||
        {
 | 
			
		||||
            personService.deletePerson(testPerson.userName);
 | 
			
		||||
            personService.deletePerson(testPerson1.userName);
 | 
			
		||||
            personService.deletePerson(testPerson2.userName);
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -157,21 +185,21 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
    public void testResetPassword() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Try the credential before change of password
 | 
			
		||||
        authenticateUser(testPerson.userName, testPerson.password);
 | 
			
		||||
        authenticateUser(testPerson1.userName, testPerson1.password);
 | 
			
		||||
 | 
			
		||||
        // Make sure to run as system
 | 
			
		||||
        AuthenticationUtil.clearCurrentSecurityContext();
 | 
			
		||||
        AuthenticationUtil.setRunAsUserSystem();
 | 
			
		||||
 | 
			
		||||
        // Request password reset
 | 
			
		||||
        resetPasswordService.requestReset(testPerson.userName, "share");
 | 
			
		||||
        resetPasswordService.requestReset(testPerson1.userName, "share");
 | 
			
		||||
        assertEquals("A reset password email should have been sent.", 1, emailUtil.getSentCount());
 | 
			
		||||
        // Check the email
 | 
			
		||||
        MimeMessage msg = emailUtil.getLastEmail();
 | 
			
		||||
        assertNotNull("There should be an email.", msg);
 | 
			
		||||
        assertEquals("Should've been only one email recipient.", 1, msg.getAllRecipients().length);
 | 
			
		||||
        // Check the recipient is the person who requested the reset password
 | 
			
		||||
        assertEquals(testPerson.email, msg.getAllRecipients()[0].toString());
 | 
			
		||||
        assertEquals(testPerson1.email, msg.getAllRecipients()[0].toString());
 | 
			
		||||
        //Check the sender is what we set as default
 | 
			
		||||
        assertEquals(DEFAULT_SENDER, msg.getFrom()[0].toString());
 | 
			
		||||
        // There should be a subject
 | 
			
		||||
@@ -192,7 +220,7 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
        emailUtil.reset();
 | 
			
		||||
        // Now that we have got the email, try to reset the password
 | 
			
		||||
        ResetPasswordDetails passwordDetails = new ResetPasswordDetails()
 | 
			
		||||
                    .setUserId(testPerson.userName)
 | 
			
		||||
                    .setUserId(testPerson1.userName)
 | 
			
		||||
                    .setPassword("newPassword")
 | 
			
		||||
                    .setWorkflowId(pair.getFirst())
 | 
			
		||||
                    .setWorkflowKey(pair.getSecond());
 | 
			
		||||
@@ -204,7 +232,7 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
        assertNotNull("There should be an email.", msg);
 | 
			
		||||
        assertEquals("Should've been only one email recipient.", 1, msg.getAllRecipients().length);
 | 
			
		||||
        // Check the recipient is the person who requested the reset password
 | 
			
		||||
        assertEquals(testPerson.email, msg.getAllRecipients()[0].toString());
 | 
			
		||||
        assertEquals(testPerson1.email, msg.getAllRecipients()[0].toString());
 | 
			
		||||
        // Check the sender is what we set as default
 | 
			
		||||
        assertEquals(DEFAULT_SENDER, msg.getFrom()[0].toString());
 | 
			
		||||
        // There should be a subject
 | 
			
		||||
@@ -215,12 +243,12 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
        assertEquals(msg.getSubject(), I18NUtil.getMessage(emailSubjectKey));
 | 
			
		||||
 | 
			
		||||
        // Try the old credential
 | 
			
		||||
        TestHelper.assertThrows(() -> authenticateUser(testPerson.userName, testPerson.password),
 | 
			
		||||
        TestHelper.assertThrows(() -> authenticateUser(testPerson1.userName, testPerson1.password),
 | 
			
		||||
                    AuthenticationException.class,
 | 
			
		||||
                    "As the user changed her password, the authentication should have failed.");
 | 
			
		||||
 | 
			
		||||
        // Try the new credential
 | 
			
		||||
        authenticateUser(testPerson.userName, "newPassword");
 | 
			
		||||
        authenticateUser(testPerson1.userName, "newPassword");
 | 
			
		||||
 | 
			
		||||
        // Make sure to run as system
 | 
			
		||||
        AuthenticationUtil.clearCurrentSecurityContext();
 | 
			
		||||
@@ -237,12 +265,12 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
    public void testRequestResetPasswordInvalid() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Request password reset
 | 
			
		||||
        TestHelper.assertThrows(() -> resetPasswordService.requestReset(testPerson.userName, null),
 | 
			
		||||
        TestHelper.assertThrows(() -> resetPasswordService.requestReset(testPerson1.userName, null),
 | 
			
		||||
                    IllegalArgumentException.class,
 | 
			
		||||
                    "Client name is mandatory.");
 | 
			
		||||
 | 
			
		||||
        // Request password reset
 | 
			
		||||
        TestHelper.assertThrows(() -> resetPasswordService.requestReset(testPerson.userName, "TestClient" + System.currentTimeMillis()),
 | 
			
		||||
        TestHelper.assertThrows(() -> resetPasswordService.requestReset(testPerson1.userName, "TestClient" + System.currentTimeMillis()),
 | 
			
		||||
                    ClientAppNotFoundException.class,
 | 
			
		||||
                    "Client is not found.");
 | 
			
		||||
        assertEquals("No email should have been sent.", 0, emailUtil.getSentCount());
 | 
			
		||||
@@ -260,23 +288,23 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
        assertEquals("No email should have been sent.", 0, emailUtil.getSentCount());
 | 
			
		||||
 | 
			
		||||
        // Disable the user
 | 
			
		||||
        enableUser(testPerson.userName, false);
 | 
			
		||||
        enableUser(testPerson1.userName, false);
 | 
			
		||||
 | 
			
		||||
        // Request password reset
 | 
			
		||||
        TestHelper.assertThrows(() -> resetPasswordService.requestReset(testPerson.userName, "share"),
 | 
			
		||||
        TestHelper.assertThrows(() -> resetPasswordService.requestReset(testPerson1.userName, "share"),
 | 
			
		||||
                    ResetPasswordWorkflowInvalidUserException.class,
 | 
			
		||||
                    "user is disabled.");
 | 
			
		||||
        assertEquals("No email should have been sent.", 0, emailUtil.getSentCount());
 | 
			
		||||
 | 
			
		||||
        // Enable the user
 | 
			
		||||
        enableUser(testPerson.userName, true);
 | 
			
		||||
        enableUser(testPerson1.userName, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testResetPasswordInvalid() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Request password reset
 | 
			
		||||
        resetPasswordService.requestReset(testPerson.userName, "share");
 | 
			
		||||
        resetPasswordService.requestReset(testPerson1.userName, "share");
 | 
			
		||||
        assertEquals("A reset password email should have been sent.", 1, emailUtil.getSentCount());
 | 
			
		||||
        // Check the email
 | 
			
		||||
        MimeMessage msg = emailUtil.getLastEmail();
 | 
			
		||||
@@ -307,7 +335,7 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
                    IllegalArgumentException.class,
 | 
			
		||||
                    "User id is mandatory.");
 | 
			
		||||
 | 
			
		||||
        passwordDetails.setUserId(testPerson.userName)
 | 
			
		||||
        passwordDetails.setUserId(testPerson1.userName)
 | 
			
		||||
                    .setPassword(null); // Password is not provided
 | 
			
		||||
        TestHelper.assertThrows(() -> resetPasswordService.initiateResetPassword(passwordDetails),
 | 
			
		||||
                    IllegalArgumentException.class,
 | 
			
		||||
@@ -363,7 +391,7 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
            // Set the duration for 1 second
 | 
			
		||||
            resetPasswordService.setTimerEnd("PT1S");
 | 
			
		||||
            // Request password reset
 | 
			
		||||
            resetPasswordService.requestReset(testPerson.userName, "share");
 | 
			
		||||
            resetPasswordService.requestReset(testPerson1.userName, "share");
 | 
			
		||||
            assertEquals("A reset password email should have been sent.", 1, emailUtil.getSentCount());
 | 
			
		||||
 | 
			
		||||
            // Check the reset password url.
 | 
			
		||||
@@ -377,7 +405,7 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
            emailUtil.reset();
 | 
			
		||||
            // Now that we have got the email, try to reset the password
 | 
			
		||||
            ResetPasswordDetails passwordDetails = new ResetPasswordDetails()
 | 
			
		||||
                        .setUserId(testPerson.userName)
 | 
			
		||||
                        .setUserId(testPerson1.userName)
 | 
			
		||||
                        .setPassword("newPassword")
 | 
			
		||||
                        .setWorkflowId(pair.getFirst())
 | 
			
		||||
                        .setWorkflowKey(pair.getSecond());
 | 
			
		||||
@@ -396,6 +424,110 @@ public class ResetPasswordServiceImplTest
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testResetPasswordForClientWorkspace() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        // Try the credential before change of password
 | 
			
		||||
        authenticateUser(testPerson2.userName, testPerson2.password);
 | 
			
		||||
 | 
			
		||||
        // Make sure to run as system
 | 
			
		||||
        AuthenticationUtil.clearCurrentSecurityContext();
 | 
			
		||||
        AuthenticationUtil.setRunAsUserSystem();
 | 
			
		||||
 | 
			
		||||
        // Request password reset
 | 
			
		||||
        resetPasswordService.requestReset(testPerson2.userName, "workspace");
 | 
			
		||||
        assertEquals("A reset password email should have been sent.", 1, emailUtil.getSentCount());
 | 
			
		||||
        // Check the email
 | 
			
		||||
        MimeMessage msg = emailUtil.getLastEmail();
 | 
			
		||||
        assertNotNull("There should be an email.", msg);
 | 
			
		||||
        assertEquals("Should've been only one email recipient.", 1, msg.getAllRecipients().length);
 | 
			
		||||
        // Check the recipient is the person who requested the reset password
 | 
			
		||||
        assertEquals(testPerson2.email, msg.getAllRecipients()[0].toString());
 | 
			
		||||
        //Check the sender is what we set as default
 | 
			
		||||
        assertEquals(DEFAULT_SENDER, msg.getFrom()[0].toString());
 | 
			
		||||
        // There should be a subject
 | 
			
		||||
        assertNotNull("There should be a subject.", msg.getSubject());
 | 
			
		||||
        // Check the default email subject - (check that we are sending the right email)
 | 
			
		||||
        String emailSubjectKey = getDeclaredField(SendResetPasswordEmailDelegate.class, "EMAIL_SUBJECT_KEY");
 | 
			
		||||
        assertNotNull(emailSubjectKey);
 | 
			
		||||
        assertEquals(msg.getSubject(), I18NUtil.getMessage(emailSubjectKey));
 | 
			
		||||
 | 
			
		||||
        // Check the reset password url.
 | 
			
		||||
        String resetPasswordUrl = (String) emailUtil.getLastEmailTemplateModelValue("reset_password_url");
 | 
			
		||||
        assertNotNull("Wrong email is sent.", resetPasswordUrl);
 | 
			
		||||
        // Get the workflow id and key
 | 
			
		||||
        Pair<String, String> pair = getWorkflowIdAndKeyFromUrl(resetPasswordUrl);
 | 
			
		||||
        assertNotNull("Workflow Id can't be null.", pair.getFirst());
 | 
			
		||||
        assertNotNull("Workflow Key can't be null.", pair.getSecond());
 | 
			
		||||
 | 
			
		||||
        emailUtil.reset();
 | 
			
		||||
        // Now that we have got the email, try to reset the password
 | 
			
		||||
        ResetPasswordDetails passwordDetails = new ResetPasswordDetails()
 | 
			
		||||
                    .setUserId(testPerson2.userName)
 | 
			
		||||
                    .setPassword("strongPassword")
 | 
			
		||||
                    .setWorkflowId(pair.getFirst())
 | 
			
		||||
                    .setWorkflowKey(pair.getSecond());
 | 
			
		||||
 | 
			
		||||
        resetPasswordService.initiateResetPassword(passwordDetails);
 | 
			
		||||
        assertEquals("A reset password confirmation email should have been sent.", 1, emailUtil.getSentCount());
 | 
			
		||||
        // Check the email
 | 
			
		||||
        msg = emailUtil.getLastEmail();
 | 
			
		||||
        assertNotNull("There should be an email.", msg);
 | 
			
		||||
        assertEquals("Should've been only one email recipient.", 1, msg.getAllRecipients().length);
 | 
			
		||||
        // Check the recipient is the person who requested the reset password
 | 
			
		||||
        assertEquals(testPerson2.email, msg.getAllRecipients()[0].toString());
 | 
			
		||||
        // Check the sender is what we set as default
 | 
			
		||||
        assertEquals(DEFAULT_SENDER, msg.getFrom()[0].toString());
 | 
			
		||||
        // There should be a subject
 | 
			
		||||
        assertNotNull("There should be a subject.", msg.getSubject());
 | 
			
		||||
        // Check the default email subject - (check that we are sending the right email)
 | 
			
		||||
        emailSubjectKey = getDeclaredField(SendResetPasswordConfirmationEmailDelegate.class, "EMAIL_SUBJECT_KEY");
 | 
			
		||||
        assertNotNull(emailSubjectKey);
 | 
			
		||||
        assertEquals(msg.getSubject(), I18NUtil.getMessage(emailSubjectKey));
 | 
			
		||||
 | 
			
		||||
        // Try the old credential
 | 
			
		||||
        TestHelper.assertThrows(() -> authenticateUser(testPerson2.userName, testPerson2.password),
 | 
			
		||||
                                AuthenticationException.class,
 | 
			
		||||
                                "As the user changed her password, the authentication should have failed.");
 | 
			
		||||
 | 
			
		||||
        // Try the new credential
 | 
			
		||||
        authenticateUser(testPerson2.userName, "strongPassword");
 | 
			
		||||
 | 
			
		||||
        // Make sure to run as system
 | 
			
		||||
        AuthenticationUtil.clearCurrentSecurityContext();
 | 
			
		||||
        AuthenticationUtil.setRunAsUserSystem();
 | 
			
		||||
        emailUtil.reset();
 | 
			
		||||
        // Try reset again with the used workflow
 | 
			
		||||
        TestHelper.assertThrows(() -> resetPasswordService.initiateResetPassword(passwordDetails),
 | 
			
		||||
                                InvalidResetPasswordWorkflowException.class,
 | 
			
		||||
                                "The workflow instance is not active (it has already been used).");
 | 
			
		||||
        assertEquals("No email should have been sent.", 0, emailUtil.getSentCount());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testCreateResetPasswordUrl()
 | 
			
		||||
    {
 | 
			
		||||
        /* Out of the box we have share and workspace registered as the clients.
 | 
			
		||||
         * See: alfresco/client/config/repo-clients-apps.properties file.
 | 
			
		||||
         */
 | 
			
		||||
 | 
			
		||||
        // Share client
 | 
			
		||||
        ClientApp share = clientAppConfig.getClient("share");
 | 
			
		||||
        String shareResetPasswordUrl =
 | 
			
		||||
                    resetPasswordService.createResetPasswordUrl(share, "workflow-id-123", "workflow-key-123");
 | 
			
		||||
        String shareExpectedUrl = UrlUtil.getShareUrl(sysAdminParams)
 | 
			
		||||
                    + "/page/reset-password?key=workflow-key-123&id=activiti$workflow-id-123";
 | 
			
		||||
        assertEquals(shareExpectedUrl, shareResetPasswordUrl);
 | 
			
		||||
 | 
			
		||||
        // Workspace client
 | 
			
		||||
        ClientApp workspace = clientAppConfig.getClient("workspace");
 | 
			
		||||
        String workspaceResetPasswordUrl =
 | 
			
		||||
                    resetPasswordService.createResetPasswordUrl(workspace, "workflow-id-456", "workflow-key-456");
 | 
			
		||||
        String workspaceExpectedUrl = UrlUtil.getAlfrescoBaseUrl(sysAdminParams)
 | 
			
		||||
                    + "/workspace/reset-password?key=workflow-key-456&id=activiti$workflow-id-456";
 | 
			
		||||
        assertEquals(workspaceExpectedUrl, workspaceResetPasswordUrl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isActive(String workflowId)
 | 
			
		||||
    {
 | 
			
		||||
        WorkflowInstance workflowInstance = workflowService.getWorkflowById(workflowId);
 | 
			
		||||
 
 | 
			
		||||
@@ -31,8 +31,8 @@ services:
 | 
			
		||||
    ports:
 | 
			
		||||
      - 8090:8090
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ${CI_WORKSPACE}/keystores/tengineAIO/tengineAIO.truststore:/tengineAIO.truststore
 | 
			
		||||
      - ${CI_WORKSPACE}/keystores/tengineAIO/tengineAIO.keystore:/tengineAIO.keystore
 | 
			
		||||
      - ${GITHUB_WORKSPACE}/keystores/tengineAIO/tengineAIO.truststore:/tengineAIO.truststore
 | 
			
		||||
      - ${GITHUB_WORKSPACE}/keystores/tengineAIO/tengineAIO.keystore:/tengineAIO.keystore
 | 
			
		||||
    environment:
 | 
			
		||||
      ACTIVEMQ_URL: "nio://activemq:61616"
 | 
			
		||||
      ACTIVEMQ_USER: "admin"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								scripts/ci/generate_keystores.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								scripts/ci/generate_keystores.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
#! /bin/bash
 | 
			
		||||
#! /bin/bash
 | 
			
		||||
 | 
			
		||||
# SETTINGS
 | 
			
		||||
# Alfresco Format: "classic" / "current" is supported only from 7.0
 | 
			
		||||
ALFRESCO_FORMAT=current
 | 
			
		||||
 | 
			
		||||
#Contains directory settings
 | 
			
		||||
source ${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/utils.sh
 | 
			
		||||
 | 
			
		||||
# Cleanup previous output of script
 | 
			
		||||
rm -rd $CA_DIR
 | 
			
		||||
rm -rd $KEYSTORES_DIR
 | 
			
		||||
rm -rd $CERTIFICATES_DIR
 | 
			
		||||
 | 
			
		||||
# SETTINGS
 | 
			
		||||
# Alfresco Format: "classic" / "current" is supported only from 7.0
 | 
			
		||||
ALFRESCO_FORMAT=current
 | 
			
		||||
 | 
			
		||||
#CA
 | 
			
		||||
${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/run_ca.sh -keysize 2048 -keystorepass password -certdname "/C=GB/ST=UK/L=Maidenhead/O=Alfresco Software Ltd./OU=Unknown/CN=Custom Alfresco CA" -servername localhost -validityduration 1
 | 
			
		||||
#Alfresco
 | 
			
		||||
${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/run_additional.sh -servicename alfresco -rootcapass password -keysize 2048 -keystoretype JCEKS -keystorepass password -truststoretype JCEKS -truststorepass password -certdname "/C=GB/ST=UK/L=Maidenhead/O=Alfresco Software Ltd./OU=Unknown/CN=Custom Alfresco Repository" -servername localhost -alfrescoformat $ALFRESCO_FORMAT
 | 
			
		||||
#Alfresco Metadata encryption
 | 
			
		||||
${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/run_encryption.sh -subfoldername alfresco -servicename encryption -encstorepass mp6yc0UD9e -encmetadatapass oKIWzVdEdA -alfrescoformat $ALFRESCO_FORMAT
 | 
			
		||||
#T-Engine AIO
 | 
			
		||||
${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/run_additional.sh -servicename tengineAIO -rootcapass password -keysize 2048 -keystoretype JCEKS -keystorepass password -truststoretype JCEKS -truststorepass password -certdname "/C=GB/ST=UK/L=Maidenhead/O=Alfresco Software Ltd./OU=Unknown/CN=T-Engine AIO" -servername localhost -alfrescoformat $ALFRESCO_FORMAT
 | 
			
		||||
		Reference in New Issue
	
	Block a user