Dave Ward 10da961097 Merged V3.3-BUG-FIX to HEAD
21981: Fixed ALF-2390: ContentService needs to return total/available space values where possible
      - Added ContentService getStoreTotalSpace and getStoreFreeSpace
      - Supported by underlying ContentStore methods
         - getSpaceUsed: Actual binary storage size (was getTotalSize, which is not deprecated)
         - getSpaceTotal: Total storage space on partition
         - getSpaceAvailable: Remaining storage space on partition
      - Added JMX methods
      - Note: getSpaceUsed is NOT exposed as a service method as it is too easy to abuse
   21982: Follow-up to Fixed ALF-2390: Patch uses new 'getSizeUsed' instead of deprecated 'getTotalSize'
   21983: Fixed ALF-766: Allow cache peer URLs to be set in ehcache-custom.xml
      - Was investigating settings required for client, so just added this in
      - By default, the 'cacheManagerPeerListenerFactory' uses no system properties, but
        a comment has been added on how to define hostName, port and socketTimeoutMillis.
      - Variables associated with that have been added to repository.properties and are
        exported to the Java system properties environment for EHCache to pick up.
   21984: More ALF-766: Allow cache peer URLs to be set in ehcache-custom.xml
      - Added alfresco.ehcache.rmi.remoteObjectPort (see ALF-765)
   22011: Fix for ALF-3005 - Blog and Discussion components not correctly checking length of modified HTML content
   22074: Fix for ALF-4447 - Failure when publishing a Post - some reverse proxies changing the response header to lower case
   22075: (RECORD ONLY) Merged HEAD to BRANCHES/DEV/V3.3-BUG-FIX:
      22073: Resolve ALF-4522: Accessing document via /alfresco/service/api/path doesn't work - "Company Home" token is duplicated
   22091: Merged PATCHES/V2.2.7 to V3.3-BUG-FIX (RECORD ONLY)
      19832: Merged BRANCHES/V3.1 to PATCHES/V2.2.7:
         17255: Fixed ETHREEOH-3180: Error appears when trying to search resources on Manage Task page
      19778: Incremented version label
      19769: ALF-2011: Backported dependencies
         ALF-2360: Merged V3.1 to PATCHES/V2.2.7
            17314: ETHREEOH-3158: Fix RepoServerMgmt to work with external authentication methods
               - AuthenticationService.getCurrentTicket / getNewTicket now call pre authentication check before issuing a new ticket, thus still allowing ticket enforcement when external authentication is in use.
         ALF-2361: Merged V3.2 to PATCHES/V2.2.7
            17456: Fix for: ETHREEOH-1465: It's impossible to get the login history for a given user (Audit)
               - all authentication routes (SSO and password) can now audit getting a new ticket for a session. SSO does not authenticate via the alfresco AuthenticationService API
               - you can now use auditing to track new sessions for users.
      19558: Created hotfix branch off TAGS/ENTERPRISE/V2.2.7
      19557: Tagged V2.2 build 533 Revision 19303 as TAGS/ENTERPRISE/V2.2.7
   22092: Merged PATCHES/V3.1.0 to V3.3-BUG-FIX (RECORD ONLY)
      21274: Merged PATCHES/V3.1.2 to PATCHES/V3.1.0
         21264: ALF-3889: JBPMDeployProcessServlet not accessible by default
            - Should only be enabled in development environment
      20242: ALF-2733: Recreate corrupt authorities produced by LDAP import
      20240: ALF-2733: Recreate corrupt authorities produced by LDAP import
      19032: Moved V3.1.0-ENTERPRISE-FINAL into ENTERPRISE/V3.1.0
      13776: Tagged V3.1 rev 13700 (build 142) as V3.1.0-ENTERPRISE-FINAL
   22093: Merged PATCHES/V3.1.1 to V3.3-BUG-FIX (RECORD ONLY)
      21570: ALF-3687: Build classpath fix
      21549: Merged PATCHES/V3.1.0 to PATCHES/V3.1.1
         20221: Merged PATCHES/V3.1.2 to PATCHES/V3.1.0
            20217: Merged PATCHES/V3.2.0 to PATCHES/V3.1.2
               19793: Merged HEAD to V3.2.0
                  19786: Refactor of previous test fix. I have pushed down the OOo-specific parts of the change from AbstractContentTransformerTest to OpenOfficeContentTransformerTest leaving an extension point in the base class should other transformations need to be excluded in the future.
                  19785: Fix for failing test OpenOfficeContentTransformerTest.testAllConversions.
                     Various OOo-related transformations are returned as available but fail on our test server with OOo on it.
                     Pending further work on these failings, I am disabling those transformations in test code whilst leaving them available in the product code. This is because in the wild a different OOo version may succeed with these transformations.
                     I had previously explicitly disabled 3 transformations in the product and I am moving that restriction from product to test code for the same reason.
                  19707: Return value from isTransformationBlocked was inverted. Fixed now.
                  19705: Refinement of previous check-in re OOo transformations.
                     I have pulled up the code that handles blocked transformations into a superclass so that the JodConverter-based transformer worker can inherit the same list of blocked transformations. To reiterate, blocked transformations are those that the OOo integration code believes should work but which are broken in practice. These are blocked by the transformers and will always be unavailable regardless of the OOo connection state.
                  19702: Fix for HEAD builds running on panda build server.
                     OOo was recently installed on panda which has activated various OOo-related transformations/extractions in the test code.
                     It appears that OOo does not support some transformations from Office 97 to Office 2007. Specifically doc to docx and xls to xlsx. These transformations have now been marked as unavailable.
      21548: Incremented version label
      21547: ALF-4121: Merged V3.2 to PATCHES/V3.1.1
         16827: ETHREEOH-2678 - Unfriendly system error occurs when trying to view Workflows information on content Details page if workflow was canceled
      21275: Merged PATCHES/V3.1.2 to PATCHES/V3.1.1
         21264: ALF-3889: JBPMDeployProcessServlet not accessible by default
            - Should only be enabled in development environment
      21235: Removed old source zip
      21233: Merged PATCHES/V3.1.2 to PATCHES/V3.1.1
         20890: ALF-3687: Apply LUCENE-1383 patch to Lucene 2.1.0 to reduce memory leaks from ThreadLocals
         20891: ALF-3687: Build classpath fix
      21227: Merged PATCHES/V3.2.1 to PATCHES/V3.1.1
         21207: Extra debug logging to track index triggering activity
      21225: Merged V3.2 to PATCHES/V3.1.1
         19598: Backported (merge not possible) HEAD rev 18790 for IndexInfo fixes
            - Hit problem where re-index threads were all waiting for merging, which had nothing to do
      21223: Merged V3.2 to PATCHES/V3.1.1
         16923: Lucene performance: avoid too many index deltas getting created by throttling transactions when the number of index entries gets above a configurable size
            - Stops performance degradation over time and out of memory errors under load
      21222: Merged V3.2 to PATCHES/V3.1.1
         16799: Fix for ETHREEOH-2843:  lucene.indexer.minMergeDocs , lucene.indexer.mergeFactor and lucene.indexer.maxMergeDocs are never read / used
            - removed unused properties and parameterised the remaining unexposed properties
      21220: Created hotfix branch off TAGS/ENTERPRISE/V3.1.1
      19033: Moved V3.1.1-ENTERPRISE-FINAL into ENTERPRISE/V3.1.1
      14897: Tagged V3.1 rev 14748 (build 229) as TAGS/V3.1.1-ENTERPRISE-FINAL
   22096: Merged PATCHES/V3.1.2 to V3.3-BUG-FIX (RECORD ONLY)
      22033: ALF-4504: Fix unit test classpath
      22031: ALF-4503: Removed spurious mergeinfo
      22030: ALF-4503: Merged V3.2 to PATCHES/V3.1.2
         19518: ALF-757: Corrected audit config resource URL so that it resolves inside Tomcat as well as JUnit!
      22028: Incremented version label
      22026: ALF-4504: Merged HEAD to PATCHES/V3.1.2
         19958: Updating pdfbox and fontbox libraries.
            The 3rd party libraries apache pdfbox and apache fontbox have been updated from version 0.8.0-incubating to version 1.1.0.

            Apache states that: [...] notable changes in this release include basic support for tagged PDF, various font handling improvements and better handling of CJK character sets.

            Source is available as before for pdfbox, but is no longer included for fontbox.
      22025: ALF-4503: Merged V3.2 to PATCHES/V3.1.2
         18000: Merged DEV/BELARUS/V3.2-2009_11_24 to V3.2
            17719: ETHREEOH-3393: AuditConfig file is read every time the audit method is called in HibernateAuditDAO
         19501: Merged DEV/BELARUS/V3.2-2010_02_24 to HEAD (with corrections)
            19243: ALF-757: Cannot start up on JBoss 5.1 due to audit configuration error
               - Removed getPath() method because it is incompatible with JBoss and other app servers where resources can't be resolved to a file
               - Now use Spring ResourceLoader instead of creating FileInputStream
               - getLastModified() still returned where the resource resolves to a file; otherwise the server startup time
      21699: Incremented version label
      21697: ALF-4275: Merged PATCHES/V3.2.0 to PATCHES/V3.1.2
         20349: Merged V3.3 to PATCHES/V3.2.0
            20346: ALF-2839: Node pre-loading generates needless resultset rows
               - Added missing Criteria.list() call
         20280: Fixed ALF-2839: Node pre-loading generates needless resultset rows
              - Split Criteria query to retrieve properties and aspects separately
      21696: ALF-4275: Merged V3.3-BUG-FIX to PATCHES/V3.1.2
         20231: Fixed ALF-2784: Degradation of performance between 3.1.1 and 3.2x (observed in JSF)
            - Handles warm caches more efficiently
            - Doesn't regress on ETWOTWO-949
            - Can be backported safely
      21694: ALF-4275: Merged PATCHES/V3.2.0 to PATCHES/V3.1.2
         20266: Test reproduction of ALF-2839 failure: Node pre-loading generates needless resultset rows
      17041: Merged V3.2 to V3.1
         17023: Fixed parentAssocCache bug when adding assocs against an empty cache   
      16987: Merge 2.2 to 3.1:
         13089: (record-only) Fix "Read-Write transaction" exception, when the user does not exist. ETWOTWO-1055.
         13091: (record-only) Fix for NFS server "Read-Write transaction started within read-only transaction" exception. ETWOTWO-1054.
         14190: (record-only) Fix for cut/paste a folder from Alfresco CIFS to local drive loses folder contents. ETWOTWO-1159.
         14191: (record-only) Additional fix for CIFS 'No more connections' error. ETWOTWO-556
         14199: (record-only) Fix for NFS problem with Solaris doing an Access check on the share level handle. ETWOTWO-1225.
         14210: (record-only) Added support for FTP EPRT and EPSV commands, on IPv4 only. ETWOTWO-325.
         14216: (record-only) Fixed FTP character encoding, ported UTF8 normalizer code from v3.x. ETWOTWO-1151.
         14229: (record-only) Remove unused import.
         14655: (record-only) Convert content I/O exceptions to file server exceptions during write and truncate. ETWOTWO-1241.
         14825: (record-only) Add support for the extended response to the CIFS NTCreateAndX call, back port of ETWOTWO-1232.
         15869: (record-only) Port of desktop action client side EXE fixes from v3.x. ETWOTWO-1374.
      16727: Fix for unable to connect via FTP via Firefox (when anonymous logons are not enabled). ETHREEOH-2012.
      16718: Fix for Alfresco and AVM spaces are empty when viewed by FTP and Alfresco is run as non-root. ETHREEOH-2652.
         Triggered when CIFS server uses default ports on Linux/Unix/Mac platforms and fails to start.
      16717: Fixed setAllowConsoleShutdown setting in standalone server can cause infinite loop. JLAN-38.
      16710: Added CIFS NT status code/text for the 'account locked' status, 0xC0000234. ETHREEOH-2897.
      16709: Fixed the FTP not logged on status return code, now uses reply code 530. JLAN-90.
      16666: Fix for CIFS cannot handle requests over 64K in JNI code, causes session disconnect, standalone server. JLAN-91.
      16559: Fix for ACL parsing in the standalone JLAN Server build. JLAN-89.
      16556: Fix for CIFS session leak and 100% CPU when connect/disconnecting quickly. ETHREEOH-2881. 
      16555: Fix for processing of NetBIOS packets over 64K in the older JNI code. Part of ETHREEOH-2882.
      16309: Merged V2.2 to V3.1 (for rev 16305)
         16304: Fix ETWOONE-335: Parallel Review does not store Priority and Due Date set at task creation
      16305: ETWOONE-335: Parallel Review does not store Priority and Due Date set at task creation
      16163: Added timstamp tracking via the file state cache, blend cached timestamps into file info/folder search results.
         Added support for . and .. pseudo entries in a wildcard folder search.
      16162: Add support for the . and .. pseudo entries in a folder search.
         Return EA size as zero in CIFS file information levels.
         Added more debug output to notify change handler.
      16160: Minor change to debug output
      15827: Fixed bug in delete node event processing.
      15780: Fix for MS Office document locking issue. ETHREEOH-2579.
      15628: Update svn:mergeinfo
      15627: Merge 3.2 to 3.1:
         15626: Fixed NetBIOS reports an invalid packet during session connection, and connection stalls for a while. JLAN-86.
      15572: Update svn:mergeinfo
      15571: Merge 3.2 to 3.1:
         15549: Check for null ClientInfo in the setCurrentUser() method and clear the auth context. Part of ETHREEOH-2538.
         15550: Fixed performance issue in the continue search code, add warn level output of folder search timing.
      15570: Merge 3.2 to 3.1:
         15548: CIFS server memory leak fixes (clear auth context, session close). ETHREEOH-2538
      15231: Fix for cut/paste file between folders on CIFS. ETHREEOH-2323.
         Added debug flags to the Alfresco filesystems, implemented in the ContentDiskDriver. Changed debug output to be single line. ENH-515.
      14930: Updated svn:mergeinfo
      14921: Merge HEAD to V3.1:
         14599: Fixes to file server ACL parsing, part of ETHREEOH-2177
      14916: Fixes for local domain lookup when WINS is configured. ETHREEOH-2263.
      14523: Add trailing 'A' to CIFS server name, removed by recent checkin.
      14484: Merged HEAD to v3.1:
         13943 Added FTP IPv6 configuration, via the <IPv6 state="enabled|disabled"/> tag. Added the ftp.ipv6 property. MOB-714.
      14483: Merged HEAD to v3.1:
         13942 Added FTP IPv6 support. MOB-714.
   22097: Merged PATCHES/V3.2.0 to V3.3-BUG-FIX (RECORD ONLY)
      21556: Incremented version label
      21555: ALF-4208: Merged PATCHES/V3.1.1 to PATCHES/V3.2.0
         21225: Merged V3.2 to PATCHES/V3.1.1
            19598: Backported (merge not possible) HEAD rev 18790 for IndexInfo fixes
               - Hit problem where re-index threads were all waiting for merging, which had nothing to do
      17875: “Tagged V3.2 build 304 Revision 17823 as TAGS/V3.2.0-ENTERPRISE-FINAL”
   22098: Merged PATCHES/V3.2.1 to V3.3-BUG-FIX (RECORD ONLY)
      17876: Fix CMIS repo and folder browser web scripts. This can now be used as sample stand-alone JSR-168 portlet.
      18309: ETHREEOH-4003: Impossibility to declare email as record
      18378: ETHREEOH-4034: Permission exception when creating non-electronic records ...
      18468: ETHREEOH-4105: Frozen Records Can Be Destroyed
      18470: Part of ETHREEOH-4089: StoreModelObjectPersister fails to load model object
         - Added toString() methods to aid with diagnostics
      18471: Fixed ETHREEOH-4089: StoreModelObjectPersister fails to load model object
         - If the XML fails to parse, then it is treated the same as a missing document.
         - Rather than having the system fail, the XML failures are reported (turn DEBUG
           on for full XML dump) and the document is treated the same as
           "store.hasDocument() = false"
         - Merges to HEAD will need merging to SpringFramework
      18494: ETHREEOH-4089: StoreModelObjectPersister fails to load model object
         - Handle document parsing exceptions as well
      19170: Fixed ALF-730: MLText is not fully carried during cut-paste or copy-paste
         - Fetching of properties for copy now uses 'mlAwareNodeService'
      19286: Fix for https://issues.alfresco.com/jira/browse/ALF-626 "Using 'null' as an authority argument in clearPermissions() cause a java.lang.NullPointerException"
      19406: Fix for ALF-649 - Web Service query() no longer returns metadata in 3.2
      19597: Gave PropertyBackedBeanExporter a shorter name for it's cluster region name
      19599: Fix ETHREEOH-2583: Make the index tracking "hole" retention period more configurable
         - Added property 'index.tracking.maxVoidRetentionTimeMinutes', which defaults to the 'maxTxnDurationMinutes'
         - Even more usefully, added 'index.tracking.minVoidCheckPeriodSeconds' that allows void checking to be less frequent
           defaulting to only doing it every 60s
         - Added explicit logging for voids: log4j.logger.org.alfresco.repo.node.index.IndexTransactionTracker.voids=DEBUG
      19654: Fix blog test
      19718: Merged HEAD to BRANCHES/V3.2:
         19678: Fix PostgreSQL handling for null Serializable values (ALF-1614)
            - Provides generic way of targeting BLOB behaviour for different dialects
            - Fixes ALF-2301 by the way
      19759: Fix for CIFS/CheckInOut.exe save of working copy breaks lock on original file. ALF-2028.
      19760: Fix for working copy checked out via CIFS is not accessible until FileStateReaper expires file state. ALF-962.
      20048: "Tagged V3.2 build 499 Revision 19935 as TAGS/ENTERPRISE.V3.2.1"
      21166: Merged V3.3-BUG-FIX to PATCHES/V3.2.1
         21165: Fixed ALF-3867: SQL format error when re-instating orphaned content URL
            - Parameter was not bounded with #
            - Added unit test to ensure SQL generated is correct
   22101: Resolve ALF-4522: Accessing document via /alfresco/service/api/path doesn't work - "Company Home" token is duplicated
   - /cmis... urls accept paths relative to CMIS root path (which is /Company Home)
   - /api/path... urls accept paths relative to root of store
   22103: Merge from HEAD to V3.3-BUG-FIX
      22099: Fix for ALF-3733.

      Note that this should also fix ALF-4465.
   22104: Merged V3.3 to V3.3-BUG-FIX
      21690: ALF-3991: Disable audit behaviour when applying the RenditionModel.ASPECT_RENDITIONED aspect
      21722: Merged PATCHES/V3.3.1 to V3.3
         21721: ALF-4039: Extend store name encoding to all non-ASCII characters, as otherwise these are incorrectly mangled by the Tomcat 5 virtualization server when they appear in a web app path
            - Also use -x instead of _x so that the full encoded user name can be included in a DNS name
            - WCM preview now working with variety of user sandboxes with exotic characters in the user name!
      21767: ALF-4234: CMIS Relationships (fix issue with source and target relationship type checking)
      21789: ALF-4333: Fix
         - Updated RepoPrimaryManifestProcessorImpl so it can handle deletions that are reported by either pre-delete noderef or archived noderef (previously only handled the latter).
         - Updated TransferManifestNodeFactory so that it handles the case where the status of the node to transfer is "deleted".
         - Updated UnitTestTransferManifestNodeFactory so that it handles the change to TransferManifestNodeFactory above.
         - Added new tests for deletion cases.
      21808: Fix for ALF-1908 - encoding for all arguments to all templates associated with WebScripts that can be exposed and executed via a URL.
         - this includes any freemarker templates in /components and /modules that can be executed via url and do not touch context objects that would cause them to be invalid when executed via that url, for example any component or template that touches page.* cannot be executed via a url in the browser, but those that do not *could* be exposed via a clever scripted URL and be manipulated to potentially contain XSS code.
      21837: ALF-4039: In WebProjectServiceImpl.getWebUserRef() do not trust results of Lucene search alone. Certain special characters are ignored or treated as equivalent. Do string comparison to ensure exact match.
      21843: Merged V3.3-BUG-FIX to V3.3
         21137: ALF-3841: Alfresco Explorer SSO Authentication Filters now accept ticket parameters too
            - Can be turned back off with ntlm.authentication.browser.ticketLogons=false or kerberos.authentication.browser.ticketLogons=false
            - Wiki updated
      21856: ALF-4391: Fix Share URL rewrite configuration
         - The URL rewrite configuration was not including the query string in the source URL meaning that it got lost in the rewritten URL (for some reason only on Websphere)
         - Solved this by using the urlrewrite.xml from ALF-260 and adding use-query-string="true"
         - Need to retest on Tomcat and WAS
      21873: Fix ALF-2974: Locate file action is absent for documents in I'm editing tab in Repository
      21907: ALF-4401: Web services not working on Websphere 7 (ever?)
         - Added in Sun SAAJ and JAXP reference implementations to shared library in order to make CXF work
         - Removed old Sun SAAJ libraries as these are embedded into JDK 1.6
      21911: ALF-4399: Broken Repository Document library in Share on Websphere.
         (Fixing fallout from argument encoding in r21808)
      21924: Further fixes for ALF-1908 - XSS argument encoding fixes in Forms runtime
      21987: ALF-4187: Fix bitrock installer config so that the RMI communication ports are randomly selected and do not clash with the RMI registry port
      21998: ALF-4323: Fix ability to use ECMA-357 ECMAScript for XML (E4X) expressions in Share on Weblogic (and Rules Management)
         - Use child first loading of org.apache.xmlbeans.* packages
         - Makes sense because it was originally donated by BEA
   22105: Merged PATCHES/V3.2.r to V3.3-BUG-FIX (RECORD ONLY)
      21082: Fixes for ALF-3777 and ALF-3778 - improvements to XSS attack mitigation.
      21375: Fix ALF-3951 - XSS attack mitigation for IE6 browser. Also minor wiki code formatting fixes & HTML entities appearing in wiki dashlet.
      21400: Merged BRANCHES/V3.2 to PATCHES/V3.2.r
         19144: Added PRE tag to whitelist of safe tags for HTML stripping in Share.
         19363: Fix for ALF-1952 - multi-pass HTML stripping
         19814: Fix for ALF-2322 - discussion topic containing non-ascii characters cannot be saved
      21759: Fix for IE6 XSS issue ALF-4307
   22106: Merged PATCHES/V3.3.1 to V3.3-BUG-FIX (RECORD ONLY)
      21838: Merged V3.3 to PATCHES/V3.3.1
         21837: ALF-4039: In WebProjectServiceImpl.getWebUserRef() do not trust results of Lucene search alone. Certain special characters are ignored or treated as equivalent. Do string comparison to ensure exact match.
      21941: ALF-4424: Merged V3.3-BUG-FIX to PATCHES/V3.3.1
         21659: Workaround for ALF-4230: use of flash technology to upload documents into a share site makes the use of (some) external authentication methods difficult (or impossible)
            - The Flash uploader can be disabled via share-config: DocumentLibary / file-upload / adobe-flash-enabled
      21944: Incremented version label
      22079: ALF-4458: Merged PATCHES/V3.2.1 to PATCHES/V3.3.1
         21606 ALF-4044: Introduced new policy.content.update.ignoreEmpty setting, that when true causes the repository to behave as it did before the fix to ALF-254. I.e. writing empty content will not trigger onContentPropertyUpdate policies or inbound content rules. This enables better compatibility with mac clients using CIFS or WebDAV; they actually create and close a file before appending its data.
      ALF-4458: Merged V3.3 to PATCHES/V3.3.1
         20855: Fix for ALF-3690. Unable to FTP files into Share site documentLibrary folder.
            This fix corrects what it essentially a typo. The dictionaryService was injected twice into the CreateNodeRuleTrigger bean, rather than the dictionaryService and the ruleService.
      22082: Incremented version label
   22107: Merged PATCHES/V3.3.1 to V3.3-BUG-FIX
      21943: ALF-4402: Use normalized repository user ID when doing filesystem quota tracking
      22080: ALF-4458: Fixed broken logic in CreateNodeRuleTrigger.onAddAspect()
         - hasAspect() check was inverted because onAddAspect() is called before aspect is actually added


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@22108 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2010-08-31 19:35:11 +00:00

1553 lines
63 KiB
Java

/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.wcm.webproject;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.mbeans.VirtServerRegistry;
import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.model.WCMAppModel;
import org.alfresco.repo.avm.AVMNodeConverter;
import org.alfresco.repo.avm.util.AVMUtil;
import org.alfresco.repo.domain.PropertyValue;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
import org.alfresco.service.cmr.avm.AVMNotFoundException;
import org.alfresco.service.cmr.avm.AVMService;
import org.alfresco.service.cmr.avm.locking.AVMLockingService;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.DNSNameMangler;
import org.alfresco.wcm.preview.PreviewURIServiceRegistry;
import org.alfresco.wcm.sandbox.SandboxConstants;
import org.alfresco.wcm.sandbox.SandboxFactory;
import org.alfresco.wcm.sandbox.SandboxInfo;
import org.alfresco.wcm.sandbox.SandboxFactory.UserRoleWrapper;
import org.alfresco.wcm.util.WCMUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.ParameterCheck;
/**
* Web Project Service Implementation
*
* @author janv
*/
public class WebProjectServiceImpl extends WCMUtil implements WebProjectService
{
/** Logger */
private static Log logger = LogFactory.getLog(WebProjectServiceImpl.class);
/** The DM store where web projects are kept */
public static final StoreRef WEBPROJECT_STORE = new StoreRef("workspace://SpacesStore");
/** The web projects root node reference */
private NodeRef webProjectsRootNodeRef; // note: WCM is not currently MT-enabled (so this is OK)
/** Services */
private NodeService nodeService;
private SearchService searchService;
private AVMService avmService;
private AuthorityService authorityService;
private PermissionService permissionService;
private PersonService personService;
private SandboxFactory sandboxFactory;
private VirtServerRegistry virtServerRegistry;
private PreviewURIServiceRegistry previewURIProviderRegistry;
private TransactionService transactionService;
private AVMLockingService avmLockingService;
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
public void setAvmService(AVMService avmService)
{
this.avmService = avmService;
}
public void setAuthorityService(AuthorityService authorityService)
{
this.authorityService = authorityService;
}
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
public void setSandboxFactory(SandboxFactory sandboxFactory)
{
this.sandboxFactory = sandboxFactory;
}
public void setVirtServerRegistry(VirtServerRegistry virtServerRegistry)
{
this.virtServerRegistry = virtServerRegistry;
}
public void setPreviewURIServiceRegistry(PreviewURIServiceRegistry previewURIProviderRegistry)
{
this.previewURIProviderRegistry = previewURIProviderRegistry;
}
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
public void setAvmLockingService(AVMLockingService avmLockingService)
{
this.avmLockingService = avmLockingService;
}
/* (non-Javadoc)
* @see org.alfresco.wcm.WebProjectService#createWebProject(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
public WebProjectInfo createWebProject(String dnsName, String name, String title, String description)
{
return createWebProject(dnsName, name, title, description, null, false, null);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.WebProjectService#createWebProject(java.lang.String, java.lang.String, java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef)
*/
public WebProjectInfo createWebProject(String dnsName, String name, String title, String description, NodeRef sourceNodeRef)
{
return createWebProject(dnsName, name, title, description, null, false, sourceNodeRef);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.WebProjectService#createWebProject(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean, org.alfresco.service.cmr.repository.NodeRef)
*/
public WebProjectInfo createWebProject(String dnsName, String name, String title, String description, String defaultWebApp, boolean useAsTemplate, NodeRef sourceNodeRef)
{
return createWebProject(new WebProjectInfoImpl(dnsName, name, title, description, defaultWebApp, useAsTemplate, sourceNodeRef, null));
}
public WebProjectInfo createWebProject(WebProjectInfo wpInfo)
{
String wpStoreId = wpInfo.getStoreId();
String name = wpInfo.getName();
String title = wpInfo.getTitle();
String description = wpInfo.getDescription();
boolean useAsTemplate = wpInfo.isTemplate();
NodeRef sourceNodeRef = wpInfo.getNodeRef();
String defaultWebApp = wpInfo.getDefaultWebApp();
String previewProviderName = wpInfo.getPreviewProviderName();
ParameterCheck.mandatoryString("wpStoreId", wpStoreId);
ParameterCheck.mandatoryString("name", name);
// Generate web project store id (an AVM store name)
wpStoreId = DNSNameMangler.MakeDNSName(wpStoreId);
if (wpStoreId.indexOf(WCMUtil.STORE_SEPARATOR) != -1)
{
throw new IllegalArgumentException("Unexpected store id '"+wpStoreId+"' - should not contain '"+WCMUtil.STORE_SEPARATOR+"'");
}
if (wpStoreId.indexOf(AVMUtil.AVM_STORE_SEPARATOR_CHAR) != -1)
{
throw new IllegalArgumentException("Unexpected store id '"+wpStoreId+"' - should not contain '"+AVMUtil.AVM_STORE_SEPARATOR_CHAR+"'");
}
if (previewProviderName == null)
{
// default preview URI service provider
previewProviderName = previewURIProviderRegistry.getDefaultProviderName();
}
else if (! previewURIProviderRegistry.getPreviewURIServiceProviders().keySet().contains(previewProviderName))
{
throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - unknown preview URI service provider ("+previewProviderName+")");
}
// default webapp name
defaultWebApp = (defaultWebApp != null && defaultWebApp.length() != 0) ? defaultWebApp : WCMUtil.DIR_ROOT;
// create the website space in the correct parent folder
Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
props.put(ContentModel.PROP_NAME, name);
props.put(WCMAppModel.PROP_ISSOURCE, useAsTemplate);
props.put(WCMAppModel.PROP_DEFAULTWEBAPP, defaultWebApp);
props.put(WCMAppModel.PROP_AVMSTORE, wpStoreId); // reference to the root AVM store
props.put(WCMAppModel.PROP_PREVIEW_PROVIDER, previewProviderName);
ChildAssociationRef childAssocRef = nodeService.createNode(
getWebProjectsRoot(),
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)),
WCMAppModel.TYPE_AVMWEBFOLDER,
props);
NodeRef wpNodeRef = childAssocRef.getChildRef();
// apply the uifacets aspect - icon, title and description props
Map<QName, Serializable> uiFacetsProps = new HashMap<QName, Serializable>(4);
uiFacetsProps.put(ApplicationModel.PROP_ICON, WCMUtil.SPACE_ICON_WEBSITE);
uiFacetsProps.put(ContentModel.PROP_TITLE, title);
uiFacetsProps.put(ContentModel.PROP_DESCRIPTION, description);
nodeService.addAspect(wpNodeRef, ApplicationModel.ASPECT_UIFACETS, uiFacetsProps);
// branch from source web project, if supplied
String branchStoreId = null;
if (sourceNodeRef != null)
{
branchStoreId = (String)nodeService.getProperty(sourceNodeRef, WCMAppModel.PROP_AVMSTORE);
}
// create the AVM staging store to represent the newly created location website
sandboxFactory.createStagingSandbox(wpStoreId, wpNodeRef, branchStoreId); // ignore return, fails if web project already exists
String stagingStore = WCMUtil.buildStagingStoreName(wpStoreId);
// create the default webapp folder under the hidden system folders
if (branchStoreId == null)
{
String stagingStoreRoot = WCMUtil.buildSandboxRootPath(stagingStore);
avmService.createDirectory(stagingStoreRoot, defaultWebApp);
avmService.addAspect(AVMNodeConverter.ExtendAVMPath(stagingStoreRoot, defaultWebApp), WCMAppModel.ASPECT_WEBAPP);
}
// now the sandbox is created set the permissions masks for the store
sandboxFactory.setStagingPermissionMasks(wpStoreId);
// set preview provider on staging store (used for preview lookup)
avmService.setStoreProperty(stagingStore,
SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER,
new PropertyValue(DataTypeDefinition.TEXT, previewProviderName));
// Snapshot the store with the empty webapp
avmService.createSnapshot(wpStoreId, null, null);
// break the permissions inheritance on the web project node so that only assigned users can access it
permissionService.setInheritParentPermissions(wpNodeRef, false);
// TODO: Currently auto-creates author sandbox for creator of web project (eg. an admin or a DM contributor to web projects root space)
// NOTE: JSF client does not yet allow explicit creation of author sandboxes
inviteWebUser(wpNodeRef, AuthenticationUtil.getFullyAuthenticatedUser(), WCMUtil.ROLE_CONTENT_MANAGER, true);
// Bind the post-commit transaction listener with data required for virtualization server notification
CreateWebProjectTransactionListener tl = new CreateWebProjectTransactionListener(wpStoreId);
AlfrescoTransactionSupport.bindListener(tl);
if (logger.isInfoEnabled())
{
logger.info("Created web project: " + wpNodeRef + " (store id: " + wpStoreId + ")");
}
// Return created web project info
return new WebProjectInfoImpl(wpStoreId, name, title, description, defaultWebApp, useAsTemplate, wpNodeRef, previewProviderName);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#createWebApp(java.lang.String, java.lang.String, java.lang.String)
*/
public void createWebApp(String wpStoreId, String webAppName, String webAppDescription)
{
createWebApp(getWebProjectNodeFromStore(wpStoreId), webAppName, webAppDescription);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#createWebApp(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String)
*/
public void createWebApp(NodeRef wpNodeRef, final String webAppName, final String webAppDescription)
{
WebProjectInfo wpInfo = getWebProject(wpNodeRef);
if (isContentManager(wpNodeRef))
{
// get AVM store name of the staging sandbox
final String stagingStoreId = wpInfo.getStagingStoreName();
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
final String parent = WCMUtil.buildSandboxRootPath(stagingStoreId);
avmService.createDirectory(parent, webAppName);
String path = AVMNodeConverter.ExtendAVMPath(parent, webAppName);
avmService.addAspect(path, ApplicationModel.ASPECT_UIFACETS);
avmService.addAspect(path, WCMAppModel.ASPECT_WEBAPP);
if (webAppDescription != null && webAppDescription.length() != 0)
{
avmService.setNodeProperty(path,
ContentModel.PROP_DESCRIPTION,
new PropertyValue(DataTypeDefinition.TEXT,
webAppDescription));
}
// Snapshot the store with the empty webapp
avmService.createSnapshot(stagingStoreId, null, null);
return null;
}
}, AuthenticationUtil.getSystemUserName());
if (logger.isInfoEnabled())
{
logger.info("Created web app: "+webAppName+" (store id: "+wpInfo.getStoreId()+")");
}
}
else
{
throw new AccessDeniedException("Only content managers may create new webapp '"+webAppName+"' (store id: "+wpInfo.getStoreId()+")");
}
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#listWebApps(java.lang.String)
*/
public List<String> listWebApps(String wpStoreId)
{
return listWebApps(getWebProjectNodeFromStore(wpStoreId));
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#listWebApps(org.alfresco.service.cmr.repository.NodeRef)
*/
public List<String> listWebApps(NodeRef wpNodeRef)
{
WebProjectInfo wpInfo = getWebProject(wpNodeRef);
String path = WCMUtil.buildSandboxRootPath(wpInfo.getStagingStoreName());
Map<String, AVMNodeDescriptor> folders = avmService.getDirectoryListing(-1, path);
List<String> webAppNames = new ArrayList<String>(folders.size());
webAppNames.addAll(folders.keySet());
return webAppNames;
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#deleteWebApp(java.lang.String, java.lang.String)
*/
public void deleteWebApp(String wpStoreId, String webAppName)
{
deleteWebApp(getWebProjectNodeFromStore(wpStoreId), webAppName);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#deleteWebApp(org.alfresco.service.cmr.repository.NodeRef, java.lang.String)
*/
public void deleteWebApp(NodeRef wpNodeRef, final String webAppName)
{
ParameterCheck.mandatoryString("webAppName", webAppName);
WebProjectInfo wpInfo = getWebProject(wpNodeRef);
if (webAppName.equals(wpInfo.getDefaultWebApp()))
{
throw new AlfrescoRuntimeException("Cannot delete default webapp '"+webAppName+"' (store id: "+wpInfo.getStoreId()+")");
}
else if (isContentManager(wpInfo.getNodeRef()))
{
// get AVM store name of the staging sandbox
final String wpStoreId = wpInfo.getStoreId();
WCMUtil.removeVServerWebapp(virtServerRegistry, WCMUtil.buildStoreWebappPath(wpStoreId, webAppName), true);
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
final String parent = WCMUtil.buildSandboxRootPath(wpStoreId);
avmService.removeNode(parent, webAppName);
// Snapshot the store with the webapp removed
avmService.createSnapshot(wpStoreId, null, null);
return null;
}
}, AuthenticationUtil.getSystemUserName());
if (logger.isInfoEnabled())
{
logger.info("Deleted web app: "+webAppName+" (store id: "+wpStoreId+")");
}
}
else
{
throw new AccessDeniedException("Only content managers may delete webapp '"+webAppName+"' (web project: "+wpNodeRef+")");
}
}
/**
* Get the node reference that is the web projects root
*
* @return NodeRef node reference
*/
public NodeRef getWebProjectsRoot()
{
if (this.webProjectsRootNodeRef == null)
{
// Get the root 'web projects' folder
ResultSet resultSet = null;
try
{
resultSet = this.searchService.query(WEBPROJECT_STORE, SearchService.LANGUAGE_LUCENE, "PATH:\""+getWebProjectsPath()+"\"");
if (resultSet.length() == 0)
{
// No root web projects folder exists
throw new AlfrescoRuntimeException("No root 'Web Projects' folder exists (is WCM enabled ?)");
}
else if (resultSet.length() != 1)
{
// More than one root web projects folder exits
throw new AlfrescoRuntimeException("More than one root 'Web Projects' folder exists");
}
this.webProjectsRootNodeRef = resultSet.getNodeRef(0);
}
finally
{
if (resultSet != null)
{
resultSet.close();
}
}
}
return this.webProjectsRootNodeRef;
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#listWebProjects()
*/
public List<WebProjectInfo> listWebProjects()
{
NodeRef wpRoot = getWebProjectsRoot();
Set<QName> nodeTypeQNames = new HashSet<QName>(1);
nodeTypeQNames.add(WCMAppModel.TYPE_AVMWEBFOLDER);
List<ChildAssociationRef> webProjects = nodeService.getChildAssocs(wpRoot, nodeTypeQNames);
List<WebProjectInfo> result = new ArrayList<WebProjectInfo>(webProjects.size());
for (ChildAssociationRef childAssocRefs : webProjects)
{
result.add(getWebProject(childAssocRefs.getChildRef()));
}
return result;
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#listWebProjects(java.lang.String)
*/
public List<WebProjectInfo> listWebProjects(String userName)
{
List<WebProjectInfo> webProjects = listWebProjects();
List<WebProjectInfo> result = new ArrayList<WebProjectInfo>(webProjects.size());
for (WebProjectInfo webProject : webProjects)
{
if (isWebUser(webProject.getNodeRef(), userName) == true)
{
result.add(webProject);
}
}
return result;
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#isWebProject(java.lang.String)
*/
public boolean isWebProject(String wpStoreId)
{
NodeRef wpNodeRef = getWebProjectNodeFromStore(wpStoreId);
if (wpNodeRef == null)
{
return false;
}
return isWebProject(wpNodeRef);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#isWebProject(org.alfresco.service.cmr.repository.NodeRef)
*/
public boolean isWebProject(NodeRef wpNodeRef)
{
if (wpNodeRef == null)
{
return false;
}
try
{
return (WCMAppModel.TYPE_AVMWEBFOLDER.equals(nodeService.getType(wpNodeRef)));
}
catch (InvalidNodeRefException e)
{
return false;
}
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#getWebProject(java.lang.String)
*/
public WebProjectInfo getWebProject(String wpStoreId)
{
WebProjectInfo result = null;
// Get the web project node
NodeRef wpNodeRef = getWebProjectNodeFromStore(wpStoreId);
if (wpNodeRef != null)
{
// Create the web project info
result = getWebProject(wpNodeRef);
}
// Return the web project info
return result;
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#getPreviewProvider(java.lang.String)
*/
public String getPreviewProvider(String wpStoreId)
{
ParameterCheck.mandatoryString("wpStoreId", wpStoreId);
String previewProviderName = null;
try
{
String stagingStoreId = WCMUtil.buildStagingStoreName(wpStoreId);
PropertyValue pValue = avmService.getStoreProperty(stagingStoreId, SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER);
if (pValue != null)
{
previewProviderName = (String)pValue.getValue(DataTypeDefinition.TEXT);
}
}
catch (AVMNotFoundException nfe)
{
logger.warn(wpStoreId + " is not a web project: " + nfe);
}
if (previewProviderName == null)
{
previewProviderName = previewURIProviderRegistry.getDefaultProviderName();
}
return previewProviderName;
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#getWebProject(org.alfresco.service.cmr.repository.NodeRef)
*/
public WebProjectInfo getWebProject(NodeRef wpNodeRef)
{
if (! isWebProject(wpNodeRef))
{
throw new IllegalArgumentException(wpNodeRef + " is not a web project");
}
// Get the properties
Map<QName, Serializable> properties = this.nodeService.getProperties(wpNodeRef);
String name = (String)properties.get(ContentModel.PROP_NAME);
String title = (String)properties.get(ContentModel.PROP_TITLE);
String description = (String)properties.get(ContentModel.PROP_DESCRIPTION);
String wpStoreId = (String)properties.get(WCMAppModel.PROP_AVMSTORE);
String defaultWebApp = (String)properties.get(WCMAppModel.PROP_DEFAULTWEBAPP);
Boolean useAsTemplate = (Boolean)properties.get(WCMAppModel.PROP_ISSOURCE);
String previewProvider = (String)properties.get(WCMAppModel.PROP_PREVIEW_PROVIDER);
// Create and return the web project info
WebProjectInfo wpInfo = new WebProjectInfoImpl(wpStoreId, name, title, description, defaultWebApp, useAsTemplate, wpNodeRef, previewProvider);
return wpInfo;
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#updateWebProject(org.alfresco.wcm.webproject.WebProjectInfo)
*/
public void updateWebProject(WebProjectInfo wpInfo)
{
NodeRef wpNodeRef = getWebProjectNodeFromStore(wpInfo.getStoreId());
if (wpNodeRef == null)
{
throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - it does not exist.");
}
if (! listWebApps(wpNodeRef).contains(wpInfo.getDefaultWebApp()))
{
throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - unknown default web app ("+wpInfo.getDefaultWebApp()+")");
}
if (wpInfo.getPreviewProviderName() == null)
{
wpInfo.setPreviewProviderName(previewURIProviderRegistry.getDefaultProviderName());
}
else if (! previewURIProviderRegistry.getPreviewURIServiceProviders().keySet().contains(wpInfo.getPreviewProviderName()))
{
throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - unknown preview URI service provider ("+wpInfo.getPreviewProviderName()+")");
}
// Note: the site preset and short name can not be updated
// Update the properties of the site - note: cannot change storeId or wpNodeRef
Map<QName, Serializable> properties = this.nodeService.getProperties(wpNodeRef);
properties.put(ContentModel.PROP_NAME, wpInfo.getName());
properties.put(ContentModel.PROP_TITLE, wpInfo.getTitle());
properties.put(ContentModel.PROP_DESCRIPTION, wpInfo.getDescription());
properties.put(WCMAppModel.PROP_DEFAULTWEBAPP, wpInfo.getDefaultWebApp());
properties.put(WCMAppModel.PROP_ISSOURCE, wpInfo.isTemplate());
properties.put(WCMAppModel.PROP_PREVIEW_PROVIDER, wpInfo.getPreviewProviderName());
this.nodeService.setProperties(wpNodeRef, properties);
// set preview provider on staging store (used for preview lookup)
String stagingStore = WCMUtil.buildStagingStoreName(wpInfo.getStoreId());
avmService.deleteStoreProperty(stagingStore, SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER);
avmService.setStoreProperty(stagingStore,
SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER,
new PropertyValue(DataTypeDefinition.TEXT, wpInfo.getPreviewProviderName()));
if (logger.isDebugEnabled())
{
logger.debug("Updated web project: " + wpNodeRef + " (store id: " + wpInfo.getStoreId() + ")");
}
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#deleteWebProject(java.lang.String)
*/
public void deleteWebProject(String wpStoreId)
{
NodeRef wpNodeRef = getWebProjectNodeFromStore(wpStoreId);
if (wpNodeRef != null)
{
deleteWebProject(wpNodeRef);
}
else
{
// by definition, the current user is not a content manager since the web project does not exist (or is not visible)
throw new AccessDeniedException("Only content managers may delete a web project");
}
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#deleteWebProject(org.alfresco.service.cmr.repository.NodeRef)
*/
public void deleteWebProject(final NodeRef wpNodeRef)
{
if (! isContentManager(wpNodeRef))
{
// the current user is not a content manager since the web project does not exist (or is not visible)
throw new AccessDeniedException("Only content managers may delete web project");
}
// delete all attached website sandboxes in reverse order to the layering
final String wpStoreId = (String)nodeService.getProperty(wpNodeRef, WCMAppModel.PROP_AVMSTORE);
if (wpStoreId != null)
{
// Notify virtualization server about removing this website
//
// Implementation note:
//
// Because the removal of virtual webapps in the virtualization
// server is recursive, it only needs to be given the name of
// the main staging store.
//
// This notification must occur *prior* to purging content
// within the AVM because the virtualization server must list
// the avm_webapps dir in each store to discover which
// virtual webapps must be unloaded. The virtualization
// server traverses the sandbox's stores in most-to-least
// dependent order, so clients don't have to worry about
// accessing a preview layer whose main layer has been torn
// out from under it.
//
// It does not matter what webapp name we give here, so "/ROOT"
// is as sensible as anything else. It's all going away.
final String sandbox = WCMUtil.buildStagingStoreName(wpStoreId);
String path = WCMUtil.buildStoreWebappPath(sandbox, "/ROOT");
WCMUtil.removeAllVServerWebapps(virtServerRegistry, path, true);
try
{
RetryingTransactionCallback<Object> deleteWebProjectWork = new RetryingTransactionCallback<Object>()
{
public Object execute() throws Throwable
{
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
List<SandboxInfo> sbInfos = sandboxFactory.listAllSandboxes(wpStoreId, true, true);
for (SandboxInfo sbInfo : sbInfos)
{
String sbStoreId = sbInfo.getSandboxId();
if (WCMUtil.isLocalhostDeployedStore(wpStoreId, sbStoreId))
{
if (getWebProject(WCMUtil.getWebProjectStoreId(sbStoreId)) != null)
{
continue;
}
}
// delete sandbox (and associated preview sandbox, if it exists)
sandboxFactory.deleteSandbox(sbInfo, false);
}
// delete all web project locks in one go (ie. all those currently held against staging store)
avmLockingService.removeLocks(wpStoreId);
StoreRef archiveStoreRef = nodeService.getStoreArchiveNode(wpNodeRef.getStoreRef()).getStoreRef();
// delete the web project node itself
nodeService.deleteNode(wpNodeRef);
nodeService.deleteNode(new NodeRef(archiveStoreRef, wpNodeRef.getId()));
sandboxFactory.removeGroupsForStore(sandbox);
return null;
}
}, AuthenticationUtil.getSystemUserName());
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(deleteWebProjectWork);
if (logger.isInfoEnabled())
{
logger.info("Deleted web project: " + wpNodeRef + " (store id: " + wpStoreId + ")");
}
}
catch (Throwable err)
{
throw new AlfrescoRuntimeException("Failed to delete web project: ", err);
}
}
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(java.lang.String)
*/
public boolean isContentManager(String storeName)
{
return isContentManager(storeName, AuthenticationUtil.getFullyAuthenticatedUser());
}
/* (non-Javadoc)
* @see org.alfresco.wcm.WebProjectService#isContentManager(java.lang.String, java.lang.String)
*/
public boolean isContentManager(String wpStoreId, String userName)
{
return isContentManager(getWebProjectNodeFromStore(wpStoreId), userName);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(org.alfresco.service.cmr.repository.NodeRef)
*/
public boolean isContentManager(NodeRef wpNodeRef)
{
return isContentManager(wpNodeRef, AuthenticationUtil.getFullyAuthenticatedUser());
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(org.alfresco.service.cmr.repository.NodeRef, java.lang.String)
*/
public boolean isContentManager(NodeRef wpNodeRef, String userName)
{
String userRole = getWebUserRole(wpNodeRef, userName);
return WCMUtil.ROLE_CONTENT_MANAGER.equals(userRole);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(java.lang.String)
*/
public boolean isWebUser(String wpStoreId)
{
return isWebUser(getWebProjectNodeFromStore(wpStoreId));
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(org.alfresco.service.cmr.repository.NodeRef)
*/
public boolean isWebUser(NodeRef wpNodeRef)
{
// note: admin is an implied web user (content manager) although will not appear in listWebUsers unless explicitly invited
return (permissionService.hasPermission(wpNodeRef, PermissionService.READ) == AccessStatus.ALLOWED);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(java.lang.String, java.lang.String)
*/
public boolean isWebUser(String wpStoreId, String username)
{
return isWebUser(getWebProjectNodeFromStore(wpStoreId), username);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String)
*/
public boolean isWebUser(final NodeRef wpNodeRef, String userName)
{
return AuthenticationUtil.runAs(new RunAsWork<Boolean>()
{
public Boolean doWork() throws Exception
{
return isWebUser(wpNodeRef);
}
}, userName);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#getWebUserCount(org.alfresco.service.cmr.repository.NodeRef)
*/
public int getWebUserCount(NodeRef wpNodeRef)
{
return WCMUtil.listWebUsers(nodeService, wpNodeRef).size();
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#listWebUsers(java.lang.String)
*/
public Map<String, String> listWebUsers(String wpStoreId)
{
return listWebUsers(getWebProjectNodeFromStore(wpStoreId));
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#listWebUsers(org.alfresco.service.cmr.repository.NodeRef)
*/
public Map<String, String> listWebUsers(NodeRef wpNodeRef)
{
// special case: allow System - eg. to allow user to create their own sandbox on-demand (createAuthorSandbox)
if (isContentManager(wpNodeRef)
|| (AuthenticationUtil.getRunAsUser().equals(AuthenticationUtil.getSystemUserName())
|| (permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED)))
{
return WCMUtil.listWebUsers(nodeService, wpNodeRef);
}
else
{
throw new AccessDeniedException("Only content managers may list users in a web project");
}
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#getWebUserRole(java.lang.String, java.lang.String)
*/
public String getWebUserRole(String wpStoreId, String userName)
{
return getWebUserRole(getWebProjectNodeFromStore(wpStoreId), userName);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#getWebUserRole(org.alfresco.service.cmr.repository.NodeRef, java.lang.String)
*/
public String getWebUserRole(NodeRef wpNodeRef, String userName)
{
ParameterCheck.mandatoryString("userName", userName);
String userRole = null;
if (! isWebProject(wpNodeRef))
{
logger.warn(wpNodeRef + " is not a web project");
return null;
}
if (authorityService.isAdminAuthority(userName))
{
// fake the Content Manager role for an admin user
userRole = WCMUtil.ROLE_CONTENT_MANAGER;
}
else
{
userRole = getWebUserRoleImpl(wpNodeRef, userName);
}
return userRole;
}
private String getWebUserRoleImpl(NodeRef wpNodeRef, String userName)
{
NodeRef userRef = getWebUserRef(wpNodeRef, userName);
String userRole = null;
if (userRef != null)
{
userRole = (String)nodeService.getProperty(userRef, WCMAppModel.PROP_WEBUSERROLE);
}
return userRole;
}
private NodeRef getWebUserRef(NodeRef wpNodeRef, String userName)
{
StringBuilder query = new StringBuilder(128);
query.append("+PARENT:\"").append(wpNodeRef).append("\" ");
query.append("+TYPE:\"").append(WCMAppModel.TYPE_WEBUSER).append("\" ");
query.append("+@").append(NamespaceService.WCMAPP_MODEL_PREFIX).append("\\:username:\"");
query.append(userName);
query.append("\"");
ResultSet resultSet = null;
List<NodeRef> nodes = null;
try
{
resultSet = searchService.query(
WEBPROJECT_STORE,
SearchService.LANGUAGE_LUCENE,
query.toString());
nodes = resultSet.getNodeRefs();
}
finally
{
if (resultSet != null)
{
resultSet.close();
}
}
// Lucene indexing may strip certain international characters or treat them as equivalent so we do string
// comparisons on the results to ensure an exact match
Iterator<NodeRef> i = nodes.iterator();
while (i.hasNext())
{
if (!nodeService.getProperty(i.next(), WCMAppModel.PROP_WEBUSERNAME).equals(userName))
{
i.remove();
}
}
if (nodes.size() == 1)
{
return nodes.get(0);
}
else if (nodes.size() == 0)
{
if (logger.isTraceEnabled())
{
logger.trace("getWebUserRef: web user ("+userName+") not found in web project: "+wpNodeRef);
}
}
else
{
logger.error("getWebUserRef: more than one web user ("+userName+") found in web project: "+wpNodeRef);
}
return null;
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#findWebProjectNodeFromPath(java.lang.String)
*/
public NodeRef getWebProjectNodeFromPath(String absoluteAVMPath)
{
return getWebProjectNodeFromStore(WCMUtil.getWebProjectStoreIdFromPath(absoluteAVMPath));
}
/*(non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#getWebProjectNodeFromStore(java.lang.String)
*/
public NodeRef getWebProjectNodeFromStore(String wpStoreId)
{
ParameterCheck.mandatoryString("wpStoreId", wpStoreId);
return WCMUtil.getWebProjectNodeFromWebProjectStore(avmService, wpStoreId);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUsersGroups(java.lang.String, java.util.Map)
*/
public void inviteWebUsersGroups(String wpStoreId, Map<String, String> userGroupRoles)
{
inviteWebUsersGroups(getWebProjectNodeFromStore(wpStoreId), userGroupRoles, false);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUsersGroups(java.lang.String, java.util.Map, boolean)
*/
public void inviteWebUsersGroups(String wpStoreId, Map<String, String> userGroupRoles, boolean autoCreateAuthorSandbox)
{
inviteWebUsersGroups(getWebProjectNodeFromStore(wpStoreId), userGroupRoles, autoCreateAuthorSandbox);
}
public void inviteWebUsersGroups(NodeRef wpNodeRef, Map<String, String> userGroupRoles, boolean autoCreateAuthorSandbox)
{
if (! (isContentManager(wpNodeRef) ||
permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED))
{
throw new AccessDeniedException("Only content managers may invite web users");
}
WebProjectInfo wpInfo = getWebProject(wpNodeRef);
String wpStoreId = wpInfo.getStoreId();
// build a list of managers who will have full permissions on ALL staging areas
List<String> managers = new ArrayList<String>(4);
Map<String, NodeRef> webSiteUsers = new HashMap<String, NodeRef>(8);
List<String> managersToRemove = new LinkedList<String>();
List<UserRoleWrapper> usersToUpdate = new LinkedList<UserRoleWrapper>();
// retrieve the list of managers from the existing users
for (Map.Entry<String, String> userRole : userGroupRoles.entrySet())
{
String authority = userRole.getKey();
String role = userRole.getValue();
for (String userAuth : findNestedUserAuthorities(authority))
{
if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role))
{
managers.add(userAuth);
}
}
}
List<ChildAssociationRef> userInfoRefs = nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL);
for (ChildAssociationRef ref : userInfoRefs)
{
NodeRef userInfoRef = ref.getChildRef();
String username = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME);
String userrole = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERROLE);
if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false)
{
managers.add(username);
}
// add each existing user to the map which will be rechecked for update changed user permissions
webSiteUsers.put(username, userInfoRef);
}
List<SandboxInfo> sandboxInfoList = new LinkedList<SandboxInfo>();
int invitedCount = 0;
boolean managersUpdateRequired = false;
for (Map.Entry<String, String> userRole : userGroupRoles.entrySet())
{
String authority = userRole.getKey();
String role = userRole.getValue();
for (String userAuth : findNestedUserAuthorities(authority))
{
if (webSiteUsers.keySet().contains(userAuth) == false)
{
if (autoCreateAuthorSandbox)
{
// create a sandbox for the user with permissions based on role
SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, managers, userAuth, role);
sandboxInfoList.add(sbInfo);
}
sandboxFactory.addStagingAreaUser(wpStoreId, userAuth, role);
// create an app:webuser instance for each authority and assoc to the web project node
createWebUser(wpNodeRef, userAuth, role);
// if this new user is a manager, we'll need to update the manager permissions applied
// to each existing user sandbox - to ensure that new managers have access to them
managersUpdateRequired |= (WCMUtil.ROLE_CONTENT_MANAGER.equals(role));
invitedCount++;
}
else
{
// TODO - split out into separate 'change role'
// if user role have been changed then update required properties etc.
NodeRef userRef = webSiteUsers.get(userAuth);
String oldUserRole = (String)nodeService.getProperty(userRef, WCMAppModel.PROP_WEBUSERROLE);
if (!role.equals(oldUserRole))
{
// change in role
Map<QName, Serializable> props = nodeService.getProperties(userRef);
props.put(WCMAppModel.PROP_WEBUSERNAME, userAuth);
props.put(WCMAppModel.PROP_WEBUSERROLE, role);
nodeService.setProperties(userRef, props);
if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role))
{
managersUpdateRequired = true;
}
else if (WCMUtil.ROLE_CONTENT_MANAGER.equals(oldUserRole))
{
managersToRemove.add(userAuth);
}
usersToUpdate.add(sandboxFactory.new UserRoleWrapper(userAuth, oldUserRole, role));
if (logger.isDebugEnabled())
{
logger.debug(userAuth +"'s role has been changed from '" + oldUserRole +
"' to '" + role + "'");
}
}
}
}
}
// Bind the post-commit transaction listener with data required for virtualization server notification
CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, listWebApps(wpNodeRef));
AlfrescoTransactionSupport.bindListener(tl);
if (managersUpdateRequired == true)
{
sandboxFactory.updateSandboxManagers(wpStoreId, managers);
}
// TODO - split out into separate 'change role'
// remove ex-managers from sandboxes
if (managersToRemove.size() != 0)
{
sandboxFactory.removeSandboxManagers(wpStoreId, managersToRemove);
}
// get permissions and roles for a web project folder type
Set<String> perms = permissionService.getSettablePermissions(WCMAppModel.TYPE_AVMWEBFOLDER);
// set permissions for each user
for (Map.Entry<String, String> userRole : userGroupRoles.entrySet())
{
String authority = userRole.getKey();
String role = userRole.getValue();
for (String permission : perms)
{
if (role.equals(permission))
{
permissionService.setPermission(wpNodeRef,
authority,
permission,
true);
break;
}
}
}
// TODO - split out into separate 'change role'
// update user's roles
if (usersToUpdate.size() != 0)
{
sandboxFactory.updateSandboxRoles(wpStoreId, usersToUpdate, perms);
}
if (logger.isInfoEnabled())
{
logger.info("Invited "+invitedCount+" web users (store id: "+wpStoreId+")");
}
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(java.lang.String, java.lang.String, java.lang.String)
*/
public void inviteWebUser(String wpStoreId, String userAuth, String role)
{
inviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, role, false);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(java.lang.String, java.lang.String, java.lang.String, boolean)
*/
public void inviteWebUser(String wpStoreId, String userAuth, String role, boolean autoCreateAuthorSandbox)
{
inviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, role, autoCreateAuthorSandbox);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, boolean)
*/
public void inviteWebUser(NodeRef wpNodeRef, String userAuth, String role, boolean autoCreateAuthorSandbox)
{
if (! (isContentManager(wpNodeRef) ||
permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED))
{
throw new AccessDeniedException("Only content managers may invite web user");
}
WebProjectInfo wpInfo = getWebProject(wpNodeRef);
final String wpStoreId = wpInfo.getStoreId();
// build a list of managers who will have full permissions on ALL staging areas
List<String> managers = new ArrayList<String>(4);
// retrieve the list of managers from the existing users
Map<String, String> existingUserRoles = listWebUsers(wpNodeRef);
for (Map.Entry<String, String> userRole : existingUserRoles.entrySet())
{
String username = userRole.getKey();
String userrole = userRole.getValue();
if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false)
{
managers.add(username);
}
}
// get permissions and roles for a web project folder type
Set<String> perms = permissionService.getSettablePermissions(WCMAppModel.TYPE_AVMWEBFOLDER);
NodeRef userRef = getWebUserRef(wpNodeRef, userAuth);
if (userRef != null)
{
// TODO - split out into separate 'change role'
// if user role has been changed then update required properties etc.
String oldUserRole = (String)nodeService.getProperty(userRef, WCMAppModel.PROP_WEBUSERROLE);
if (!role.equals(oldUserRole))
{
// change in role
Map<QName, Serializable> props = nodeService.getProperties(userRef);
props.put(WCMAppModel.PROP_WEBUSERNAME, userAuth);
props.put(WCMAppModel.PROP_WEBUSERROLE, role);
nodeService.setProperties(userRef, props);
if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role))
{
managers.add(userAuth);
sandboxFactory.updateSandboxManagers(wpStoreId, managers);
}
else if (WCMUtil.ROLE_CONTENT_MANAGER.equals(oldUserRole))
{
List<String> managersToRemove = new LinkedList<String>();
managersToRemove.add(userAuth);
sandboxFactory.removeSandboxManagers(wpStoreId, managersToRemove);
}
List<UserRoleWrapper> usersToUpdate = new LinkedList<UserRoleWrapper>();
usersToUpdate.add(sandboxFactory.new UserRoleWrapper(userAuth, oldUserRole, role));
sandboxFactory.updateSandboxRoles(wpStoreId, usersToUpdate, perms);
if (logger.isInfoEnabled())
{
logger.info("Web user "+userAuth +"'s role has been changed from '" + oldUserRole + "' to '" + role + "' (store id: "+wpStoreId+")");
}
}
}
else
{
if (autoCreateAuthorSandbox)
{
// create a sandbox for the user with permissions based on role
SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, managers, userAuth, role);
List<SandboxInfo> sandboxInfoList = new LinkedList<SandboxInfo>();
sandboxInfoList.add(sbInfo);
// Bind the post-commit transaction listener with data required for virtualization server notification
CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, listWebApps(wpNodeRef));
AlfrescoTransactionSupport.bindListener(tl);
}
// if this new user is a manager, we'll need to update the manager permissions applied
// to each existing user sandbox - to ensure that new user has access to them
if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role))
{
managers.add(userAuth);
sandboxFactory.updateSandboxManagers(wpStoreId, managers);
}
sandboxFactory.addStagingAreaUser(wpStoreId, userAuth, role);
// create an app:webuser instance for the user and assoc to the web project node
createWebUser(wpNodeRef, userAuth, role);
// set permissions for the user
for (String permission : perms)
{
if (role.equals(permission))
{
permissionService.setPermission(wpNodeRef,
userAuth,
permission,
true);
break;
}
}
if (logger.isInfoEnabled())
{
logger.info("Invited web user: "+userAuth+" (store id: "+wpStoreId+")");
}
}
}
private void createWebUser(NodeRef wpNodeRef, String userName, String userRole)
{
// create an app:webuser instance for the user and assoc to the web project node
Map<QName, Serializable> props = new HashMap<QName, Serializable>(2, 1.0f);
props.put(WCMAppModel.PROP_WEBUSERNAME, userName);
props.put(WCMAppModel.PROP_WEBUSERROLE, userRole);
nodeService.createNode(wpNodeRef,
WCMAppModel.ASSOC_WEBUSER,
WCMAppModel.ASSOC_WEBUSER,
WCMAppModel.TYPE_WEBUSER,
props);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(java.lang.String, java.lang.String)
*/
public void uninviteWebUser(String wpStoreId, String userAuth)
{
uninviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, false);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(java.lang.String, java.lang.String, boolean)
*/
public void uninviteWebUser(String wpStoreId, String userAuth, boolean autoDeleteAuthorSandbox)
{
uninviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, autoDeleteAuthorSandbox);
}
/* (non-Javadoc)
* @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, boolean)
*/
public void uninviteWebUser(NodeRef wpNodeRef, String userAuth, boolean autoDeleteAuthorSandbox)
{
if (! isContentManager(wpNodeRef))
{
throw new AccessDeniedException("Only content managers may uninvite web user '"+userAuth+"' from web project: "+wpNodeRef);
}
ParameterCheck.mandatory("wpNodeRef", wpNodeRef);
ParameterCheck.mandatoryString("userAuth", userAuth);
WebProjectInfo wpInfo = getWebProject(wpNodeRef);
String wpStoreId = wpInfo.getStoreId();
String userMainStore = WCMUtil.buildUserMainStoreName(wpStoreId, userAuth);
if (autoDeleteAuthorSandbox)
{
sandboxFactory.deleteSandbox(userMainStore);
}
// remove the store reference from the website folder meta-data (see also WCMUtil.listWebUsers)
List<ChildAssociationRef> userInfoRefs = nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL);
// retrieve the list of managers from the existing users
List<String> managers = new ArrayList<String>(4);
for (ChildAssociationRef ref : userInfoRefs)
{
NodeRef userInfoRef = ref.getChildRef();
String username = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME);
String userrole = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERROLE);
if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false)
{
managers.add(username);
}
}
for (ChildAssociationRef ref : userInfoRefs)
{
NodeRef userInfoRef = ref.getChildRef();
String user = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME);
if (userAuth.equals(user))
{
// remove the association to this web project user meta-data
nodeService.removeChild(wpNodeRef, ref.getChildRef());
// remove permission for the user (also fixes ETWOONE-338)
permissionService.clearPermission(wpNodeRef, userAuth);
if (logger.isInfoEnabled())
{
logger.info("Uninvited web user: "+userAuth+" (store id: "+wpStoreId+")");
}
break; // for loop
}
}
}
/**
* Find all nested user authorities contained with an authority
*
* @param authority The authority to search, USER authorities are returned immediately, GROUP authorites
* are recursively scanned for contained USER authorities.
*
* @return a Set of USER authorities
*/
private Set<String> findNestedUserAuthorities(String authority)
{
Set<String> users;
AuthorityType authType = AuthorityType.getAuthorityType(authority);
if (authType.equals(AuthorityType.USER))
{
users = new HashSet<String>(1, 1.0f);
if (personService.personExists(authority) == true)
{
users.add(authority);
}
}
else if (authType.equals(AuthorityType.GROUP))
{
// walk each member of the group
users = authorityService.getContainedAuthorities(AuthorityType.USER, authority, false);
for (String userAuth : users)
{
if (personService.personExists(userAuth) == false)
{
users.remove(authType);
}
}
}
else
{
users = Collections.<String>emptySet();
}
return users;
}
private String getWebProjectsPath()
{
return "/"+SPACES_COMPANY_HOME_CHILDNAME+"/"+SPACES_WCM_CHILDNAME;
}
private static final String SPACES_COMPANY_HOME_CHILDNAME = "app:company_home"; // should match repository property: spaces.company_home.childname
private static final String SPACES_WCM_CHILDNAME = "app:wcm"; // should match repository property: spaces.wcm.childname
/**
* Transaction listener - invoked after commit
*/
private class CreateWebProjectTransactionListener extends TransactionListenerAdapter
{
private String wpStoreId;
public CreateWebProjectTransactionListener(String wpStoreId)
{
this.wpStoreId = wpStoreId;
}
/**
* @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit()
*/
@Override
public void afterCommit()
{
// post-commit
if (wpStoreId != null)
{
// update the virtualisation server with the default ROOT webapp path
// performed after the main txn has committed successfully
String newStoreName = WCMUtil.buildStagingStoreName(wpStoreId);
String path = WCMUtil.buildStoreWebappPath(newStoreName, WCMUtil.DIR_ROOT);
WCMUtil.updateVServerWebapp(virtServerRegistry, path, true);
}
}
}
/**
* Create Sandbox Transaction listener - invoked after commit
*/
private class CreateSandboxTransactionListener extends TransactionListenerAdapter
{
private List<SandboxInfo> sandboxInfoList;
private List<String> webAppNames;
public CreateSandboxTransactionListener(List<SandboxInfo> sandboxInfoList, List<String> webAppNames)
{
this.sandboxInfoList = sandboxInfoList;
this.webAppNames = webAppNames;
}
/**
* @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit()
*/
@Override
public void afterCommit()
{
// Handle notification to the virtualization server
// (this needs to occur after the sandboxes are created in the main txn)
// reload virtualisation server for webapp(s) in this web project
for (SandboxInfo sandboxInfo : this.sandboxInfoList)
{
String newlyInvitedStoreName = WCMUtil.buildStagingStoreName(sandboxInfo.getMainStoreName());
for (String webAppName : webAppNames)
{
String path = WCMUtil.buildStoreWebappPath(newlyInvitedStoreName, webAppName);
WCMUtil.updateVServerWebapp(virtServerRegistry, path, true);
}
}
}
}
}