diff --git a/source/java/org/alfresco/email/server/AliasableAspectCopyBehaviourCallback.java b/source/java/org/alfresco/email/server/AliasableAspectCopyBehaviourCallback.java index fa1f869745..c46a200e51 100644 --- a/source/java/org/alfresco/email/server/AliasableAspectCopyBehaviourCallback.java +++ b/source/java/org/alfresco/email/server/AliasableAspectCopyBehaviourCallback.java @@ -1,49 +1,49 @@ -package org.alfresco.email.server; - -import java.io.Serializable; -import java.util.Collections; -import java.util.Map; - -import org.alfresco.repo.copy.CopyBehaviourCallback; -import org.alfresco.repo.copy.CopyDetails; -import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; -import org.alfresco.service.namespace.QName; - -public class AliasableAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback -{ - static final CopyBehaviourCallback INSTANCE = new AliasableAspectCopyBehaviourCallback(); - - /** - * Disallows copying of the {@link EmailServerModel#ASPECT_ALIASABLE} aspect. - */ - @Override - public boolean getMustCopy(QName classQName, CopyDetails copyDetails) - { - if (classQName.equals(EmailServerModel.ASPECT_ALIASABLE)) - { - return false; - } - else - { - return true; - } - } - - /** - * Prevents copying off the {@link org.alfresco.model.ContentModel#PROP_NAME cm:name} property. - */ - @Override - public Map getCopyProperties( - QName classQName, - CopyDetails copyDetails, - Map properties) - { - if (classQName.equals(EmailServerModel.ASPECT_ALIASABLE)) - { - return Collections.emptyMap(); - } - return properties; - } -} - - +package org.alfresco.email.server; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; + +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.service.namespace.QName; + +public class AliasableAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback +{ + static final CopyBehaviourCallback INSTANCE = new AliasableAspectCopyBehaviourCallback(); + + /** + * Disallows copying of the {@link EmailServerModel#ASPECT_ALIASABLE} aspect. + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + if (classQName.equals(EmailServerModel.ASPECT_ALIASABLE)) + { + return false; + } + else + { + return true; + } + } + + /** + * Prevents copying off the {@link org.alfresco.model.ContentModel#PROP_NAME cm:name} property. + */ + @Override + public Map getCopyProperties( + QName classQName, + CopyDetails copyDetails, + Map properties) + { + if (classQName.equals(EmailServerModel.ASPECT_ALIASABLE)) + { + return Collections.emptyMap(); + } + return properties; + } +} + + diff --git a/source/java/org/alfresco/encryption/BootstrapReEncryptor.java b/source/java/org/alfresco/encryption/BootstrapReEncryptor.java index aed062f9b9..ecaa9f4d74 100644 --- a/source/java/org/alfresco/encryption/BootstrapReEncryptor.java +++ b/source/java/org/alfresco/encryption/BootstrapReEncryptor.java @@ -1,64 +1,64 @@ -package org.alfresco.encryption; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; - -/** - * - * @since 4.0 - * - */ -public class BootstrapReEncryptor extends AbstractLifecycleBean -{ - private static Log logger = LogFactory.getLog(BootstrapReEncryptor.class); - - private boolean enabled; - private ReEncryptor reEncryptor; - - public void setEnabled(boolean enabled) - { - this.enabled = enabled; - } - - public void setReEncryptor(ReEncryptor reEncryptor) - { - this.reEncryptor = reEncryptor; - } - - public int reEncrypt() - { - try - { - return reEncryptor.bootstrapReEncrypt(); - } - catch(MissingKeyException e) - { - throw new AlfrescoRuntimeException("Bootstrap re-encryption failed", e); - } - } - - @Override - protected void onBootstrap(ApplicationEvent event) - { - if(enabled) - { - if(logger.isDebugEnabled()) - { - logger.debug("Re-encrypting encryptable properties..."); - } - int propertiesReEncrypted = reEncrypt(); - if(logger.isDebugEnabled()) - { - logger.debug("...done, re-encrypted " + propertiesReEncrypted + " properties."); - } - } - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - } +package org.alfresco.encryption; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * + * @since 4.0 + * + */ +public class BootstrapReEncryptor extends AbstractLifecycleBean +{ + private static Log logger = LogFactory.getLog(BootstrapReEncryptor.class); + + private boolean enabled; + private ReEncryptor reEncryptor; + + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + public void setReEncryptor(ReEncryptor reEncryptor) + { + this.reEncryptor = reEncryptor; + } + + public int reEncrypt() + { + try + { + return reEncryptor.bootstrapReEncrypt(); + } + catch(MissingKeyException e) + { + throw new AlfrescoRuntimeException("Bootstrap re-encryption failed", e); + } + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + if(enabled) + { + if(logger.isDebugEnabled()) + { + logger.debug("Re-encrypting encryptable properties..."); + } + int propertiesReEncrypted = reEncrypt(); + if(logger.isDebugEnabled()) + { + logger.debug("...done, re-encrypted " + propertiesReEncrypted + " properties."); + } + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } } \ No newline at end of file diff --git a/source/java/org/alfresco/encryption/EncryptionChecker.java b/source/java/org/alfresco/encryption/EncryptionChecker.java index af3af040ca..c9f083a47f 100644 --- a/source/java/org/alfresco/encryption/EncryptionChecker.java +++ b/source/java/org/alfresco/encryption/EncryptionChecker.java @@ -1,66 +1,66 @@ -package org.alfresco.encryption; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.transaction.TransactionService; -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; - -/** - * The EncryptionChecker checks the state of the repository's encryption system. - * In particular it checks: - *
    - *
  • that the keystore exists and, if not, creates one. - *
  • that the encryption keys have not been changed. If so, the bootstrap will be halted. - *
- * - * @since 4.0 - * - */ -public class EncryptionChecker extends AbstractLifecycleBean -{ - private TransactionService transactionService; - private KeyStoreChecker keyStoreChecker; - - public void setKeyStoreChecker(KeyStoreChecker keyStoreChecker) - { - this.keyStoreChecker = keyStoreChecker; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - @Override - protected void onBootstrap(ApplicationEvent event) - { - RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); - txnHelper.setForceWritable(true); // Force write in case server is read-only - - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - try - { - keyStoreChecker.validateKeyStores(); - } - catch(Throwable e) - { - // Just throw as a runtime exception - throw new AlfrescoRuntimeException("Keystores are invalid", e); - } - - return null; - } - }); - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - - } -} +package org.alfresco.encryption; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.transaction.TransactionService; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * The EncryptionChecker checks the state of the repository's encryption system. + * In particular it checks: + *
    + *
  • that the keystore exists and, if not, creates one. + *
  • that the encryption keys have not been changed. If so, the bootstrap will be halted. + *
+ * + * @since 4.0 + * + */ +public class EncryptionChecker extends AbstractLifecycleBean +{ + private TransactionService transactionService; + private KeyStoreChecker keyStoreChecker; + + public void setKeyStoreChecker(KeyStoreChecker keyStoreChecker) + { + this.keyStoreChecker = keyStoreChecker; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + txnHelper.setForceWritable(true); // Force write in case server is read-only + + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + try + { + keyStoreChecker.validateKeyStores(); + } + catch(Throwable e) + { + // Just throw as a runtime exception + throw new AlfrescoRuntimeException("Keystores are invalid", e); + } + + return null; + } + }); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + + } +} diff --git a/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java b/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java index a63bdd3bbc..d04c15b08b 100644 --- a/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java +++ b/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java @@ -1,208 +1,208 @@ -package org.alfresco.encryption; - -import java.io.Serializable; -import java.security.InvalidKeyException; -import java.security.Key; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.attributes.AttributeService; -import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.EqualsHelper; -import org.alfresco.util.GUID; -import org.alfresco.util.Pair; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Registered Encryption Keys are stored in the AttributeService directly under a top level key defined by - * TOP_LEVEL_KEY (which means that all key aliases must be unique across however many keystores are being used). - * - * @since 4.0 - * - */ -// TODO caching? This will probably not be used extensively. -// TODO instead of persisting the Pair when registering a key, create two attributes per key (one for the -// guid and one for the encrypted value of the guid). This means a custom class does not need to be bound to -// the attribute service. -public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry -{ - public static String TOP_LEVEL_KEY = "keyCheck"; - private static final Log logger = LogFactory.getLog(EncryptionKeysRegistryImpl.class); - - private TransactionService transactionService; - private AttributeService attributeService; - private String cipherAlgorithm; - private String cipherProvider; - - public void setAttributeService(AttributeService attributeService) - { - this.attributeService = attributeService; - } - - public void setCipherAlgorithm(String cipherAlgorithm) - { - this.cipherAlgorithm = cipherAlgorithm; - } - - public void setCipherProvider(String cipherProvider) - { - this.cipherProvider = cipherProvider; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - protected Encryptor getEncryptor(final KeyMap keys) - { - DefaultEncryptor encryptor = new DefaultEncryptor(); - encryptor.setCipherAlgorithm(cipherAlgorithm); - encryptor.setCipherProvider(cipherProvider); - encryptor.setKeyProvider(new KeyProvider() - { - @Override - public Key getKey(String keyAlias) - { - return keys.getCachedKey(keyAlias).getKey(); - } - }); - return encryptor; - } - - public void init() - { - } - - public void registerKey(String keyAlias, Key key) - { - if(isKeyRegistered(keyAlias)) - { - throw new IllegalArgumentException("Key " + keyAlias + " is already registered"); - } - - // register the key by creating an attribute that stores a guid and its encrypted value - String guid = GUID.generate(); - - KeyMap keys = new KeyMap(); - keys.setKey(keyAlias, key); - Encryptor encryptor = getEncryptor(keys); - Serializable encrypted = encryptor.sealObject(keyAlias, null, guid); - Pair keyCheck = new Pair(guid, encrypted); - attributeService.createAttribute(keyCheck, TOP_LEVEL_KEY, keyAlias); - logger.info("Registered key " + keyAlias); - } - - public void unregisterKey(String keyAlias) - { - attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias); - } - - public boolean isKeyRegistered(String keyAlias) - { - try - { - return (attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias) != null); - } - catch(Throwable e) - { - // there is an issue getting the attribute. Remove it. - attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias); - return (attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias) != null); - } - } - - public List getRegisteredKeys(final Set keyStoreKeys) - { - final List registeredKeys = new ArrayList(); - - attributeService.getAttributes(new AttributeQueryCallback() - { - public boolean handleAttribute(Long id, Serializable value, - Serializable[] keys) - { - // Add as a registered key if the keystore contains the key - String keyAlias = (String)keys[1]; - if(keyStoreKeys.contains(keyAlias)) - { - registeredKeys.add(keyAlias); - } - return true; - } - - }, - TOP_LEVEL_KEY); - - return registeredKeys; - } - - @SuppressWarnings("unchecked") - public KEY_STATUS checkKey(String keyAlias, Key key) - { - Pair keyCheck = null; - - if(attributeService.exists(TOP_LEVEL_KEY, keyAlias)) - { - try - { - // check that the key has not changed by decrypting the encrypted guid attribute - // comparing against the guid - try - { - keyCheck = (Pair)attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias); - } - catch(Throwable e) - { - // there is an issue getting the attribute. Remove it. - attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias); - return KEY_STATUS.MISSING; - } - - if(keyCheck == null) - { - return KEY_STATUS.MISSING; - } - - KeyMap keys = new KeyMap(); - keys.setKey(keyAlias, key); - Encryptor encryptor = getEncryptor(keys); - Serializable storedGUID = encryptor.unsealObject(keyAlias, keyCheck.getSecond()); - return EqualsHelper.nullSafeEquals(storedGUID, keyCheck.getFirst()) ? KEY_STATUS.OK : KEY_STATUS.CHANGED; - } - catch(InvalidKeyException e) - { - // key exception indicates that the key has changed - it can't decrypt the - // previously-encrypted data - return KEY_STATUS.CHANGED; - } - } - else - { - return KEY_STATUS.MISSING; - } - } - - // note that this removes _all_ keys in the keystore. Use with care. - public void removeRegisteredKeys(final Set keys) - { - RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); - final RetryingTransactionCallback removeKeysCallback = new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - for(String keyAlias : keys) - { - attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias); - } - - return null; - } - }; - retryingTransactionHelper.doInTransaction(removeKeysCallback, false); - } -} +package org.alfresco.encryption; + +import java.io.Serializable; +import java.security.InvalidKeyException; +import java.security.Key; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.GUID; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Registered Encryption Keys are stored in the AttributeService directly under a top level key defined by + * TOP_LEVEL_KEY (which means that all key aliases must be unique across however many keystores are being used). + * + * @since 4.0 + * + */ +// TODO caching? This will probably not be used extensively. +// TODO instead of persisting the Pair when registering a key, create two attributes per key (one for the +// guid and one for the encrypted value of the guid). This means a custom class does not need to be bound to +// the attribute service. +public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry +{ + public static String TOP_LEVEL_KEY = "keyCheck"; + private static final Log logger = LogFactory.getLog(EncryptionKeysRegistryImpl.class); + + private TransactionService transactionService; + private AttributeService attributeService; + private String cipherAlgorithm; + private String cipherProvider; + + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + public void setCipherAlgorithm(String cipherAlgorithm) + { + this.cipherAlgorithm = cipherAlgorithm; + } + + public void setCipherProvider(String cipherProvider) + { + this.cipherProvider = cipherProvider; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + protected Encryptor getEncryptor(final KeyMap keys) + { + DefaultEncryptor encryptor = new DefaultEncryptor(); + encryptor.setCipherAlgorithm(cipherAlgorithm); + encryptor.setCipherProvider(cipherProvider); + encryptor.setKeyProvider(new KeyProvider() + { + @Override + public Key getKey(String keyAlias) + { + return keys.getCachedKey(keyAlias).getKey(); + } + }); + return encryptor; + } + + public void init() + { + } + + public void registerKey(String keyAlias, Key key) + { + if(isKeyRegistered(keyAlias)) + { + throw new IllegalArgumentException("Key " + keyAlias + " is already registered"); + } + + // register the key by creating an attribute that stores a guid and its encrypted value + String guid = GUID.generate(); + + KeyMap keys = new KeyMap(); + keys.setKey(keyAlias, key); + Encryptor encryptor = getEncryptor(keys); + Serializable encrypted = encryptor.sealObject(keyAlias, null, guid); + Pair keyCheck = new Pair(guid, encrypted); + attributeService.createAttribute(keyCheck, TOP_LEVEL_KEY, keyAlias); + logger.info("Registered key " + keyAlias); + } + + public void unregisterKey(String keyAlias) + { + attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias); + } + + public boolean isKeyRegistered(String keyAlias) + { + try + { + return (attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias) != null); + } + catch(Throwable e) + { + // there is an issue getting the attribute. Remove it. + attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias); + return (attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias) != null); + } + } + + public List getRegisteredKeys(final Set keyStoreKeys) + { + final List registeredKeys = new ArrayList(); + + attributeService.getAttributes(new AttributeQueryCallback() + { + public boolean handleAttribute(Long id, Serializable value, + Serializable[] keys) + { + // Add as a registered key if the keystore contains the key + String keyAlias = (String)keys[1]; + if(keyStoreKeys.contains(keyAlias)) + { + registeredKeys.add(keyAlias); + } + return true; + } + + }, + TOP_LEVEL_KEY); + + return registeredKeys; + } + + @SuppressWarnings("unchecked") + public KEY_STATUS checkKey(String keyAlias, Key key) + { + Pair keyCheck = null; + + if(attributeService.exists(TOP_LEVEL_KEY, keyAlias)) + { + try + { + // check that the key has not changed by decrypting the encrypted guid attribute + // comparing against the guid + try + { + keyCheck = (Pair)attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias); + } + catch(Throwable e) + { + // there is an issue getting the attribute. Remove it. + attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias); + return KEY_STATUS.MISSING; + } + + if(keyCheck == null) + { + return KEY_STATUS.MISSING; + } + + KeyMap keys = new KeyMap(); + keys.setKey(keyAlias, key); + Encryptor encryptor = getEncryptor(keys); + Serializable storedGUID = encryptor.unsealObject(keyAlias, keyCheck.getSecond()); + return EqualsHelper.nullSafeEquals(storedGUID, keyCheck.getFirst()) ? KEY_STATUS.OK : KEY_STATUS.CHANGED; + } + catch(InvalidKeyException e) + { + // key exception indicates that the key has changed - it can't decrypt the + // previously-encrypted data + return KEY_STATUS.CHANGED; + } + } + else + { + return KEY_STATUS.MISSING; + } + } + + // note that this removes _all_ keys in the keystore. Use with care. + public void removeRegisteredKeys(final Set keys) + { + RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); + final RetryingTransactionCallback removeKeysCallback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + for(String keyAlias : keys) + { + attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias); + } + + return null; + } + }; + retryingTransactionHelper.doInTransaction(removeKeysCallback, false); + } +} diff --git a/source/java/org/alfresco/encryption/KeyStoreChecker.java b/source/java/org/alfresco/encryption/KeyStoreChecker.java index 78be80a2a0..613f63f9e6 100644 --- a/source/java/org/alfresco/encryption/KeyStoreChecker.java +++ b/source/java/org/alfresco/encryption/KeyStoreChecker.java @@ -1,30 +1,30 @@ -package org.alfresco.encryption; - -/** - * Checks the repository key stores. - * - * @since 4.0 - * - */ -public class KeyStoreChecker -{ - private AlfrescoKeyStore mainKeyStore; - - public KeyStoreChecker() - { - } - - public void setMainKeyStore(AlfrescoKeyStore mainKeyStore) - { - this.mainKeyStore = mainKeyStore; - } - - public void validateKeyStores() throws InvalidKeystoreException, MissingKeyException - { - mainKeyStore.validateKeys(); - if(!mainKeyStore.exists()) - { - mainKeyStore.create(); - } - } -} +package org.alfresco.encryption; + +/** + * Checks the repository key stores. + * + * @since 4.0 + * + */ +public class KeyStoreChecker +{ + private AlfrescoKeyStore mainKeyStore; + + public KeyStoreChecker() + { + } + + public void setMainKeyStore(AlfrescoKeyStore mainKeyStore) + { + this.mainKeyStore = mainKeyStore; + } + + public void validateKeyStores() throws InvalidKeystoreException, MissingKeyException + { + mainKeyStore.validateKeys(); + if(!mainKeyStore.exists()) + { + mainKeyStore.create(); + } + } +} diff --git a/source/java/org/alfresco/encryption/ReEncryptor.java b/source/java/org/alfresco/encryption/ReEncryptor.java index 5ecb10a9f6..317891a2a0 100644 --- a/source/java/org/alfresco/encryption/ReEncryptor.java +++ b/source/java/org/alfresco/encryption/ReEncryptor.java @@ -1,335 +1,335 @@ -package org.alfresco.encryption; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import javax.crypto.SealedObject; - -import org.alfresco.repo.batch.BatchProcessWorkProvider; -import org.alfresco.repo.batch.BatchProcessor; -import org.alfresco.repo.dictionary.DictionaryDAO; -import org.alfresco.repo.domain.node.NodeDAO; -import org.alfresco.repo.domain.node.NodePropertyEntity; -import org.alfresco.repo.domain.node.NodePropertyKey; -import org.alfresco.repo.domain.node.NodePropertyValue; -import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.repo.lock.JobLockService; -import org.alfresco.repo.lock.LockAcquisitionException; -import org.alfresco.repo.node.encryption.MetadataEncryptor; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * Re-encrypts encryptable repository properties using a new set of encryption keys. - * Decrypts the repository properties using the default encryptor, falling back to - * a backup decryptor (using the old encryption keys) if necessary, and then re-encrypts - * the properties. - * - * Can run in one of two ways: - * - *
    - *
  • during bootstrap. - *
  • by using JMX (available only to Enterprise). In this case, the system can stay running while the re-encryption takes place. - *
- * - * @since 4.0 - */ -public class ReEncryptor implements ApplicationContextAware -{ - private static Log logger = LogFactory.getLog(ReEncryptor.class); - - private NodeDAO nodeDAO; - private DictionaryDAO dictionaryDAO; - private QNameDAO qnameDAO; - - private MetadataEncryptor metadataEncryptor; - - private ApplicationContext applicationContext; - private TransactionService transactionService; - private RetryingTransactionHelper transactionHelper; - - private int numThreads; - private int chunkSize; - private boolean splitTxns = true; - - private static final QName LOCK = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "OrphanReaper"); - private JobLockService jobLockService; - - /** - * Set the transaction provider so that each execution can be performed within a transaction - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - this.transactionHelper = transactionService.getRetryingTransactionHelper(); - } - - public void setMetadataEncryptor(MetadataEncryptor metadataEncryptor) - { - this.metadataEncryptor = metadataEncryptor; - } - - public MetadataEncryptor getMetadataEncryptor() - { - return metadataEncryptor; - } - - public void setJobLockService(JobLockService jobLockService) - { - this.jobLockService = jobLockService; - } - - public void setNumThreads(int numThreads) - { - this.numThreads = numThreads; - } - - public void setChunkSize(int chunkSize) - { - this.chunkSize = chunkSize; - } - - public void setSplitTxns(boolean splitTxns) - { - this.splitTxns = splitTxns; - } - - public void setNodeDAO(NodeDAO nodeDAO) - { - this.nodeDAO = nodeDAO; - } - - public void setDictionaryDAO(DictionaryDAO dictionaryDAO) - { - this.dictionaryDAO = dictionaryDAO; - } - - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - - /** - * Attempts to get the lock. If the lock couldn't be taken, then null is returned. - * - * @return Returns the lock token or null - */ - private String getLock(long time) - { - try - { - return jobLockService.getLock(LOCK, time); - } - catch (LockAcquisitionException e) - { - return null; - } - } - - /** - * Attempts to get the lock. If it fails, the current transaction is marked for rollback. - */ - private void refreshLock(String lockToken, long time) - { - if (lockToken == null) - { - throw new IllegalArgumentException("Must provide existing lockToken"); - } - jobLockService.refreshLock(lockToken, LOCK, time); - } - - protected void reEncryptProperties(final List properties, final String lockToken) - { - final Iterator it = properties.iterator(); - - // TODO use BatchProcessWorkerAdaptor? - - BatchProcessor.BatchProcessWorker worker = new BatchProcessor.BatchProcessWorker() - { - public String getIdentifier(NodePropertyEntity entity) - { - return String.valueOf(entity.getNodeId()); - } - - public void beforeProcess() throws Throwable - { - refreshLock(lockToken, chunkSize * 100L); - } - - public void afterProcess() throws Throwable - { - } - - public void process(final NodePropertyEntity entity) throws Throwable - { - NodePropertyValue nodePropValue = entity.getValue(); - // TODO check that we have the correct type i.e. can be cast to Serializable - Serializable value = nodePropValue.getSerializableValue(); - if(value instanceof SealedObject) - { - SealedObject sealed = (SealedObject)value; - - NodePropertyKey propertyKey = entity.getKey(); - QName propertyQName = qnameDAO.getQName(propertyKey.getQnameId()).getSecond(); - - // decrypt... - Serializable decrypted = metadataEncryptor.decrypt(propertyQName, sealed); - - // ...and then re-encrypt. The new key will be used. - Serializable resealed = metadataEncryptor.encrypt(propertyQName, decrypted); - - // TODO update resealed using batch update? - // does the node DAO do batch updating? - nodeDAO.setNodeProperties(entity.getNodeId(), Collections.singletonMap(propertyQName, resealed)); - } - else - { - NodePropertyKey nodeKey = entity.getKey(); - QName propertyQName = qnameDAO.getQName(nodeKey.getQnameId()).getSecond(); - logger.warn("Encountered an encrypted property that is not a SealedObject, for node id " + - entity.getNodeId() + ", property " + propertyQName); - } - } - }; - - BatchProcessWorkProvider provider = new BatchProcessWorkProvider() - { - @Override - public int getTotalEstimatedWorkSize() - { - return properties.size(); - } - - @Override - public Collection getNextWork() - { - List sublist = new ArrayList(chunkSize); - - synchronized(it) - { - int count = 0; - while(it.hasNext() && count < chunkSize) - { - sublist.add(it.next()); - count++; - } - } - - return sublist; - } - }; - - new BatchProcessor( - "Reencryptor", - transactionHelper, - provider, - numThreads, chunkSize, - applicationContext, - logger, 100).process(worker, splitTxns); - } - - /** - * Re-encrypt using the configured backup keystore to decrypt and the main keystore to encrypt - */ - public int bootstrapReEncrypt() throws MissingKeyException - { - if(!metadataEncryptor.backupKeyAvailable(KeyProvider.ALIAS_METADATA)) - { - throw new MissingKeyException("Backup key store is either not present or does not contain a metadata encryption key"); - } - return reEncrypt(); - } - - /** - * Re-encrypt by decrypting using the configured keystore and encrypting using a keystore configured using the provided new key store parameters. - * Called from e.g. JMX. - * - * Assumes that the main key store has been already been reloaded. - * - * Note: it is the responsibility of the end user to ensure that the underlying keystores have been set up appropriately - * i.e. the old key store is backed up to the location defined by the property '${dir.keystore}/backup-keystore' and the new - * key store replaces it. This can be done while the repository is running. - */ - public int reEncrypt() throws MissingKeyException - { - if(!metadataEncryptor.keyAvailable(KeyProvider.ALIAS_METADATA)) - { - throw new MissingKeyException("Main key store is either not present or does not contain a metadata encryption key"); - } - if(!metadataEncryptor.backupKeyAvailable(KeyProvider.ALIAS_METADATA)) - { - throw new MissingKeyException("Backup key store is either not present or does not contain a metadata encryption key"); - } - - int numProps = reEncryptImpl(); - return numProps; - } - - protected int reEncryptImpl() - { - // Take out a re-encryptor lock - RetryingTransactionCallback txnWork = new RetryingTransactionCallback() - { - public String execute() throws Exception - { - String lockToken = getLock(20000L); - return lockToken; - } - }; - - String lockToken = transactionService.getRetryingTransactionHelper().doInTransaction(txnWork, false, true); - if(lockToken == null) - { - logger.warn("Can't get lock. Assume multiple re-encryptors ..."); - return 0; - } - - // get encrypted properties - Collection propertyDefs = dictionaryDAO.getPropertiesOfDataType(DataTypeDefinition.ENCRYPTED); - Set qnames = new HashSet(); - for(PropertyDefinition propDef : propertyDefs) - { - qnames.add(propDef.getName()); - } - - // TODO use callback mechanism, or select based on set of nodes? - List properties = nodeDAO.selectNodePropertiesByTypes(qnames); - - if(logger.isDebugEnabled()) - { - logger.debug("Found " + properties.size() + " properties to re-encrypt..."); - } - - // reencrypt these properties TODO don't call if num props == 0 - reEncryptProperties(properties, lockToken); - - if(logger.isDebugEnabled()) - { - logger.debug("...done re-encrypting."); - } - - return properties.size(); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - this.applicationContext = applicationContext; - } -} +package org.alfresco.encryption; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.crypto.SealedObject; + +import org.alfresco.repo.batch.BatchProcessWorkProvider; +import org.alfresco.repo.batch.BatchProcessor; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.node.NodePropertyEntity; +import org.alfresco.repo.domain.node.NodePropertyKey; +import org.alfresco.repo.domain.node.NodePropertyValue; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; +import org.alfresco.repo.node.encryption.MetadataEncryptor; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Re-encrypts encryptable repository properties using a new set of encryption keys. + * Decrypts the repository properties using the default encryptor, falling back to + * a backup decryptor (using the old encryption keys) if necessary, and then re-encrypts + * the properties. + * + * Can run in one of two ways: + * + *
    + *
  • during bootstrap. + *
  • by using JMX (available only to Enterprise). In this case, the system can stay running while the re-encryption takes place. + *
+ * + * @since 4.0 + */ +public class ReEncryptor implements ApplicationContextAware +{ + private static Log logger = LogFactory.getLog(ReEncryptor.class); + + private NodeDAO nodeDAO; + private DictionaryDAO dictionaryDAO; + private QNameDAO qnameDAO; + + private MetadataEncryptor metadataEncryptor; + + private ApplicationContext applicationContext; + private TransactionService transactionService; + private RetryingTransactionHelper transactionHelper; + + private int numThreads; + private int chunkSize; + private boolean splitTxns = true; + + private static final QName LOCK = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "OrphanReaper"); + private JobLockService jobLockService; + + /** + * Set the transaction provider so that each execution can be performed within a transaction + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + this.transactionHelper = transactionService.getRetryingTransactionHelper(); + } + + public void setMetadataEncryptor(MetadataEncryptor metadataEncryptor) + { + this.metadataEncryptor = metadataEncryptor; + } + + public MetadataEncryptor getMetadataEncryptor() + { + return metadataEncryptor; + } + + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + + public void setNumThreads(int numThreads) + { + this.numThreads = numThreads; + } + + public void setChunkSize(int chunkSize) + { + this.chunkSize = chunkSize; + } + + public void setSplitTxns(boolean splitTxns) + { + this.splitTxns = splitTxns; + } + + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + public void setDictionaryDAO(DictionaryDAO dictionaryDAO) + { + this.dictionaryDAO = dictionaryDAO; + } + + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + /** + * Attempts to get the lock. If the lock couldn't be taken, then null is returned. + * + * @return Returns the lock token or null + */ + private String getLock(long time) + { + try + { + return jobLockService.getLock(LOCK, time); + } + catch (LockAcquisitionException e) + { + return null; + } + } + + /** + * Attempts to get the lock. If it fails, the current transaction is marked for rollback. + */ + private void refreshLock(String lockToken, long time) + { + if (lockToken == null) + { + throw new IllegalArgumentException("Must provide existing lockToken"); + } + jobLockService.refreshLock(lockToken, LOCK, time); + } + + protected void reEncryptProperties(final List properties, final String lockToken) + { + final Iterator it = properties.iterator(); + + // TODO use BatchProcessWorkerAdaptor? + + BatchProcessor.BatchProcessWorker worker = new BatchProcessor.BatchProcessWorker() + { + public String getIdentifier(NodePropertyEntity entity) + { + return String.valueOf(entity.getNodeId()); + } + + public void beforeProcess() throws Throwable + { + refreshLock(lockToken, chunkSize * 100L); + } + + public void afterProcess() throws Throwable + { + } + + public void process(final NodePropertyEntity entity) throws Throwable + { + NodePropertyValue nodePropValue = entity.getValue(); + // TODO check that we have the correct type i.e. can be cast to Serializable + Serializable value = nodePropValue.getSerializableValue(); + if(value instanceof SealedObject) + { + SealedObject sealed = (SealedObject)value; + + NodePropertyKey propertyKey = entity.getKey(); + QName propertyQName = qnameDAO.getQName(propertyKey.getQnameId()).getSecond(); + + // decrypt... + Serializable decrypted = metadataEncryptor.decrypt(propertyQName, sealed); + + // ...and then re-encrypt. The new key will be used. + Serializable resealed = metadataEncryptor.encrypt(propertyQName, decrypted); + + // TODO update resealed using batch update? + // does the node DAO do batch updating? + nodeDAO.setNodeProperties(entity.getNodeId(), Collections.singletonMap(propertyQName, resealed)); + } + else + { + NodePropertyKey nodeKey = entity.getKey(); + QName propertyQName = qnameDAO.getQName(nodeKey.getQnameId()).getSecond(); + logger.warn("Encountered an encrypted property that is not a SealedObject, for node id " + + entity.getNodeId() + ", property " + propertyQName); + } + } + }; + + BatchProcessWorkProvider provider = new BatchProcessWorkProvider() + { + @Override + public int getTotalEstimatedWorkSize() + { + return properties.size(); + } + + @Override + public Collection getNextWork() + { + List sublist = new ArrayList(chunkSize); + + synchronized(it) + { + int count = 0; + while(it.hasNext() && count < chunkSize) + { + sublist.add(it.next()); + count++; + } + } + + return sublist; + } + }; + + new BatchProcessor( + "Reencryptor", + transactionHelper, + provider, + numThreads, chunkSize, + applicationContext, + logger, 100).process(worker, splitTxns); + } + + /** + * Re-encrypt using the configured backup keystore to decrypt and the main keystore to encrypt + */ + public int bootstrapReEncrypt() throws MissingKeyException + { + if(!metadataEncryptor.backupKeyAvailable(KeyProvider.ALIAS_METADATA)) + { + throw new MissingKeyException("Backup key store is either not present or does not contain a metadata encryption key"); + } + return reEncrypt(); + } + + /** + * Re-encrypt by decrypting using the configured keystore and encrypting using a keystore configured using the provided new key store parameters. + * Called from e.g. JMX. + * + * Assumes that the main key store has been already been reloaded. + * + * Note: it is the responsibility of the end user to ensure that the underlying keystores have been set up appropriately + * i.e. the old key store is backed up to the location defined by the property '${dir.keystore}/backup-keystore' and the new + * key store replaces it. This can be done while the repository is running. + */ + public int reEncrypt() throws MissingKeyException + { + if(!metadataEncryptor.keyAvailable(KeyProvider.ALIAS_METADATA)) + { + throw new MissingKeyException("Main key store is either not present or does not contain a metadata encryption key"); + } + if(!metadataEncryptor.backupKeyAvailable(KeyProvider.ALIAS_METADATA)) + { + throw new MissingKeyException("Backup key store is either not present or does not contain a metadata encryption key"); + } + + int numProps = reEncryptImpl(); + return numProps; + } + + protected int reEncryptImpl() + { + // Take out a re-encryptor lock + RetryingTransactionCallback txnWork = new RetryingTransactionCallback() + { + public String execute() throws Exception + { + String lockToken = getLock(20000L); + return lockToken; + } + }; + + String lockToken = transactionService.getRetryingTransactionHelper().doInTransaction(txnWork, false, true); + if(lockToken == null) + { + logger.warn("Can't get lock. Assume multiple re-encryptors ..."); + return 0; + } + + // get encrypted properties + Collection propertyDefs = dictionaryDAO.getPropertiesOfDataType(DataTypeDefinition.ENCRYPTED); + Set qnames = new HashSet(); + for(PropertyDefinition propDef : propertyDefs) + { + qnames.add(propDef.getName()); + } + + // TODO use callback mechanism, or select based on set of nodes? + List properties = nodeDAO.selectNodePropertiesByTypes(qnames); + + if(logger.isDebugEnabled()) + { + logger.debug("Found " + properties.size() + " properties to re-encrypt..."); + } + + // reencrypt these properties TODO don't call if num props == 0 + reEncryptProperties(properties, lockToken); + + if(logger.isDebugEnabled()) + { + logger.debug("...done re-encrypting."); + } + + return properties.size(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } +} diff --git a/source/java/org/alfresco/filesys/alfresco/NetworkFileLegacyReferenceCount.java b/source/java/org/alfresco/filesys/alfresco/NetworkFileLegacyReferenceCount.java index e933c2b43a..752c16d391 100644 --- a/source/java/org/alfresco/filesys/alfresco/NetworkFileLegacyReferenceCount.java +++ b/source/java/org/alfresco/filesys/alfresco/NetworkFileLegacyReferenceCount.java @@ -1,29 +1,29 @@ -package org.alfresco.filesys.alfresco; - -/** - * Does this NetworkFile have reference counting? - */ -public interface NetworkFileLegacyReferenceCount -{ - /** - * Increment the file open count, first open = 1; - * - * @return the current open count - */ - public int incrementLegacyOpenCount(); - - /** - * Decrement the file open count - * - * @return the current open count - */ - public int decrementLagacyOpenCount(); - - /** - * Return the open file count - * - * @return the current open count - */ - public int getLegacyOpenCount(); - -} +package org.alfresco.filesys.alfresco; + +/** + * Does this NetworkFile have reference counting? + */ +public interface NetworkFileLegacyReferenceCount +{ + /** + * Increment the file open count, first open = 1; + * + * @return the current open count + */ + public int incrementLegacyOpenCount(); + + /** + * Decrement the file open count + * + * @return the current open count + */ + public int decrementLagacyOpenCount(); + + /** + * Return the open file count + * + * @return the current open count + */ + public int getLegacyOpenCount(); + +} diff --git a/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlay.java b/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlay.java index c99a03bf92..1747445063 100644 --- a/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlay.java +++ b/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlay.java @@ -1,44 +1,44 @@ -package org.alfresco.filesys.alfresco; - -import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; -import org.alfresco.service.cmr.repository.NodeRef; - -public interface PseudoFileOverlay -{ - /** - * Is this a pseudo file? - * @param parentDir NodeRef - * @param name String - * @return true the file is a pseudo file - */ - public boolean isPseudoFile(NodeRef parentDir, String name); - - /** - * Get the pseudo file - * @param parentDir NodeRef - * @param name String - * @return the pseudoFile or null if there is no pseudo file - */ - public PseudoFile getPseudoFile(NodeRef parentDir, String name); - - /** - * Search for the pseudo files on the specified path - * @param parentDir NodeRef - * @param name String - * @return list of pseudo files. - */ - public PseudoFileList searchPseudoFiles(NodeRef parentDir, String name); - - /** - * Delete a pseudo file. - * - * Pseudo files may need to be deleted for delete folder operations to work - * correctly. - * - * A pseudo file can be deleted for a short time. However it may re-appear at some point - * later since there is no permanent persistence of pseudo files which are ephemeral! - */ - public void delete(NodeRef parentDir, String name); - -} +package org.alfresco.filesys.alfresco; + +import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; +import org.alfresco.service.cmr.repository.NodeRef; + +public interface PseudoFileOverlay +{ + /** + * Is this a pseudo file? + * @param parentDir NodeRef + * @param name String + * @return true the file is a pseudo file + */ + public boolean isPseudoFile(NodeRef parentDir, String name); + + /** + * Get the pseudo file + * @param parentDir NodeRef + * @param name String + * @return the pseudoFile or null if there is no pseudo file + */ + public PseudoFile getPseudoFile(NodeRef parentDir, String name); + + /** + * Search for the pseudo files on the specified path + * @param parentDir NodeRef + * @param name String + * @return list of pseudo files. + */ + public PseudoFileList searchPseudoFiles(NodeRef parentDir, String name); + + /** + * Delete a pseudo file. + * + * Pseudo files may need to be deleted for delete folder operations to work + * correctly. + * + * A pseudo file can be deleted for a short time. However it may re-appear at some point + * later since there is no permanent persistence of pseudo files which are ephemeral! + */ + public void delete(NodeRef parentDir, String name); + +} diff --git a/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlayImpl.java b/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlayImpl.java index 5efc9de07a..2aae41204b 100644 --- a/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlayImpl.java +++ b/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlayImpl.java @@ -1,505 +1,505 @@ -package org.alfresco.filesys.alfresco; - -import java.io.Serializable; -import java.util.Enumeration; -import java.util.Map; - -import org.alfresco.filesys.repo.ContentDiskDriver2; -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.FileName; -import org.alfresco.jlan.server.filesys.pseudo.MemoryPseudoFile; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; -import org.alfresco.jlan.util.WildCard; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.admin.SysAdminParams; -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.site.SiteModel; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Re-implementation of PseudoFiles for ContentDiskDriver2 - * - * Overlays "desktop actions" - * - * @author mrogers - * */ -public class PseudoFileOverlayImpl implements PseudoFileOverlay -{ - private SysAdminParams sysAdminParams; - private AlfrescoContext context; - private NodeService nodeService; - private SimpleCache deletePseudoFileCache; - - private static final Log logger = LogFactory.getLog(PseudoFileOverlayImpl.class); - - PseudoFileList pl = new PseudoFileList(); - - public void init() - { - PropertyCheck.mandatory(this, "nodeService", getNodeService()); - PropertyCheck.mandatory(this, "context", context); - PropertyCheck.mandatory(this, "sysAdminParams", sysAdminParams); - PropertyCheck.mandatory(this, "deletePseudoFileCache", deletePseudoFileCache); - - DesktopActionTable actions = context.getDesktopActions(); - - if(actions != null) - { - Enumeration actionNames = actions.enumerateActionNames(); - - while(actionNames.hasMoreElements()) - { - // Get the current desktop action - String name = actionNames.nextElement(); - DesktopAction action = actions.getAction(name); - - // Add the pseudo file for the desktop action - - if ( action.hasPseudoFile()) - { - PseudoFile file = action.getPseudoFile(); - pl.addFile(file); - } - } - } - } - - private PseudoFile generateAlfrescoURLShortcut(NodeRef nodeRef) - { - if ( context.isAlfrescoURLEnabled()) - { - // Make sure the state has the associated node details - - // Build the URL file data - - StringBuilder urlStr = new StringBuilder(); - - urlStr.append("[InternetShortcut]\r\n"); - urlStr.append("URL="); - urlStr.append(getAlfrescoURLPrefix()); - urlStr.append("navigate/browse/workspace/SpacesStore/"); - urlStr.append( nodeRef.getId()); - urlStr.append("\r\n"); - - // Create the in memory pseudo file for the URL link - - byte[] urlData = urlStr.toString().getBytes(); - - MemoryPseudoFile urlFile = new MemoryPseudoFile( context.getURLFileName(), urlData); - return urlFile; - } - return null; - } - - /** - * Return the site name if the node ref is in a document library - * Return null if the document is not in a site - */ - // MER URRGH - copied from IMAP service - I don't like it there either! - private String getSiteForNode(NodeRef nodeRef) - { - if(logger.isDebugEnabled()) - { - logger.debug("get site for node:" + nodeRef); - } - boolean isInDocLibrary = false; - - NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef(); - - if(nodeService.getType(parent).equals(SiteModel.TYPE_SITE)) - { - String folderName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - if(folderName.equalsIgnoreCase("documentlibrary")) - { - isInDocLibrary = true; - } - } - else - { - while (parent != null && !nodeService.getType(parent).equals(SiteModel.TYPE_SITE)) - { - String parentName = (String) nodeService.getProperty(parent, ContentModel.PROP_NAME); - if (parentName.equalsIgnoreCase("documentlibrary")) - { - isInDocLibrary = true; - } - - parent = nodeService.getPrimaryParent(parent).getParentRef(); - } - } - - if (parent == null) - { - logger.debug("folder is not in a site"); - return null; - } - else - { - if(isInDocLibrary) - { - if(nodeService.getType(parent).equals(SiteModel.TYPE_SITE)) - { - String siteName = (String)nodeService.getProperty(parent, ContentModel.PROP_NAME); - if(logger.isDebugEnabled()) - { - logger.debug("got a site:" + siteName); - } - return siteName; - } - } - logger.debug("folder is not in doc library"); - - return null; - } - - } - - private PseudoFile generateShareURLShortcut(NodeRef nodeRef) - { - if(logger.isDebugEnabled()) - { - logger.debug("generateShareURLShortcut nodeRef" + nodeRef); - } - if ( context.isShareURLEnabled()) - { - String site = getSiteForNode(nodeRef); - if(site != null) - { - // Make sure the state has the associated node details - // Build the URL file data - - StringBuilder urlStr = new StringBuilder(); - -// This is the URL generated -// http://markr:8080/share/page/site/wibble/folder-details?nodeRef=workspace://SpacesStore/f72b2475-7571-46fe-947b-b0ee1b6a82ea - urlStr.append("[InternetShortcut]\r\n"); - urlStr.append("URL="); - urlStr.append(getShareURLPrefix()); - urlStr.append("page/site/"); - urlStr.append(site + "/folder-details?nodeRef="); - urlStr.append(nodeRef.getStoreRef() + "/"); - urlStr.append( nodeRef.getId()); - urlStr.append("\r\n"); - - // Should this be the URL instead -// http://markr:8080/share/page/site/wibble/documentlibrary#filter=path%7C%2Ffolder%2520A%2FFolderB&page=1 - - // Create the in memory pseudo file for the URL link - if(logger.isDebugEnabled()) - { - logger.debug("generateShareURLShortcut url as string:" + urlStr); - } - byte[] urlData = urlStr.toString().getBytes(); - - MemoryPseudoFile urlFile = new MemoryPseudoFile( context.getShareURLFileName(), urlData); - return urlFile; - } - - - } - return null; - } - - - /** - * - */ - public boolean isPseudoFile(NodeRef parentDir, String name) - { - if ( parentDir == null) - { - return false; - } - - if(context.isAlfrescoURLEnabled()) - { - if(context.getURLFileName().equals(name)) - { - return true; - } - } - - if(context.isShareURLEnabled()) - { - if(context.getShareURLFileName().equals(name)) - { - return true; - } - } - - if(getPseudoFile(parentDir, name) != null) - { - return true; - } - else - { - return false; - } - } - - /** - * Get the pseudo file - * @param parentDir NodeRef - * @param fname String - * @return the pseudoFile or null if there is no pseudo file - */ - public PseudoFile getPseudoFile(NodeRef parentDir, String fname) - { - if ( parentDir == null) - { - return null; - } - if (isDeleted(parentDir, fname)) - { - return null; - } - if(context.isAlfrescoURLEnabled()) - { - if(context.getURLFileName().equals(fname)) - { - if(logger.isDebugEnabled()) - { - logger.debug("returning URL pseudo file"); - } - return generateAlfrescoURLShortcut(parentDir); - } - } - - if(context.isShareURLEnabled()) - { - if(context.getShareURLFileName().equals(fname)) - { - if(logger.isDebugEnabled()) - { - logger.debug("returning Share URL pseudo file"); - } - return generateShareURLShortcut(parentDir); - } - } - - PseudoFile file = pl.findFile(fname, false); - return file; - } - - /** - * - */ - public PseudoFileList searchPseudoFiles(NodeRef parentDir, String name) - { - if(logger.isDebugEnabled()) - { - logger.debug("searchPseudoFile parentDir:" + parentDir +", name:" + name); - } - //return pseudo files matching the path/pattern - - if ( parentDir == null || name == null || name.length() == 0 || name.equals("\\")) - { - return null; - } - - String fname = name; - - if ( fname.equals( "*.*")) - { - fname = "*"; - } - - if ( WildCard.containsWildcards(fname)) - { - // does contain wildcards - - // Check if the wildcard is for all files or a subset - - if ( fname.equals( "*")) - { - // Match all pseudo files - PseudoFileList filterList = new PseudoFileList(); - - // copy desktop actions which do not depend on parentDir - for ( int i = 0; i < pl.numberOfFiles(); i++) - { - PseudoFile pseudoFile = pl.getFileAt(i); - if(!isDeleted(parentDir, pseudoFile.getFileName())) - { - // File is not deleted - filterList.addFile(pseudoFile); - } - } - - // The URL file is dependent upon the parent dir - if(context.isAlfrescoURLEnabled()) - { - if(!isDeleted(parentDir, context.getURLFileName())) - { - filterList.addFile(generateAlfrescoURLShortcut(parentDir)); - } - else - { - if(logger.isDebugEnabled()) - { - logger.debug("alfresco URL pseudo file deleted"); - } - } - } - - if(context.isShareURLEnabled()) - { - if(!isDeleted(parentDir, context.getShareURLFileName())) - { - PseudoFile sharePseudoFile = generateShareURLShortcut(parentDir); - if(sharePseudoFile != null) - { - filterList.addFile(sharePseudoFile); - } - } - } - - return filterList; - } - else - { - // Generate a subset of pseudo files that match the wildcard search pattern - - WildCard wildCard = new WildCard( fname, false); - PseudoFileList filterList = new PseudoFileList(); - - for ( int i = 0; i < pl.numberOfFiles(); i++) - { - PseudoFile pseudoFile = pl.getFileAt( i); - if ( wildCard.matchesPattern( pseudoFile.getFileName())) - { - if(!isDeleted(parentDir, pseudoFile.getFileName())) - { - // Add the pseudo file to the filtered list - filterList.addFile( pseudoFile); - } - } - } - - // The URL file is dependent upon the parent dir - if(context.isAlfrescoURLEnabled()) - { - if(wildCard.matchesPattern(context.getURLFileName())) - { - if(!isDeleted(parentDir, context.getURLFileName())) - { - filterList.addFile(generateAlfrescoURLShortcut(parentDir)); - } - } - } - - if(context.isShareURLEnabled()) - { - if(wildCard.matchesPattern(context.getShareURLFileName())) - { - if(!isDeleted(parentDir, context.getShareURLFileName())) - { - PseudoFile sharePseudoFile = generateShareURLShortcut(parentDir); - - if(sharePseudoFile != null) - { - filterList.addFile(sharePseudoFile); - } - } - } - } - - return filterList; - // Use the filtered pseudo file list, or null if there were no matches - } - } - else - { - // does not contain wild cards - PseudoFileList filterList = new PseudoFileList(); - PseudoFile file = getPseudoFile(parentDir, fname); - - if(file != null && !isDeleted(parentDir, fname)) - { - filterList.addFile(file); - } - - return filterList; - } - } - - @Override - public void delete(NodeRef parentDir, String name) - { - if(logger.isDebugEnabled()) - { - logger.debug("delete pseudo file parentDir:" + parentDir + ", name: " + name); - } - getDeletePseudoFileCache().put(toDeleteKey(parentDir, name), "Deleted"); - } - - private String toDeleteKey(NodeRef parentNoderef, String name) - { - return (parentNoderef.toString() + "/" + name + ":" + context.getDeviceName()).toLowerCase(); - } - - private boolean isDeleted(NodeRef parentDir, String name) - { - String key = toDeleteKey(parentDir, name); - boolean isDeleted = getDeletePseudoFileCache().contains(key); - if(logger.isDebugEnabled()) - { - logger.debug("pseudoFile isDeleted: " + isDeleted + ", for name:" + name); - } - return isDeleted; - } - - // - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public NodeService getNodeService() - { - return nodeService; - } - - public void setContext(AlfrescoContext context) - { - this.context = context; - } - - public AlfrescoContext getContext() - { - return context; - } - - private final String getAlfrescoURLPrefix() - { - return sysAdminParams.getAlfrescoProtocol() + "://" + sysAdminParams.getAlfrescoHost() + ":" + sysAdminParams.getAlfrescoPort() + "/" + sysAdminParams.getAlfrescoContext() + "/"; - } - private final String getShareURLPrefix() - { - return sysAdminParams.getShareProtocol() + "://" + sysAdminParams.getShareHost() + ":" + sysAdminParams.getSharePort() + "/" + sysAdminParams.getShareContext() + "/"; - } - - public void setSysAdminParams(SysAdminParams sysAdminParams) - { - this.sysAdminParams = sysAdminParams; - } - - public SysAdminParams getSysAdminParams() - { - return sysAdminParams; - } - - public SimpleCache getDeletePseudoFileCache() - { - return deletePseudoFileCache; - } - - public void setDeletePseudoFileCache(SimpleCache deletePseudoFileCache) { - this.deletePseudoFileCache = deletePseudoFileCache; - } -} +package org.alfresco.filesys.alfresco; + +import java.io.Serializable; +import java.util.Enumeration; +import java.util.Map; + +import org.alfresco.filesys.repo.ContentDiskDriver2; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.jlan.server.filesys.pseudo.MemoryPseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; +import org.alfresco.jlan.util.WildCard; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Re-implementation of PseudoFiles for ContentDiskDriver2 + * + * Overlays "desktop actions" + * + * @author mrogers + * */ +public class PseudoFileOverlayImpl implements PseudoFileOverlay +{ + private SysAdminParams sysAdminParams; + private AlfrescoContext context; + private NodeService nodeService; + private SimpleCache deletePseudoFileCache; + + private static final Log logger = LogFactory.getLog(PseudoFileOverlayImpl.class); + + PseudoFileList pl = new PseudoFileList(); + + public void init() + { + PropertyCheck.mandatory(this, "nodeService", getNodeService()); + PropertyCheck.mandatory(this, "context", context); + PropertyCheck.mandatory(this, "sysAdminParams", sysAdminParams); + PropertyCheck.mandatory(this, "deletePseudoFileCache", deletePseudoFileCache); + + DesktopActionTable actions = context.getDesktopActions(); + + if(actions != null) + { + Enumeration actionNames = actions.enumerateActionNames(); + + while(actionNames.hasMoreElements()) + { + // Get the current desktop action + String name = actionNames.nextElement(); + DesktopAction action = actions.getAction(name); + + // Add the pseudo file for the desktop action + + if ( action.hasPseudoFile()) + { + PseudoFile file = action.getPseudoFile(); + pl.addFile(file); + } + } + } + } + + private PseudoFile generateAlfrescoURLShortcut(NodeRef nodeRef) + { + if ( context.isAlfrescoURLEnabled()) + { + // Make sure the state has the associated node details + + // Build the URL file data + + StringBuilder urlStr = new StringBuilder(); + + urlStr.append("[InternetShortcut]\r\n"); + urlStr.append("URL="); + urlStr.append(getAlfrescoURLPrefix()); + urlStr.append("navigate/browse/workspace/SpacesStore/"); + urlStr.append( nodeRef.getId()); + urlStr.append("\r\n"); + + // Create the in memory pseudo file for the URL link + + byte[] urlData = urlStr.toString().getBytes(); + + MemoryPseudoFile urlFile = new MemoryPseudoFile( context.getURLFileName(), urlData); + return urlFile; + } + return null; + } + + /** + * Return the site name if the node ref is in a document library + * Return null if the document is not in a site + */ + // MER URRGH - copied from IMAP service - I don't like it there either! + private String getSiteForNode(NodeRef nodeRef) + { + if(logger.isDebugEnabled()) + { + logger.debug("get site for node:" + nodeRef); + } + boolean isInDocLibrary = false; + + NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef(); + + if(nodeService.getType(parent).equals(SiteModel.TYPE_SITE)) + { + String folderName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + if(folderName.equalsIgnoreCase("documentlibrary")) + { + isInDocLibrary = true; + } + } + else + { + while (parent != null && !nodeService.getType(parent).equals(SiteModel.TYPE_SITE)) + { + String parentName = (String) nodeService.getProperty(parent, ContentModel.PROP_NAME); + if (parentName.equalsIgnoreCase("documentlibrary")) + { + isInDocLibrary = true; + } + + parent = nodeService.getPrimaryParent(parent).getParentRef(); + } + } + + if (parent == null) + { + logger.debug("folder is not in a site"); + return null; + } + else + { + if(isInDocLibrary) + { + if(nodeService.getType(parent).equals(SiteModel.TYPE_SITE)) + { + String siteName = (String)nodeService.getProperty(parent, ContentModel.PROP_NAME); + if(logger.isDebugEnabled()) + { + logger.debug("got a site:" + siteName); + } + return siteName; + } + } + logger.debug("folder is not in doc library"); + + return null; + } + + } + + private PseudoFile generateShareURLShortcut(NodeRef nodeRef) + { + if(logger.isDebugEnabled()) + { + logger.debug("generateShareURLShortcut nodeRef" + nodeRef); + } + if ( context.isShareURLEnabled()) + { + String site = getSiteForNode(nodeRef); + if(site != null) + { + // Make sure the state has the associated node details + // Build the URL file data + + StringBuilder urlStr = new StringBuilder(); + +// This is the URL generated +// http://markr:8080/share/page/site/wibble/folder-details?nodeRef=workspace://SpacesStore/f72b2475-7571-46fe-947b-b0ee1b6a82ea + urlStr.append("[InternetShortcut]\r\n"); + urlStr.append("URL="); + urlStr.append(getShareURLPrefix()); + urlStr.append("page/site/"); + urlStr.append(site + "/folder-details?nodeRef="); + urlStr.append(nodeRef.getStoreRef() + "/"); + urlStr.append( nodeRef.getId()); + urlStr.append("\r\n"); + + // Should this be the URL instead +// http://markr:8080/share/page/site/wibble/documentlibrary#filter=path%7C%2Ffolder%2520A%2FFolderB&page=1 + + // Create the in memory pseudo file for the URL link + if(logger.isDebugEnabled()) + { + logger.debug("generateShareURLShortcut url as string:" + urlStr); + } + byte[] urlData = urlStr.toString().getBytes(); + + MemoryPseudoFile urlFile = new MemoryPseudoFile( context.getShareURLFileName(), urlData); + return urlFile; + } + + + } + return null; + } + + + /** + * + */ + public boolean isPseudoFile(NodeRef parentDir, String name) + { + if ( parentDir == null) + { + return false; + } + + if(context.isAlfrescoURLEnabled()) + { + if(context.getURLFileName().equals(name)) + { + return true; + } + } + + if(context.isShareURLEnabled()) + { + if(context.getShareURLFileName().equals(name)) + { + return true; + } + } + + if(getPseudoFile(parentDir, name) != null) + { + return true; + } + else + { + return false; + } + } + + /** + * Get the pseudo file + * @param parentDir NodeRef + * @param fname String + * @return the pseudoFile or null if there is no pseudo file + */ + public PseudoFile getPseudoFile(NodeRef parentDir, String fname) + { + if ( parentDir == null) + { + return null; + } + if (isDeleted(parentDir, fname)) + { + return null; + } + if(context.isAlfrescoURLEnabled()) + { + if(context.getURLFileName().equals(fname)) + { + if(logger.isDebugEnabled()) + { + logger.debug("returning URL pseudo file"); + } + return generateAlfrescoURLShortcut(parentDir); + } + } + + if(context.isShareURLEnabled()) + { + if(context.getShareURLFileName().equals(fname)) + { + if(logger.isDebugEnabled()) + { + logger.debug("returning Share URL pseudo file"); + } + return generateShareURLShortcut(parentDir); + } + } + + PseudoFile file = pl.findFile(fname, false); + return file; + } + + /** + * + */ + public PseudoFileList searchPseudoFiles(NodeRef parentDir, String name) + { + if(logger.isDebugEnabled()) + { + logger.debug("searchPseudoFile parentDir:" + parentDir +", name:" + name); + } + //return pseudo files matching the path/pattern + + if ( parentDir == null || name == null || name.length() == 0 || name.equals("\\")) + { + return null; + } + + String fname = name; + + if ( fname.equals( "*.*")) + { + fname = "*"; + } + + if ( WildCard.containsWildcards(fname)) + { + // does contain wildcards + + // Check if the wildcard is for all files or a subset + + if ( fname.equals( "*")) + { + // Match all pseudo files + PseudoFileList filterList = new PseudoFileList(); + + // copy desktop actions which do not depend on parentDir + for ( int i = 0; i < pl.numberOfFiles(); i++) + { + PseudoFile pseudoFile = pl.getFileAt(i); + if(!isDeleted(parentDir, pseudoFile.getFileName())) + { + // File is not deleted + filterList.addFile(pseudoFile); + } + } + + // The URL file is dependent upon the parent dir + if(context.isAlfrescoURLEnabled()) + { + if(!isDeleted(parentDir, context.getURLFileName())) + { + filterList.addFile(generateAlfrescoURLShortcut(parentDir)); + } + else + { + if(logger.isDebugEnabled()) + { + logger.debug("alfresco URL pseudo file deleted"); + } + } + } + + if(context.isShareURLEnabled()) + { + if(!isDeleted(parentDir, context.getShareURLFileName())) + { + PseudoFile sharePseudoFile = generateShareURLShortcut(parentDir); + if(sharePseudoFile != null) + { + filterList.addFile(sharePseudoFile); + } + } + } + + return filterList; + } + else + { + // Generate a subset of pseudo files that match the wildcard search pattern + + WildCard wildCard = new WildCard( fname, false); + PseudoFileList filterList = new PseudoFileList(); + + for ( int i = 0; i < pl.numberOfFiles(); i++) + { + PseudoFile pseudoFile = pl.getFileAt( i); + if ( wildCard.matchesPattern( pseudoFile.getFileName())) + { + if(!isDeleted(parentDir, pseudoFile.getFileName())) + { + // Add the pseudo file to the filtered list + filterList.addFile( pseudoFile); + } + } + } + + // The URL file is dependent upon the parent dir + if(context.isAlfrescoURLEnabled()) + { + if(wildCard.matchesPattern(context.getURLFileName())) + { + if(!isDeleted(parentDir, context.getURLFileName())) + { + filterList.addFile(generateAlfrescoURLShortcut(parentDir)); + } + } + } + + if(context.isShareURLEnabled()) + { + if(wildCard.matchesPattern(context.getShareURLFileName())) + { + if(!isDeleted(parentDir, context.getShareURLFileName())) + { + PseudoFile sharePseudoFile = generateShareURLShortcut(parentDir); + + if(sharePseudoFile != null) + { + filterList.addFile(sharePseudoFile); + } + } + } + } + + return filterList; + // Use the filtered pseudo file list, or null if there were no matches + } + } + else + { + // does not contain wild cards + PseudoFileList filterList = new PseudoFileList(); + PseudoFile file = getPseudoFile(parentDir, fname); + + if(file != null && !isDeleted(parentDir, fname)) + { + filterList.addFile(file); + } + + return filterList; + } + } + + @Override + public void delete(NodeRef parentDir, String name) + { + if(logger.isDebugEnabled()) + { + logger.debug("delete pseudo file parentDir:" + parentDir + ", name: " + name); + } + getDeletePseudoFileCache().put(toDeleteKey(parentDir, name), "Deleted"); + } + + private String toDeleteKey(NodeRef parentNoderef, String name) + { + return (parentNoderef.toString() + "/" + name + ":" + context.getDeviceName()).toLowerCase(); + } + + private boolean isDeleted(NodeRef parentDir, String name) + { + String key = toDeleteKey(parentDir, name); + boolean isDeleted = getDeletePseudoFileCache().contains(key); + if(logger.isDebugEnabled()) + { + logger.debug("pseudoFile isDeleted: " + isDeleted + ", for name:" + name); + } + return isDeleted; + } + + // + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public NodeService getNodeService() + { + return nodeService; + } + + public void setContext(AlfrescoContext context) + { + this.context = context; + } + + public AlfrescoContext getContext() + { + return context; + } + + private final String getAlfrescoURLPrefix() + { + return sysAdminParams.getAlfrescoProtocol() + "://" + sysAdminParams.getAlfrescoHost() + ":" + sysAdminParams.getAlfrescoPort() + "/" + sysAdminParams.getAlfrescoContext() + "/"; + } + private final String getShareURLPrefix() + { + return sysAdminParams.getShareProtocol() + "://" + sysAdminParams.getShareHost() + ":" + sysAdminParams.getSharePort() + "/" + sysAdminParams.getShareContext() + "/"; + } + + public void setSysAdminParams(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; + } + + public SysAdminParams getSysAdminParams() + { + return sysAdminParams; + } + + public SimpleCache getDeletePseudoFileCache() + { + return deletePseudoFileCache; + } + + public void setDeletePseudoFileCache(SimpleCache deletePseudoFileCache) { + this.deletePseudoFileCache = deletePseudoFileCache; + } +} diff --git a/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java b/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java index 3db0b67653..05eeebfe75 100644 --- a/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java +++ b/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java @@ -1,125 +1,125 @@ -package org.alfresco.filesys.alfresco; - -import java.io.FileNotFoundException; -import java.io.IOException; - -import org.alfresco.filesys.repo.OpenFileMode; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Extra methods for DiskInterface, primarily implemented to support CIFS shuffles. - */ -public interface RepositoryDiskInterface -{ - /** - * Copy the content from one node to another. - * - * @param rootNode NodeRef - * @param fromPath - the source node - * @param toPath - the target node - * @throws FileNotFoundException - */ - public void copyContent(NodeRef rootNode, String fromPath, String toPath) throws FileNotFoundException; - - - /** - * CreateFile. - * - * @param rootNode NodeRef - * @param Path - path - * @param allocationSize size to allocate for new file - * @param isHidden boolean - * @throws FileNotFoundException - */ - public NetworkFile createFile(NodeRef rootNode, String Path, long allocationSize, boolean isHidden) throws IOException; - - /** - * RestoreFile. - * - * Either restores the file or creates a new one. - * - * @param sess SrvSession - * @param tree TreeConnection - * @param rootNode NodeRef - * @param path - path - * @param allocationSize size to allocate for new file - * @param originalNodeRef NodeRef - * @throws FileNotFoundException - */ - public NetworkFile restoreFile(SrvSession sess, - TreeConnection tree, - NodeRef rootNode, - String path, - long allocationSize, - NodeRef originalNodeRef) throws IOException; - - - /** - * - * @param session // temp until refactor - * @param tree // temp until refactor - * @param rootNode NodeRef - * @param path String - * @param mode OpenFileMode - * @param truncate boolean - * @return NetworkFile - */ - public NetworkFile openFile(SrvSession session, TreeConnection tree, NodeRef rootNode, String path, OpenFileMode mode, boolean truncate) throws IOException; - - /** - * CloseFile. - * - * @param tree TreeConnection - * @param rootNode NodeRef - * @param Path - path - * @param file - file - * @throws FileNotFoundException - * @return node ref of deleted file or null if no file deleted - */ - public NodeRef closeFile(TreeConnection tree, NodeRef rootNode, String Path, NetworkFile file) throws IOException; - - - /** - * Delete file - * @param session SrvSession - * @param tree TreeConnection - * @param rootNode NodeRef - * @param path String - * @return NodeRef of file deleted or null if no file deleted - * @throws IOException - */ - public NodeRef deleteFile2(final SrvSession session, final TreeConnection tree, NodeRef rootNode, String path) throws IOException; - - /** - * - * @param session SrvSession - * @param tree TreeConnection - * @param file NetworkFile - */ - public void reduceQuota(SrvSession session, TreeConnection tree, NetworkFile file); - - /** - * - * @param rootNode NodeRef - * @param path String - */ - public void deleteEmptyFile(NodeRef rootNode, String path); - - /** - * Rename the specified file. - * - * @param rootNode root node - * @param oldName java.lang.String - * @param newName java.lang.String - * @param soft boolean - * @param moveAsSystem move as system - * @exception java.io.IOException The exception description. - */ - public void renameFile(NodeRef rootNode, String oldName, String newName, boolean soft, boolean moveAsSystem) - throws java.io.IOException; - - -} +package org.alfresco.filesys.alfresco; + +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.alfresco.filesys.repo.OpenFileMode; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Extra methods for DiskInterface, primarily implemented to support CIFS shuffles. + */ +public interface RepositoryDiskInterface +{ + /** + * Copy the content from one node to another. + * + * @param rootNode NodeRef + * @param fromPath - the source node + * @param toPath - the target node + * @throws FileNotFoundException + */ + public void copyContent(NodeRef rootNode, String fromPath, String toPath) throws FileNotFoundException; + + + /** + * CreateFile. + * + * @param rootNode NodeRef + * @param Path - path + * @param allocationSize size to allocate for new file + * @param isHidden boolean + * @throws FileNotFoundException + */ + public NetworkFile createFile(NodeRef rootNode, String Path, long allocationSize, boolean isHidden) throws IOException; + + /** + * RestoreFile. + * + * Either restores the file or creates a new one. + * + * @param sess SrvSession + * @param tree TreeConnection + * @param rootNode NodeRef + * @param path - path + * @param allocationSize size to allocate for new file + * @param originalNodeRef NodeRef + * @throws FileNotFoundException + */ + public NetworkFile restoreFile(SrvSession sess, + TreeConnection tree, + NodeRef rootNode, + String path, + long allocationSize, + NodeRef originalNodeRef) throws IOException; + + + /** + * + * @param session // temp until refactor + * @param tree // temp until refactor + * @param rootNode NodeRef + * @param path String + * @param mode OpenFileMode + * @param truncate boolean + * @return NetworkFile + */ + public NetworkFile openFile(SrvSession session, TreeConnection tree, NodeRef rootNode, String path, OpenFileMode mode, boolean truncate) throws IOException; + + /** + * CloseFile. + * + * @param tree TreeConnection + * @param rootNode NodeRef + * @param Path - path + * @param file - file + * @throws FileNotFoundException + * @return node ref of deleted file or null if no file deleted + */ + public NodeRef closeFile(TreeConnection tree, NodeRef rootNode, String Path, NetworkFile file) throws IOException; + + + /** + * Delete file + * @param session SrvSession + * @param tree TreeConnection + * @param rootNode NodeRef + * @param path String + * @return NodeRef of file deleted or null if no file deleted + * @throws IOException + */ + public NodeRef deleteFile2(final SrvSession session, final TreeConnection tree, NodeRef rootNode, String path) throws IOException; + + /** + * + * @param session SrvSession + * @param tree TreeConnection + * @param file NetworkFile + */ + public void reduceQuota(SrvSession session, TreeConnection tree, NetworkFile file); + + /** + * + * @param rootNode NodeRef + * @param path String + */ + public void deleteEmptyFile(NodeRef rootNode, String path); + + /** + * Rename the specified file. + * + * @param rootNode root node + * @param oldName java.lang.String + * @param newName java.lang.String + * @param soft boolean + * @param moveAsSystem move as system + * @exception java.io.IOException The exception description. + */ + public void renameFile(NodeRef rootNode, String oldName, String newName, boolean soft, boolean moveAsSystem) + throws java.io.IOException; + + +} diff --git a/source/java/org/alfresco/filesys/alfresco/ShuffleCache.java b/source/java/org/alfresco/filesys/alfresco/ShuffleCache.java index 682db064ce..760e02d2ee 100644 --- a/source/java/org/alfresco/filesys/alfresco/ShuffleCache.java +++ b/source/java/org/alfresco/filesys/alfresco/ShuffleCache.java @@ -1,59 +1,59 @@ -package org.alfresco.filesys.alfresco; - -/** - * Cache for alfresco "save shuffles" which are used by some applications - * to compensate for a most computer filesystem being non atomic. - * - *

- * Overlays an Alfresco repository with temporary files being created and - * soft deleted from folders that are likely to have save shuffles going on. - *

- * Implementations must be thread safe - */ -public interface ShuffleCache -{ - - /** - * Add a new temporary file to the "shuffle cache". Content is not persisted - * in the alfresco repo until either a rename occurs or after a time delay. - */ - public void createTemporaryFile(String path); - - /** - * Soft delete a file. The file may be re-instated later or the delete made - * permenant after a time delay. - */ - public void softDelete(String path); - - /** - * Takes the contents of a temporary file and applies it to the new path. - *

- * If the new path has been soft deleted then the soft delete is removed. - *

- * After the contents of the temporary file have been written the it may may be made - * available for garbage collection. - * - * @param oldPath the location of the temporaryFile - * @param newPath the location of the new file. - */ - public void renameTemporaryFile(String oldPath, String newPath); - - /** - * Does the specified directory contain a shuffled temporary file - * @param dir String - * @return boolean - */ - boolean isShuffleDirectory(String dir); - - /** - * Has the path been "soft deleted" - */ - boolean isDeleted(String path); - - /** - * Has the path been "soft created" - * @param path String - * @return boolean - */ - boolean isCreated(String path); -} +package org.alfresco.filesys.alfresco; + +/** + * Cache for alfresco "save shuffles" which are used by some applications + * to compensate for a most computer filesystem being non atomic. + * + *

+ * Overlays an Alfresco repository with temporary files being created and + * soft deleted from folders that are likely to have save shuffles going on. + *

+ * Implementations must be thread safe + */ +public interface ShuffleCache +{ + + /** + * Add a new temporary file to the "shuffle cache". Content is not persisted + * in the alfresco repo until either a rename occurs or after a time delay. + */ + public void createTemporaryFile(String path); + + /** + * Soft delete a file. The file may be re-instated later or the delete made + * permenant after a time delay. + */ + public void softDelete(String path); + + /** + * Takes the contents of a temporary file and applies it to the new path. + *

+ * If the new path has been soft deleted then the soft delete is removed. + *

+ * After the contents of the temporary file have been written the it may may be made + * available for garbage collection. + * + * @param oldPath the location of the temporaryFile + * @param newPath the location of the new file. + */ + public void renameTemporaryFile(String oldPath, String newPath); + + /** + * Does the specified directory contain a shuffled temporary file + * @param dir String + * @return boolean + */ + boolean isShuffleDirectory(String dir); + + /** + * Has the path been "soft deleted" + */ + boolean isDeleted(String path); + + /** + * Has the path been "soft created" + * @param path String + * @return boolean + */ + boolean isCreated(String path); +} diff --git a/source/java/org/alfresco/filesys/alfresco/ShuffleCacheImpl.java b/source/java/org/alfresco/filesys/alfresco/ShuffleCacheImpl.java index 6b1a20c320..3c4e208496 100644 --- a/source/java/org/alfresco/filesys/alfresco/ShuffleCacheImpl.java +++ b/source/java/org/alfresco/filesys/alfresco/ShuffleCacheImpl.java @@ -1,101 +1,101 @@ -package org.alfresco.filesys.alfresco; - -import java.util.Map; - -/** - * Cache for alfresco "CIFS shuffles" - * - * - */ -public class ShuffleCacheImpl implements ShuffleCache -{ - /** - * time in ms that temporary files should live in the cache before - * being persisted. - */ - private long timeBeforePersist = 5 * 60000L; // 5 minutes default - - /** - * Is the cache caseSensitive? - */ - private boolean caseSensitive; - - /** - * The shuffle folder cache keyed by path. - * - */ - private Map folderCache; - - - /** - * The information held for each folder that has a "shuffle" - * in progress. - * @author mrogers - */ - private class ShuffleFolderInfo - { - - } - - @Override - public void createTemporaryFile(String path) - { - // TODO Auto-generated method stub - - } - - @Override - public void softDelete(String path) - { - // TODO Auto-generated method stub - - } - - @Override - public void renameTemporaryFile(String oldPath, String newPath) - { - // TODO Auto-generated method stub - - } - - @Override - public boolean isShuffleDirectory(String dir) - { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean isDeleted(String path) - { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean isCreated(String path) - { - // TODO Auto-generated method stub - return false; - } - - void setTimeBeforePersist(long timeBeforePersist) - { - this.timeBeforePersist = timeBeforePersist; - } - - long getTimeBeforePersist() - { - return timeBeforePersist; - } - - public void setCaseSensitive(boolean caseSensitive) - { - this.caseSensitive = caseSensitive; - } - - public boolean isCaseSensitive() - { - return caseSensitive; - } -} +package org.alfresco.filesys.alfresco; + +import java.util.Map; + +/** + * Cache for alfresco "CIFS shuffles" + * + * + */ +public class ShuffleCacheImpl implements ShuffleCache +{ + /** + * time in ms that temporary files should live in the cache before + * being persisted. + */ + private long timeBeforePersist = 5 * 60000L; // 5 minutes default + + /** + * Is the cache caseSensitive? + */ + private boolean caseSensitive; + + /** + * The shuffle folder cache keyed by path. + * + */ + private Map folderCache; + + + /** + * The information held for each folder that has a "shuffle" + * in progress. + * @author mrogers + */ + private class ShuffleFolderInfo + { + + } + + @Override + public void createTemporaryFile(String path) + { + // TODO Auto-generated method stub + + } + + @Override + public void softDelete(String path) + { + // TODO Auto-generated method stub + + } + + @Override + public void renameTemporaryFile(String oldPath, String newPath) + { + // TODO Auto-generated method stub + + } + + @Override + public boolean isShuffleDirectory(String dir) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isDeleted(String path) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isCreated(String path) + { + // TODO Auto-generated method stub + return false; + } + + void setTimeBeforePersist(long timeBeforePersist) + { + this.timeBeforePersist = timeBeforePersist; + } + + long getTimeBeforePersist() + { + return timeBeforePersist; + } + + public void setCaseSensitive(boolean caseSensitive) + { + this.caseSensitive = caseSensitive; + } + + public boolean isCaseSensitive() + { + return caseSensitive; + } +} diff --git a/source/java/org/alfresco/filesys/alfresco/package-info.java b/source/java/org/alfresco/filesys/alfresco/package-info.java index 52a9b7b3c1..8129527030 100644 --- a/source/java/org/alfresco/filesys/alfresco/package-info.java +++ b/source/java/org/alfresco/filesys/alfresco/package-info.java @@ -1,15 +1,15 @@ -/** - * FileSystem - * - * DesktopAction - * - * AlfrescoDiskDriver - * - * MultiTenantShareMapper - * - * - */ -@PackageMarker -package org.alfresco.filesys.alfresco; -import org.alfresco.util.PackageMarker; - +/** + * FileSystem + * + * DesktopAction + * + * AlfrescoDiskDriver + * + * MultiTenantShareMapper + * + * + */ +@PackageMarker +package org.alfresco.filesys.alfresco; +import org.alfresco.util.PackageMarker; + diff --git a/source/java/org/alfresco/filesys/auth/cifs/package-info.java b/source/java/org/alfresco/filesys/auth/cifs/package-info.java index e73873bd5f..65aef5a8ea 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/package-info.java +++ b/source/java/org/alfresco/filesys/auth/cifs/package-info.java @@ -1,14 +1,14 @@ -/** - * Provides authentication implementations for CIFS - *

- * AlfrescoCifsAuthenticator - * EnterpriseCifsAuthenticator deals with Kerberos, NTLMv1 and NTLMv2 - * PassthruCifsAuthenticator deals with authenticating against an external system - * - *

- * CifsAuthenticatorBase abstract base class. - */ -@PackageMarker -package org.alfresco.filesys.auth.cifs; -import org.alfresco.util.PackageMarker; - +/** + * Provides authentication implementations for CIFS + *

+ * AlfrescoCifsAuthenticator + * EnterpriseCifsAuthenticator deals with Kerberos, NTLMv1 and NTLMv2 + * PassthruCifsAuthenticator deals with authenticating against an external system + * + *

+ * CifsAuthenticatorBase abstract base class. + */ +@PackageMarker +package org.alfresco.filesys.auth.cifs; +import org.alfresco.util.PackageMarker; + diff --git a/source/java/org/alfresco/filesys/auth/ftp/package-info.java b/source/java/org/alfresco/filesys/auth/ftp/package-info.java index 58588652b3..13af20ff00 100644 --- a/source/java/org/alfresco/filesys/auth/ftp/package-info.java +++ b/source/java/org/alfresco/filesys/auth/ftp/package-info.java @@ -1,5 +1,5 @@ -/** - */ -@PackageMarker -package org.alfresco.filesys.auth.ftp; -import org.alfresco.util.PackageMarker; +/** + */ +@PackageMarker +package org.alfresco.filesys.auth.ftp; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/filesys/auth/nfs/package-info.java b/source/java/org/alfresco/filesys/auth/nfs/package-info.java index 069f711037..36114895ba 100644 --- a/source/java/org/alfresco/filesys/auth/nfs/package-info.java +++ b/source/java/org/alfresco/filesys/auth/nfs/package-info.java @@ -1,5 +1,5 @@ -/** - */ -@PackageMarker -package org.alfresco.filesys.auth.nfs; -import org.alfresco.util.PackageMarker; +/** + */ +@PackageMarker +package org.alfresco.filesys.auth.nfs; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/filesys/auth/package-info.java b/source/java/org/alfresco/filesys/auth/package-info.java index 4bb679248a..0342d787cb 100644 --- a/source/java/org/alfresco/filesys/auth/package-info.java +++ b/source/java/org/alfresco/filesys/auth/package-info.java @@ -1,5 +1,5 @@ -/** - */ -@PackageMarker -package org.alfresco.filesys.auth; -import org.alfresco.util.PackageMarker; +/** + */ +@PackageMarker +package org.alfresco.filesys.auth; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/filesys/config/ClusterConfigBean.java b/source/java/org/alfresco/filesys/config/ClusterConfigBean.java index b5855e093c..395989e492 100644 --- a/source/java/org/alfresco/filesys/config/ClusterConfigBean.java +++ b/source/java/org/alfresco/filesys/config/ClusterConfigBean.java @@ -1,45 +1,45 @@ -package org.alfresco.filesys.config; - -/** - * The Class ClusterConfigBean. - * - * @author mrogers - * @since 4.0 - */ -public class ClusterConfigBean -{ - private String debugFlags; - private int nearCacheTimeout; - - public boolean getClusterEnabled() - { - // No clustering support in community edition. - return false; - } - - public String getClusterName() - { - // No clustering support in community edition. - return null; - } - - public void setDebugFlags(String debugFlags) - { - this.debugFlags = debugFlags; - } - - public String getDebugFlags() - { - return debugFlags; - } - - public void setNearCacheTimeout(int nearCacheTimeout) - { - this.nearCacheTimeout = nearCacheTimeout; - } - - public int getNearCacheTimeout() - { - return nearCacheTimeout; - } -} +package org.alfresco.filesys.config; + +/** + * The Class ClusterConfigBean. + * + * @author mrogers + * @since 4.0 + */ +public class ClusterConfigBean +{ + private String debugFlags; + private int nearCacheTimeout; + + public boolean getClusterEnabled() + { + // No clustering support in community edition. + return false; + } + + public String getClusterName() + { + // No clustering support in community edition. + return null; + } + + public void setDebugFlags(String debugFlags) + { + this.debugFlags = debugFlags; + } + + public String getDebugFlags() + { + return debugFlags; + } + + public void setNearCacheTimeout(int nearCacheTimeout) + { + this.nearCacheTimeout = nearCacheTimeout; + } + + public int getNearCacheTimeout() + { + return nearCacheTimeout; + } +} diff --git a/source/java/org/alfresco/filesys/config/acl/package-info.java b/source/java/org/alfresco/filesys/config/acl/package-info.java index 1b217aeb0b..2dc9da8ca5 100644 --- a/source/java/org/alfresco/filesys/config/acl/package-info.java +++ b/source/java/org/alfresco/filesys/config/acl/package-info.java @@ -1,5 +1,5 @@ -/** - */ -@PackageMarker -package org.alfresco.filesys.config.acl; -import org.alfresco.util.PackageMarker; +/** + */ +@PackageMarker +package org.alfresco.filesys.config.acl; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/filesys/config/package-info.java b/source/java/org/alfresco/filesys/config/package-info.java index 79209a0e0c..9f811753b5 100644 --- a/source/java/org/alfresco/filesys/config/package-info.java +++ b/source/java/org/alfresco/filesys/config/package-info.java @@ -1,5 +1,5 @@ -/** - */ -@PackageMarker -package org.alfresco.filesys.config; -import org.alfresco.util.PackageMarker; +/** + */ +@PackageMarker +package org.alfresco.filesys.config; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/filesys/debug/package-info.java b/source/java/org/alfresco/filesys/debug/package-info.java index acd114817f..0e4de07f10 100644 --- a/source/java/org/alfresco/filesys/debug/package-info.java +++ b/source/java/org/alfresco/filesys/debug/package-info.java @@ -1,5 +1,5 @@ -/** - */ -@PackageMarker -package org.alfresco.filesys.debug; -import org.alfresco.util.PackageMarker; +/** + */ +@PackageMarker +package org.alfresco.filesys.debug; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/filesys/package-info.java b/source/java/org/alfresco/filesys/package-info.java index ef434ad620..427681d4b5 100644 --- a/source/java/org/alfresco/filesys/package-info.java +++ b/source/java/org/alfresco/filesys/package-info.java @@ -1,6 +1,6 @@ -/** - * The Alfresco file system interface implementation - */ -@PackageMarker -package org.alfresco.filesys; -import org.alfresco.util.PackageMarker; +/** + * The Alfresco file system interface implementation + */ +@PackageMarker +package org.alfresco.filesys; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/filesys/repo/AlfrescoFolder.java b/source/java/org/alfresco/filesys/repo/AlfrescoFolder.java index 8fac5b0fa8..2225151c78 100644 --- a/source/java/org/alfresco/filesys/repo/AlfrescoFolder.java +++ b/source/java/org/alfresco/filesys/repo/AlfrescoFolder.java @@ -1,93 +1,93 @@ -package org.alfresco.filesys.repo; - -import java.io.IOException; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.jlan.server.filesys.cache.FileState; -import org.alfresco.jlan.server.filesys.cache.NetworkFileStateInterface; - -/** - * Object returned to JLAN if the repository object is a folder. - */ -public class AlfrescoFolder extends NetworkFile implements NetworkFileStateInterface -{ - public AlfrescoFolder(String path, FileInfo fileInfo, boolean readOnly) - { - super(path); - setFullName(path); - - // Set the file timestamps - - if ( fileInfo.hasCreationDateTime()) - setCreationDate( fileInfo.getCreationDateTime()); - - if ( fileInfo.hasModifyDateTime()) - setModifyDate(fileInfo.getModifyDateTime()); - - if ( fileInfo.hasAccessDateTime()) - setAccessDate(fileInfo.getAccessDateTime()); - - // Set the file attributes - setAttributes(fileInfo.getFileAttributes()); - } - - @Override - public void openFile(boolean createFlag) throws IOException - { - throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); - } - - @Override - public int readFile(byte[] buf, int len, int pos, long fileOff) - throws IOException - { - throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); - } - - @Override - public void writeFile(byte[] buf, int len, int pos, long fileOff) - throws IOException - { - throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); - } - - @Override - public long seekFile(long pos, int typ) throws IOException - { - return 0; - } - - @Override - public void flushFile() throws IOException - { - // Do nothing. - } - - @Override - public void truncateFile(long siz) throws IOException - { - throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); - } - - @Override - public void closeFile() throws IOException - { - setClosed(true); - } - - // For JLAN file state lock manager - public void setFileState(FileState fileState) - { - this.fileState = fileState; - } - - @Override - public FileState getFileState() - { - return fileState; - - } - private FileState fileState; -} +package org.alfresco.filesys.repo; + +import java.io.IOException; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.NetworkFileStateInterface; + +/** + * Object returned to JLAN if the repository object is a folder. + */ +public class AlfrescoFolder extends NetworkFile implements NetworkFileStateInterface +{ + public AlfrescoFolder(String path, FileInfo fileInfo, boolean readOnly) + { + super(path); + setFullName(path); + + // Set the file timestamps + + if ( fileInfo.hasCreationDateTime()) + setCreationDate( fileInfo.getCreationDateTime()); + + if ( fileInfo.hasModifyDateTime()) + setModifyDate(fileInfo.getModifyDateTime()); + + if ( fileInfo.hasAccessDateTime()) + setAccessDate(fileInfo.getAccessDateTime()); + + // Set the file attributes + setAttributes(fileInfo.getFileAttributes()); + } + + @Override + public void openFile(boolean createFlag) throws IOException + { + throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); + } + + @Override + public int readFile(byte[] buf, int len, int pos, long fileOff) + throws IOException + { + throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); + } + + @Override + public void writeFile(byte[] buf, int len, int pos, long fileOff) + throws IOException + { + throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); + } + + @Override + public long seekFile(long pos, int typ) throws IOException + { + return 0; + } + + @Override + public void flushFile() throws IOException + { + // Do nothing. + } + + @Override + public void truncateFile(long siz) throws IOException + { + throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); + } + + @Override + public void closeFile() throws IOException + { + setClosed(true); + } + + // For JLAN file state lock manager + public void setFileState(FileState fileState) + { + this.fileState = fileState; + } + + @Override + public FileState getFileState() + { + return fileState; + + } + private FileState fileState; +} diff --git a/source/java/org/alfresco/filesys/repo/BufferedContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/BufferedContentDiskDriver.java index eb4732d825..cd216b110a 100644 --- a/source/java/org/alfresco/filesys/repo/BufferedContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/BufferedContentDiskDriver.java @@ -1,650 +1,650 @@ -package org.alfresco.filesys.repo; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.Serializable; -import java.util.Date; - -import org.alfresco.filesys.config.ServerConfigurationBean; -import org.alfresco.filesys.alfresco.ExtendedDiskInterface; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.core.DeviceContext; -import org.alfresco.jlan.server.core.DeviceContextException; -import org.alfresco.jlan.server.core.SharedDevice; -import org.alfresco.jlan.server.filesys.DiskDeviceContext; -import org.alfresco.jlan.server.filesys.DiskInterface; -import org.alfresco.jlan.server.filesys.DiskSizeInterface; -import org.alfresco.jlan.server.filesys.FileAccessToken; -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.FileOpenParams; -import org.alfresco.jlan.server.filesys.FileStatus; -import org.alfresco.jlan.server.filesys.IOControlNotImplementedException; -import org.alfresco.jlan.server.filesys.IOCtlInterface; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.jlan.server.filesys.SearchContext; -import org.alfresco.jlan.server.filesys.SrvDiskInfo; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.jlan.server.filesys.cache.FileState; -import org.alfresco.jlan.server.filesys.cache.FileStateCache; -import org.alfresco.jlan.server.locking.FileLockingInterface; -import org.alfresco.jlan.server.locking.LockManager; -import org.alfresco.jlan.server.locking.OpLockInterface; -import org.alfresco.jlan.server.locking.OpLockManager; -import org.alfresco.jlan.smb.SMBException; -import org.alfresco.jlan.util.DataBuffer; -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.node.NodeServicePolicies; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.config.ConfigElement; - -/** - * Alfresco Content Disk Driver Cache - *

- * Decorates ContentDiskDriver with a performance cache of some frequently used - * results. In particular for getFileInformation and fileExists - */ -/* - * MER - this class is also acting as a proxy to gather together the different interfaces - * and present them to JLAN. This was not the intention and is a short term hack. It - * should be possible to un-spring the buffering, however that's not possible at the moment. - */ -public class BufferedContentDiskDriver implements ExtendedDiskInterface, - DiskInterface, - DiskSizeInterface, - IOCtlInterface, - OpLockInterface, - FileLockingInterface, - NodeServicePolicies.OnDeleteNodePolicy, - NodeServicePolicies.OnMoveNodePolicy -{ - // Logging - private static final Log logger = LogFactory.getLog(BufferedContentDiskDriver.class); - - private ExtendedDiskInterface diskInterface; - - private DiskSizeInterface diskSizeInterface; - - private IOCtlInterface ioctlInterface; - - private OpLockInterface opLockInterface; - - private FileLockingInterface fileLockingInterface; - - private PolicyComponent policyComponent; - - public void init() - { - PropertyCheck.mandatory(this, "diskInterface", diskInterface); - PropertyCheck.mandatory(this, "diskSizeInterface", diskSizeInterface); - PropertyCheck.mandatory(this, "ioctltInterface", ioctlInterface); - PropertyCheck.mandatory(this, "fileInfoCache", fileInfoCache); - PropertyCheck.mandatory(this, "fileLockingInterface", getFileLockingInterface()); - PropertyCheck.mandatory(this, "opLockInterface", getOpLockInterface()); - PropertyCheck.mandatory(this, "fileLockingInterface", fileLockingInterface); - PropertyCheck.mandatory(this, "policyComponent", getPolicyComponent()); - - getPolicyComponent().bindClassBehaviour( NodeServicePolicies.OnDeleteNodePolicy.QNAME, - this, new JavaBehaviour(this, "onDeleteNode")); - getPolicyComponent().bindClassBehaviour( NodeServicePolicies.OnMoveNodePolicy.QNAME, - this, new JavaBehaviour(this, "onMoveNode")); - } - - /** - * FileInfo Cache for path to FileInfo - */ - private SimpleCache fileInfoCache; - - /** - * Set the cache that maintains node ID-NodeRef cross referencing data - * - * @param cache the cache - */ - public void setFileInfoCache(SimpleCache cache) - { - this.fileInfoCache = cache; - } - - private static class FileInfoKey implements Serializable - { - /** - * - */ - private static final long serialVersionUID = 1L; - - String deviceName; - String path; - String user; - int hashCode; - - public FileInfoKey(SrvSession sess, String path, TreeConnection tree) - { - this.path = path; - this.user = sess.getUniqueId(); - this.deviceName = tree.getSharedDevice().getName(); - -// if(deviceName == null) -// { -// throw new RuntimeException("device name is null"); -// } -// if(path == null) -// { -// throw new RuntimeException("path is null"); -// } -// if(user == null) -// { -// throw new RuntimeException("unique id is null"); -// } - } - - @Override - public boolean equals(Object other) - { - if (this == other) - { - return true; - } - if (other == null || !(other instanceof FileInfoKey)) - { - return false; - } - - FileInfoKey o = (FileInfoKey)other; - - return path.equals(o.path) && user.equals(o.user) && deviceName.equals(o.deviceName); - } - - @Override - public int hashCode() - { - if(hashCode == 0) - { - hashCode = (user+path+deviceName).hashCode(); - } - return hashCode; - } - } - - private FileInfo getFileInformationInternal(SrvSession sess, TreeConnection tree, - String path) throws IOException - { - - //String userName = AuthenticationUtil.getFullyAuthenticatedUser(); - SharedDevice device = tree.getSharedDevice(); - String deviceName = device.getName(); - - if(logger.isDebugEnabled()) - { - logger.debug("getFileInformation session:" + sess.getUniqueId() + ", deviceName:" + deviceName + ", path:" + path); - } - - if(path == null) - { - throw new IllegalArgumentException("Path is null"); - } - - FileInfoKey key = new FileInfoKey(sess, path, tree); - - FileInfo fromCache = fileInfoCache.get(key); - - if(fromCache != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("returning FileInfo from cache"); - } - return fromCache; - } - - FileInfo info = diskInterface.getFileInformation(sess, tree, path); - - if(info != null) - { - /** - * Don't cache directories since the modification date is important. - */ - if(!info.isDirectory()) - { - fileInfoCache.put(key, info); - } - } - - /* - * Dual Key the cache so it can be looked up by NodeRef or Path - */ - if(info instanceof ContentFileInfo) - { - ContentFileInfo cinfo = (ContentFileInfo)info; - fileInfoCache.put(cinfo.getNodeRef(), info); - } - - return info; - } - - - @Override - public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, - String path) throws IOException - { - ContentContext tctx = (ContentContext) tree.getContext(); - - FileInfo info = getFileInformationInternal(sess, tree, path); - - /* - * Some information is not maintained by the repo and represents an in-progress update. - * For example as a file is being written the modification and access dates change. - */ - if(tctx.hasStateCache()) - { - FileStateCache cache = tctx.getStateCache(); - FileState fstate = cache.findFileState(path, false); - if(fstate != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("state cache available - overwriting from state cache: isDirectory=" +info.isDirectory()); - } - FileInfo finfo = new FileInfo(); - finfo.copyFrom(info); - - /** - * File state is probably stale for directories which is why we don't attempt to - * cache. - */ - if(!info.isDirectory()) - { - /* - * What about stale file state values here? - */ - if(fstate.hasFileSize()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace file size " + info.getSize() + " with " + fstate.getFileSize()); - } - finfo.setFileSize(fstate.getFileSize()); - } - if ( fstate.hasAccessDateTime()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace access date " + new Date(finfo.getAccessDateTime()) + " with " + new Date(fstate.getAccessDateTime())); - } - finfo.setAccessDateTime(fstate.getAccessDateTime()); - } - if ( fstate.hasChangeDateTime()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace change date " + new Date(finfo.getChangeDateTime()) + " with " + new Date(fstate.getChangeDateTime())); - } - finfo.setChangeDateTime(fstate.getChangeDateTime()); - } - if ( fstate.hasModifyDateTime()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace modified date " + new Date(finfo.getModifyDateTime()) + " with " + new Date(fstate.getModifyDateTime())); - } - finfo.setModifyDateTime(fstate.getModifyDateTime()); - } - if ( fstate.hasAllocationSize()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace allocation size" + finfo.getAllocationSize() + " with " + fstate.getAllocationSize()); - } - finfo.setAllocationSize(fstate.getAllocationSize()); - } - } - - if(logger.isDebugEnabled()) - { - logger.debug("Return getFileInformation, path: " + path + - ", returning:" + finfo + - ", readOnly:" +finfo.isReadOnly() + - ", fileId:" +finfo.getFileId() + - ", fileSize:" +finfo.getSize() + - ", directoryId:" + finfo.getDirectoryId() + - ", createdDate: " + new Date(finfo.getCreationDateTime()) + - ", accessDate:" + new Date(finfo.getAccessDateTime()) + - ", modifiedDate:" + new Date(finfo.getModifyDateTime()) + - ", changeDate:" + new Date(finfo.getChangeDateTime()) + - ", fileAttributes: 0x"+ Integer.toHexString(info.getFileAttributes()) + - ", mode: 0x" + Integer.toHexString(finfo.getMode())); - } - - return finfo; - } - } - - if(logger.isDebugEnabled()) - { - logger.debug("getFileInformation Return:" + path + " returning" + info); - } - - return info; - - } - - @Override - public int fileExists(SrvSession sess, TreeConnection tree, String path) - { - String deviceName = tree.getSharedDevice().getName(); - - if(logger.isDebugEnabled()) - { - logger.debug("fileExists session:" + sess.getUniqueId() + ", deviceName" + deviceName + ", path:" + path); - } - - FileInfoKey key = new FileInfoKey(sess, path, tree); - - FileInfo fromCache = fileInfoCache.get(key); - - if(fromCache != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("fileExists found FileInfo in cache"); - } - if (fromCache.isDirectory()) - { - return FileStatus.DirectoryExists; - } - else - { - return FileStatus.FileExists; - } - } - else - { - try - { - FileInfo lookup = getFileInformationInternal(sess, tree, path); - - if(logger.isDebugEnabled()) - { - logger.debug("fileExists obtained file information"); - } - if (lookup.isDirectory()) - { - return FileStatus.DirectoryExists; - } - else - { - return FileStatus.FileExists; - } - } - catch (IOException ie) - { - return FileStatus.NotExist; - } - } - } - - @Override - public DeviceContext createContext(String shareName, ConfigElement args) - throws DeviceContextException - { - return diskInterface.createContext(shareName, args); - } - - @Override - public void treeOpened(SrvSession sess, TreeConnection tree) - { - diskInterface.treeOpened(sess, tree); - } - - @Override - public void treeClosed(SrvSession sess, TreeConnection tree) - { - diskInterface.treeClosed(sess, tree); - } - - @Override - public DataBuffer processIOControl(SrvSession sess, TreeConnection tree, - int ctrlCode, int fid, DataBuffer dataBuf, boolean isFSCtrl, - int filter) throws IOControlNotImplementedException, SMBException - { - return ioctlInterface.processIOControl(sess, tree, ctrlCode, fid, dataBuf, isFSCtrl, filter); - } - - @Override - public void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo diskDev) - throws IOException - { - diskSizeInterface.getDiskInformation(ctx, diskDev); - } - - @Override - public void closeFile(SrvSession sess, TreeConnection tree, - NetworkFile param) throws IOException - { - diskInterface.closeFile(sess, tree, param); - - /** - * If the fileInfo cache may have just had some content updated. - */ - if(!param.isDirectory() && !param.isReadOnly()) - { - fileInfoCache.clear(); - } - } - - @Override - public void createDirectory(SrvSession sess, TreeConnection tree, - FileOpenParams params) throws IOException - { - diskInterface.createDirectory(sess, tree, params); - } - - @Override - public NetworkFile createFile(SrvSession sess, TreeConnection tree, - FileOpenParams params) throws IOException - { - return diskInterface.createFile(sess, tree, params); - } - - @Override - public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) - throws IOException - { - fileInfoCache.remove(dir); - - diskInterface.deleteDirectory(sess, tree, dir); - } - - @Override - public void deleteFile(SrvSession sess, TreeConnection tree, String name) - throws IOException - { - fileInfoCache.remove(name); - - diskInterface.deleteFile(sess, tree, name); - } - - @Override - public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) - throws IOException - { - diskInterface.flushFile(sess, tree, file); - } - - @Override - public boolean isReadOnly(SrvSession sess, DeviceContext ctx) - throws IOException - { - return diskInterface.isReadOnly(sess, ctx); - } - - @Override - public NetworkFile openFile(SrvSession sess, TreeConnection tree, - FileOpenParams params) throws IOException - { - return diskInterface.openFile(sess, tree, params); - } - - @Override - public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, - byte[] buf, int bufPos, int siz, long filePos) throws IOException - { - return diskInterface.readFile(sess, tree, file, buf, bufPos, siz, filePos); - } - - @Override - public void renameFile(SrvSession sess, TreeConnection tree, - String oldName, String newName) throws IOException - { - diskInterface.renameFile(sess, tree, oldName, newName); - } - - @Override - public long seekFile(SrvSession sess, TreeConnection tree, - NetworkFile file, long pos, int typ) throws IOException - { - return diskInterface.seekFile(sess, tree, file, pos, typ); - } - - @Override - public void setFileInformation(SrvSession sess, TreeConnection tree, - String name, FileInfo info) throws IOException - { - diskInterface.setFileInformation(sess, tree, name, info); - } - - @Override - public SearchContext startSearch(SrvSession sess, TreeConnection tree, - String searchPath, int attrib) throws FileNotFoundException - { - return diskInterface.startSearch(sess, tree, searchPath, attrib); - } - - @Override - public void truncateFile(SrvSession sess, TreeConnection tree, - NetworkFile file, long siz) throws IOException - { - diskInterface.truncateFile(sess, tree, file, siz); - } - - @Override - public int writeFile(SrvSession sess, TreeConnection tree, - NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff) - throws IOException - { - return diskInterface.writeFile(sess, tree, file, buf, bufoff, siz, fileoff); - } - - @Override - public void registerContext(DeviceContext ctx) - throws DeviceContextException - { - diskInterface.registerContext(ctx); - } - - public void setDiskInterface(ExtendedDiskInterface diskInterface) - { - this.diskInterface = diskInterface; - } - - public ExtendedDiskInterface getDiskInterface() - { - return diskInterface; - } - - public void setDiskSizeInterface(DiskSizeInterface diskSizeInterface) - { - this.diskSizeInterface = diskSizeInterface; - } - - public DiskSizeInterface getDiskSizeInterface() - { - return diskSizeInterface; - } - - public void setIoctlInterface(IOCtlInterface iocltlInterface) - { - this.ioctlInterface = iocltlInterface; - } - - public IOCtlInterface getIoctlInterface() - { - return ioctlInterface; - } - - @Override - public void onMoveNode(ChildAssociationRef oldChildAssocRef, - ChildAssociationRef newChildAssocRef) - { - if(fileInfoCache.contains(oldChildAssocRef.getChildRef())) - { - logger.debug("cached node moved - clear the cache"); - fileInfoCache.clear(); - } - } - - @Override - public void onDeleteNode(ChildAssociationRef oldChildAssocRef, boolean isArchived) - { - if(fileInfoCache.contains(oldChildAssocRef.getChildRef())) - { - logger.debug("cached node deleted - clear the cache"); - fileInfoCache.clear(); - } - } - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public PolicyComponent getPolicyComponent() - { - return policyComponent; - } - - public void setOpLockInterface(OpLockInterface opLockInterface) - { - this.opLockInterface = opLockInterface; - } - - public OpLockInterface getOpLockInterface() - { - return opLockInterface; - } - - @Override - public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) - { - return opLockInterface.getOpLockManager(sess, tree); - } - - @Override - public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) - { - return opLockInterface.isOpLocksEnabled(sess, tree); - } - - @Override - public LockManager getLockManager(SrvSession sess, TreeConnection tree) - { - return getFileLockingInterface().getLockManager(sess, tree); - } - - - public void setFileLockingInterface(FileLockingInterface fileLockingInterface) - { - this.fileLockingInterface = fileLockingInterface; - } - - - public FileLockingInterface getFileLockingInterface() - { - return fileLockingInterface; - } -} +package org.alfresco.filesys.repo; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Serializable; +import java.util.Date; + +import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.filesys.alfresco.ExtendedDiskInterface; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.core.SharedDevice; +import org.alfresco.jlan.server.filesys.DiskDeviceContext; +import org.alfresco.jlan.server.filesys.DiskInterface; +import org.alfresco.jlan.server.filesys.DiskSizeInterface; +import org.alfresco.jlan.server.filesys.FileAccessToken; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.FileStatus; +import org.alfresco.jlan.server.filesys.IOControlNotImplementedException; +import org.alfresco.jlan.server.filesys.IOCtlInterface; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.SrvDiskInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.FileStateCache; +import org.alfresco.jlan.server.locking.FileLockingInterface; +import org.alfresco.jlan.server.locking.LockManager; +import org.alfresco.jlan.server.locking.OpLockInterface; +import org.alfresco.jlan.server.locking.OpLockManager; +import org.alfresco.jlan.smb.SMBException; +import org.alfresco.jlan.util.DataBuffer; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.config.ConfigElement; + +/** + * Alfresco Content Disk Driver Cache + *

+ * Decorates ContentDiskDriver with a performance cache of some frequently used + * results. In particular for getFileInformation and fileExists + */ +/* + * MER - this class is also acting as a proxy to gather together the different interfaces + * and present them to JLAN. This was not the intention and is a short term hack. It + * should be possible to un-spring the buffering, however that's not possible at the moment. + */ +public class BufferedContentDiskDriver implements ExtendedDiskInterface, + DiskInterface, + DiskSizeInterface, + IOCtlInterface, + OpLockInterface, + FileLockingInterface, + NodeServicePolicies.OnDeleteNodePolicy, + NodeServicePolicies.OnMoveNodePolicy +{ + // Logging + private static final Log logger = LogFactory.getLog(BufferedContentDiskDriver.class); + + private ExtendedDiskInterface diskInterface; + + private DiskSizeInterface diskSizeInterface; + + private IOCtlInterface ioctlInterface; + + private OpLockInterface opLockInterface; + + private FileLockingInterface fileLockingInterface; + + private PolicyComponent policyComponent; + + public void init() + { + PropertyCheck.mandatory(this, "diskInterface", diskInterface); + PropertyCheck.mandatory(this, "diskSizeInterface", diskSizeInterface); + PropertyCheck.mandatory(this, "ioctltInterface", ioctlInterface); + PropertyCheck.mandatory(this, "fileInfoCache", fileInfoCache); + PropertyCheck.mandatory(this, "fileLockingInterface", getFileLockingInterface()); + PropertyCheck.mandatory(this, "opLockInterface", getOpLockInterface()); + PropertyCheck.mandatory(this, "fileLockingInterface", fileLockingInterface); + PropertyCheck.mandatory(this, "policyComponent", getPolicyComponent()); + + getPolicyComponent().bindClassBehaviour( NodeServicePolicies.OnDeleteNodePolicy.QNAME, + this, new JavaBehaviour(this, "onDeleteNode")); + getPolicyComponent().bindClassBehaviour( NodeServicePolicies.OnMoveNodePolicy.QNAME, + this, new JavaBehaviour(this, "onMoveNode")); + } + + /** + * FileInfo Cache for path to FileInfo + */ + private SimpleCache fileInfoCache; + + /** + * Set the cache that maintains node ID-NodeRef cross referencing data + * + * @param cache the cache + */ + public void setFileInfoCache(SimpleCache cache) + { + this.fileInfoCache = cache; + } + + private static class FileInfoKey implements Serializable + { + /** + * + */ + private static final long serialVersionUID = 1L; + + String deviceName; + String path; + String user; + int hashCode; + + public FileInfoKey(SrvSession sess, String path, TreeConnection tree) + { + this.path = path; + this.user = sess.getUniqueId(); + this.deviceName = tree.getSharedDevice().getName(); + +// if(deviceName == null) +// { +// throw new RuntimeException("device name is null"); +// } +// if(path == null) +// { +// throw new RuntimeException("path is null"); +// } +// if(user == null) +// { +// throw new RuntimeException("unique id is null"); +// } + } + + @Override + public boolean equals(Object other) + { + if (this == other) + { + return true; + } + if (other == null || !(other instanceof FileInfoKey)) + { + return false; + } + + FileInfoKey o = (FileInfoKey)other; + + return path.equals(o.path) && user.equals(o.user) && deviceName.equals(o.deviceName); + } + + @Override + public int hashCode() + { + if(hashCode == 0) + { + hashCode = (user+path+deviceName).hashCode(); + } + return hashCode; + } + } + + private FileInfo getFileInformationInternal(SrvSession sess, TreeConnection tree, + String path) throws IOException + { + + //String userName = AuthenticationUtil.getFullyAuthenticatedUser(); + SharedDevice device = tree.getSharedDevice(); + String deviceName = device.getName(); + + if(logger.isDebugEnabled()) + { + logger.debug("getFileInformation session:" + sess.getUniqueId() + ", deviceName:" + deviceName + ", path:" + path); + } + + if(path == null) + { + throw new IllegalArgumentException("Path is null"); + } + + FileInfoKey key = new FileInfoKey(sess, path, tree); + + FileInfo fromCache = fileInfoCache.get(key); + + if(fromCache != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("returning FileInfo from cache"); + } + return fromCache; + } + + FileInfo info = diskInterface.getFileInformation(sess, tree, path); + + if(info != null) + { + /** + * Don't cache directories since the modification date is important. + */ + if(!info.isDirectory()) + { + fileInfoCache.put(key, info); + } + } + + /* + * Dual Key the cache so it can be looked up by NodeRef or Path + */ + if(info instanceof ContentFileInfo) + { + ContentFileInfo cinfo = (ContentFileInfo)info; + fileInfoCache.put(cinfo.getNodeRef(), info); + } + + return info; + } + + + @Override + public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, + String path) throws IOException + { + ContentContext tctx = (ContentContext) tree.getContext(); + + FileInfo info = getFileInformationInternal(sess, tree, path); + + /* + * Some information is not maintained by the repo and represents an in-progress update. + * For example as a file is being written the modification and access dates change. + */ + if(tctx.hasStateCache()) + { + FileStateCache cache = tctx.getStateCache(); + FileState fstate = cache.findFileState(path, false); + if(fstate != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("state cache available - overwriting from state cache: isDirectory=" +info.isDirectory()); + } + FileInfo finfo = new FileInfo(); + finfo.copyFrom(info); + + /** + * File state is probably stale for directories which is why we don't attempt to + * cache. + */ + if(!info.isDirectory()) + { + /* + * What about stale file state values here? + */ + if(fstate.hasFileSize()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace file size " + info.getSize() + " with " + fstate.getFileSize()); + } + finfo.setFileSize(fstate.getFileSize()); + } + if ( fstate.hasAccessDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace access date " + new Date(finfo.getAccessDateTime()) + " with " + new Date(fstate.getAccessDateTime())); + } + finfo.setAccessDateTime(fstate.getAccessDateTime()); + } + if ( fstate.hasChangeDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace change date " + new Date(finfo.getChangeDateTime()) + " with " + new Date(fstate.getChangeDateTime())); + } + finfo.setChangeDateTime(fstate.getChangeDateTime()); + } + if ( fstate.hasModifyDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace modified date " + new Date(finfo.getModifyDateTime()) + " with " + new Date(fstate.getModifyDateTime())); + } + finfo.setModifyDateTime(fstate.getModifyDateTime()); + } + if ( fstate.hasAllocationSize()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace allocation size" + finfo.getAllocationSize() + " with " + fstate.getAllocationSize()); + } + finfo.setAllocationSize(fstate.getAllocationSize()); + } + } + + if(logger.isDebugEnabled()) + { + logger.debug("Return getFileInformation, path: " + path + + ", returning:" + finfo + + ", readOnly:" +finfo.isReadOnly() + + ", fileId:" +finfo.getFileId() + + ", fileSize:" +finfo.getSize() + + ", directoryId:" + finfo.getDirectoryId() + + ", createdDate: " + new Date(finfo.getCreationDateTime()) + + ", accessDate:" + new Date(finfo.getAccessDateTime()) + + ", modifiedDate:" + new Date(finfo.getModifyDateTime()) + + ", changeDate:" + new Date(finfo.getChangeDateTime()) + + ", fileAttributes: 0x"+ Integer.toHexString(info.getFileAttributes()) + + ", mode: 0x" + Integer.toHexString(finfo.getMode())); + } + + return finfo; + } + } + + if(logger.isDebugEnabled()) + { + logger.debug("getFileInformation Return:" + path + " returning" + info); + } + + return info; + + } + + @Override + public int fileExists(SrvSession sess, TreeConnection tree, String path) + { + String deviceName = tree.getSharedDevice().getName(); + + if(logger.isDebugEnabled()) + { + logger.debug("fileExists session:" + sess.getUniqueId() + ", deviceName" + deviceName + ", path:" + path); + } + + FileInfoKey key = new FileInfoKey(sess, path, tree); + + FileInfo fromCache = fileInfoCache.get(key); + + if(fromCache != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("fileExists found FileInfo in cache"); + } + if (fromCache.isDirectory()) + { + return FileStatus.DirectoryExists; + } + else + { + return FileStatus.FileExists; + } + } + else + { + try + { + FileInfo lookup = getFileInformationInternal(sess, tree, path); + + if(logger.isDebugEnabled()) + { + logger.debug("fileExists obtained file information"); + } + if (lookup.isDirectory()) + { + return FileStatus.DirectoryExists; + } + else + { + return FileStatus.FileExists; + } + } + catch (IOException ie) + { + return FileStatus.NotExist; + } + } + } + + @Override + public DeviceContext createContext(String shareName, ConfigElement args) + throws DeviceContextException + { + return diskInterface.createContext(shareName, args); + } + + @Override + public void treeOpened(SrvSession sess, TreeConnection tree) + { + diskInterface.treeOpened(sess, tree); + } + + @Override + public void treeClosed(SrvSession sess, TreeConnection tree) + { + diskInterface.treeClosed(sess, tree); + } + + @Override + public DataBuffer processIOControl(SrvSession sess, TreeConnection tree, + int ctrlCode, int fid, DataBuffer dataBuf, boolean isFSCtrl, + int filter) throws IOControlNotImplementedException, SMBException + { + return ioctlInterface.processIOControl(sess, tree, ctrlCode, fid, dataBuf, isFSCtrl, filter); + } + + @Override + public void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo diskDev) + throws IOException + { + diskSizeInterface.getDiskInformation(ctx, diskDev); + } + + @Override + public void closeFile(SrvSession sess, TreeConnection tree, + NetworkFile param) throws IOException + { + diskInterface.closeFile(sess, tree, param); + + /** + * If the fileInfo cache may have just had some content updated. + */ + if(!param.isDirectory() && !param.isReadOnly()) + { + fileInfoCache.clear(); + } + } + + @Override + public void createDirectory(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + diskInterface.createDirectory(sess, tree, params); + } + + @Override + public NetworkFile createFile(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + return diskInterface.createFile(sess, tree, params); + } + + @Override + public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) + throws IOException + { + fileInfoCache.remove(dir); + + diskInterface.deleteDirectory(sess, tree, dir); + } + + @Override + public void deleteFile(SrvSession sess, TreeConnection tree, String name) + throws IOException + { + fileInfoCache.remove(name); + + diskInterface.deleteFile(sess, tree, name); + } + + @Override + public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) + throws IOException + { + diskInterface.flushFile(sess, tree, file); + } + + @Override + public boolean isReadOnly(SrvSession sess, DeviceContext ctx) + throws IOException + { + return diskInterface.isReadOnly(sess, ctx); + } + + @Override + public NetworkFile openFile(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + return diskInterface.openFile(sess, tree, params); + } + + @Override + public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buf, int bufPos, int siz, long filePos) throws IOException + { + return diskInterface.readFile(sess, tree, file, buf, bufPos, siz, filePos); + } + + @Override + public void renameFile(SrvSession sess, TreeConnection tree, + String oldName, String newName) throws IOException + { + diskInterface.renameFile(sess, tree, oldName, newName); + } + + @Override + public long seekFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long pos, int typ) throws IOException + { + return diskInterface.seekFile(sess, tree, file, pos, typ); + } + + @Override + public void setFileInformation(SrvSession sess, TreeConnection tree, + String name, FileInfo info) throws IOException + { + diskInterface.setFileInformation(sess, tree, name, info); + } + + @Override + public SearchContext startSearch(SrvSession sess, TreeConnection tree, + String searchPath, int attrib) throws FileNotFoundException + { + return diskInterface.startSearch(sess, tree, searchPath, attrib); + } + + @Override + public void truncateFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long siz) throws IOException + { + diskInterface.truncateFile(sess, tree, file, siz); + } + + @Override + public int writeFile(SrvSession sess, TreeConnection tree, + NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff) + throws IOException + { + return diskInterface.writeFile(sess, tree, file, buf, bufoff, siz, fileoff); + } + + @Override + public void registerContext(DeviceContext ctx) + throws DeviceContextException + { + diskInterface.registerContext(ctx); + } + + public void setDiskInterface(ExtendedDiskInterface diskInterface) + { + this.diskInterface = diskInterface; + } + + public ExtendedDiskInterface getDiskInterface() + { + return diskInterface; + } + + public void setDiskSizeInterface(DiskSizeInterface diskSizeInterface) + { + this.diskSizeInterface = diskSizeInterface; + } + + public DiskSizeInterface getDiskSizeInterface() + { + return diskSizeInterface; + } + + public void setIoctlInterface(IOCtlInterface iocltlInterface) + { + this.ioctlInterface = iocltlInterface; + } + + public IOCtlInterface getIoctlInterface() + { + return ioctlInterface; + } + + @Override + public void onMoveNode(ChildAssociationRef oldChildAssocRef, + ChildAssociationRef newChildAssocRef) + { + if(fileInfoCache.contains(oldChildAssocRef.getChildRef())) + { + logger.debug("cached node moved - clear the cache"); + fileInfoCache.clear(); + } + } + + @Override + public void onDeleteNode(ChildAssociationRef oldChildAssocRef, boolean isArchived) + { + if(fileInfoCache.contains(oldChildAssocRef.getChildRef())) + { + logger.debug("cached node deleted - clear the cache"); + fileInfoCache.clear(); + } + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public PolicyComponent getPolicyComponent() + { + return policyComponent; + } + + public void setOpLockInterface(OpLockInterface opLockInterface) + { + this.opLockInterface = opLockInterface; + } + + public OpLockInterface getOpLockInterface() + { + return opLockInterface; + } + + @Override + public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) + { + return opLockInterface.getOpLockManager(sess, tree); + } + + @Override + public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) + { + return opLockInterface.isOpLocksEnabled(sess, tree); + } + + @Override + public LockManager getLockManager(SrvSession sess, TreeConnection tree) + { + return getFileLockingInterface().getLockManager(sess, tree); + } + + + public void setFileLockingInterface(FileLockingInterface fileLockingInterface) + { + this.fileLockingInterface = fileLockingInterface; + } + + + public FileLockingInterface getFileLockingInterface() + { + return fileLockingInterface; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/repo/CIFSContentComparator.java b/source/java/org/alfresco/filesys/repo/CIFSContentComparator.java index 3c4e55a700..74762111ff 100644 --- a/source/java/org/alfresco/filesys/repo/CIFSContentComparator.java +++ b/source/java/org/alfresco/filesys/repo/CIFSContentComparator.java @@ -1,498 +1,498 @@ -package org.alfresco.filesys.repo; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; - -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.util.EqualsHelper; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.poi.hslf.HSLFSlideShow; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.poifs.filesystem.DirectoryEntry; -import org.apache.poi.poifs.filesystem.EntryUtils; -import org.apache.poi.poifs.filesystem.FilteringDirectoryNode; -import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; - -/** - * Compares content for to see if content is equal. - *

- * Most mimetypes can simply be binary compared but for some mimetypes - * there may be trivial differences so a binary compare is not sufficient. - *

- * In particular MS Project and MS Excel write to header fields without changing content. - * - * @author mrogers - * - */ -public class CIFSContentComparator implements ContentComparator -{ - // TODO Externalize Map of mimetype to comparator - private Map customComparators = new HashMap(); - - private static final Log logger = LogFactory.getLog(CIFSContentComparator.class); - - /** - * - */ - public void init() - { - customComparators.put("application/vnd.ms-project", new MPPContentComparator()); - customComparators.put("application/vnd.ms-excel", new XLSContentComparator()); - customComparators.put("application/vnd.ms-powerpoint", new PPTContentComparator()); - } - - @Override - public boolean isContentEqual(ContentReader existingContent, - File newFile) - { - String mimetype = existingContent.getMimetype(); - logger.debug("isContentEqual mimetype=" + mimetype); - - long newSize = newFile.length(); - - ContentComparator custom = customComparators.get(mimetype); - - if(custom == null) - { - // No custom comparator - check length then do a binary diff - if(existingContent.getSize() != newSize) - { - // Different size - logger.debug("generic comparision, size is different - not equal"); - return false; - } - - InputStream rightIs = null; - InputStream leftIs = null; - try - { - rightIs = new BufferedInputStream(new FileInputStream(newFile)); - leftIs = existingContent.getContentInputStream(); - boolean retVal = EqualsHelper.binaryStreamEquals(leftIs, rightIs); - rightIs = null; - leftIs = null; - - if(logger.isDebugEnabled()) - { - logger.debug("generic comparision, binary content comparison equal=" + retVal); - } - return retVal; - } - catch (IOException e) - { - - logger.debug("Unable to compare contents", e); - return false; - } - finally - { - if(leftIs != null) - { - try - { - leftIs.close(); - } - catch (IOException e) - { - // Do nothing this is cleanup code - } - } - if(rightIs != null) - { - try - { - rightIs.close(); - } - catch (IOException e) - { - // Do nothing this is cleanup code - } - } - } - } - else - { - // there is a custom comparator for this mimetype - return custom.isContentEqual(existingContent, newFile); - } - } - - private boolean isContentIdentical(NPOIFSFileSystem fs1, NPOIFSFileSystem fs2, Collection excludes) throws IOException - { - DirectoryEntry de1 = fs1.getRoot(); - DirectoryEntry de2 = fs2.getRoot(); - - FilteringDirectoryNode fs1Filtered = new FilteringDirectoryNode(de1, excludes); - FilteringDirectoryNode fs2Filtered = new FilteringDirectoryNode(de2, excludes); - - boolean retVal = EntryUtils.areDirectoriesIdentical(fs1Filtered, fs2Filtered); - if(logger.isDebugEnabled()) - { - logger.debug("returning equal="+ retVal); - } - return retVal; - } - - - // Comparator for MS Project - private class MPPContentComparator implements ContentComparator - { - - @Override - public boolean isContentEqual(ContentReader existingContent, - File newFile) - { - long newSize = newFile.length(); - - if(logger.isDebugEnabled()) - { - logger.debug("comparing two project files size:" + existingContent.getSize() + ", and " + newFile.length()); - } - - if(existingContent.getSize() != newSize) - { - logger.debug("project files are different size"); - // Different size - return false; - } - - /** - * Use POI to compare the content of the MPP file, exluding certain properties - */ - InputStream leftIs = null; - - try - { - Collection excludes = new HashSet(); - excludes.add("Props"); - excludes.add("Props12"); - excludes.add("Props9"); - - leftIs = existingContent.getContentInputStream(); - - // this call guarantees that leftIs is closed. - NPOIFSFileSystem fs2 = new NPOIFSFileSystem(leftIs); - // this call keeps an open file handle and needs closing. - NPOIFSFileSystem fs1 = new NPOIFSFileSystem(newFile); - try - { - - return isContentIdentical(fs1, fs2, excludes); - } - finally - { - try - { - fs1.close(); - } - catch (IOException e) - { - // ignore - } - try - { - fs2.close(); - } - catch (IOException e) - { - // ignore - } - } - } - catch (ContentIOException ce) - { - logger.debug("Unable to compare contents", ce); - return false; - } - catch (IOException e) - { - logger.debug("Unable to compare contents", e); - return false; - } - finally - { - if(leftIs != null) - { - try - { - leftIs.close(); - } - catch (IOException e) - { - // Ignore - } - } - } - } - } - - // Comparator for MS Excel - private class XLSContentComparator implements ContentComparator - { - - @Override - public boolean isContentEqual(ContentReader existingContent, - File newFile) - { - long newSize = newFile.length(); - - if(logger.isDebugEnabled()) - { - logger.debug("comparing two excel files size:" + existingContent.getSize() + ", and " + newFile.length()); - } - - if(existingContent.getSize() != newSize) - { - logger.debug("excel files are different size"); - // Different size - return false; - } - - /** - * Use POI to compare the content of the XLS file, exluding certain properties - */ - File tpm1 = null; - File tpm2 = null; - InputStream leftIs = null; - try - { - Collection excludes = new HashSet(); - - tpm1 = TempFileProvider.createTempFile("CIFSContentComparator1", "xls"); - tpm2 = TempFileProvider.createTempFile("CIFSContentComparator2", "xls"); - - leftIs = existingContent.getContentInputStream(); - HSSFWorkbook wb1 = new HSSFWorkbook(leftIs); - HSSFWorkbook wb2 = new HSSFWorkbook(new FileInputStream(newFile)); - wb1.writeProtectWorkbook("", "CIFSContentComparator"); - wb2.writeProtectWorkbook("", "CIFSContentComparator"); - - FileOutputStream os = new FileOutputStream(tpm1); - try - { - wb1.write(os); - } - finally - { - os.close(); - } - FileOutputStream os2 = new FileOutputStream(tpm2); - try - { - wb2.write(os2); - } - finally - { - os2.close(); - } - - NPOIFSFileSystem fs1 = new NPOIFSFileSystem(tpm1); - NPOIFSFileSystem fs2 = new NPOIFSFileSystem(tpm2); - - return isContentIdentical(fs1, fs2, excludes); - } - catch (ContentIOException ce) - { - logger.debug("Unable to compare contents", ce); - return false; - } - catch (IOException e) - { - logger.debug("Unable to compare contents", e); - return false; - } - finally - { - if(tpm1 != null) - { - try - { - tpm1.delete(); - } - catch (Exception e) - { - // ignore - } - } - if(tpm2 != null) - { - try - { - tpm2.delete(); - } - catch (Exception e) - { - // ignore - } - } - if(leftIs != null) - { - try - { - leftIs.close(); - } - catch (IOException e) - { - // Ignore - } - } - } - } - } - - // Comparator for MS PowerPoint - private class PPTContentComparator implements ContentComparator - { - - @Override - public boolean isContentEqual(ContentReader existingContent, File newFile) - { - long fileSizesDifference = newFile.length() - existingContent.getSize(); - - if(logger.isDebugEnabled()) - { - logger.debug("comparing two powerpoint files size:" + existingContent.getSize() + ", and " + newFile.length()); - } - - File tpm1 = null; - File tpm2 = null; - InputStream leftIs = null; - try - { - if(fileSizesDifference != 0) - { - // ALF-18793 - // Experience has shown that the size of opened/closed file increases to 3072 bytes. - // (That occurs only in case if the file has been created on one MS PowerPoint instance and opened/closed on another - // due to change of lastEditUsername property (if they are different)). - if (fileSizesDifference > 3072 && fileSizesDifference < 0) - { - logger.debug("powerpoint files are different size"); - // Different size - return false; - } - - Collection excludes = new HashSet(); - excludes.add("Current User"); - - leftIs = existingContent.getContentInputStream(); - HSLFSlideShow slideShow1 = new HSLFSlideShow(leftIs); - HSLFSlideShow slideShow2 = new HSLFSlideShow(new FileInputStream(newFile)); - - String lastEditUsername1 = slideShow1.getCurrentUserAtom().getLastEditUsername(); - String lastEditUsername2 = slideShow2.getCurrentUserAtom().getLastEditUsername(); - - if (lastEditUsername1.equals(lastEditUsername2)) - { - logger.debug("powerpoint files are edited by different users"); - // Different size - return false; - } - else - { - //make sure that nothing has been changed except lastEditUsername - tpm1 = TempFileProvider.createTempFile("CIFSContentComparator1", "ppt"); - FileOutputStream os = new FileOutputStream(tpm1); - try - { - slideShow1.write(os); - } - finally - { - try - { - os.close(); - } - catch (IOException ie) - { - // ignore - } - } - tpm2 = TempFileProvider.createTempFile("CIFSContentComparator2", "ppt"); - FileOutputStream os2 = new FileOutputStream(tpm2); - try - { - slideShow2.write(os2); - } - finally - { - try - { - os2.close(); - } - catch (IOException ie) - { - // ignore - } - } - - NPOIFSFileSystem fs1 = new NPOIFSFileSystem(tpm1); - NPOIFSFileSystem fs2 = new NPOIFSFileSystem(tpm2); - - return isContentIdentical(fs1, fs2, excludes); - } - - } - - return true; - } - catch (ContentIOException ce) - { - logger.debug("Unable to compare contents", ce); - return false; - } - catch (IOException e) - { - logger.debug("Unable to compare contents", e); - return false; - } - finally - { - if(tpm1 != null) - { - try - { - tpm1.delete(); - } - catch (Exception e) - { - // ignore - } - } - if(tpm2 != null) - { - try - { - tpm2.delete(); - } - catch (Exception e) - { - // ignore - } - } - if(leftIs != null) - { - try - { - leftIs.close(); - } - catch (IOException e) - { - // Ignore - } - } - } - } - } -} +package org.alfresco.filesys.repo; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.poi.hslf.HSLFSlideShow; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.DirectoryEntry; +import org.apache.poi.poifs.filesystem.EntryUtils; +import org.apache.poi.poifs.filesystem.FilteringDirectoryNode; +import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; + +/** + * Compares content for to see if content is equal. + *

+ * Most mimetypes can simply be binary compared but for some mimetypes + * there may be trivial differences so a binary compare is not sufficient. + *

+ * In particular MS Project and MS Excel write to header fields without changing content. + * + * @author mrogers + * + */ +public class CIFSContentComparator implements ContentComparator +{ + // TODO Externalize Map of mimetype to comparator + private Map customComparators = new HashMap(); + + private static final Log logger = LogFactory.getLog(CIFSContentComparator.class); + + /** + * + */ + public void init() + { + customComparators.put("application/vnd.ms-project", new MPPContentComparator()); + customComparators.put("application/vnd.ms-excel", new XLSContentComparator()); + customComparators.put("application/vnd.ms-powerpoint", new PPTContentComparator()); + } + + @Override + public boolean isContentEqual(ContentReader existingContent, + File newFile) + { + String mimetype = existingContent.getMimetype(); + logger.debug("isContentEqual mimetype=" + mimetype); + + long newSize = newFile.length(); + + ContentComparator custom = customComparators.get(mimetype); + + if(custom == null) + { + // No custom comparator - check length then do a binary diff + if(existingContent.getSize() != newSize) + { + // Different size + logger.debug("generic comparision, size is different - not equal"); + return false; + } + + InputStream rightIs = null; + InputStream leftIs = null; + try + { + rightIs = new BufferedInputStream(new FileInputStream(newFile)); + leftIs = existingContent.getContentInputStream(); + boolean retVal = EqualsHelper.binaryStreamEquals(leftIs, rightIs); + rightIs = null; + leftIs = null; + + if(logger.isDebugEnabled()) + { + logger.debug("generic comparision, binary content comparison equal=" + retVal); + } + return retVal; + } + catch (IOException e) + { + + logger.debug("Unable to compare contents", e); + return false; + } + finally + { + if(leftIs != null) + { + try + { + leftIs.close(); + } + catch (IOException e) + { + // Do nothing this is cleanup code + } + } + if(rightIs != null) + { + try + { + rightIs.close(); + } + catch (IOException e) + { + // Do nothing this is cleanup code + } + } + } + } + else + { + // there is a custom comparator for this mimetype + return custom.isContentEqual(existingContent, newFile); + } + } + + private boolean isContentIdentical(NPOIFSFileSystem fs1, NPOIFSFileSystem fs2, Collection excludes) throws IOException + { + DirectoryEntry de1 = fs1.getRoot(); + DirectoryEntry de2 = fs2.getRoot(); + + FilteringDirectoryNode fs1Filtered = new FilteringDirectoryNode(de1, excludes); + FilteringDirectoryNode fs2Filtered = new FilteringDirectoryNode(de2, excludes); + + boolean retVal = EntryUtils.areDirectoriesIdentical(fs1Filtered, fs2Filtered); + if(logger.isDebugEnabled()) + { + logger.debug("returning equal="+ retVal); + } + return retVal; + } + + + // Comparator for MS Project + private class MPPContentComparator implements ContentComparator + { + + @Override + public boolean isContentEqual(ContentReader existingContent, + File newFile) + { + long newSize = newFile.length(); + + if(logger.isDebugEnabled()) + { + logger.debug("comparing two project files size:" + existingContent.getSize() + ", and " + newFile.length()); + } + + if(existingContent.getSize() != newSize) + { + logger.debug("project files are different size"); + // Different size + return false; + } + + /** + * Use POI to compare the content of the MPP file, exluding certain properties + */ + InputStream leftIs = null; + + try + { + Collection excludes = new HashSet(); + excludes.add("Props"); + excludes.add("Props12"); + excludes.add("Props9"); + + leftIs = existingContent.getContentInputStream(); + + // this call guarantees that leftIs is closed. + NPOIFSFileSystem fs2 = new NPOIFSFileSystem(leftIs); + // this call keeps an open file handle and needs closing. + NPOIFSFileSystem fs1 = new NPOIFSFileSystem(newFile); + try + { + + return isContentIdentical(fs1, fs2, excludes); + } + finally + { + try + { + fs1.close(); + } + catch (IOException e) + { + // ignore + } + try + { + fs2.close(); + } + catch (IOException e) + { + // ignore + } + } + } + catch (ContentIOException ce) + { + logger.debug("Unable to compare contents", ce); + return false; + } + catch (IOException e) + { + logger.debug("Unable to compare contents", e); + return false; + } + finally + { + if(leftIs != null) + { + try + { + leftIs.close(); + } + catch (IOException e) + { + // Ignore + } + } + } + } + } + + // Comparator for MS Excel + private class XLSContentComparator implements ContentComparator + { + + @Override + public boolean isContentEqual(ContentReader existingContent, + File newFile) + { + long newSize = newFile.length(); + + if(logger.isDebugEnabled()) + { + logger.debug("comparing two excel files size:" + existingContent.getSize() + ", and " + newFile.length()); + } + + if(existingContent.getSize() != newSize) + { + logger.debug("excel files are different size"); + // Different size + return false; + } + + /** + * Use POI to compare the content of the XLS file, exluding certain properties + */ + File tpm1 = null; + File tpm2 = null; + InputStream leftIs = null; + try + { + Collection excludes = new HashSet(); + + tpm1 = TempFileProvider.createTempFile("CIFSContentComparator1", "xls"); + tpm2 = TempFileProvider.createTempFile("CIFSContentComparator2", "xls"); + + leftIs = existingContent.getContentInputStream(); + HSSFWorkbook wb1 = new HSSFWorkbook(leftIs); + HSSFWorkbook wb2 = new HSSFWorkbook(new FileInputStream(newFile)); + wb1.writeProtectWorkbook("", "CIFSContentComparator"); + wb2.writeProtectWorkbook("", "CIFSContentComparator"); + + FileOutputStream os = new FileOutputStream(tpm1); + try + { + wb1.write(os); + } + finally + { + os.close(); + } + FileOutputStream os2 = new FileOutputStream(tpm2); + try + { + wb2.write(os2); + } + finally + { + os2.close(); + } + + NPOIFSFileSystem fs1 = new NPOIFSFileSystem(tpm1); + NPOIFSFileSystem fs2 = new NPOIFSFileSystem(tpm2); + + return isContentIdentical(fs1, fs2, excludes); + } + catch (ContentIOException ce) + { + logger.debug("Unable to compare contents", ce); + return false; + } + catch (IOException e) + { + logger.debug("Unable to compare contents", e); + return false; + } + finally + { + if(tpm1 != null) + { + try + { + tpm1.delete(); + } + catch (Exception e) + { + // ignore + } + } + if(tpm2 != null) + { + try + { + tpm2.delete(); + } + catch (Exception e) + { + // ignore + } + } + if(leftIs != null) + { + try + { + leftIs.close(); + } + catch (IOException e) + { + // Ignore + } + } + } + } + } + + // Comparator for MS PowerPoint + private class PPTContentComparator implements ContentComparator + { + + @Override + public boolean isContentEqual(ContentReader existingContent, File newFile) + { + long fileSizesDifference = newFile.length() - existingContent.getSize(); + + if(logger.isDebugEnabled()) + { + logger.debug("comparing two powerpoint files size:" + existingContent.getSize() + ", and " + newFile.length()); + } + + File tpm1 = null; + File tpm2 = null; + InputStream leftIs = null; + try + { + if(fileSizesDifference != 0) + { + // ALF-18793 + // Experience has shown that the size of opened/closed file increases to 3072 bytes. + // (That occurs only in case if the file has been created on one MS PowerPoint instance and opened/closed on another + // due to change of lastEditUsername property (if they are different)). + if (fileSizesDifference > 3072 && fileSizesDifference < 0) + { + logger.debug("powerpoint files are different size"); + // Different size + return false; + } + + Collection excludes = new HashSet(); + excludes.add("Current User"); + + leftIs = existingContent.getContentInputStream(); + HSLFSlideShow slideShow1 = new HSLFSlideShow(leftIs); + HSLFSlideShow slideShow2 = new HSLFSlideShow(new FileInputStream(newFile)); + + String lastEditUsername1 = slideShow1.getCurrentUserAtom().getLastEditUsername(); + String lastEditUsername2 = slideShow2.getCurrentUserAtom().getLastEditUsername(); + + if (lastEditUsername1.equals(lastEditUsername2)) + { + logger.debug("powerpoint files are edited by different users"); + // Different size + return false; + } + else + { + //make sure that nothing has been changed except lastEditUsername + tpm1 = TempFileProvider.createTempFile("CIFSContentComparator1", "ppt"); + FileOutputStream os = new FileOutputStream(tpm1); + try + { + slideShow1.write(os); + } + finally + { + try + { + os.close(); + } + catch (IOException ie) + { + // ignore + } + } + tpm2 = TempFileProvider.createTempFile("CIFSContentComparator2", "ppt"); + FileOutputStream os2 = new FileOutputStream(tpm2); + try + { + slideShow2.write(os2); + } + finally + { + try + { + os2.close(); + } + catch (IOException ie) + { + // ignore + } + } + + NPOIFSFileSystem fs1 = new NPOIFSFileSystem(tpm1); + NPOIFSFileSystem fs2 = new NPOIFSFileSystem(tpm2); + + return isContentIdentical(fs1, fs2, excludes); + } + + } + + return true; + } + catch (ContentIOException ce) + { + logger.debug("Unable to compare contents", ce); + return false; + } + catch (IOException e) + { + logger.debug("Unable to compare contents", e); + return false; + } + finally + { + if(tpm1 != null) + { + try + { + tpm1.delete(); + } + catch (Exception e) + { + // ignore + } + } + if(tpm2 != null) + { + try + { + tpm2.delete(); + } + catch (Exception e) + { + // ignore + } + } + if(leftIs != null) + { + try + { + leftIs.close(); + } + catch (IOException e) + { + // Ignore + } + } + } + } + } +} diff --git a/source/java/org/alfresco/filesys/repo/CommandExecutor.java b/source/java/org/alfresco/filesys/repo/CommandExecutor.java index e59475e036..8018cb5a60 100644 --- a/source/java/org/alfresco/filesys/repo/CommandExecutor.java +++ b/source/java/org/alfresco/filesys/repo/CommandExecutor.java @@ -1,22 +1,22 @@ -package org.alfresco.filesys.repo; - -import java.io.IOException; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.filesys.TreeConnection; - -/** - * The Command Executor - executes commands! - */ -public interface CommandExecutor -{ - /** - * Execute the command. - * @param command - * - * @return an object for return or null if there is no return value. - * @throws IOException - */ - public Object execute(SrvSession sess, TreeConnection tree, Command command) throws IOException; -} +package org.alfresco.filesys.repo; + +import java.io.IOException; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.filesys.TreeConnection; + +/** + * The Command Executor - executes commands! + */ +public interface CommandExecutor +{ + /** + * Execute the command. + * @param command + * + * @return an object for return or null if there is no return value. + * @throws IOException + */ + public Object execute(SrvSession sess, TreeConnection tree, Command command) throws IOException; +} diff --git a/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java b/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java index 6dfe125cc9..cf54f1f0db 100644 --- a/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java +++ b/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java @@ -1,391 +1,391 @@ -package org.alfresco.filesys.repo; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.channels.FileChannel; -import java.util.List; - -import org.alfresco.filesys.alfresco.ExtendedDiskInterface; -import org.alfresco.filesys.alfresco.RepositoryDiskInterface; -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; -import org.alfresco.filesys.repo.rules.commands.CreateFileCommand; -import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; -import org.alfresco.filesys.repo.rules.commands.DoNothingCommand; -import org.alfresco.filesys.repo.rules.commands.MoveFileCommand; -import org.alfresco.filesys.repo.rules.commands.OpenFileCommand; -import org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand; -import org.alfresco.filesys.repo.rules.commands.RemoveNoContentFileOnError; -import org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand; -import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; -import org.alfresco.filesys.repo.rules.commands.RestoreFileCommand; -import org.alfresco.filesys.repo.rules.commands.ReturnValueCommand; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.FileFilterMode; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Content Disk Driver Command Executor - *

- * Executes commands against the repository. - */ -public class CommandExecutorImpl implements CommandExecutor -{ - private static Log logger = LogFactory.getLog(CommandExecutorImpl.class); - - // Services go here. - private TransactionService transactionService; - private RepositoryDiskInterface repositoryDiskInterface; - private ExtendedDiskInterface diskInterface; - - - public void init() - { - PropertyCheck.mandatory(this, "transactionService", transactionService); - PropertyCheck.mandatory(this, "diskInterface", diskInterface); - PropertyCheck.mandatory(this, "repositoryDiskInterface", getRepositoryDiskInterface()); - } - - @Override - public Object execute(final SrvSession sess, final TreeConnection tree, final Command command) throws IOException - { - TxnReadState readState = command.getTransactionRequired(); - - Object ret = null; - - // No transaction required. - if(readState == TxnReadState.TXN_NONE) - { - ret = executeInternal(sess, tree, command, null); - } - else - { - // Yes a transaction is required. - RetryingTransactionHelper helper = transactionService.getRetryingTransactionHelper(); - - boolean readOnly = readState == TxnReadState.TXN_READ_ONLY; - - RetryingTransactionCallback cb = new RetryingTransactionCallback() - { - /** - * Perform a set of commands as a unit of transactional work. - * - * @return Return the result of the unit of work - * @throws IOException - */ - public Object execute() throws IOException - { - try - { - return executeInternal(sess, tree, command, null); - } - catch (IOException e) - { - // Ensure original checked IOExceptions get propagated - throw new PropagatingException(e); - } - } - }; - - try - { - ret = helper.doInTransaction(cb, readOnly); - } - catch(PropagatingException pe) - { - if(command instanceof CompoundCommand) - { - if(logger.isDebugEnabled()) - { - logger.debug("error executing command :command" + command, pe); - } - - CompoundCommand c = (CompoundCommand)command; - // Error Callback Here ? - List commands = c.getPostErrorCommands(); - - if(commands != null) - { - for(Command c2 : commands) - { - try - { - executeInternal(sess, tree, c2, ret); - } - catch(Throwable t) - { - logger.warn("caught and ignored exception from error handler", t); - // Swallow exception from error handler. - } - } - } - } - - // Unwrap checked exceptions - throw (IOException) pe.getCause(); - } - } - - /** - * execute post commit commands. - */ - if(command instanceof CompoundCommand) - { - logger.debug("post commit of compound command"); - CompoundCommand c = (CompoundCommand)command; - List commands = c.getPostCommitCommands(); - - if(commands != null) - { - for(Command c2 : commands) - { - // TODO - what about exceptions from post commit? - executeInternal(sess, tree, c2, ret); - } - } - } - - return ret; - } - - /** - * @param sess SrvSession - * @param tree TreeConnection - * @param command Command - * @param result Object - * @return Object - * @throws IOException - */ - private Object executeInternal(SrvSession sess, TreeConnection tree, Command command, Object result) throws IOException - { - FileFilterMode.setClient(ClientHelper.getClient(sess)); - try - { - if(command instanceof CompoundCommand) - { - Object ret = null; - logger.debug("compound command received"); - CompoundCommand x = (CompoundCommand)command; - - for(Command compoundPart : x.getCommands()) - { - logger.debug("running part of compound command"); - Object val = executeInternal(sess, tree, compoundPart, result); - if(val != null) - { - // Return the value from the last command. - ret = val; - } - } - return ret; - } - else if(command instanceof CreateFileCommand) - { - logger.debug("create file command"); - CreateFileCommand create = (CreateFileCommand)command; - return repositoryDiskInterface.createFile(create.getRootNode(), create.getPath(), create.getAllocationSize(), create.isHidden()); - } - else if(command instanceof RestoreFileCommand) - { - logger.debug("restore file command"); - RestoreFileCommand restore = (RestoreFileCommand)command; - return repositoryDiskInterface.restoreFile(sess, tree, restore.getRootNode(), restore.getPath(), restore.getAllocationSize(), restore.getOriginalNodeRef()); - } - else if(command instanceof DeleteFileCommand) - { - logger.debug("delete file command"); - DeleteFileCommand delete = (DeleteFileCommand)command; - return repositoryDiskInterface.deleteFile2(sess, tree, delete.getRootNode(), delete.getPath()); - } - else if(command instanceof OpenFileCommand) - { - logger.debug("open file command"); - OpenFileCommand o = (OpenFileCommand)command; - - OpenFileMode mode = o.getMode(); - return repositoryDiskInterface.openFile(sess, tree, o.getRootNodeRef(), o.getPath(), mode, o.isTruncate()); - - } - else if(command instanceof CloseFileCommand) - { - logger.debug("close file command"); - CloseFileCommand c = (CloseFileCommand)command; - return repositoryDiskInterface.closeFile(tree, c.getRootNodeRef(), c.getPath(), c.getNetworkFile()); - } - else if(command instanceof ReduceQuotaCommand) - { - logger.debug("reduceQuota file command"); - ReduceQuotaCommand r = (ReduceQuotaCommand)command; - repositoryDiskInterface.reduceQuota(sess, tree, r.getNetworkFile()); - } - else if(command instanceof RenameFileCommand) - { - logger.debug("rename command"); - RenameFileCommand rename = (RenameFileCommand)command; - - repositoryDiskInterface.renameFile(rename.getRootNode(), rename.getFromPath(), rename.getToPath(), rename.isSoft(), false); - } - else if(command instanceof MoveFileCommand) - { - logger.debug("move command"); - MoveFileCommand move = (MoveFileCommand)command; - repositoryDiskInterface.renameFile(move.getRootNode(), move.getFromPath(), move.getToPath(), false, move.isMoveAsSystem()); - } - else if(command instanceof CopyContentCommand) - { - if(logger.isDebugEnabled()) - { - logger.debug("Copy content command - copy content"); - } - CopyContentCommand copy = (CopyContentCommand)command; - repositoryDiskInterface.copyContent(copy.getRootNode(), copy.getFromPath(), copy.getToPath()); - } - else if(command instanceof DoNothingCommand) - { - if(logger.isDebugEnabled()) - { - logger.debug("Do Nothing Command - doing nothing"); - } - } - else if(command instanceof ResultCallback) - { - if(logger.isDebugEnabled()) - { - logger.debug("Result Callback"); - } - ResultCallback callback = (ResultCallback)command; - callback.execute(result); - } - else if(command instanceof RemoveTempFileCommand) - { - RemoveTempFileCommand r = (RemoveTempFileCommand)command; - if(logger.isDebugEnabled()) - { - logger.debug("Remove Temp File:" + r.getNetworkFile()); - } - File file = r.getNetworkFile().getFile(); - boolean isDeleted = file.delete(); - - if(!isDeleted) - { - logger.debug("unable to delete temp file:" + r.getNetworkFile() + ", closed="+ r.getNetworkFile().isClosed()); - - /* - * Unable to delete temporary file - * Could be a bug with the file handle not being closed, but yourkit does not - * find anything awry. - * There are reported Windows JVM bugs such as 4715154 ... - */ - FileOutputStream fos = new FileOutputStream(file); - FileChannel outChan = null; - try - { - outChan = fos.getChannel(); - outChan.truncate(0); - } - catch (IOException e) - { - logger.debug("unable to clean up file", e); - } - finally - { - if(outChan != null) - { - try - { - outChan.close(); - } - catch(IOException e){} - } - fos.close(); - } - } - - } - else if(command instanceof ReturnValueCommand) - { - ReturnValueCommand r = (ReturnValueCommand)command; - if(logger.isDebugEnabled()) - { - logger.debug("Return value"); - } - return r.getReturnValue(); - } - else if(command instanceof RemoveNoContentFileOnError) - { - RemoveNoContentFileOnError r = (RemoveNoContentFileOnError)command; - if(logger.isDebugEnabled()) - { - logger.debug("Remove no content file on error"); - } - repositoryDiskInterface.deleteEmptyFile(r.getRootNodeRef(), r.getPath()); - } - } - finally - { - FileFilterMode.clearClient(); - } - - return null; - } - - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - - public TransactionService getTransactionService() - { - return transactionService; - } - - public void setRepositoryDiskInterface(RepositoryDiskInterface repositoryDiskInterface) - { - this.repositoryDiskInterface = repositoryDiskInterface; - } - - public RepositoryDiskInterface getRepositoryDiskInterface() - { - return repositoryDiskInterface; - } - - public void setDiskInterface(ExtendedDiskInterface diskInterface) - { - this.diskInterface = diskInterface; - } - - public ExtendedDiskInterface getDiskInterface() - { - return diskInterface; - } - - /** - * A wrapper for checked exceptions to be passed through the retrying transaction handler. - */ - protected static class PropagatingException extends RuntimeException - { - private static final long serialVersionUID = 1L; - - /** - * @param cause Throwable - */ - public PropagatingException(Throwable cause) - { - super(cause); - } - } - -} +package org.alfresco.filesys.repo; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.util.List; + +import org.alfresco.filesys.alfresco.ExtendedDiskInterface; +import org.alfresco.filesys.alfresco.RepositoryDiskInterface; +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.CreateFileCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.DoNothingCommand; +import org.alfresco.filesys.repo.rules.commands.MoveFileCommand; +import org.alfresco.filesys.repo.rules.commands.OpenFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand; +import org.alfresco.filesys.repo.rules.commands.RemoveNoContentFileOnError; +import org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.commands.RestoreFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReturnValueCommand; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.FileFilterMode; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Content Disk Driver Command Executor + *

+ * Executes commands against the repository. + */ +public class CommandExecutorImpl implements CommandExecutor +{ + private static Log logger = LogFactory.getLog(CommandExecutorImpl.class); + + // Services go here. + private TransactionService transactionService; + private RepositoryDiskInterface repositoryDiskInterface; + private ExtendedDiskInterface diskInterface; + + + public void init() + { + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "diskInterface", diskInterface); + PropertyCheck.mandatory(this, "repositoryDiskInterface", getRepositoryDiskInterface()); + } + + @Override + public Object execute(final SrvSession sess, final TreeConnection tree, final Command command) throws IOException + { + TxnReadState readState = command.getTransactionRequired(); + + Object ret = null; + + // No transaction required. + if(readState == TxnReadState.TXN_NONE) + { + ret = executeInternal(sess, tree, command, null); + } + else + { + // Yes a transaction is required. + RetryingTransactionHelper helper = transactionService.getRetryingTransactionHelper(); + + boolean readOnly = readState == TxnReadState.TXN_READ_ONLY; + + RetryingTransactionCallback cb = new RetryingTransactionCallback() + { + /** + * Perform a set of commands as a unit of transactional work. + * + * @return Return the result of the unit of work + * @throws IOException + */ + public Object execute() throws IOException + { + try + { + return executeInternal(sess, tree, command, null); + } + catch (IOException e) + { + // Ensure original checked IOExceptions get propagated + throw new PropagatingException(e); + } + } + }; + + try + { + ret = helper.doInTransaction(cb, readOnly); + } + catch(PropagatingException pe) + { + if(command instanceof CompoundCommand) + { + if(logger.isDebugEnabled()) + { + logger.debug("error executing command :command" + command, pe); + } + + CompoundCommand c = (CompoundCommand)command; + // Error Callback Here ? + List commands = c.getPostErrorCommands(); + + if(commands != null) + { + for(Command c2 : commands) + { + try + { + executeInternal(sess, tree, c2, ret); + } + catch(Throwable t) + { + logger.warn("caught and ignored exception from error handler", t); + // Swallow exception from error handler. + } + } + } + } + + // Unwrap checked exceptions + throw (IOException) pe.getCause(); + } + } + + /** + * execute post commit commands. + */ + if(command instanceof CompoundCommand) + { + logger.debug("post commit of compound command"); + CompoundCommand c = (CompoundCommand)command; + List commands = c.getPostCommitCommands(); + + if(commands != null) + { + for(Command c2 : commands) + { + // TODO - what about exceptions from post commit? + executeInternal(sess, tree, c2, ret); + } + } + } + + return ret; + } + + /** + * @param sess SrvSession + * @param tree TreeConnection + * @param command Command + * @param result Object + * @return Object + * @throws IOException + */ + private Object executeInternal(SrvSession sess, TreeConnection tree, Command command, Object result) throws IOException + { + FileFilterMode.setClient(ClientHelper.getClient(sess)); + try + { + if(command instanceof CompoundCommand) + { + Object ret = null; + logger.debug("compound command received"); + CompoundCommand x = (CompoundCommand)command; + + for(Command compoundPart : x.getCommands()) + { + logger.debug("running part of compound command"); + Object val = executeInternal(sess, tree, compoundPart, result); + if(val != null) + { + // Return the value from the last command. + ret = val; + } + } + return ret; + } + else if(command instanceof CreateFileCommand) + { + logger.debug("create file command"); + CreateFileCommand create = (CreateFileCommand)command; + return repositoryDiskInterface.createFile(create.getRootNode(), create.getPath(), create.getAllocationSize(), create.isHidden()); + } + else if(command instanceof RestoreFileCommand) + { + logger.debug("restore file command"); + RestoreFileCommand restore = (RestoreFileCommand)command; + return repositoryDiskInterface.restoreFile(sess, tree, restore.getRootNode(), restore.getPath(), restore.getAllocationSize(), restore.getOriginalNodeRef()); + } + else if(command instanceof DeleteFileCommand) + { + logger.debug("delete file command"); + DeleteFileCommand delete = (DeleteFileCommand)command; + return repositoryDiskInterface.deleteFile2(sess, tree, delete.getRootNode(), delete.getPath()); + } + else if(command instanceof OpenFileCommand) + { + logger.debug("open file command"); + OpenFileCommand o = (OpenFileCommand)command; + + OpenFileMode mode = o.getMode(); + return repositoryDiskInterface.openFile(sess, tree, o.getRootNodeRef(), o.getPath(), mode, o.isTruncate()); + + } + else if(command instanceof CloseFileCommand) + { + logger.debug("close file command"); + CloseFileCommand c = (CloseFileCommand)command; + return repositoryDiskInterface.closeFile(tree, c.getRootNodeRef(), c.getPath(), c.getNetworkFile()); + } + else if(command instanceof ReduceQuotaCommand) + { + logger.debug("reduceQuota file command"); + ReduceQuotaCommand r = (ReduceQuotaCommand)command; + repositoryDiskInterface.reduceQuota(sess, tree, r.getNetworkFile()); + } + else if(command instanceof RenameFileCommand) + { + logger.debug("rename command"); + RenameFileCommand rename = (RenameFileCommand)command; + + repositoryDiskInterface.renameFile(rename.getRootNode(), rename.getFromPath(), rename.getToPath(), rename.isSoft(), false); + } + else if(command instanceof MoveFileCommand) + { + logger.debug("move command"); + MoveFileCommand move = (MoveFileCommand)command; + repositoryDiskInterface.renameFile(move.getRootNode(), move.getFromPath(), move.getToPath(), false, move.isMoveAsSystem()); + } + else if(command instanceof CopyContentCommand) + { + if(logger.isDebugEnabled()) + { + logger.debug("Copy content command - copy content"); + } + CopyContentCommand copy = (CopyContentCommand)command; + repositoryDiskInterface.copyContent(copy.getRootNode(), copy.getFromPath(), copy.getToPath()); + } + else if(command instanceof DoNothingCommand) + { + if(logger.isDebugEnabled()) + { + logger.debug("Do Nothing Command - doing nothing"); + } + } + else if(command instanceof ResultCallback) + { + if(logger.isDebugEnabled()) + { + logger.debug("Result Callback"); + } + ResultCallback callback = (ResultCallback)command; + callback.execute(result); + } + else if(command instanceof RemoveTempFileCommand) + { + RemoveTempFileCommand r = (RemoveTempFileCommand)command; + if(logger.isDebugEnabled()) + { + logger.debug("Remove Temp File:" + r.getNetworkFile()); + } + File file = r.getNetworkFile().getFile(); + boolean isDeleted = file.delete(); + + if(!isDeleted) + { + logger.debug("unable to delete temp file:" + r.getNetworkFile() + ", closed="+ r.getNetworkFile().isClosed()); + + /* + * Unable to delete temporary file + * Could be a bug with the file handle not being closed, but yourkit does not + * find anything awry. + * There are reported Windows JVM bugs such as 4715154 ... + */ + FileOutputStream fos = new FileOutputStream(file); + FileChannel outChan = null; + try + { + outChan = fos.getChannel(); + outChan.truncate(0); + } + catch (IOException e) + { + logger.debug("unable to clean up file", e); + } + finally + { + if(outChan != null) + { + try + { + outChan.close(); + } + catch(IOException e){} + } + fos.close(); + } + } + + } + else if(command instanceof ReturnValueCommand) + { + ReturnValueCommand r = (ReturnValueCommand)command; + if(logger.isDebugEnabled()) + { + logger.debug("Return value"); + } + return r.getReturnValue(); + } + else if(command instanceof RemoveNoContentFileOnError) + { + RemoveNoContentFileOnError r = (RemoveNoContentFileOnError)command; + if(logger.isDebugEnabled()) + { + logger.debug("Remove no content file on error"); + } + repositoryDiskInterface.deleteEmptyFile(r.getRootNodeRef(), r.getPath()); + } + } + finally + { + FileFilterMode.clearClient(); + } + + return null; + } + + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + + public TransactionService getTransactionService() + { + return transactionService; + } + + public void setRepositoryDiskInterface(RepositoryDiskInterface repositoryDiskInterface) + { + this.repositoryDiskInterface = repositoryDiskInterface; + } + + public RepositoryDiskInterface getRepositoryDiskInterface() + { + return repositoryDiskInterface; + } + + public void setDiskInterface(ExtendedDiskInterface diskInterface) + { + this.diskInterface = diskInterface; + } + + public ExtendedDiskInterface getDiskInterface() + { + return diskInterface; + } + + /** + * A wrapper for checked exceptions to be passed through the retrying transaction handler. + */ + protected static class PropagatingException extends RuntimeException + { + private static final long serialVersionUID = 1L; + + /** + * @param cause Throwable + */ + public PropagatingException(Throwable cause) + { + super(cause); + } + } + +} diff --git a/source/java/org/alfresco/filesys/repo/ContentComparator.java b/source/java/org/alfresco/filesys/repo/ContentComparator.java index f3b9a01090..151ed002f7 100644 --- a/source/java/org/alfresco/filesys/repo/ContentComparator.java +++ b/source/java/org/alfresco/filesys/repo/ContentComparator.java @@ -1,21 +1,21 @@ -package org.alfresco.filesys.repo; - -import java.io.File; -import java.io.InputStream; - -import org.alfresco.service.cmr.repository.ContentReader; - -public interface ContentComparator -{ - /** - * Are the two content items equal? - *

- * For most cases a simple binary comparison is sufficient but some mimetypes - * trivial changes need to be discarded. - *

- * @param existingContent - * @param file file - * @return true content is equal, false content is different. - */ - boolean isContentEqual(ContentReader existingContent, File file); -} +package org.alfresco.filesys.repo; + +import java.io.File; +import java.io.InputStream; + +import org.alfresco.service.cmr.repository.ContentReader; + +public interface ContentComparator +{ + /** + * Are the two content items equal? + *

+ * For most cases a simple binary comparison is sufficient but some mimetypes + * trivial changes need to be discarded. + *

+ * @param existingContent + * @param file file + * @return true content is equal, false content is different. + */ + boolean isContentEqual(ContentReader existingContent, File file); +} diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskCallback.java b/source/java/org/alfresco/filesys/repo/ContentDiskCallback.java index 7cf2369434..5749b5f39c 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskCallback.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskCallback.java @@ -1,76 +1,76 @@ -package org.alfresco.filesys.repo; - -import java.io.IOException; - -import org.alfresco.filesys.config.ServerConfigurationBean; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.core.DeviceContext; -import org.alfresco.jlan.server.core.DeviceContextException; -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.FileOpenParams; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.jlan.server.filesys.SearchContext; -import org.alfresco.jlan.server.filesys.TreeConnection; - -/** - * Called by the NonTransactionalContentDiskDriver to advise of operations completed - * by the ContentDiskInterface. - */ -public interface ContentDiskCallback -{ - public void getFileInformation(SrvSession sess, TreeConnection tree, - String path, FileInfo info); - - public void fileExists(SrvSession sess, TreeConnection tree, String path, int fileExists); - - public void treeOpened(SrvSession sess, TreeConnection tree); - - public void treeClosed(SrvSession sess, TreeConnection tree); - - public void closeFile(SrvSession sess, TreeConnection tree, - NetworkFile param); - - public void createDirectory(SrvSession sess, TreeConnection tree, - FileOpenParams params); - - public void createFile(SrvSession sess, TreeConnection tree, - FileOpenParams params, NetworkFile newFile); - - - public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir); - - public void deleteFile(SrvSession sess, TreeConnection tree, String name); - - public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file); - - public void isReadOnly(SrvSession sess, DeviceContext ctx, boolean isReadOnly); - - public void openFile(SrvSession sess, TreeConnection tree, - FileOpenParams param, NetworkFile openFile); - - public void readFile(SrvSession sess, TreeConnection tree, NetworkFile file, - byte[] buf, int bufPos, int siz, long filePos, int readSize); - - public void renameFile(SrvSession sess, TreeConnection tree, - String oldPath, String newPath); - - public void seekFile(SrvSession sess, TreeConnection tree, - NetworkFile file, long pos, int typ) throws IOException; - - public void setFileInformation(SrvSession sess, TreeConnection tree, - String name, FileInfo info) throws IOException; - - public void startSearch(SrvSession sess, TreeConnection tree, - String searchPath, int attrib, SearchContext context); - - public void truncateFile(SrvSession sess, TreeConnection tree, - NetworkFile file, long siz); - - - public void writeFile(SrvSession sess, TreeConnection tree, - NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff, int writeSize); - - public void registerContext(DeviceContext ctx, ServerConfigurationBean serverConfig) - throws DeviceContextException; - -} +package org.alfresco.filesys.repo; + +import java.io.IOException; + +import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.TreeConnection; + +/** + * Called by the NonTransactionalContentDiskDriver to advise of operations completed + * by the ContentDiskInterface. + */ +public interface ContentDiskCallback +{ + public void getFileInformation(SrvSession sess, TreeConnection tree, + String path, FileInfo info); + + public void fileExists(SrvSession sess, TreeConnection tree, String path, int fileExists); + + public void treeOpened(SrvSession sess, TreeConnection tree); + + public void treeClosed(SrvSession sess, TreeConnection tree); + + public void closeFile(SrvSession sess, TreeConnection tree, + NetworkFile param); + + public void createDirectory(SrvSession sess, TreeConnection tree, + FileOpenParams params); + + public void createFile(SrvSession sess, TreeConnection tree, + FileOpenParams params, NetworkFile newFile); + + + public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir); + + public void deleteFile(SrvSession sess, TreeConnection tree, String name); + + public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file); + + public void isReadOnly(SrvSession sess, DeviceContext ctx, boolean isReadOnly); + + public void openFile(SrvSession sess, TreeConnection tree, + FileOpenParams param, NetworkFile openFile); + + public void readFile(SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buf, int bufPos, int siz, long filePos, int readSize); + + public void renameFile(SrvSession sess, TreeConnection tree, + String oldPath, String newPath); + + public void seekFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long pos, int typ) throws IOException; + + public void setFileInformation(SrvSession sess, TreeConnection tree, + String name, FileInfo info) throws IOException; + + public void startSearch(SrvSession sess, TreeConnection tree, + String searchPath, int attrib, SearchContext context); + + public void truncateFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long siz); + + + public void writeFile(SrvSession sess, TreeConnection tree, + NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff, int writeSize); + + public void registerContext(DeviceContext ctx, ServerConfigurationBean serverConfig) + throws DeviceContextException; + +} diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java index 73fa0491aa..5ec5638a07 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java @@ -1,3273 +1,3273 @@ -package org.alfresco.filesys.repo; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.net.InetAddress; -import java.nio.charset.Charset; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.filesys.alfresco.AlfrescoContext; -import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; -import org.alfresco.filesys.alfresco.ExtendedDiskInterface; -import org.alfresco.filesys.alfresco.PseudoFileOverlayImpl; -import org.alfresco.filesys.alfresco.RepositoryDiskInterface; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.core.DeviceContext; -import org.alfresco.jlan.server.core.DeviceContextException; -import org.alfresco.jlan.server.filesys.AccessDeniedException; -import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException; -import org.alfresco.jlan.server.filesys.DiskDeviceContext; -import org.alfresco.jlan.server.filesys.DiskFullException; -import org.alfresco.jlan.server.filesys.DiskInterface; -import org.alfresco.jlan.server.filesys.DiskSizeInterface; -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.FileName; -import org.alfresco.jlan.server.filesys.FileOpenParams; -import org.alfresco.jlan.server.filesys.FileStatus; -import org.alfresco.jlan.server.filesys.IOControlNotImplementedException; -import org.alfresco.jlan.server.filesys.IOCtlInterface; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.jlan.server.filesys.PermissionDeniedException; -import org.alfresco.jlan.server.filesys.SearchContext; -import org.alfresco.jlan.server.filesys.SrvDiskInfo; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.jlan.server.filesys.cache.FileState; -import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; -import org.alfresco.jlan.server.filesys.pseudo.PseudoNetworkFile; -import org.alfresco.jlan.server.filesys.quota.QuotaManager; -import org.alfresco.jlan.server.filesys.quota.QuotaManagerException; -import org.alfresco.jlan.server.locking.FileLockingInterface; -import org.alfresco.jlan.server.locking.LockManager; -import org.alfresco.jlan.server.locking.OpLockInterface; -import org.alfresco.jlan.server.locking.OpLockManager; -import org.alfresco.jlan.smb.SMBException; -import org.alfresco.jlan.smb.server.SMBServer; -import org.alfresco.jlan.smb.server.SMBSrvSession; -import org.alfresco.jlan.util.DataBuffer; -import org.alfresco.jlan.util.MemorySize; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.content.encoding.ContentCharsetFinder; -import org.alfresco.repo.content.filestore.FileContentReader; -import org.alfresco.repo.model.filefolder.HiddenAspect; -import org.alfresco.repo.node.archive.NodeArchiveService; -import org.alfresco.repo.node.archive.RestoreNodeReport; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.NodeLockedException; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.MimetypeService; -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.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.PropertyCheck; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.config.ConfigElement; - -/** - * Alfresco Content repository filesystem driver class - *

- * Provides a JLAN ContentDiskDriver for various JLAN protocols - * such as SMB/CIFS, NFS and FTP. - * - */ -public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedDiskInterface, - DiskInterface, - DiskSizeInterface, - IOCtlInterface, - RepositoryDiskInterface, - OpLockInterface, - FileLockingInterface -{ - // Logging - private static final Log logger = LogFactory.getLog(ContentDiskDriver2.class); - - private static final Log readLogger = LogFactory.getLog("org.alfresco.filesys.repo.ContentDiskDriver2.Read"); - private static final Log writeLogger = LogFactory.getLog("org.alfresco.filesys.repo.ContentDiskDriver2.Write"); - - // Services and helpers - private CifsHelper cifsHelper; - private NamespaceService namespaceService; - private NodeService nodeService; - private SearchService searchService; - private ContentService contentService; - private MimetypeService mimetypeService; - private PermissionService permissionService; - private FileFolderService fileFolderService; - private LockService lockService; - private CheckOutCheckInService checkOutCheckInService; - private AuthenticationContext authContext; - private AuthenticationService authService; - private BehaviourFilter policyBehaviourFilter; - private NodeMonitorFactory m_nodeMonitorFactory; - private ContentComparator contentComparator; - private NodeArchiveService nodeArchiveService; - private HiddenAspect hiddenAspect; - private LockKeeper lockKeeper; - - // TODO Should not be here - should be specific to a context. - private boolean isLockedFilesAsOffline; - - /** - * - */ - public void init() - { - PropertyCheck.mandatory(this, "checkOutCheckInService", checkOutCheckInService); - PropertyCheck.mandatory(this, "cifsHelper", cifsHelper); - PropertyCheck.mandatory(this, "namespaceService", namespaceService); - PropertyCheck.mandatory(this, "nodeService", nodeService); - PropertyCheck.mandatory(this, "searchService", searchService); - PropertyCheck.mandatory(this, "contentService", contentService); - PropertyCheck.mandatory(this, "mimetypeService", mimetypeService); - PropertyCheck.mandatory(this, "permissionService", permissionService); - PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); - PropertyCheck.mandatory(this, "lockService",lockService); - PropertyCheck.mandatory(this, "authContext", authContext); - PropertyCheck.mandatory(this, "authService", authService); - PropertyCheck.mandatory(this, "policyBehaviourFilter", policyBehaviourFilter); - PropertyCheck.mandatory(this, "m_nodeMonitorFactory", m_nodeMonitorFactory); - PropertyCheck.mandatory(this, "ioControlHandler", ioControlHandler); - PropertyCheck.mandatory(this, "contentComparator", getContentComparator()); - PropertyCheck.mandatory(this, "nodeArchiveService", nodeArchiveService); - PropertyCheck.mandatory(this, "hiddenAspect", hiddenAspect); - PropertyCheck.mandatory(this, "lockKeeper", lockKeeper); - PropertyCheck.mandatory(this, "deletePseudoFileCache", deletePseudoFileCache); - } - - /** - * Return the CIFS helper - * - * @return CifsHelper - */ - public final CifsHelper getCifsHelper() - { - return this.cifsHelper; - } - - /** - * Return the authentication service - * - * @return AuthenticationService - */ - public final AuthenticationService getAuthenticationService() - { - return authService; - } - - /** - * Return the authentication context - * - * @return AuthenticationContext - */ - public final AuthenticationContext getAuthenticationContext() { - return authContext; - } - - /** - * Return the node service - * - * @return NodeService - */ - public final NodeService getNodeService() - { - return this.nodeService; - } - - /** - * Return the content service - * - * @return ContentService - */ - public final ContentService getContentService() - { - return this.contentService; - } - - /** - * Return the namespace service - * - * @return NamespaceService - */ - public final NamespaceService getNamespaceService() - { - return this.namespaceService; - } - - /** - * Return the search service - * - * @return SearchService - */ - public final SearchService getSearchService(){ - return this.searchService; - } - - /** - * Return the file folder service - * - * @return FileFolderService - */ - public final FileFolderService getFileFolderService() { - return this.fileFolderService; - } - - /** - * Return the permission service - * - * @return PermissionService - */ - public final PermissionService getPermissionService() { - return this.permissionService; - } - - /** - * Return the lock service - * - * @return LockService - */ - public final LockService getLockService() { - return lockService; - } - - /** - * Get the policy behaviour filter, used to inhibit versioning on a per transaction basis - */ - public BehaviourFilter getPolicyFilter() - { - return policyBehaviourFilter; - } - - - /** - * @param contentService the content service - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * @param namespaceService the namespace service - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param searchService the search service - */ - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - /** - * Set the permission service - * - * @param permissionService PermissionService - */ - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - /** - * Set the authentication context - * - * @param authContext AuthenticationContext - */ - public void setAuthenticationContext(AuthenticationContext authContext) - { - this.authContext = authContext; - } - - /** - * Set the authentication service - * - * @param authService AuthenticationService - */ - public void setAuthenticationService(AuthenticationService authService) - { - this.authService = authService; - } - - /** - * Set the file folder service - * - * @param fileService FileFolderService - */ - public void setFileFolderService(FileFolderService fileService) - { - fileFolderService = fileService; - } - - /** - * @param mimetypeService service for helping with mimetypes and encoding - */ - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } - - /** - * Set the node monitor factory - * - * @param nodeMonitorFactory NodeMonitorFactory - */ - public void setNodeMonitorFactory(NodeMonitorFactory nodeMonitorFactory) { - m_nodeMonitorFactory = nodeMonitorFactory; - } - - - /** - * Set the lock service - * - * @param lockService LockService - */ - public void setLockService(LockService lockService) { - this.lockService = lockService; - } - - /** - * Set the policy behaviour filter, used to inhibit versioning on a per transaction basis - * - * @param policyFilter PolicyBehaviourFilter - */ - public void setPolicyFilter(BehaviourFilter policyFilter) - { - this.policyBehaviourFilter = policyFilter; - } - - /** - * @param hiddenAspect - */ - public void setHiddenAspect(HiddenAspect hiddenAspect) - { - this.hiddenAspect = hiddenAspect; - } - - /** - * @param lockKeeper lockKeeper - */ - public void setAlfrescoLockKeeper(LockKeeper lockKeeper) - { - this.lockKeeper = lockKeeper; - } - - // Configuration key names - - private static final String KEY_STORE = "store"; - private static final String KEY_ROOT_PATH = "rootPath"; - private static final String KEY_RELATIVE_PATH = "relativePath"; - - /** - * Parse and validate the parameter string and create a device context object for this instance - * of the shared device. The same DeviceInterface implementation may be used for multiple - * shares. - *

- * @deprecated - no longer used. Construction of context is via spring now. - * @param deviceName The name of the device - * @param cfg ConfigElement the configuration of the device context. - * @return DeviceContext - * @exception DeviceContextException - */ - public DeviceContext createContext(String deviceName, ConfigElement cfg) throws DeviceContextException - { - logger.error("Obsolete method called"); - throw new DeviceContextException("Obsolete Method called"); - } - - /* - * Register context implementation - *

- * Results in various obscure bits and pieces being initialised, most importantly the - * calculation of the root node ref. - *

- * There's a load of initialisation that needs to be moved out of this method, like the - * instantiation of the lock manager, quota manager and node monitor. - */ - public void registerContext(DeviceContext ctx) throws DeviceContextException - { - logger.debug("registerContext"); - super.registerContext(ctx); - - final ContentContext context = (ContentContext)ctx; - - final String rootPath = context.getRootPath(); - final String storeValue = context.getStoreName(); - - /** - * Work using the repo needs to run as system. - */ - RunAsWork runAsSystem = new RunAsWork() - { - @Override - public Void doWork() throws Exception - { - StoreRef storeRef = new StoreRef(storeValue); - - // Connect to the repo and ensure that the store exists - - if (! nodeService.exists(storeRef)) - { - throw new DeviceContextException("Store not created prior to application startup: " + storeRef); - } - - NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); - - // Find the root node for this device - List nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPath, null, namespaceService, false); - - NodeRef rootNodeRef = null; - - if (nodeRefs.size() > 1) - { - throw new DeviceContextException("Multiple possible roots for device: \n" + - " root path: " + rootPath + "\n" + - " results: " + nodeRefs); - } - else if (nodeRefs.size() == 0) - { - // Nothing found - throw new DeviceContextException("No root found for device: \n" + - " root path: " + rootPath); - } - else - { - // We found the root node ref - rootNodeRef = nodeRefs.get(0); - } - - // Check if a relative path has been specified - String relPath = context.getRelativePath(); - - try - { - if ( relPath != null && relPath.length() > 0) - { - // Find the node and validate that the relative path is to a folder - NodeRef relPathNode = cifsHelper.getNodeRef( rootNodeRef, relPath); - - if ( cifsHelper.isDirectory( relPathNode) == false) - { - throw new DeviceContextException("Relative path is not a folder, " + relPath); - } - - // Use the relative path node as the root of the filesystem - rootNodeRef = relPathNode; - } - else - { - // Make sure the default root node is a folder - if ( cifsHelper.isDirectory( rootNodeRef) == false) - { - throw new DeviceContextException("Root node is not a folder type node"); - } - } - } - catch (Exception ex) - { - if(logger.isDebugEnabled()) - { - logger.debug("Error during create context", ex); - } - throw new DeviceContextException("Unable to find root node.", ex); - } - - // Record the root node ref - if(logger.isDebugEnabled()) - { - logger.debug("set root node ref:" + rootNodeRef); - } - context.setRootNodeRef(rootNodeRef); - - return null; - } - }; - - /** - * Run the above code as system - in particular resolves root node ref. - */ - AuthenticationUtil.runAs(runAsSystem, AuthenticationUtil.getSystemUserName()); - - - /* - * Now we have some odds and ends below that should really be configured elsewhere - */ - - // Check if locked files should be marked as offline - if ( context.getOfflineFiles() ) - { - // Enable marking locked files as offline - isLockedFilesAsOffline = true; - logger.info("Locked files will be marked as offline"); - } - - // Enable file state caching - -// context.enableStateCache(serverConfig, true); -// context.getStateCache().setCaseSensitive( false); - - logger.debug("initialise the node monitor"); - // Install the node service monitor - if ( !context.getDisableNodeMonitor() && m_nodeMonitorFactory != null) - { - NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor(context); - context.setNodeMonitor( nodeMonitor); - } - - logger.debug("initialise the file state lock manager"); - - - // Check if oplocks are enabled - - if ( context.getDisableOplocks() == true) - { - logger.warn("Oplock support disabled for filesystem " + context.getDeviceName()); - } - - // Start the quota manager, if enabled - if ( context.hasQuotaManager()) - { - try - { - // Start the quota manager - context.getQuotaManager().startManager( this, context); - logger.info("Quota manager enabled for filesystem"); - } - catch ( QuotaManagerException ex) - { - logger.error("Failed to start quota manager", ex); - } - } - - // TODO mode to spring - PseudoFileOverlayImpl ps = new PseudoFileOverlayImpl(); - ps.setContext(context); - ps.setNodeService(nodeService); - ps.setSysAdminParams(context.getSysAdminParams()); - ps.setDeletePseudoFileCache(deletePseudoFileCache); - context.setPseudoFileOverlay(ps); - ps.init(); - } - - /** - * Determine if the disk device is read-only. - * - * @param sess Server session - * @param ctx Device context - * @return boolean - * @exception java.io.IOException If an error occurs. - */ - public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException - { - if(logger.isDebugEnabled()) - { - logger.debug("isReadOnly"); - } - return !m_transactionService.getAllowWrite(); - } - - /** - * Get the file information for the specified file. - * - * @param session Server session - * @param tree Tree connection - * @param path File name/path that information is required for. - * @return File information if valid, else null - * @exception java.io.IOException The exception description. - */ - public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException - { - if(logger.isDebugEnabled()) - { - logger.debug("getFileInformation:" + path + ", session:" + session.getUniqueId()); - } - ContentContext ctx = (ContentContext) tree.getContext(); - - boolean readOnly = !m_transactionService.getAllowWrite(); - - if ( path == null || path.length() == 0) - { - path = FileName.DOS_SEPERATOR_STR; - } - - String infoPath = path; - - try - { - FileInfo finfo = null; - - // Is the node a pseudo file ? - if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) - { - String[] paths = FileName.splitPath(path); - // lookup parent directory - NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); - - // Check whether we are opening a pseudo file - if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) - { - PseudoFile pfile = ctx.getPseudoFileOverlay().getPseudoFile(dirNodeRef, paths[1]); - if(logger.isDebugEnabled()) - { - if (pfile != null) - { - logger.debug("returning psuedo file details:" + pfile); - } - else - { - logger.debug("Try to return deleted pseudo file :" + paths[1]); - } - } - if (pfile != null) - { - return pfile.getFileInfo(); - } - else - { - throw new FileNotFoundException("The pseudo file was deleted"); - } - } - } - - // no - this is not a specially named pseudo file. - NodeRef nodeRef = getNodeForPath(tree, infoPath); - - if ( nodeRef != null) - { - // Get the file information for the node - - finfo = getCifsHelper().getFileInformation(nodeRef, readOnly, isLockedFilesAsOffline); - - /** - * Special processing for root node - */ - if(path.equals(FileName.DOS_SEPERATOR_STR)) - { - finfo.setFileName(""); - } - - // DEBUG - if ( logger.isDebugEnabled()) - { - logger.debug("getFileInformation found nodeRef for nodeRef :"+ nodeRef + ", path: " + path); - } - - // Moved to CIFS Helper -// // Set the file id from the node's DBID -// long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); -// finfo.setFileId((int) (id & 0xFFFFFFFFL)); - } - - // Return the file information or null if the node ref does not exist - return finfo; - } - catch (FileNotFoundException e) - { - // Debug - - if (logger.isDebugEnabled()) - { - // exception not logged - cifs does lots of these - logger.debug("Get file info - file not found, " + path); - } - throw e; - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Get file info - access denied, " + path, ex); - } - - // Convert to a filesystem access denied status - throw new AccessDeniedException("Get file information " + path); - } - catch (AlfrescoRuntimeException ex) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Get file info error" + path, ex); - } - - // Convert to a general I/O exception - throw new IOException("Get file information " + path, ex); - } - } - - /** - * Start a new search on the filesystem using the specified searchPath that may contain - * wildcards. - * - * @param session Server session - * @param tree Tree connection - * @param searchPath File(s) to search for, may include wildcards. - * @param attributes Attributes of the file(s) to search for, see class SMBFileAttribute. - * @return SearchContext - * @exception java.io.FileNotFoundException If the search could not be started. - */ - public SearchContext startSearch(SrvSession session, TreeConnection tree, String searchPath, int attributes) throws FileNotFoundException - { - if(logger.isDebugEnabled()) - { - logger.debug("startSearch: "+ searchPath + ", session:" + session.getUniqueId()); - } - // Access the device context - - ContentContext ctx = (ContentContext) tree.getContext(); - - try - { - String searchFileSpec = searchPath; - - NodeRef searchRootNodeRef = ctx.getRootNode(); - - String[] paths = FileName.splitPath(searchPath); - String dotPath = paths[0]; - - // lookup parent directory - NodeRef dirNodeRef = getNodeForPath(tree, dotPath); - if(dirNodeRef != null) - { - searchRootNodeRef = dirNodeRef; - searchFileSpec = paths[1]; - } - - // Convert the all files wildcard - if ( searchFileSpec.equals( "*.*")) - { - searchFileSpec = "*"; - } - - // Debug - long startTime = 0L; - if ( logger.isDebugEnabled()) - { - startTime = System.currentTimeMillis(); - } - - // Perform the search - - logger.debug("Call repo to do search"); - - List results = getCifsHelper().getNodeRefs(searchRootNodeRef, searchFileSpec); - // Debug - if ( logger.isDebugEnabled()) - { - long endTime = System.currentTimeMillis(); - if (( endTime - startTime) > 500) - { - logger.debug("Search for searchPath=" + searchPath + ", searchSpec=" + searchFileSpec + ", searchRootNode=" + searchRootNodeRef + " took " - + ( endTime - startTime) + "ms results=" + results.size()); - } - } - - /** - * Search pseudo files if they are enabled - */ - PseudoFileList pseudoList = null; - if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) - { - logger.debug("search pseudo files"); - pseudoList = ctx.getPseudoFileOverlay().searchPseudoFiles(dirNodeRef, searchFileSpec); - } - - DotDotContentSearchContext searchCtx = new DotDotContentSearchContext(getCifsHelper(), results, searchFileSpec, pseudoList, paths[0], isLockedFilesAsOffline); - - FileInfo dotInfo = getCifsHelper().getFileInformation(searchRootNodeRef, false, isLockedFilesAsOffline); - - if ( searchPath.equals( FileName.DOS_SEPERATOR_STR)) { - // Searching the root folder, re-use the search folder file information for the '..' pseudo entry - FileInfo dotDotInfo = new FileInfo(); - dotDotInfo.copyFrom(dotInfo); - searchCtx.setDotInfo(dotInfo); - searchCtx.setDotDotInfo( dotDotInfo); - } - else - { - String[] parent = FileName.splitPath(dotPath); - NodeRef parentNodeRef = getNodeForPath(tree, parent[0]); - if(parentNodeRef != null) - { - FileInfo dotDotInfo = getCifsHelper().getFileInformation(parentNodeRef, false, isLockedFilesAsOffline); - searchCtx.setDotDotInfo(dotDotInfo); - } - - // Searching a normal, non root, folder - // Need to set dot and dotdot - searchCtx.setDotInfo(dotInfo); - - } - - // Debug - if (logger.isDebugEnabled()) - { - logger.debug("Started search: search path=" + searchPath + " attributes=" + attributes + ", ctx=" + searchCtx); - } - - // TODO -- - // Need to resolve the file info here so it's within the transaction boundary. - - return searchCtx; - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Start search - access denied, " + searchPath); - } - - // Convert to a file not found status - - throw new FileNotFoundException("Start search " + searchPath); - } - catch (AlfrescoRuntimeException ex) - { - // This is an error even though we "handle" it here. - - if ( logger.isErrorEnabled()) - { - logger.error("Exception in Start search", ex); - } - - // Convert to a file not found status - - throw new FileNotFoundException("Start search " + searchPath); - } - } - - /** - * Check if the specified file exists, and whether it is a file or directory. - * - * - * @param session Server session - * @param tree Tree connection - * @param name the path of the file - * @return FileStatus (0: NotExist, 1 : FileExist, 2: DirectoryExists) - * @see FileStatus - */ - public int fileExists(SrvSession session, TreeConnection tree, String name) - { - if(logger.isDebugEnabled()) - { - logger.debug("fileExists:" + name + ", session:" + session.getUniqueId()); - } - ContentContext ctx = (ContentContext) tree.getContext(); - int status = FileStatus.Unknown; - try - { - if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) - { - String[] paths = FileName.splitPath(name); - // lookup parent directory - NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); - // Check whether we are opening a pseudo file - if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) - { - return FileStatus.FileExists; - } - } - - // Get the file information to check if the file/folder exists - FileInfo info = getFileInformation(session, tree, name); - if (info.isDirectory()) - { - if(logger.isDebugEnabled()) - { - logger.debug("is directory"); - } - status = FileStatus.DirectoryExists; - } - else - { - if(logger.isDebugEnabled()) - { - logger.debug("is file"); - } - status = FileStatus.FileExists; - } - - if (logger.isDebugEnabled()) - { - logger.debug("File status determined: name=" + name + " status=" + status); - } - - return status; - } - catch (FileNotFoundException e) - { - if(logger.isDebugEnabled()) - { - logger.debug("file does not exist"); - } - status = FileStatus.NotExist; - return status; - } - catch (IOException e) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("File exists error, " + name, e); - } - - status = FileStatus.NotExist; - return status; - } - - } - - /** - * Open a file or folder - obsolete implementation. - * - * @param session SrvSession - * @param tree TreeConnection - * @param params FileOpenParams - * @return NetworkFile - * @exception IOException - */ - public NetworkFile openFile(SrvSession session, TreeConnection tree, FileOpenParams params) throws IOException - { - // obsolete - logger.error("Obsolete method called"); - throw new AlfrescoRuntimeException("obsolete method called"); - } - - /** - * Create a new file on the file system. - * - * @param sess Server session - * @param tree Tree connection - * @param params File create parameters - * @return NetworkFile - * @exception java.io.IOException If an error occurs. - */ - public NetworkFile createFile(SrvSession sess, final TreeConnection tree, final FileOpenParams params) throws IOException - { - // Obsolete - logger.error("Obsolete method called"); - throw new AlfrescoRuntimeException("obsolete method called"); - } - - /** - * Create a new directory on this file system. - * - * - * @param sess Server session - * @param tree Tree connection. - * @param params Directory create parameters - * @exception java.io.IOException If an error occurs. - */ - public void createDirectory(SrvSession sess, final TreeConnection tree, final FileOpenParams params) throws IOException - { - final ContentContext ctx = (ContentContext) tree.getContext(); - - if (logger.isDebugEnabled()) - { - logger.debug("createDirectory :" + params); - } - - try - { - NodeRef dirNodeRef; - String folderName; - - String path = params.getPath(); - - String[] paths = FileName.splitPath(path); - - if (paths[0] != null && paths[0].length() > 1) - { - // lookup parent directory - dirNodeRef = getNodeForPath(tree, paths[0]); - folderName = paths[1]; - } - else - { - dirNodeRef = ctx.getRootNode(); - folderName = path; - } - - if(dirNodeRef == null) - { - throw new IOException("Create directory parent folder not found" + params.getFullPath()); - } - - NodeRef nodeRef = getCifsHelper().createNode(dirNodeRef, folderName, ContentModel.TYPE_FOLDER); - - - if (logger.isDebugEnabled()) - { - logger.debug("Created directory: path=" + params.getPath() + " file open params=" + params + " node=" + nodeRef); - } - - // void return - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Create directory - access denied, " + params.getFullPath()); - } - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Create directory " + params.getFullPath()); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Create directory error", ex); - } - - // Convert to a general I/O exception - - throw new IOException("Create directory " + params.getFullPath(), ex); - } - } - - /** - * Delete the directory from the filesystem. - *

- * The directory must be empty in order to be able to delete ity - * - * @param session Server session - * @param tree Tree connection - * @param dir Directory name. - * @exception java.io.IOException The exception description. - */ - public void deleteDirectory(SrvSession session, TreeConnection tree, final String dir) throws IOException - { - // get the device root - - if (logger.isDebugEnabled()) - { - logger.debug("deleteDirectory: " + dir + ", session:" + session.getUniqueId()); - } - - ContentContext ctx = (ContentContext) tree.getContext(); - final NodeRef deviceRootNodeRef = ctx.getRootNode(); - - try - { - // Get the node for the folder - - NodeRef nodeRef = getCifsHelper().getNodeRef(deviceRootNodeRef, dir); - if (fileFolderService.exists(nodeRef)) - { - // Check if the folder is empty - // Get pseudo files - PseudoFileList pseudoFileList = new PseudoFileList(); - if (session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) - { - pseudoFileList = ctx.getPseudoFileOverlay().searchPseudoFiles(nodeRef, "*"); - } - if (getCifsHelper().isFolderEmpty(nodeRef) && pseudoFileList.isEmpty()) - { - // Delete the folder node - fileFolderService.delete(nodeRef); - } - else - { - throw new DirectoryNotEmptyException( dir); - } - } - - // Debug - - if (logger.isDebugEnabled()) - { - logger.debug("Deleted directory: directory=" + dir + " node=" + nodeRef); - } - // void return - } - catch (FileNotFoundException e) - { - // Debug - - if (logger.isDebugEnabled()) - { - logger.debug("Delete directory - file not found, " + dir); - } - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Delete directory - access denied, " + dir); - } - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Delete directory, access denied :" + dir); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("Delete directory", ex); - - // Convert to a general I/O exception - - throw new IOException("Delete directory " + dir, ex); - } - } - - /** - * Flush any buffered output for the specified file. - * - * @param session Server session - * @param tree Tree connection - * @param file Network file context. - * @exception java.io.IOException The exception description. - */ - public void flushFile(SrvSession session, TreeConnection tree, NetworkFile file) throws IOException - { - // Debug - - ContentContext ctx = (ContentContext) tree.getContext(); - - if ( logger.isDebugEnabled()) - { - logger.debug("Flush file=" + file.getFullName()+ ", session:" + session.getUniqueId()); - } - - // Flush the file data - file.flushFile(); - } - - /** - * Close the file. - * - * @param session Server session - * @param tree Tree connection. - * @param file Network file context. - * - * @exception java.io.IOException If an error occurs. - */ - public void closeFile(SrvSession session, TreeConnection tree, final NetworkFile file) throws IOException - { - throw new AlfrescoRuntimeException("obsolete method called"); - } - - public void deleteFile(final SrvSession session, final TreeConnection tree, final String name) throws IOException - { - throw new AlfrescoRuntimeException("obsolete method called"); - } - - /** - * Delete the specified file. - * - * @param session Server session - * @param tree Tree connection - * @param rootNode Root node - * @param path NetworkFile - * @exception java.io.IOException The exception description. - * @return NodeRef of deletedFile - */ - public NodeRef deleteFile2(final SrvSession session, final TreeConnection tree, NodeRef rootNode, String path) throws IOException - { - // Get the device context - - final ContentContext ctx = (ContentContext) tree.getContext(); - - if(logger.isDebugEnabled()) - { - logger.debug("deleteFile:" + path + ", session:" + session.getUniqueId()); - } - - try - { - if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) - { - String[] paths = FileName.splitPath(path); - // lookup parent directory - NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); - - // Check whether we are closing a pseudo file - if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) - { - // pseudo delete a pseudo file - ctx.getPseudoFileOverlay().delete(dirNodeRef, paths[1]); - return null; - } - } - - // Check if there is a quota manager enabled, if so then we need to save the current file size - - final QuotaManager quotaMgr = ctx.getQuotaManager(); - - // Get the node and delete it - final NodeRef nodeRef = getNodeForPath(tree, path); - - if (fileFolderService.exists(nodeRef)) - { - lockKeeper.removeLock(nodeRef); - - // Get the size of the file being deleted - final FileInfo fInfo = quotaMgr == null ? null : getFileInformation(session, tree, path); - - if(logger.isDebugEnabled()) - { - logger.debug("deleted file" + path); - } - fileFolderService.delete(nodeRef); - - //TODO Needs to be post-commit - if (quotaMgr != null) - { - quotaMgr.releaseSpace(session, tree, fInfo.getFileId(), path, fInfo.getSize()); - } - - // Debug - - if (logger.isDebugEnabled()) - { - logger.debug("Deleted file: " + path + ", nodeRef=" + nodeRef); - } - - // void return - return nodeRef; - } - } - catch (NodeLockedException ex) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Delete file - access denied (locked)", ex); - } - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Unable to delete " + path); - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Delete file - access denied", ex); - } - - // Convert to a filesystem access denied status - throw new AccessDeniedException("Unable to delete " + path); - } - catch (IOException ex) - { - // Allow I/O Exceptions to pass through - if ( logger.isDebugEnabled()) - { - logger.debug("Delete file error - pass through IO Exception", ex); - } - throw ex; - } - catch (Exception ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Delete file error", ex); - } - - // Convert to a general I/O exception - IOException ioe = new IOException("Delete file " + path); - ioe.initCause(ex); - throw ioe; - } - return null; - } - - public void renameFile(final SrvSession session, final TreeConnection tree, final String oldName, final String newName) - throws IOException - { - throw new AlfrescoRuntimeException("obsolete method called"); - } - - /** - * Rename the specified file. - * - * @param rootNode - * @param oldName path/name of old file - * @param newName path/name of new file - * @exception java.io.IOException The exception description. - */ - public void renameFile(NodeRef rootNode, final String oldName, final String newName, boolean soft, boolean moveAsSystem) - throws IOException - { - - if (logger.isDebugEnabled()) - { - logger.debug("RenameFile oldName=" + oldName + ", newName=" + newName + ", soft" + soft); - } - - try - { - // Get the file/folder to move - final NodeRef nodeToMoveRef = getCifsHelper().getNodeRef(rootNode, oldName); - - // Check if the node is a link node - - if ( nodeToMoveRef != null && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null) - { - throw new AccessDeniedException("Cannot rename link nodes"); - } - - // Get the new target folder - it must be a folder - String[] splitPaths = FileName.splitPath(newName); - String[] oldPaths = FileName.splitPath(oldName); - - final NodeRef targetFolderRef = getCifsHelper().getNodeRef(rootNode, splitPaths[0]); - final NodeRef sourceFolderRef = getCifsHelper().getNodeRef(rootNode, oldPaths[0]); - final String name = splitPaths[1]; - - // Check if this is a rename within the same folder - - final boolean sameFolder = splitPaths[0].equalsIgnoreCase(oldPaths[0]); - - // Check if we are renaming a folder, or the rename is to a different folder - boolean isFolder = getCifsHelper().isDirectory(nodeToMoveRef); - - if ( isFolder == true || sameFolder == false) - { - - // Rename or move the file/folder to another folder - if (sameFolder == true) - { - fileFolderService.rename(nodeToMoveRef, name); - if (logger.isDebugEnabled()) - { - logger.debug(" Renamed " + (isFolder ? "folder" : "file") + ":" + - " Old name: " + oldName + "\n" + - " New name: " + newName + "\n" ); - } - - } - else - { - if (moveAsSystem) - { - if (logger.isDebugEnabled()) - { - logger.debug("Run move as System for: " + oldName); - } - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Object doWork() throws Exception - { - return fileFolderService.moveFrom(nodeToMoveRef, sourceFolderRef, targetFolderRef, name); - } - }, AuthenticationUtil.getSystemUserName()); - } - else - { - fileFolderService.moveFrom(nodeToMoveRef, sourceFolderRef, targetFolderRef, name); - } - - logger.debug( - "Moved between different folders: \n" + - " Old name: " + oldName + "\n" + - " New name: " + newName + "\n" + - " Source folder: " + sourceFolderRef + "\n" + - " Target folder: " + targetFolderRef + "\n" + - " Node: " + nodeToMoveRef + "\n" + - " Aspects: " + nodeService.getAspects(nodeToMoveRef)); - } - - if (logger.isDebugEnabled()) - { - logger.debug(" Renamed " + (isFolder ? "folder" : "file") + " using " - + (sameFolder ? "rename" : "move")); - } - } - else - { - // Rename a file within the same folder - - - if (logger.isDebugEnabled()) - { - logger.debug( - "Rename file within same folder: \n" + - " Old name: " + oldName + "\n" + - " New name: " + newName + "\n" + - " Source folder: " + sourceFolderRef + "\n" + - " Target folder: " + targetFolderRef + "\n" + - " Node: " + nodeToMoveRef + "\n" + - " Aspects: " + nodeService.getAspects(nodeToMoveRef)); - } - if(soft) - { - logger.debug("this is a soft delete - use copy rather than rename"); - fileFolderService.copy(nodeToMoveRef, null, name); - nodeService.addAspect(nodeToMoveRef, ContentModel.ASPECT_SOFT_DELETE, null); - } - else - { - fileFolderService.rename(nodeToMoveRef, name); - } - } - } - catch (org.alfresco.service.cmr.model.FileNotFoundException e) - { - if (logger.isDebugEnabled()) - { - logger.debug("Rename file - about to throw file not exists exception file:" + oldName, e); - } - throw new java.io.FileNotFoundException("renameFile: file not found file: + oldName"); - } - catch (org.alfresco.service.cmr.model.FileExistsException e) - { - if (logger.isDebugEnabled()) - { - logger.debug("Rename file - about to throw file exists exception", e); - } - throw new org.alfresco.jlan.server.filesys.FileExistsException(newName); - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - if (logger.isDebugEnabled()) - { - logger.debug("Rename file - about to throw permissions denied exception", ex); - } - throw new org.alfresco.jlan.server.filesys.PermissionDeniedException("renameFile: No permissions to rename file:" + oldName); - } - catch (NodeLockedException ex) - { - if (logger.isDebugEnabled()) - { - logger.debug("Rename file - about to throw access denied exception", ex); - } - // Convert to an filesystem access denied exception - throw new AccessDeniedException("renameFile: Access Denied - Node locked file:" + oldName); - } - catch (AlfrescoRuntimeException ex) - { - if (logger.isDebugEnabled()) - { - logger.debug("Rename file about to throw access denied exception", ex); - } - throw new AlfrescoRuntimeException("renameFile failed: \n" + - " Old name: " + oldName + "\n" + - " New name: " + newName + "\n" + - ex); - - } - } - - /** - * Set file information - * - * @param sess SrvSession - * @param tree TreeConnection - * @param name String - * @param info FileInfo - * @exception IOException - */ - public void setFileInformation(SrvSession sess, final TreeConnection tree, final String name, final FileInfo info) throws IOException - { - // Get the device context - - final ContentContext ctx = (ContentContext) tree.getContext(); - - if (logger.isDebugEnabled()) - { - logger.debug("setFileInformation name=" + name + ", info=" + info); - } - - NetworkFile networkFile = info.getNetworkFile(); - - try - { - - if(sess.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) - { - String[] paths = FileName.splitPath(name); - // lookup parent directory - NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); - - // Check whether we are opening a pseudo file - if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) - { - if(logger.isDebugEnabled()) - { - logger.debug("pseudo file so do nothing"); - } - return; - } - } - - // Get the file/folder node - NodeRef nodeRef = getNodeForPath(tree, name); - - // Check permissions on the file/folder node - - if ( permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) - { - if(logger.isDebugEnabled()) - { - logger.debug("write access denied to :" + name); - } - throw new AccessDeniedException("No write access to " + name); - } - - // Inhibit versioning for this transaction - getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE); - - // Check if the file is being marked for deletion, if so then check if the file is locked - - /* - * Which DeleteOnClose flag has priority? - * SetDeleteOnClose is not set or used in this method. - * The NTProtocolHandler sets the deleteOnClose in both - * info and the NetworkFile - it's the one in NetworkFile that results in the file being deleted. - */ - if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) - { - if(logger.isDebugEnabled()) - { - logger.debug("Set Delete On Close for :" + name); - } - // Check for delete permission - if ( permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) - { - throw new PermissionDeniedException("No delete access to :" + name); - } - - // Check if the node is locked - lockService.checkForLock(nodeRef); - - // Get the node for the folder - - if (fileFolderService.exists(nodeRef)) - { - // Check if it is a folder that is being deleted, make sure it is empty - - boolean isFolder = true; - - ContentFileInfo cInfo = getCifsHelper().getFileInformation(nodeRef, false, isLockedFilesAsOffline); - - if ( cInfo != null && cInfo.isDirectory() == false) - { - isFolder = false; - } - - // Check if the folder is empty - if ( isFolder == true && getCifsHelper().isFolderEmpty( nodeRef) == false) - { - throw new DirectoryNotEmptyException( name); - } - } - - - } - - if(info.hasSetFlag(FileInfo.SetAttributes)) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Set attributes" + name + ", file attrs = " + info.getFileAttributes()); - } - - //TODO MER Think we may need to implement, Temporary, Hidden, System, Archive - if(info.isSystem()) - { - logger.debug("Set system aspect (not yet implemented)" + name); - } - if(info.isTemporary()) - { - logger.debug("Set temporary aspect (not yet implemented)" + name); - } - - if(info.isHidden()) - { - // yes is hidden - if ( logger.isDebugEnabled()) - { - logger.debug("Set hidden aspect" + name); - } - hiddenAspect.hideNodeExplicit(nodeRef); - } - else - { - // not hidden - if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_HIDDEN)) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Reset hidden aspect" + name); - } - hiddenAspect.unhideExplicit(nodeRef); - } - } - } // End of setting attributes - - if( info.hasSetFlag(FileInfo.SetAllocationSize)) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Set allocation size" + name + info.getAllocationSize()); - } - // Not yet implemented - } - - if( info.hasSetFlag(FileInfo.SetFileSize)) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Set file size" + name + info.getSize()); - } - // Not yet implemented - } - - if( info.hasSetFlag(FileInfo.SetMode)) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Set Mode" + name + info.getMode()); - } - // Not yet implemented - set the unix mode e.g. 777 - } - - // Set the creation and modified date/time - Map auditableProps = new HashMap(5); - - - - if ( info.hasSetFlag(FileInfo.SetCreationDate) && info.hasCreationDateTime()) - { - // Set the creation date on the file/folder node - Date createDate = new Date(info.getCreationDateTime()); - auditableProps.put(ContentModel.PROP_CREATED, createDate); - if ( logger.isDebugEnabled()) - { - logger.debug("Set creation date" + name + ", " + createDate); - } - } - if ( info.hasSetFlag(FileInfo.SetModifyDate) && info.hasModifyDateTime()) - { - // Set the modification date on the file/folder node - Date modifyDate = new Date( info.getModifyDateTime()); - auditableProps.put(ContentModel.PROP_MODIFIED, modifyDate); - - // Set the network file so we don't reverse this change in close file. - if(networkFile != null && !networkFile.isReadOnly()) - { - networkFile.setModifyDate(info.getModifyDateTime()); - if(networkFile instanceof TempNetworkFile) - { - TempNetworkFile tnf = (TempNetworkFile)networkFile; - tnf.setModificationDateSetDirectly(true); - } - } - - if ( logger.isDebugEnabled()) - { - logger.debug("Set modification date" + name + ", " + modifyDate); - } - - } - - // Change Date is last write ? - if ( info.hasSetFlag(FileInfo.SetChangeDate) && info.hasChangeDateTime()) - { - Date changeDate = new Date( info.getChangeDateTime()); - if ( logger.isDebugEnabled()) - { - logger.debug("Set change date (Not implemented)" + name + ", " + changeDate); - } - - } - if ( info.hasSetFlag(FileInfo.SetAccessDate) && info.hasAccessDateTime()) - { - Date accessDate = new Date( info.getAccessDateTime()); - if ( logger.isDebugEnabled()) - { - logger.debug("Set access date (Not implemented)" + name + ", " + accessDate); - } - } - - // Did we have any cm:auditable properties? - if (auditableProps.size() > 0) - { - getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); - nodeService.addProperties(nodeRef, auditableProps); - - // DEBUG - if ( logger.isDebugEnabled()) - { - logger.debug("Set auditable props: " + auditableProps + " file=" + name); - } - } - - return; - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Set file information - access denied, " + name); - } - // Convert to a filesystem access denied status - throw new AccessDeniedException("Set file information " + name); - } - catch (AlfrescoRuntimeException ex) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Open file error", ex); - } - // Convert to a general I/O exception - - throw new IOException("Set file information " + name, ex); - } - } - - /** - * Truncate a file to the specified size - * - * @param sess Server session - * @param tree Tree connection - * @param file Network file details - * @param size New file length - * @exception java.io.IOException The exception description. - */ - public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long size) throws IOException - { - // Keep track of the allocation/release size in case the file resize fails - - ContentContext ctx = (ContentContext) tree.getContext(); - - if (logger.isDebugEnabled()) - { - logger.debug("truncateFile file:" + file + ", size: "+ size); - } - - long allocSize = 0L; - long releaseSize = 0L; - - // Check if there is a quota manager - QuotaManager quotaMgr = ctx.getQuotaManager(); - - if ( ctx.hasQuotaManager()) { - - // Check if the file content has been opened, we need the content to be opened to get the - // current file size - - if ( file instanceof ContentNetworkFile) { - ContentNetworkFile contentFile = (ContentNetworkFile) file; - if ( contentFile.hasContent() == false) - { - contentFile.openContent( false, false); - } - } - else if( file instanceof TempNetworkFile) - { - - } - else - { - throw new IOException("Invalid file class type, " + file.getClass().getName()); - } - // Determine if the new file size will release space or require space allocating - - if ( size > file.getFileSize()) - { - - // Calculate the space to be allocated - - allocSize = size - file.getFileSize(); - - // Allocate space to extend the file - - quotaMgr.allocateSpace(sess, tree, file, allocSize); - } - else - { - - // Calculate the space to be released as the file is to be truncated, release the space if - // the file truncation is successful - releaseSize = file.getFileSize() - size; - } - } - - // Check if this is a file extend, update the cached allocation size if necessary - - if ( file instanceof ContentNetworkFile) { - - // Get the cached state for the file - ContentNetworkFile contentFile = (ContentNetworkFile) file; - FileState fstate = contentFile.getFileState(); - if ( fstate != null && size > fstate.getAllocationSize()) - { - fstate.setAllocationSize(size); - } - } - - if( file instanceof TempNetworkFile) - { - TempNetworkFile contentFile = (TempNetworkFile) file; - FileState fstate = contentFile.getFileState(); - if ( fstate != null && size > fstate.getAllocationSize()) - { - fstate.setAllocationSize(size); - } - } - - // Set the file length - - try - { - file.truncateFile(size); - } - catch (IOException ex) - { - if(logger.isDebugEnabled()) - { - logger.debug("unable to truncate the file + :" + file.getFullName(), ex); - } - // Check if we allocated space to the file - - if ( allocSize > 0 && quotaMgr != null) - { - quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, allocSize); - } - - // Rethrow the exception - throw ex; - } - - // Check if space has been released by the file resizing - - if ( releaseSize > 0 && quotaMgr != null) - { - quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, releaseSize); - } - // Debug - - if (logger.isDebugEnabled()) - { - logger.debug("Truncated file: network file=" + file + " size=" + size); - - } - } - - /** - * Read a block of data from the specified file. - * - * @param sess Session details - * @param tree Tree connection - * @param file Network file - * @param buffer Buffer to return data to - * @param bufferPosition Starting position in the return buffer - * @param size Maximum size of data to return - * @param fileOffset File offset to read data - * @return Number of bytes read - * @exception java.io.IOException The exception description. - */ - public int readFile( - SrvSession sess, TreeConnection tree, NetworkFile file, - byte[] buffer, int bufferPosition, int size, long fileOffset) throws IOException - { - // Check if the file is a directory - - if(readLogger.isDebugEnabled()) - { - readLogger.debug("read File:" + file + ", size" + size); - } - - if(file.isDirectory()) - { - if(logger.isDebugEnabled()) - { - logger.debug("read file called for a directory - throw AccessDeniedException"); - } - throw new AccessDeniedException("read called for a directory"); - } - - // Read a block of data from the file - - int count = file.readFile(buffer, size, bufferPosition, fileOffset); - - if ( count == -1) - { - // Read count of -1 indicates a read past the end of file - - count = 0; - } - - // Debug - - //ContentContext ctx = (ContentContext) tree.getContext(); - - if (readLogger.isDebugEnabled()) - { - readLogger.debug("Read bytes from file: network file=" + file + " buffer size=" + buffer.length + " buffer pos=" + bufferPosition + - " size=" + size + " file offset=" + fileOffset + " bytes read=" + count); - } - - return count; - } - - /** - * Seek to the specified file position. - * - * @param sess Server session - * @param tree Tree connection - * @param file Network file. - * @param pos Position to seek to. - * @param typ Seek type. - * @return New file position, relative to the start of file. - */ - public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ) throws IOException - { - if(logger.isDebugEnabled()) - { - logger.debug("seek File"); - } - - // Check if the file is a directory - - if ( file.isDirectory()) - { - throw new AccessDeniedException(); - } - - // Set the file position - - return file.seekFile(pos, typ); - } - - /** - * Write a block of data to the file. - * - * @param sess Server session - * @param tree Tree connection - * @param file Network file details - * @param buffer byte[] Data to be written - * @param bufferOffset Offset within the buffer that the data starts - * @param size int Data length - * @param fileOffset Position within the file that the data is to be written. - * @return Number of bytes actually written - * @exception java.io.IOException The exception description. - */ - public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, - byte[] buffer, int bufferOffset, int size, long fileOffset) throws IOException - { - if(writeLogger.isDebugEnabled()) - { - writeLogger.debug("write File:" + file + " size:" + size); - } - - // Check if there is a quota manager - - ContentContext ctx = (ContentContext) tree.getContext(); - QuotaManager quotaMgr = ctx.getQuotaManager(); - - long curSize = file.getFileSize(); - - if ( quotaMgr != null) - { - - // Check if the file requires extending - - long extendSize = 0L; - long endOfWrite = fileOffset + size; - - if ( endOfWrite > curSize) - { - // Calculate the amount the file must be extended - - extendSize = endOfWrite - file.getFileSize(); - - // Allocate space for the file extend - if(writeLogger.isDebugEnabled()) - { - writeLogger.debug("writeFile: allocate more space fileName:" + file.getName() + ", extendTo:"+ extendSize); - } - - - long alloc = quotaMgr.allocateSpace(sess, tree, file, extendSize); - - if(file instanceof TempNetworkFile) - { - TempNetworkFile tnf = (TempNetworkFile)file; - FileState fstate = tnf.getFileState(); - if(fstate != null) - { - fstate.setAllocationSize(alloc); - } - } - } - } - - // Write to the file - - file.writeFile(buffer, size, bufferOffset, fileOffset); - - // Check if the file size was reduced by the write, may have been extended previously - - if ( quotaMgr != null) - { - - // Check if the file size reduced - - if ( file.getFileSize() < curSize) - { - - // Release space that was freed by the write - - quotaMgr.releaseSpace( sess, tree, file.getFileId(), file.getFullName(), curSize - file.getFileSize()); - } - } - - // Debug - - if (writeLogger.isDebugEnabled()) - { - writeLogger.debug("Wrote bytes to file: network file=" + file + " buffer size=" + buffer.length + " size=" + size + " file offset=" + fileOffset); - } - - return size; - } - - /** - * Get the node for the specified path - * - * @param tree TreeConnection - * @param path String - * @return NodeRef - * @exception FileNotFoundException - */ - private NodeRef getNodeForPath(TreeConnection tree, String path) - throws FileNotFoundException - { - ContentContext ctx = (ContentContext) tree.getContext(); - return getCifsHelper().getNodeRef(ctx.getRootNode(), path); - } - - /** - * Get the node for the specified path - * - * @param rootNode rootNode - * @param path String - * @return NodeRef - * @exception FileNotFoundException - */ - public NodeRef getNodeForPath(NodeRef rootNode, String path) - throws FileNotFoundException - { - if(logger.isDebugEnabled()) - { - logger.debug("getNodeRefForPath:" + path); - } - - return getCifsHelper().getNodeRef(rootNode, path); - } - - /** - * Convert a node into a share relative path - * - * @param tree TreeConnection - * @param nodeRef NodeRef - * @return String - * @exception FileNotFoundException - */ -// private String getPathForNode( TreeConnection tree, NodeRef nodeRef) -// throws FileNotFoundException -// { -// // Convert the target node to a path -// ContentContext ctx = (ContentContext) tree.getContext(); -// -// return getPathForNode(ctx.getRootNode(), nodeRef); -// -// } -// - /** - * Convert a node into a share relative path - * - * @param rootNode rootNode - * @param nodeRef NodeRef - * @return String - * @exception FileNotFoundException - */ - private String getPathForNode( NodeRef rootNode, NodeRef nodeRef) - throws FileNotFoundException - { - if(logger.isDebugEnabled()) - { - logger.debug("getPathForNode:" + nodeRef); - } - - List linkPaths = null; - - try - { - linkPaths = fileFolderService.getNamePath( rootNode, nodeRef); - } - catch ( org.alfresco.service.cmr.model.FileNotFoundException ex) - { - throw new FileNotFoundException(); - } - - // Build the share relative path to the node - - StringBuilder pathStr = new StringBuilder(); - - for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) - { - pathStr.append( FileName.DOS_SEPERATOR); - pathStr.append( fInfo.getName()); - } - - // Return the share relative path - - return pathStr.toString(); - } - - /** - * Return the lock manager used by this filesystem - * - * @param sess SrvSession - * @param tree TreeConnection - * @return LockManager - */ - public LockManager getLockManager(SrvSession sess, TreeConnection tree) - { - AlfrescoContext alfCtx = (AlfrescoContext) tree.getContext(); - return alfCtx.getLockManager(); - } - - /** - * Disk Size Interface implementation - */ - private interface DiskSizeInterfaceConsts - { - static final int DiskBlockSize = 512; // bytes per block - static final long DiskAllocationUnit = 32 * MemorySize.KILOBYTE; - static final long DiskBlocksPerUnit = DiskAllocationUnit / DiskBlockSize; - - // Disk size returned in the content store does not support free/total size - - static final long DiskSizeDefault = 1 * MemorySize.TERABYTE; - static final long DiskFreeDefault = DiskSizeDefault / 2; - } - - /** - * Get the disk information for this shared disk device. - * - * @param ctx DiskDeviceContext - * @param diskDev SrvDiskInfo - * @exception IOException - */ - public void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo diskDev) throws IOException - { - if(logger.isDebugEnabled()) - { - logger.debug("getDiskInformation"); - } - - // Set the block size and blocks per allocation unit - diskDev.setBlockSize( DiskSizeInterfaceConsts.DiskBlockSize); - diskDev.setBlocksPerAllocationUnit( DiskSizeInterfaceConsts.DiskBlocksPerUnit); - - // Get the free and total disk size in bytes from the content store - - long freeSpace = contentService.getStoreFreeSpace(); - long totalSpace= contentService.getStoreTotalSpace(); - - if ( totalSpace == -1L) { - - // Use a fixed value for the total space, content store does not support size information - - totalSpace = DiskSizeInterfaceConsts.DiskSizeDefault; - freeSpace = DiskSizeInterfaceConsts.DiskFreeDefault; - } - - // Convert the total/free space values to allocation units - - diskDev.setTotalUnits( totalSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); - diskDev.setFreeUnits( freeSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); - - if(logger.isDebugEnabled()) - { - logger.debug("getDiskInformation returning diskDev:" + diskDev); - } - } - - public void setCifsHelper(CifsHelper cifsHelper) - { - this.cifsHelper = cifsHelper; - } - - @Override - public void treeOpened(SrvSession sess, TreeConnection tree) - { - // Nothing to do - } - - @Override - public void treeClosed(SrvSession sess, TreeConnection tree) - { - // Nothing to do - } - -// Implementation of IOCtlInterface - - /** - * Process a filesystem I/O control request - * - * @param sess Server session - * @param tree Tree connection. - * @param ctrlCode I/O control code - * @param fid File id - * @param dataBuf I/O control specific input data - * @param isFSCtrl true if this is a filesystem control, or false for a device control - * @param filter if bit0 is set indicates that the control applies to the share root handle - * @return DataBuffer - * @exception IOControlNotImplementedException - * @exception SMBException - */ - public org.alfresco.jlan.util.DataBuffer processIOControl(SrvSession sess, TreeConnection tree, int ctrlCode, int fid, DataBuffer dataBuf, - boolean isFSCtrl, int filter) - throws IOControlNotImplementedException, SMBException - { - // Validate the file id - if(logger.isDebugEnabled()) - { - logger.debug("processIOControl ctrlCode: 0x" + Integer.toHexString(ctrlCode) + ", fid:" + fid); - } - - final ContentContext ctx = (ContentContext) tree.getContext(); - try - { - org.alfresco.jlan.util.DataBuffer buff = ioControlHandler.processIOControl(sess, tree, ctrlCode, fid, dataBuf, isFSCtrl, filter, this, ctx); - - return buff; - } - catch(SMBException smbException) - { - if(logger.isDebugEnabled()) - { - logger.debug("SMB Exception fid:" + fid, smbException); - } - throw smbException; - } - catch(IOControlNotImplementedException ioException) - { - if(logger.isDebugEnabled()) - { - logger.debug("IO Control Not Implemented Exception fid:" + fid, ioException); - } - throw ioException; - } - } - - - public void setCheckOutCheckInService(CheckOutCheckInService service) - { - this.checkOutCheckInService = service; - } - - /** - * @return the service to provide check-in and check-out data - */ - public final CheckOutCheckInService getCheckOutCheckInService() - { - return checkOutCheckInService; - } - - // Implementation of RepositoryDiskInterface - @Override - public void copyContent(NodeRef rootNode, String fromPath, String toPath) throws FileNotFoundException - { - if(logger.isDebugEnabled()) - { - logger.debug("copyContent from:" + fromPath + " to:" + toPath); - } - - NodeRef sourceNodeRef = getNodeForPath(rootNode, fromPath); - NodeRef targetNodeRef = getNodeForPath(rootNode, toPath); - - Serializable prop = nodeService.getProperty(sourceNodeRef, ContentModel.PROP_CONTENT); - if(prop != null) - { - if(prop instanceof ContentData) - { - ContentData data = (ContentData)prop; - if(data.getMimetype().equalsIgnoreCase(MimetypeMap.MIMETYPE_BINARY)) - { - if(logger.isDebugEnabled()) - { - logger.debug("mimetype is binary - guess mimetype has failed"); - } - Serializable targetProp = nodeService.getProperty(targetNodeRef, ContentModel.PROP_CONTENT); - - if(targetProp != null && targetProp instanceof ContentData) - { - ContentData targetData = (ContentData)targetProp; - logger.debug("copy the existing mimetype"); - prop = ContentData.setMimetype(data, targetData.getMimetype()); - } - } - } - - nodeService.setProperty(targetNodeRef, ContentModel.PROP_CONTENT, prop); - } - else - { - logger.debug("no content to save"); - // No content to set - need to remove old content - ContentWriter writer = contentService.getWriter(targetNodeRef, ContentModel.PROP_CONTENT, true); - writer.putContent(""); - } - - } - - @Override - public NetworkFile createFile(NodeRef rootNode, String path, long allocationSize, boolean isHidden) - throws IOException - { - - if (logger.isDebugEnabled()) - { - logger.debug("createFile :" + path); - } - - try - { - NodeRef dirNodeRef; - String folderName; - - String[] paths = FileName.splitPath(path); - - if (paths[0] != null && paths[0].length() > 1) - { - // lookup parent directory - dirNodeRef = getNodeForPath(rootNode, paths[0]); - folderName = paths[1]; - } - else - { - dirNodeRef = rootNode; - folderName = path; - } - - boolean soft = false; - - NodeRef existing = fileFolderService.searchSimple(dirNodeRef, folderName); - if (existing != null) - { - if(nodeService.hasAspect(existing, ContentModel.ASPECT_SOFT_DELETE)) - { - logger.debug("existing node has soft delete aspect"); - soft = true; - } - } - - NodeRef nodeRef = null; - - if(soft) - { - nodeRef = existing; - } - else - { - nodeRef = cifsHelper.createNode(dirNodeRef, folderName, ContentModel.TYPE_CONTENT); - nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null); - lockKeeper.addLock(nodeRef); - } - - if(isHidden) - { - // yes is hidden - if ( logger.isDebugEnabled()) - { - logger.debug("Set hidden aspect, nodeRef:" + nodeRef); - } - hiddenAspect.hideNodeExplicit(nodeRef); - } - - File file = TempFileProvider.createTempFile("cifs", ".bin"); - - TempNetworkFile netFile = new TempNetworkFile(file, path); - netFile.setChanged(true); - - Serializable created = nodeService.getProperty(nodeRef, ContentModel.PROP_CREATED); - if(created != null && created instanceof Date) - { - Date d = (Date)created; - if(logger.isDebugEnabled()) - { - logger.debug("replacing create date to date:" + d); - } - netFile.setCreationDate(d.getTime()); - netFile.setModifyDate(d.getTime()); - } - - // Always allow write access to a newly created file - netFile.setGrantedAccess(NetworkFile.READWRITE); - netFile.setAllowedAccess(NetworkFile.READWRITE); - - - - // Generate a file id for the file - - if ( netFile != null) - { - long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); - netFile.setFileId((int) (id & 0xFFFFFFFFL)); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Created file: path=" + path + " node=" + nodeRef + " network file=" + netFile); - } - - // Return the new network file - - return netFile; - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Create file - access denied, " + path); - } - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Unable to create file " + path); - } - catch (IOException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Create file - content I/O error, " + path); - } - - throw ex; - } - catch (ContentIOException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Create file - content I/O error, " + path); - } - // Convert to a filesystem disk full status - - throw new DiskFullException("Unable to create file " + path); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Create file error", ex); - } - - // Convert to a general I/O exception - - throw new IOException("Unable to create file " + path, ex); - } - } - - /** - * Open the file - Repo Specific implementation - */ - public NetworkFile openFile(SrvSession session, TreeConnection tree, NodeRef rootNode, String path, OpenFileMode mode, boolean truncate) throws IOException - { - ContentContext ctx = (ContentContext) tree.getContext(); - - if(logger.isDebugEnabled()) - { - logger.debug("openFile :" + path + ", mode:" + mode ); - } - try - { - String name = path; - - if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) - { - String[] paths = FileName.splitPath(name); - // lookup parent directory - NodeRef dirNodeRef = getNodeForPath(rootNode, paths[0]); - - // Check whether we are opening a pseudo file - if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) - { - PseudoFile pfile = ctx.getPseudoFileOverlay().getPseudoFile(dirNodeRef, paths[1]); - if(logger.isDebugEnabled()) - { - if (pfile != null) - { - logger.debug("Opened pseudo file :" + pfile); - } - else - { - logger.debug("Try to open deleted pseudo file :" + paths[1]); - } - } - if (pfile != null) - { - return pfile.getFile( path); - } - else - { - throw new FileNotFoundException("The pseudo file was deleted"); - } - } - } - - // not a psudo file - - NodeRef nodeRef = getNodeForPath(rootNode, path); - - boolean readOnly=false; - - // Check permissions on the file/folder - switch(mode) - { - case READ_ONLY: - // follow through - case ATTRIBUTES_ONLY: - if(permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED) - { - if(logger.isDebugEnabled()) - { - logger.debug("about to throw an no read access denied exception path:" +path); - } - throw new AccessDeniedException("No read access to " + path); - } - readOnly = true; - break; - - case READ_WRITE: - case WRITE_ONLY: - if(!m_transactionService.getAllowWrite()) - { - throw new AccessDeniedException("Repo is write only, No write access to " + path); - } - if(permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) - { - if(logger.isDebugEnabled()) - { - logger.debug("about to throw an no write access denied exception path:" + path); - } - - throw new AccessDeniedException("No write access to " + path); - } - lockService.checkForLock(nodeRef); - readOnly=false; - break; - case DELETE: - if(!m_transactionService.getAllowWrite()) - { - throw new AccessDeniedException("Repo is write only, No write access to " + path); - } - lockService.checkForLock(nodeRef); - - } - - // Check if the node is a link node - NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION); - NetworkFile netFile = null; - - if ( linkRef == null) - { - // A normal node, not a link node - - // TODO MER REWRITE HERE - FileInfo fileInfo = cifsHelper.getFileInformation(nodeRef, "", false, false); - - // TODO this is wasteful - the isDirectory is in the params. We should split off an openDirectory method. - if(fileInfo.isDirectory()) - { - logger.debug("open file - is a directory!"); - netFile = new AlfrescoFolder(path, fileInfo, readOnly); - } - else - { - // A normal file - switch (mode) - { - case READ_ONLY: - - logger.debug("open file for read only"); - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); - netFile.setGrantedAccess( NetworkFile.READONLY); - break; - - case READ_WRITE: - { - logger.debug("open file for read write"); - File file = TempFileProvider.createTempFile("cifs", ".bin"); - - lockKeeper.addLock(nodeRef); - - if(!truncate) - { - // Need to open a temp file with a copy of the content. - ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); - if(reader != null) - { - reader.getContent(file); - } - } - - netFile = new TempNetworkFile(file, name); - netFile.setCreationDate(fileInfo.getCreationDateTime()); - netFile.setModifyDate(fileInfo.getModifyDateTime()); - - netFile.setGrantedAccess( NetworkFile.READWRITE); - - if(truncate) - { - netFile.truncateFile(0); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Created file: path=" + name + " node=" + nodeRef + " network file=" + netFile); - } - - } - break; - - case ATTRIBUTES_ONLY: - logger.debug("open file for attributes only"); - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, true, session); - netFile.setGrantedAccess( NetworkFile.READONLY); - break; - - case DELETE: - //TODO Not sure about this one. - logger.debug("open file for delete"); - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); - netFile.setGrantedAccess( NetworkFile.READONLY); - break; - - case WRITE_ONLY: - { - // consider this as open read/write/truncate) - logger.debug("open file write only"); - File file = TempFileProvider.createTempFile("cifs", ".bin"); - - netFile = new TempNetworkFile(file, name); - - // Needs to be READWRITE for JavaNetworkFile - there's no such thing as WRITEONLY! - netFile.setGrantedAccess( NetworkFile.READWRITE); - - if (logger.isDebugEnabled()) - { - logger.debug("Created temporary file: path=" + name + " node=" + nodeRef + " network file=" + netFile); - } - } - } - } // end of a normal file - } - else - { - // This is a link node - - // TODO - This server name stuff should be replaced In particular the - // See PseudoFileOverlayImp - // Get the CIFS server name - - String srvName = null; - SMBServer cifsServer = (SMBServer) session.getServer().getConfiguration().findServer( "CIFS"); - - if(session instanceof SMBSrvSession) - { - SMBSrvSession smbSess = (SMBSrvSession)session; - srvName = smbSess.getShareHostName(); - } - else if ( cifsServer != null) - { - // Use the CIFS server name in the URL - - srvName = cifsServer.getServerName(); - } - else - { - // Use the local server name in the URL - srvName = InetAddress.getLocalHost().getHostName(); - } - - // Convert the target node to a path, convert to URL format - - String pathl = getPathForNode( rootNode, linkRef); - path = pathl.replace( FileName.DOS_SEPERATOR, '/'); - - String lnkForWinPath = convertStringToUnicode(path); - - // Build the URL file data - - StringBuilder urlStr = new StringBuilder(); - - urlStr.append("[InternetShortcut]\r\n"); - urlStr.append("URL=file://"); - urlStr.append( srvName); - urlStr.append("/"); - urlStr.append( tree.getSharedDevice().getName()); - urlStr.append( lnkForWinPath); - urlStr.append("\r\n"); - - // Create the in memory pseudo file for the URL link - - byte[] urlData = urlStr.toString().getBytes(); - - // Get the file information for the link node - - FileInfo fInfo = getCifsHelper().getFileInformation( nodeRef, false, isLockedFilesAsOffline); - - // Set the file size to the actual data length - - fInfo.setFileSize( urlData.length); - - // Create the network file using the in-memory file data - - netFile = new LinkMemoryNetworkFile( fInfo.getFileName(), urlData, fInfo, nodeRef); - netFile.setFullName( pathl); - } - - // Generate a file id for the file - - if ( netFile != null) - { - long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); - netFile.setFileId(( int) ( id & 0xFFFFFFFFL)); - - // Indicate the file is open - - netFile.setClosed( false); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Opened network file: path=" + path + " network file=" + netFile); - } - - // Return the network file - - return netFile; - } - catch (NodeLockedException nle) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Open file - node is locked, " + path); - } - throw new AccessDeniedException("File is locked, no write access to " + path); - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - { - logger.debug("Open file - access denied, " + path); - } - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Open file " + path); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if (logger.isDebugEnabled()) - { - logger.debug("Open file error", ex); - } - // Convert to a general I/O exception - - throw new IOException("Open file " + path, ex); - } - } - - private String convertStringToUnicode(String str) - { - StringBuffer ostr = new StringBuffer(); - for (int i = 0; i < str.length(); i++) - { - char ch = str.charAt(i); - // Does the char need to be converted to unicode? - if ((ch >= 0x0020) && (ch <= 0x007e)) - { - // No - ostr.append(ch); - } - else if (ch > 0xFF) - { - // No - ostr.append(ch); - } - // Yes. - else - { - ostr.append("%"); - String hex = Integer.toHexString(str.charAt(i) & 0xFFFF); - hex.length(); - // Prepend zeros because unicode requires 2 digits - for (int j = 0; j < 2 - hex.length(); j++) - - ostr.append("0"); - ostr.append(hex.toLowerCase()); - } - } - return (new String(ostr)); - } - - /** - * Close the file. - * - * @exception java.io.IOException If an error occurs. - * @return node ref of deleted file - */ - public NodeRef closeFile(TreeConnection tree, NodeRef rootNode, String path, NetworkFile file) throws IOException - { - if ( logger.isDebugEnabled()) - { - logger.debug("Close file:" + path + ", readOnly=" + file.isReadOnly() ); - } - - if( file instanceof PseudoNetworkFile || file instanceof MemoryNetworkFile) - { - file.close(); - - if(file.hasDeleteOnClose()) - { - if(logger.isDebugEnabled()) - { - logger.debug("delete on close a pseudo file"); - } - final ContentContext ctx = (ContentContext) tree.getContext(); - - String[] paths = FileName.splitPath(path); - - if (paths[0] != null && paths[0].length() > 1) - { - // lookup parent directory - NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); - ctx.getPseudoFileOverlay().delete(dirNodeRef, paths[1]); - } - } - return null; - } - - /** - * Delete on close attribute - node needs to be deleted. - */ - if(file.hasDeleteOnClose()) - { - NodeRef target = null; - - if(logger.isDebugEnabled()) - { - logger.debug("closeFile has delete on close set path:" + path); - } - try - { - target = getCifsHelper().getNodeRef(rootNode, path); - if(target!=null) - { - nodeService.deleteNode(target); - } - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Delete file from close file- access denied", ex); - } - // Convert to a filesystem access denied status - throw new AccessDeniedException("Unable to delete " + path); - } - - // Still need to close the open file handle. - file.close(); - - if (logger.isDebugEnabled()) - { - logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose()); - } - - return target; - } - - // Check for a temp file - which will be a new file or a read/write file - if ( file instanceof TempNetworkFile) - { - if(logger.isDebugEnabled()) - { - logger.debug("Got a temp network file to close path:" + path); - } - - // Some content was written to the temp file. - TempNetworkFile tempFile =(TempNetworkFile)file; - - NodeRef target = getCifsHelper().getNodeRef(rootNode, tempFile.getFullName()); - - lockKeeper.removeLock(target); - - if(nodeService.hasAspect(target, ContentModel.ASPECT_NO_CONTENT)) - { - if(logger.isDebugEnabled()) - { - logger.debug("removed no content aspect"); - } - nodeService.removeAspect(target, ContentModel.ASPECT_NO_CONTENT); - } - - if(tempFile.isChanged()) - { - tempFile.flushFile(); - tempFile.close(); - - /* - * Need to work out whether content has changed. Some odd situations do not change content. - */ - boolean contentChanged = true; - - ContentReader existingContent = contentService.getReader(target, ContentModel.PROP_CONTENT); - if(existingContent != null) - { - existingContent.getSize(); - existingContent.getMimetype(); - contentChanged = isContentChanged(existingContent, tempFile); - - /* - * MNT-248 fix - * No need to create a version of a zero byte file - */ - if (file.getFileSize() > 0 && existingContent.getSize() == 0 && nodeService.hasAspect(target, ContentModel.ASPECT_VERSIONABLE)) - { - getPolicyFilter().disableBehaviour(target, ContentModel.ASPECT_VERSIONABLE); - } - } - - if(contentChanged) - { - logger.debug("content has changed, need to create a new content item"); - - /** - * Take over the behaviour of the auditable aspect - */ - getPolicyFilter().disableBehaviour(target, ContentModel.ASPECT_AUDITABLE); - nodeService.setProperty(target, ContentModel.PROP_MODIFIER, authService.getCurrentUserName()); - if(tempFile.isModificationDateSetDirectly()) - { - logger.debug("modification date set directly"); - nodeService.setProperty(target, ContentModel.PROP_MODIFIED, new Date(tempFile.getModifyDate())); - } - else - { - logger.debug("modification date not set directly"); - nodeService.setProperty(target, ContentModel.PROP_MODIFIED, new Date()); - } - - /** - * Take a guess at the mimetype - */ - String mimetype = mimetypeService.guessMimetype(tempFile.getFullName(), new FileContentReader(tempFile.getFile())); - logger.debug("guesssed mimetype:" + mimetype); - - /** - * mime type guessing may have failed in which case we should assume the mimetype has not changed. - */ - if(mimetype.equalsIgnoreCase(MimetypeMap.MIMETYPE_BINARY)) - { - // mimetype guessing may have failed - if(existingContent != null) - { - // copy the mimetype from the existing content. - mimetype = existingContent.getMimetype(); - if(logger.isDebugEnabled()) - { - logger.debug("using mimetype of existing content :" + mimetype); - } - } - } - - String encoding; - // Take a guess at the locale - InputStream is = new BufferedInputStream(new FileInputStream(tempFile.getFile())); - try - { - ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder(); - Charset charset = charsetFinder.getCharset(is, mimetype); - encoding = charset.name(); - } - finally - { - if(is != null) - { - try - { - is.close(); - } - catch (IOException e) - { - // Ignore - } - } - } - ContentWriter writer = contentService.getWriter(target, ContentModel.PROP_CONTENT, true); - writer.setMimetype(mimetype); - writer.setEncoding(encoding); - writer.putContent(tempFile.getFile()); - } // if content changed - } - } - - try - { - // Defer to the network file to close the stream and remove the content - - file.close(); - - // DEBUG - - if (logger.isDebugEnabled()) - { - logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose() + ", write count" + file.getWriteCount()); - - if ( file.hasDeleteOnClose() == false && file instanceof ContentNetworkFile) - { - ContentNetworkFile cFile = (ContentNetworkFile) file; - logger.debug(" File " + file.getFullName() + ", version=" + nodeService.getProperty( cFile.getNodeRef(), ContentModel.PROP_VERSION_LABEL)); - } - } - - return null; - } - catch (IOException e) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Exception in closeFile - path:" + path, e); - } - throw new IOException("Unable to closeFile :" + path + e.toString(), e); - } - catch (Error e) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Exception in closeFile - path:" + path, e); - } - - throw e; - } - } - - /** - * - * @param session - * @param tree - * @param file - */ - public void reduceQuota(SrvSession session, TreeConnection tree, NetworkFile file) - { - if(file.hasDeleteOnClose()) - { - final ContentContext ctx = (ContentContext) tree.getContext(); - - if(logger.isDebugEnabled()) - { - logger.debug("closeFile has delete on close set"); - } - - if(file instanceof TempNetworkFile) - { - TempNetworkFile tnf = (TempNetworkFile)file; - final QuotaManager quotaMgr = ctx.getQuotaManager(); - if (quotaMgr != null) - { - try - { - quotaMgr.releaseSpace(session, tree, file.getFileId(), file.getName(), tnf.getFileSizeInt()); - } - catch (IOException e) - { - logger.error(e); - } - } - } - } - } - - public void deleteEmptyFile(NodeRef rootNode, String path) - { - try - { - NodeRef target = getCifsHelper().getNodeRef(rootNode, path); - if(target!=null) - { - if (nodeService.hasAspect(target, ContentModel.ASPECT_NO_CONTENT)) - { - nodeService.deleteNode(target); - } - } - } - catch(IOException ne) - { - // Do nothing - if ( logger.isDebugEnabled()) - { - logger.debug("Unable to delete empty file:" + path, ne); - } - - } - } - - @Override - public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) - { - AlfrescoContext alfCtx = (AlfrescoContext) tree.getContext(); - return alfCtx.getOpLockManager(); - } - - @Override - public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) - { - if(getOpLockManager(sess, tree) != null) - { - return true; - } - return false; - } - - /** - * Compare the content for significant changes. For example Project and Excel play with headers, - * which should not result in new versions being created. - * @param existingContent - * @param newFile - * @return true the content has changed, false the content has not changed significantly. - */ - private boolean isContentChanged(ContentReader existingContent, TempNetworkFile newFile) - { - return !contentComparator.isContentEqual(existingContent, newFile.getFile()); - } - - public void setContentComparator(ContentComparator contentComparator) - { - this.contentComparator = contentComparator; - } - - public ContentComparator getContentComparator() - { - return contentComparator; - } - - @Override - public NetworkFile restoreFile( - SrvSession sess, - TreeConnection tree, - NodeRef rootNode, - String path, - long allocationSize, - NodeRef originalNodeRef) throws IOException - { - // First attempt to restore the node - - if(logger.isDebugEnabled()) - { - logger.debug("restore node:" + originalNodeRef + ", path:" + path); - } - - NodeRef archivedNodeRef = getNodeArchiveService().getArchivedNode(originalNodeRef); - - if(nodeService.exists(archivedNodeRef)) - { - NodeRef restoredNodeRef = nodeService.restoreNode(archivedNodeRef, null, null, null); - if (logger.isDebugEnabled()) - { - logger.debug("node has been restored nodeRef," + restoredNodeRef + ", path " + path); - } - - return openFile(sess, tree, rootNode, path, OpenFileMode.READ_WRITE, true); - } - else - { - return createFile(rootNode, path, allocationSize, false); - } - } - - public void setNodeArchiveService(NodeArchiveService nodeArchiveService) - { - this.nodeArchiveService = nodeArchiveService; - } - - public NodeArchiveService getNodeArchiveService() - { - return nodeArchiveService; - } - - private SimpleCache deletePseudoFileCache; - - public void setDeletePseudoFileCache(SimpleCache deletePseudoFileCache) - { - this.deletePseudoFileCache = deletePseudoFileCache; - } - -} +package org.alfresco.filesys.repo; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.net.InetAddress; +import java.nio.charset.Charset; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.alfresco.AlfrescoContext; +import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; +import org.alfresco.filesys.alfresco.ExtendedDiskInterface; +import org.alfresco.filesys.alfresco.PseudoFileOverlayImpl; +import org.alfresco.filesys.alfresco.RepositoryDiskInterface; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.filesys.AccessDeniedException; +import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException; +import org.alfresco.jlan.server.filesys.DiskDeviceContext; +import org.alfresco.jlan.server.filesys.DiskFullException; +import org.alfresco.jlan.server.filesys.DiskInterface; +import org.alfresco.jlan.server.filesys.DiskSizeInterface; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.FileStatus; +import org.alfresco.jlan.server.filesys.IOControlNotImplementedException; +import org.alfresco.jlan.server.filesys.IOCtlInterface; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.PermissionDeniedException; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.SrvDiskInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; +import org.alfresco.jlan.server.filesys.pseudo.PseudoNetworkFile; +import org.alfresco.jlan.server.filesys.quota.QuotaManager; +import org.alfresco.jlan.server.filesys.quota.QuotaManagerException; +import org.alfresco.jlan.server.locking.FileLockingInterface; +import org.alfresco.jlan.server.locking.LockManager; +import org.alfresco.jlan.server.locking.OpLockInterface; +import org.alfresco.jlan.server.locking.OpLockManager; +import org.alfresco.jlan.smb.SMBException; +import org.alfresco.jlan.smb.server.SMBServer; +import org.alfresco.jlan.smb.server.SMBSrvSession; +import org.alfresco.jlan.util.DataBuffer; +import org.alfresco.jlan.util.MemorySize; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.encoding.ContentCharsetFinder; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.repo.model.filefolder.HiddenAspect; +import org.alfresco.repo.node.archive.NodeArchiveService; +import org.alfresco.repo.node.archive.RestoreNodeReport; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.NodeLockedException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MimetypeService; +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.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.config.ConfigElement; + +/** + * Alfresco Content repository filesystem driver class + *

+ * Provides a JLAN ContentDiskDriver for various JLAN protocols + * such as SMB/CIFS, NFS and FTP. + * + */ +public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedDiskInterface, + DiskInterface, + DiskSizeInterface, + IOCtlInterface, + RepositoryDiskInterface, + OpLockInterface, + FileLockingInterface +{ + // Logging + private static final Log logger = LogFactory.getLog(ContentDiskDriver2.class); + + private static final Log readLogger = LogFactory.getLog("org.alfresco.filesys.repo.ContentDiskDriver2.Read"); + private static final Log writeLogger = LogFactory.getLog("org.alfresco.filesys.repo.ContentDiskDriver2.Write"); + + // Services and helpers + private CifsHelper cifsHelper; + private NamespaceService namespaceService; + private NodeService nodeService; + private SearchService searchService; + private ContentService contentService; + private MimetypeService mimetypeService; + private PermissionService permissionService; + private FileFolderService fileFolderService; + private LockService lockService; + private CheckOutCheckInService checkOutCheckInService; + private AuthenticationContext authContext; + private AuthenticationService authService; + private BehaviourFilter policyBehaviourFilter; + private NodeMonitorFactory m_nodeMonitorFactory; + private ContentComparator contentComparator; + private NodeArchiveService nodeArchiveService; + private HiddenAspect hiddenAspect; + private LockKeeper lockKeeper; + + // TODO Should not be here - should be specific to a context. + private boolean isLockedFilesAsOffline; + + /** + * + */ + public void init() + { + PropertyCheck.mandatory(this, "checkOutCheckInService", checkOutCheckInService); + PropertyCheck.mandatory(this, "cifsHelper", cifsHelper); + PropertyCheck.mandatory(this, "namespaceService", namespaceService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "searchService", searchService); + PropertyCheck.mandatory(this, "contentService", contentService); + PropertyCheck.mandatory(this, "mimetypeService", mimetypeService); + PropertyCheck.mandatory(this, "permissionService", permissionService); + PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); + PropertyCheck.mandatory(this, "lockService",lockService); + PropertyCheck.mandatory(this, "authContext", authContext); + PropertyCheck.mandatory(this, "authService", authService); + PropertyCheck.mandatory(this, "policyBehaviourFilter", policyBehaviourFilter); + PropertyCheck.mandatory(this, "m_nodeMonitorFactory", m_nodeMonitorFactory); + PropertyCheck.mandatory(this, "ioControlHandler", ioControlHandler); + PropertyCheck.mandatory(this, "contentComparator", getContentComparator()); + PropertyCheck.mandatory(this, "nodeArchiveService", nodeArchiveService); + PropertyCheck.mandatory(this, "hiddenAspect", hiddenAspect); + PropertyCheck.mandatory(this, "lockKeeper", lockKeeper); + PropertyCheck.mandatory(this, "deletePseudoFileCache", deletePseudoFileCache); + } + + /** + * Return the CIFS helper + * + * @return CifsHelper + */ + public final CifsHelper getCifsHelper() + { + return this.cifsHelper; + } + + /** + * Return the authentication service + * + * @return AuthenticationService + */ + public final AuthenticationService getAuthenticationService() + { + return authService; + } + + /** + * Return the authentication context + * + * @return AuthenticationContext + */ + public final AuthenticationContext getAuthenticationContext() { + return authContext; + } + + /** + * Return the node service + * + * @return NodeService + */ + public final NodeService getNodeService() + { + return this.nodeService; + } + + /** + * Return the content service + * + * @return ContentService + */ + public final ContentService getContentService() + { + return this.contentService; + } + + /** + * Return the namespace service + * + * @return NamespaceService + */ + public final NamespaceService getNamespaceService() + { + return this.namespaceService; + } + + /** + * Return the search service + * + * @return SearchService + */ + public final SearchService getSearchService(){ + return this.searchService; + } + + /** + * Return the file folder service + * + * @return FileFolderService + */ + public final FileFolderService getFileFolderService() { + return this.fileFolderService; + } + + /** + * Return the permission service + * + * @return PermissionService + */ + public final PermissionService getPermissionService() { + return this.permissionService; + } + + /** + * Return the lock service + * + * @return LockService + */ + public final LockService getLockService() { + return lockService; + } + + /** + * Get the policy behaviour filter, used to inhibit versioning on a per transaction basis + */ + public BehaviourFilter getPolicyFilter() + { + return policyBehaviourFilter; + } + + + /** + * @param contentService the content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @param namespaceService the namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param searchService the search service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Set the permission service + * + * @param permissionService PermissionService + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * Set the authentication context + * + * @param authContext AuthenticationContext + */ + public void setAuthenticationContext(AuthenticationContext authContext) + { + this.authContext = authContext; + } + + /** + * Set the authentication service + * + * @param authService AuthenticationService + */ + public void setAuthenticationService(AuthenticationService authService) + { + this.authService = authService; + } + + /** + * Set the file folder service + * + * @param fileService FileFolderService + */ + public void setFileFolderService(FileFolderService fileService) + { + fileFolderService = fileService; + } + + /** + * @param mimetypeService service for helping with mimetypes and encoding + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * Set the node monitor factory + * + * @param nodeMonitorFactory NodeMonitorFactory + */ + public void setNodeMonitorFactory(NodeMonitorFactory nodeMonitorFactory) { + m_nodeMonitorFactory = nodeMonitorFactory; + } + + + /** + * Set the lock service + * + * @param lockService LockService + */ + public void setLockService(LockService lockService) { + this.lockService = lockService; + } + + /** + * Set the policy behaviour filter, used to inhibit versioning on a per transaction basis + * + * @param policyFilter PolicyBehaviourFilter + */ + public void setPolicyFilter(BehaviourFilter policyFilter) + { + this.policyBehaviourFilter = policyFilter; + } + + /** + * @param hiddenAspect + */ + public void setHiddenAspect(HiddenAspect hiddenAspect) + { + this.hiddenAspect = hiddenAspect; + } + + /** + * @param lockKeeper lockKeeper + */ + public void setAlfrescoLockKeeper(LockKeeper lockKeeper) + { + this.lockKeeper = lockKeeper; + } + + // Configuration key names + + private static final String KEY_STORE = "store"; + private static final String KEY_ROOT_PATH = "rootPath"; + private static final String KEY_RELATIVE_PATH = "relativePath"; + + /** + * Parse and validate the parameter string and create a device context object for this instance + * of the shared device. The same DeviceInterface implementation may be used for multiple + * shares. + *

+ * @deprecated - no longer used. Construction of context is via spring now. + * @param deviceName The name of the device + * @param cfg ConfigElement the configuration of the device context. + * @return DeviceContext + * @exception DeviceContextException + */ + public DeviceContext createContext(String deviceName, ConfigElement cfg) throws DeviceContextException + { + logger.error("Obsolete method called"); + throw new DeviceContextException("Obsolete Method called"); + } + + /* + * Register context implementation + *

+ * Results in various obscure bits and pieces being initialised, most importantly the + * calculation of the root node ref. + *

+ * There's a load of initialisation that needs to be moved out of this method, like the + * instantiation of the lock manager, quota manager and node monitor. + */ + public void registerContext(DeviceContext ctx) throws DeviceContextException + { + logger.debug("registerContext"); + super.registerContext(ctx); + + final ContentContext context = (ContentContext)ctx; + + final String rootPath = context.getRootPath(); + final String storeValue = context.getStoreName(); + + /** + * Work using the repo needs to run as system. + */ + RunAsWork runAsSystem = new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + StoreRef storeRef = new StoreRef(storeValue); + + // Connect to the repo and ensure that the store exists + + if (! nodeService.exists(storeRef)) + { + throw new DeviceContextException("Store not created prior to application startup: " + storeRef); + } + + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + // Find the root node for this device + List nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPath, null, namespaceService, false); + + NodeRef rootNodeRef = null; + + if (nodeRefs.size() > 1) + { + throw new DeviceContextException("Multiple possible roots for device: \n" + + " root path: " + rootPath + "\n" + + " results: " + nodeRefs); + } + else if (nodeRefs.size() == 0) + { + // Nothing found + throw new DeviceContextException("No root found for device: \n" + + " root path: " + rootPath); + } + else + { + // We found the root node ref + rootNodeRef = nodeRefs.get(0); + } + + // Check if a relative path has been specified + String relPath = context.getRelativePath(); + + try + { + if ( relPath != null && relPath.length() > 0) + { + // Find the node and validate that the relative path is to a folder + NodeRef relPathNode = cifsHelper.getNodeRef( rootNodeRef, relPath); + + if ( cifsHelper.isDirectory( relPathNode) == false) + { + throw new DeviceContextException("Relative path is not a folder, " + relPath); + } + + // Use the relative path node as the root of the filesystem + rootNodeRef = relPathNode; + } + else + { + // Make sure the default root node is a folder + if ( cifsHelper.isDirectory( rootNodeRef) == false) + { + throw new DeviceContextException("Root node is not a folder type node"); + } + } + } + catch (Exception ex) + { + if(logger.isDebugEnabled()) + { + logger.debug("Error during create context", ex); + } + throw new DeviceContextException("Unable to find root node.", ex); + } + + // Record the root node ref + if(logger.isDebugEnabled()) + { + logger.debug("set root node ref:" + rootNodeRef); + } + context.setRootNodeRef(rootNodeRef); + + return null; + } + }; + + /** + * Run the above code as system - in particular resolves root node ref. + */ + AuthenticationUtil.runAs(runAsSystem, AuthenticationUtil.getSystemUserName()); + + + /* + * Now we have some odds and ends below that should really be configured elsewhere + */ + + // Check if locked files should be marked as offline + if ( context.getOfflineFiles() ) + { + // Enable marking locked files as offline + isLockedFilesAsOffline = true; + logger.info("Locked files will be marked as offline"); + } + + // Enable file state caching + +// context.enableStateCache(serverConfig, true); +// context.getStateCache().setCaseSensitive( false); + + logger.debug("initialise the node monitor"); + // Install the node service monitor + if ( !context.getDisableNodeMonitor() && m_nodeMonitorFactory != null) + { + NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor(context); + context.setNodeMonitor( nodeMonitor); + } + + logger.debug("initialise the file state lock manager"); + + + // Check if oplocks are enabled + + if ( context.getDisableOplocks() == true) + { + logger.warn("Oplock support disabled for filesystem " + context.getDeviceName()); + } + + // Start the quota manager, if enabled + if ( context.hasQuotaManager()) + { + try + { + // Start the quota manager + context.getQuotaManager().startManager( this, context); + logger.info("Quota manager enabled for filesystem"); + } + catch ( QuotaManagerException ex) + { + logger.error("Failed to start quota manager", ex); + } + } + + // TODO mode to spring + PseudoFileOverlayImpl ps = new PseudoFileOverlayImpl(); + ps.setContext(context); + ps.setNodeService(nodeService); + ps.setSysAdminParams(context.getSysAdminParams()); + ps.setDeletePseudoFileCache(deletePseudoFileCache); + context.setPseudoFileOverlay(ps); + ps.init(); + } + + /** + * Determine if the disk device is read-only. + * + * @param sess Server session + * @param ctx Device context + * @return boolean + * @exception java.io.IOException If an error occurs. + */ + public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("isReadOnly"); + } + return !m_transactionService.getAllowWrite(); + } + + /** + * Get the file information for the specified file. + * + * @param session Server session + * @param tree Tree connection + * @param path File name/path that information is required for. + * @return File information if valid, else null + * @exception java.io.IOException The exception description. + */ + public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("getFileInformation:" + path + ", session:" + session.getUniqueId()); + } + ContentContext ctx = (ContentContext) tree.getContext(); + + boolean readOnly = !m_transactionService.getAllowWrite(); + + if ( path == null || path.length() == 0) + { + path = FileName.DOS_SEPERATOR_STR; + } + + String infoPath = path; + + try + { + FileInfo finfo = null; + + // Is the node a pseudo file ? + if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) + { + String[] paths = FileName.splitPath(path); + // lookup parent directory + NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); + + // Check whether we are opening a pseudo file + if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) + { + PseudoFile pfile = ctx.getPseudoFileOverlay().getPseudoFile(dirNodeRef, paths[1]); + if(logger.isDebugEnabled()) + { + if (pfile != null) + { + logger.debug("returning psuedo file details:" + pfile); + } + else + { + logger.debug("Try to return deleted pseudo file :" + paths[1]); + } + } + if (pfile != null) + { + return pfile.getFileInfo(); + } + else + { + throw new FileNotFoundException("The pseudo file was deleted"); + } + } + } + + // no - this is not a specially named pseudo file. + NodeRef nodeRef = getNodeForPath(tree, infoPath); + + if ( nodeRef != null) + { + // Get the file information for the node + + finfo = getCifsHelper().getFileInformation(nodeRef, readOnly, isLockedFilesAsOffline); + + /** + * Special processing for root node + */ + if(path.equals(FileName.DOS_SEPERATOR_STR)) + { + finfo.setFileName(""); + } + + // DEBUG + if ( logger.isDebugEnabled()) + { + logger.debug("getFileInformation found nodeRef for nodeRef :"+ nodeRef + ", path: " + path); + } + + // Moved to CIFS Helper +// // Set the file id from the node's DBID +// long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); +// finfo.setFileId((int) (id & 0xFFFFFFFFL)); + } + + // Return the file information or null if the node ref does not exist + return finfo; + } + catch (FileNotFoundException e) + { + // Debug + + if (logger.isDebugEnabled()) + { + // exception not logged - cifs does lots of these + logger.debug("Get file info - file not found, " + path); + } + throw e; + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Get file info - access denied, " + path, ex); + } + + // Convert to a filesystem access denied status + throw new AccessDeniedException("Get file information " + path); + } + catch (AlfrescoRuntimeException ex) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Get file info error" + path, ex); + } + + // Convert to a general I/O exception + throw new IOException("Get file information " + path, ex); + } + } + + /** + * Start a new search on the filesystem using the specified searchPath that may contain + * wildcards. + * + * @param session Server session + * @param tree Tree connection + * @param searchPath File(s) to search for, may include wildcards. + * @param attributes Attributes of the file(s) to search for, see class SMBFileAttribute. + * @return SearchContext + * @exception java.io.FileNotFoundException If the search could not be started. + */ + public SearchContext startSearch(SrvSession session, TreeConnection tree, String searchPath, int attributes) throws FileNotFoundException + { + if(logger.isDebugEnabled()) + { + logger.debug("startSearch: "+ searchPath + ", session:" + session.getUniqueId()); + } + // Access the device context + + ContentContext ctx = (ContentContext) tree.getContext(); + + try + { + String searchFileSpec = searchPath; + + NodeRef searchRootNodeRef = ctx.getRootNode(); + + String[] paths = FileName.splitPath(searchPath); + String dotPath = paths[0]; + + // lookup parent directory + NodeRef dirNodeRef = getNodeForPath(tree, dotPath); + if(dirNodeRef != null) + { + searchRootNodeRef = dirNodeRef; + searchFileSpec = paths[1]; + } + + // Convert the all files wildcard + if ( searchFileSpec.equals( "*.*")) + { + searchFileSpec = "*"; + } + + // Debug + long startTime = 0L; + if ( logger.isDebugEnabled()) + { + startTime = System.currentTimeMillis(); + } + + // Perform the search + + logger.debug("Call repo to do search"); + + List results = getCifsHelper().getNodeRefs(searchRootNodeRef, searchFileSpec); + // Debug + if ( logger.isDebugEnabled()) + { + long endTime = System.currentTimeMillis(); + if (( endTime - startTime) > 500) + { + logger.debug("Search for searchPath=" + searchPath + ", searchSpec=" + searchFileSpec + ", searchRootNode=" + searchRootNodeRef + " took " + + ( endTime - startTime) + "ms results=" + results.size()); + } + } + + /** + * Search pseudo files if they are enabled + */ + PseudoFileList pseudoList = null; + if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) + { + logger.debug("search pseudo files"); + pseudoList = ctx.getPseudoFileOverlay().searchPseudoFiles(dirNodeRef, searchFileSpec); + } + + DotDotContentSearchContext searchCtx = new DotDotContentSearchContext(getCifsHelper(), results, searchFileSpec, pseudoList, paths[0], isLockedFilesAsOffline); + + FileInfo dotInfo = getCifsHelper().getFileInformation(searchRootNodeRef, false, isLockedFilesAsOffline); + + if ( searchPath.equals( FileName.DOS_SEPERATOR_STR)) { + // Searching the root folder, re-use the search folder file information for the '..' pseudo entry + FileInfo dotDotInfo = new FileInfo(); + dotDotInfo.copyFrom(dotInfo); + searchCtx.setDotInfo(dotInfo); + searchCtx.setDotDotInfo( dotDotInfo); + } + else + { + String[] parent = FileName.splitPath(dotPath); + NodeRef parentNodeRef = getNodeForPath(tree, parent[0]); + if(parentNodeRef != null) + { + FileInfo dotDotInfo = getCifsHelper().getFileInformation(parentNodeRef, false, isLockedFilesAsOffline); + searchCtx.setDotDotInfo(dotDotInfo); + } + + // Searching a normal, non root, folder + // Need to set dot and dotdot + searchCtx.setDotInfo(dotInfo); + + } + + // Debug + if (logger.isDebugEnabled()) + { + logger.debug("Started search: search path=" + searchPath + " attributes=" + attributes + ", ctx=" + searchCtx); + } + + // TODO -- + // Need to resolve the file info here so it's within the transaction boundary. + + return searchCtx; + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Start search - access denied, " + searchPath); + } + + // Convert to a file not found status + + throw new FileNotFoundException("Start search " + searchPath); + } + catch (AlfrescoRuntimeException ex) + { + // This is an error even though we "handle" it here. + + if ( logger.isErrorEnabled()) + { + logger.error("Exception in Start search", ex); + } + + // Convert to a file not found status + + throw new FileNotFoundException("Start search " + searchPath); + } + } + + /** + * Check if the specified file exists, and whether it is a file or directory. + * + * + * @param session Server session + * @param tree Tree connection + * @param name the path of the file + * @return FileStatus (0: NotExist, 1 : FileExist, 2: DirectoryExists) + * @see FileStatus + */ + public int fileExists(SrvSession session, TreeConnection tree, String name) + { + if(logger.isDebugEnabled()) + { + logger.debug("fileExists:" + name + ", session:" + session.getUniqueId()); + } + ContentContext ctx = (ContentContext) tree.getContext(); + int status = FileStatus.Unknown; + try + { + if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) + { + String[] paths = FileName.splitPath(name); + // lookup parent directory + NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); + // Check whether we are opening a pseudo file + if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) + { + return FileStatus.FileExists; + } + } + + // Get the file information to check if the file/folder exists + FileInfo info = getFileInformation(session, tree, name); + if (info.isDirectory()) + { + if(logger.isDebugEnabled()) + { + logger.debug("is directory"); + } + status = FileStatus.DirectoryExists; + } + else + { + if(logger.isDebugEnabled()) + { + logger.debug("is file"); + } + status = FileStatus.FileExists; + } + + if (logger.isDebugEnabled()) + { + logger.debug("File status determined: name=" + name + " status=" + status); + } + + return status; + } + catch (FileNotFoundException e) + { + if(logger.isDebugEnabled()) + { + logger.debug("file does not exist"); + } + status = FileStatus.NotExist; + return status; + } + catch (IOException e) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("File exists error, " + name, e); + } + + status = FileStatus.NotExist; + return status; + } + + } + + /** + * Open a file or folder - obsolete implementation. + * + * @param session SrvSession + * @param tree TreeConnection + * @param params FileOpenParams + * @return NetworkFile + * @exception IOException + */ + public NetworkFile openFile(SrvSession session, TreeConnection tree, FileOpenParams params) throws IOException + { + // obsolete + logger.error("Obsolete method called"); + throw new AlfrescoRuntimeException("obsolete method called"); + } + + /** + * Create a new file on the file system. + * + * @param sess Server session + * @param tree Tree connection + * @param params File create parameters + * @return NetworkFile + * @exception java.io.IOException If an error occurs. + */ + public NetworkFile createFile(SrvSession sess, final TreeConnection tree, final FileOpenParams params) throws IOException + { + // Obsolete + logger.error("Obsolete method called"); + throw new AlfrescoRuntimeException("obsolete method called"); + } + + /** + * Create a new directory on this file system. + * + * + * @param sess Server session + * @param tree Tree connection. + * @param params Directory create parameters + * @exception java.io.IOException If an error occurs. + */ + public void createDirectory(SrvSession sess, final TreeConnection tree, final FileOpenParams params) throws IOException + { + final ContentContext ctx = (ContentContext) tree.getContext(); + + if (logger.isDebugEnabled()) + { + logger.debug("createDirectory :" + params); + } + + try + { + NodeRef dirNodeRef; + String folderName; + + String path = params.getPath(); + + String[] paths = FileName.splitPath(path); + + if (paths[0] != null && paths[0].length() > 1) + { + // lookup parent directory + dirNodeRef = getNodeForPath(tree, paths[0]); + folderName = paths[1]; + } + else + { + dirNodeRef = ctx.getRootNode(); + folderName = path; + } + + if(dirNodeRef == null) + { + throw new IOException("Create directory parent folder not found" + params.getFullPath()); + } + + NodeRef nodeRef = getCifsHelper().createNode(dirNodeRef, folderName, ContentModel.TYPE_FOLDER); + + + if (logger.isDebugEnabled()) + { + logger.debug("Created directory: path=" + params.getPath() + " file open params=" + params + " node=" + nodeRef); + } + + // void return + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Create directory - access denied, " + params.getFullPath()); + } + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Create directory " + params.getFullPath()); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Create directory error", ex); + } + + // Convert to a general I/O exception + + throw new IOException("Create directory " + params.getFullPath(), ex); + } + } + + /** + * Delete the directory from the filesystem. + *

+ * The directory must be empty in order to be able to delete ity + * + * @param session Server session + * @param tree Tree connection + * @param dir Directory name. + * @exception java.io.IOException The exception description. + */ + public void deleteDirectory(SrvSession session, TreeConnection tree, final String dir) throws IOException + { + // get the device root + + if (logger.isDebugEnabled()) + { + logger.debug("deleteDirectory: " + dir + ", session:" + session.getUniqueId()); + } + + ContentContext ctx = (ContentContext) tree.getContext(); + final NodeRef deviceRootNodeRef = ctx.getRootNode(); + + try + { + // Get the node for the folder + + NodeRef nodeRef = getCifsHelper().getNodeRef(deviceRootNodeRef, dir); + if (fileFolderService.exists(nodeRef)) + { + // Check if the folder is empty + // Get pseudo files + PseudoFileList pseudoFileList = new PseudoFileList(); + if (session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) + { + pseudoFileList = ctx.getPseudoFileOverlay().searchPseudoFiles(nodeRef, "*"); + } + if (getCifsHelper().isFolderEmpty(nodeRef) && pseudoFileList.isEmpty()) + { + // Delete the folder node + fileFolderService.delete(nodeRef); + } + else + { + throw new DirectoryNotEmptyException( dir); + } + } + + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Deleted directory: directory=" + dir + " node=" + nodeRef); + } + // void return + } + catch (FileNotFoundException e) + { + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Delete directory - file not found, " + dir); + } + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Delete directory - access denied, " + dir); + } + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Delete directory, access denied :" + dir); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("Delete directory", ex); + + // Convert to a general I/O exception + + throw new IOException("Delete directory " + dir, ex); + } + } + + /** + * Flush any buffered output for the specified file. + * + * @param session Server session + * @param tree Tree connection + * @param file Network file context. + * @exception java.io.IOException The exception description. + */ + public void flushFile(SrvSession session, TreeConnection tree, NetworkFile file) throws IOException + { + // Debug + + ContentContext ctx = (ContentContext) tree.getContext(); + + if ( logger.isDebugEnabled()) + { + logger.debug("Flush file=" + file.getFullName()+ ", session:" + session.getUniqueId()); + } + + // Flush the file data + file.flushFile(); + } + + /** + * Close the file. + * + * @param session Server session + * @param tree Tree connection. + * @param file Network file context. + * + * @exception java.io.IOException If an error occurs. + */ + public void closeFile(SrvSession session, TreeConnection tree, final NetworkFile file) throws IOException + { + throw new AlfrescoRuntimeException("obsolete method called"); + } + + public void deleteFile(final SrvSession session, final TreeConnection tree, final String name) throws IOException + { + throw new AlfrescoRuntimeException("obsolete method called"); + } + + /** + * Delete the specified file. + * + * @param session Server session + * @param tree Tree connection + * @param rootNode Root node + * @param path NetworkFile + * @exception java.io.IOException The exception description. + * @return NodeRef of deletedFile + */ + public NodeRef deleteFile2(final SrvSession session, final TreeConnection tree, NodeRef rootNode, String path) throws IOException + { + // Get the device context + + final ContentContext ctx = (ContentContext) tree.getContext(); + + if(logger.isDebugEnabled()) + { + logger.debug("deleteFile:" + path + ", session:" + session.getUniqueId()); + } + + try + { + if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) + { + String[] paths = FileName.splitPath(path); + // lookup parent directory + NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); + + // Check whether we are closing a pseudo file + if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) + { + // pseudo delete a pseudo file + ctx.getPseudoFileOverlay().delete(dirNodeRef, paths[1]); + return null; + } + } + + // Check if there is a quota manager enabled, if so then we need to save the current file size + + final QuotaManager quotaMgr = ctx.getQuotaManager(); + + // Get the node and delete it + final NodeRef nodeRef = getNodeForPath(tree, path); + + if (fileFolderService.exists(nodeRef)) + { + lockKeeper.removeLock(nodeRef); + + // Get the size of the file being deleted + final FileInfo fInfo = quotaMgr == null ? null : getFileInformation(session, tree, path); + + if(logger.isDebugEnabled()) + { + logger.debug("deleted file" + path); + } + fileFolderService.delete(nodeRef); + + //TODO Needs to be post-commit + if (quotaMgr != null) + { + quotaMgr.releaseSpace(session, tree, fInfo.getFileId(), path, fInfo.getSize()); + } + + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Deleted file: " + path + ", nodeRef=" + nodeRef); + } + + // void return + return nodeRef; + } + } + catch (NodeLockedException ex) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Delete file - access denied (locked)", ex); + } + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Unable to delete " + path); + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Delete file - access denied", ex); + } + + // Convert to a filesystem access denied status + throw new AccessDeniedException("Unable to delete " + path); + } + catch (IOException ex) + { + // Allow I/O Exceptions to pass through + if ( logger.isDebugEnabled()) + { + logger.debug("Delete file error - pass through IO Exception", ex); + } + throw ex; + } + catch (Exception ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Delete file error", ex); + } + + // Convert to a general I/O exception + IOException ioe = new IOException("Delete file " + path); + ioe.initCause(ex); + throw ioe; + } + return null; + } + + public void renameFile(final SrvSession session, final TreeConnection tree, final String oldName, final String newName) + throws IOException + { + throw new AlfrescoRuntimeException("obsolete method called"); + } + + /** + * Rename the specified file. + * + * @param rootNode + * @param oldName path/name of old file + * @param newName path/name of new file + * @exception java.io.IOException The exception description. + */ + public void renameFile(NodeRef rootNode, final String oldName, final String newName, boolean soft, boolean moveAsSystem) + throws IOException + { + + if (logger.isDebugEnabled()) + { + logger.debug("RenameFile oldName=" + oldName + ", newName=" + newName + ", soft" + soft); + } + + try + { + // Get the file/folder to move + final NodeRef nodeToMoveRef = getCifsHelper().getNodeRef(rootNode, oldName); + + // Check if the node is a link node + + if ( nodeToMoveRef != null && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null) + { + throw new AccessDeniedException("Cannot rename link nodes"); + } + + // Get the new target folder - it must be a folder + String[] splitPaths = FileName.splitPath(newName); + String[] oldPaths = FileName.splitPath(oldName); + + final NodeRef targetFolderRef = getCifsHelper().getNodeRef(rootNode, splitPaths[0]); + final NodeRef sourceFolderRef = getCifsHelper().getNodeRef(rootNode, oldPaths[0]); + final String name = splitPaths[1]; + + // Check if this is a rename within the same folder + + final boolean sameFolder = splitPaths[0].equalsIgnoreCase(oldPaths[0]); + + // Check if we are renaming a folder, or the rename is to a different folder + boolean isFolder = getCifsHelper().isDirectory(nodeToMoveRef); + + if ( isFolder == true || sameFolder == false) + { + + // Rename or move the file/folder to another folder + if (sameFolder == true) + { + fileFolderService.rename(nodeToMoveRef, name); + if (logger.isDebugEnabled()) + { + logger.debug(" Renamed " + (isFolder ? "folder" : "file") + ":" + + " Old name: " + oldName + "\n" + + " New name: " + newName + "\n" ); + } + + } + else + { + if (moveAsSystem) + { + if (logger.isDebugEnabled()) + { + logger.debug("Run move as System for: " + oldName); + } + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + return fileFolderService.moveFrom(nodeToMoveRef, sourceFolderRef, targetFolderRef, name); + } + }, AuthenticationUtil.getSystemUserName()); + } + else + { + fileFolderService.moveFrom(nodeToMoveRef, sourceFolderRef, targetFolderRef, name); + } + + logger.debug( + "Moved between different folders: \n" + + " Old name: " + oldName + "\n" + + " New name: " + newName + "\n" + + " Source folder: " + sourceFolderRef + "\n" + + " Target folder: " + targetFolderRef + "\n" + + " Node: " + nodeToMoveRef + "\n" + + " Aspects: " + nodeService.getAspects(nodeToMoveRef)); + } + + if (logger.isDebugEnabled()) + { + logger.debug(" Renamed " + (isFolder ? "folder" : "file") + " using " + + (sameFolder ? "rename" : "move")); + } + } + else + { + // Rename a file within the same folder + + + if (logger.isDebugEnabled()) + { + logger.debug( + "Rename file within same folder: \n" + + " Old name: " + oldName + "\n" + + " New name: " + newName + "\n" + + " Source folder: " + sourceFolderRef + "\n" + + " Target folder: " + targetFolderRef + "\n" + + " Node: " + nodeToMoveRef + "\n" + + " Aspects: " + nodeService.getAspects(nodeToMoveRef)); + } + if(soft) + { + logger.debug("this is a soft delete - use copy rather than rename"); + fileFolderService.copy(nodeToMoveRef, null, name); + nodeService.addAspect(nodeToMoveRef, ContentModel.ASPECT_SOFT_DELETE, null); + } + else + { + fileFolderService.rename(nodeToMoveRef, name); + } + } + } + catch (org.alfresco.service.cmr.model.FileNotFoundException e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Rename file - about to throw file not exists exception file:" + oldName, e); + } + throw new java.io.FileNotFoundException("renameFile: file not found file: + oldName"); + } + catch (org.alfresco.service.cmr.model.FileExistsException e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Rename file - about to throw file exists exception", e); + } + throw new org.alfresco.jlan.server.filesys.FileExistsException(newName); + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + if (logger.isDebugEnabled()) + { + logger.debug("Rename file - about to throw permissions denied exception", ex); + } + throw new org.alfresco.jlan.server.filesys.PermissionDeniedException("renameFile: No permissions to rename file:" + oldName); + } + catch (NodeLockedException ex) + { + if (logger.isDebugEnabled()) + { + logger.debug("Rename file - about to throw access denied exception", ex); + } + // Convert to an filesystem access denied exception + throw new AccessDeniedException("renameFile: Access Denied - Node locked file:" + oldName); + } + catch (AlfrescoRuntimeException ex) + { + if (logger.isDebugEnabled()) + { + logger.debug("Rename file about to throw access denied exception", ex); + } + throw new AlfrescoRuntimeException("renameFile failed: \n" + + " Old name: " + oldName + "\n" + + " New name: " + newName + "\n" + + ex); + + } + } + + /** + * Set file information + * + * @param sess SrvSession + * @param tree TreeConnection + * @param name String + * @param info FileInfo + * @exception IOException + */ + public void setFileInformation(SrvSession sess, final TreeConnection tree, final String name, final FileInfo info) throws IOException + { + // Get the device context + + final ContentContext ctx = (ContentContext) tree.getContext(); + + if (logger.isDebugEnabled()) + { + logger.debug("setFileInformation name=" + name + ", info=" + info); + } + + NetworkFile networkFile = info.getNetworkFile(); + + try + { + + if(sess.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) + { + String[] paths = FileName.splitPath(name); + // lookup parent directory + NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); + + // Check whether we are opening a pseudo file + if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) + { + if(logger.isDebugEnabled()) + { + logger.debug("pseudo file so do nothing"); + } + return; + } + } + + // Get the file/folder node + NodeRef nodeRef = getNodeForPath(tree, name); + + // Check permissions on the file/folder node + + if ( permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) + { + if(logger.isDebugEnabled()) + { + logger.debug("write access denied to :" + name); + } + throw new AccessDeniedException("No write access to " + name); + } + + // Inhibit versioning for this transaction + getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE); + + // Check if the file is being marked for deletion, if so then check if the file is locked + + /* + * Which DeleteOnClose flag has priority? + * SetDeleteOnClose is not set or used in this method. + * The NTProtocolHandler sets the deleteOnClose in both + * info and the NetworkFile - it's the one in NetworkFile that results in the file being deleted. + */ + if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Set Delete On Close for :" + name); + } + // Check for delete permission + if ( permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) + { + throw new PermissionDeniedException("No delete access to :" + name); + } + + // Check if the node is locked + lockService.checkForLock(nodeRef); + + // Get the node for the folder + + if (fileFolderService.exists(nodeRef)) + { + // Check if it is a folder that is being deleted, make sure it is empty + + boolean isFolder = true; + + ContentFileInfo cInfo = getCifsHelper().getFileInformation(nodeRef, false, isLockedFilesAsOffline); + + if ( cInfo != null && cInfo.isDirectory() == false) + { + isFolder = false; + } + + // Check if the folder is empty + if ( isFolder == true && getCifsHelper().isFolderEmpty( nodeRef) == false) + { + throw new DirectoryNotEmptyException( name); + } + } + + + } + + if(info.hasSetFlag(FileInfo.SetAttributes)) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Set attributes" + name + ", file attrs = " + info.getFileAttributes()); + } + + //TODO MER Think we may need to implement, Temporary, Hidden, System, Archive + if(info.isSystem()) + { + logger.debug("Set system aspect (not yet implemented)" + name); + } + if(info.isTemporary()) + { + logger.debug("Set temporary aspect (not yet implemented)" + name); + } + + if(info.isHidden()) + { + // yes is hidden + if ( logger.isDebugEnabled()) + { + logger.debug("Set hidden aspect" + name); + } + hiddenAspect.hideNodeExplicit(nodeRef); + } + else + { + // not hidden + if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_HIDDEN)) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Reset hidden aspect" + name); + } + hiddenAspect.unhideExplicit(nodeRef); + } + } + } // End of setting attributes + + if( info.hasSetFlag(FileInfo.SetAllocationSize)) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Set allocation size" + name + info.getAllocationSize()); + } + // Not yet implemented + } + + if( info.hasSetFlag(FileInfo.SetFileSize)) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Set file size" + name + info.getSize()); + } + // Not yet implemented + } + + if( info.hasSetFlag(FileInfo.SetMode)) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Set Mode" + name + info.getMode()); + } + // Not yet implemented - set the unix mode e.g. 777 + } + + // Set the creation and modified date/time + Map auditableProps = new HashMap(5); + + + + if ( info.hasSetFlag(FileInfo.SetCreationDate) && info.hasCreationDateTime()) + { + // Set the creation date on the file/folder node + Date createDate = new Date(info.getCreationDateTime()); + auditableProps.put(ContentModel.PROP_CREATED, createDate); + if ( logger.isDebugEnabled()) + { + logger.debug("Set creation date" + name + ", " + createDate); + } + } + if ( info.hasSetFlag(FileInfo.SetModifyDate) && info.hasModifyDateTime()) + { + // Set the modification date on the file/folder node + Date modifyDate = new Date( info.getModifyDateTime()); + auditableProps.put(ContentModel.PROP_MODIFIED, modifyDate); + + // Set the network file so we don't reverse this change in close file. + if(networkFile != null && !networkFile.isReadOnly()) + { + networkFile.setModifyDate(info.getModifyDateTime()); + if(networkFile instanceof TempNetworkFile) + { + TempNetworkFile tnf = (TempNetworkFile)networkFile; + tnf.setModificationDateSetDirectly(true); + } + } + + if ( logger.isDebugEnabled()) + { + logger.debug("Set modification date" + name + ", " + modifyDate); + } + + } + + // Change Date is last write ? + if ( info.hasSetFlag(FileInfo.SetChangeDate) && info.hasChangeDateTime()) + { + Date changeDate = new Date( info.getChangeDateTime()); + if ( logger.isDebugEnabled()) + { + logger.debug("Set change date (Not implemented)" + name + ", " + changeDate); + } + + } + if ( info.hasSetFlag(FileInfo.SetAccessDate) && info.hasAccessDateTime()) + { + Date accessDate = new Date( info.getAccessDateTime()); + if ( logger.isDebugEnabled()) + { + logger.debug("Set access date (Not implemented)" + name + ", " + accessDate); + } + } + + // Did we have any cm:auditable properties? + if (auditableProps.size() > 0) + { + getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + nodeService.addProperties(nodeRef, auditableProps); + + // DEBUG + if ( logger.isDebugEnabled()) + { + logger.debug("Set auditable props: " + auditableProps + " file=" + name); + } + } + + return; + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Set file information - access denied, " + name); + } + // Convert to a filesystem access denied status + throw new AccessDeniedException("Set file information " + name); + } + catch (AlfrescoRuntimeException ex) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Open file error", ex); + } + // Convert to a general I/O exception + + throw new IOException("Set file information " + name, ex); + } + } + + /** + * Truncate a file to the specified size + * + * @param sess Server session + * @param tree Tree connection + * @param file Network file details + * @param size New file length + * @exception java.io.IOException The exception description. + */ + public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long size) throws IOException + { + // Keep track of the allocation/release size in case the file resize fails + + ContentContext ctx = (ContentContext) tree.getContext(); + + if (logger.isDebugEnabled()) + { + logger.debug("truncateFile file:" + file + ", size: "+ size); + } + + long allocSize = 0L; + long releaseSize = 0L; + + // Check if there is a quota manager + QuotaManager quotaMgr = ctx.getQuotaManager(); + + if ( ctx.hasQuotaManager()) { + + // Check if the file content has been opened, we need the content to be opened to get the + // current file size + + if ( file instanceof ContentNetworkFile) { + ContentNetworkFile contentFile = (ContentNetworkFile) file; + if ( contentFile.hasContent() == false) + { + contentFile.openContent( false, false); + } + } + else if( file instanceof TempNetworkFile) + { + + } + else + { + throw new IOException("Invalid file class type, " + file.getClass().getName()); + } + // Determine if the new file size will release space or require space allocating + + if ( size > file.getFileSize()) + { + + // Calculate the space to be allocated + + allocSize = size - file.getFileSize(); + + // Allocate space to extend the file + + quotaMgr.allocateSpace(sess, tree, file, allocSize); + } + else + { + + // Calculate the space to be released as the file is to be truncated, release the space if + // the file truncation is successful + releaseSize = file.getFileSize() - size; + } + } + + // Check if this is a file extend, update the cached allocation size if necessary + + if ( file instanceof ContentNetworkFile) { + + // Get the cached state for the file + ContentNetworkFile contentFile = (ContentNetworkFile) file; + FileState fstate = contentFile.getFileState(); + if ( fstate != null && size > fstate.getAllocationSize()) + { + fstate.setAllocationSize(size); + } + } + + if( file instanceof TempNetworkFile) + { + TempNetworkFile contentFile = (TempNetworkFile) file; + FileState fstate = contentFile.getFileState(); + if ( fstate != null && size > fstate.getAllocationSize()) + { + fstate.setAllocationSize(size); + } + } + + // Set the file length + + try + { + file.truncateFile(size); + } + catch (IOException ex) + { + if(logger.isDebugEnabled()) + { + logger.debug("unable to truncate the file + :" + file.getFullName(), ex); + } + // Check if we allocated space to the file + + if ( allocSize > 0 && quotaMgr != null) + { + quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, allocSize); + } + + // Rethrow the exception + throw ex; + } + + // Check if space has been released by the file resizing + + if ( releaseSize > 0 && quotaMgr != null) + { + quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, releaseSize); + } + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Truncated file: network file=" + file + " size=" + size); + + } + } + + /** + * Read a block of data from the specified file. + * + * @param sess Session details + * @param tree Tree connection + * @param file Network file + * @param buffer Buffer to return data to + * @param bufferPosition Starting position in the return buffer + * @param size Maximum size of data to return + * @param fileOffset File offset to read data + * @return Number of bytes read + * @exception java.io.IOException The exception description. + */ + public int readFile( + SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buffer, int bufferPosition, int size, long fileOffset) throws IOException + { + // Check if the file is a directory + + if(readLogger.isDebugEnabled()) + { + readLogger.debug("read File:" + file + ", size" + size); + } + + if(file.isDirectory()) + { + if(logger.isDebugEnabled()) + { + logger.debug("read file called for a directory - throw AccessDeniedException"); + } + throw new AccessDeniedException("read called for a directory"); + } + + // Read a block of data from the file + + int count = file.readFile(buffer, size, bufferPosition, fileOffset); + + if ( count == -1) + { + // Read count of -1 indicates a read past the end of file + + count = 0; + } + + // Debug + + //ContentContext ctx = (ContentContext) tree.getContext(); + + if (readLogger.isDebugEnabled()) + { + readLogger.debug("Read bytes from file: network file=" + file + " buffer size=" + buffer.length + " buffer pos=" + bufferPosition + + " size=" + size + " file offset=" + fileOffset + " bytes read=" + count); + } + + return count; + } + + /** + * Seek to the specified file position. + * + * @param sess Server session + * @param tree Tree connection + * @param file Network file. + * @param pos Position to seek to. + * @param typ Seek type. + * @return New file position, relative to the start of file. + */ + public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("seek File"); + } + + // Check if the file is a directory + + if ( file.isDirectory()) + { + throw new AccessDeniedException(); + } + + // Set the file position + + return file.seekFile(pos, typ); + } + + /** + * Write a block of data to the file. + * + * @param sess Server session + * @param tree Tree connection + * @param file Network file details + * @param buffer byte[] Data to be written + * @param bufferOffset Offset within the buffer that the data starts + * @param size int Data length + * @param fileOffset Position within the file that the data is to be written. + * @return Number of bytes actually written + * @exception java.io.IOException The exception description. + */ + public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buffer, int bufferOffset, int size, long fileOffset) throws IOException + { + if(writeLogger.isDebugEnabled()) + { + writeLogger.debug("write File:" + file + " size:" + size); + } + + // Check if there is a quota manager + + ContentContext ctx = (ContentContext) tree.getContext(); + QuotaManager quotaMgr = ctx.getQuotaManager(); + + long curSize = file.getFileSize(); + + if ( quotaMgr != null) + { + + // Check if the file requires extending + + long extendSize = 0L; + long endOfWrite = fileOffset + size; + + if ( endOfWrite > curSize) + { + // Calculate the amount the file must be extended + + extendSize = endOfWrite - file.getFileSize(); + + // Allocate space for the file extend + if(writeLogger.isDebugEnabled()) + { + writeLogger.debug("writeFile: allocate more space fileName:" + file.getName() + ", extendTo:"+ extendSize); + } + + + long alloc = quotaMgr.allocateSpace(sess, tree, file, extendSize); + + if(file instanceof TempNetworkFile) + { + TempNetworkFile tnf = (TempNetworkFile)file; + FileState fstate = tnf.getFileState(); + if(fstate != null) + { + fstate.setAllocationSize(alloc); + } + } + } + } + + // Write to the file + + file.writeFile(buffer, size, bufferOffset, fileOffset); + + // Check if the file size was reduced by the write, may have been extended previously + + if ( quotaMgr != null) + { + + // Check if the file size reduced + + if ( file.getFileSize() < curSize) + { + + // Release space that was freed by the write + + quotaMgr.releaseSpace( sess, tree, file.getFileId(), file.getFullName(), curSize - file.getFileSize()); + } + } + + // Debug + + if (writeLogger.isDebugEnabled()) + { + writeLogger.debug("Wrote bytes to file: network file=" + file + " buffer size=" + buffer.length + " size=" + size + " file offset=" + fileOffset); + } + + return size; + } + + /** + * Get the node for the specified path + * + * @param tree TreeConnection + * @param path String + * @return NodeRef + * @exception FileNotFoundException + */ + private NodeRef getNodeForPath(TreeConnection tree, String path) + throws FileNotFoundException + { + ContentContext ctx = (ContentContext) tree.getContext(); + return getCifsHelper().getNodeRef(ctx.getRootNode(), path); + } + + /** + * Get the node for the specified path + * + * @param rootNode rootNode + * @param path String + * @return NodeRef + * @exception FileNotFoundException + */ + public NodeRef getNodeForPath(NodeRef rootNode, String path) + throws FileNotFoundException + { + if(logger.isDebugEnabled()) + { + logger.debug("getNodeRefForPath:" + path); + } + + return getCifsHelper().getNodeRef(rootNode, path); + } + + /** + * Convert a node into a share relative path + * + * @param tree TreeConnection + * @param nodeRef NodeRef + * @return String + * @exception FileNotFoundException + */ +// private String getPathForNode( TreeConnection tree, NodeRef nodeRef) +// throws FileNotFoundException +// { +// // Convert the target node to a path +// ContentContext ctx = (ContentContext) tree.getContext(); +// +// return getPathForNode(ctx.getRootNode(), nodeRef); +// +// } +// + /** + * Convert a node into a share relative path + * + * @param rootNode rootNode + * @param nodeRef NodeRef + * @return String + * @exception FileNotFoundException + */ + private String getPathForNode( NodeRef rootNode, NodeRef nodeRef) + throws FileNotFoundException + { + if(logger.isDebugEnabled()) + { + logger.debug("getPathForNode:" + nodeRef); + } + + List linkPaths = null; + + try + { + linkPaths = fileFolderService.getNamePath( rootNode, nodeRef); + } + catch ( org.alfresco.service.cmr.model.FileNotFoundException ex) + { + throw new FileNotFoundException(); + } + + // Build the share relative path to the node + + StringBuilder pathStr = new StringBuilder(); + + for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) + { + pathStr.append( FileName.DOS_SEPERATOR); + pathStr.append( fInfo.getName()); + } + + // Return the share relative path + + return pathStr.toString(); + } + + /** + * Return the lock manager used by this filesystem + * + * @param sess SrvSession + * @param tree TreeConnection + * @return LockManager + */ + public LockManager getLockManager(SrvSession sess, TreeConnection tree) + { + AlfrescoContext alfCtx = (AlfrescoContext) tree.getContext(); + return alfCtx.getLockManager(); + } + + /** + * Disk Size Interface implementation + */ + private interface DiskSizeInterfaceConsts + { + static final int DiskBlockSize = 512; // bytes per block + static final long DiskAllocationUnit = 32 * MemorySize.KILOBYTE; + static final long DiskBlocksPerUnit = DiskAllocationUnit / DiskBlockSize; + + // Disk size returned in the content store does not support free/total size + + static final long DiskSizeDefault = 1 * MemorySize.TERABYTE; + static final long DiskFreeDefault = DiskSizeDefault / 2; + } + + /** + * Get the disk information for this shared disk device. + * + * @param ctx DiskDeviceContext + * @param diskDev SrvDiskInfo + * @exception IOException + */ + public void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo diskDev) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("getDiskInformation"); + } + + // Set the block size and blocks per allocation unit + diskDev.setBlockSize( DiskSizeInterfaceConsts.DiskBlockSize); + diskDev.setBlocksPerAllocationUnit( DiskSizeInterfaceConsts.DiskBlocksPerUnit); + + // Get the free and total disk size in bytes from the content store + + long freeSpace = contentService.getStoreFreeSpace(); + long totalSpace= contentService.getStoreTotalSpace(); + + if ( totalSpace == -1L) { + + // Use a fixed value for the total space, content store does not support size information + + totalSpace = DiskSizeInterfaceConsts.DiskSizeDefault; + freeSpace = DiskSizeInterfaceConsts.DiskFreeDefault; + } + + // Convert the total/free space values to allocation units + + diskDev.setTotalUnits( totalSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); + diskDev.setFreeUnits( freeSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); + + if(logger.isDebugEnabled()) + { + logger.debug("getDiskInformation returning diskDev:" + diskDev); + } + } + + public void setCifsHelper(CifsHelper cifsHelper) + { + this.cifsHelper = cifsHelper; + } + + @Override + public void treeOpened(SrvSession sess, TreeConnection tree) + { + // Nothing to do + } + + @Override + public void treeClosed(SrvSession sess, TreeConnection tree) + { + // Nothing to do + } + +// Implementation of IOCtlInterface + + /** + * Process a filesystem I/O control request + * + * @param sess Server session + * @param tree Tree connection. + * @param ctrlCode I/O control code + * @param fid File id + * @param dataBuf I/O control specific input data + * @param isFSCtrl true if this is a filesystem control, or false for a device control + * @param filter if bit0 is set indicates that the control applies to the share root handle + * @return DataBuffer + * @exception IOControlNotImplementedException + * @exception SMBException + */ + public org.alfresco.jlan.util.DataBuffer processIOControl(SrvSession sess, TreeConnection tree, int ctrlCode, int fid, DataBuffer dataBuf, + boolean isFSCtrl, int filter) + throws IOControlNotImplementedException, SMBException + { + // Validate the file id + if(logger.isDebugEnabled()) + { + logger.debug("processIOControl ctrlCode: 0x" + Integer.toHexString(ctrlCode) + ", fid:" + fid); + } + + final ContentContext ctx = (ContentContext) tree.getContext(); + try + { + org.alfresco.jlan.util.DataBuffer buff = ioControlHandler.processIOControl(sess, tree, ctrlCode, fid, dataBuf, isFSCtrl, filter, this, ctx); + + return buff; + } + catch(SMBException smbException) + { + if(logger.isDebugEnabled()) + { + logger.debug("SMB Exception fid:" + fid, smbException); + } + throw smbException; + } + catch(IOControlNotImplementedException ioException) + { + if(logger.isDebugEnabled()) + { + logger.debug("IO Control Not Implemented Exception fid:" + fid, ioException); + } + throw ioException; + } + } + + + public void setCheckOutCheckInService(CheckOutCheckInService service) + { + this.checkOutCheckInService = service; + } + + /** + * @return the service to provide check-in and check-out data + */ + public final CheckOutCheckInService getCheckOutCheckInService() + { + return checkOutCheckInService; + } + + // Implementation of RepositoryDiskInterface + @Override + public void copyContent(NodeRef rootNode, String fromPath, String toPath) throws FileNotFoundException + { + if(logger.isDebugEnabled()) + { + logger.debug("copyContent from:" + fromPath + " to:" + toPath); + } + + NodeRef sourceNodeRef = getNodeForPath(rootNode, fromPath); + NodeRef targetNodeRef = getNodeForPath(rootNode, toPath); + + Serializable prop = nodeService.getProperty(sourceNodeRef, ContentModel.PROP_CONTENT); + if(prop != null) + { + if(prop instanceof ContentData) + { + ContentData data = (ContentData)prop; + if(data.getMimetype().equalsIgnoreCase(MimetypeMap.MIMETYPE_BINARY)) + { + if(logger.isDebugEnabled()) + { + logger.debug("mimetype is binary - guess mimetype has failed"); + } + Serializable targetProp = nodeService.getProperty(targetNodeRef, ContentModel.PROP_CONTENT); + + if(targetProp != null && targetProp instanceof ContentData) + { + ContentData targetData = (ContentData)targetProp; + logger.debug("copy the existing mimetype"); + prop = ContentData.setMimetype(data, targetData.getMimetype()); + } + } + } + + nodeService.setProperty(targetNodeRef, ContentModel.PROP_CONTENT, prop); + } + else + { + logger.debug("no content to save"); + // No content to set - need to remove old content + ContentWriter writer = contentService.getWriter(targetNodeRef, ContentModel.PROP_CONTENT, true); + writer.putContent(""); + } + + } + + @Override + public NetworkFile createFile(NodeRef rootNode, String path, long allocationSize, boolean isHidden) + throws IOException + { + + if (logger.isDebugEnabled()) + { + logger.debug("createFile :" + path); + } + + try + { + NodeRef dirNodeRef; + String folderName; + + String[] paths = FileName.splitPath(path); + + if (paths[0] != null && paths[0].length() > 1) + { + // lookup parent directory + dirNodeRef = getNodeForPath(rootNode, paths[0]); + folderName = paths[1]; + } + else + { + dirNodeRef = rootNode; + folderName = path; + } + + boolean soft = false; + + NodeRef existing = fileFolderService.searchSimple(dirNodeRef, folderName); + if (existing != null) + { + if(nodeService.hasAspect(existing, ContentModel.ASPECT_SOFT_DELETE)) + { + logger.debug("existing node has soft delete aspect"); + soft = true; + } + } + + NodeRef nodeRef = null; + + if(soft) + { + nodeRef = existing; + } + else + { + nodeRef = cifsHelper.createNode(dirNodeRef, folderName, ContentModel.TYPE_CONTENT); + nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null); + lockKeeper.addLock(nodeRef); + } + + if(isHidden) + { + // yes is hidden + if ( logger.isDebugEnabled()) + { + logger.debug("Set hidden aspect, nodeRef:" + nodeRef); + } + hiddenAspect.hideNodeExplicit(nodeRef); + } + + File file = TempFileProvider.createTempFile("cifs", ".bin"); + + TempNetworkFile netFile = new TempNetworkFile(file, path); + netFile.setChanged(true); + + Serializable created = nodeService.getProperty(nodeRef, ContentModel.PROP_CREATED); + if(created != null && created instanceof Date) + { + Date d = (Date)created; + if(logger.isDebugEnabled()) + { + logger.debug("replacing create date to date:" + d); + } + netFile.setCreationDate(d.getTime()); + netFile.setModifyDate(d.getTime()); + } + + // Always allow write access to a newly created file + netFile.setGrantedAccess(NetworkFile.READWRITE); + netFile.setAllowedAccess(NetworkFile.READWRITE); + + + + // Generate a file id for the file + + if ( netFile != null) + { + long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); + netFile.setFileId((int) (id & 0xFFFFFFFFL)); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Created file: path=" + path + " node=" + nodeRef + " network file=" + netFile); + } + + // Return the new network file + + return netFile; + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Create file - access denied, " + path); + } + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Unable to create file " + path); + } + catch (IOException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Create file - content I/O error, " + path); + } + + throw ex; + } + catch (ContentIOException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Create file - content I/O error, " + path); + } + // Convert to a filesystem disk full status + + throw new DiskFullException("Unable to create file " + path); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Create file error", ex); + } + + // Convert to a general I/O exception + + throw new IOException("Unable to create file " + path, ex); + } + } + + /** + * Open the file - Repo Specific implementation + */ + public NetworkFile openFile(SrvSession session, TreeConnection tree, NodeRef rootNode, String path, OpenFileMode mode, boolean truncate) throws IOException + { + ContentContext ctx = (ContentContext) tree.getContext(); + + if(logger.isDebugEnabled()) + { + logger.debug("openFile :" + path + ", mode:" + mode ); + } + try + { + String name = path; + + if(session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) + { + String[] paths = FileName.splitPath(name); + // lookup parent directory + NodeRef dirNodeRef = getNodeForPath(rootNode, paths[0]); + + // Check whether we are opening a pseudo file + if(ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) + { + PseudoFile pfile = ctx.getPseudoFileOverlay().getPseudoFile(dirNodeRef, paths[1]); + if(logger.isDebugEnabled()) + { + if (pfile != null) + { + logger.debug("Opened pseudo file :" + pfile); + } + else + { + logger.debug("Try to open deleted pseudo file :" + paths[1]); + } + } + if (pfile != null) + { + return pfile.getFile( path); + } + else + { + throw new FileNotFoundException("The pseudo file was deleted"); + } + } + } + + // not a psudo file + + NodeRef nodeRef = getNodeForPath(rootNode, path); + + boolean readOnly=false; + + // Check permissions on the file/folder + switch(mode) + { + case READ_ONLY: + // follow through + case ATTRIBUTES_ONLY: + if(permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED) + { + if(logger.isDebugEnabled()) + { + logger.debug("about to throw an no read access denied exception path:" +path); + } + throw new AccessDeniedException("No read access to " + path); + } + readOnly = true; + break; + + case READ_WRITE: + case WRITE_ONLY: + if(!m_transactionService.getAllowWrite()) + { + throw new AccessDeniedException("Repo is write only, No write access to " + path); + } + if(permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) + { + if(logger.isDebugEnabled()) + { + logger.debug("about to throw an no write access denied exception path:" + path); + } + + throw new AccessDeniedException("No write access to " + path); + } + lockService.checkForLock(nodeRef); + readOnly=false; + break; + case DELETE: + if(!m_transactionService.getAllowWrite()) + { + throw new AccessDeniedException("Repo is write only, No write access to " + path); + } + lockService.checkForLock(nodeRef); + + } + + // Check if the node is a link node + NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION); + NetworkFile netFile = null; + + if ( linkRef == null) + { + // A normal node, not a link node + + // TODO MER REWRITE HERE + FileInfo fileInfo = cifsHelper.getFileInformation(nodeRef, "", false, false); + + // TODO this is wasteful - the isDirectory is in the params. We should split off an openDirectory method. + if(fileInfo.isDirectory()) + { + logger.debug("open file - is a directory!"); + netFile = new AlfrescoFolder(path, fileInfo, readOnly); + } + else + { + // A normal file + switch (mode) + { + case READ_ONLY: + + logger.debug("open file for read only"); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); + netFile.setGrantedAccess( NetworkFile.READONLY); + break; + + case READ_WRITE: + { + logger.debug("open file for read write"); + File file = TempFileProvider.createTempFile("cifs", ".bin"); + + lockKeeper.addLock(nodeRef); + + if(!truncate) + { + // Need to open a temp file with a copy of the content. + ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + if(reader != null) + { + reader.getContent(file); + } + } + + netFile = new TempNetworkFile(file, name); + netFile.setCreationDate(fileInfo.getCreationDateTime()); + netFile.setModifyDate(fileInfo.getModifyDateTime()); + + netFile.setGrantedAccess( NetworkFile.READWRITE); + + if(truncate) + { + netFile.truncateFile(0); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Created file: path=" + name + " node=" + nodeRef + " network file=" + netFile); + } + + } + break; + + case ATTRIBUTES_ONLY: + logger.debug("open file for attributes only"); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, true, session); + netFile.setGrantedAccess( NetworkFile.READONLY); + break; + + case DELETE: + //TODO Not sure about this one. + logger.debug("open file for delete"); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); + netFile.setGrantedAccess( NetworkFile.READONLY); + break; + + case WRITE_ONLY: + { + // consider this as open read/write/truncate) + logger.debug("open file write only"); + File file = TempFileProvider.createTempFile("cifs", ".bin"); + + netFile = new TempNetworkFile(file, name); + + // Needs to be READWRITE for JavaNetworkFile - there's no such thing as WRITEONLY! + netFile.setGrantedAccess( NetworkFile.READWRITE); + + if (logger.isDebugEnabled()) + { + logger.debug("Created temporary file: path=" + name + " node=" + nodeRef + " network file=" + netFile); + } + } + } + } // end of a normal file + } + else + { + // This is a link node + + // TODO - This server name stuff should be replaced In particular the + // See PseudoFileOverlayImp + // Get the CIFS server name + + String srvName = null; + SMBServer cifsServer = (SMBServer) session.getServer().getConfiguration().findServer( "CIFS"); + + if(session instanceof SMBSrvSession) + { + SMBSrvSession smbSess = (SMBSrvSession)session; + srvName = smbSess.getShareHostName(); + } + else if ( cifsServer != null) + { + // Use the CIFS server name in the URL + + srvName = cifsServer.getServerName(); + } + else + { + // Use the local server name in the URL + srvName = InetAddress.getLocalHost().getHostName(); + } + + // Convert the target node to a path, convert to URL format + + String pathl = getPathForNode( rootNode, linkRef); + path = pathl.replace( FileName.DOS_SEPERATOR, '/'); + + String lnkForWinPath = convertStringToUnicode(path); + + // Build the URL file data + + StringBuilder urlStr = new StringBuilder(); + + urlStr.append("[InternetShortcut]\r\n"); + urlStr.append("URL=file://"); + urlStr.append( srvName); + urlStr.append("/"); + urlStr.append( tree.getSharedDevice().getName()); + urlStr.append( lnkForWinPath); + urlStr.append("\r\n"); + + // Create the in memory pseudo file for the URL link + + byte[] urlData = urlStr.toString().getBytes(); + + // Get the file information for the link node + + FileInfo fInfo = getCifsHelper().getFileInformation( nodeRef, false, isLockedFilesAsOffline); + + // Set the file size to the actual data length + + fInfo.setFileSize( urlData.length); + + // Create the network file using the in-memory file data + + netFile = new LinkMemoryNetworkFile( fInfo.getFileName(), urlData, fInfo, nodeRef); + netFile.setFullName( pathl); + } + + // Generate a file id for the file + + if ( netFile != null) + { + long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); + netFile.setFileId(( int) ( id & 0xFFFFFFFFL)); + + // Indicate the file is open + + netFile.setClosed( false); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Opened network file: path=" + path + " network file=" + netFile); + } + + // Return the network file + + return netFile; + } + catch (NodeLockedException nle) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Open file - node is locked, " + path); + } + throw new AccessDeniedException("File is locked, no write access to " + path); + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Open file - access denied, " + path); + } + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Open file " + path); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Open file error", ex); + } + // Convert to a general I/O exception + + throw new IOException("Open file " + path, ex); + } + } + + private String convertStringToUnicode(String str) + { + StringBuffer ostr = new StringBuffer(); + for (int i = 0; i < str.length(); i++) + { + char ch = str.charAt(i); + // Does the char need to be converted to unicode? + if ((ch >= 0x0020) && (ch <= 0x007e)) + { + // No + ostr.append(ch); + } + else if (ch > 0xFF) + { + // No + ostr.append(ch); + } + // Yes. + else + { + ostr.append("%"); + String hex = Integer.toHexString(str.charAt(i) & 0xFFFF); + hex.length(); + // Prepend zeros because unicode requires 2 digits + for (int j = 0; j < 2 - hex.length(); j++) + + ostr.append("0"); + ostr.append(hex.toLowerCase()); + } + } + return (new String(ostr)); + } + + /** + * Close the file. + * + * @exception java.io.IOException If an error occurs. + * @return node ref of deleted file + */ + public NodeRef closeFile(TreeConnection tree, NodeRef rootNode, String path, NetworkFile file) throws IOException + { + if ( logger.isDebugEnabled()) + { + logger.debug("Close file:" + path + ", readOnly=" + file.isReadOnly() ); + } + + if( file instanceof PseudoNetworkFile || file instanceof MemoryNetworkFile) + { + file.close(); + + if(file.hasDeleteOnClose()) + { + if(logger.isDebugEnabled()) + { + logger.debug("delete on close a pseudo file"); + } + final ContentContext ctx = (ContentContext) tree.getContext(); + + String[] paths = FileName.splitPath(path); + + if (paths[0] != null && paths[0].length() > 1) + { + // lookup parent directory + NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); + ctx.getPseudoFileOverlay().delete(dirNodeRef, paths[1]); + } + } + return null; + } + + /** + * Delete on close attribute - node needs to be deleted. + */ + if(file.hasDeleteOnClose()) + { + NodeRef target = null; + + if(logger.isDebugEnabled()) + { + logger.debug("closeFile has delete on close set path:" + path); + } + try + { + target = getCifsHelper().getNodeRef(rootNode, path); + if(target!=null) + { + nodeService.deleteNode(target); + } + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Delete file from close file- access denied", ex); + } + // Convert to a filesystem access denied status + throw new AccessDeniedException("Unable to delete " + path); + } + + // Still need to close the open file handle. + file.close(); + + if (logger.isDebugEnabled()) + { + logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose()); + } + + return target; + } + + // Check for a temp file - which will be a new file or a read/write file + if ( file instanceof TempNetworkFile) + { + if(logger.isDebugEnabled()) + { + logger.debug("Got a temp network file to close path:" + path); + } + + // Some content was written to the temp file. + TempNetworkFile tempFile =(TempNetworkFile)file; + + NodeRef target = getCifsHelper().getNodeRef(rootNode, tempFile.getFullName()); + + lockKeeper.removeLock(target); + + if(nodeService.hasAspect(target, ContentModel.ASPECT_NO_CONTENT)) + { + if(logger.isDebugEnabled()) + { + logger.debug("removed no content aspect"); + } + nodeService.removeAspect(target, ContentModel.ASPECT_NO_CONTENT); + } + + if(tempFile.isChanged()) + { + tempFile.flushFile(); + tempFile.close(); + + /* + * Need to work out whether content has changed. Some odd situations do not change content. + */ + boolean contentChanged = true; + + ContentReader existingContent = contentService.getReader(target, ContentModel.PROP_CONTENT); + if(existingContent != null) + { + existingContent.getSize(); + existingContent.getMimetype(); + contentChanged = isContentChanged(existingContent, tempFile); + + /* + * MNT-248 fix + * No need to create a version of a zero byte file + */ + if (file.getFileSize() > 0 && existingContent.getSize() == 0 && nodeService.hasAspect(target, ContentModel.ASPECT_VERSIONABLE)) + { + getPolicyFilter().disableBehaviour(target, ContentModel.ASPECT_VERSIONABLE); + } + } + + if(contentChanged) + { + logger.debug("content has changed, need to create a new content item"); + + /** + * Take over the behaviour of the auditable aspect + */ + getPolicyFilter().disableBehaviour(target, ContentModel.ASPECT_AUDITABLE); + nodeService.setProperty(target, ContentModel.PROP_MODIFIER, authService.getCurrentUserName()); + if(tempFile.isModificationDateSetDirectly()) + { + logger.debug("modification date set directly"); + nodeService.setProperty(target, ContentModel.PROP_MODIFIED, new Date(tempFile.getModifyDate())); + } + else + { + logger.debug("modification date not set directly"); + nodeService.setProperty(target, ContentModel.PROP_MODIFIED, new Date()); + } + + /** + * Take a guess at the mimetype + */ + String mimetype = mimetypeService.guessMimetype(tempFile.getFullName(), new FileContentReader(tempFile.getFile())); + logger.debug("guesssed mimetype:" + mimetype); + + /** + * mime type guessing may have failed in which case we should assume the mimetype has not changed. + */ + if(mimetype.equalsIgnoreCase(MimetypeMap.MIMETYPE_BINARY)) + { + // mimetype guessing may have failed + if(existingContent != null) + { + // copy the mimetype from the existing content. + mimetype = existingContent.getMimetype(); + if(logger.isDebugEnabled()) + { + logger.debug("using mimetype of existing content :" + mimetype); + } + } + } + + String encoding; + // Take a guess at the locale + InputStream is = new BufferedInputStream(new FileInputStream(tempFile.getFile())); + try + { + ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder(); + Charset charset = charsetFinder.getCharset(is, mimetype); + encoding = charset.name(); + } + finally + { + if(is != null) + { + try + { + is.close(); + } + catch (IOException e) + { + // Ignore + } + } + } + ContentWriter writer = contentService.getWriter(target, ContentModel.PROP_CONTENT, true); + writer.setMimetype(mimetype); + writer.setEncoding(encoding); + writer.putContent(tempFile.getFile()); + } // if content changed + } + } + + try + { + // Defer to the network file to close the stream and remove the content + + file.close(); + + // DEBUG + + if (logger.isDebugEnabled()) + { + logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose() + ", write count" + file.getWriteCount()); + + if ( file.hasDeleteOnClose() == false && file instanceof ContentNetworkFile) + { + ContentNetworkFile cFile = (ContentNetworkFile) file; + logger.debug(" File " + file.getFullName() + ", version=" + nodeService.getProperty( cFile.getNodeRef(), ContentModel.PROP_VERSION_LABEL)); + } + } + + return null; + } + catch (IOException e) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Exception in closeFile - path:" + path, e); + } + throw new IOException("Unable to closeFile :" + path + e.toString(), e); + } + catch (Error e) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Exception in closeFile - path:" + path, e); + } + + throw e; + } + } + + /** + * + * @param session + * @param tree + * @param file + */ + public void reduceQuota(SrvSession session, TreeConnection tree, NetworkFile file) + { + if(file.hasDeleteOnClose()) + { + final ContentContext ctx = (ContentContext) tree.getContext(); + + if(logger.isDebugEnabled()) + { + logger.debug("closeFile has delete on close set"); + } + + if(file instanceof TempNetworkFile) + { + TempNetworkFile tnf = (TempNetworkFile)file; + final QuotaManager quotaMgr = ctx.getQuotaManager(); + if (quotaMgr != null) + { + try + { + quotaMgr.releaseSpace(session, tree, file.getFileId(), file.getName(), tnf.getFileSizeInt()); + } + catch (IOException e) + { + logger.error(e); + } + } + } + } + } + + public void deleteEmptyFile(NodeRef rootNode, String path) + { + try + { + NodeRef target = getCifsHelper().getNodeRef(rootNode, path); + if(target!=null) + { + if (nodeService.hasAspect(target, ContentModel.ASPECT_NO_CONTENT)) + { + nodeService.deleteNode(target); + } + } + } + catch(IOException ne) + { + // Do nothing + if ( logger.isDebugEnabled()) + { + logger.debug("Unable to delete empty file:" + path, ne); + } + + } + } + + @Override + public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) + { + AlfrescoContext alfCtx = (AlfrescoContext) tree.getContext(); + return alfCtx.getOpLockManager(); + } + + @Override + public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) + { + if(getOpLockManager(sess, tree) != null) + { + return true; + } + return false; + } + + /** + * Compare the content for significant changes. For example Project and Excel play with headers, + * which should not result in new versions being created. + * @param existingContent + * @param newFile + * @return true the content has changed, false the content has not changed significantly. + */ + private boolean isContentChanged(ContentReader existingContent, TempNetworkFile newFile) + { + return !contentComparator.isContentEqual(existingContent, newFile.getFile()); + } + + public void setContentComparator(ContentComparator contentComparator) + { + this.contentComparator = contentComparator; + } + + public ContentComparator getContentComparator() + { + return contentComparator; + } + + @Override + public NetworkFile restoreFile( + SrvSession sess, + TreeConnection tree, + NodeRef rootNode, + String path, + long allocationSize, + NodeRef originalNodeRef) throws IOException + { + // First attempt to restore the node + + if(logger.isDebugEnabled()) + { + logger.debug("restore node:" + originalNodeRef + ", path:" + path); + } + + NodeRef archivedNodeRef = getNodeArchiveService().getArchivedNode(originalNodeRef); + + if(nodeService.exists(archivedNodeRef)) + { + NodeRef restoredNodeRef = nodeService.restoreNode(archivedNodeRef, null, null, null); + if (logger.isDebugEnabled()) + { + logger.debug("node has been restored nodeRef," + restoredNodeRef + ", path " + path); + } + + return openFile(sess, tree, rootNode, path, OpenFileMode.READ_WRITE, true); + } + else + { + return createFile(rootNode, path, allocationSize, false); + } + } + + public void setNodeArchiveService(NodeArchiveService nodeArchiveService) + { + this.nodeArchiveService = nodeArchiveService; + } + + public NodeArchiveService getNodeArchiveService() + { + return nodeArchiveService; + } + + private SimpleCache deletePseudoFileCache; + + public void setDeletePseudoFileCache(SimpleCache deletePseudoFileCache) + { + this.deletePseudoFileCache = deletePseudoFileCache; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java b/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java index 408a8cf74f..7469d75a3c 100644 --- a/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java +++ b/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java @@ -1,12 +1,12 @@ - -package org.alfresco.filesys.repo; - -/** - * - * @author mrogers - * - */ -public interface InFlightCorrectable -{ - public void setInFlightCorrector(InFlightCorrector correctable); -} + +package org.alfresco.filesys.repo; + +/** + * + * @author mrogers + * + */ +public interface InFlightCorrectable +{ + public void setInFlightCorrector(InFlightCorrector correctable); +} diff --git a/source/java/org/alfresco/filesys/repo/InFlightCorrector.java b/source/java/org/alfresco/filesys/repo/InFlightCorrector.java index aa0688fc69..72fa761755 100644 --- a/source/java/org/alfresco/filesys/repo/InFlightCorrector.java +++ b/source/java/org/alfresco/filesys/repo/InFlightCorrector.java @@ -1,29 +1,29 @@ -package org.alfresco.filesys.repo; - -import java.util.Date; - -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.jlan.server.filesys.cache.FileState; -import org.alfresco.jlan.server.filesys.cache.FileStateCache; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * The in flight corrector corrects search results that have not yet been committed to the - * repository. - * - * It substitutes the "in flight" valuses from the state cache in place of the values committed to - * the repo - * - * @author mrogers - */ -public interface InFlightCorrector -{ - /** - * Correct thr results with in flight details. - * @param info - * @param folderPath - */ - public void correct(FileInfo info, String folderPath); -} +package org.alfresco.filesys.repo; + +import java.util.Date; + +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.FileStateCache; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The in flight corrector corrects search results that have not yet been committed to the + * repository. + * + * It substitutes the "in flight" valuses from the state cache in place of the values committed to + * the repo + * + * @author mrogers + */ +public interface InFlightCorrector +{ + /** + * Correct thr results with in flight details. + * @param info + * @param folderPath + */ + public void correct(FileInfo info, String folderPath); +} diff --git a/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java b/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java index 6e9f497ad0..2f6a052fad 100644 --- a/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java +++ b/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java @@ -1,92 +1,92 @@ -package org.alfresco.filesys.repo; - -import java.util.Date; - -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.jlan.server.filesys.cache.FileState; -import org.alfresco.jlan.server.filesys.cache.FileStateCache; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * The in flight corrector corrects search results that have not yet been committed to the - * repository. - * - * It substitutes the "in flight" valuses from the state cache in place of the values committed to - * the repo - * - * @author mrogers - */ -public class InFlightCorrectorImpl implements InFlightCorrector -{ - TreeConnection tree; - - private static final Log logger = LogFactory.getLog(InFlightCorrectorImpl.class); - - public InFlightCorrectorImpl(TreeConnection tree) - { - this.tree = tree; - } - public void correct(FileInfo info, String folderPath) - { - ContentContext tctx = (ContentContext) tree.getContext(); - - String path = folderPath + info.getFileName(); - - if(tctx.hasStateCache()) - { - FileStateCache cache = tctx.getStateCache(); - FileState fstate = cache.findFileState( path, true); - - if(fstate != null) - { - logger.debug("correct " + path); - /* - * What about stale file state values here? - */ - if(fstate.hasFileSize()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace file size " + info.getSize() + " with " + fstate.getFileSize()); - } - info.setFileSize(fstate.getFileSize()); - } - if ( fstate.hasAccessDateTime()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace access date " + new Date(info.getAccessDateTime()) + " with " + new Date(fstate.getAccessDateTime())); - } - info.setAccessDateTime(fstate.getAccessDateTime()); - } - if ( fstate.hasChangeDateTime()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace change date " + new Date(info.getChangeDateTime()) + " with " + new Date(fstate.getChangeDateTime())); - } - info.setChangeDateTime(fstate.getChangeDateTime()); - } - if ( fstate.hasModifyDateTime()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace modified date " + new Date(info.getModifyDateTime()) + " with " + new Date(fstate.getModifyDateTime())); - } - info.setModifyDateTime(fstate.getModifyDateTime()); - } - if ( fstate.hasAllocationSize()) - { - if(logger.isDebugEnabled()) - { - logger.debug("replace allocation size" + info.getAllocationSize() + " with " + fstate.getAllocationSize()); - } - info.setAllocationSize(fstate.getAllocationSize()); - } - } - } - } - -} +package org.alfresco.filesys.repo; + +import java.util.Date; + +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.FileStateCache; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The in flight corrector corrects search results that have not yet been committed to the + * repository. + * + * It substitutes the "in flight" valuses from the state cache in place of the values committed to + * the repo + * + * @author mrogers + */ +public class InFlightCorrectorImpl implements InFlightCorrector +{ + TreeConnection tree; + + private static final Log logger = LogFactory.getLog(InFlightCorrectorImpl.class); + + public InFlightCorrectorImpl(TreeConnection tree) + { + this.tree = tree; + } + public void correct(FileInfo info, String folderPath) + { + ContentContext tctx = (ContentContext) tree.getContext(); + + String path = folderPath + info.getFileName(); + + if(tctx.hasStateCache()) + { + FileStateCache cache = tctx.getStateCache(); + FileState fstate = cache.findFileState( path, true); + + if(fstate != null) + { + logger.debug("correct " + path); + /* + * What about stale file state values here? + */ + if(fstate.hasFileSize()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace file size " + info.getSize() + " with " + fstate.getFileSize()); + } + info.setFileSize(fstate.getFileSize()); + } + if ( fstate.hasAccessDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace access date " + new Date(info.getAccessDateTime()) + " with " + new Date(fstate.getAccessDateTime())); + } + info.setAccessDateTime(fstate.getAccessDateTime()); + } + if ( fstate.hasChangeDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace change date " + new Date(info.getChangeDateTime()) + " with " + new Date(fstate.getChangeDateTime())); + } + info.setChangeDateTime(fstate.getChangeDateTime()); + } + if ( fstate.hasModifyDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace modified date " + new Date(info.getModifyDateTime()) + " with " + new Date(fstate.getModifyDateTime())); + } + info.setModifyDateTime(fstate.getModifyDateTime()); + } + if ( fstate.hasAllocationSize()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace allocation size" + info.getAllocationSize() + " with " + fstate.getAllocationSize()); + } + info.setAllocationSize(fstate.getAllocationSize()); + } + } + } + } + +} diff --git a/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java b/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java index d599bdef46..3bf3b0ec5b 100644 --- a/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java +++ b/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java @@ -1,727 +1,727 @@ -package org.alfresco.filesys.repo; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Date; - -import org.alfresco.filesys.alfresco.ExtendedDiskInterface; -import org.alfresco.filesys.alfresco.NetworkFileLegacyReferenceCount; -import org.alfresco.filesys.config.ServerConfigurationBean; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.core.DeviceContext; -import org.alfresco.jlan.server.core.DeviceContextException; -import org.alfresco.jlan.server.filesys.FileAccessToken; -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.FileOpenParams; -import org.alfresco.jlan.server.filesys.FileStatus; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.jlan.server.filesys.SearchContext; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.jlan.server.filesys.cache.FileState; -import org.alfresco.jlan.server.filesys.cache.FileStateCache; -import org.alfresco.jlan.server.filesys.cache.NetworkFileStateInterface; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; -import org.alfresco.jlan.server.locking.FileLockingInterface; -import org.alfresco.jlan.server.locking.LockManager; -import org.alfresco.jlan.server.locking.OpLockInterface; -import org.alfresco.jlan.server.locking.OpLockManager; -import org.alfresco.jlan.smb.SharingMode; -import org.alfresco.model.ContentModel; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.config.ConfigElement; - -/** - * The Legacy file state driver is used to update JLAN's file state cache. - *

- * This class decorates an ExtendedDiskInterface with odds and ends to keep JLan happy. - *

- * In particular this implementation cannot contain any code that requires access to the - * alfresco repository. - * - */ -public class LegacyFileStateDriver implements ExtendedDiskInterface -{ - private ExtendedDiskInterface diskInterface; - - private OpLockInterface opLockInterface; - - private FileLockingInterface fileLockingInterface; - - public void init() - { - PropertyCheck.mandatory(this, "diskInterface", diskInterface); - PropertyCheck.mandatory(this, "fileLockingInterface", fileLockingInterface); - PropertyCheck.mandatory(this, "opLockInterface", getOpLockInterface()); - } - - private static final Log logger = LogFactory.getLog(LegacyFileStateDriver.class); - - @Override - public void treeOpened(SrvSession sess, TreeConnection tree) - { - diskInterface.treeOpened(sess, tree); - - } - - @Override - public void treeClosed(SrvSession sess, TreeConnection tree) - { - diskInterface.treeClosed(sess, tree); - } - - @Override - public NetworkFile createFile(SrvSession sess, TreeConnection tree, - FileOpenParams params) throws IOException - { - ContentContext tctx = (ContentContext) tree.getContext(); - - FileStateCache cache = null; - FileState fstate = null; - - FileAccessToken token = null; - - if(tctx.hasStateCache()) - { - cache = tctx.getStateCache(); - fstate = tctx.getStateCache().findFileState( params.getPath(), true); - token = cache.grantFileAccess(params, fstate, FileStatus.NotExist); - if(logger.isDebugEnabled()) - { - logger.debug("create file created lock token:" + token); - } - } - - try - { - NetworkFile newFile = diskInterface.createFile(sess, tree, params); - - int openCount = 1; - - if(newFile instanceof NetworkFileLegacyReferenceCount) - { - NetworkFileLegacyReferenceCount counter = (NetworkFileLegacyReferenceCount)newFile; - openCount = counter.incrementLegacyOpenCount(); - } - // This is the create so we store the first access token always - newFile.setAccessToken(token); - - if(tctx.hasStateCache()) - { - fstate.setProcessId(params.getProcessId()); - fstate.setSharedAccess( params.getSharedAccess()); - - // Indicate that the file is open - fstate.setFileStatus(newFile.isDirectory()? FileStatus.DirectoryExists : FileStatus.FileExists); - - long allocationSize = params.getAllocationSize(); - if(allocationSize > 0) - { - fstate.setAllocationSize(allocationSize); - fstate.setFileSize(allocationSize); - } - - if (newFile instanceof NodeRefNetworkFile) - { - NodeRefNetworkFile x = (NodeRefNetworkFile)newFile; - x.setFileState(fstate); - } - - if (newFile instanceof TempNetworkFile) - { - TempNetworkFile x = (TempNetworkFile)newFile; - x.setFileState(fstate); - } - } - - if (newFile instanceof NodeRefNetworkFile) - { - NodeRefNetworkFile x = (NodeRefNetworkFile)newFile; - x.setProcessId( params.getProcessId()); - x.setAccessToken(token); - } - - if (newFile instanceof TempNetworkFile) - { - TempNetworkFile x = (TempNetworkFile)newFile; - x.setAccessToken(token); - } - - return newFile; - - } - catch(IOException ie) - { - if(logger.isDebugEnabled()) - { - logger.debug("create file exception caught", ie); - } - if(tctx.hasStateCache() && token != null) - { - if(cache != null && fstate != null && token != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("create file release lock token:" + token); - } - cache.releaseFileAccess(fstate, token); - } - } - throw ie; - } - catch (RuntimeException re) - { - // we could be out of memory or a NPE or some other unforseen situation. JLAN will complain loudly ... as it should. - if(logger.isDebugEnabled()) - { - logger.debug("create file exception caught", re); - } - if(tctx.hasStateCache() && token != null) - { - if(cache != null && fstate != null && token != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("create file release lock token:" + token); - } - cache.releaseFileAccess(fstate, token); - } - } - throw re; - } - } - - @Override - public NetworkFile openFile(SrvSession sess, TreeConnection tree, - FileOpenParams params) throws IOException - { - ContentContext tctx = (ContentContext) tree.getContext(); - String path = params.getPath(); - - boolean rollbackOpen = false; - boolean rollbackToken = false; - boolean rollbackCount = false; - boolean rollbackSetToken = false; - - FileAccessToken token = null; - - FileStateCache cache = null; - FileState fstate = null; - NetworkFile openFile = null; - - if(tctx.hasStateCache()) - { - cache = tctx.getStateCache(); - fstate = tctx.getStateCache().findFileState( params.getPath(), true); - - if(!params.isDirectory()) - { - try - { - token = cache.grantFileAccess(params, fstate, FileStatus.Unknown); - } - catch (IOException e) - { - if(logger.isDebugEnabled()) - { - logger.debug("UNABLE to grant file access for path:" + path + ", params" + params, e); - } - throw e; - } - - rollbackToken = true; - if(logger.isDebugEnabled()) - { - logger.debug("open file created lock token:" + token + ", for path:" + path); - } - } - } - - try - { - openFile = diskInterface.openFile(sess, tree, params); - rollbackOpen = true; - - if(openFile instanceof NetworkFileLegacyReferenceCount) - { - NetworkFileLegacyReferenceCount counter = (NetworkFileLegacyReferenceCount)openFile; - int legacyOpenCount = counter.incrementLegacyOpenCount(); - if(logger.isDebugEnabled()) - { - logger.debug("openFile: legacyOpenCount: " + legacyOpenCount); - } - - rollbackCount = true; - } - else - { - logger.debug("openFile does not implement NetworkFileLegacyReferenceCount"); - } - - if( openFile.hasAccessToken()) - { - // already has an access token, release the second token - if(cache != null && fstate != null && token != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("already has access token, release lock token:" + token); - } - cache.releaseFileAccess(fstate, token); - } - } - else - { - if(logger.isDebugEnabled()) - { - logger.debug("store access token on open network file object token:" + token); - } - - // first access token - openFile.setAccessToken(token); - rollbackSetToken = true; - } - - if(tctx.hasStateCache()) - { - fstate = tctx.getStateCache().findFileState( path, true); - fstate.setProcessId(params.getProcessId()); - fstate.setSharedAccess( params.getSharedAccess()); - - // Access date time is read/write time not open time - // fstate.updateAccessDateTime(); - - fstate.setFileSize(openFile.getFileSize()); - fstate.updateChangeDateTime(openFile.getModifyDate()); - fstate.updateModifyDateTime(openFile.getModifyDate()); - } - - if (openFile instanceof ContentNetworkFile) - { - ContentNetworkFile x = (ContentNetworkFile)openFile; - x.setProcessId( params.getProcessId()); - - if(fstate != null) - { - x.setFileState(fstate); - fstate.setFileStatus(FileStatus.FileExists); - } - } - else if (openFile instanceof TempNetworkFile) - { - TempNetworkFile x = (TempNetworkFile)openFile; - if(fstate != null) - { - x.setFileState(fstate); - fstate.setFileStatus(FileStatus.FileExists); - } - } - else if (openFile instanceof AlfrescoFolder) - { - AlfrescoFolder x = (AlfrescoFolder)openFile; - if(fstate != null) - { - x.setFileState(fstate); - fstate.setFileStatus(FileStatus.DirectoryExists); - } - } - else if (openFile instanceof NetworkFile) - { - NetworkFile x = (NetworkFile)openFile; - if(fstate != null) - { - // NetworkFile does not have setFileState - //x.setFileState(fstate); - fstate.setFileStatus(FileStatus.FileExists); - } - } - - rollbackToken = false; - rollbackCount = false; - rollbackSetToken = false; - rollbackOpen = false; - - if(logger.isDebugEnabled()) - { - logger.debug("successfully opened file:" + openFile); - } - - return openFile; - } - finally - { - if(rollbackToken) - { - if(logger.isDebugEnabled()) - { - logger.debug("rollback token:" + token); - } - if(cache != null && fstate != null && token != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("open file release lock token:" + token); - } - cache.releaseFileAccess(fstate, token); - } - } - if(rollbackCount) - { - if(logger.isDebugEnabled()) - { - logger.debug("rollback legacy open count:" + token); - } - if(openFile instanceof NetworkFileLegacyReferenceCount) - { - NetworkFileLegacyReferenceCount counter = (NetworkFileLegacyReferenceCount)openFile; - counter.decrementLagacyOpenCount(); - } - } - if(rollbackSetToken) - { - if(logger.isDebugEnabled()) - { - logger.debug("rollback set access token:" + token); - } - openFile.setAccessToken(null); - } - if(rollbackOpen) - { - if(logger.isDebugEnabled()) - { - logger.debug("rollback open:" + token); - } - diskInterface.closeFile(sess, tree, openFile); - } - } - } - - @Override - public void closeFile(SrvSession sess, TreeConnection tree, - NetworkFile file) throws IOException - { - ContentContext tctx = (ContentContext) tree.getContext(); - FileStateCache cache = null; - FileState fstate = null; - - if(logger.isDebugEnabled()) - { - logger.debug("closeFile:" + file.getFullName() + ", accessToken:" + file.getAccessToken()); - } - - int legacyOpenCount = 0; - - if(file instanceof NetworkFileLegacyReferenceCount) - { - NetworkFileLegacyReferenceCount counter = (NetworkFileLegacyReferenceCount)file; - legacyOpenCount = counter.decrementLagacyOpenCount(); - if(logger.isDebugEnabled()) - { - logger.debug("closeFile: legacyOpenCount=" + legacyOpenCount); - } - } - else - { - logger.debug("file to close does not implement NetworkFileLegacyReferenceCount"); - } - - try - { - if ( file.hasOpLock()) - { - if ( logger.isDebugEnabled()) - { - logger.debug("File Has OpLock - release oplock for closed file, file=" + file.getFullName()); - } - // Release the oplock - - OpLockManager oplockMgr = opLockInterface.getOpLockManager(sess, tree); - - oplockMgr.releaseOpLock( file.getOpLock().getPath()); - - // DEBUG - - if ( logger.isDebugEnabled()) - { - logger.debug("Released oplock for closed file, file=" + file.getFullName()); - } - } - - - // Release any locks on the file owned by this session - - if ( file.hasLocks()) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Release all locks, file=" + file.getFullName()); - } - - LockManager lockMgr = fileLockingInterface.getLockManager(sess, tree); - - if(lockMgr != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("Releasing locks for closed file, file=" + file.getFullName() + ", locks=" + file.numberOfLocks()); - } - // Release all locks on the file owned by this session - - lockMgr.releaseLocksForFile(sess, tree, file); - } - } - - diskInterface.closeFile(sess, tree, file); - - logger.debug("file closed"); - - } - finally - { - if(tctx.hasStateCache()) - { - cache = tctx.getStateCache(); - fstate = cache.findFileState( file.getFullName(), true); - - if(legacyOpenCount == 0 || file.isForce()) - { - if(cache != null && fstate != null && file.getAccessToken() != null) - { - FileAccessToken token = file.getAccessToken(); - if(logger.isDebugEnabled() && token != null) - { - logger.debug("close file, legacy count == 0 release access token:" + token); - } - cache.releaseFileAccess(fstate, token); - file.setAccessToken( null); - } - } - - if(fstate.getOpenCount() == 0 ) - { - logger.debug("fstate OpenCount == 0, reset in-flight state"); - fstate.setAllocationSize(-1); - fstate.setFileSize(-1); - fstate.updateChangeDateTime(0); - fstate.updateModifyDateTime(0); - } - } - } - } - - @Override - public void registerContext(DeviceContext ctx) throws DeviceContextException - { - diskInterface.registerContext(ctx); - } - - public void setDiskInterface(ExtendedDiskInterface diskInterface) - { - this.diskInterface = diskInterface; - } - - public ExtendedDiskInterface getDiskInterface() - { - return diskInterface; - } - - @Override - public void createDirectory(SrvSession sess, TreeConnection tree, - FileOpenParams params) throws IOException - { - diskInterface.createDirectory(sess, tree, params); - } - - @Override - public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) - throws IOException - { - diskInterface.deleteDirectory(sess, tree, dir); - } - - @Override - public void deleteFile(SrvSession sess, TreeConnection tree, String name) - throws IOException - { - ContentContext tctx = (ContentContext) tree.getContext(); - - diskInterface.deleteFile(sess, tree, name); - - if(tctx.hasStateCache()) - { - FileStateCache cache = tctx.getStateCache(); - FileState fstate = cache.findFileState( name, false); - - if(fstate != null) - { - fstate.setFileStatus(FileStatus.NotExist); - fstate.setOpenCount(0); - } - } - } - - @Override - public int fileExists(SrvSession sess, TreeConnection tree, String name) - { - return diskInterface.fileExists(sess, tree, name); - } - - @Override - public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) - throws IOException - { - diskInterface.flushFile(sess, tree, file); - } - - @Override - public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, - String name) throws IOException - { - return diskInterface.getFileInformation(sess, tree, name); - } - - @Override - public boolean isReadOnly(SrvSession sess, DeviceContext ctx) - throws IOException - { - return diskInterface.isReadOnly(sess, ctx); - } - - @Override - public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, - byte[] buf, int bufPos, int siz, long filePos) throws IOException - { - return diskInterface.readFile(sess, tree, file, buf, bufPos, siz, filePos); - } - - @Override - public void renameFile(SrvSession sess, TreeConnection tree, - String oldName, String newName) throws IOException - { - ContentContext tctx = (ContentContext) tree.getContext(); - - diskInterface.renameFile(sess, tree, oldName, newName); - - if(tctx.hasStateCache()) - { - FileStateCache cache = tctx.getStateCache(); - FileState fstate = cache.findFileState( oldName, false); - - if(fstate != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("rename file state from:" + oldName + ", to:" + newName); - } - cache.renameFileState(newName, fstate, fstate.isDirectory()); - } - } - - } - - @Override - public long seekFile(SrvSession sess, TreeConnection tree, - NetworkFile file, long pos, int typ) throws IOException - { - return diskInterface.seekFile(sess, tree, file, pos, typ); - } - - @Override - public void setFileInformation(SrvSession sess, TreeConnection tree, - String name, FileInfo info) throws IOException - { - - diskInterface.setFileInformation(sess, tree, name, info); - - ContentContext tctx = (ContentContext) tree.getContext(); - - if(tctx.hasStateCache()) - { - FileStateCache cache = tctx.getStateCache(); - FileState fstate = cache.findFileState( name, true); - -// if ( info.hasSetFlag(FileInfo.SetCreationDate)) -// { -// if ( logger.isDebugEnabled()) -// { -// logger.debug("Set creation date in file state cache" + name + ", " + info.getCreationDateTime()); -// } -// Date createDate = new Date( info.getCreationDateTime()); -// fstate.u(createDate.getTime()); -// } - if ( info.hasSetFlag(FileInfo.SetModifyDate)) - { - if ( logger.isDebugEnabled()) - { - logger.debug("Set modification date in file state cache" + name + ", " + info.getModifyDateTime()); - } - Date modifyDate = new Date( info.getModifyDateTime()); - fstate.updateModifyDateTime(modifyDate.getTime()); - } - } - } - - @Override - public SearchContext startSearch(SrvSession sess, TreeConnection tree, - String searchPath, int attrib) throws FileNotFoundException - { - InFlightCorrector t = new InFlightCorrectorImpl(tree); - - SearchContext ctx = diskInterface.startSearch(sess, tree, searchPath, attrib); - - if(ctx instanceof InFlightCorrectable) - { - InFlightCorrectable thingable = (InFlightCorrectable)ctx; - thingable.setInFlightCorrector(t); - } - - return ctx; - - } - - @Override - public void truncateFile(SrvSession sess, TreeConnection tree, - NetworkFile file, long siz) throws IOException - { - diskInterface.truncateFile(sess, tree, file, siz); - } - - @Override - public int writeFile(SrvSession sess, TreeConnection tree, - NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff) - throws IOException - { - return diskInterface.writeFile(sess, tree, file, buf, bufoff, siz, fileoff); - } - - @Override - public DeviceContext createContext(String shareName, ConfigElement args) - throws DeviceContextException - { - - return diskInterface.createContext(shareName, args); - } - - - public void setFileLockingInterface(FileLockingInterface fileLockingInterface) - { - this.fileLockingInterface = fileLockingInterface; - } - - public FileLockingInterface getFileLockingInterface() - { - return fileLockingInterface; - } - - public void setOpLockInterface(OpLockInterface opLockInterface) - { - this.opLockInterface = opLockInterface; - } - - public OpLockInterface getOpLockInterface() - { - return opLockInterface; - } -} +package org.alfresco.filesys.repo; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Date; + +import org.alfresco.filesys.alfresco.ExtendedDiskInterface; +import org.alfresco.filesys.alfresco.NetworkFileLegacyReferenceCount; +import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.filesys.FileAccessToken; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.FileStatus; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.FileStateCache; +import org.alfresco.jlan.server.filesys.cache.NetworkFileStateInterface; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; +import org.alfresco.jlan.server.locking.FileLockingInterface; +import org.alfresco.jlan.server.locking.LockManager; +import org.alfresco.jlan.server.locking.OpLockInterface; +import org.alfresco.jlan.server.locking.OpLockManager; +import org.alfresco.jlan.smb.SharingMode; +import org.alfresco.model.ContentModel; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.config.ConfigElement; + +/** + * The Legacy file state driver is used to update JLAN's file state cache. + *

+ * This class decorates an ExtendedDiskInterface with odds and ends to keep JLan happy. + *

+ * In particular this implementation cannot contain any code that requires access to the + * alfresco repository. + * + */ +public class LegacyFileStateDriver implements ExtendedDiskInterface +{ + private ExtendedDiskInterface diskInterface; + + private OpLockInterface opLockInterface; + + private FileLockingInterface fileLockingInterface; + + public void init() + { + PropertyCheck.mandatory(this, "diskInterface", diskInterface); + PropertyCheck.mandatory(this, "fileLockingInterface", fileLockingInterface); + PropertyCheck.mandatory(this, "opLockInterface", getOpLockInterface()); + } + + private static final Log logger = LogFactory.getLog(LegacyFileStateDriver.class); + + @Override + public void treeOpened(SrvSession sess, TreeConnection tree) + { + diskInterface.treeOpened(sess, tree); + + } + + @Override + public void treeClosed(SrvSession sess, TreeConnection tree) + { + diskInterface.treeClosed(sess, tree); + } + + @Override + public NetworkFile createFile(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + ContentContext tctx = (ContentContext) tree.getContext(); + + FileStateCache cache = null; + FileState fstate = null; + + FileAccessToken token = null; + + if(tctx.hasStateCache()) + { + cache = tctx.getStateCache(); + fstate = tctx.getStateCache().findFileState( params.getPath(), true); + token = cache.grantFileAccess(params, fstate, FileStatus.NotExist); + if(logger.isDebugEnabled()) + { + logger.debug("create file created lock token:" + token); + } + } + + try + { + NetworkFile newFile = diskInterface.createFile(sess, tree, params); + + int openCount = 1; + + if(newFile instanceof NetworkFileLegacyReferenceCount) + { + NetworkFileLegacyReferenceCount counter = (NetworkFileLegacyReferenceCount)newFile; + openCount = counter.incrementLegacyOpenCount(); + } + // This is the create so we store the first access token always + newFile.setAccessToken(token); + + if(tctx.hasStateCache()) + { + fstate.setProcessId(params.getProcessId()); + fstate.setSharedAccess( params.getSharedAccess()); + + // Indicate that the file is open + fstate.setFileStatus(newFile.isDirectory()? FileStatus.DirectoryExists : FileStatus.FileExists); + + long allocationSize = params.getAllocationSize(); + if(allocationSize > 0) + { + fstate.setAllocationSize(allocationSize); + fstate.setFileSize(allocationSize); + } + + if (newFile instanceof NodeRefNetworkFile) + { + NodeRefNetworkFile x = (NodeRefNetworkFile)newFile; + x.setFileState(fstate); + } + + if (newFile instanceof TempNetworkFile) + { + TempNetworkFile x = (TempNetworkFile)newFile; + x.setFileState(fstate); + } + } + + if (newFile instanceof NodeRefNetworkFile) + { + NodeRefNetworkFile x = (NodeRefNetworkFile)newFile; + x.setProcessId( params.getProcessId()); + x.setAccessToken(token); + } + + if (newFile instanceof TempNetworkFile) + { + TempNetworkFile x = (TempNetworkFile)newFile; + x.setAccessToken(token); + } + + return newFile; + + } + catch(IOException ie) + { + if(logger.isDebugEnabled()) + { + logger.debug("create file exception caught", ie); + } + if(tctx.hasStateCache() && token != null) + { + if(cache != null && fstate != null && token != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("create file release lock token:" + token); + } + cache.releaseFileAccess(fstate, token); + } + } + throw ie; + } + catch (RuntimeException re) + { + // we could be out of memory or a NPE or some other unforseen situation. JLAN will complain loudly ... as it should. + if(logger.isDebugEnabled()) + { + logger.debug("create file exception caught", re); + } + if(tctx.hasStateCache() && token != null) + { + if(cache != null && fstate != null && token != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("create file release lock token:" + token); + } + cache.releaseFileAccess(fstate, token); + } + } + throw re; + } + } + + @Override + public NetworkFile openFile(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + ContentContext tctx = (ContentContext) tree.getContext(); + String path = params.getPath(); + + boolean rollbackOpen = false; + boolean rollbackToken = false; + boolean rollbackCount = false; + boolean rollbackSetToken = false; + + FileAccessToken token = null; + + FileStateCache cache = null; + FileState fstate = null; + NetworkFile openFile = null; + + if(tctx.hasStateCache()) + { + cache = tctx.getStateCache(); + fstate = tctx.getStateCache().findFileState( params.getPath(), true); + + if(!params.isDirectory()) + { + try + { + token = cache.grantFileAccess(params, fstate, FileStatus.Unknown); + } + catch (IOException e) + { + if(logger.isDebugEnabled()) + { + logger.debug("UNABLE to grant file access for path:" + path + ", params" + params, e); + } + throw e; + } + + rollbackToken = true; + if(logger.isDebugEnabled()) + { + logger.debug("open file created lock token:" + token + ", for path:" + path); + } + } + } + + try + { + openFile = diskInterface.openFile(sess, tree, params); + rollbackOpen = true; + + if(openFile instanceof NetworkFileLegacyReferenceCount) + { + NetworkFileLegacyReferenceCount counter = (NetworkFileLegacyReferenceCount)openFile; + int legacyOpenCount = counter.incrementLegacyOpenCount(); + if(logger.isDebugEnabled()) + { + logger.debug("openFile: legacyOpenCount: " + legacyOpenCount); + } + + rollbackCount = true; + } + else + { + logger.debug("openFile does not implement NetworkFileLegacyReferenceCount"); + } + + if( openFile.hasAccessToken()) + { + // already has an access token, release the second token + if(cache != null && fstate != null && token != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("already has access token, release lock token:" + token); + } + cache.releaseFileAccess(fstate, token); + } + } + else + { + if(logger.isDebugEnabled()) + { + logger.debug("store access token on open network file object token:" + token); + } + + // first access token + openFile.setAccessToken(token); + rollbackSetToken = true; + } + + if(tctx.hasStateCache()) + { + fstate = tctx.getStateCache().findFileState( path, true); + fstate.setProcessId(params.getProcessId()); + fstate.setSharedAccess( params.getSharedAccess()); + + // Access date time is read/write time not open time + // fstate.updateAccessDateTime(); + + fstate.setFileSize(openFile.getFileSize()); + fstate.updateChangeDateTime(openFile.getModifyDate()); + fstate.updateModifyDateTime(openFile.getModifyDate()); + } + + if (openFile instanceof ContentNetworkFile) + { + ContentNetworkFile x = (ContentNetworkFile)openFile; + x.setProcessId( params.getProcessId()); + + if(fstate != null) + { + x.setFileState(fstate); + fstate.setFileStatus(FileStatus.FileExists); + } + } + else if (openFile instanceof TempNetworkFile) + { + TempNetworkFile x = (TempNetworkFile)openFile; + if(fstate != null) + { + x.setFileState(fstate); + fstate.setFileStatus(FileStatus.FileExists); + } + } + else if (openFile instanceof AlfrescoFolder) + { + AlfrescoFolder x = (AlfrescoFolder)openFile; + if(fstate != null) + { + x.setFileState(fstate); + fstate.setFileStatus(FileStatus.DirectoryExists); + } + } + else if (openFile instanceof NetworkFile) + { + NetworkFile x = (NetworkFile)openFile; + if(fstate != null) + { + // NetworkFile does not have setFileState + //x.setFileState(fstate); + fstate.setFileStatus(FileStatus.FileExists); + } + } + + rollbackToken = false; + rollbackCount = false; + rollbackSetToken = false; + rollbackOpen = false; + + if(logger.isDebugEnabled()) + { + logger.debug("successfully opened file:" + openFile); + } + + return openFile; + } + finally + { + if(rollbackToken) + { + if(logger.isDebugEnabled()) + { + logger.debug("rollback token:" + token); + } + if(cache != null && fstate != null && token != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("open file release lock token:" + token); + } + cache.releaseFileAccess(fstate, token); + } + } + if(rollbackCount) + { + if(logger.isDebugEnabled()) + { + logger.debug("rollback legacy open count:" + token); + } + if(openFile instanceof NetworkFileLegacyReferenceCount) + { + NetworkFileLegacyReferenceCount counter = (NetworkFileLegacyReferenceCount)openFile; + counter.decrementLagacyOpenCount(); + } + } + if(rollbackSetToken) + { + if(logger.isDebugEnabled()) + { + logger.debug("rollback set access token:" + token); + } + openFile.setAccessToken(null); + } + if(rollbackOpen) + { + if(logger.isDebugEnabled()) + { + logger.debug("rollback open:" + token); + } + diskInterface.closeFile(sess, tree, openFile); + } + } + } + + @Override + public void closeFile(SrvSession sess, TreeConnection tree, + NetworkFile file) throws IOException + { + ContentContext tctx = (ContentContext) tree.getContext(); + FileStateCache cache = null; + FileState fstate = null; + + if(logger.isDebugEnabled()) + { + logger.debug("closeFile:" + file.getFullName() + ", accessToken:" + file.getAccessToken()); + } + + int legacyOpenCount = 0; + + if(file instanceof NetworkFileLegacyReferenceCount) + { + NetworkFileLegacyReferenceCount counter = (NetworkFileLegacyReferenceCount)file; + legacyOpenCount = counter.decrementLagacyOpenCount(); + if(logger.isDebugEnabled()) + { + logger.debug("closeFile: legacyOpenCount=" + legacyOpenCount); + } + } + else + { + logger.debug("file to close does not implement NetworkFileLegacyReferenceCount"); + } + + try + { + if ( file.hasOpLock()) + { + if ( logger.isDebugEnabled()) + { + logger.debug("File Has OpLock - release oplock for closed file, file=" + file.getFullName()); + } + // Release the oplock + + OpLockManager oplockMgr = opLockInterface.getOpLockManager(sess, tree); + + oplockMgr.releaseOpLock( file.getOpLock().getPath()); + + // DEBUG + + if ( logger.isDebugEnabled()) + { + logger.debug("Released oplock for closed file, file=" + file.getFullName()); + } + } + + + // Release any locks on the file owned by this session + + if ( file.hasLocks()) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Release all locks, file=" + file.getFullName()); + } + + LockManager lockMgr = fileLockingInterface.getLockManager(sess, tree); + + if(lockMgr != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("Releasing locks for closed file, file=" + file.getFullName() + ", locks=" + file.numberOfLocks()); + } + // Release all locks on the file owned by this session + + lockMgr.releaseLocksForFile(sess, tree, file); + } + } + + diskInterface.closeFile(sess, tree, file); + + logger.debug("file closed"); + + } + finally + { + if(tctx.hasStateCache()) + { + cache = tctx.getStateCache(); + fstate = cache.findFileState( file.getFullName(), true); + + if(legacyOpenCount == 0 || file.isForce()) + { + if(cache != null && fstate != null && file.getAccessToken() != null) + { + FileAccessToken token = file.getAccessToken(); + if(logger.isDebugEnabled() && token != null) + { + logger.debug("close file, legacy count == 0 release access token:" + token); + } + cache.releaseFileAccess(fstate, token); + file.setAccessToken( null); + } + } + + if(fstate.getOpenCount() == 0 ) + { + logger.debug("fstate OpenCount == 0, reset in-flight state"); + fstate.setAllocationSize(-1); + fstate.setFileSize(-1); + fstate.updateChangeDateTime(0); + fstate.updateModifyDateTime(0); + } + } + } + } + + @Override + public void registerContext(DeviceContext ctx) throws DeviceContextException + { + diskInterface.registerContext(ctx); + } + + public void setDiskInterface(ExtendedDiskInterface diskInterface) + { + this.diskInterface = diskInterface; + } + + public ExtendedDiskInterface getDiskInterface() + { + return diskInterface; + } + + @Override + public void createDirectory(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + diskInterface.createDirectory(sess, tree, params); + } + + @Override + public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) + throws IOException + { + diskInterface.deleteDirectory(sess, tree, dir); + } + + @Override + public void deleteFile(SrvSession sess, TreeConnection tree, String name) + throws IOException + { + ContentContext tctx = (ContentContext) tree.getContext(); + + diskInterface.deleteFile(sess, tree, name); + + if(tctx.hasStateCache()) + { + FileStateCache cache = tctx.getStateCache(); + FileState fstate = cache.findFileState( name, false); + + if(fstate != null) + { + fstate.setFileStatus(FileStatus.NotExist); + fstate.setOpenCount(0); + } + } + } + + @Override + public int fileExists(SrvSession sess, TreeConnection tree, String name) + { + return diskInterface.fileExists(sess, tree, name); + } + + @Override + public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) + throws IOException + { + diskInterface.flushFile(sess, tree, file); + } + + @Override + public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, + String name) throws IOException + { + return diskInterface.getFileInformation(sess, tree, name); + } + + @Override + public boolean isReadOnly(SrvSession sess, DeviceContext ctx) + throws IOException + { + return diskInterface.isReadOnly(sess, ctx); + } + + @Override + public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buf, int bufPos, int siz, long filePos) throws IOException + { + return diskInterface.readFile(sess, tree, file, buf, bufPos, siz, filePos); + } + + @Override + public void renameFile(SrvSession sess, TreeConnection tree, + String oldName, String newName) throws IOException + { + ContentContext tctx = (ContentContext) tree.getContext(); + + diskInterface.renameFile(sess, tree, oldName, newName); + + if(tctx.hasStateCache()) + { + FileStateCache cache = tctx.getStateCache(); + FileState fstate = cache.findFileState( oldName, false); + + if(fstate != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("rename file state from:" + oldName + ", to:" + newName); + } + cache.renameFileState(newName, fstate, fstate.isDirectory()); + } + } + + } + + @Override + public long seekFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long pos, int typ) throws IOException + { + return diskInterface.seekFile(sess, tree, file, pos, typ); + } + + @Override + public void setFileInformation(SrvSession sess, TreeConnection tree, + String name, FileInfo info) throws IOException + { + + diskInterface.setFileInformation(sess, tree, name, info); + + ContentContext tctx = (ContentContext) tree.getContext(); + + if(tctx.hasStateCache()) + { + FileStateCache cache = tctx.getStateCache(); + FileState fstate = cache.findFileState( name, true); + +// if ( info.hasSetFlag(FileInfo.SetCreationDate)) +// { +// if ( logger.isDebugEnabled()) +// { +// logger.debug("Set creation date in file state cache" + name + ", " + info.getCreationDateTime()); +// } +// Date createDate = new Date( info.getCreationDateTime()); +// fstate.u(createDate.getTime()); +// } + if ( info.hasSetFlag(FileInfo.SetModifyDate)) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Set modification date in file state cache" + name + ", " + info.getModifyDateTime()); + } + Date modifyDate = new Date( info.getModifyDateTime()); + fstate.updateModifyDateTime(modifyDate.getTime()); + } + } + } + + @Override + public SearchContext startSearch(SrvSession sess, TreeConnection tree, + String searchPath, int attrib) throws FileNotFoundException + { + InFlightCorrector t = new InFlightCorrectorImpl(tree); + + SearchContext ctx = diskInterface.startSearch(sess, tree, searchPath, attrib); + + if(ctx instanceof InFlightCorrectable) + { + InFlightCorrectable thingable = (InFlightCorrectable)ctx; + thingable.setInFlightCorrector(t); + } + + return ctx; + + } + + @Override + public void truncateFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long siz) throws IOException + { + diskInterface.truncateFile(sess, tree, file, siz); + } + + @Override + public int writeFile(SrvSession sess, TreeConnection tree, + NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff) + throws IOException + { + return diskInterface.writeFile(sess, tree, file, buf, bufoff, siz, fileoff); + } + + @Override + public DeviceContext createContext(String shareName, ConfigElement args) + throws DeviceContextException + { + + return diskInterface.createContext(shareName, args); + } + + + public void setFileLockingInterface(FileLockingInterface fileLockingInterface) + { + this.fileLockingInterface = fileLockingInterface; + } + + public FileLockingInterface getFileLockingInterface() + { + return fileLockingInterface; + } + + public void setOpLockInterface(OpLockInterface opLockInterface) + { + this.opLockInterface = opLockInterface; + } + + public OpLockInterface getOpLockInterface() + { + return opLockInterface; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/repo/LockKeeper.java b/source/java/org/alfresco/filesys/repo/LockKeeper.java index 049d93a551..ca37375cac 100644 --- a/source/java/org/alfresco/filesys/repo/LockKeeper.java +++ b/source/java/org/alfresco/filesys/repo/LockKeeper.java @@ -1,32 +1,32 @@ -package org.alfresco.filesys.repo; - -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * The lock keeper tracks multiple locks on files open via the file system protocols. - * - * There can be multiple locks on the same file, the lock keeper keeps track of the individual locks and delegates to the LockService - * - * @author mrogers - */ -public interface LockKeeper -{ - /** - * Transactional method to make a lock on the specified node ref. - * - * @param nodeRef - */ - public void addLock(NodeRef nodeRef); - - /** - * Transactional method to remove a lock on the specified node ref. - * @param nodeRef - */ - public void removeLock(NodeRef nodeRef); - - /** - * - */ - public void refreshAllLocks(); - -} +package org.alfresco.filesys.repo; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * The lock keeper tracks multiple locks on files open via the file system protocols. + * + * There can be multiple locks on the same file, the lock keeper keeps track of the individual locks and delegates to the LockService + * + * @author mrogers + */ +public interface LockKeeper +{ + /** + * Transactional method to make a lock on the specified node ref. + * + * @param nodeRef + */ + public void addLock(NodeRef nodeRef); + + /** + * Transactional method to remove a lock on the specified node ref. + * @param nodeRef + */ + public void removeLock(NodeRef nodeRef); + + /** + * + */ + public void refreshAllLocks(); + +} diff --git a/source/java/org/alfresco/filesys/repo/LockKeeperImpl.java b/source/java/org/alfresco/filesys/repo/LockKeeperImpl.java index 81bf27acd8..f50381de90 100644 --- a/source/java/org/alfresco/filesys/repo/LockKeeperImpl.java +++ b/source/java/org/alfresco/filesys/repo/LockKeeperImpl.java @@ -1,225 +1,225 @@ -package org.alfresco.filesys.repo; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Date; - -import org.alfresco.repo.cache.TransactionalCache; -import org.alfresco.repo.lock.mem.Lifetime; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockStatus; -import org.alfresco.service.cmr.lock.LockType; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * AlfrescoLockKeeperImpl - *

- * Repository level locking for CIFS, prevents files open via CIFS/FTP/JLAN being interfered with by the alfresco "back end". - * - * Delegates ephemeral locking requests to the lockService. - * - * @author mrogers - * - */ -public class LockKeeperImpl implements LockKeeper -{ - private String LOCK_KEEPER_KEY = "AlfrescoLockKeeperImpl"; - - private LockService lockService; - private TransactionService transactionService; - - private TransactionalCache lockKeeperTransactionalCache; - - private int timeToExpire = 3600 * 2; // 2 Hours - private boolean lockEnabled = true; - - private static final Log logger = LogFactory.getLog(LockKeeperImpl.class); - - public void init() - { - PropertyCheck.mandatory(this, "lockService", getLockService()); - PropertyCheck.mandatory(this, "lockKeeperTransactionalCache", getLockKeeperTransactionalCache()); - PropertyCheck.mandatory(this, "transactionService", getTransactionService()); - } - - - @Override - public void addLock(NodeRef nodeRef) - { - if(lockEnabled) - { - if(logger.isDebugEnabled()) - { - logger.debug("lock nodeRef:" + nodeRef); - } - getLockService().lock(nodeRef, LockType.WRITE_LOCK, getTimeToExpire(), Lifetime.EPHEMERAL, LOCK_KEEPER_KEY); - lockKeeperTransactionalCache.put(nodeRef, new KeeperInfo(AuthenticationUtil.getFullyAuthenticatedUser())); - } - } - - @Override - public void removeLock(NodeRef nodeRef) - { - if(lockEnabled) - { - logger.trace("removeLock nodeRef:" + nodeRef); - getLockService().unlock(nodeRef); - lockKeeperTransactionalCache.remove(nodeRef); - } - } - - @Override - public void refreshAllLocks() - { - Collection nodes = lockKeeperTransactionalCache.getKeys(); - if(logger.isTraceEnabled()) - { - logger.trace("RefreshAllLocks called for #locks, " + nodes.size()); - } - - if(!transactionService.getAllowWrite()) - { - if(logger.isTraceEnabled()) - { - logger.trace("Repo is read only - do nothing"); - return; - } - } - for(NodeRef nodeRef : nodes) - { - final NodeRef nodeRefToRefresh = nodeRef; - final KeeperInfo keeperInfo = lockKeeperTransactionalCache.get(nodeRefToRefresh); - final String additionalInfo = lockService.getAdditionalInfo(nodeRefToRefresh); - - transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - if(LOCK_KEEPER_KEY.equalsIgnoreCase(additionalInfo)) - { - // Its one of this class's locks - AuthenticationUtil.setFullyAuthenticatedUser(keeperInfo.getOwner()); - - //TODO What about node does not exist? - switch (lockService.getLockStatus(nodeRefToRefresh)) - { - case LOCK_OWNER: - if(logger.isDebugEnabled()) - { - logger.debug("refresh ephemeral lock nodeRef: " + nodeRefToRefresh); - } - // Expect to go here - refresh the lock - getLockService().lock(nodeRefToRefresh, LockType.WRITE_LOCK, getTimeToExpire(), Lifetime.EPHEMERAL, LOCK_KEEPER_KEY); - break; - case LOCKED: - // Locked by somebody else? Something has gone wrong here - case LOCK_EXPIRED: - default: - if(logger.isDebugEnabled()) - { - logger.debug("remove lock from lock keeper cache, nodeRef: " + nodeRefToRefresh); - } - lockKeeperTransactionalCache.remove(nodeRefToRefresh); - } - } - else - { - if(logger.isDebugEnabled()) - { - logger.debug("not a lock keeper lock, remove lock from lock keeper cache, nodeRef: " + nodeRefToRefresh); - } - lockKeeperTransactionalCache.remove(nodeRefToRefresh); - } - return null; - } - - }, false, true); - } - } - - public void setLockEnabled(boolean lockEnabled) { - this.lockEnabled = lockEnabled; - } - - public boolean isLockEnabled() { - return lockEnabled; - } - - - public void setLockService(LockService lockService) { - this.lockService = lockService; - } - - - public LockService getLockService() { - return lockService; - } - - - public void setLockKeeperTransactionalCache( - TransactionalCache lockKeeperTransactionalCache) - { - this.lockKeeperTransactionalCache = lockKeeperTransactionalCache; - } - - - public TransactionalCache getLockKeeperTransactionalCache() - { - return lockKeeperTransactionalCache; - } - - - public void setTransactionService(TransactionService transactionHelper) - { - this.transactionService = transactionHelper; - } - - - public TransactionService getTransactionService() - { - return transactionService; - } - - public void setTimeToExpire(int timeToExpire) { - this.timeToExpire = timeToExpire; - } - - - public int getTimeToExpire() { - return timeToExpire; - } - - private class KeeperInfo implements Serializable - { - /** - * - */ - private static final long serialVersionUID = -4200553975218699638L; - /** - * - */ - KeeperInfo(String owner) - { - this.setOwner(owner); - lockTime = new Date(); - } - public void setOwner(String owner) { - this.owner = owner; - } - public String getOwner() { - return owner; - } - private String owner; - Date lockTime; - } - -} +package org.alfresco.filesys.repo; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Date; + +import org.alfresco.repo.cache.TransactionalCache; +import org.alfresco.repo.lock.mem.Lifetime; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * AlfrescoLockKeeperImpl + *

+ * Repository level locking for CIFS, prevents files open via CIFS/FTP/JLAN being interfered with by the alfresco "back end". + * + * Delegates ephemeral locking requests to the lockService. + * + * @author mrogers + * + */ +public class LockKeeperImpl implements LockKeeper +{ + private String LOCK_KEEPER_KEY = "AlfrescoLockKeeperImpl"; + + private LockService lockService; + private TransactionService transactionService; + + private TransactionalCache lockKeeperTransactionalCache; + + private int timeToExpire = 3600 * 2; // 2 Hours + private boolean lockEnabled = true; + + private static final Log logger = LogFactory.getLog(LockKeeperImpl.class); + + public void init() + { + PropertyCheck.mandatory(this, "lockService", getLockService()); + PropertyCheck.mandatory(this, "lockKeeperTransactionalCache", getLockKeeperTransactionalCache()); + PropertyCheck.mandatory(this, "transactionService", getTransactionService()); + } + + + @Override + public void addLock(NodeRef nodeRef) + { + if(lockEnabled) + { + if(logger.isDebugEnabled()) + { + logger.debug("lock nodeRef:" + nodeRef); + } + getLockService().lock(nodeRef, LockType.WRITE_LOCK, getTimeToExpire(), Lifetime.EPHEMERAL, LOCK_KEEPER_KEY); + lockKeeperTransactionalCache.put(nodeRef, new KeeperInfo(AuthenticationUtil.getFullyAuthenticatedUser())); + } + } + + @Override + public void removeLock(NodeRef nodeRef) + { + if(lockEnabled) + { + logger.trace("removeLock nodeRef:" + nodeRef); + getLockService().unlock(nodeRef); + lockKeeperTransactionalCache.remove(nodeRef); + } + } + + @Override + public void refreshAllLocks() + { + Collection nodes = lockKeeperTransactionalCache.getKeys(); + if(logger.isTraceEnabled()) + { + logger.trace("RefreshAllLocks called for #locks, " + nodes.size()); + } + + if(!transactionService.getAllowWrite()) + { + if(logger.isTraceEnabled()) + { + logger.trace("Repo is read only - do nothing"); + return; + } + } + for(NodeRef nodeRef : nodes) + { + final NodeRef nodeRefToRefresh = nodeRef; + final KeeperInfo keeperInfo = lockKeeperTransactionalCache.get(nodeRefToRefresh); + final String additionalInfo = lockService.getAdditionalInfo(nodeRefToRefresh); + + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + if(LOCK_KEEPER_KEY.equalsIgnoreCase(additionalInfo)) + { + // Its one of this class's locks + AuthenticationUtil.setFullyAuthenticatedUser(keeperInfo.getOwner()); + + //TODO What about node does not exist? + switch (lockService.getLockStatus(nodeRefToRefresh)) + { + case LOCK_OWNER: + if(logger.isDebugEnabled()) + { + logger.debug("refresh ephemeral lock nodeRef: " + nodeRefToRefresh); + } + // Expect to go here - refresh the lock + getLockService().lock(nodeRefToRefresh, LockType.WRITE_LOCK, getTimeToExpire(), Lifetime.EPHEMERAL, LOCK_KEEPER_KEY); + break; + case LOCKED: + // Locked by somebody else? Something has gone wrong here + case LOCK_EXPIRED: + default: + if(logger.isDebugEnabled()) + { + logger.debug("remove lock from lock keeper cache, nodeRef: " + nodeRefToRefresh); + } + lockKeeperTransactionalCache.remove(nodeRefToRefresh); + } + } + else + { + if(logger.isDebugEnabled()) + { + logger.debug("not a lock keeper lock, remove lock from lock keeper cache, nodeRef: " + nodeRefToRefresh); + } + lockKeeperTransactionalCache.remove(nodeRefToRefresh); + } + return null; + } + + }, false, true); + } + } + + public void setLockEnabled(boolean lockEnabled) { + this.lockEnabled = lockEnabled; + } + + public boolean isLockEnabled() { + return lockEnabled; + } + + + public void setLockService(LockService lockService) { + this.lockService = lockService; + } + + + public LockService getLockService() { + return lockService; + } + + + public void setLockKeeperTransactionalCache( + TransactionalCache lockKeeperTransactionalCache) + { + this.lockKeeperTransactionalCache = lockKeeperTransactionalCache; + } + + + public TransactionalCache getLockKeeperTransactionalCache() + { + return lockKeeperTransactionalCache; + } + + + public void setTransactionService(TransactionService transactionHelper) + { + this.transactionService = transactionHelper; + } + + + public TransactionService getTransactionService() + { + return transactionService; + } + + public void setTimeToExpire(int timeToExpire) { + this.timeToExpire = timeToExpire; + } + + + public int getTimeToExpire() { + return timeToExpire; + } + + private class KeeperInfo implements Serializable + { + /** + * + */ + private static final long serialVersionUID = -4200553975218699638L; + /** + * + */ + KeeperInfo(String owner) + { + this.setOwner(owner); + lockTime = new Date(); + } + public void setOwner(String owner) { + this.owner = owner; + } + public String getOwner() { + return owner; + } + private String owner; + Date lockTime; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/LockKeeperRefreshJob.java b/source/java/org/alfresco/filesys/repo/LockKeeperRefreshJob.java index 60793c1776..8a80345a8f 100644 --- a/source/java/org/alfresco/filesys/repo/LockKeeperRefreshJob.java +++ b/source/java/org/alfresco/filesys/repo/LockKeeperRefreshJob.java @@ -1,43 +1,43 @@ -package org.alfresco.filesys.repo; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.quartz.Job; -import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; - -public class LockKeeperRefreshJob implements Job -{ - - private static final Log log = LogFactory.getLog(LockKeeperRefreshJob.class); - - @Override public void execute(JobExecutionContext context) throws JobExecutionException - { - if (log.isTraceEnabled()) - { - log.trace("Starting Lock Keeper Refresh Job"); - } - - final LockKeeper lockKeeper = getRequiredQuartzJobParameter(context, "alfrescoLockKeeper", LockKeeper.class); - - lockKeeper.refreshAllLocks(); - } - - - private T getRequiredQuartzJobParameter(JobExecutionContext context, String dataKey, Class requiredClass) throws JobExecutionException - { - @SuppressWarnings("unchecked") - final T result = (T) context.getJobDetail().getJobDataMap().get(dataKey); - if (result == null) - { - if (log.isErrorEnabled()) - { - log.error("PULL: Did not retrieve required service for quartz job: " + dataKey); - } - throw new JobExecutionException("Missing job data: " + dataKey); - } - return result; - } - - -} +package org.alfresco.filesys.repo; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +public class LockKeeperRefreshJob implements Job +{ + + private static final Log log = LogFactory.getLog(LockKeeperRefreshJob.class); + + @Override public void execute(JobExecutionContext context) throws JobExecutionException + { + if (log.isTraceEnabled()) + { + log.trace("Starting Lock Keeper Refresh Job"); + } + + final LockKeeper lockKeeper = getRequiredQuartzJobParameter(context, "alfrescoLockKeeper", LockKeeper.class); + + lockKeeper.refreshAllLocks(); + } + + + private T getRequiredQuartzJobParameter(JobExecutionContext context, String dataKey, Class requiredClass) throws JobExecutionException + { + @SuppressWarnings("unchecked") + final T result = (T) context.getJobDetail().getJobDataMap().get(dataKey); + if (result == null) + { + if (log.isErrorEnabled()) + { + log.error("PULL: Did not retrieve required service for quartz job: " + dataKey); + } + throw new JobExecutionException("Missing job data: " + dataKey); + } + return result; + } + + +} diff --git a/source/java/org/alfresco/filesys/repo/NonTransactionalRuleContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/NonTransactionalRuleContentDiskDriver.java index 3d578a34d5..54f8a5a5db 100644 --- a/source/java/org/alfresco/filesys/repo/NonTransactionalRuleContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/NonTransactionalRuleContentDiskDriver.java @@ -1,684 +1,684 @@ -package org.alfresco.filesys.repo; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Date; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.alfresco.filesys.alfresco.ExtendedDiskInterface; -import org.alfresco.filesys.alfresco.RepositoryDiskInterface; -import org.alfresco.filesys.config.ServerConfigurationBean; -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.filesys.repo.rules.EvaluatorContext; -import org.alfresco.filesys.repo.rules.Operation; -import org.alfresco.filesys.repo.rules.RuleEvaluator; -import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; -import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.core.DeviceContext; -import org.alfresco.jlan.server.core.DeviceContextException; -import org.alfresco.jlan.server.filesys.AccessDeniedException; -import org.alfresco.jlan.server.filesys.FileAction; -import org.alfresco.jlan.server.filesys.FileAttribute; -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.FileName; -import org.alfresco.jlan.server.filesys.FileOpenParams; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.jlan.server.filesys.SearchContext; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.jlan.smb.SharingMode; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.util.FileFilterMode; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.config.ConfigElement; - -/** - * Non Transactional DiskDriver with rules engine. - *

- * Provides a DiskInterface that deals with "shuffles". Shuffles are implemented by the Rules Engine. - *

- * Sits on top of the repository and is non-retryable and non-transactional. - * It is, however thread safe and multiple callers may call in parallel. - */ -public class NonTransactionalRuleContentDiskDriver implements ExtendedDiskInterface -{ - /** - * The Driver State. Contained within the JLAN SrvSession. - */ - private class DriverState - { - /** - * key, value pair storage for the session - */ - Map sessionState = new ConcurrentHashMap(); - - /** - * Map of folderName to Evaluator Context. - */ - Map contextMap = new ConcurrentHashMap(); - } - - private static final Log logger = LogFactory.getLog(NonTransactionalRuleContentDiskDriver.class); - - private ExtendedDiskInterface diskInterface; - private RuleEvaluator ruleEvaluator; - private RepositoryDiskInterface repositoryDiskInterface; - private CommandExecutor commandExecutor; - - public void init() - { - PropertyCheck.mandatory(this, "diskInterface", diskInterface); - PropertyCheck.mandatory(this, "ruleEvaluator", getRuleEvaluator()); - PropertyCheck.mandatory(this, "repositoryDiskInterface", getRepositoryDiskInterface()); - PropertyCheck.mandatory(this, "commandExecutor", getCommandExecutor()); - } - - @Override - public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, - String path) throws IOException - { - if(logger.isDebugEnabled()) - { - logger.debug("getFileInformation:" + path); - } - FileFilterMode.setClient(ClientHelper.getClient(sess)); - try - { - FileInfo info = diskInterface.getFileInformation(sess, tree, path); - return info; - } - finally - { - FileFilterMode.clearClient(); - - } - } - - @Override - public int fileExists(SrvSession sess, TreeConnection tree, String path) - { - int fileExists = diskInterface.fileExists(sess, tree, path); - - return fileExists; - } - - @Override - public DeviceContext createContext(String shareName, ConfigElement args) - throws DeviceContextException - { - return diskInterface.createContext(shareName, args); - } - - @Override - public void treeOpened(SrvSession sess, TreeConnection tree) - { - diskInterface.treeOpened(sess, tree); - } - - @Override - public void treeClosed(SrvSession sess, TreeConnection tree) - { - diskInterface.treeClosed(sess, tree); - - } - - @Override - public void closeFile(SrvSession sess, TreeConnection tree, - NetworkFile param) throws IOException - { - if(logger.isDebugEnabled()) - { - logger.debug("closeFile:" + param.getFullName()); - } - - ContentContext tctx = (ContentContext) tree.getContext(); - NodeRef rootNode = tctx.getRootNode(); - - DriverState driverState = getDriverState(sess); - - String[] paths = FileName.splitPath(param.getFullName()); - String folder = paths[0]; - String file = paths[1]; - - try - { - EvaluatorContext ctx = getEvaluatorContext(driverState, folder); - - Operation o = new CloseFileOperation(file, param, rootNode, param.getFullName(), param.hasDeleteOnClose(), param.isForce()); - Command c = ruleEvaluator.evaluate(ctx, o); - - commandExecutor.execute(sess, tree, c); - - releaseEvaluatorContextIfEmpty(driverState, ctx, folder); - } - catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) - { - throw new AccessDeniedException("Unable to close file " + param.getFullName(), ade); - } - - } - - @Override - public void createDirectory(SrvSession sess, TreeConnection tree, - FileOpenParams params) throws IOException - { - try - { - FileFilterMode.setClient(ClientHelper.getClient(sess)); - try - { - diskInterface.createDirectory(sess, tree, params); - } - finally - { - FileFilterMode.clearClient(); - } - } - catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) - { - throw new AccessDeniedException("Unable to create directory " + params.getPath(), ade); - } - } - - @Override - public NetworkFile createFile(SrvSession sess, TreeConnection tree, - FileOpenParams params) throws IOException - { - try - { - int attr = params.getAttributes(); - if(logger.isDebugEnabled()) - { - int sharedAccess = params.getSharedAccess(); - String strSharedAccess = SharingMode.getSharingModeAsString(sharedAccess); - - logger.debug("createFile:" + params.getPath() - + ", isDirectory: " + params.isDirectory() - + ", isStream: " + params.isStream() - + ", readOnlyAccess: " + params.isReadOnlyAccess() - + ", readWriteAccess: " + params.isReadWriteAccess() - + ", writeOnlyAccess:" +params.isWriteOnlyAccess() - + ", attributesOnlyAccess:" +params.isAttributesOnlyAccess() - + ", sequentialAccessOnly:" + params.isSequentialAccessOnly() - + ", requestBatchOpLock:" +params.requestBatchOpLock() - + ", requestExclusiveOpLock:" +params.requestExclusiveOpLock() - + ", isDeleteOnClose:" +params.isDeleteOnClose() - + ", sharedAccess: " + strSharedAccess - + ", allocationSize: " + params.getAllocationSize() - + ", isHidden:" + FileAttribute.isHidden(attr) - + ", isSystem:" + FileAttribute.isSystem(attr)); - } - - long creationDateTime = params.getCreationDateTime(); - if(creationDateTime != 0) - { - logger.debug("creationDateTime is set:" + new Date(creationDateTime)); - } - - ContentContext tctx = (ContentContext) tree.getContext(); - NodeRef rootNode = tctx.getRootNode(); - - String[] paths = FileName.splitPath(params.getPath()); - String folder = paths[0]; - String file = paths[1]; - - DriverState driverState = getDriverState(sess); - EvaluatorContext ctx = getEvaluatorContext(driverState, folder); - - Operation o = new CreateFileOperation(file, rootNode, params.getPath(), params.getAllocationSize(), FileAttribute.isHidden(attr)); - Command c = ruleEvaluator.evaluate(ctx, o); - - Object ret = commandExecutor.execute(sess, tree, c); - - if(ret != null && ret instanceof NetworkFile) - { - return (NetworkFile)ret; - } - else - { - // Error - contact broken - logger.error("contract broken - NetworkFile not returned. " + ret == null ? "Return value is null" : ret); - return null; - } - } - catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) - { - throw new AccessDeniedException("Unable to create file " + params.getPath(), ade); - } - } - - @Override - public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) - throws IOException - { - try - { - diskInterface.deleteDirectory(sess, tree, dir); - } - catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) - { - throw new AccessDeniedException("Unable to delete directory " + dir, ade); - } - } - - @Override - public void deleteFile(SrvSession sess, TreeConnection tree, String name) - throws IOException - { - if(logger.isDebugEnabled()) - { - logger.debug("deleteFile name:" + name); - } - try - { - ContentContext tctx = (ContentContext) tree.getContext(); - NodeRef rootNode = tctx.getRootNode(); - - DriverState driverState = getDriverState(sess); - - String[] paths = FileName.splitPath(name); - String folder = paths[0]; - String file = paths[1]; - - EvaluatorContext ctx = getEvaluatorContext(driverState, folder); - - Operation o = new DeleteFileOperation(file, rootNode, name); - Command c = ruleEvaluator.evaluate(ctx, o); - commandExecutor.execute(sess, tree, c); - - releaseEvaluatorContextIfEmpty(driverState, ctx, folder); - } - catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) - { - throw new AccessDeniedException("Unable to delete file " + name, ade); - } - - - } // End of deleteFile - - @Override - public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) - throws IOException - { - diskInterface.flushFile(sess, tree, file); - - } - - @Override - public boolean isReadOnly(SrvSession sess, DeviceContext ctx) - throws IOException - { - boolean isReadOnly = diskInterface.isReadOnly(sess, ctx); - - return isReadOnly; - } - - @Override - public NetworkFile openFile(SrvSession sess, TreeConnection tree, - FileOpenParams param) throws IOException - { - String path = param.getPath(); - - boolean truncate = param.isOverwrite(); - - if(logger.isDebugEnabled()) - { - int sharedAccess = param.getSharedAccess(); - String strSharedAccess = SharingMode.getSharingModeAsString(sharedAccess); - - logger.debug("openFile:" + path - + ", isDirectory: " + param.isDirectory() - + ", isStream: " + param.isStream() - + ", readOnlyAccess: " + param.isReadOnlyAccess() - + ", readWriteAccess: " + param.isReadWriteAccess() - + ", writeOnlyAccess:" +param.isWriteOnlyAccess() - + ", attributesOnlyAccess:" +param.isAttributesOnlyAccess() - + ", sequentialAccessOnly:" + param.isSequentialAccessOnly() - + ", writeThrough:" + param.isWriteThrough() - + ", truncate:" + truncate - + ", requestBatchOpLock:" +param.requestBatchOpLock() - + ", requestExclusiveOpLock:" +param.requestExclusiveOpLock() - + ", isDeleteOnClose:" +param.isDeleteOnClose() - + ", allocationSize:" + param.getAllocationSize() - + ", sharedAccess: " + strSharedAccess - + ", openAction: " + param.getOpenAction() - + param - ); - } - - ContentContext tctx = (ContentContext) tree.getContext(); - NodeRef rootNode = tctx.getRootNode(); - - DriverState driverState = getDriverState(sess); - - String[] paths = FileName.splitPath(path); - String folder = paths[0]; - String file = paths[1]; - - EvaluatorContext ctx = getEvaluatorContext(driverState, folder); - - - - OpenFileMode openMode = OpenFileMode.READ_ONLY; - - if(param.isAttributesOnlyAccess()) - { - openMode = OpenFileMode.ATTRIBUTES_ONLY; - } - else if (param.isReadWriteAccess()) - { - openMode = OpenFileMode.READ_WRITE; - } - else if (param.isWriteOnlyAccess()) - { - openMode = OpenFileMode.WRITE_ONLY; - } - else if (param.isReadOnlyAccess()) - { - openMode = OpenFileMode.READ_ONLY; - } - else if(param.isDeleteOnClose()) - { - if(logger.isDebugEnabled()) - { - logger.debug("open file has delete on close"); - } - openMode = OpenFileMode.DELETE; - } - - try - { - Operation o = new OpenFileOperation(file, openMode, truncate, rootNode, path); - Command c = ruleEvaluator.evaluate(ctx, o); - Object ret = commandExecutor.execute(sess, tree, c); - - if(ret != null && ret instanceof NetworkFile) - { - NetworkFile x = (NetworkFile)ret; - - if(logger.isDebugEnabled()) - { - logger.debug("returning open file: for path:" + path +", ret:" + ret); - } - return x; - } - else - { - // Error - contact broken - logger.error("contract broken - NetworkFile not returned. " + ret == null ? "Return value is null" : ret); - return null; - } - } - catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) - { - throw new AccessDeniedException("Unable to open file " + param.getPath(), ade); - } - - //return diskInterface.openFile(sess, tree, params); - } // End of OpenFile - - @Override - public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, - byte[] buf, int bufPos, int siz, long filePos) throws IOException - { - int readSize = diskInterface.readFile(sess, tree, file, buf, bufPos, siz, filePos); - return readSize; - } - - @Override - public void renameFile(SrvSession sess, TreeConnection tree, - String oldPath, String newPath) throws IOException - { - ContentContext tctx = (ContentContext) tree.getContext(); - NodeRef rootNode = tctx.getRootNode(); - - if(logger.isDebugEnabled()) - { - logger.debug("renameFile oldPath:" + oldPath + ", newPath:" + newPath); - } - - DriverState driverState = getDriverState(sess); - - // Is this a rename within the same folder or a move between folders? - - String[] paths = FileName.splitPath(oldPath); - String oldFolder = paths[0]; - String oldFile = paths[1]; - - paths = FileName.splitPath(newPath); - String newFolder = paths[0]; - String newFile = paths[1]; - - try - { - if(oldFolder.equalsIgnoreCase(newFolder)) - { - logger.debug("renameFileCommand - is a rename within the same folder"); - - EvaluatorContext ctx = getEvaluatorContext(driverState, oldFolder); - - Operation o = new RenameFileOperation(oldFile, newFile, oldPath, newPath, rootNode); - Command c = ruleEvaluator.evaluate(ctx, o); - commandExecutor.execute(sess, tree, c); - - ruleEvaluator.notifyRename(ctx, o, c); - - releaseEvaluatorContextIfEmpty(driverState, ctx, oldFolder); - - } - else - { - logger.debug("moveFileCommand - move between folders"); - - Operation o = new MoveFileOperation(oldFile, newFile, oldPath, newPath, rootNode); - - /* - * Note: At the moment we only have move scenarios for the destination folder - so - * we only need to evaluate against a single (destination) context/folder. - * This will require re-design as and when we need to have scenarios for the source/folder - */ - - //EvaluatorContext ctx1 = getEvaluatorContext(driverState, oldFolder); - EvaluatorContext ctx2 = getEvaluatorContext(driverState, newFolder); - - Command c = ruleEvaluator.evaluate(ctx2, o); - - commandExecutor.execute(sess, tree, c); - - releaseEvaluatorContextIfEmpty(driverState, ctx2, newFolder); - - // diskInterface.renameFile(sess, tree, oldPath, newPath); - - } - } - catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) - { - throw new AccessDeniedException("Unable to rename file file " + oldPath, ade); - } - - } - - @Override - public long seekFile(SrvSession sess, TreeConnection tree, - NetworkFile file, long pos, int typ) throws IOException - { - long ret = diskInterface.seekFile(sess, tree, file, pos, typ); - - - return ret; - } - - @Override - public void setFileInformation(SrvSession sess, TreeConnection tree, - String name, FileInfo info) throws IOException - { - diskInterface.setFileInformation(sess, tree, name, info); - - } - - @Override - public SearchContext startSearch(SrvSession sess, TreeConnection tree, - String searchPath, int attrib) throws FileNotFoundException - { - FileFilterMode.setClient(ClientHelper.getClient(sess)); - try - { - SearchContext context = diskInterface.startSearch(sess, tree, searchPath, attrib); - return context; - } - finally - { - FileFilterMode.clearClient(); - } - } - - @Override - public void truncateFile(SrvSession sess, TreeConnection tree, - NetworkFile file, long siz) throws IOException - { - diskInterface.truncateFile(sess, tree, file, siz); - - } - - @Override - public int writeFile(SrvSession sess, TreeConnection tree, - NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff) - throws IOException - { - int writeSize = diskInterface.writeFile(sess, tree, file, buf, bufoff, siz, fileoff); - - return writeSize; - } - - public void setDiskInterface(ExtendedDiskInterface diskInterface) - { - this.diskInterface = diskInterface; - } - - public ExtendedDiskInterface getDiskInterface() - { - return diskInterface; - } - - public void setRuleEvaluator(RuleEvaluator ruleEvaluator) - { - this.ruleEvaluator = ruleEvaluator; - } - - public RuleEvaluator getRuleEvaluator() - { - return ruleEvaluator; - } - - @Override - public void registerContext(DeviceContext ctx) - throws DeviceContextException - { - diskInterface.registerContext(ctx); - } - - public void setRepositoryDiskInterface(RepositoryDiskInterface repositoryDiskInterface) - { - this.repositoryDiskInterface = repositoryDiskInterface; - } - - public RepositoryDiskInterface getRepositoryDiskInterface() - { - return repositoryDiskInterface; - } - - public void setCommandExecutor(CommandExecutor commandExecutor) - { - this.commandExecutor = commandExecutor; - } - - public CommandExecutor getCommandExecutor() - { - return commandExecutor; - } - - - /** - * Get the driver state from the session. - * @param sess SrvSession - * @return the driver state. - */ - private DriverState getDriverState(SrvSession sess) - { - synchronized (sess) - { - // Get the driver state - Object state = sess.getDriverState(); - if(state == null) - { - state = new DriverState(); - sess.setDriverState(state); - if(logger.isDebugEnabled()) - { - logger.debug("new driver state created"); - } - - } - DriverState driverState = (DriverState)state; - return driverState; - } - } - - /** - * Get the evaluator context from the state and the folder. - * @param driverState DriverState - * @param folder String - * @return EvaluatorContext - */ - private EvaluatorContext getEvaluatorContext(DriverState driverState, String folder) - { - synchronized(driverState.contextMap) - { - EvaluatorContext ctx = driverState.contextMap.get(folder); - if(ctx == null) - { - ctx = ruleEvaluator.createContext(driverState.sessionState); - driverState.contextMap.put(folder, ctx); - if(logger.isDebugEnabled()) - { - logger.debug("new driver context: " + folder); - } - } - return ctx; - } - } - - /** - * Release the evaluator context if there are no active scenarios. - * @param driverState DriverState - * @param ctx EvaluatorContext - * @param folder String - */ - private void releaseEvaluatorContextIfEmpty(DriverState driverState, EvaluatorContext ctx, String folder) - { - synchronized(driverState.contextMap) - { - if(ctx != null) - { - if(ctx.getScenarioInstances().size() > 0) - { - } - else - { - driverState.contextMap.remove(folder); - } - } - - } - } - - -} +package org.alfresco.filesys.repo; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.alfresco.filesys.alfresco.ExtendedDiskInterface; +import org.alfresco.filesys.alfresco.RepositoryDiskInterface; +import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.filesys.repo.rules.EvaluatorContext; +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.filesys.repo.rules.RuleEvaluator; +import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.filesys.AccessDeniedException; +import org.alfresco.jlan.server.filesys.FileAction; +import org.alfresco.jlan.server.filesys.FileAttribute; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.smb.SharingMode; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.FileFilterMode; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.config.ConfigElement; + +/** + * Non Transactional DiskDriver with rules engine. + *

+ * Provides a DiskInterface that deals with "shuffles". Shuffles are implemented by the Rules Engine. + *

+ * Sits on top of the repository and is non-retryable and non-transactional. + * It is, however thread safe and multiple callers may call in parallel. + */ +public class NonTransactionalRuleContentDiskDriver implements ExtendedDiskInterface +{ + /** + * The Driver State. Contained within the JLAN SrvSession. + */ + private class DriverState + { + /** + * key, value pair storage for the session + */ + Map sessionState = new ConcurrentHashMap(); + + /** + * Map of folderName to Evaluator Context. + */ + Map contextMap = new ConcurrentHashMap(); + } + + private static final Log logger = LogFactory.getLog(NonTransactionalRuleContentDiskDriver.class); + + private ExtendedDiskInterface diskInterface; + private RuleEvaluator ruleEvaluator; + private RepositoryDiskInterface repositoryDiskInterface; + private CommandExecutor commandExecutor; + + public void init() + { + PropertyCheck.mandatory(this, "diskInterface", diskInterface); + PropertyCheck.mandatory(this, "ruleEvaluator", getRuleEvaluator()); + PropertyCheck.mandatory(this, "repositoryDiskInterface", getRepositoryDiskInterface()); + PropertyCheck.mandatory(this, "commandExecutor", getCommandExecutor()); + } + + @Override + public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, + String path) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("getFileInformation:" + path); + } + FileFilterMode.setClient(ClientHelper.getClient(sess)); + try + { + FileInfo info = diskInterface.getFileInformation(sess, tree, path); + return info; + } + finally + { + FileFilterMode.clearClient(); + + } + } + + @Override + public int fileExists(SrvSession sess, TreeConnection tree, String path) + { + int fileExists = diskInterface.fileExists(sess, tree, path); + + return fileExists; + } + + @Override + public DeviceContext createContext(String shareName, ConfigElement args) + throws DeviceContextException + { + return diskInterface.createContext(shareName, args); + } + + @Override + public void treeOpened(SrvSession sess, TreeConnection tree) + { + diskInterface.treeOpened(sess, tree); + } + + @Override + public void treeClosed(SrvSession sess, TreeConnection tree) + { + diskInterface.treeClosed(sess, tree); + + } + + @Override + public void closeFile(SrvSession sess, TreeConnection tree, + NetworkFile param) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("closeFile:" + param.getFullName()); + } + + ContentContext tctx = (ContentContext) tree.getContext(); + NodeRef rootNode = tctx.getRootNode(); + + DriverState driverState = getDriverState(sess); + + String[] paths = FileName.splitPath(param.getFullName()); + String folder = paths[0]; + String file = paths[1]; + + try + { + EvaluatorContext ctx = getEvaluatorContext(driverState, folder); + + Operation o = new CloseFileOperation(file, param, rootNode, param.getFullName(), param.hasDeleteOnClose(), param.isForce()); + Command c = ruleEvaluator.evaluate(ctx, o); + + commandExecutor.execute(sess, tree, c); + + releaseEvaluatorContextIfEmpty(driverState, ctx, folder); + } + catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) + { + throw new AccessDeniedException("Unable to close file " + param.getFullName(), ade); + } + + } + + @Override + public void createDirectory(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + try + { + FileFilterMode.setClient(ClientHelper.getClient(sess)); + try + { + diskInterface.createDirectory(sess, tree, params); + } + finally + { + FileFilterMode.clearClient(); + } + } + catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) + { + throw new AccessDeniedException("Unable to create directory " + params.getPath(), ade); + } + } + + @Override + public NetworkFile createFile(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + try + { + int attr = params.getAttributes(); + if(logger.isDebugEnabled()) + { + int sharedAccess = params.getSharedAccess(); + String strSharedAccess = SharingMode.getSharingModeAsString(sharedAccess); + + logger.debug("createFile:" + params.getPath() + + ", isDirectory: " + params.isDirectory() + + ", isStream: " + params.isStream() + + ", readOnlyAccess: " + params.isReadOnlyAccess() + + ", readWriteAccess: " + params.isReadWriteAccess() + + ", writeOnlyAccess:" +params.isWriteOnlyAccess() + + ", attributesOnlyAccess:" +params.isAttributesOnlyAccess() + + ", sequentialAccessOnly:" + params.isSequentialAccessOnly() + + ", requestBatchOpLock:" +params.requestBatchOpLock() + + ", requestExclusiveOpLock:" +params.requestExclusiveOpLock() + + ", isDeleteOnClose:" +params.isDeleteOnClose() + + ", sharedAccess: " + strSharedAccess + + ", allocationSize: " + params.getAllocationSize() + + ", isHidden:" + FileAttribute.isHidden(attr) + + ", isSystem:" + FileAttribute.isSystem(attr)); + } + + long creationDateTime = params.getCreationDateTime(); + if(creationDateTime != 0) + { + logger.debug("creationDateTime is set:" + new Date(creationDateTime)); + } + + ContentContext tctx = (ContentContext) tree.getContext(); + NodeRef rootNode = tctx.getRootNode(); + + String[] paths = FileName.splitPath(params.getPath()); + String folder = paths[0]; + String file = paths[1]; + + DriverState driverState = getDriverState(sess); + EvaluatorContext ctx = getEvaluatorContext(driverState, folder); + + Operation o = new CreateFileOperation(file, rootNode, params.getPath(), params.getAllocationSize(), FileAttribute.isHidden(attr)); + Command c = ruleEvaluator.evaluate(ctx, o); + + Object ret = commandExecutor.execute(sess, tree, c); + + if(ret != null && ret instanceof NetworkFile) + { + return (NetworkFile)ret; + } + else + { + // Error - contact broken + logger.error("contract broken - NetworkFile not returned. " + ret == null ? "Return value is null" : ret); + return null; + } + } + catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) + { + throw new AccessDeniedException("Unable to create file " + params.getPath(), ade); + } + } + + @Override + public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) + throws IOException + { + try + { + diskInterface.deleteDirectory(sess, tree, dir); + } + catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) + { + throw new AccessDeniedException("Unable to delete directory " + dir, ade); + } + } + + @Override + public void deleteFile(SrvSession sess, TreeConnection tree, String name) + throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("deleteFile name:" + name); + } + try + { + ContentContext tctx = (ContentContext) tree.getContext(); + NodeRef rootNode = tctx.getRootNode(); + + DriverState driverState = getDriverState(sess); + + String[] paths = FileName.splitPath(name); + String folder = paths[0]; + String file = paths[1]; + + EvaluatorContext ctx = getEvaluatorContext(driverState, folder); + + Operation o = new DeleteFileOperation(file, rootNode, name); + Command c = ruleEvaluator.evaluate(ctx, o); + commandExecutor.execute(sess, tree, c); + + releaseEvaluatorContextIfEmpty(driverState, ctx, folder); + } + catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) + { + throw new AccessDeniedException("Unable to delete file " + name, ade); + } + + + } // End of deleteFile + + @Override + public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) + throws IOException + { + diskInterface.flushFile(sess, tree, file); + + } + + @Override + public boolean isReadOnly(SrvSession sess, DeviceContext ctx) + throws IOException + { + boolean isReadOnly = diskInterface.isReadOnly(sess, ctx); + + return isReadOnly; + } + + @Override + public NetworkFile openFile(SrvSession sess, TreeConnection tree, + FileOpenParams param) throws IOException + { + String path = param.getPath(); + + boolean truncate = param.isOverwrite(); + + if(logger.isDebugEnabled()) + { + int sharedAccess = param.getSharedAccess(); + String strSharedAccess = SharingMode.getSharingModeAsString(sharedAccess); + + logger.debug("openFile:" + path + + ", isDirectory: " + param.isDirectory() + + ", isStream: " + param.isStream() + + ", readOnlyAccess: " + param.isReadOnlyAccess() + + ", readWriteAccess: " + param.isReadWriteAccess() + + ", writeOnlyAccess:" +param.isWriteOnlyAccess() + + ", attributesOnlyAccess:" +param.isAttributesOnlyAccess() + + ", sequentialAccessOnly:" + param.isSequentialAccessOnly() + + ", writeThrough:" + param.isWriteThrough() + + ", truncate:" + truncate + + ", requestBatchOpLock:" +param.requestBatchOpLock() + + ", requestExclusiveOpLock:" +param.requestExclusiveOpLock() + + ", isDeleteOnClose:" +param.isDeleteOnClose() + + ", allocationSize:" + param.getAllocationSize() + + ", sharedAccess: " + strSharedAccess + + ", openAction: " + param.getOpenAction() + + param + ); + } + + ContentContext tctx = (ContentContext) tree.getContext(); + NodeRef rootNode = tctx.getRootNode(); + + DriverState driverState = getDriverState(sess); + + String[] paths = FileName.splitPath(path); + String folder = paths[0]; + String file = paths[1]; + + EvaluatorContext ctx = getEvaluatorContext(driverState, folder); + + + + OpenFileMode openMode = OpenFileMode.READ_ONLY; + + if(param.isAttributesOnlyAccess()) + { + openMode = OpenFileMode.ATTRIBUTES_ONLY; + } + else if (param.isReadWriteAccess()) + { + openMode = OpenFileMode.READ_WRITE; + } + else if (param.isWriteOnlyAccess()) + { + openMode = OpenFileMode.WRITE_ONLY; + } + else if (param.isReadOnlyAccess()) + { + openMode = OpenFileMode.READ_ONLY; + } + else if(param.isDeleteOnClose()) + { + if(logger.isDebugEnabled()) + { + logger.debug("open file has delete on close"); + } + openMode = OpenFileMode.DELETE; + } + + try + { + Operation o = new OpenFileOperation(file, openMode, truncate, rootNode, path); + Command c = ruleEvaluator.evaluate(ctx, o); + Object ret = commandExecutor.execute(sess, tree, c); + + if(ret != null && ret instanceof NetworkFile) + { + NetworkFile x = (NetworkFile)ret; + + if(logger.isDebugEnabled()) + { + logger.debug("returning open file: for path:" + path +", ret:" + ret); + } + return x; + } + else + { + // Error - contact broken + logger.error("contract broken - NetworkFile not returned. " + ret == null ? "Return value is null" : ret); + return null; + } + } + catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) + { + throw new AccessDeniedException("Unable to open file " + param.getPath(), ade); + } + + //return diskInterface.openFile(sess, tree, params); + } // End of OpenFile + + @Override + public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buf, int bufPos, int siz, long filePos) throws IOException + { + int readSize = diskInterface.readFile(sess, tree, file, buf, bufPos, siz, filePos); + return readSize; + } + + @Override + public void renameFile(SrvSession sess, TreeConnection tree, + String oldPath, String newPath) throws IOException + { + ContentContext tctx = (ContentContext) tree.getContext(); + NodeRef rootNode = tctx.getRootNode(); + + if(logger.isDebugEnabled()) + { + logger.debug("renameFile oldPath:" + oldPath + ", newPath:" + newPath); + } + + DriverState driverState = getDriverState(sess); + + // Is this a rename within the same folder or a move between folders? + + String[] paths = FileName.splitPath(oldPath); + String oldFolder = paths[0]; + String oldFile = paths[1]; + + paths = FileName.splitPath(newPath); + String newFolder = paths[0]; + String newFile = paths[1]; + + try + { + if(oldFolder.equalsIgnoreCase(newFolder)) + { + logger.debug("renameFileCommand - is a rename within the same folder"); + + EvaluatorContext ctx = getEvaluatorContext(driverState, oldFolder); + + Operation o = new RenameFileOperation(oldFile, newFile, oldPath, newPath, rootNode); + Command c = ruleEvaluator.evaluate(ctx, o); + commandExecutor.execute(sess, tree, c); + + ruleEvaluator.notifyRename(ctx, o, c); + + releaseEvaluatorContextIfEmpty(driverState, ctx, oldFolder); + + } + else + { + logger.debug("moveFileCommand - move between folders"); + + Operation o = new MoveFileOperation(oldFile, newFile, oldPath, newPath, rootNode); + + /* + * Note: At the moment we only have move scenarios for the destination folder - so + * we only need to evaluate against a single (destination) context/folder. + * This will require re-design as and when we need to have scenarios for the source/folder + */ + + //EvaluatorContext ctx1 = getEvaluatorContext(driverState, oldFolder); + EvaluatorContext ctx2 = getEvaluatorContext(driverState, newFolder); + + Command c = ruleEvaluator.evaluate(ctx2, o); + + commandExecutor.execute(sess, tree, c); + + releaseEvaluatorContextIfEmpty(driverState, ctx2, newFolder); + + // diskInterface.renameFile(sess, tree, oldPath, newPath); + + } + } + catch(org.alfresco.repo.security.permissions.AccessDeniedException ade) + { + throw new AccessDeniedException("Unable to rename file file " + oldPath, ade); + } + + } + + @Override + public long seekFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long pos, int typ) throws IOException + { + long ret = diskInterface.seekFile(sess, tree, file, pos, typ); + + + return ret; + } + + @Override + public void setFileInformation(SrvSession sess, TreeConnection tree, + String name, FileInfo info) throws IOException + { + diskInterface.setFileInformation(sess, tree, name, info); + + } + + @Override + public SearchContext startSearch(SrvSession sess, TreeConnection tree, + String searchPath, int attrib) throws FileNotFoundException + { + FileFilterMode.setClient(ClientHelper.getClient(sess)); + try + { + SearchContext context = diskInterface.startSearch(sess, tree, searchPath, attrib); + return context; + } + finally + { + FileFilterMode.clearClient(); + } + } + + @Override + public void truncateFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long siz) throws IOException + { + diskInterface.truncateFile(sess, tree, file, siz); + + } + + @Override + public int writeFile(SrvSession sess, TreeConnection tree, + NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff) + throws IOException + { + int writeSize = diskInterface.writeFile(sess, tree, file, buf, bufoff, siz, fileoff); + + return writeSize; + } + + public void setDiskInterface(ExtendedDiskInterface diskInterface) + { + this.diskInterface = diskInterface; + } + + public ExtendedDiskInterface getDiskInterface() + { + return diskInterface; + } + + public void setRuleEvaluator(RuleEvaluator ruleEvaluator) + { + this.ruleEvaluator = ruleEvaluator; + } + + public RuleEvaluator getRuleEvaluator() + { + return ruleEvaluator; + } + + @Override + public void registerContext(DeviceContext ctx) + throws DeviceContextException + { + diskInterface.registerContext(ctx); + } + + public void setRepositoryDiskInterface(RepositoryDiskInterface repositoryDiskInterface) + { + this.repositoryDiskInterface = repositoryDiskInterface; + } + + public RepositoryDiskInterface getRepositoryDiskInterface() + { + return repositoryDiskInterface; + } + + public void setCommandExecutor(CommandExecutor commandExecutor) + { + this.commandExecutor = commandExecutor; + } + + public CommandExecutor getCommandExecutor() + { + return commandExecutor; + } + + + /** + * Get the driver state from the session. + * @param sess SrvSession + * @return the driver state. + */ + private DriverState getDriverState(SrvSession sess) + { + synchronized (sess) + { + // Get the driver state + Object state = sess.getDriverState(); + if(state == null) + { + state = new DriverState(); + sess.setDriverState(state); + if(logger.isDebugEnabled()) + { + logger.debug("new driver state created"); + } + + } + DriverState driverState = (DriverState)state; + return driverState; + } + } + + /** + * Get the evaluator context from the state and the folder. + * @param driverState DriverState + * @param folder String + * @return EvaluatorContext + */ + private EvaluatorContext getEvaluatorContext(DriverState driverState, String folder) + { + synchronized(driverState.contextMap) + { + EvaluatorContext ctx = driverState.contextMap.get(folder); + if(ctx == null) + { + ctx = ruleEvaluator.createContext(driverState.sessionState); + driverState.contextMap.put(folder, ctx); + if(logger.isDebugEnabled()) + { + logger.debug("new driver context: " + folder); + } + } + return ctx; + } + } + + /** + * Release the evaluator context if there are no active scenarios. + * @param driverState DriverState + * @param ctx EvaluatorContext + * @param folder String + */ + private void releaseEvaluatorContextIfEmpty(DriverState driverState, EvaluatorContext ctx, String folder) + { + synchronized(driverState.contextMap) + { + if(ctx != null) + { + if(ctx.getScenarioInstances().size() > 0) + { + } + else + { + driverState.contextMap.remove(folder); + } + } + + } + } + + +} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/repo/OpenFileMode.java b/source/java/org/alfresco/filesys/repo/OpenFileMode.java index 2042106b83..ce51a40b0f 100644 --- a/source/java/org/alfresco/filesys/repo/OpenFileMode.java +++ b/source/java/org/alfresco/filesys/repo/OpenFileMode.java @@ -1,10 +1,10 @@ -package org.alfresco.filesys.repo; - -public enum OpenFileMode -{ - READ_ONLY, - WRITE_ONLY, - READ_WRITE, - DELETE, - ATTRIBUTES_ONLY -} +package org.alfresco.filesys.repo; + +public enum OpenFileMode +{ + READ_ONLY, + WRITE_ONLY, + READ_WRITE, + DELETE, + ATTRIBUTES_ONLY +} diff --git a/source/java/org/alfresco/filesys/repo/ResultCallback.java b/source/java/org/alfresco/filesys/repo/ResultCallback.java index 6f6787f39d..f21c3d96b9 100644 --- a/source/java/org/alfresco/filesys/repo/ResultCallback.java +++ b/source/java/org/alfresco/filesys/repo/ResultCallback.java @@ -1,13 +1,13 @@ -package org.alfresco.filesys.repo; - -import org.alfresco.filesys.repo.rules.Command; - -public interface ResultCallback extends Command -{ - /** - * Call the callback with the result of the operation. - * @param result the result. - */ - void execute(Object result); - -} +package org.alfresco.filesys.repo; + +import org.alfresco.filesys.repo.rules.Command; + +public interface ResultCallback extends Command +{ + /** + * Call the callback with the result of the operation. + * @param result the result. + */ + void execute(Object result); + +} diff --git a/source/java/org/alfresco/filesys/repo/TempNetworkFile.java b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java index 4a89784879..0d195e4d84 100644 --- a/source/java/org/alfresco/filesys/repo/TempNetworkFile.java +++ b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java @@ -1,212 +1,212 @@ -package org.alfresco.filesys.repo; - -import java.io.File; -import java.io.IOException; -import java.io.Reader; - -import org.alfresco.filesys.alfresco.NetworkFileLegacyReferenceCount; -import org.alfresco.jlan.server.filesys.FileAttribute; -import org.alfresco.jlan.server.filesys.cache.FileState; -import org.alfresco.jlan.server.filesys.cache.NetworkFileStateInterface; -import org.alfresco.jlan.smb.server.disk.JavaNetworkFile; - -/** - * Temporary Java backed network file. - * - * @author mrogers - */ -public class TempNetworkFile extends JavaNetworkFile - implements NetworkFileStateInterface, - NetworkFileLegacyReferenceCount -{ - private boolean changed = false; - boolean modificationDateSetDirectly = false; - - - - /** - * Create a new temporary file with no existing content. - * - * @param file the underlying File - * @param netPath where in the repo this file is going. - */ - public TempNetworkFile(File file, String netPath) - { - super(file, netPath); - setFullName(netPath); - setAttributes(FileAttribute.NTNormal); - setClosed(false); - } - - /** - * A new temporary network file with some existing content. - * @param file File - * @param netPath String - * @param existingContent Reader - */ - public TempNetworkFile(File file, String netPath, Reader existingContent) - { - super(file, netPath); - setFullName(netPath); - setAttributes(FileAttribute.NTNormal); - setClosed(false); - } - - /** - * Access to the underlying file. - * @return the file. - */ - public File getFile() - { - return m_file; - } - - public String toString() - { - return "TempNetworkFile:" + getFullName() + " path: " + m_file.getAbsolutePath(); - } - - @Override - public int readFile(byte[] buf, int len, int pos, long fileOff) - throws java.io.IOException - { - if(fileState != null) - { - fileState.updateAccessDateTime(); - } - return super.readFile(buf, len, pos, fileOff); - } - - @Override - public void writeFile(byte[] buf, int len, int pos) throws IOException - { - changed = true; - - super.writeFile(buf, len, pos); - - long size = m_io.length(); - - setFileSize(size); - if(fileState != null) - { - fileState.updateModifyDateTime(); - fileState.updateAccessDateTime(); - fileState.setFileSize(size); - fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); - } - } - - @Override - public void writeFile(byte[] buffer, int length, int position, long fileOffset) - throws IOException - { - changed = true; - - super.writeFile(buffer, length, position, fileOffset); - - long size = m_io.length(); - setFileSize(size); - if(fileState != null) - { - fileState.updateModifyDateTime(); - fileState.updateAccessDateTime(); - fileState.setFileSize(size); - fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); - } - } - - @Override - public void truncateFile(long size) throws IOException - { - super.truncateFile(size); - - if(size == 0) - { - changed = true; - } - - setFileSize(size); - if(fileState != null) - { - fileState.updateModifyDateTime(); - fileState.updateAccessDateTime(); - fileState.setFileSize(size); - fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); - } - } - - // For JLAN file state lock manager - public void setFileState(FileState fileState) - { - this.fileState = fileState; - } - - @Override - public FileState getFileState() - { - return fileState; - } - - /** - * Tell JLAN it needs to call disk.closeFile rather than short cutting. - * @return boolean - */ - public boolean allowsOpenCloseViaNetworkFile() { - return false; - } - - - public void setChanged(boolean changed) - { - this.changed = changed; - } - - public boolean isChanged() - { - return changed; - } - - public boolean isModificationDateSetDirectly() - { - return modificationDateSetDirectly; - } - - public void setModificationDateSetDirectly(boolean modificationDateSetDirectly) - { - this.modificationDateSetDirectly = modificationDateSetDirectly; - } - - private int legacyOpenCount = 0; - - /** - * Increment the legacy file open count - * - * @return int - */ - public synchronized final int incrementLegacyOpenCount() { - legacyOpenCount++; - return legacyOpenCount; - } - - /** - * Decrement the legacy file open count - * - * @return int - */ - public synchronized final int decrementLagacyOpenCount() { - legacyOpenCount--; - return legacyOpenCount; - } - - /** - * Return the legacy open file count - * - * @return int - */ - public final int getLegacyOpenCount() { - return legacyOpenCount; - } - - - private FileState fileState; -} +package org.alfresco.filesys.repo; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; + +import org.alfresco.filesys.alfresco.NetworkFileLegacyReferenceCount; +import org.alfresco.jlan.server.filesys.FileAttribute; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.NetworkFileStateInterface; +import org.alfresco.jlan.smb.server.disk.JavaNetworkFile; + +/** + * Temporary Java backed network file. + * + * @author mrogers + */ +public class TempNetworkFile extends JavaNetworkFile + implements NetworkFileStateInterface, + NetworkFileLegacyReferenceCount +{ + private boolean changed = false; + boolean modificationDateSetDirectly = false; + + + + /** + * Create a new temporary file with no existing content. + * + * @param file the underlying File + * @param netPath where in the repo this file is going. + */ + public TempNetworkFile(File file, String netPath) + { + super(file, netPath); + setFullName(netPath); + setAttributes(FileAttribute.NTNormal); + setClosed(false); + } + + /** + * A new temporary network file with some existing content. + * @param file File + * @param netPath String + * @param existingContent Reader + */ + public TempNetworkFile(File file, String netPath, Reader existingContent) + { + super(file, netPath); + setFullName(netPath); + setAttributes(FileAttribute.NTNormal); + setClosed(false); + } + + /** + * Access to the underlying file. + * @return the file. + */ + public File getFile() + { + return m_file; + } + + public String toString() + { + return "TempNetworkFile:" + getFullName() + " path: " + m_file.getAbsolutePath(); + } + + @Override + public int readFile(byte[] buf, int len, int pos, long fileOff) + throws java.io.IOException + { + if(fileState != null) + { + fileState.updateAccessDateTime(); + } + return super.readFile(buf, len, pos, fileOff); + } + + @Override + public void writeFile(byte[] buf, int len, int pos) throws IOException + { + changed = true; + + super.writeFile(buf, len, pos); + + long size = m_io.length(); + + setFileSize(size); + if(fileState != null) + { + fileState.updateModifyDateTime(); + fileState.updateAccessDateTime(); + fileState.setFileSize(size); + fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); + } + } + + @Override + public void writeFile(byte[] buffer, int length, int position, long fileOffset) + throws IOException + { + changed = true; + + super.writeFile(buffer, length, position, fileOffset); + + long size = m_io.length(); + setFileSize(size); + if(fileState != null) + { + fileState.updateModifyDateTime(); + fileState.updateAccessDateTime(); + fileState.setFileSize(size); + fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); + } + } + + @Override + public void truncateFile(long size) throws IOException + { + super.truncateFile(size); + + if(size == 0) + { + changed = true; + } + + setFileSize(size); + if(fileState != null) + { + fileState.updateModifyDateTime(); + fileState.updateAccessDateTime(); + fileState.setFileSize(size); + fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); + } + } + + // For JLAN file state lock manager + public void setFileState(FileState fileState) + { + this.fileState = fileState; + } + + @Override + public FileState getFileState() + { + return fileState; + } + + /** + * Tell JLAN it needs to call disk.closeFile rather than short cutting. + * @return boolean + */ + public boolean allowsOpenCloseViaNetworkFile() { + return false; + } + + + public void setChanged(boolean changed) + { + this.changed = changed; + } + + public boolean isChanged() + { + return changed; + } + + public boolean isModificationDateSetDirectly() + { + return modificationDateSetDirectly; + } + + public void setModificationDateSetDirectly(boolean modificationDateSetDirectly) + { + this.modificationDateSetDirectly = modificationDateSetDirectly; + } + + private int legacyOpenCount = 0; + + /** + * Increment the legacy file open count + * + * @return int + */ + public synchronized final int incrementLegacyOpenCount() { + legacyOpenCount++; + return legacyOpenCount; + } + + /** + * Decrement the legacy file open count + * + * @return int + */ + public synchronized final int decrementLagacyOpenCount() { + legacyOpenCount--; + return legacyOpenCount; + } + + /** + * Return the legacy open file count + * + * @return int + */ + public final int getLegacyOpenCount() { + return legacyOpenCount; + } + + + private FileState fileState; +} diff --git a/source/java/org/alfresco/filesys/repo/desk/package-info.java b/source/java/org/alfresco/filesys/repo/desk/package-info.java index 2125d08612..96f30c0adc 100644 --- a/source/java/org/alfresco/filesys/repo/desk/package-info.java +++ b/source/java/org/alfresco/filesys/repo/desk/package-info.java @@ -1,6 +1,6 @@ -/** - * Implementation of desk top actions for file system protocols. - */ -@PackageMarker -package org.alfresco.filesys.repo.desk; -import org.alfresco.util.PackageMarker; +/** + * Implementation of desk top actions for file system protocols. + */ +@PackageMarker +package org.alfresco.filesys.repo.desk; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/filesys/repo/package-info.java b/source/java/org/alfresco/filesys/repo/package-info.java index 8b31f88863..b1e59f8a32 100644 --- a/source/java/org/alfresco/filesys/repo/package-info.java +++ b/source/java/org/alfresco/filesys/repo/package-info.java @@ -1,21 +1,21 @@ -/** - * The Alfesco filesystem to repository translation layer - * - *

- * Alfresco ContentDiskDriver and supporting classes. - * - *

- * NodeEventQueue containing NodeEvents such as when a node - * is created or deleted. - * - *

- * NodeMonitor which is bound to various node policies and updates - * the file state cache. - * - *

- * Quota Management which contains a UserQuota for each active user. - * - */ -@PackageMarker -package org.alfresco.filesys.repo; -import org.alfresco.util.PackageMarker; +/** + * The Alfesco filesystem to repository translation layer + * + *

+ * Alfresco ContentDiskDriver and supporting classes. + * + *

+ * NodeEventQueue containing NodeEvents such as when a node + * is created or deleted. + * + *

+ * NodeMonitor which is bound to various node policies and updates + * the file state cache. + * + *

+ * Quota Management which contains a UserQuota for each active user. + * + */ +@PackageMarker +package org.alfresco.filesys.repo; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/filesys/repo/rules/Command.java b/source/java/org/alfresco/filesys/repo/rules/Command.java index 9d131d5ee5..c53bc9be21 100644 --- a/source/java/org/alfresco/filesys/repo/rules/Command.java +++ b/source/java/org/alfresco/filesys/repo/rules/Command.java @@ -1,18 +1,18 @@ -package org.alfresco.filesys.repo.rules; - -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; - -/** - * A Command is something that must be done. Commands are higher level - * than Operations. So a rule returns a command or set of commands to - * implement an operation. - */ -public interface Command -{ - /** - * Is a transaction required to run this command? - */ - TxnReadState getTransactionRequired(); - - -} +package org.alfresco.filesys.repo.rules; + +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; + +/** + * A Command is something that must be done. Commands are higher level + * than Operations. So a rule returns a command or set of commands to + * implement an operation. + */ +public interface Command +{ + /** + * Is a transaction required to run this command? + */ + TxnReadState getTransactionRequired(); + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/DependentInstance.java b/source/java/org/alfresco/filesys/repo/rules/DependentInstance.java index 880a21d7ad..7b7124d2f5 100644 --- a/source/java/org/alfresco/filesys/repo/rules/DependentInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/DependentInstance.java @@ -1,21 +1,21 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.List; - -/** - * A dependent instance takes account of some other instance. - */ -public interface DependentInstance -{ - /** - * Notifies the scenario that there are conflicting loosing scenarios. - *

- * The winning scenario may modify its behavior to take account of the loosers. - *

- * @param results - * @param command - * @return the new command - */ - Command win(List results, Command command); - -} +package org.alfresco.filesys.repo.rules; + +import java.util.List; + +/** + * A dependent instance takes account of some other instance. + */ +public interface DependentInstance +{ + /** + * Notifies the scenario that there are conflicting loosing scenarios. + *

+ * The winning scenario may modify its behavior to take account of the loosers. + *

+ * @param results + * @param command + * @return the new command + */ + Command win(List results, Command command); + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/EvaluatorContext.java b/source/java/org/alfresco/filesys/repo/rules/EvaluatorContext.java index 33077dc9ee..ad6ec21748 100644 --- a/source/java/org/alfresco/filesys/repo/rules/EvaluatorContext.java +++ b/source/java/org/alfresco/filesys/repo/rules/EvaluatorContext.java @@ -1,30 +1,30 @@ -/** - * package org.alfresco.filesys.repo.rules; - * @author mrogers - * - */ -package org.alfresco.filesys.repo.rules; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * EvaluatorContext - */ -public interface EvaluatorContext -{ - /** - * Get the current scenario instances for this context - * @return a list of the curent scenario instances - */ - public List getScenarioInstances(); - - /** - * Get the session state for this context. - * @return the session state for this context. - */ - public Map getSessionState(); - - -} +/** + * package org.alfresco.filesys.repo.rules; + * @author mrogers + * + */ +package org.alfresco.filesys.repo.rules; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * EvaluatorContext + */ +public interface EvaluatorContext +{ + /** + * Get the current scenario instances for this context + * @return a list of the curent scenario instances + */ + public List getScenarioInstances(); + + /** + * Get the session state for this context. + * @return the session state for this context. + */ + public Map getSessionState(); + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/Operation.java b/source/java/org/alfresco/filesys/repo/rules/Operation.java index 5f4a4fc4c1..2b6d54de04 100644 --- a/source/java/org/alfresco/filesys/repo/rules/Operation.java +++ b/source/java/org/alfresco/filesys/repo/rules/Operation.java @@ -1,10 +1,10 @@ -package org.alfresco.filesys.repo.rules; - -/** - * An operation is a primitive thing that needs to be executed. - * Such as a create, rename or delete. - */ -public interface Operation -{ - -} +package org.alfresco.filesys.repo.rules; + +/** + * An operation is a primitive thing that needs to be executed. + * Such as a create, rename or delete. + */ +public interface Operation +{ + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/OperationExecutor.java b/source/java/org/alfresco/filesys/repo/rules/OperationExecutor.java index 9ef120ab3b..31a068ae73 100644 --- a/source/java/org/alfresco/filesys/repo/rules/OperationExecutor.java +++ b/source/java/org/alfresco/filesys/repo/rules/OperationExecutor.java @@ -1,11 +1,11 @@ -package org.alfresco.filesys.repo.rules; - -/** - * An operation executor is an implementation of how to execute an - * operation. i.e. It is the thing that does stuff to the repo. - */ -public interface OperationExecutor -{ - public void execute(Operation operation); - -} +package org.alfresco.filesys.repo.rules; + +/** + * An operation executor is an implementation of how to execute an + * operation. i.e. It is the thing that does stuff to the repo. + */ +public interface OperationExecutor +{ + public void execute(Operation operation); + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/RuleEvaluator.java b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluator.java index a57184504d..8a12f57c30 100644 --- a/source/java/org/alfresco/filesys/repo/rules/RuleEvaluator.java +++ b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluator.java @@ -1,34 +1,34 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.Map; - -/** - * The Rule Evaluator evaluates the operation and returns - * details of the commands to implement those operations. - *

- * It is configured with a list of scenarios. - */ -public interface RuleEvaluator -{ - /** - * Create a new evaluator context. Typically for a particular folder. - * An evaluator context groups operations together. - * @return the new context. - */ - public EvaluatorContext createContext(MapsessionContext); - - /** - * Evaluate the scenarios contained within the context against the current operation - * @param context - the context to evaluate the operation - * @param operation - the operation to be evaluated. - * @return Command the command to fulfil the operation - */ - public Command evaluate(EvaluatorContext context, Operation operation); - - /** - * Tell the context of a rename - */ - public void notifyRename(EvaluatorContext context, Operation operation, Command c); - - -} +package org.alfresco.filesys.repo.rules; + +import java.util.Map; + +/** + * The Rule Evaluator evaluates the operation and returns + * details of the commands to implement those operations. + *

+ * It is configured with a list of scenarios. + */ +public interface RuleEvaluator +{ + /** + * Create a new evaluator context. Typically for a particular folder. + * An evaluator context groups operations together. + * @return the new context. + */ + public EvaluatorContext createContext(MapsessionContext); + + /** + * Evaluate the scenarios contained within the context against the current operation + * @param context - the context to evaluate the operation + * @param operation - the operation to be evaluated. + * @return Command the command to fulfil the operation + */ + public Command evaluate(EvaluatorContext context, Operation operation); + + /** + * Tell the context of a rename + */ + public void notifyRename(EvaluatorContext context, Operation operation, Command c); + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java index d5908b31e0..a32d689e0f 100644 --- a/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java +++ b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java @@ -1,216 +1,216 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * The Rule Evaluator evaluates the operation and returns - * details of the commands to implement those operations. - *

- * It is configured with a list of scenarios which act as factories for scenario instances. - */ -public class RuleEvaluatorImpl implements RuleEvaluator -{ - private static Log logger = LogFactory.getLog(RuleEvaluatorImpl.class); - - /** - * The evaluator context, one for each folder - */ - private class EvaluatorContextImpl implements EvaluatorContext - { - MapsessionState; - - EvaluatorContextImpl (MapsessionState) - { - this.sessionState = sessionState; - } - /** - * Current instances of scenarios - */ - private List currentScenarioInstances = new ArrayList(); - - @Override - public List getScenarioInstances() - { - return currentScenarioInstances; - } - - @Override - public Map getSessionState() - { - return sessionState; - } - } - - public void init() - { - PropertyCheck.mandatory(this, "scenarios", scenarios); - } - - /** - * The scenarios contained within this RuleEvaluator - */ - private List scenarios; - - /** - * Evaluate the scenarios against the current operation - * @param operation the operation to be evaluated - * @return the command to execute that operation - */ - public Command evaluate(EvaluatorContext context, Operation operation) - { - if(logger.isDebugEnabled()) - { - logger.debug("evaluate:" + operation); - } - - /** - * For each scenario, do we need to create a new scenario - * instance for the specified operation ? - */ - List results = new ArrayList(5); - - // currentScenarioInstances needs to be protected for concurrency. - synchronized (context.getScenarioInstances()) - { - for(Scenario scenario : scenarios) - { - ScenarioInstance instance = scenario.createInstance(context, operation); - if(instance != null) - { - context.getScenarioInstances().add(instance); - } - } - - /** - * For each active scenario. - */ - Iterator i = context.getScenarioInstances().iterator(); - - while(i.hasNext()) - { - - ScenarioInstance scenario = i.next(); - if(logger.isDebugEnabled()) - { - logger.debug("evaluating:" + scenario + " operation: " +operation ); - } - Command executor = scenario.evaluate(operation); - if(executor != null) - { - results.add(new ScenarioResult(scenario, executor)); - - } - if(scenario.isComplete()) - { - // That scenario is no longer active. - if(logger.isDebugEnabled()) - { - logger.debug("Scenario is complete:" + scenario); - } - i.remove(); - } - } - } // End of syncronized block - - // results contains the results of the evaluator - - Map executors = new HashMap(); - - // HOW to arbitrate between many scenario executors - // Idea : Scenarios have rankings. - for(ScenarioResult result : results) - { - executors.put(result.scenario.getRanking(), result); - } - - ScenarioResult ex = executors.get(Ranking.HIGH); - if (ex != null) - { - if(ex.scenario instanceof DependentInstance) - { - DependentInstance di = (DependentInstance)ex.scenario; - for(ScenarioResult looser : results) - { - if(ex != looser) - { - Command c = di.win(results, ex.command); - logger.debug("returning merged high priority executor"); - - return c; - } - } - } - - logger.debug("returning high priority executor"); - return ex.command; - } - ex = executors.get(Ranking.MEDIUM); - if (ex != null) - { - logger.debug("returning medium priority executor"); - return ex.command; - } - ex = executors.get(Ranking.LOW); - if (ex != null) - { - logger.debug("returning low priority executor"); - return ex.command; - } - - return null; - } - - public void setScenarios(List scenarios) - { - this.scenarios = scenarios; - } - - public List getScenarios() - { - return scenarios; - } - - @Override - public EvaluatorContext createContext(MapsessionState) - { - EvaluatorContextImpl impl = new EvaluatorContextImpl(sessionState); - - return impl; - } - - @Override - public void notifyRename(EvaluatorContext context, Operation operation, - Command command) - { - // currentScenarioInstances needs to be protected for concurrency. - synchronized (context.getScenarioInstances()) - { - /** - * For each active scenario. - */ - Iterator i = context.getScenarioInstances().iterator(); - - while(i.hasNext()) - { - ScenarioInstance scenario = i.next(); - if(scenario instanceof ScenarioInstanceRenameAware) - { - ScenarioInstanceRenameAware awareScenario = (ScenarioInstanceRenameAware)scenario; - awareScenario.notifyRename(operation, command); - } - - } - } - - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The Rule Evaluator evaluates the operation and returns + * details of the commands to implement those operations. + *

+ * It is configured with a list of scenarios which act as factories for scenario instances. + */ +public class RuleEvaluatorImpl implements RuleEvaluator +{ + private static Log logger = LogFactory.getLog(RuleEvaluatorImpl.class); + + /** + * The evaluator context, one for each folder + */ + private class EvaluatorContextImpl implements EvaluatorContext + { + MapsessionState; + + EvaluatorContextImpl (MapsessionState) + { + this.sessionState = sessionState; + } + /** + * Current instances of scenarios + */ + private List currentScenarioInstances = new ArrayList(); + + @Override + public List getScenarioInstances() + { + return currentScenarioInstances; + } + + @Override + public Map getSessionState() + { + return sessionState; + } + } + + public void init() + { + PropertyCheck.mandatory(this, "scenarios", scenarios); + } + + /** + * The scenarios contained within this RuleEvaluator + */ + private List scenarios; + + /** + * Evaluate the scenarios against the current operation + * @param operation the operation to be evaluated + * @return the command to execute that operation + */ + public Command evaluate(EvaluatorContext context, Operation operation) + { + if(logger.isDebugEnabled()) + { + logger.debug("evaluate:" + operation); + } + + /** + * For each scenario, do we need to create a new scenario + * instance for the specified operation ? + */ + List results = new ArrayList(5); + + // currentScenarioInstances needs to be protected for concurrency. + synchronized (context.getScenarioInstances()) + { + for(Scenario scenario : scenarios) + { + ScenarioInstance instance = scenario.createInstance(context, operation); + if(instance != null) + { + context.getScenarioInstances().add(instance); + } + } + + /** + * For each active scenario. + */ + Iterator i = context.getScenarioInstances().iterator(); + + while(i.hasNext()) + { + + ScenarioInstance scenario = i.next(); + if(logger.isDebugEnabled()) + { + logger.debug("evaluating:" + scenario + " operation: " +operation ); + } + Command executor = scenario.evaluate(operation); + if(executor != null) + { + results.add(new ScenarioResult(scenario, executor)); + + } + if(scenario.isComplete()) + { + // That scenario is no longer active. + if(logger.isDebugEnabled()) + { + logger.debug("Scenario is complete:" + scenario); + } + i.remove(); + } + } + } // End of syncronized block + + // results contains the results of the evaluator + + Map executors = new HashMap(); + + // HOW to arbitrate between many scenario executors + // Idea : Scenarios have rankings. + for(ScenarioResult result : results) + { + executors.put(result.scenario.getRanking(), result); + } + + ScenarioResult ex = executors.get(Ranking.HIGH); + if (ex != null) + { + if(ex.scenario instanceof DependentInstance) + { + DependentInstance di = (DependentInstance)ex.scenario; + for(ScenarioResult looser : results) + { + if(ex != looser) + { + Command c = di.win(results, ex.command); + logger.debug("returning merged high priority executor"); + + return c; + } + } + } + + logger.debug("returning high priority executor"); + return ex.command; + } + ex = executors.get(Ranking.MEDIUM); + if (ex != null) + { + logger.debug("returning medium priority executor"); + return ex.command; + } + ex = executors.get(Ranking.LOW); + if (ex != null) + { + logger.debug("returning low priority executor"); + return ex.command; + } + + return null; + } + + public void setScenarios(List scenarios) + { + this.scenarios = scenarios; + } + + public List getScenarios() + { + return scenarios; + } + + @Override + public EvaluatorContext createContext(MapsessionState) + { + EvaluatorContextImpl impl = new EvaluatorContextImpl(sessionState); + + return impl; + } + + @Override + public void notifyRename(EvaluatorContext context, Operation operation, + Command command) + { + // currentScenarioInstances needs to be protected for concurrency. + synchronized (context.getScenarioInstances()) + { + /** + * For each active scenario. + */ + Iterator i = context.getScenarioInstances().iterator(); + + while(i.hasNext()) + { + ScenarioInstance scenario = i.next(); + if(scenario instanceof ScenarioInstanceRenameAware) + { + ScenarioInstanceRenameAware awareScenario = (ScenarioInstanceRenameAware)scenario; + awareScenario.notifyRename(operation, command); + } + + } + } + + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/Scenario.java b/source/java/org/alfresco/filesys/repo/rules/Scenario.java index ed916d4f96..2fe87a20da 100644 --- a/source/java/org/alfresco/filesys/repo/rules/Scenario.java +++ b/source/java/org/alfresco/filesys/repo/rules/Scenario.java @@ -1,23 +1,23 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.List; - -/** - * A scenario is a factory for scenario instances. - * - */ -public interface Scenario -{ - /** - * Create a new ScenarioInstance - *

- * If the scenario is interested in the specified operation then - * return a new scenario instance. - * @param ctx EvaluatorContext. - * @param operation the operation to be performed - * @return the scenario instance or null if a new instance is not required. - */ - ScenarioInstance createInstance(EvaluatorContext ctx, Operation operation); - - -} +package org.alfresco.filesys.repo.rules; + +import java.util.List; + +/** + * A scenario is a factory for scenario instances. + * + */ +public interface Scenario +{ + /** + * Create a new ScenarioInstance + *

+ * If the scenario is interested in the specified operation then + * return a new scenario instance. + * @param ctx EvaluatorContext. + * @param operation the operation to be performed + * @return the scenario instance or null if a new instance is not required. + */ + ScenarioInstance createInstance(EvaluatorContext ctx, Operation operation); + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java index d7614972e3..e9d056afa1 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java @@ -1,97 +1,97 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * A shuffle - * - * a) New file created. - * b) Existing file moved out of the way - * c) New file moved into place. - * d) Old file deleted. - */ -public class ScenarioCreateShuffle implements Scenario -{ - private static Log logger = LogFactory.getLog(ScenarioCreateShuffle.class); - - /** - * The regex pattern of a create that will trigger a new instance of - * the scenario. - */ - private Pattern pattern; - private String strPattern; - - - private long timeout = 30000; - - private Ranking ranking = Ranking.HIGH; - - @Override - public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) - { - /** - * This scenario is triggered by a create of a file matching - * the pattern - */ - if(operation instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)operation; - - Matcher m = pattern.matcher(c.getName()); - if(m.matches()) - { - if(logger.isDebugEnabled()) - { - logger.debug("New Scenario Create Shuffle Instance pattern:" + strPattern); - } - - ScenarioCreateShuffleInstance instance = new ScenarioCreateShuffleInstance() ; - instance.setTimeout(timeout); - instance.setRanking(ranking); - return instance; - } - } - - // No not interested. - return null; - - } - - public void setPattern(String pattern) - { - this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); - this.strPattern = pattern; - } - - public String getPattern() - { - return this.strPattern; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public Ranking getRanking() - { - return ranking; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A shuffle + * + * a) New file created. + * b) Existing file moved out of the way + * c) New file moved into place. + * d) Old file deleted. + */ +public class ScenarioCreateShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioCreateShuffle.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + + private long timeout = 30000; + + private Ranking ranking = Ranking.HIGH; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * This scenario is triggered by a create of a file matching + * the pattern + */ + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + + Matcher m = pattern.matcher(c.getName()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Create Shuffle Instance pattern:" + strPattern); + } + + ScenarioCreateShuffleInstance instance = new ScenarioCreateShuffleInstance() ; + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return this.strPattern; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java index 640b49ecf8..9781d84800 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java @@ -1,273 +1,273 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; -import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.alfresco.jlan.server.filesys.FileName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This is an instance of a "classic shuffle" triggered by a create of a - * file matching a specified pattern. - *

- * a) New file created. Typically with an obscure name. - * b) Existing file moved out of the way - * c) New file moved into place. - * d) Old file deleted. - * - *

- * If this filter is active then this is what happens. - * a) New file created. New file created (X). - * b) Existing file moved out of the way (Y to Z). Raname tracked. - * c) New file moved into place (X to Y). Scenario kicks in to change commands. - * d) Old file deleted. - */ -public class ScenarioCreateShuffleInstance implements ScenarioInstance -{ - private static Log logger = LogFactory.getLog(ScenarioCreateShuffleInstance.class); - - enum InternalState - { - NONE, - RENAME, - DELETE - } - - InternalState internalState = InternalState.NONE; - - private Date startTime = new Date(); - - private String createName; - private String move1; - private String move2; - private Ranking ranking; - - /** - * Timeout in ms. Default 30 seconds. - */ - private long timeout = 60000; - - private boolean isComplete; - - /** - * Keep track of re-names - */ - private Maprenames = new HashMap(); - - /** - * Evaluate the next operation - * @param operation Operation - */ - public Command evaluate(Operation operation) - { - - /** - * Anti-pattern : timeout - */ - Date now = new Date(); - if(now.getTime() > startTime.getTime() + getTimeout()) - { - if(logger.isDebugEnabled()) - { - logger.debug("Instance timed out createName:" + createName); - isComplete = true; - return null; - } - } - - /** - * Anti-pattern for all states - delete the file we are - * shuffling - */ - if(createName != null) - { - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation d = (DeleteFileOperation)operation; - if(d.getName().equals(createName)) - { - if(logger.isDebugEnabled()) - { - logger.debug("Anti-pattern : Shuffle file deleted createName:" + createName); - } - isComplete = true; - return null; - } - } - } - - switch (internalState) - { - case NONE: - // Looking for a create transition - if(operation instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)operation; - this.createName = c.getName(); - if(logger.isDebugEnabled()) - { - logger.debug("entering RENAME state: " + createName); - } - internalState = InternalState.RENAME; - return null; - } - else - { - // anything else bomb out - if(logger.isDebugEnabled()) - { - logger.debug("State error, expected a CREATE"); - } - isComplete = true; - } - break; - - case RENAME: - - /** - * Looking for two renames X(createName) to Y(middle) to Z(end) - */ - if(operation instanceof RenameFileOperation) - { - if(logger.isDebugEnabled()) - { - logger.debug("Tracking rename: " + operation); - } - RenameFileOperation r = (RenameFileOperation)operation; - renames.put(r.getFrom(), r.getTo()); - - // Now see if this rename makes a pair. - String middle = renames.get(createName); - if(middle != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("Got second rename" ); - } - - String end = renames.get(middle); - - if(end != null) - { - if(logger.isDebugEnabled()) - { - logger.debug("Got two renames " ); - } - this.move1 = middle; - this.move2 = end; - - - if(logger.isDebugEnabled()) - { - logger.debug("entering DELETE state"); - } - - internalState = InternalState.DELETE; - - /** - * This shuffle reverses the rename out of the way and then copies the - * content only. Finally it moves the temp file into place for the subsequent - * delete. - * a) Rename Z to Y (Reverse previous move) - * b) Copy Content from X to Y - * c) Rename X to Z (move temp file out to old location) - */ - if(logger.isDebugEnabled()) - { - logger.debug("Go and shuffle! createName:" + createName + " move1 " + move1 + " move2 " + move2); - } - - String[] paths = FileName.splitPath(r.getFromPath()); - String oldFolder = paths[0]; - // String oldFile = paths[1]; - - ArrayList commands = new ArrayList(); - RenameFileCommand r1 = new RenameFileCommand(end, middle, r.getRootNodeRef(), oldFolder + "\\" + end, oldFolder + "\\" + middle); - CopyContentCommand copyContent = new CopyContentCommand(createName, move1, r.getRootNodeRef(), oldFolder + "\\" + createName, oldFolder + "\\" + middle); - RenameFileCommand r2 = new RenameFileCommand(createName, end, r.getRootNodeRef(), oldFolder + "\\" + createName, oldFolder + "\\" + end); - - commands.add(r1); - commands.add(copyContent); - commands.add(r2); - - return new CompoundCommand(commands); - } - } - } - - break; - - case DELETE: - - /** - * Looking for a delete of the destination - */ - - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation d = (DeleteFileOperation)operation; - if(d.getName().equals(move2)) - { - if(logger.isDebugEnabled()) - { - logger.debug("Scenario complete createName:" + createName); - } - isComplete = true; - } - } - - /** - * Todo consider create shuffle with backup file which will never - * calls delete - do we need to pattern match on "Backup*". - * At the moment the delete state does nothing - hence - * we can simply set complete here for all situations. - */ - isComplete = true; - - break; - } - - return null; - } - - @Override - public boolean isComplete() - { - return isComplete; - } - - @Override - public Ranking getRanking() - { - return ranking; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public String toString() - { - return "ScenarioShuffleInstance: createName:" + createName; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This is an instance of a "classic shuffle" triggered by a create of a + * file matching a specified pattern. + *

+ * a) New file created. Typically with an obscure name. + * b) Existing file moved out of the way + * c) New file moved into place. + * d) Old file deleted. + * + *

+ * If this filter is active then this is what happens. + * a) New file created. New file created (X). + * b) Existing file moved out of the way (Y to Z). Raname tracked. + * c) New file moved into place (X to Y). Scenario kicks in to change commands. + * d) Old file deleted. + */ +public class ScenarioCreateShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioCreateShuffleInstance.class); + + enum InternalState + { + NONE, + RENAME, + DELETE + } + + InternalState internalState = InternalState.NONE; + + private Date startTime = new Date(); + + private String createName; + private String move1; + private String move2; + private Ranking ranking; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 60000; + + private boolean isComplete; + + /** + * Keep track of re-names + */ + private Maprenames = new HashMap(); + + /** + * Evaluate the next operation + * @param operation Operation + */ + public Command evaluate(Operation operation) + { + + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out createName:" + createName); + isComplete = true; + return null; + } + } + + /** + * Anti-pattern for all states - delete the file we are + * shuffling + */ + if(createName != null) + { + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(d.getName().equals(createName)) + { + if(logger.isDebugEnabled()) + { + logger.debug("Anti-pattern : Shuffle file deleted createName:" + createName); + } + isComplete = true; + return null; + } + } + } + + switch (internalState) + { + case NONE: + // Looking for a create transition + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + this.createName = c.getName(); + if(logger.isDebugEnabled()) + { + logger.debug("entering RENAME state: " + createName); + } + internalState = InternalState.RENAME; + return null; + } + else + { + // anything else bomb out + if(logger.isDebugEnabled()) + { + logger.debug("State error, expected a CREATE"); + } + isComplete = true; + } + break; + + case RENAME: + + /** + * Looking for two renames X(createName) to Y(middle) to Z(end) + */ + if(operation instanceof RenameFileOperation) + { + if(logger.isDebugEnabled()) + { + logger.debug("Tracking rename: " + operation); + } + RenameFileOperation r = (RenameFileOperation)operation; + renames.put(r.getFrom(), r.getTo()); + + // Now see if this rename makes a pair. + String middle = renames.get(createName); + if(middle != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("Got second rename" ); + } + + String end = renames.get(middle); + + if(end != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("Got two renames " ); + } + this.move1 = middle; + this.move2 = end; + + + if(logger.isDebugEnabled()) + { + logger.debug("entering DELETE state"); + } + + internalState = InternalState.DELETE; + + /** + * This shuffle reverses the rename out of the way and then copies the + * content only. Finally it moves the temp file into place for the subsequent + * delete. + * a) Rename Z to Y (Reverse previous move) + * b) Copy Content from X to Y + * c) Rename X to Z (move temp file out to old location) + */ + if(logger.isDebugEnabled()) + { + logger.debug("Go and shuffle! createName:" + createName + " move1 " + move1 + " move2 " + move2); + } + + String[] paths = FileName.splitPath(r.getFromPath()); + String oldFolder = paths[0]; + // String oldFile = paths[1]; + + ArrayList commands = new ArrayList(); + RenameFileCommand r1 = new RenameFileCommand(end, middle, r.getRootNodeRef(), oldFolder + "\\" + end, oldFolder + "\\" + middle); + CopyContentCommand copyContent = new CopyContentCommand(createName, move1, r.getRootNodeRef(), oldFolder + "\\" + createName, oldFolder + "\\" + middle); + RenameFileCommand r2 = new RenameFileCommand(createName, end, r.getRootNodeRef(), oldFolder + "\\" + createName, oldFolder + "\\" + end); + + commands.add(r1); + commands.add(copyContent); + commands.add(r2); + + return new CompoundCommand(commands); + } + } + } + + break; + + case DELETE: + + /** + * Looking for a delete of the destination + */ + + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(d.getName().equals(move2)) + { + if(logger.isDebugEnabled()) + { + logger.debug("Scenario complete createName:" + createName); + } + isComplete = true; + } + } + + /** + * Todo consider create shuffle with backup file which will never + * calls delete - do we need to pattern match on "Backup*". + * At the moment the delete state does nothing - hence + * we can simply set complete here for all situations. + */ + isComplete = true; + + break; + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioShuffleInstance: createName:" + createName; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRenameOrCreate.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRenameOrCreate.java index 0d1dd95f46..accc678038 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRenameOrCreate.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRenameOrCreate.java @@ -1,128 +1,128 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * The DeleteOnClose rename shuffle is a delete on close of a file resulting in a file being deleted followed by a - * rename or a create - * - * First case of this is Mac Mountain Lion Preview application. - * and then a new copy of the file put into place. - * - * a) DeleteOnClose fileA - * b) Close fileA - * c) Rename whatever fileA - * - * Second case First case of this is Mac Drag and drop. - * and then a new copy of the file put into place. - * - * a) Delete fileA - * b) Close fileA - * c) Create fileA - * - * Third case Gedit. - * - * a) Delete fileA - * b) Rename .goutputstream fileA - * - */ -public class ScenarioDeleteRenameOrCreate implements Scenario -{ - private static Log logger = LogFactory.getLog(ScenarioDeleteRenameOrCreate.class); - - /** - * The regex pattern of a close that will trigger a new instance of - * the scenario. - */ - private Pattern pattern; - private String strPattern; - - private long timeout = 30000; - - @Override - public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) - { - /** - * This scenario is triggered by a rename of a file matching - * the pattern - */ - if(operation instanceof CloseFileOperation) - { - CloseFileOperation c = (CloseFileOperation)operation; - - Matcher m = pattern.matcher(c.getName()); - if(m.matches() && c.getNetworkFile().hasDeleteOnClose()) - { - if(logger.isDebugEnabled()) - { - logger.debug("New Scenario ScenarioDeleteRenameOrCreate strPattern:" + pattern); - } - ScenarioDeleteRenameOrCreateInstance instance = new ScenarioDeleteRenameOrCreateInstance(); - instance.setTimeout(timeout); - instance.setRanking(ranking); - return instance; - } - } - - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation c = (DeleteFileOperation)operation; - - Matcher m = pattern.matcher(c.getName()); - if(m.matches()) - { - if(logger.isDebugEnabled()) - { - logger.debug("New Scenario ScenarioDeleteRenameOrCreate strPattern:" + pattern); - } - ScenarioDeleteRenameOrCreateInstance instance = new ScenarioDeleteRenameOrCreateInstance(); - instance.setTimeout(timeout); - instance.setRanking(ranking); - return instance; - } - } - - // No not interested. - return null; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setPattern(String pattern) - { - this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); - this.strPattern = pattern; - } - - public String getPattern() - { - return strPattern; - } - - private Ranking ranking = Ranking.HIGH; - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public Ranking getRanking() - { - return ranking; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The DeleteOnClose rename shuffle is a delete on close of a file resulting in a file being deleted followed by a + * rename or a create + * + * First case of this is Mac Mountain Lion Preview application. + * and then a new copy of the file put into place. + * + * a) DeleteOnClose fileA + * b) Close fileA + * c) Rename whatever fileA + * + * Second case First case of this is Mac Drag and drop. + * and then a new copy of the file put into place. + * + * a) Delete fileA + * b) Close fileA + * c) Create fileA + * + * Third case Gedit. + * + * a) Delete fileA + * b) Rename .goutputstream fileA + * + */ +public class ScenarioDeleteRenameOrCreate implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioDeleteRenameOrCreate.class); + + /** + * The regex pattern of a close that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + private long timeout = 30000; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * This scenario is triggered by a rename of a file matching + * the pattern + */ + if(operation instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)operation; + + Matcher m = pattern.matcher(c.getName()); + if(m.matches() && c.getNetworkFile().hasDeleteOnClose()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario ScenarioDeleteRenameOrCreate strPattern:" + pattern); + } + ScenarioDeleteRenameOrCreateInstance instance = new ScenarioDeleteRenameOrCreateInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation c = (DeleteFileOperation)operation; + + Matcher m = pattern.matcher(c.getName()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario ScenarioDeleteRenameOrCreate strPattern:" + pattern); + } + ScenarioDeleteRenameOrCreateInstance instance = new ScenarioDeleteRenameOrCreateInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return strPattern; + } + + private Ranking ranking = Ranking.HIGH; + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRenameOrCreateInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRenameOrCreateInstance.java index 8daf869568..313dd19d16 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRenameOrCreateInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRenameOrCreateInstance.java @@ -1,270 +1,270 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.ArrayList; -import java.util.Date; - -import org.alfresco.filesys.repo.ResultCallback; -import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; -import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; -import org.alfresco.filesys.repo.rules.commands.RestoreFileCommand; -import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * First case of this is Mac Mountain Lion Preview application. - * and then a new copy of the file put into place. - * - * a) DeleteOnClose fileA - * b) Close fileA - * c) Rename whatever fileA - * - * a) Delete fileA - * b)Rename File~ to File - * - * This rule will kick in and ... - * - */ -class ScenarioDeleteRenameOrCreateInstance implements ScenarioInstance -{ - private static Log logger = LogFactory.getLog(ScenarioDeleteRenameOrCreateInstance.class); - - private Date startTime = new Date(); - - /** - * Timeout in ms. Default 30 seconds. - */ - private long timeout = 30000; - - private boolean isComplete = false; - - private Ranking ranking = Ranking.HIGH; - - private NodeRef originalNodeRef = null; - - enum InternalState - { - NONE, - INITIALISED - } ; - - InternalState state = InternalState.NONE; - - String name; - - /** - * Evaluate the next operation - * @param operation - */ - public Command evaluate(Operation operation) - { - /** - * Anti-pattern : timeout - */ - Date now = new Date(); - if(now.getTime() > startTime.getTime() + getTimeout()) - { - if(logger.isDebugEnabled()) - { - logger.debug("Instance timed out"); - } - isComplete = true; - return null; - } - - switch (state) - { - case NONE: - if(operation instanceof CloseFileOperation) - { - CloseFileOperation c = (CloseFileOperation)operation; - this.name = c.getName(); - logger.debug("New scenario initialised for file " + name); - state = InternalState.INITIALISED; - - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - commands.add(new CloseFileCommand(c.getName(), c.getNetworkFile(), c.getRootNodeRef(), c.getPath())); - postCommitCommands.add(newDeleteFileCallbackCommand()); - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - } - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation c = (DeleteFileOperation)operation; - this.name = c.getName(); - logger.debug("New scenario initialised for file " + name); - state = InternalState.INITIALISED; - - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - commands.add(new DeleteFileCommand(c.getName(), c.getRootNodeRef(), c.getPath())); - postCommitCommands.add(newDeleteFileCallbackCommand()); - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - } - break; - - - case INITIALISED: - - if(operation instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)operation; - - if(c.getName().equalsIgnoreCase(name)) - { - isComplete = true; - if(originalNodeRef != null) - { - logger.debug("Delete create shuffle fire!:" + this); - return new RestoreFileCommand(c.getName(), c.getRootNodeRef(), c.getPath(), c.getAllocationSize(), originalNodeRef); - } - return null; - } - } - - if(operation instanceof RenameFileOperation) - { - RenameFileOperation r = (RenameFileOperation)operation; - if(name.equals(r.getTo())) - { - logger.debug("Delete Rename shuffle - fire!"); - - if(originalNodeRef != null) - { - /** - * Shuffle is as follows - * a) Copy content from File to File~ - * b) Delete File - * c) Rename File~ to File - */ - ArrayList commands = new ArrayList(); - RestoreFileCommand r1 = new RestoreFileCommand(r.getTo(), r.getRootNodeRef(), r.getToPath(), 0, originalNodeRef); - CopyContentCommand copyContent = new CopyContentCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); - DeleteFileCommand d1 = new DeleteFileCommand(r.getFrom(), r.getRootNodeRef(), r.getFromPath()); - - commands.add(r1); - commands.add(copyContent); - commands.add(d1); - logger.debug("Scenario complete"); - isComplete = true; - return new CompoundCommand(commands); - } - else - { - logger.debug("Scenario complete"); - isComplete = true; - return null; - } - } - } - - - if(operation instanceof MoveFileOperation) - { - MoveFileOperation r = (MoveFileOperation)operation; - if(name.equals(r.getTo())) - { - logger.debug("Delete Rename shuffle - fire!"); - - if(originalNodeRef != null) - { - /** - * Shuffle is as follows - * a) Copy content from File to File~ - * b) Delete File - * c) Rename File~ to File - */ - ArrayList commands = new ArrayList(); - RestoreFileCommand r1 = new RestoreFileCommand(r.getTo(), r.getRootNodeRef(), r.getToPath(), 0, originalNodeRef); - CopyContentCommand copyContent = new CopyContentCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); - DeleteFileCommand d1 = new DeleteFileCommand(r.getFrom(), r.getRootNodeRef(), r.getFromPath()); - - commands.add(r1); - commands.add(copyContent); - commands.add(d1); - logger.debug("Scenario complete"); - isComplete = true; - return new CompoundCommand(commands); - } - else - { - logger.debug("Scenario complete"); - isComplete = true; - return null; - } - } - } - } - - return null; - } - - @Override - public boolean isComplete() - { - return isComplete; - } - - public String toString() - { - return "ScenarioDeleteRenameOrCreate name:" + name ; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - @Override - public Ranking getRanking() - { - return ranking; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - /** - * Called for delete file. - */ - private ResultCallback newDeleteFileCallbackCommand() - { - return new ResultCallback() - { - @Override - public void execute(Object result) - { - if(result instanceof NodeRef) - { - logger.debug("got node ref of deleted node"); - originalNodeRef = (NodeRef)result; - } - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } - }; - } - -} - +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.Date; + +import org.alfresco.filesys.repo.ResultCallback; +import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.RestoreFileCommand; +import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * First case of this is Mac Mountain Lion Preview application. + * and then a new copy of the file put into place. + * + * a) DeleteOnClose fileA + * b) Close fileA + * c) Rename whatever fileA + * + * a) Delete fileA + * b)Rename File~ to File + * + * This rule will kick in and ... + * + */ +class ScenarioDeleteRenameOrCreateInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioDeleteRenameOrCreateInstance.class); + + private Date startTime = new Date(); + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete = false; + + private Ranking ranking = Ranking.HIGH; + + private NodeRef originalNodeRef = null; + + enum InternalState + { + NONE, + INITIALISED + } ; + + InternalState state = InternalState.NONE; + + String name; + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out"); + } + isComplete = true; + return null; + } + + switch (state) + { + case NONE: + if(operation instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)operation; + this.name = c.getName(); + logger.debug("New scenario initialised for file " + name); + state = InternalState.INITIALISED; + + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + commands.add(new CloseFileCommand(c.getName(), c.getNetworkFile(), c.getRootNodeRef(), c.getPath())); + postCommitCommands.add(newDeleteFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation c = (DeleteFileOperation)operation; + this.name = c.getName(); + logger.debug("New scenario initialised for file " + name); + state = InternalState.INITIALISED; + + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + commands.add(new DeleteFileCommand(c.getName(), c.getRootNodeRef(), c.getPath())); + postCommitCommands.add(newDeleteFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + break; + + + case INITIALISED: + + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + + if(c.getName().equalsIgnoreCase(name)) + { + isComplete = true; + if(originalNodeRef != null) + { + logger.debug("Delete create shuffle fire!:" + this); + return new RestoreFileCommand(c.getName(), c.getRootNodeRef(), c.getPath(), c.getAllocationSize(), originalNodeRef); + } + return null; + } + } + + if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + if(name.equals(r.getTo())) + { + logger.debug("Delete Rename shuffle - fire!"); + + if(originalNodeRef != null) + { + /** + * Shuffle is as follows + * a) Copy content from File to File~ + * b) Delete File + * c) Rename File~ to File + */ + ArrayList commands = new ArrayList(); + RestoreFileCommand r1 = new RestoreFileCommand(r.getTo(), r.getRootNodeRef(), r.getToPath(), 0, originalNodeRef); + CopyContentCommand copyContent = new CopyContentCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); + DeleteFileCommand d1 = new DeleteFileCommand(r.getFrom(), r.getRootNodeRef(), r.getFromPath()); + + commands.add(r1); + commands.add(copyContent); + commands.add(d1); + logger.debug("Scenario complete"); + isComplete = true; + return new CompoundCommand(commands); + } + else + { + logger.debug("Scenario complete"); + isComplete = true; + return null; + } + } + } + + + if(operation instanceof MoveFileOperation) + { + MoveFileOperation r = (MoveFileOperation)operation; + if(name.equals(r.getTo())) + { + logger.debug("Delete Rename shuffle - fire!"); + + if(originalNodeRef != null) + { + /** + * Shuffle is as follows + * a) Copy content from File to File~ + * b) Delete File + * c) Rename File~ to File + */ + ArrayList commands = new ArrayList(); + RestoreFileCommand r1 = new RestoreFileCommand(r.getTo(), r.getRootNodeRef(), r.getToPath(), 0, originalNodeRef); + CopyContentCommand copyContent = new CopyContentCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); + DeleteFileCommand d1 = new DeleteFileCommand(r.getFrom(), r.getRootNodeRef(), r.getFromPath()); + + commands.add(r1); + commands.add(copyContent); + commands.add(d1); + logger.debug("Scenario complete"); + isComplete = true; + return new CompoundCommand(commands); + } + else + { + logger.debug("Scenario complete"); + isComplete = true; + return null; + } + } + } + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + public String toString() + { + return "ScenarioDeleteRenameOrCreate name:" + name ; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + /** + * Called for delete file. + */ + private ResultCallback newDeleteFileCallbackCommand() + { + return new ResultCallback() + { + @Override + public void execute(Object result) + { + if(result instanceof NodeRef) + { + logger.debug("got node ref of deleted node"); + originalNodeRef = (NodeRef)result; + } + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } + }; + } + +} + diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRestore.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRestore.java index cafe20a877..087c2e1194 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRestore.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRestore.java @@ -1,94 +1,94 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * A delete restore shuffle. - * - * Files are deleted then re-created. - */ -public class ScenarioDeleteRestore implements Scenario -{ - private static Log logger = LogFactory.getLog(ScenarioDeleteRestore.class); - - /** - * The regex pattern of a create that will trigger a new instance of - * the scenario. - */ - private Pattern pattern; - private String strPattern; - - private long timeout = 30000; - - private Ranking ranking = Ranking.HIGH; - - @Override - public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) - { - /** - * This scenario is triggered by a delete of a file matching - * the pattern - */ - - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation c = (DeleteFileOperation)operation; - - Matcher m = pattern.matcher(c.getName()); - if(m.matches()) - { - if(logger.isDebugEnabled()) - { - logger.debug("New Scenario Delete Restore Shuffle Instance:" + c.getName()); - } - - ScenarioDeleteRestoreInstance instance = new ScenarioDeleteRestoreInstance() ; - instance.setTimeout(timeout); - instance.setRanking(ranking); - return instance; - } - } - - // No not interested. - return null; - - } - - public void setPattern(String pattern) - { - this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); - this.strPattern = pattern; - } - - public String getPattern() - { - return this.strPattern; - } - - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public Ranking getRanking() - { - return ranking; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A delete restore shuffle. + * + * Files are deleted then re-created. + */ +public class ScenarioDeleteRestore implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioDeleteRestore.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + private long timeout = 30000; + + private Ranking ranking = Ranking.HIGH; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * This scenario is triggered by a delete of a file matching + * the pattern + */ + + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation c = (DeleteFileOperation)operation; + + Matcher m = pattern.matcher(c.getName()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Delete Restore Shuffle Instance:" + c.getName()); + } + + ScenarioDeleteRestoreInstance instance = new ScenarioDeleteRestoreInstance() ; + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return this.strPattern; + } + + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRestoreInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRestoreInstance.java index 39fa1bab42..3240b25a75 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRestoreInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteRestoreInstance.java @@ -1,214 +1,214 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - - -import org.alfresco.filesys.repo.ResultCallback; -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; -import org.alfresco.filesys.repo.rules.commands.RestoreFileCommand; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This is an instance of a "delete restore shuffle" - * - * Triggered by a delete of a file followed by a recreate of that same file. - * - *

First implemented for MacOS Lion Finder. - * - *

- * Sequence of operations. - * a) File Deleted - * b) File Created. - */ -public class ScenarioDeleteRestoreInstance implements ScenarioInstance -{ - private static Log logger = LogFactory.getLog(ScenarioDeleteRestoreInstance.class); - - enum InternalState - { - NONE, - LOOKING_FOR_CREATE - } - - InternalState internalState = InternalState.NONE; - - private Date startTime = new Date(); - - private String deleteName; - - private NodeRef originalNodeRef; - - private Ranking ranking; - - /** - * Timeout in ms. Default 30 seconds. - */ - private long timeout = 60000; - - private boolean isComplete; - - /** - * Keep track of deletes that we substitute with a rename - * could be more than one if scenarios overlap - * - * From, TempFileName - */ - private Map deletes = new HashMap(); - - /** - * Evaluate the next operation - * @param operation Operation - */ - public Command evaluate(Operation operation) - { - - /** - * Anti-pattern : timeout - */ - Date now = new Date(); - if(now.getTime() > startTime.getTime() + getTimeout()) - { - if(logger.isDebugEnabled()) - { - logger.debug("Instance timed out deleteName:" + deleteName); - isComplete = true; - return null; - } - } - - switch (internalState) - { - - case NONE: - - /** - * Looking for file being deleted deleted - - */ - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation d = (DeleteFileOperation)operation; - - deleteName = d.getName(); - - if(logger.isDebugEnabled()) - { - logger.debug("entering LOOKING_FOR_CREATE state: " + deleteName); - } - internalState = InternalState.LOOKING_FOR_CREATE; - - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - commands.add(new DeleteFileCommand(d.getName(), d.getRootNodeRef(), d.getPath())); - postCommitCommands.add(newDeleteFileCallbackCommand()); - - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - - } - else - { - // anything else bomb out - if(logger.isDebugEnabled()) - { - logger.debug("State error, expected a DELETE"); - } - isComplete = true; - } - break; - - case LOOKING_FOR_CREATE: - - /** - * Looking for a create operation of the deleted file - */ - if(operation instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)operation; - - if(c.getName().equalsIgnoreCase(deleteName)) - { - isComplete = true; - if(originalNodeRef != null) - { - logger.debug("Scenario fires:" + this); - return new RestoreFileCommand(c.getName(), c.getRootNodeRef(), c.getPath(), c.getAllocationSize(), originalNodeRef); - } - - return null; - } - } - } - - return null; - } - - @Override - public boolean isComplete() - { - return isComplete; - } - - @Override - public Ranking getRanking() - { - return ranking; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public String toString() - { - return "ScenarioDeleteRestoreShuffleInstance:" + deleteName; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - /** - * Called for delete file. - */ - private ResultCallback newDeleteFileCallbackCommand() - { - return new ResultCallback() - { - @Override - public void execute(Object result) - { - if(result instanceof NodeRef) - { - logger.debug("got node ref of deleted node"); - originalNodeRef = (NodeRef)result; - } - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } - }; - } - - -} +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + + +import org.alfresco.filesys.repo.ResultCallback; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.RestoreFileCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This is an instance of a "delete restore shuffle" + * + * Triggered by a delete of a file followed by a recreate of that same file. + * + *

First implemented for MacOS Lion Finder. + * + *

+ * Sequence of operations. + * a) File Deleted + * b) File Created. + */ +public class ScenarioDeleteRestoreInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioDeleteRestoreInstance.class); + + enum InternalState + { + NONE, + LOOKING_FOR_CREATE + } + + InternalState internalState = InternalState.NONE; + + private Date startTime = new Date(); + + private String deleteName; + + private NodeRef originalNodeRef; + + private Ranking ranking; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 60000; + + private boolean isComplete; + + /** + * Keep track of deletes that we substitute with a rename + * could be more than one if scenarios overlap + * + * From, TempFileName + */ + private Map deletes = new HashMap(); + + /** + * Evaluate the next operation + * @param operation Operation + */ + public Command evaluate(Operation operation) + { + + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out deleteName:" + deleteName); + isComplete = true; + return null; + } + } + + switch (internalState) + { + + case NONE: + + /** + * Looking for file being deleted deleted + + */ + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + + deleteName = d.getName(); + + if(logger.isDebugEnabled()) + { + logger.debug("entering LOOKING_FOR_CREATE state: " + deleteName); + } + internalState = InternalState.LOOKING_FOR_CREATE; + + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + commands.add(new DeleteFileCommand(d.getName(), d.getRootNodeRef(), d.getPath())); + postCommitCommands.add(newDeleteFileCallbackCommand()); + + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + + } + else + { + // anything else bomb out + if(logger.isDebugEnabled()) + { + logger.debug("State error, expected a DELETE"); + } + isComplete = true; + } + break; + + case LOOKING_FOR_CREATE: + + /** + * Looking for a create operation of the deleted file + */ + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + + if(c.getName().equalsIgnoreCase(deleteName)) + { + isComplete = true; + if(originalNodeRef != null) + { + logger.debug("Scenario fires:" + this); + return new RestoreFileCommand(c.getName(), c.getRootNodeRef(), c.getPath(), c.getAllocationSize(), originalNodeRef); + } + + return null; + } + } + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioDeleteRestoreShuffleInstance:" + deleteName; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + /** + * Called for delete file. + */ + private ResultCallback newDeleteFileCallbackCommand() + { + return new ResultCallback() + { + @Override + public void execute(Object result) + { + if(result instanceof NodeRef) + { + logger.debug("got node ref of deleted node"); + originalNodeRef = (NodeRef)result; + } + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } + }; + } + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffle.java index df6db9ccea..1c42289fef 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffle.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffle.java @@ -1,138 +1,138 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * A double rename shuffle - * - * a) Existing file renamed out of the way. X.fm to X.backup.fm - * b) New file moved renamed into place. X.fm.C29 - * - * Scenario is triggered by the first rename matching a pattern. - */ -public class ScenarioDoubleRenameShuffle implements Scenario -{ - private static Log logger = LogFactory.getLog(ScenarioDoubleRenameShuffle.class); - - /** - * The regex pattern of a create that will trigger a new instance of - * the scenario. - */ - private Pattern pattern; - private String strPattern; - private Pattern interimPattern; - private String strInterimPattern; - private boolean deleteBackup; - private boolean moveAsSystem; - - private long timeout = 30000; - - private Ranking ranking = Ranking.HIGH; - - @Override - public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) - { - /** - * This scenario is triggered by a rename of a file matching - * the pattern - */ - if(operation instanceof RenameFileOperation) - { - RenameFileOperation r = (RenameFileOperation)operation; - - Matcher m = pattern.matcher(r.getTo()); - if(m.matches()) - { - if(logger.isDebugEnabled()) - { - logger.debug("New Scenario Double Rename Shuffle Instance strPattern:" + pattern + " matches" + r.getTo()); - } - ScenarioDoubleRenameShuffleInstance instance = new ScenarioDoubleRenameShuffleInstance(); - instance.setTimeout(timeout); - instance.setRanking(ranking); - instance.setDeleteBackup(deleteBackup); - instance.setMoveAsSystem(moveAsSystem); - if (interimPattern != null) - { - instance.setInterimPattern(interimPattern); - } - return instance; - } - } - - // No not interested. - return null; - - } - - public void setPattern(String pattern) - { - this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); - this.strPattern = pattern; - } - - public String getPattern() - { - return this.strPattern; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public Ranking getRanking() - { - return ranking; - } - - public void setDeleteBackup(boolean deleteBackup) - { - this.deleteBackup = deleteBackup; - } - - public boolean isDeleteBackup() - { - return deleteBackup; - } - - public boolean isMoveAsSystem() - { - return moveAsSystem; - } - - public void setMoveAsSystem(boolean retryAsSystem) - { - this.moveAsSystem = retryAsSystem; - } - - public void setInterimPattern(String intermediateMovePattern) - { - if (null != intermediateMovePattern) - { - this.interimPattern = Pattern.compile(intermediateMovePattern, Pattern.CASE_INSENSITIVE); - this.strInterimPattern = intermediateMovePattern; - } - } - - public String getInterimPattern() - { - return this.strInterimPattern; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A double rename shuffle + * + * a) Existing file renamed out of the way. X.fm to X.backup.fm + * b) New file moved renamed into place. X.fm.C29 + * + * Scenario is triggered by the first rename matching a pattern. + */ +public class ScenarioDoubleRenameShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioDoubleRenameShuffle.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + private Pattern interimPattern; + private String strInterimPattern; + private boolean deleteBackup; + private boolean moveAsSystem; + + private long timeout = 30000; + + private Ranking ranking = Ranking.HIGH; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * This scenario is triggered by a rename of a file matching + * the pattern + */ + if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + + Matcher m = pattern.matcher(r.getTo()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Double Rename Shuffle Instance strPattern:" + pattern + " matches" + r.getTo()); + } + ScenarioDoubleRenameShuffleInstance instance = new ScenarioDoubleRenameShuffleInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + instance.setDeleteBackup(deleteBackup); + instance.setMoveAsSystem(moveAsSystem); + if (interimPattern != null) + { + instance.setInterimPattern(interimPattern); + } + return instance; + } + } + + // No not interested. + return null; + + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return this.strPattern; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } + + public void setDeleteBackup(boolean deleteBackup) + { + this.deleteBackup = deleteBackup; + } + + public boolean isDeleteBackup() + { + return deleteBackup; + } + + public boolean isMoveAsSystem() + { + return moveAsSystem; + } + + public void setMoveAsSystem(boolean retryAsSystem) + { + this.moveAsSystem = retryAsSystem; + } + + public void setInterimPattern(String intermediateMovePattern) + { + if (null != intermediateMovePattern) + { + this.interimPattern = Pattern.compile(intermediateMovePattern, Pattern.CASE_INSENSITIVE); + this.strInterimPattern = intermediateMovePattern; + } + } + + public String getInterimPattern() + { + return this.strInterimPattern; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java index 42de538b17..b650ab680c 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java @@ -1,339 +1,339 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; -import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; -import org.alfresco.filesys.repo.rules.commands.MoveFileCommand; -import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; -import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.alfresco.jlan.server.filesys.FileName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This is an instance of a "double rename shuffle" triggered by rename of a file to a special pattern - * file matching a specified pattern. (*.backup.fm) - * - * a) Existing file moved out of the way. X.fm to X.backup.fm - * b) New file moved into place. X.fm.C29 X.fm - *

- * If this filter is active then this is what happens. - * a) Existing file moved out of the way (Y to Z). Raname tracked. - * b) New file moved into place (X to Y). - * - * Scenario kicks in to change commands. - */ -public class ScenarioDoubleRenameShuffleInstance implements ScenarioInstance -{ - private static Log logger = LogFactory.getLog(ScenarioDoubleRenameShuffleInstance.class); - - enum InternalState - { - NONE, - RENAME1, - RENAME2 - } - - InternalState internalState = InternalState.NONE; - - private Date startTime = new Date(); - - private String fileMiddle; - private String fileFrom; - private String fileEnd; - - private Ranking ranking; - private boolean deleteBackup; - private boolean moveAsSystem; - private Pattern interimPattern; - - /** - * Timeout in ms. Default 30 seconds. - */ - private long timeout = 30000; - - private boolean isComplete; - private String folderMiddle; - private String folderEnd; - - /** - * Keep track of re-names - */ - private Map renames = new HashMap(); - - /** - * Evaluate the next operation - * @param operation - */ - public Command evaluate(Operation operation) - { - - /** - * Anti-pattern : timeout - */ - Date now = new Date(); - if(now.getTime() > startTime.getTime() + getTimeout()) - { - if(logger.isDebugEnabled()) - { - logger.debug("Instance timed out"); - - } - isComplete = true; - return null; - } - - switch (internalState) - { - - case NONE: - - /** - * Looking for first rename Y(middle) to Z(end) - */ - if(operation instanceof RenameFileOperation) - { - if(logger.isDebugEnabled()) - { - logger.debug("Got first rename - tracking rename: " + operation); - } - RenameFileOperation r = (RenameFileOperation)operation; - fileMiddle = r.getFrom(); - fileEnd = r.getTo(); - - String[] paths = FileName.splitPath(r.getFromPath()); - folderMiddle = paths[0]; - - String[] paths2 = FileName.splitPath(r.getToPath()); - folderEnd = paths2[0]; - - internalState = InternalState.RENAME1; - - return new RenameFileCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); - } - else - { - // anything else bomb out - if(logger.isDebugEnabled()) - { - logger.debug("State error, expected a RENAME"); - } - isComplete = true; - } - - - case RENAME1: - - /** - * Looking for the second of two renames X(createName) to Y(middle) to Z(end) - */ - if(operation instanceof RenameFileOperation) - { - if(logger.isDebugEnabled()) - { - logger.debug("Tracking rename: " + operation); - } - RenameFileOperation r = (RenameFileOperation)operation; - - // Now see if this rename makes a pair - if(fileMiddle.equalsIgnoreCase(r.getTo())) - { - if(logger.isDebugEnabled()) - { - logger.debug("Got second rename" ); - } - - fileFrom = r.getFrom(); - - /** - * This shuffle reverses the rename out of the way and then copies the - * content only. Finally it moves the temp file into place for the subsequent - * delete. - * a) Rename Z to Y (Reverse previous move) - * b) Copy Content from X to Y - * c) Rename X to Z (move temp file out to old location) - */ - if(logger.isDebugEnabled()) - { - logger.debug("Go and shuffle! fromName:" + fileFrom + " middle: " + fileMiddle + " end: " + fileEnd); - } - - String[] paths = FileName.splitPath(r.getFromPath()); - String oldFolder = paths[0]; - - ArrayList commands = new ArrayList(); - - RenameFileCommand r1 = new RenameFileCommand(fileEnd, fileMiddle, r.getRootNodeRef(), oldFolder + "\\" + fileEnd, oldFolder + "\\" + fileMiddle); - commands.add(r1); - CopyContentCommand copyContent = new CopyContentCommand(fileFrom, fileMiddle, r.getRootNodeRef(), oldFolder + "\\" + fileFrom, oldFolder + "\\" + fileMiddle); - commands.add(copyContent); - if(deleteBackup) - { - logger.debug("deleteBackup option turned on"); - DeleteFileCommand d1 = new DeleteFileCommand(oldFolder, r.getRootNodeRef(), oldFolder + "\\" + fileFrom); - commands.add(d1); - } - else - { - RenameFileCommand r2 = new RenameFileCommand(fileFrom, fileEnd, r.getRootNodeRef(), oldFolder + "\\" + fileFrom, oldFolder + "\\" + fileEnd); - commands.add(r2); - } - - /** - * TODO - we may need to copy a new node for the backup and delete the temp node. - * It depends if we care about the contents of the Backup file. - */ - - isComplete = true; - return new CompoundCommand(commands); - } - } - - if(operation instanceof MoveFileOperation) - { - if(logger.isDebugEnabled()) - { - logger.info("Tracking rename: " + operation); - } - MoveFileOperation r = (MoveFileOperation)operation; - - // Now see if this rename makes a pair - if(fileMiddle.equalsIgnoreCase(r.getTo())) - { - if(logger.isDebugEnabled()) - { - logger.debug("Got second rename" ); - } - - fileFrom = r.getFrom(); - - /** - * This shuffle reverses the rename out of the way and then copies the - * content only. Finally it moves the temp file into place for the subsequent - * delete. - * a) Rename Z to Y (Reverse previous move) - * b) Copy Content from X to Y - * c) Rename X to Z (move temp file out to old location) - */ - if(logger.isDebugEnabled()) - { - logger.debug("Go and shuffle! fromName:" + fileFrom + " middle: " + fileMiddle + " end: " + fileEnd); - } - - String[] paths = FileName.splitPath(r.getFromPath()); - String oldFolder = paths[0]; - - ArrayList commands = new ArrayList(); - - RenameFileCommand r1 = new RenameFileCommand(fileEnd, fileMiddle, r.getRootNodeRef(), folderEnd + "\\" + fileEnd, folderMiddle + "\\" + fileMiddle); - commands.add(r1); - CopyContentCommand copyContent = new CopyContentCommand(fileFrom, fileMiddle, r.getRootNodeRef(), oldFolder + "\\" + fileFrom, folderMiddle + "\\" + fileMiddle); - commands.add(copyContent); - if(deleteBackup) - { - logger.debug("deleteBackup option turned on"); - DeleteFileCommand d1 = new DeleteFileCommand(oldFolder, r.getRootNodeRef(), oldFolder + "\\" + fileFrom); - commands.add(d1); - } - else - { - MoveFileCommand m1 = new MoveFileCommand(fileFrom, fileEnd, r.getRootNodeRef(), oldFolder + "\\" + fileFrom, folderEnd + "\\" + fileEnd, isMoveAsSystem()); - commands.add(m1); - } - /** - * TODO - we may need to copy a new node for the backup and delete the temp node. - * It depends if we care about the contents of the Backup file. - */ - - isComplete = true; - return new CompoundCommand(commands); - } - else - { - if ((interimPattern != null)) - { - // ALF-16257: temporary Word file moved from .TemporaryItems - Matcher m = interimPattern.matcher(r.getFromPath()); - if(m.matches() && r.getFrom().equals(r.getTo())) - { - if(logger.isDebugEnabled()) - { - logger.debug("Got system move from temporary folder: " + r.getFrom() + " to " + r.getToPath()); - } - return new MoveFileCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath(), true); - } - } - } - } - - break; - } - - return null; - } - - public boolean isMoveAsSystem() - { - return moveAsSystem; - } - - public void setMoveAsSystem(boolean moveAsSystem) - { - this.moveAsSystem = moveAsSystem; - } - - @Override - public boolean isComplete() - { - return isComplete; - } - - @Override - public Ranking getRanking() - { - return ranking; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public String toString() - { - return "ScenarioDoubleRename:" + fileMiddle; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setDeleteBackup(boolean deleteBackup) - { - this.deleteBackup = deleteBackup; - } - - public boolean isDeleteBackup() - { - return deleteBackup; - } - - public void setInterimPattern(Pattern interimPattern) - { - this.interimPattern = interimPattern; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.MoveFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This is an instance of a "double rename shuffle" triggered by rename of a file to a special pattern + * file matching a specified pattern. (*.backup.fm) + * + * a) Existing file moved out of the way. X.fm to X.backup.fm + * b) New file moved into place. X.fm.C29 X.fm + *

+ * If this filter is active then this is what happens. + * a) Existing file moved out of the way (Y to Z). Raname tracked. + * b) New file moved into place (X to Y). + * + * Scenario kicks in to change commands. + */ +public class ScenarioDoubleRenameShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioDoubleRenameShuffleInstance.class); + + enum InternalState + { + NONE, + RENAME1, + RENAME2 + } + + InternalState internalState = InternalState.NONE; + + private Date startTime = new Date(); + + private String fileMiddle; + private String fileFrom; + private String fileEnd; + + private Ranking ranking; + private boolean deleteBackup; + private boolean moveAsSystem; + private Pattern interimPattern; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete; + private String folderMiddle; + private String folderEnd; + + /** + * Keep track of re-names + */ + private Map renames = new HashMap(); + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out"); + + } + isComplete = true; + return null; + } + + switch (internalState) + { + + case NONE: + + /** + * Looking for first rename Y(middle) to Z(end) + */ + if(operation instanceof RenameFileOperation) + { + if(logger.isDebugEnabled()) + { + logger.debug("Got first rename - tracking rename: " + operation); + } + RenameFileOperation r = (RenameFileOperation)operation; + fileMiddle = r.getFrom(); + fileEnd = r.getTo(); + + String[] paths = FileName.splitPath(r.getFromPath()); + folderMiddle = paths[0]; + + String[] paths2 = FileName.splitPath(r.getToPath()); + folderEnd = paths2[0]; + + internalState = InternalState.RENAME1; + + return new RenameFileCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); + } + else + { + // anything else bomb out + if(logger.isDebugEnabled()) + { + logger.debug("State error, expected a RENAME"); + } + isComplete = true; + } + + + case RENAME1: + + /** + * Looking for the second of two renames X(createName) to Y(middle) to Z(end) + */ + if(operation instanceof RenameFileOperation) + { + if(logger.isDebugEnabled()) + { + logger.debug("Tracking rename: " + operation); + } + RenameFileOperation r = (RenameFileOperation)operation; + + // Now see if this rename makes a pair + if(fileMiddle.equalsIgnoreCase(r.getTo())) + { + if(logger.isDebugEnabled()) + { + logger.debug("Got second rename" ); + } + + fileFrom = r.getFrom(); + + /** + * This shuffle reverses the rename out of the way and then copies the + * content only. Finally it moves the temp file into place for the subsequent + * delete. + * a) Rename Z to Y (Reverse previous move) + * b) Copy Content from X to Y + * c) Rename X to Z (move temp file out to old location) + */ + if(logger.isDebugEnabled()) + { + logger.debug("Go and shuffle! fromName:" + fileFrom + " middle: " + fileMiddle + " end: " + fileEnd); + } + + String[] paths = FileName.splitPath(r.getFromPath()); + String oldFolder = paths[0]; + + ArrayList commands = new ArrayList(); + + RenameFileCommand r1 = new RenameFileCommand(fileEnd, fileMiddle, r.getRootNodeRef(), oldFolder + "\\" + fileEnd, oldFolder + "\\" + fileMiddle); + commands.add(r1); + CopyContentCommand copyContent = new CopyContentCommand(fileFrom, fileMiddle, r.getRootNodeRef(), oldFolder + "\\" + fileFrom, oldFolder + "\\" + fileMiddle); + commands.add(copyContent); + if(deleteBackup) + { + logger.debug("deleteBackup option turned on"); + DeleteFileCommand d1 = new DeleteFileCommand(oldFolder, r.getRootNodeRef(), oldFolder + "\\" + fileFrom); + commands.add(d1); + } + else + { + RenameFileCommand r2 = new RenameFileCommand(fileFrom, fileEnd, r.getRootNodeRef(), oldFolder + "\\" + fileFrom, oldFolder + "\\" + fileEnd); + commands.add(r2); + } + + /** + * TODO - we may need to copy a new node for the backup and delete the temp node. + * It depends if we care about the contents of the Backup file. + */ + + isComplete = true; + return new CompoundCommand(commands); + } + } + + if(operation instanceof MoveFileOperation) + { + if(logger.isDebugEnabled()) + { + logger.info("Tracking rename: " + operation); + } + MoveFileOperation r = (MoveFileOperation)operation; + + // Now see if this rename makes a pair + if(fileMiddle.equalsIgnoreCase(r.getTo())) + { + if(logger.isDebugEnabled()) + { + logger.debug("Got second rename" ); + } + + fileFrom = r.getFrom(); + + /** + * This shuffle reverses the rename out of the way and then copies the + * content only. Finally it moves the temp file into place for the subsequent + * delete. + * a) Rename Z to Y (Reverse previous move) + * b) Copy Content from X to Y + * c) Rename X to Z (move temp file out to old location) + */ + if(logger.isDebugEnabled()) + { + logger.debug("Go and shuffle! fromName:" + fileFrom + " middle: " + fileMiddle + " end: " + fileEnd); + } + + String[] paths = FileName.splitPath(r.getFromPath()); + String oldFolder = paths[0]; + + ArrayList commands = new ArrayList(); + + RenameFileCommand r1 = new RenameFileCommand(fileEnd, fileMiddle, r.getRootNodeRef(), folderEnd + "\\" + fileEnd, folderMiddle + "\\" + fileMiddle); + commands.add(r1); + CopyContentCommand copyContent = new CopyContentCommand(fileFrom, fileMiddle, r.getRootNodeRef(), oldFolder + "\\" + fileFrom, folderMiddle + "\\" + fileMiddle); + commands.add(copyContent); + if(deleteBackup) + { + logger.debug("deleteBackup option turned on"); + DeleteFileCommand d1 = new DeleteFileCommand(oldFolder, r.getRootNodeRef(), oldFolder + "\\" + fileFrom); + commands.add(d1); + } + else + { + MoveFileCommand m1 = new MoveFileCommand(fileFrom, fileEnd, r.getRootNodeRef(), oldFolder + "\\" + fileFrom, folderEnd + "\\" + fileEnd, isMoveAsSystem()); + commands.add(m1); + } + /** + * TODO - we may need to copy a new node for the backup and delete the temp node. + * It depends if we care about the contents of the Backup file. + */ + + isComplete = true; + return new CompoundCommand(commands); + } + else + { + if ((interimPattern != null)) + { + // ALF-16257: temporary Word file moved from .TemporaryItems + Matcher m = interimPattern.matcher(r.getFromPath()); + if(m.matches() && r.getFrom().equals(r.getTo())) + { + if(logger.isDebugEnabled()) + { + logger.debug("Got system move from temporary folder: " + r.getFrom() + " to " + r.getToPath()); + } + return new MoveFileCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath(), true); + } + } + } + } + + break; + } + + return null; + } + + public boolean isMoveAsSystem() + { + return moveAsSystem; + } + + public void setMoveAsSystem(boolean moveAsSystem) + { + this.moveAsSystem = moveAsSystem; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioDoubleRename:" + fileMiddle; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setDeleteBackup(boolean deleteBackup) + { + this.deleteBackup = deleteBackup; + } + + public boolean isDeleteBackup() + { + return deleteBackup; + } + + public void setInterimPattern(Pattern interimPattern) + { + this.interimPattern = interimPattern; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioInstance.java index 011c255abf..691ac1a492 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioInstance.java @@ -1,40 +1,40 @@ -package org.alfresco.filesys.repo.rules; - -/** - * A scenario instance is an active scenario. It has a ranking, an - * evaluate method and knows whether it is complete. - *

- * The evaluate method is called repeatedly as operations are processed. - */ -public interface ScenarioInstance -{ - enum Ranking - { - LOW, // Bottom priority - MEDIUM, - HIGH, - - }; - - /** - * Get the Ranking - * @return Ranking - */ - public Ranking getRanking(); - - /** - * evaluate the scenario against the current operation - * - * @param operation Operation - */ - public Command evaluate(Operation operation); - - /** - * Is the scenario complete? - * - * @return boolean - */ - public boolean isComplete(); - - -} +package org.alfresco.filesys.repo.rules; + +/** + * A scenario instance is an active scenario. It has a ranking, an + * evaluate method and knows whether it is complete. + *

+ * The evaluate method is called repeatedly as operations are processed. + */ +public interface ScenarioInstance +{ + enum Ranking + { + LOW, // Bottom priority + MEDIUM, + HIGH, + + }; + + /** + * Get the Ranking + * @return Ranking + */ + public Ranking getRanking(); + + /** + * evaluate the scenario against the current operation + * + * @param operation Operation + */ + public Command evaluate(Operation operation); + + /** + * Is the scenario complete? + * + * @return boolean + */ + public boolean isComplete(); + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioInstanceRenameAware.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioInstanceRenameAware.java index 47aa989aba..22eed478c4 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioInstanceRenameAware.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioInstanceRenameAware.java @@ -1,19 +1,19 @@ -package org.alfresco.filesys.repo.rules; - -/** - * The scenario instance wants to be notified about rename. - * - * @author mrogers - * - */ -public interface ScenarioInstanceRenameAware -{ - /** - * Notify the scenario of a successful rename operation. - * - * @param operation - * @param command - */ - public void notifyRename(Operation operation, Command command); - -} +package org.alfresco.filesys.repo.rules; + +/** + * The scenario instance wants to be notified about rename. + * + * @author mrogers + * + */ +public interface ScenarioInstanceRenameAware +{ + /** + * Notify the scenario of a successful rename operation. + * + * @param operation + * @param command + */ + public void notifyRename(Operation operation, Command command); + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java index 0e381658a9..a5cb61498d 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java @@ -1,163 +1,163 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * The Open File Scenario is a sequence of multiple openFile operations - * - * Only on the last close does the repo get closed. Open Files in the middle - * share the same file handle. - * - * For example: - * - * 1) open(readOnly) - * 2) open(readWrite) - * 3) open(readOnly) - does nothing. - * 4) close - does nothing - * 5) close - does nothing - * 6) close - updates the repo - */ -public class ScenarioOpenFile implements Scenario -{ - private static Log logger = LogFactory.getLog(ScenarioOpenFile.class); - - private String pattern; - - private long timeout = 300000; - - @Override - public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) - { - /** - * This scenario is triggered by an open or create of a new file - */ - if(operation instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)operation; - if(c.getName() == null) - { - logger.debug("c.getName is null! - scenario not active"); - return null; - } - - if(c.getName().matches(pattern)) - { - - if(checkScenarioActive(c.getName(),ctx.getScenarioInstances())) - { - logger.debug("scenario already active for name" + c.getName()); - return null; - } - - if(logger.isDebugEnabled()) - { - logger.debug("New Open File Instance for CreateFileOperation:" + c); - } - - ScenarioOpenFileInstance instance = new ScenarioOpenFileInstance(); - instance.setTimeout(timeout); - instance.setRanking(ranking); - return instance; - } - } - - if(operation instanceof OpenFileOperation) - { - - OpenFileOperation o = (OpenFileOperation)operation; - - if(o.getName() == null) - { - logger.debug("o.getName is null! - scenario not active"); - return null; - } - - if(o.getName().matches(pattern)) - { - if(checkScenarioActive(o.getName(),ctx.getScenarioInstances())) - { - logger.debug("scenario already active for name" + o.getName()); - return null; - } - - if(logger.isDebugEnabled()) - { - logger.debug("New Open File Instance for OpenFileOperation:" + o); - } - - ScenarioOpenFileInstance instance = new ScenarioOpenFileInstance(); - instance.setTimeout(timeout); - instance.setRanking(ranking); - return instance; - } - } - - // No not interested. - return null; - - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setPattern(String pattern) - { - this.pattern = pattern; - } - - public String getPattern() - { - return pattern; - } - - private Ranking ranking = Ranking.HIGH; - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public Ranking getRanking() - { - return ranking; - } - - /** - * Check whether there is already an instance of the ScenarioOpenFile for the file - */ - private boolean checkScenarioActive(String name, final List currentInstances) - { - for(ScenarioInstance instance: currentInstances) - { - if(instance instanceof ScenarioOpenFileInstance) - { - ScenarioOpenFileInstance i = (ScenarioOpenFileInstance)instance; - if(i.getName() != null && name != null) - { - if(i.getName().equalsIgnoreCase(name)) - { - return true; - } - } - } - } - return false; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The Open File Scenario is a sequence of multiple openFile operations + * + * Only on the last close does the repo get closed. Open Files in the middle + * share the same file handle. + * + * For example: + * + * 1) open(readOnly) + * 2) open(readWrite) + * 3) open(readOnly) - does nothing. + * 4) close - does nothing + * 5) close - does nothing + * 6) close - updates the repo + */ +public class ScenarioOpenFile implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioOpenFile.class); + + private String pattern; + + private long timeout = 300000; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * This scenario is triggered by an open or create of a new file + */ + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + if(c.getName() == null) + { + logger.debug("c.getName is null! - scenario not active"); + return null; + } + + if(c.getName().matches(pattern)) + { + + if(checkScenarioActive(c.getName(),ctx.getScenarioInstances())) + { + logger.debug("scenario already active for name" + c.getName()); + return null; + } + + if(logger.isDebugEnabled()) + { + logger.debug("New Open File Instance for CreateFileOperation:" + c); + } + + ScenarioOpenFileInstance instance = new ScenarioOpenFileInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + if(operation instanceof OpenFileOperation) + { + + OpenFileOperation o = (OpenFileOperation)operation; + + if(o.getName() == null) + { + logger.debug("o.getName is null! - scenario not active"); + return null; + } + + if(o.getName().matches(pattern)) + { + if(checkScenarioActive(o.getName(),ctx.getScenarioInstances())) + { + logger.debug("scenario already active for name" + o.getName()); + return null; + } + + if(logger.isDebugEnabled()) + { + logger.debug("New Open File Instance for OpenFileOperation:" + o); + } + + ScenarioOpenFileInstance instance = new ScenarioOpenFileInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setPattern(String pattern) + { + this.pattern = pattern; + } + + public String getPattern() + { + return pattern; + } + + private Ranking ranking = Ranking.HIGH; + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } + + /** + * Check whether there is already an instance of the ScenarioOpenFile for the file + */ + private boolean checkScenarioActive(String name, final List currentInstances) + { + for(ScenarioInstance instance: currentInstances) + { + if(instance instanceof ScenarioOpenFileInstance) + { + ScenarioOpenFileInstance i = (ScenarioOpenFileInstance)instance; + if(i.getName() != null && name != null) + { + if(i.getName().equalsIgnoreCase(name)) + { + return true; + } + } + } + } + return false; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java index eb2b9932d0..c4316560a7 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java @@ -1,637 +1,637 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.alfresco.filesys.repo.OpenFileMode; -import org.alfresco.filesys.repo.ResultCallback; -import org.alfresco.filesys.repo.TempNetworkFile; -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.ScenarioRenameShuffleInstance.InternalState; -import org.alfresco.filesys.repo.rules.commands.CallbackCommand; -import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; -import org.alfresco.filesys.repo.rules.commands.CreateFileCommand; -import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; -import org.alfresco.filesys.repo.rules.commands.DoNothingCommand; -import org.alfresco.filesys.repo.rules.commands.OpenFileCommand; -import org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand; -import org.alfresco.filesys.repo.rules.commands.RemoveNoContentFileOnError; -import org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand; -import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; -import org.alfresco.filesys.repo.rules.commands.ReturnValueCommand; -import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * An open file scenario is ... - *

- * 1) open(readOnly) - * 2) close(readOnly) - *

- * 1) open(readOnly) - * 2) open(readWrite) - * 3) close(readOnly) - * 4 close(readWrite) updates the repo - *

- * 1) open(readOnly) - * 2) open(readWrite) - * 3) open(readWrite) - does nothing. Increments Open Count. - * 4) close(readWrite) - does nothing. Decrements Open Count. - * 5) close(readWrite) - updates the repo. - * 6) close(readOnly) - closes read only - *

- * 1) open (readWrite) - * 2) open (readOnly) - file already open for read/write - * 3) close - * 4) close - * - */ -class ScenarioOpenFileInstance implements ScenarioInstance, DependentInstance, ScenarioInstanceRenameAware -{ - private static Log logger = LogFactory.getLog(ScenarioOpenFileInstance.class); - - private Date startTime = new Date(); - - private String name; - - enum InternalState - { - NONE, - OPENING, - OPEN, - ERROR - } ; - - InternalState state = InternalState.NONE; - - /** - * For each read only open file - */ - private NetworkFile fileHandleReadOnly; - private int openReadOnlyCount = 0; - - /** - * For each read/write open file - */ - private NetworkFile fileHandleReadWrite; - private int openReadWriteCount = 0; - - /** - * Timeout in ms. Default 30 seconds. - */ - private long timeout = 30000; - - private boolean isComplete = false; - - private Ranking ranking = Ranking.HIGH; - - /** - * Evaluate the next operation - * @param operation - */ - public Command evaluate(Operation operation) - { - /** - * Anti-pattern : timeout - this scenario does not timeout - */ - // Date now = new Date(); - // if(now.getTime() > startTime.getTime() + getTimeout()) - // { - // if(logger.isDebugEnabled()) - // { - // logger.debug("Instance timed out"); - // } - // } - - /** - * Anti Pattern - Delete of the open file. - */ - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation d = (DeleteFileOperation)operation; - - if(d.getName() == null) - { - return null; - } - - if(name.equalsIgnoreCase(d.getName())) - { - logger.debug("Anti-Pattern - delete of the open file, scenario:" + this); - isComplete = true; - return null; - } - } - - switch (state) - { - case NONE: - if(operation instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)operation; - name = c.getName(); - - if(name != null) - { - state = InternalState.OPENING; - logger.debug("Create File name:" + name); - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - commands.add(new CreateFileCommand(c.getName(), c.getRootNodeRef(), c.getPath(), c.getAllocationSize(), c.isHidden())); - postCommitCommands.add(newOpenFileCallbackCommand()); - postErrorCommands.add(newOpenFileErrorCallbackCommand()); - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - } - } - else if(operation instanceof OpenFileOperation) - { - OpenFileOperation o = (OpenFileOperation)operation; - name = o.getName(); - if(name != null) - { - state = InternalState.OPENING; - logger.debug("Open File name:" + name); - ArrayList commands = new ArrayList(); - commands.add(new OpenFileCommand(o.getName(), o.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - postCommitCommands.add(newOpenFileCallbackCommand()); - postErrorCommands.add(newOpenFileErrorCallbackCommand()); - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - } - } - - // Scenario Not Started - logger.debug("Scenario not started - no name"); - isComplete = true; - return null; - - case OPENING: - - if(operation instanceof OpenFileOperation) - { - OpenFileOperation o = (OpenFileOperation)operation; - - if(o.getName() == null) - { - return null; - } - - if(name.equalsIgnoreCase(o.getName())) - { - /** - * TODO What to do here - one thread is in the middle of - * opening a file while another tries to open the same file - * sleep for a bit? then check state again? What happens if file - * closes while sleeping. For now log an error. - */ - logger.error("Second open while in opening state. :" + name); -// isComplete = true; -// return null; - } - } - - /** - * Anti-pattern : timeout - is this needed ? - */ - Date now = new Date(); - if(now.getTime() > startTime.getTime() + getTimeout()) - { - if(logger.isDebugEnabled()) - { - logger.debug("Instance in OPENING STATE timed out name" + name); - } - isComplete = true; - } - return null; - - case ERROR: - - logger.debug("Open has failed :" + name); - isComplete = true; - return null; - - case OPEN: - - if(operation instanceof RenameFileOperation) - { - RenameFileOperation r = (RenameFileOperation)operation; - if(r.getFrom() == null) - { - return null; - } - - if(name.equalsIgnoreCase(r.getFrom())) - { - logger.warn("rename of an open file"); - } - } - - if(operation instanceof CloseFileOperation) - { - CloseFileOperation c = (CloseFileOperation)operation; - - if(c.getName() == null) - { - return null; - } - - if(name.equalsIgnoreCase(c.getName())) - { - NetworkFile file = c.getNetworkFile(); - if(isReadOnly(file)) - { - // Read Only File - if(openReadOnlyCount == 1) - { - if(logger.isDebugEnabled()) - { - logger.debug("Close of last read only file handle:" + this); - } - - openReadOnlyCount = 0; - - if(openReadWriteCount <= 0) - { - if(logger.isDebugEnabled()) - { - logger.debug("Scenario is complete:" + this); - } - isComplete=true; - } - - if (file instanceof TempNetworkFile) - { - logger.debug("this is the last close of a temp read only file"); - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - - commands.add(new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); - postCommitCommands.add(new RemoveTempFileCommand((TempNetworkFile)file)); - return new CompoundCommand(commands, postCommitCommands); - } - else - { - return new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath()); - } - } - - if(logger.isDebugEnabled()) - { - logger.debug("Only decrement count of read only file handle:" + this); - } - - openReadOnlyCount--; - - return new DoNothingCommand(); - } - else - { - // This is a close of a Read Write File - // Read Only File - if(openReadWriteCount == 1) - { - if(logger.isDebugEnabled()) - { - logger.debug("Close of last read write file handle:" + this); - } - - openReadWriteCount = 0; - - if(openReadOnlyCount <= 0) - { - if(logger.isDebugEnabled()) - { - logger.debug("Scenario is complete:" + this); - } - isComplete=true; - } - - - // - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - - commands.add(new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); - - //postErrorCommands.add(new RemoveNoContentFileOnError(c.getName(), c.getRootNodeRef(), c.getPath())); - - if(c.isDeleteOnClose()) - { - postCommitCommands.add(new ReduceQuotaCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); - } - - if (file instanceof TempNetworkFile) - { - postCommitCommands.add(new RemoveTempFileCommand((TempNetworkFile)file)); - } - - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - - } - - if(logger.isDebugEnabled()) - { - logger.debug("Only decrement count of read write file handle:" + this); - } - - openReadWriteCount--; - - return new DoNothingCommand(); - } - } - } - else if(operation instanceof OpenFileOperation) - { - OpenFileOperation o = (OpenFileOperation)operation; - - if(o.getName() == null) - { - return null; - } - - if(name != null && name.equalsIgnoreCase(o.getName())) - { - if(o.getMode() == OpenFileMode.READ_WRITE) - { - // This is an open of a read write access - if(openReadWriteCount == 0) - { - logger.debug("Open first read/write from scenario:" + this); - ArrayList commands = new ArrayList(); - commands.add(new OpenFileCommand(o.getName(), o.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); - ArrayList postCommitCommands = new ArrayList(); - postCommitCommands.add(newOpenFileCallbackCommand()); - return new CompoundCommand(commands, postCommitCommands); - } - else - { - // TODO Need a permission check here and increment post check - openReadWriteCount++; - logger.debug("Return already open read/write file handle from scenario:" + this); - return new ReturnValueCommand(fileHandleReadWrite); - } - } - else - { - // This is an open for read only access - - if(openReadWriteCount > 0) - { - //however the file is already open for read/write - openReadWriteCount++; - logger.debug("Return already open read/write file handle from scenario:" + this); - return new ReturnValueCommand(fileHandleReadWrite); - } - - if(openReadOnlyCount == 0) - { - logger.debug("Open first read only from scenario:" + this); - ArrayList commands = new ArrayList(); - commands.add(new OpenFileCommand(o.getName(), o.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); - ArrayList postCommitCommands = new ArrayList(); - postCommitCommands.add(newOpenFileCallbackCommand()); - return new CompoundCommand(commands, postCommitCommands); - } - else - { - openReadOnlyCount++; - logger.debug("Return already open only file handle from scenario:" + this); - return new ReturnValueCommand(fileHandleReadOnly); - } - } - } - } - - break; - - } - - return null; - } - - @Override - public boolean isComplete() - { - return isComplete; - } - - public String toString() - { - return "ScenarioOpenFileInstance name:" + name; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - @Override - public Ranking getRanking() - { - return ranking; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public String getName() - { - return name; - } - - /** - * Called for open file. - */ - private ResultCallback newOpenFileCallbackCommand() - { - return new ResultCallback() - { - @Override - public void execute(Object result) - { - if(result instanceof NetworkFile) - { - - // Now update the state of this scenario - we have an open fileHandle - NetworkFile fileHandle = (NetworkFile)result; - - state = InternalState.OPEN; - - if(isReadOnly(fileHandle)) - { - openReadOnlyCount++; - fileHandleReadOnly=fileHandle; - if(logger.isDebugEnabled()) - { - logger.debug("file opened read only:" + result + ", name:" + name); - } - } - else - { - openReadWriteCount++; - fileHandleReadWrite=fileHandle; - - if(logger.isDebugEnabled()) - { - logger.debug("file opened read write :" + result + ", name:" + name); - } - } - } - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } - }; - } - - /** - * Called for open file error. - */ - private ResultCallback newOpenFileErrorCallbackCommand() - { - return new ResultCallback() - { - @Override - public void execute(Object result) - { - logger.debug("error handler - set state to error for name:" + name); - isComplete = true; - state = InternalState.ERROR; - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } - }; - } - - - private boolean isReadOnly(NetworkFile file) - { - return (file.getGrantedAccess() == NetworkFile.READONLY); - } - - /* This openFileInstance knows about ScenarioDeleteRestore */ - @Override - public Command win(List results, Command command) - { - if(command instanceof CompoundCommand) - { - CompoundCommand c = (CompoundCommand)command; - for(ScenarioResult looser : results) - { - if(looser.scenario instanceof ScenarioDeleteRestoreInstance) - { - Command l = looser.command; - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - commands.add(l); - postCommitCommands.addAll(c.getPostCommitCommands()); - postErrorCommands.addAll(c.getPostErrorCommands()); - - logger.debug("returning merged high priority executor"); - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - } - - - if(looser.scenario instanceof ScenarioDeleteRenameOrCreateInstance) - { - - Command x = looser.command; - if(x instanceof CompoundCommand) - { - CompoundCommand l = (CompoundCommand)x; - - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - commands.addAll(c.getCommands()); - postCommitCommands.addAll(c.getPostCommitCommands()); - // Merge in the loosing post commit - postCommitCommands.addAll(l.getPostCommitCommands()); - postErrorCommands.addAll(c.getPostErrorCommands()); - - logger.debug("returning merged high priority executor"); - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - } - else - { - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - commands.add(x); - postCommitCommands.addAll(c.getPostCommitCommands()); - postErrorCommands.addAll(c.getPostErrorCommands()); - - logger.debug("returning merged high priority executor"); - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - } - } - } - } - // No change - return command; - } - - @Override - public void notifyRename(Operation operation, Command command) - { - if(operation instanceof RenameFileOperation) - { - RenameFileOperation r = (RenameFileOperation)operation; - if(r.getFrom() == null) - { - return; - } - - if(name.equalsIgnoreCase(r.getFrom())) - { - if(logger.isWarnEnabled()) - { - logger.warn("rename of this scenario: to " + r.getTo()); - } - - name = r.getTo(); - - if (fileHandleReadWrite != null) - { - fileHandleReadWrite.setName(r.getTo()); - fileHandleReadWrite.setFullName(r.getToPath()); - } - - if (fileHandleReadOnly != null) - { - fileHandleReadOnly.setName(r.getTo()); - fileHandleReadOnly.setFullName(r.getToPath()); - } - } - } - } -} - +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.alfresco.filesys.repo.OpenFileMode; +import org.alfresco.filesys.repo.ResultCallback; +import org.alfresco.filesys.repo.TempNetworkFile; +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.ScenarioRenameShuffleInstance.InternalState; +import org.alfresco.filesys.repo.rules.commands.CallbackCommand; +import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.CreateFileCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.DoNothingCommand; +import org.alfresco.filesys.repo.rules.commands.OpenFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand; +import org.alfresco.filesys.repo.rules.commands.RemoveNoContentFileOnError; +import org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReturnValueCommand; +import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * An open file scenario is ... + *

+ * 1) open(readOnly) + * 2) close(readOnly) + *

+ * 1) open(readOnly) + * 2) open(readWrite) + * 3) close(readOnly) + * 4 close(readWrite) updates the repo + *

+ * 1) open(readOnly) + * 2) open(readWrite) + * 3) open(readWrite) - does nothing. Increments Open Count. + * 4) close(readWrite) - does nothing. Decrements Open Count. + * 5) close(readWrite) - updates the repo. + * 6) close(readOnly) - closes read only + *

+ * 1) open (readWrite) + * 2) open (readOnly) - file already open for read/write + * 3) close + * 4) close + * + */ +class ScenarioOpenFileInstance implements ScenarioInstance, DependentInstance, ScenarioInstanceRenameAware +{ + private static Log logger = LogFactory.getLog(ScenarioOpenFileInstance.class); + + private Date startTime = new Date(); + + private String name; + + enum InternalState + { + NONE, + OPENING, + OPEN, + ERROR + } ; + + InternalState state = InternalState.NONE; + + /** + * For each read only open file + */ + private NetworkFile fileHandleReadOnly; + private int openReadOnlyCount = 0; + + /** + * For each read/write open file + */ + private NetworkFile fileHandleReadWrite; + private int openReadWriteCount = 0; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete = false; + + private Ranking ranking = Ranking.HIGH; + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + /** + * Anti-pattern : timeout - this scenario does not timeout + */ + // Date now = new Date(); + // if(now.getTime() > startTime.getTime() + getTimeout()) + // { + // if(logger.isDebugEnabled()) + // { + // logger.debug("Instance timed out"); + // } + // } + + /** + * Anti Pattern - Delete of the open file. + */ + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + + if(d.getName() == null) + { + return null; + } + + if(name.equalsIgnoreCase(d.getName())) + { + logger.debug("Anti-Pattern - delete of the open file, scenario:" + this); + isComplete = true; + return null; + } + } + + switch (state) + { + case NONE: + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + name = c.getName(); + + if(name != null) + { + state = InternalState.OPENING; + logger.debug("Create File name:" + name); + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + commands.add(new CreateFileCommand(c.getName(), c.getRootNodeRef(), c.getPath(), c.getAllocationSize(), c.isHidden())); + postCommitCommands.add(newOpenFileCallbackCommand()); + postErrorCommands.add(newOpenFileErrorCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + } + else if(operation instanceof OpenFileOperation) + { + OpenFileOperation o = (OpenFileOperation)operation; + name = o.getName(); + if(name != null) + { + state = InternalState.OPENING; + logger.debug("Open File name:" + name); + ArrayList commands = new ArrayList(); + commands.add(new OpenFileCommand(o.getName(), o.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + postCommitCommands.add(newOpenFileCallbackCommand()); + postErrorCommands.add(newOpenFileErrorCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + } + + // Scenario Not Started + logger.debug("Scenario not started - no name"); + isComplete = true; + return null; + + case OPENING: + + if(operation instanceof OpenFileOperation) + { + OpenFileOperation o = (OpenFileOperation)operation; + + if(o.getName() == null) + { + return null; + } + + if(name.equalsIgnoreCase(o.getName())) + { + /** + * TODO What to do here - one thread is in the middle of + * opening a file while another tries to open the same file + * sleep for a bit? then check state again? What happens if file + * closes while sleeping. For now log an error. + */ + logger.error("Second open while in opening state. :" + name); +// isComplete = true; +// return null; + } + } + + /** + * Anti-pattern : timeout - is this needed ? + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance in OPENING STATE timed out name" + name); + } + isComplete = true; + } + return null; + + case ERROR: + + logger.debug("Open has failed :" + name); + isComplete = true; + return null; + + case OPEN: + + if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + if(r.getFrom() == null) + { + return null; + } + + if(name.equalsIgnoreCase(r.getFrom())) + { + logger.warn("rename of an open file"); + } + } + + if(operation instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)operation; + + if(c.getName() == null) + { + return null; + } + + if(name.equalsIgnoreCase(c.getName())) + { + NetworkFile file = c.getNetworkFile(); + if(isReadOnly(file)) + { + // Read Only File + if(openReadOnlyCount == 1) + { + if(logger.isDebugEnabled()) + { + logger.debug("Close of last read only file handle:" + this); + } + + openReadOnlyCount = 0; + + if(openReadWriteCount <= 0) + { + if(logger.isDebugEnabled()) + { + logger.debug("Scenario is complete:" + this); + } + isComplete=true; + } + + if (file instanceof TempNetworkFile) + { + logger.debug("this is the last close of a temp read only file"); + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + + commands.add(new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + postCommitCommands.add(new RemoveTempFileCommand((TempNetworkFile)file)); + return new CompoundCommand(commands, postCommitCommands); + } + else + { + return new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath()); + } + } + + if(logger.isDebugEnabled()) + { + logger.debug("Only decrement count of read only file handle:" + this); + } + + openReadOnlyCount--; + + return new DoNothingCommand(); + } + else + { + // This is a close of a Read Write File + // Read Only File + if(openReadWriteCount == 1) + { + if(logger.isDebugEnabled()) + { + logger.debug("Close of last read write file handle:" + this); + } + + openReadWriteCount = 0; + + if(openReadOnlyCount <= 0) + { + if(logger.isDebugEnabled()) + { + logger.debug("Scenario is complete:" + this); + } + isComplete=true; + } + + + // + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + + commands.add(new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + + //postErrorCommands.add(new RemoveNoContentFileOnError(c.getName(), c.getRootNodeRef(), c.getPath())); + + if(c.isDeleteOnClose()) + { + postCommitCommands.add(new ReduceQuotaCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + } + + if (file instanceof TempNetworkFile) + { + postCommitCommands.add(new RemoveTempFileCommand((TempNetworkFile)file)); + } + + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + + } + + if(logger.isDebugEnabled()) + { + logger.debug("Only decrement count of read write file handle:" + this); + } + + openReadWriteCount--; + + return new DoNothingCommand(); + } + } + } + else if(operation instanceof OpenFileOperation) + { + OpenFileOperation o = (OpenFileOperation)operation; + + if(o.getName() == null) + { + return null; + } + + if(name != null && name.equalsIgnoreCase(o.getName())) + { + if(o.getMode() == OpenFileMode.READ_WRITE) + { + // This is an open of a read write access + if(openReadWriteCount == 0) + { + logger.debug("Open first read/write from scenario:" + this); + ArrayList commands = new ArrayList(); + commands.add(new OpenFileCommand(o.getName(), o.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); + ArrayList postCommitCommands = new ArrayList(); + postCommitCommands.add(newOpenFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands); + } + else + { + // TODO Need a permission check here and increment post check + openReadWriteCount++; + logger.debug("Return already open read/write file handle from scenario:" + this); + return new ReturnValueCommand(fileHandleReadWrite); + } + } + else + { + // This is an open for read only access + + if(openReadWriteCount > 0) + { + //however the file is already open for read/write + openReadWriteCount++; + logger.debug("Return already open read/write file handle from scenario:" + this); + return new ReturnValueCommand(fileHandleReadWrite); + } + + if(openReadOnlyCount == 0) + { + logger.debug("Open first read only from scenario:" + this); + ArrayList commands = new ArrayList(); + commands.add(new OpenFileCommand(o.getName(), o.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); + ArrayList postCommitCommands = new ArrayList(); + postCommitCommands.add(newOpenFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands); + } + else + { + openReadOnlyCount++; + logger.debug("Return already open only file handle from scenario:" + this); + return new ReturnValueCommand(fileHandleReadOnly); + } + } + } + } + + break; + + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + public String toString() + { + return "ScenarioOpenFileInstance name:" + name; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String getName() + { + return name; + } + + /** + * Called for open file. + */ + private ResultCallback newOpenFileCallbackCommand() + { + return new ResultCallback() + { + @Override + public void execute(Object result) + { + if(result instanceof NetworkFile) + { + + // Now update the state of this scenario - we have an open fileHandle + NetworkFile fileHandle = (NetworkFile)result; + + state = InternalState.OPEN; + + if(isReadOnly(fileHandle)) + { + openReadOnlyCount++; + fileHandleReadOnly=fileHandle; + if(logger.isDebugEnabled()) + { + logger.debug("file opened read only:" + result + ", name:" + name); + } + } + else + { + openReadWriteCount++; + fileHandleReadWrite=fileHandle; + + if(logger.isDebugEnabled()) + { + logger.debug("file opened read write :" + result + ", name:" + name); + } + } + } + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } + }; + } + + /** + * Called for open file error. + */ + private ResultCallback newOpenFileErrorCallbackCommand() + { + return new ResultCallback() + { + @Override + public void execute(Object result) + { + logger.debug("error handler - set state to error for name:" + name); + isComplete = true; + state = InternalState.ERROR; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } + }; + } + + + private boolean isReadOnly(NetworkFile file) + { + return (file.getGrantedAccess() == NetworkFile.READONLY); + } + + /* This openFileInstance knows about ScenarioDeleteRestore */ + @Override + public Command win(List results, Command command) + { + if(command instanceof CompoundCommand) + { + CompoundCommand c = (CompoundCommand)command; + for(ScenarioResult looser : results) + { + if(looser.scenario instanceof ScenarioDeleteRestoreInstance) + { + Command l = looser.command; + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + commands.add(l); + postCommitCommands.addAll(c.getPostCommitCommands()); + postErrorCommands.addAll(c.getPostErrorCommands()); + + logger.debug("returning merged high priority executor"); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + + + if(looser.scenario instanceof ScenarioDeleteRenameOrCreateInstance) + { + + Command x = looser.command; + if(x instanceof CompoundCommand) + { + CompoundCommand l = (CompoundCommand)x; + + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + commands.addAll(c.getCommands()); + postCommitCommands.addAll(c.getPostCommitCommands()); + // Merge in the loosing post commit + postCommitCommands.addAll(l.getPostCommitCommands()); + postErrorCommands.addAll(c.getPostErrorCommands()); + + logger.debug("returning merged high priority executor"); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + else + { + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + commands.add(x); + postCommitCommands.addAll(c.getPostCommitCommands()); + postErrorCommands.addAll(c.getPostErrorCommands()); + + logger.debug("returning merged high priority executor"); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + } + } + } + // No change + return command; + } + + @Override + public void notifyRename(Operation operation, Command command) + { + if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + if(r.getFrom() == null) + { + return; + } + + if(name.equalsIgnoreCase(r.getFrom())) + { + if(logger.isWarnEnabled()) + { + logger.warn("rename of this scenario: to " + r.getTo()); + } + + name = r.getTo(); + + if (fileHandleReadWrite != null) + { + fileHandleReadWrite.setName(r.getTo()); + fileHandleReadWrite.setFullName(r.getToPath()); + } + + if (fileHandleReadOnly != null) + { + fileHandleReadOnly.setName(r.getTo()); + fileHandleReadOnly.setFullName(r.getToPath()); + } + } + } + } +} + diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameCreateShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameCreateShuffle.java index bdc08886f5..d48745e539 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameCreateShuffle.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameCreateShuffle.java @@ -1,88 +1,88 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -public class ScenarioRenameCreateShuffle implements Scenario -{ - private static Log logger = LogFactory.getLog(ScenarioRenameCreateShuffle.class); - - /** - * The regex pattern of a create that will trigger a new instance of - * the scenario. - */ - private Pattern pattern; - private String strPattern; - - private long timeout = 30000; - - @Override - public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) - { - /** - * This scenario is triggered by a rename of a file matching - * the pattern - */ - if(operation instanceof RenameFileOperation) - { - RenameFileOperation r = (RenameFileOperation)operation; - - Matcher m = pattern.matcher(r.getTo()); - if(m.matches()) - { - if(logger.isDebugEnabled()) - { - logger.debug("New Scenario Rename Shuffle Create Instance strPattern:" + pattern); - } - ScenarioRenameCreateShuffleInstance instance = new ScenarioRenameCreateShuffleInstance(); - instance.setTimeout(timeout); - instance.setRanking(ranking); - return instance; - } - } - - // No not interested. - return null; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setPattern(String pattern) - { - this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); - this.strPattern = pattern; - } - - public String getPattern() - { - return strPattern; - } - - private Ranking ranking = Ranking.HIGH; - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public Ranking getRanking() - { - return ranking; - } -} - - +package org.alfresco.filesys.repo.rules; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class ScenarioRenameCreateShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioRenameCreateShuffle.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + private long timeout = 30000; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * This scenario is triggered by a rename of a file matching + * the pattern + */ + if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + + Matcher m = pattern.matcher(r.getTo()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Rename Shuffle Create Instance strPattern:" + pattern); + } + ScenarioRenameCreateShuffleInstance instance = new ScenarioRenameCreateShuffleInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return strPattern; + } + + private Ranking ranking = Ranking.HIGH; + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} + + diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameCreateShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameCreateShuffleInstance.java index 317f78198e..407d9b35e2 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameCreateShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameCreateShuffleInstance.java @@ -1,130 +1,130 @@ -package org.alfresco.filesys.repo.rules; - - -import java.util.ArrayList; -import java.util.Date; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; -import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; -import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; -import org.alfresco.filesys.repo.rules.commands.SoftRenameFileCommand; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.alfresco.jlan.server.filesys.FileName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * A rename create scenario is ... - * - * a) Rename File to File~ - * b) Create File - * - * This rule will kick in and copy the content and then switch the two file over. - * - */ -class ScenarioRenameCreateShuffleInstance implements ScenarioInstance -{ - private static Log logger = LogFactory.getLog(ScenarioRenameShuffleInstance.class); - - private Date startTime = new Date(); - - /** - * Timeout in ms. Default 30 seconds. - */ - private long timeout = 30000; - - private boolean isComplete = false; - - private Ranking ranking = Ranking.HIGH; - - enum InternalState - { - NONE, - INITIALISED, - LOOK_FOR_DELETE - } ; - - InternalState state = InternalState.NONE; - - String from; - String to; - - /** - * Evaluate the next operation - * @param operation - */ - public Command evaluate(Operation operation) - { - /** - * Anti-pattern : timeout - */ - Date now = new Date(); - if(now.getTime() > startTime.getTime() + getTimeout()) - { - if(logger.isDebugEnabled()) - { - logger.debug("Instance timed out"); - } - isComplete = true; - return null; - } - - switch (state) - { - case NONE: - if(operation instanceof RenameFileOperation) - { - logger.debug("New scenario initialised"); - RenameFileOperation r = (RenameFileOperation)operation; - this.from = r.getFrom(); - this.to = r.getTo(); - state = InternalState.INITIALISED; - - SoftRenameFileCommand r1 = new SoftRenameFileCommand(from, to, r.getRootNodeRef(), r.getFromPath(), r.getToPath()); - isComplete = true; - return r1; - } - break; - - } - - - return null; - } - - @Override - public boolean isComplete() - { - return isComplete; - } - - public String toString() - { - return "ScenarioRenameCreateShuffleInstance from:" + from + " to:" + to; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - @Override - public Ranking getRanking() - { - return ranking; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } -} +package org.alfresco.filesys.repo.rules; + + +import java.util.ArrayList; +import java.util.Date; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.commands.SoftRenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A rename create scenario is ... + * + * a) Rename File to File~ + * b) Create File + * + * This rule will kick in and copy the content and then switch the two file over. + * + */ +class ScenarioRenameCreateShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioRenameShuffleInstance.class); + + private Date startTime = new Date(); + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete = false; + + private Ranking ranking = Ranking.HIGH; + + enum InternalState + { + NONE, + INITIALISED, + LOOK_FOR_DELETE + } ; + + InternalState state = InternalState.NONE; + + String from; + String to; + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out"); + } + isComplete = true; + return null; + } + + switch (state) + { + case NONE: + if(operation instanceof RenameFileOperation) + { + logger.debug("New scenario initialised"); + RenameFileOperation r = (RenameFileOperation)operation; + this.from = r.getFrom(); + this.to = r.getTo(); + state = InternalState.INITIALISED; + + SoftRenameFileCommand r1 = new SoftRenameFileCommand(from, to, r.getRootNodeRef(), r.getFromPath(), r.getToPath()); + isComplete = true; + return r1; + } + break; + + } + + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + public String toString() + { + return "ScenarioRenameCreateShuffleInstance from:" + from + " to:" + to; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java index f0b0018dd0..2ed9b2c70f 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java @@ -1,97 +1,97 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * The "Vi" rename shuffle is a sequence where a file is moved out of the way - * and then a new copy of the file put into place. - * - * a) Rename File to File~ - * b) Create File - * c) Delete File~ - * - */ -public class ScenarioRenameShuffle implements Scenario -{ - private static Log logger = LogFactory.getLog(ScenarioRenameShuffle.class); - - /** - * The regex pattern of a create that will trigger a new instance of - * the scenario. - */ - private Pattern pattern; - private String strPattern; - - private long timeout = 30000; - - @Override - public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) - { - /** - * This scenario is triggered by a rename of a file matching - * the pattern - */ - if(operation instanceof RenameFileOperation) - { - RenameFileOperation r = (RenameFileOperation)operation; - - Matcher m = pattern.matcher(r.getTo()); - if(m.matches()) - { - if(logger.isDebugEnabled()) - { - logger.debug("New Scenario Rename Shuffle Instance strPattern:" + pattern); - } - ScenarioRenameShuffleInstance instance = new ScenarioRenameShuffleInstance(); - instance.setTimeout(timeout); - instance.setRanking(ranking); - return instance; - } - } - - // No not interested. - return null; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setPattern(String pattern) - { - this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); - this.strPattern = pattern; - } - - public String getPattern() - { - return strPattern; - } - - private Ranking ranking = Ranking.HIGH; - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public Ranking getRanking() - { - return ranking; - } - - -} +package org.alfresco.filesys.repo.rules; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The "Vi" rename shuffle is a sequence where a file is moved out of the way + * and then a new copy of the file put into place. + * + * a) Rename File to File~ + * b) Create File + * c) Delete File~ + * + */ +public class ScenarioRenameShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioRenameShuffle.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + private long timeout = 30000; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * This scenario is triggered by a rename of a file matching + * the pattern + */ + if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + + Matcher m = pattern.matcher(r.getTo()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Rename Shuffle Instance strPattern:" + pattern); + } + ScenarioRenameShuffleInstance instance = new ScenarioRenameShuffleInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return strPattern; + } + + private Ranking ranking = Ranking.HIGH; + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffleInstance.java index 684616df56..61293eb237 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffleInstance.java @@ -1,172 +1,172 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.ArrayList; -import java.util.Date; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; -import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; -import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.alfresco.jlan.server.filesys.FileName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * A rename scenario is ... - * - * a) Rename File to File~ - * b) Create File - * c) Delete File~ - * - * This rule will kick in and copy the content and then switch the two file over. - * - */ -class ScenarioRenameShuffleInstance implements ScenarioInstance -{ - private static Log logger = LogFactory.getLog(ScenarioRenameShuffleInstance.class); - - private Date startTime = new Date(); - - /** - * Timeout in ms. Default 30 seconds. - */ - private long timeout = 30000; - - private boolean isComplete = false; - - private Ranking ranking = Ranking.HIGH; - - enum InternalState - { - NONE, - INITIALISED, - LOOK_FOR_DELETE - } ; - - InternalState state = InternalState.NONE; - - String from; - String to; - - /** - * Evaluate the next operation - * @param operation Operation - */ - public Command evaluate(Operation operation) - { - /** - * Anti-pattern : timeout - */ - Date now = new Date(); - if(now.getTime() > startTime.getTime() + getTimeout()) - { - if(logger.isDebugEnabled()) - { - logger.debug("Instance timed out"); - } - isComplete = true; - return null; - } - - switch (state) - { - case NONE: - if(operation instanceof RenameFileOperation) - { - logger.debug("New scenario initialised"); - RenameFileOperation r = (RenameFileOperation)operation; - this.from = r.getFrom(); - this.to = r.getTo(); - state = InternalState.INITIALISED; - } - break; - - case INITIALISED: - - if(operation instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)operation; - if(from.equals(c.getName())) - { - logger.debug("transition to LOOK_FOR_DELETE"); - - state = InternalState.LOOK_FOR_DELETE; - } - } - break; - - case LOOK_FOR_DELETE: - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation d = (DeleteFileOperation)operation; - if(to.equals(d.getName())) - { - logger.debug("Rename shuffle complete - fire!"); - - String[] paths = FileName.splitPath(d.getPath()); - String oldFolder = paths[0]; - - /** - * Shuffle is as follows - * a) Copy content from File to File~ - * b) Delete File - * c) Rename File~ to File - */ - ArrayList commands = new ArrayList(); - CopyContentCommand copyContent = new CopyContentCommand(from, to, d.getRootNodeRef(), oldFolder + "\\" + from, oldFolder + "\\" + to); - RenameFileCommand r1 = new RenameFileCommand(to, from, d.getRootNodeRef(), oldFolder + "\\" + to, oldFolder + "\\" + from); - DeleteFileCommand d1 = new DeleteFileCommand(from, d.getRootNodeRef(), oldFolder + "\\" + from); - - commands.add(copyContent); - commands.add(d1); - commands.add(r1); - - logger.debug("Scenario complete"); - isComplete = true; - return new CompoundCommand(commands); - } - } - } - - - return null; - } - - @Override - public boolean isComplete() - { - return isComplete; - } - - public String toString() - { - return "ScenarioRenameShuffleInstance from:" + from + " to:" + to; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - @Override - public Ranking getRanking() - { - return ranking; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - -} - +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.Date; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A rename scenario is ... + * + * a) Rename File to File~ + * b) Create File + * c) Delete File~ + * + * This rule will kick in and copy the content and then switch the two file over. + * + */ +class ScenarioRenameShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioRenameShuffleInstance.class); + + private Date startTime = new Date(); + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete = false; + + private Ranking ranking = Ranking.HIGH; + + enum InternalState + { + NONE, + INITIALISED, + LOOK_FOR_DELETE + } ; + + InternalState state = InternalState.NONE; + + String from; + String to; + + /** + * Evaluate the next operation + * @param operation Operation + */ + public Command evaluate(Operation operation) + { + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out"); + } + isComplete = true; + return null; + } + + switch (state) + { + case NONE: + if(operation instanceof RenameFileOperation) + { + logger.debug("New scenario initialised"); + RenameFileOperation r = (RenameFileOperation)operation; + this.from = r.getFrom(); + this.to = r.getTo(); + state = InternalState.INITIALISED; + } + break; + + case INITIALISED: + + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + if(from.equals(c.getName())) + { + logger.debug("transition to LOOK_FOR_DELETE"); + + state = InternalState.LOOK_FOR_DELETE; + } + } + break; + + case LOOK_FOR_DELETE: + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(to.equals(d.getName())) + { + logger.debug("Rename shuffle complete - fire!"); + + String[] paths = FileName.splitPath(d.getPath()); + String oldFolder = paths[0]; + + /** + * Shuffle is as follows + * a) Copy content from File to File~ + * b) Delete File + * c) Rename File~ to File + */ + ArrayList commands = new ArrayList(); + CopyContentCommand copyContent = new CopyContentCommand(from, to, d.getRootNodeRef(), oldFolder + "\\" + from, oldFolder + "\\" + to); + RenameFileCommand r1 = new RenameFileCommand(to, from, d.getRootNodeRef(), oldFolder + "\\" + to, oldFolder + "\\" + from); + DeleteFileCommand d1 = new DeleteFileCommand(from, d.getRootNodeRef(), oldFolder + "\\" + from); + + commands.add(copyContent); + commands.add(d1); + commands.add(r1); + + logger.debug("Scenario complete"); + isComplete = true; + return new CompoundCommand(commands); + } + } + } + + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + public String toString() + { + return "ScenarioRenameShuffleInstance from:" + from + " to:" + to; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + +} + diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioResult.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioResult.java index 34e6b6c6af..6d0438ab26 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioResult.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioResult.java @@ -1,14 +1,14 @@ - -package org.alfresco.filesys.repo.rules; - -public class ScenarioResult -{ - public ScenarioResult(ScenarioInstance scenario, Command command) - { - this.scenario = scenario; - this.command = command; - } - ScenarioInstance scenario; - Command command; -}; - + +package org.alfresco.filesys.repo.rules; + +public class ScenarioResult +{ + public ScenarioResult(ScenarioInstance scenario, Command command) + { + this.scenario = scenario; + this.command = command; + } + ScenarioInstance scenario; + Command command; +}; + diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java index 08fe5ad154..5ff864f44d 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java @@ -1,43 +1,43 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.List; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; - -/** - * The Bog Standard Instance. This always executes. - * - * @author mrogers - */ -public class ScenarioSimpleNonBuffered implements Scenario -{ - private ScenarioSimpleNonBufferedInstance instance = new ScenarioSimpleNonBufferedInstance(); - - private Ranking ranking = Ranking.LOW; - - @Override - public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) - { - /** - * The bog standard scenario is always interested. - */ - return instance; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - instance.setRanking(ranking); - } - - public Ranking getRanking() - { - return ranking; - } - - public String toString() - { - return "ScenarioSimpleNonBuffered - default instance"; - } - -} +package org.alfresco.filesys.repo.rules; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; + +/** + * The Bog Standard Instance. This always executes. + * + * @author mrogers + */ +public class ScenarioSimpleNonBuffered implements Scenario +{ + private ScenarioSimpleNonBufferedInstance instance = new ScenarioSimpleNonBufferedInstance(); + + private Ranking ranking = Ranking.LOW; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * The bog standard scenario is always interested. + */ + return instance; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + instance.setRanking(ranking); + } + + public Ranking getRanking() + { + return ranking; + } + + public String toString() + { + return "ScenarioSimpleNonBuffered - default instance"; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBufferedInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBufferedInstance.java index c5ef4b3d51..2a3f59805c 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBufferedInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBufferedInstance.java @@ -1,118 +1,118 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.ArrayList; - -import org.alfresco.filesys.repo.TempNetworkFile; -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; -import org.alfresco.filesys.repo.rules.commands.CreateFileCommand; -import org.alfresco.filesys.repo.rules.commands.DoNothingCommand; -import org.alfresco.filesys.repo.rules.commands.MoveFileCommand; -import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; -import org.alfresco.filesys.repo.rules.commands.OpenFileCommand; -import org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand; -import org.alfresco.filesys.repo.rules.commands.RemoveNoContentFileOnError; -import org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand; -import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; -import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; -import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.alfresco.jlan.server.filesys.NetworkFile; - -/** - * The Simple Standard Scenario is what will be done if no other - * scenario intervenes. - */ -public class ScenarioSimpleNonBufferedInstance implements ScenarioInstance -{ - private Ranking ranking = Ranking.LOW; - - @Override - public Command evaluate(Operation operation) - { - if(operation instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)operation; - return new CreateFileCommand(c.getName(), c.getRootNodeRef(), c.getPath(), c.getAllocationSize(), c.isHidden()); - } - else if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation d = (DeleteFileOperation)operation; - return new DeleteFileCommand(d.getName(), d.getRootNodeRef(), d.getPath()); - } - else if(operation instanceof RenameFileOperation) - { - RenameFileOperation r = (RenameFileOperation)operation; - return new RenameFileCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); - } - else if(operation instanceof MoveFileOperation) - { - MoveFileOperation m = (MoveFileOperation)operation; - return new MoveFileCommand(m.getFrom(), m.getTo(), m.getRootNodeRef(), m.getFromPath(), m.getToPath()); - } - else if(operation instanceof OpenFileOperation) - { - OpenFileOperation o = (OpenFileOperation)operation; - return new OpenFileCommand(o.getName(), o.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath()); - } - else if(operation instanceof CloseFileOperation) - { - CloseFileOperation c = (CloseFileOperation)operation; - - NetworkFile file = c.getNetworkFile(); - - ArrayList commands = new ArrayList(); - ArrayList postCommitCommands = new ArrayList(); - ArrayList postErrorCommands = new ArrayList(); - - commands.add(new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); - - // postErrorCommands.add(new RemoveNoContentFileOnError(c.getName(), c.getRootNodeRef(), c.getPath())); - - if(c.isDeleteOnClose()) - { - postCommitCommands.add(new ReduceQuotaCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); - } - - if (file instanceof TempNetworkFile) - { - postCommitCommands.add(new RemoveTempFileCommand((TempNetworkFile)file)); - } - - return new CompoundCommand(commands, postCommitCommands, postErrorCommands); - - } - else return new DoNothingCommand(); - } - - @Override - public boolean isComplete() - { - /** - * This instance is always complete - */ - return true; - } - - - @Override - public Ranking getRanking() - { - return ranking; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public String toString() - { - return "ScenarioSimpleNonBuffered default instance"; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; + +import org.alfresco.filesys.repo.TempNetworkFile; +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.CreateFileCommand; +import org.alfresco.filesys.repo.rules.commands.DoNothingCommand; +import org.alfresco.filesys.repo.rules.commands.MoveFileCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.OpenFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand; +import org.alfresco.filesys.repo.rules.commands.RemoveNoContentFileOnError; +import org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.NetworkFile; + +/** + * The Simple Standard Scenario is what will be done if no other + * scenario intervenes. + */ +public class ScenarioSimpleNonBufferedInstance implements ScenarioInstance +{ + private Ranking ranking = Ranking.LOW; + + @Override + public Command evaluate(Operation operation) + { + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + return new CreateFileCommand(c.getName(), c.getRootNodeRef(), c.getPath(), c.getAllocationSize(), c.isHidden()); + } + else if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + return new DeleteFileCommand(d.getName(), d.getRootNodeRef(), d.getPath()); + } + else if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + return new RenameFileCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); + } + else if(operation instanceof MoveFileOperation) + { + MoveFileOperation m = (MoveFileOperation)operation; + return new MoveFileCommand(m.getFrom(), m.getTo(), m.getRootNodeRef(), m.getFromPath(), m.getToPath()); + } + else if(operation instanceof OpenFileOperation) + { + OpenFileOperation o = (OpenFileOperation)operation; + return new OpenFileCommand(o.getName(), o.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath()); + } + else if(operation instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)operation; + + NetworkFile file = c.getNetworkFile(); + + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + + commands.add(new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + + // postErrorCommands.add(new RemoveNoContentFileOnError(c.getName(), c.getRootNodeRef(), c.getPath())); + + if(c.isDeleteOnClose()) + { + postCommitCommands.add(new ReduceQuotaCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + } + + if (file instanceof TempNetworkFile) + { + postCommitCommands.add(new RemoveTempFileCommand((TempNetworkFile)file)); + } + + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + + } + else return new DoNothingCommand(); + } + + @Override + public boolean isComplete() + { + /** + * This instance is always complete + */ + return true; + } + + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioSimpleNonBuffered default instance"; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffle.java index 0a3cf65c7c..620b1eeac9 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffle.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffle.java @@ -1,209 +1,209 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; -import org.alfresco.jlan.server.filesys.FileName; -import org.alfresco.util.MaxSizeMap; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * A temp delete shuffle. - * - * Files are created in a temporary directory - * and then a delete and move. - */ -public class ScenarioTempDeleteShuffle implements Scenario -{ - private static Log logger = LogFactory.getLog(ScenarioTempDeleteShuffle.class); - - protected final static String SCENARIO_KEY = "org.alfresco.filesys.repo.rules.ScenarioTempDeleteShuffle"; - - /** - * The regex pattern of a create that will identify a temporary directory. - */ - private Pattern tempDirPattern; - private String strTempDirPattern; - - /** - * The regex pattern of a create that will trigger a new instance of - * the scenario. - */ - private Pattern pattern; - private String strPattern; - - - private long timeout = 30000; - - private Ranking ranking = Ranking.HIGH; - - @Override - public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) - { - /** - * This scenario is triggered by a delete of a file matching - * the pattern - */ - if(operation instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)operation; - - // check whether file is below .TemporaryItems - String path = c.getPath(); - - // if path contains .TemporaryItems - Matcher d = tempDirPattern.matcher(path); - if(d.matches()) - { - logger.debug("pattern matches temp dir folder so this is a new create in a temp dir"); - Matcher m = pattern.matcher(c.getName()); - if(m.matches()) - { - // and how to lock - since we are already have one lock on the scenarios/folder here - // this is a potential deadlock and synchronization bottleneck - Map createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); - - if(createdTempFiles == null) - { - synchronized(ctx.getSessionState()) - { - logger.debug("created new temp file map and added it to the session state"); - createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); - if(createdTempFiles == null) - { - createdTempFiles = Collections.synchronizedMap(new MaxSizeMap(5, false)); - ctx.getSessionState().put(SCENARIO_KEY, createdTempFiles); - } - } - } - createdTempFiles.put(c.getName(), c.getName()); - - // TODO - Return a different scenario instance here ??? - // So it can time out and have anti-patterns etc? - } - } - } - - if (operation instanceof MoveFileOperation) - { - MoveFileOperation mf = (MoveFileOperation)operation; - - // check whether file is below .TemporaryItems - String path = mf.getFromPath(); - - // if path contains .TemporaryItems - Matcher d = tempDirPattern.matcher(path); - if(d.matches()) - { - logger.debug("pattern matches temp dir folder so this is a new create in a temp dir"); - Matcher m = pattern.matcher(mf.getFrom()); - if(m.matches()) - { - // and how to lock - since we are already have one lock on the scenarios/folder here - // this is a potential deadlock and synchronization bottleneck - Map createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); - - if(createdTempFiles == null) - { - synchronized(ctx.getSessionState()) - { - logger.debug("created new temp file map and added it to the session state"); - createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); - if(createdTempFiles == null) - { - createdTempFiles = Collections.synchronizedMap(new MaxSizeMap(5, false)); - ctx.getSessionState().put(SCENARIO_KEY, createdTempFiles); - } - } - } - createdTempFiles.remove(mf.getFrom()); - - // TODO - Return a different scenario instance here ??? - // So it can time out and have anti-patterns etc? - } - } - } - - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation c = (DeleteFileOperation)operation; - - Matcher m = pattern.matcher(c.getName()); - if(m.matches()) - { - Map createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); - - if(createdTempFiles != null) - { - if(createdTempFiles.containsKey(c.getName())) - { - if(logger.isDebugEnabled()) - { - logger.debug("New Scenario Temp Delete Shuffle Instance:" + c.getName()); - } - - ScenarioTempDeleteShuffleInstance instance = new ScenarioTempDeleteShuffleInstance() ; - instance.setTimeout(timeout); - instance.setRanking(ranking); - return instance; - } - } - } - } - - // No not interested. - return null; - - } - - public void setPattern(String pattern) - { - this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); - this.strPattern = pattern; - } - - public String getPattern() - { - return this.strPattern; - } - - public void setTempDirPattern(String tempDirPattern) - { - this.tempDirPattern = Pattern.compile(tempDirPattern, Pattern.CASE_INSENSITIVE); - this.strTempDirPattern = tempDirPattern; - } - - public String getTempDirPattern() - { - return this.strTempDirPattern; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public Ranking getRanking() - { - return ranking; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.util.MaxSizeMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A temp delete shuffle. + * + * Files are created in a temporary directory + * and then a delete and move. + */ +public class ScenarioTempDeleteShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioTempDeleteShuffle.class); + + protected final static String SCENARIO_KEY = "org.alfresco.filesys.repo.rules.ScenarioTempDeleteShuffle"; + + /** + * The regex pattern of a create that will identify a temporary directory. + */ + private Pattern tempDirPattern; + private String strTempDirPattern; + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + + private long timeout = 30000; + + private Ranking ranking = Ranking.HIGH; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * This scenario is triggered by a delete of a file matching + * the pattern + */ + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + + // check whether file is below .TemporaryItems + String path = c.getPath(); + + // if path contains .TemporaryItems + Matcher d = tempDirPattern.matcher(path); + if(d.matches()) + { + logger.debug("pattern matches temp dir folder so this is a new create in a temp dir"); + Matcher m = pattern.matcher(c.getName()); + if(m.matches()) + { + // and how to lock - since we are already have one lock on the scenarios/folder here + // this is a potential deadlock and synchronization bottleneck + Map createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); + + if(createdTempFiles == null) + { + synchronized(ctx.getSessionState()) + { + logger.debug("created new temp file map and added it to the session state"); + createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); + if(createdTempFiles == null) + { + createdTempFiles = Collections.synchronizedMap(new MaxSizeMap(5, false)); + ctx.getSessionState().put(SCENARIO_KEY, createdTempFiles); + } + } + } + createdTempFiles.put(c.getName(), c.getName()); + + // TODO - Return a different scenario instance here ??? + // So it can time out and have anti-patterns etc? + } + } + } + + if (operation instanceof MoveFileOperation) + { + MoveFileOperation mf = (MoveFileOperation)operation; + + // check whether file is below .TemporaryItems + String path = mf.getFromPath(); + + // if path contains .TemporaryItems + Matcher d = tempDirPattern.matcher(path); + if(d.matches()) + { + logger.debug("pattern matches temp dir folder so this is a new create in a temp dir"); + Matcher m = pattern.matcher(mf.getFrom()); + if(m.matches()) + { + // and how to lock - since we are already have one lock on the scenarios/folder here + // this is a potential deadlock and synchronization bottleneck + Map createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); + + if(createdTempFiles == null) + { + synchronized(ctx.getSessionState()) + { + logger.debug("created new temp file map and added it to the session state"); + createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); + if(createdTempFiles == null) + { + createdTempFiles = Collections.synchronizedMap(new MaxSizeMap(5, false)); + ctx.getSessionState().put(SCENARIO_KEY, createdTempFiles); + } + } + } + createdTempFiles.remove(mf.getFrom()); + + // TODO - Return a different scenario instance here ??? + // So it can time out and have anti-patterns etc? + } + } + } + + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation c = (DeleteFileOperation)operation; + + Matcher m = pattern.matcher(c.getName()); + if(m.matches()) + { + Map createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY); + + if(createdTempFiles != null) + { + if(createdTempFiles.containsKey(c.getName())) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Temp Delete Shuffle Instance:" + c.getName()); + } + + ScenarioTempDeleteShuffleInstance instance = new ScenarioTempDeleteShuffleInstance() ; + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + } + } + + // No not interested. + return null; + + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return this.strPattern; + } + + public void setTempDirPattern(String tempDirPattern) + { + this.tempDirPattern = Pattern.compile(tempDirPattern, Pattern.CASE_INSENSITIVE); + this.strTempDirPattern = tempDirPattern; + } + + public String getTempDirPattern() + { + return this.strTempDirPattern; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffleInstance.java index 60d3c46876..37d841c8c7 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffleInstance.java @@ -1,223 +1,223 @@ -package org.alfresco.filesys.repo.rules; - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.filesys.repo.rules.commands.CompoundCommand; -import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; -import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; -import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; -import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; -import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; -import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; -import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; -import org.alfresco.jlan.server.filesys.FileName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This is an instance of a "temp delete shuffle" triggered by a delete of a file matching - * a newly created file in a temporary directory. - * - *

First implemented for TextEdit from MacOS Lion - * - *

- * Sequence of operations. - * a) Temporary Directory Created - * b) Temporary file created in temporary directory. - * c) Target file deleted - * d) Temp file moved in place of target file. - * e) Temporary directory deleted. - *

- * If this filter is active then this is what happens. - * a) Temp file created - in another folder. - * b) Existing file deleted. Scenario kicks in to rename rather than delete. - * c) New file moved into place (X to Y). Scenario kicks in - * 1) renames file from step c - * 2) copies content from temp file to target file - * 3) deletes temp file. - * d) Clean up scenario. - */ -public class ScenarioTempDeleteShuffleInstance implements ScenarioInstance -{ - private static Log logger = LogFactory.getLog(ScenarioTempDeleteShuffleInstance.class); - - enum InternalState - { - NONE, - DELETE_SUBSTITUTED, // Scenario has intervened and renamed rather than delete - MOVED - } - - InternalState internalState = InternalState.NONE; - - private Date startTime = new Date(); - - private String lockName; - - private Ranking ranking; - - /** - * Timeout in ms. Default 30 seconds. - */ - private long timeout = 60000; - - private boolean isComplete; - - /** - * Keep track of deletes that we substitute with a rename - * could be more than one if scenarios overlap - * - * From, TempFileName - */ - private Map deletes = new HashMap(); - - /** - * Evaluate the next operation - * @param operation - */ - public Command evaluate(Operation operation) - { - - /** - * Anti-pattern : timeout - */ - Date now = new Date(); - if(now.getTime() > startTime.getTime() + getTimeout()) - { - if(logger.isDebugEnabled()) - { - logger.debug("Instance timed out lockName:" + lockName); - isComplete = true; - return null; - } - } - - switch (internalState) - { - - case NONE: - - /** - * Looking for target file being deleted - * - * Need to intervene and replace delete with a rename to temp file. - */ - if(operation instanceof DeleteFileOperation) - { - DeleteFileOperation d = (DeleteFileOperation)operation; - - - if(logger.isDebugEnabled()) - { - logger.debug("entering DELETE_SUBSTITUTED state: " + lockName); - } - - String tempName = ".shuffle" + d.getName(); - - deletes.put(d.getName(), tempName); - - String[] paths = FileName.splitPath(d.getPath()); - String currentFolder = paths[0]; - - RenameFileCommand r1 = new RenameFileCommand(d.getName(), tempName, d.getRootNodeRef(), d.getPath(), currentFolder + "\\" + tempName); - - internalState = InternalState.DELETE_SUBSTITUTED; - - return r1; - - } - else - { - // anything else bomb out - if(logger.isDebugEnabled()) - { - logger.debug("State error, expected a DELETE"); - } - isComplete = true; - } - break; - - case DELETE_SUBSTITUTED: - - /** - * Looking for a move operation of the deleted file - */ - if(operation instanceof MoveFileOperation) - { - MoveFileOperation m = (MoveFileOperation)operation; - - String targetFile = m.getTo(); - - if(deletes.containsKey(targetFile)) - { - String tempName = deletes.get(targetFile); - - String[] paths = FileName.splitPath(m.getToPath()); - String currentFolder = paths[0]; - - /** - * This is where the scenario fires. - * a) Rename the temp file back to the targetFile - * b) Copy content from moved file - * c) Delete rather than move file - */ - logger.debug("scenario fires"); - ArrayList commands = new ArrayList(); - - RenameFileCommand r1 = new RenameFileCommand(tempName, targetFile, m.getRootNodeRef(), currentFolder + "\\" + tempName, m.getToPath()); - - CopyContentCommand copyContent = new CopyContentCommand(m.getFrom(), targetFile, m.getRootNodeRef(), m.getFromPath(), m.getToPath()); - - DeleteFileCommand d1 = new DeleteFileCommand(m.getFrom(), m.getRootNodeRef(), m.getFromPath()); - - commands.add(r1); - commands.add(copyContent); - commands.add(d1); - - logger.debug("Scenario complete"); - isComplete = true; - - return new CompoundCommand(commands); - } - } - - } - - return null; - } - - @Override - public boolean isComplete() - { - return isComplete; - } - - @Override - public Ranking getRanking() - { - return ranking; - } - - public void setRanking(Ranking ranking) - { - this.ranking = ranking; - } - - public String toString() - { - return "ScenarioTempDeleteShuffleInstance:" + lockName; - } - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } -} +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This is an instance of a "temp delete shuffle" triggered by a delete of a file matching + * a newly created file in a temporary directory. + * + *

First implemented for TextEdit from MacOS Lion + * + *

+ * Sequence of operations. + * a) Temporary Directory Created + * b) Temporary file created in temporary directory. + * c) Target file deleted + * d) Temp file moved in place of target file. + * e) Temporary directory deleted. + *

+ * If this filter is active then this is what happens. + * a) Temp file created - in another folder. + * b) Existing file deleted. Scenario kicks in to rename rather than delete. + * c) New file moved into place (X to Y). Scenario kicks in + * 1) renames file from step c + * 2) copies content from temp file to target file + * 3) deletes temp file. + * d) Clean up scenario. + */ +public class ScenarioTempDeleteShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioTempDeleteShuffleInstance.class); + + enum InternalState + { + NONE, + DELETE_SUBSTITUTED, // Scenario has intervened and renamed rather than delete + MOVED + } + + InternalState internalState = InternalState.NONE; + + private Date startTime = new Date(); + + private String lockName; + + private Ranking ranking; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 60000; + + private boolean isComplete; + + /** + * Keep track of deletes that we substitute with a rename + * could be more than one if scenarios overlap + * + * From, TempFileName + */ + private Map deletes = new HashMap(); + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out lockName:" + lockName); + isComplete = true; + return null; + } + } + + switch (internalState) + { + + case NONE: + + /** + * Looking for target file being deleted + * + * Need to intervene and replace delete with a rename to temp file. + */ + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + + + if(logger.isDebugEnabled()) + { + logger.debug("entering DELETE_SUBSTITUTED state: " + lockName); + } + + String tempName = ".shuffle" + d.getName(); + + deletes.put(d.getName(), tempName); + + String[] paths = FileName.splitPath(d.getPath()); + String currentFolder = paths[0]; + + RenameFileCommand r1 = new RenameFileCommand(d.getName(), tempName, d.getRootNodeRef(), d.getPath(), currentFolder + "\\" + tempName); + + internalState = InternalState.DELETE_SUBSTITUTED; + + return r1; + + } + else + { + // anything else bomb out + if(logger.isDebugEnabled()) + { + logger.debug("State error, expected a DELETE"); + } + isComplete = true; + } + break; + + case DELETE_SUBSTITUTED: + + /** + * Looking for a move operation of the deleted file + */ + if(operation instanceof MoveFileOperation) + { + MoveFileOperation m = (MoveFileOperation)operation; + + String targetFile = m.getTo(); + + if(deletes.containsKey(targetFile)) + { + String tempName = deletes.get(targetFile); + + String[] paths = FileName.splitPath(m.getToPath()); + String currentFolder = paths[0]; + + /** + * This is where the scenario fires. + * a) Rename the temp file back to the targetFile + * b) Copy content from moved file + * c) Delete rather than move file + */ + logger.debug("scenario fires"); + ArrayList commands = new ArrayList(); + + RenameFileCommand r1 = new RenameFileCommand(tempName, targetFile, m.getRootNodeRef(), currentFolder + "\\" + tempName, m.getToPath()); + + CopyContentCommand copyContent = new CopyContentCommand(m.getFrom(), targetFile, m.getRootNodeRef(), m.getFromPath(), m.getToPath()); + + DeleteFileCommand d1 = new DeleteFileCommand(m.getFrom(), m.getRootNodeRef(), m.getFromPath()); + + commands.add(r1); + commands.add(copyContent); + commands.add(d1); + + logger.debug("Scenario complete"); + isComplete = true; + + return new CompoundCommand(commands); + } + } + + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioTempDeleteShuffleInstance:" + lockName; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/VirtualFilesystem.java b/source/java/org/alfresco/filesys/repo/rules/VirtualFilesystem.java index 7bdd7601bf..2a8fa8702b 100644 --- a/source/java/org/alfresco/filesys/repo/rules/VirtualFilesystem.java +++ b/source/java/org/alfresco/filesys/repo/rules/VirtualFilesystem.java @@ -1,6 +1,6 @@ -package org.alfresco.filesys.repo.rules; - -public class VirtualFilesystem -{ - -} +package org.alfresco.filesys.repo.rules; + +public class VirtualFilesystem +{ + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CallbackCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CallbackCommand.java index e0df86096e..f02c2dec76 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/CallbackCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CallbackCommand.java @@ -1,22 +1,22 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; - -/** - * Callback command. - * - * Makes a callback when executed. - */ -public class CallbackCommand implements Command -{ - public CallbackCommand() - { - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; + +/** + * Callback command. + * + * Makes a callback when executed. + */ +public class CallbackCommand implements Command +{ + public CallbackCommand() + { + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CloseFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CloseFileCommand.java index 08515f9ad0..0a40de4224 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/CloseFileCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CloseFileCommand.java @@ -1,52 +1,52 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Open File command - */ -public class CloseFileCommand implements Command -{ - private String name; - private String path; - private NodeRef rootNode; - - private NetworkFile networkFile; - - public CloseFileCommand(String name, NetworkFile networkFile, NodeRef rootNode, String path) - { - this.name = name; - this.networkFile = networkFile; - this.rootNode = rootNode; - this.path = path; - } - - public String getName() - { - return name; - } - - public NetworkFile getNetworkFile() - { - return networkFile; - } - - public NodeRef getRootNodeRef() - { - return rootNode; - } - - public String getPath() - { - return path; - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_READ_WRITE; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Open File command + */ +public class CloseFileCommand implements Command +{ + private String name; + private String path; + private NodeRef rootNode; + + private NetworkFile networkFile; + + public CloseFileCommand(String name, NetworkFile networkFile, NodeRef rootNode, String path) + { + this.name = name; + this.networkFile = networkFile; + this.rootNode = rootNode; + this.path = path; + } + + public String getName() + { + return name; + } + + public NetworkFile getNetworkFile() + { + return networkFile; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CompoundCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CompoundCommand.java index a2dd9b002e..c48b43652a 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/CompoundCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CompoundCommand.java @@ -1,80 +1,80 @@ -package org.alfresco.filesys.repo.rules.commands; - -import java.util.ArrayList; -import java.util.List; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; - -/** - * A compound operation contains one or more other commands. - */ -public class CompoundCommand implements Command -{ - List commands; - - List postCommitCommands; - - List postErrorCommands; - - /** - * New Compound Command containing the specified commands. - * @param commands - */ - public CompoundCommand(List commands) - { - this.commands = new ArrayList(commands); - } - - /** - * New Compound Command containing the specified commands. - * @param commands - */ - public CompoundCommand(List commands, List postCommitCommands) - { - this.commands = new ArrayList(commands); - - this.postCommitCommands = new ArrayList(postCommitCommands); - } - - public CompoundCommand(List commands, List postCommitCommands, ListpostErrorCommands) - { - this.commands = new ArrayList(commands); - - this.postCommitCommands = new ArrayList(postCommitCommands); - - this.postErrorCommands = new ArrayList(postErrorCommands); - } - - public List getCommands() - { - return commands; - } - - public List getPostCommitCommands() - { - return postCommitCommands; - } - - public List getPostErrorCommands() - { - return postErrorCommands; - } - - @Override - public TxnReadState getTransactionRequired() - { - TxnReadState readState = TxnReadState.TXN_NONE; - for(Command command : commands) - { - TxnReadState x = command.getTransactionRequired(); - - if(x != null && x.compareTo(readState) > 0) - { - readState = x; - } - } - - return readState; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; + +/** + * A compound operation contains one or more other commands. + */ +public class CompoundCommand implements Command +{ + List commands; + + List postCommitCommands; + + List postErrorCommands; + + /** + * New Compound Command containing the specified commands. + * @param commands + */ + public CompoundCommand(List commands) + { + this.commands = new ArrayList(commands); + } + + /** + * New Compound Command containing the specified commands. + * @param commands + */ + public CompoundCommand(List commands, List postCommitCommands) + { + this.commands = new ArrayList(commands); + + this.postCommitCommands = new ArrayList(postCommitCommands); + } + + public CompoundCommand(List commands, List postCommitCommands, ListpostErrorCommands) + { + this.commands = new ArrayList(commands); + + this.postCommitCommands = new ArrayList(postCommitCommands); + + this.postErrorCommands = new ArrayList(postErrorCommands); + } + + public List getCommands() + { + return commands; + } + + public List getPostCommitCommands() + { + return postCommitCommands; + } + + public List getPostErrorCommands() + { + return postErrorCommands; + } + + @Override + public TxnReadState getTransactionRequired() + { + TxnReadState readState = TxnReadState.TXN_NONE; + for(Command command : commands) + { + TxnReadState x = command.getTransactionRequired(); + + if(x != null && x.compareTo(readState) > 0) + { + readState = x; + } + } + + return readState; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CopyContentCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CopyContentCommand.java index 09fe59202f..e45570280c 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/CopyContentCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CopyContentCommand.java @@ -1,63 +1,63 @@ -package org.alfresco.filesys.repo.rules.commands; - -import java.util.List; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * CopyContent command - * - * Copy just the content from one node to another - * - */ -public class CopyContentCommand implements Command -{ - - private String from; - private String to; - private NodeRef rootNode; - private String fromPath; - private String toPath; - - public CopyContentCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath) - { - this.from = from; - this.to = to; - this.rootNode = rootNode; - this.fromPath = fromPath; - this.toPath = toPath; - } - - public String getTo() - { - return from; - } - - public String getFrom() - { - return from; - } - - public NodeRef getRootNode() - { - return rootNode; - } - - public String getFromPath() - { - return fromPath; - } - - public String getToPath() - { - return toPath; - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_READ_WRITE; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * CopyContent command + * + * Copy just the content from one node to another + * + */ +public class CopyContentCommand implements Command +{ + + private String from; + private String to; + private NodeRef rootNode; + private String fromPath; + private String toPath; + + public CopyContentCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath) + { + this.from = from; + this.to = to; + this.rootNode = rootNode; + this.fromPath = fromPath; + this.toPath = toPath; + } + + public String getTo() + { + return from; + } + + public String getFrom() + { + return from; + } + + public NodeRef getRootNode() + { + return rootNode; + } + + public String getFromPath() + { + return fromPath; + } + + public String getToPath() + { + return toPath; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CreateFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CreateFileCommand.java index abc8c8df2c..64df3de32c 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/CreateFileCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CreateFileCommand.java @@ -1,66 +1,66 @@ -package org.alfresco.filesys.repo.rules.commands; - -import java.util.List; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * CreateFile command - */ -public class CreateFileCommand implements Command -{ - private String name; - private NodeRef rootNode; - private String path; - private long allocationSize; - private boolean isHidden; - - public CreateFileCommand(String name, NodeRef rootNode, String path, long allocationSize, boolean isHidden) - { - this.name = name; - this.path = path; - this.rootNode = rootNode; - this.allocationSize = allocationSize; - this.isHidden = isHidden; - } - - public String getName() - { - return name; - } - - public NodeRef getRootNode() - { - return rootNode; - } - - public String getPath() - { - return path; - } - - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_READ_WRITE; - } - - public void setAllocationSize(long allocationSize) - { - this.allocationSize = allocationSize; - } - - public long getAllocationSize() - { - return allocationSize; - } - - public boolean isHidden() - { - return isHidden; - } - -} +package org.alfresco.filesys.repo.rules.commands; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * CreateFile command + */ +public class CreateFileCommand implements Command +{ + private String name; + private NodeRef rootNode; + private String path; + private long allocationSize; + private boolean isHidden; + + public CreateFileCommand(String name, NodeRef rootNode, String path, long allocationSize, boolean isHidden) + { + this.name = name; + this.path = path; + this.rootNode = rootNode; + this.allocationSize = allocationSize; + this.isHidden = isHidden; + } + + public String getName() + { + return name; + } + + public NodeRef getRootNode() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } + + public void setAllocationSize(long allocationSize) + { + this.allocationSize = allocationSize; + } + + public long getAllocationSize() + { + return allocationSize; + } + + public boolean isHidden() + { + return isHidden; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/DeleteFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/DeleteFileCommand.java index bc873316df..0a3b3983f9 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/DeleteFileCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/DeleteFileCommand.java @@ -1,49 +1,49 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Delete File command - */ -public class DeleteFileCommand implements Command -{ - private String name; - private NodeRef rootNode; - private String path; - - /** - * - * @param name name of file - * @param rootNode root node - * @param path full path of file - */ - public DeleteFileCommand(String name, NodeRef rootNode, String path) - { - this.name = name; - this.rootNode = rootNode; - this.path = path; - } - - public String getName() - { - return name; - } - - public NodeRef getRootNode() - { - return rootNode; - } - - public String getPath() - { - return path; - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_READ_WRITE; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Delete File command + */ +public class DeleteFileCommand implements Command +{ + private String name; + private NodeRef rootNode; + private String path; + + /** + * + * @param name name of file + * @param rootNode root node + * @param path full path of file + */ + public DeleteFileCommand(String name, NodeRef rootNode, String path) + { + this.name = name; + this.rootNode = rootNode; + this.path = path; + } + + public String getName() + { + return name; + } + + public NodeRef getRootNode() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/DoNothingCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/DoNothingCommand.java index ae449b5862..832fd079ca 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/DoNothingCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/DoNothingCommand.java @@ -1,22 +1,22 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Do Nothing Command, Does what it says on the tin! - */ -public class DoNothingCommand implements Command -{ - - public DoNothingCommand() - { - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Do Nothing Command, Does what it says on the tin! + */ +public class DoNothingCommand implements Command +{ + + public DoNothingCommand() + { + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/MoveFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/MoveFileCommand.java index 5baf05c928..305a95c2cd 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/MoveFileCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/MoveFileCommand.java @@ -1,90 +1,90 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -public class MoveFileCommand implements Command -{ - - private String from; - private String to; - private NodeRef rootNode; - private String fromPath; - private String toPath; - private boolean isMoveAsSystem = false; - - public MoveFileCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath) - { - this.from = from; - this.to = to; - this.rootNode = rootNode; - this.fromPath = fromPath; - this.toPath = toPath; - } - - // ALF-16257: in shuffle scenarios if user has insufficient permissions rename should be done as System - public MoveFileCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath, boolean moveAsSystem) - { - this(from, to, rootNode, fromPath, toPath); - this.isMoveAsSystem = moveAsSystem; - } - - public String getFrom() - { - return from; - } - - public String getTo() - { - return to; - } - - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_READ_WRITE; - } - - - public void setRootNode(NodeRef rootNode) - { - this.rootNode = rootNode; - } - - - public NodeRef getRootNode() - { - return rootNode; - } - - - public void setFromPath(String fromPath) - { - this.fromPath = fromPath; - } - - - public String getFromPath() - { - return fromPath; - } - - - public void setToPath(String toPath) - { - this.toPath = toPath; - } - - - public String getToPath() - { - return toPath; - } - - public boolean isMoveAsSystem() - { - return isMoveAsSystem; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +public class MoveFileCommand implements Command +{ + + private String from; + private String to; + private NodeRef rootNode; + private String fromPath; + private String toPath; + private boolean isMoveAsSystem = false; + + public MoveFileCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath) + { + this.from = from; + this.to = to; + this.rootNode = rootNode; + this.fromPath = fromPath; + this.toPath = toPath; + } + + // ALF-16257: in shuffle scenarios if user has insufficient permissions rename should be done as System + public MoveFileCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath, boolean moveAsSystem) + { + this(from, to, rootNode, fromPath, toPath); + this.isMoveAsSystem = moveAsSystem; + } + + public String getFrom() + { + return from; + } + + public String getTo() + { + return to; + } + + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } + + + public void setRootNode(NodeRef rootNode) + { + this.rootNode = rootNode; + } + + + public NodeRef getRootNode() + { + return rootNode; + } + + + public void setFromPath(String fromPath) + { + this.fromPath = fromPath; + } + + + public String getFromPath() + { + return fromPath; + } + + + public void setToPath(String toPath) + { + this.toPath = toPath; + } + + + public String getToPath() + { + return toPath; + } + + public boolean isMoveAsSystem() + { + return isMoveAsSystem; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/OpenFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/OpenFileCommand.java index f9237be950..72defcd7d4 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/OpenFileCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/OpenFileCommand.java @@ -1,68 +1,68 @@ -package org.alfresco.filesys.repo.rules.commands; - -import java.util.List; - -import org.alfresco.filesys.repo.OpenFileMode; -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Open File command - */ -public class OpenFileCommand implements Command -{ - private String name; - private OpenFileMode mode = OpenFileMode.READ_ONLY; - private boolean truncate = false; - private String path; - private NodeRef rootNode; - - /** - * - * @param name - * @param mode - * @param truncate - * @param rootNode - * @param path - */ - public OpenFileCommand(String name, OpenFileMode mode, boolean truncate, NodeRef rootNode, String path) - { - this.name = name; - this.mode = mode; - this.truncate = truncate; - this.rootNode = rootNode; - this.path = path; - } - - public String getName() - { - return name; - } - - public String getPath() - { - return path; - } - - public NodeRef getRootNodeRef() - { - return rootNode; - } - - public OpenFileMode getMode() - { - return mode; - } - - public boolean isTruncate() - { - return truncate; - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_READ_ONLY; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import java.util.List; + +import org.alfresco.filesys.repo.OpenFileMode; +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Open File command + */ +public class OpenFileCommand implements Command +{ + private String name; + private OpenFileMode mode = OpenFileMode.READ_ONLY; + private boolean truncate = false; + private String path; + private NodeRef rootNode; + + /** + * + * @param name + * @param mode + * @param truncate + * @param rootNode + * @param path + */ + public OpenFileCommand(String name, OpenFileMode mode, boolean truncate, NodeRef rootNode, String path) + { + this.name = name; + this.mode = mode; + this.truncate = truncate; + this.rootNode = rootNode; + this.path = path; + } + + public String getName() + { + return name; + } + + public String getPath() + { + return path; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + public OpenFileMode getMode() + { + return mode; + } + + public boolean isTruncate() + { + return truncate; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_ONLY; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/PostCommitCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/PostCommitCommand.java index 5abe4f5b9a..26e58e41bc 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/PostCommitCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/PostCommitCommand.java @@ -1,15 +1,15 @@ -package org.alfresco.filesys.repo.rules.commands; - -import java.util.List; - -import org.alfresco.filesys.repo.rules.Command; - -/** - * The post commit command is executed after a successful completion of a command. - * @author mrogers - * - */ -public class PostCommitCommand -{ - List commands; -} +package org.alfresco.filesys.repo.rules.commands; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; + +/** + * The post commit command is executed after a successful completion of a command. + * @author mrogers + * + */ +public class PostCommitCommand +{ + List commands; +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/RemoveNoContentFileOnError.java b/source/java/org/alfresco/filesys/repo/rules/commands/RemoveNoContentFileOnError.java index 80ee52d8b0..115ec8553c 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/RemoveNoContentFileOnError.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/RemoveNoContentFileOnError.java @@ -1,51 +1,51 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Remove file with CONTENT_NO_ASPECT on error - */ -public class RemoveNoContentFileOnError implements Command -{ - private String name; - private String path; - private NodeRef rootNode; - - private NetworkFile networkFile; - - public RemoveNoContentFileOnError(String name, NodeRef rootNode, String path) - { - this.name = name; - this.rootNode = rootNode; - this.path = path; - } - - public String getName() - { - return name; - } - - public NetworkFile getNetworkFile() - { - return networkFile; - } - - public NodeRef getRootNodeRef() - { - return rootNode; - } - - public String getPath() - { - return path; - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Remove file with CONTENT_NO_ASPECT on error + */ +public class RemoveNoContentFileOnError implements Command +{ + private String name; + private String path; + private NodeRef rootNode; + + private NetworkFile networkFile; + + public RemoveNoContentFileOnError(String name, NodeRef rootNode, String path) + { + this.name = name; + this.rootNode = rootNode; + this.path = path; + } + + public String getName() + { + return name; + } + + public NetworkFile getNetworkFile() + { + return networkFile; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/RemoveTempFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/RemoveTempFileCommand.java index cd278d892f..6c04d40736 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/RemoveTempFileCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/RemoveTempFileCommand.java @@ -1,32 +1,32 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.TempNetworkFile; -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Remove a temporary file - */ -public class RemoveTempFileCommand implements Command -{ - - private TempNetworkFile networkFile; - - public RemoveTempFileCommand(TempNetworkFile file) - { - this.networkFile = file; - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } - - public TempNetworkFile getNetworkFile() - { - return networkFile; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.TempNetworkFile; +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Remove a temporary file + */ +public class RemoveTempFileCommand implements Command +{ + + private TempNetworkFile networkFile; + + public RemoveTempFileCommand(TempNetworkFile file) + { + this.networkFile = file; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } + + public TempNetworkFile getNetworkFile() + { + return networkFile; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/RenameFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/RenameFileCommand.java index 9a7693e67a..fa26f59896 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/RenameFileCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/RenameFileCommand.java @@ -1,87 +1,87 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Rename command - */ -public class RenameFileCommand implements Command -{ - - private String from; - private String to; - private NodeRef rootNode; - private String fromPath; - private String toPath; - private boolean isSoft = false; - - public RenameFileCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath) - { - this.from = from; - this.to = to; - this.rootNode = rootNode; - this.fromPath = fromPath; - this.toPath = toPath; - } - - - public String getFrom() - { - return from; - } - - public String getTo() - { - return to; - } - - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_READ_WRITE; - } - - - public void setRootNode(NodeRef rootNode) - { - this.rootNode = rootNode; - } - - - public NodeRef getRootNode() - { - return rootNode; - } - - - public void setFromPath(String fromPath) - { - this.fromPath = fromPath; - } - - - public String getFromPath() - { - return fromPath; - } - - - public void setToPath(String toPath) - { - this.toPath = toPath; - } - - - public String getToPath() - { - return toPath; - } - - public boolean isSoft() - { - return isSoft; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Rename command + */ +public class RenameFileCommand implements Command +{ + + private String from; + private String to; + private NodeRef rootNode; + private String fromPath; + private String toPath; + private boolean isSoft = false; + + public RenameFileCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath) + { + this.from = from; + this.to = to; + this.rootNode = rootNode; + this.fromPath = fromPath; + this.toPath = toPath; + } + + + public String getFrom() + { + return from; + } + + public String getTo() + { + return to; + } + + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } + + + public void setRootNode(NodeRef rootNode) + { + this.rootNode = rootNode; + } + + + public NodeRef getRootNode() + { + return rootNode; + } + + + public void setFromPath(String fromPath) + { + this.fromPath = fromPath; + } + + + public String getFromPath() + { + return fromPath; + } + + + public void setToPath(String toPath) + { + this.toPath = toPath; + } + + + public String getToPath() + { + return toPath; + } + + public boolean isSoft() + { + return isSoft; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/RestoreFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/RestoreFileCommand.java index 0c53380bf1..9f1512c24f 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/RestoreFileCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/RestoreFileCommand.java @@ -1,63 +1,63 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Restore File Command - */ -public class RestoreFileCommand implements Command -{ - private String name; - private NodeRef rootNode; - private String path; - private long allocationSize; - private NodeRef originalNodeRef; - - public RestoreFileCommand(String name, NodeRef rootNode, String path, long allocationSize, NodeRef originalNodeRef) - { - this.name = name; - this.path = path; - this.rootNode = rootNode; - this.allocationSize = allocationSize; - this.originalNodeRef = originalNodeRef; - } - - public String getName() - { - return name; - } - - public NodeRef getRootNode() - { - return rootNode; - } - - public String getPath() - { - return path; - } - - public NodeRef getOriginalNodeRef() - { - return originalNodeRef; - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_READ_WRITE; - } - - public void setAllocationSize(long allocationSize) - { - this.allocationSize = allocationSize; - } - - public long getAllocationSize() - { - return allocationSize; - } - -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Restore File Command + */ +public class RestoreFileCommand implements Command +{ + private String name; + private NodeRef rootNode; + private String path; + private long allocationSize; + private NodeRef originalNodeRef; + + public RestoreFileCommand(String name, NodeRef rootNode, String path, long allocationSize, NodeRef originalNodeRef) + { + this.name = name; + this.path = path; + this.rootNode = rootNode; + this.allocationSize = allocationSize; + this.originalNodeRef = originalNodeRef; + } + + public String getName() + { + return name; + } + + public NodeRef getRootNode() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + public NodeRef getOriginalNodeRef() + { + return originalNodeRef; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } + + public void setAllocationSize(long allocationSize) + { + this.allocationSize = allocationSize; + } + + public long getAllocationSize() + { + return allocationSize; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/ReturnValueCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/ReturnValueCommand.java index 480eb8014a..51577fe08e 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/ReturnValueCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/ReturnValueCommand.java @@ -1,29 +1,29 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; - -/** - * Return a specified value to the executor - */ -public class ReturnValueCommand implements Command -{ - - Object returnValue; - public ReturnValueCommand(Object returnValue) - { - this.returnValue = returnValue; - } - - public Object getReturnValue() - { - return returnValue; - } - - @Override - public TxnReadState getTransactionRequired() - { - - return TxnReadState.TXN_NONE; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; + +/** + * Return a specified value to the executor + */ +public class ReturnValueCommand implements Command +{ + + Object returnValue; + public ReturnValueCommand(Object returnValue) + { + this.returnValue = returnValue; + } + + public Object getReturnValue() + { + return returnValue; + } + + @Override + public TxnReadState getTransactionRequired() + { + + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/SoftRenameFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/SoftRenameFileCommand.java index 32eb9045d9..f1adb08a23 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/SoftRenameFileCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/SoftRenameFileCommand.java @@ -1,19 +1,19 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.service.cmr.repository.NodeRef; - -public class SoftRenameFileCommand extends RenameFileCommand -{ - - public SoftRenameFileCommand(String from, String to, NodeRef rootNode, - String fromPath, String toPath) - { - super(from, to, rootNode, fromPath, toPath); - } - - public boolean isSoft() - { - return true; - } - -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.service.cmr.repository.NodeRef; + +public class SoftRenameFileCommand extends RenameFileCommand +{ + + public SoftRenameFileCommand(String from, String to, NodeRef rootNode, + String fromPath, String toPath) + { + super(from, to, rootNode, fromPath, toPath); + } + + public boolean isSoft() + { + return true; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/UpdateQuotaCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/UpdateQuotaCommand.java index b62ca874d2..9ae9714336 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/UpdateQuotaCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/UpdateQuotaCommand.java @@ -1,22 +1,22 @@ -package org.alfresco.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Update the user's quota. - */ -public class UpdateQuotaCommand implements Command -{ - - public UpdateQuotaCommand() - { - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } -} +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Update the user's quota. + */ +public class UpdateQuotaCommand implements Command +{ + + public UpdateQuotaCommand() + { + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/CloseFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/CloseFileOperation.java index 69026036f2..b6110e4295 100644 --- a/source/java/org/alfresco/filesys/repo/rules/operations/CloseFileOperation.java +++ b/source/java/org/alfresco/filesys/repo/rules/operations/CloseFileOperation.java @@ -1,84 +1,84 @@ -package org.alfresco.filesys.repo.rules.operations; - -import org.alfresco.filesys.repo.rules.Operation; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Close File Operation. - *

- * Close a file with the given name. - */ -public class CloseFileOperation implements Operation -{ - private String name; - private NodeRef rootNodeRef; - private String path; - - private NetworkFile networkFile; - boolean deleteOnClose; - boolean force; - - public CloseFileOperation(String name, NetworkFile networkFile, NodeRef rootNodeRef, String path, boolean deleteOnClose, boolean force) - { - this.name = name; - this.networkFile = networkFile; - this.rootNodeRef = rootNodeRef; - this.path = path; - this.deleteOnClose = deleteOnClose; - this.force = force; - } - - public String getName() - { - return name; - } - - public NodeRef getRootNodeRef() - { - return rootNodeRef; - } - - public String getPath() - { - return path; - } - - public NetworkFile getNetworkFile() - { - return networkFile; - } - - public String toString() - { - return "CloseFileOperation: " + name; - } - - public int hashCode() - { - return name.hashCode(); - } - - public boolean isDeleteOnClose() - { - return deleteOnClose; - } - - public boolean isForce() - { - return force; - } - - public boolean equals(Object o) - { - if(o instanceof CloseFileOperation) - { - CloseFileOperation c = (CloseFileOperation)o; - if(name.equals(c.getName())) - { - return true; - } - } - return false; - } -} +package org.alfresco.filesys.repo.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Close File Operation. + *

+ * Close a file with the given name. + */ +public class CloseFileOperation implements Operation +{ + private String name; + private NodeRef rootNodeRef; + private String path; + + private NetworkFile networkFile; + boolean deleteOnClose; + boolean force; + + public CloseFileOperation(String name, NetworkFile networkFile, NodeRef rootNodeRef, String path, boolean deleteOnClose, boolean force) + { + this.name = name; + this.networkFile = networkFile; + this.rootNodeRef = rootNodeRef; + this.path = path; + this.deleteOnClose = deleteOnClose; + this.force = force; + } + + public String getName() + { + return name; + } + + public NodeRef getRootNodeRef() + { + return rootNodeRef; + } + + public String getPath() + { + return path; + } + + public NetworkFile getNetworkFile() + { + return networkFile; + } + + public String toString() + { + return "CloseFileOperation: " + name; + } + + public int hashCode() + { + return name.hashCode(); + } + + public boolean isDeleteOnClose() + { + return deleteOnClose; + } + + public boolean isForce() + { + return force; + } + + public boolean equals(Object o) + { + if(o instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)o; + if(name.equals(c.getName())) + { + return true; + } + } + return false; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/CreateFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/CreateFileOperation.java index f6a58ef555..e6974d39d8 100644 --- a/source/java/org/alfresco/filesys/repo/rules/operations/CreateFileOperation.java +++ b/source/java/org/alfresco/filesys/repo/rules/operations/CreateFileOperation.java @@ -1,80 +1,80 @@ -package org.alfresco.filesys.repo.rules.operations; - -import org.alfresco.filesys.repo.rules.Operation; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Create File Operation. - *

- * Create a file with the given name. - */ -public class CreateFileOperation implements Operation -{ - private String name; - private NodeRef rootNodeRef; - private String path; - private long allocationSize; - boolean isHidden; - - public CreateFileOperation(String name, NodeRef rootNodeRef, String path, long allocationSize, boolean isHidden) - { - this.name = name; - this.rootNodeRef = rootNodeRef; - this.path = path; - this.allocationSize = allocationSize; - this.isHidden = isHidden; - } - - public String getName() - { - return name; - } - - public String toString() - { - return "CreateFileOperation: " + name; - } - - public String getPath() - { - return path; - } - - public NodeRef getRootNodeRef() - { - return rootNodeRef; - } - - public int hashCode() - { - return name.hashCode(); - } - - public boolean equals(Object o) - { - if(o instanceof CreateFileOperation) - { - CreateFileOperation c = (CreateFileOperation)o; - if(name.equals(c.getName())) - { - return true; - } - } - return false; - } - - public void setAllocationSize(long allocationSize) - { - this.allocationSize = allocationSize; - } - - public long getAllocationSize() - { - return allocationSize; - } - - public boolean isHidden() - { - return isHidden; - } -} +package org.alfresco.filesys.repo.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Create File Operation. + *

+ * Create a file with the given name. + */ +public class CreateFileOperation implements Operation +{ + private String name; + private NodeRef rootNodeRef; + private String path; + private long allocationSize; + boolean isHidden; + + public CreateFileOperation(String name, NodeRef rootNodeRef, String path, long allocationSize, boolean isHidden) + { + this.name = name; + this.rootNodeRef = rootNodeRef; + this.path = path; + this.allocationSize = allocationSize; + this.isHidden = isHidden; + } + + public String getName() + { + return name; + } + + public String toString() + { + return "CreateFileOperation: " + name; + } + + public String getPath() + { + return path; + } + + public NodeRef getRootNodeRef() + { + return rootNodeRef; + } + + public int hashCode() + { + return name.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)o; + if(name.equals(c.getName())) + { + return true; + } + } + return false; + } + + public void setAllocationSize(long allocationSize) + { + this.allocationSize = allocationSize; + } + + public long getAllocationSize() + { + return allocationSize; + } + + public boolean isHidden() + { + return isHidden; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/DeleteFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/DeleteFileOperation.java index 64eb76b07b..d36db0d133 100644 --- a/source/java/org/alfresco/filesys/repo/rules/operations/DeleteFileOperation.java +++ b/source/java/org/alfresco/filesys/repo/rules/operations/DeleteFileOperation.java @@ -1,64 +1,64 @@ -package org.alfresco.filesys.repo.rules.operations; - -import org.alfresco.filesys.repo.rules.Operation; -import org.alfresco.service.cmr.repository.NodeRef; - -public class DeleteFileOperation implements Operation -{ - - private String name; - private NodeRef rootNodeRef; - private String path; - - /** - * Delete File Operation - * @param name of file - * @param rootNodeRef root node ref - * @param path path + name of file to delete - */ - public DeleteFileOperation(String name, NodeRef rootNodeRef, String path) - { - this.name = name; - this.rootNodeRef = rootNodeRef; - this.path = path; - } - - public String getName() - { - return name; - } - - public String getPath() - { - return path; - } - - public NodeRef getRootNodeRef() - { - return rootNodeRef; - } - - public String toString() - { - return "DeleteFileOperation: " + name; - } - - public int hashCode() - { - return name.hashCode(); - } - - public boolean equals(Object o) - { - if(o instanceof DeleteFileOperation) - { - DeleteFileOperation c = (DeleteFileOperation)o; - if(name.equals(c.getName())) - { - return true; - } - } - return false; - } - -} +package org.alfresco.filesys.repo.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.service.cmr.repository.NodeRef; + +public class DeleteFileOperation implements Operation +{ + + private String name; + private NodeRef rootNodeRef; + private String path; + + /** + * Delete File Operation + * @param name of file + * @param rootNodeRef root node ref + * @param path path + name of file to delete + */ + public DeleteFileOperation(String name, NodeRef rootNodeRef, String path) + { + this.name = name; + this.rootNodeRef = rootNodeRef; + this.path = path; + } + + public String getName() + { + return name; + } + + public String getPath() + { + return path; + } + + public NodeRef getRootNodeRef() + { + return rootNodeRef; + } + + public String toString() + { + return "DeleteFileOperation: " + name; + } + + public int hashCode() + { + return name.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof DeleteFileOperation) + { + DeleteFileOperation c = (DeleteFileOperation)o; + if(name.equals(c.getName())) + { + return true; + } + } + return false; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/MoveFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/MoveFileOperation.java index 2cf6e5e7d8..e63dd3c673 100644 --- a/source/java/org/alfresco/filesys/repo/rules/operations/MoveFileOperation.java +++ b/source/java/org/alfresco/filesys/repo/rules/operations/MoveFileOperation.java @@ -1,85 +1,85 @@ -package org.alfresco.filesys.repo.rules.operations; - -import org.alfresco.filesys.repo.rules.Operation; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Rename a file within the same directory - */ -public class MoveFileOperation implements Operation -{ - private String from; - private String to; - private String fromPath; - private String toPath; - NodeRef rootNodeRef; - - /** - * - * @param from name of file from - * @param to name of file to - * @param fromPath full path of from - * @param toPath full path of to - * @param rootNodeRef - */ - public MoveFileOperation(String from, String to, String fromPath, String toPath, NodeRef rootNodeRef) - { - this.from = from; - this.to = to; - this.fromPath = fromPath; - this.toPath = toPath; - this.rootNodeRef = rootNodeRef; - } - - - public String getFrom() - { - return from; - } - - public String getTo() - { - return to; - } - - public String getToPath() - { - return toPath; - } - - public String getFromPath() - { - return fromPath; - } - - public NodeRef getRootNodeRef() - { - return rootNodeRef; - } - - public String toString() - { - return "MoveFileOperation: from " + fromPath + " to "+ toPath; - } - - public int hashCode() - { - return fromPath.hashCode(); - } - - public boolean equals(Object o) - { - if(o instanceof MoveFileOperation) - { - MoveFileOperation r = (MoveFileOperation)o; - if(fromPath.equals(r.getFromPath()) && toPath.equals(r.getToPath())) - { - return true; - } - } - return false; - } -} - - - +package org.alfresco.filesys.repo.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Rename a file within the same directory + */ +public class MoveFileOperation implements Operation +{ + private String from; + private String to; + private String fromPath; + private String toPath; + NodeRef rootNodeRef; + + /** + * + * @param from name of file from + * @param to name of file to + * @param fromPath full path of from + * @param toPath full path of to + * @param rootNodeRef + */ + public MoveFileOperation(String from, String to, String fromPath, String toPath, NodeRef rootNodeRef) + { + this.from = from; + this.to = to; + this.fromPath = fromPath; + this.toPath = toPath; + this.rootNodeRef = rootNodeRef; + } + + + public String getFrom() + { + return from; + } + + public String getTo() + { + return to; + } + + public String getToPath() + { + return toPath; + } + + public String getFromPath() + { + return fromPath; + } + + public NodeRef getRootNodeRef() + { + return rootNodeRef; + } + + public String toString() + { + return "MoveFileOperation: from " + fromPath + " to "+ toPath; + } + + public int hashCode() + { + return fromPath.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof MoveFileOperation) + { + MoveFileOperation r = (MoveFileOperation)o; + if(fromPath.equals(r.getFromPath()) && toPath.equals(r.getToPath())) + { + return true; + } + } + return false; + } +} + + + diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/OpenFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/OpenFileOperation.java index fa8cfd2082..8a66c76e6a 100644 --- a/source/java/org/alfresco/filesys/repo/rules/operations/OpenFileOperation.java +++ b/source/java/org/alfresco/filesys/repo/rules/operations/OpenFileOperation.java @@ -1,85 +1,85 @@ -package org.alfresco.filesys.repo.rules.operations; - -import org.alfresco.filesys.repo.OpenFileMode; -import org.alfresco.filesys.repo.rules.Operation; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Open File Operation. - *

- * Open a file with the given name. - */ -public class OpenFileOperation implements Operation -{ - private String name; - private OpenFileMode mode; - private boolean truncate = false; - private String path; - private NodeRef rootNode; - - /** - * - * @param name the name of the file to open - * @param mode if true open the file in read/write - * @param truncate boolean - * @param rootNode root node - * @param path the full path/name to open - */ - public OpenFileOperation(String name, OpenFileMode mode, boolean truncate, NodeRef rootNode, String path) - { - this.name = name; - this.rootNode = rootNode; - this.truncate = truncate; - this.path = path; - this.mode = mode; - } - - public String getName() - { - return name; - } - - public String getPath() - { - return path; - } - - public NodeRef getRootNodeRef() - { - return rootNode; - } - - - public OpenFileMode getMode() - { - return mode; - } - - public boolean isTruncate() - { - return truncate; - } - - public String toString() - { - return "OpenFileOperation: " + name; - } - - public int hashCode() - { - return name.hashCode(); - } - - public boolean equals(Object o) - { - if(o instanceof OpenFileOperation) - { - OpenFileOperation c = (OpenFileOperation)o; - if(name.equals(c.getName())) - { - return true; - } - } - return false; - } -} +package org.alfresco.filesys.repo.rules.operations; + +import org.alfresco.filesys.repo.OpenFileMode; +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Open File Operation. + *

+ * Open a file with the given name. + */ +public class OpenFileOperation implements Operation +{ + private String name; + private OpenFileMode mode; + private boolean truncate = false; + private String path; + private NodeRef rootNode; + + /** + * + * @param name the name of the file to open + * @param mode if true open the file in read/write + * @param truncate boolean + * @param rootNode root node + * @param path the full path/name to open + */ + public OpenFileOperation(String name, OpenFileMode mode, boolean truncate, NodeRef rootNode, String path) + { + this.name = name; + this.rootNode = rootNode; + this.truncate = truncate; + this.path = path; + this.mode = mode; + } + + public String getName() + { + return name; + } + + public String getPath() + { + return path; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + + public OpenFileMode getMode() + { + return mode; + } + + public boolean isTruncate() + { + return truncate; + } + + public String toString() + { + return "OpenFileOperation: " + name; + } + + public int hashCode() + { + return name.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof OpenFileOperation) + { + OpenFileOperation c = (OpenFileOperation)o; + if(name.equals(c.getName())) + { + return true; + } + } + return false; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/RenameFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/RenameFileOperation.java index 46ba262cb6..5c5ffd4316 100644 --- a/source/java/org/alfresco/filesys/repo/rules/operations/RenameFileOperation.java +++ b/source/java/org/alfresco/filesys/repo/rules/operations/RenameFileOperation.java @@ -1,85 +1,85 @@ -package org.alfresco.filesys.repo.rules.operations; - -import org.alfresco.filesys.repo.rules.Operation; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Rename a file within the same directory - */ -public class RenameFileOperation implements Operation -{ - private String from; - private String to; - private String fromPath; - private String toPath; - NodeRef rootNodeRef; - - /** - * - * @param from name of file from - * @param to name of file to - * @param fromPath full path of from - * @param toPath full path of to - * @param rootNodeRef - */ - public RenameFileOperation(String from, String to, String fromPath, String toPath, NodeRef rootNodeRef) - { - this.from = from; - this.to = to; - this.fromPath = fromPath; - this.toPath = toPath; - this.rootNodeRef = rootNodeRef; - } - - - public String getFrom() - { - return from; - } - - public String getTo() - { - return to; - } - - public String getToPath() - { - return toPath; - } - - public String getFromPath() - { - return fromPath; - } - - public NodeRef getRootNodeRef() - { - return rootNodeRef; - } - - public String toString() - { - return "RenameFileOperation: from " + from + " to "+ to; - } - - public int hashCode() - { - return from.hashCode(); - } - - public boolean equals(Object o) - { - if(o instanceof RenameFileOperation) - { - RenameFileOperation r = (RenameFileOperation)o; - if(from.equals(r.getFrom()) && to.equals(r.getTo())) - { - return true; - } - } - return false; - } - - - -} +package org.alfresco.filesys.repo.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Rename a file within the same directory + */ +public class RenameFileOperation implements Operation +{ + private String from; + private String to; + private String fromPath; + private String toPath; + NodeRef rootNodeRef; + + /** + * + * @param from name of file from + * @param to name of file to + * @param fromPath full path of from + * @param toPath full path of to + * @param rootNodeRef + */ + public RenameFileOperation(String from, String to, String fromPath, String toPath, NodeRef rootNodeRef) + { + this.from = from; + this.to = to; + this.fromPath = fromPath; + this.toPath = toPath; + this.rootNodeRef = rootNodeRef; + } + + + public String getFrom() + { + return from; + } + + public String getTo() + { + return to; + } + + public String getToPath() + { + return toPath; + } + + public String getFromPath() + { + return fromPath; + } + + public NodeRef getRootNodeRef() + { + return rootNodeRef; + } + + public String toString() + { + return "RenameFileOperation: from " + from + " to "+ to; + } + + public int hashCode() + { + return from.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)o; + if(from.equals(r.getFrom()) && to.equals(r.getTo())) + { + return true; + } + } + return false; + } + + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/package-info.java b/source/java/org/alfresco/filesys/repo/rules/package-info.java index f290b8e840..51a3f057eb 100644 --- a/source/java/org/alfresco/filesys/repo/rules/package-info.java +++ b/source/java/org/alfresco/filesys/repo/rules/package-info.java @@ -1,19 +1,19 @@ -/** - * Filesystem Rule Evaluator to support Scenarios. - *

- * Low level operations (create, update, delete etc) are passed into the RuleEvaluator. - *

- * The RuleEvaluator is configured with a list of Scenarios which process the operations as and - * when their patterns match. - * The RuleEvaluator evaluates the stream of operations and returns commands to execute. - *

- * The Command Executor executes the commands returned from the RuleEvaluator. - *

- * Each Scenario is a Factory for A ScenarioInstance. The RuleEvaluator contains a set of active scenario instances. - *

- * @since 4.0 - */ -@PackageMarker -package org.alfresco.filesys.repo.rules; -import org.alfresco.util.PackageMarker; - +/** + * Filesystem Rule Evaluator to support Scenarios. + *

+ * Low level operations (create, update, delete etc) are passed into the RuleEvaluator. + *

+ * The RuleEvaluator is configured with a list of Scenarios which process the operations as and + * when their patterns match. + * The RuleEvaluator evaluates the stream of operations and returns commands to execute. + *

+ * The Command Executor executes the commands returned from the RuleEvaluator. + *

+ * Each Scenario is a Factory for A ScenarioInstance. The RuleEvaluator contains a set of active scenario instances. + *

+ * @since 4.0 + */ +@PackageMarker +package org.alfresco.filesys.repo.rules; +import org.alfresco.util.PackageMarker; + diff --git a/source/java/org/alfresco/filesys/util/package-info.java b/source/java/org/alfresco/filesys/util/package-info.java index 1062ffd8ac..c420b515bf 100644 --- a/source/java/org/alfresco/filesys/util/package-info.java +++ b/source/java/org/alfresco/filesys/util/package-info.java @@ -1,10 +1,10 @@ -/** - * Filesystem utilities - * - * Contains : - * CifsMounter to mount and unmount a CIFS filesystem. - * - */ -@PackageMarker -package org.alfresco.filesys.util; -import org.alfresco.util.PackageMarker; +/** + * Filesystem utilities + * + * Contains : + * CifsMounter to mount and unmount a CIFS filesystem. + * + */ +@PackageMarker +package org.alfresco.filesys.util; +import org.alfresco.util.PackageMarker; diff --git a/source/java/org/alfresco/model/ApplicationModel.java b/source/java/org/alfresco/model/ApplicationModel.java index 48a7ee9c15..9f23ca09f1 100644 --- a/source/java/org/alfresco/model/ApplicationModel.java +++ b/source/java/org/alfresco/model/ApplicationModel.java @@ -1,44 +1,44 @@ -package org.alfresco.model; - -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; - -/** - * @author Kevin Roast - */ -public interface ApplicationModel -{ - // workflow - static final QName ASPECT_SIMPLE_WORKFLOW = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "simpleworkflow"); - static final QName PROP_APPROVE_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveStep"); - static final QName PROP_APPROVE_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveFolder"); - static final QName PROP_APPROVE_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveMove"); - static final QName PROP_REJECT_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectStep"); - static final QName PROP_REJECT_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectFolder"); - static final QName PROP_REJECT_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectMove"); - - // ui facets aspect - static final QName ASPECT_UIFACETS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "uifacets"); - static final QName PROP_ICON = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "icon"); - - // inlineeditable aspect - static final QName ASPECT_INLINEEDITABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "inlineeditable"); - static final QName PROP_EDITINLINE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "editInline"); - - // configurable aspect - static final QName ASPECT_CONFIGURABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurable"); - static final QName TYPE_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); - static final QName ASSOC_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); - - // object links - static final QName TYPE_FILELINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "filelink"); - static final QName TYPE_FOLDERLINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "folderlink"); - - // feed source aspect - static final QName ASPECT_FEEDSOURCE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "feedsource"); - static final QName PROP_FEEDTEMPLATE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "template"); - - // Default view config aspect - static final QName ASPECT_DEFAULT_VIEW_CONFIG = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "defaultViewConfig"); - static final QName PROP_DEFAULT_VIEW_ID = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "defaultViewId"); -} +package org.alfresco.model; + +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * @author Kevin Roast + */ +public interface ApplicationModel +{ + // workflow + static final QName ASPECT_SIMPLE_WORKFLOW = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "simpleworkflow"); + static final QName PROP_APPROVE_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveStep"); + static final QName PROP_APPROVE_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveFolder"); + static final QName PROP_APPROVE_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveMove"); + static final QName PROP_REJECT_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectStep"); + static final QName PROP_REJECT_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectFolder"); + static final QName PROP_REJECT_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectMove"); + + // ui facets aspect + static final QName ASPECT_UIFACETS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "uifacets"); + static final QName PROP_ICON = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "icon"); + + // inlineeditable aspect + static final QName ASPECT_INLINEEDITABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "inlineeditable"); + static final QName PROP_EDITINLINE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "editInline"); + + // configurable aspect + static final QName ASPECT_CONFIGURABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurable"); + static final QName TYPE_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); + static final QName ASSOC_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); + + // object links + static final QName TYPE_FILELINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "filelink"); + static final QName TYPE_FOLDERLINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "folderlink"); + + // feed source aspect + static final QName ASPECT_FEEDSOURCE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "feedsource"); + static final QName PROP_FEEDTEMPLATE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "template"); + + // Default view config aspect + static final QName ASPECT_DEFAULT_VIEW_CONFIG = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "defaultViewConfig"); + static final QName PROP_DEFAULT_VIEW_ID = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "defaultViewId"); +} diff --git a/source/java/org/alfresco/model/BlogIntegrationModel.java b/source/java/org/alfresco/model/BlogIntegrationModel.java index 7b1356427d..882733a4bb 100644 --- a/source/java/org/alfresco/model/BlogIntegrationModel.java +++ b/source/java/org/alfresco/model/BlogIntegrationModel.java @@ -1,29 +1,29 @@ -package org.alfresco.model; - -import org.alfresco.service.namespace.QName; - -/** - * @author Roy Wetherall - */ -public interface BlogIntegrationModel -{ - static final String MODEL_URL = "http://www.alfresco.org/model/blogintegration/1.0"; - static final String MODEL_PREFIX = "blg"; - - static final QName ASPECT_BLOG_DETAILS = QName.createQName(MODEL_URL, "blogDetails"); - static final QName PROP_BLOG_IMPLEMENTATION = QName.createQName(MODEL_URL, "blogImplementation"); - static final QName PROP_ID = QName.createQName(MODEL_URL, "id"); - static final QName PROP_NAME = QName.createQName(MODEL_URL, "name"); - static final QName PROP_DESCRIPTION = QName.createQName(MODEL_URL, "description"); - static final QName PROP_URL = QName.createQName(MODEL_URL, "url"); - static final QName PROP_USER_NAME = QName.createQName(MODEL_URL, "userName"); - static final QName PROP_PASSWORD = QName.createQName(MODEL_URL, "password"); - - static final QName ASPECT_BLOG_POST = QName.createQName(MODEL_URL, "blogPost"); - static final QName PROP_POST_ID = QName.createQName(MODEL_URL, "postId"); - static final QName PROP_PUBLISHED = QName.createQName(MODEL_URL, "published"); - static final QName PROP_LINK = QName.createQName(MODEL_URL, "link"); - static final QName PROP_POSTED = QName.createQName(MODEL_URL, "posted"); - static final QName PROP_LAST_UPDATE = QName.createQName(MODEL_URL, "lastUpdate"); - static final QName ASSOC_BLOG_DETAILS = QName.createQName(MODEL_URL, "blogDetails"); -} +package org.alfresco.model; + +import org.alfresco.service.namespace.QName; + +/** + * @author Roy Wetherall + */ +public interface BlogIntegrationModel +{ + static final String MODEL_URL = "http://www.alfresco.org/model/blogintegration/1.0"; + static final String MODEL_PREFIX = "blg"; + + static final QName ASPECT_BLOG_DETAILS = QName.createQName(MODEL_URL, "blogDetails"); + static final QName PROP_BLOG_IMPLEMENTATION = QName.createQName(MODEL_URL, "blogImplementation"); + static final QName PROP_ID = QName.createQName(MODEL_URL, "id"); + static final QName PROP_NAME = QName.createQName(MODEL_URL, "name"); + static final QName PROP_DESCRIPTION = QName.createQName(MODEL_URL, "description"); + static final QName PROP_URL = QName.createQName(MODEL_URL, "url"); + static final QName PROP_USER_NAME = QName.createQName(MODEL_URL, "userName"); + static final QName PROP_PASSWORD = QName.createQName(MODEL_URL, "password"); + + static final QName ASPECT_BLOG_POST = QName.createQName(MODEL_URL, "blogPost"); + static final QName PROP_POST_ID = QName.createQName(MODEL_URL, "postId"); + static final QName PROP_PUBLISHED = QName.createQName(MODEL_URL, "published"); + static final QName PROP_LINK = QName.createQName(MODEL_URL, "link"); + static final QName PROP_POSTED = QName.createQName(MODEL_URL, "posted"); + static final QName PROP_LAST_UPDATE = QName.createQName(MODEL_URL, "lastUpdate"); + static final QName ASSOC_BLOG_DETAILS = QName.createQName(MODEL_URL, "blogDetails"); +} diff --git a/source/java/org/alfresco/opencmis/ActivityPosterImpl.java b/source/java/org/alfresco/opencmis/ActivityPosterImpl.java index ab484b7da3..fcaca573ba 100644 --- a/source/java/org/alfresco/opencmis/ActivityPosterImpl.java +++ b/source/java/org/alfresco/opencmis/ActivityPosterImpl.java @@ -1,319 +1,319 @@ -package org.alfresco.opencmis; - -import java.util.List; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.Client; -import org.alfresco.repo.Client.ClientType; -import org.alfresco.repo.activities.ActivityType; -import org.alfresco.repo.model.filefolder.HiddenAspect; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.cmr.activities.ActivityPoster; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileFolderServiceType; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -/** - * OpenCMIS methods may use an instance of this class to post activity data. - * - * @see CmisActivityPoster - * @author sglover - */ -public class ActivityPosterImpl implements CmisActivityPoster, InitializingBean -{ - private static final String APP_TOOL = "CMIS"; - public static final char PathSeperatorChar = '/'; - - // Logging - private static Log logger = LogFactory.getLog(CmisActivityPoster.class); - - private ActivityPoster poster; - private SiteService siteService; - private TenantService tenantService; - private NodeService nodeService; - private FileFolderService fileFolderService; - private HiddenAspect hiddenAspect; - - private boolean activitiesEnabled = true; - - /** - * Constructor - */ - public ActivityPosterImpl() - { - } - - public void setHiddenAspect(HiddenAspect hiddenAspect) - { - this.hiddenAspect = hiddenAspect; - } - - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setActivitiesEnabled(boolean activitiesEnabled) - { - this.activitiesEnabled = activitiesEnabled; - } - - public void setPoster(ActivityPoster poster) - { - this.poster = poster; - } - - private final String getPathFromNode(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException - { - // Check if the nodes are valid, or equal - if (rootNodeRef == null || nodeRef == null) - throw new IllegalArgumentException("Invalid node(s) in getPathFromNode call"); - - // short cut if the path node is the root node - if (rootNodeRef.equals(nodeRef)) - return ""; - - // get the path elements - List pathInfos = fileFolderService.getNamePath(rootNodeRef, nodeRef); - - // build the path string - StringBuilder sb = new StringBuilder(pathInfos.size() * 20); - for (FileInfo fileInfo : pathInfos) - { - sb.append(PathSeperatorChar); - sb.append(fileInfo.getName()); - } - // done - if (logger.isDebugEnabled()) - { - logger.debug("Build name path for node: \n" + - " root: " + rootNodeRef + "\n" + - " target: " + nodeRef + "\n" + - " path: " + sb); - } - return sb.toString(); - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() throws Exception - { - PropertyCheck.mandatory(this, "poster", poster); - PropertyCheck.mandatory(this, "siteService", siteService); - PropertyCheck.mandatory(this, "tenantService", tenantService); - PropertyCheck.mandatory(this, "nodeService", nodeService); - PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); - } - - private String getCurrentTenantDomain() - { - String tenantDomain = tenantService.getCurrentUserDomain(); - if (tenantDomain == null) - { - return TenantService.DEFAULT_DOMAIN; - } - return tenantDomain; - } - - private boolean isFolder(NodeRef nodeRef) - { - QName typeQName = nodeService.getType(nodeRef); - FileFolderServiceType type = fileFolderService.getType(typeQName); - boolean isFolder = type.equals(FileFolderServiceType.FOLDER); - return isFolder; - } - - /** - * {@inheritDoc} - */ - @Override - public void postFileFolderAdded(NodeRef nodeRef) - { - if(activitiesEnabled && !hiddenAspect.hasHiddenAspect(nodeRef)) - { - SiteInfo siteInfo = siteService.getSite(nodeRef); - String siteId = (siteInfo != null ? siteInfo.getShortName() : null); - - if(siteId != null && !siteId.equals("")) - { - // post only for nodes within sites - NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); - - String path = null; - boolean isFolder = isFolder(nodeRef); - String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - - if(isFolder) - { - NodeRef documentLibrary = siteService.getContainer(siteId, SiteService.DOCUMENT_LIBRARY); - path = "/"; - try - { - path = getPathFromNode(documentLibrary, nodeRef); - } - catch (FileNotFoundException error) - { - if (logger.isDebugEnabled()) - { - logger.debug("No " + SiteService.DOCUMENT_LIBRARY + " container found."); - } - } - } - FileInfo fileInfo = fileFolderService.getFileInfo(nodeRef); - poster.postFileFolderActivity((isFolder ? ActivityType.FOLDER_ADDED : ActivityType.FILE_ADDED), path, getCurrentTenantDomain(), - siteId, parentNodeRef, nodeRef, name, APP_TOOL, Client.asType(ClientType.cmis), fileInfo); - - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public void postFileFolderUpdated(boolean isFolder, NodeRef nodeRef) - { - if(activitiesEnabled && !hiddenAspect.hasHiddenAspect(nodeRef)) - { - SiteInfo siteInfo = siteService.getSite(nodeRef); - String siteId = (siteInfo != null ? siteInfo.getShortName() : null); - if(siteId != null && !siteId.equals("")) - { - // post only for nodes within sites - String fileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - - if (!isFolder) - { - FileInfo fileInfo = fileFolderService.getFileInfo(nodeRef); - poster.postFileFolderActivity(ActivityType.FILE_UPDATED, null, getCurrentTenantDomain(), - siteId, null, nodeRef, fileName, APP_TOOL, Client.asType(ClientType.cmis), fileInfo); - } - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public void postFileFolderDeleted(ActivityInfo activityInfo) - { - if(activitiesEnabled && activityInfo.getSiteId() != null) - { - poster.postFileFolderActivity((activityInfo.isFolder() ? ActivityType.FOLDER_DELETED : ActivityType.FILE_DELETED), activityInfo.getParentPath(), getCurrentTenantDomain(), - activityInfo.getSiteId(), activityInfo.getParentNodeRef(), activityInfo.getNodeRef(), activityInfo.getFileName(), APP_TOOL, Client.asType(ClientType.cmis), null); - } - } - - public ActivityInfo getActivityInfo(NodeRef nodeRef) - { - SiteInfo siteInfo = siteService.getSite(nodeRef); - String siteId = (siteInfo != null ? siteInfo.getShortName() : null); - if(siteId != null && !siteId.equals("")) - { - NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); - FileInfo fileInfo = fileFolderService.getFileInfo(nodeRef); - String name = fileInfo.getName(); - boolean isFolder = fileInfo.isFolder(); - - NodeRef documentLibrary = siteService.getContainer(siteId, SiteService.DOCUMENT_LIBRARY); - String parentPath = "/"; - try - { - parentPath = getPathFromNode(documentLibrary, parentNodeRef); - } - catch (FileNotFoundException error) - { - if (logger.isDebugEnabled()) - { - logger.debug("No " + SiteService.DOCUMENT_LIBRARY + " container found."); - } - } - - return new ActivityInfo(nodeRef, parentPath, parentNodeRef, siteId, name, isFolder); - } - else - { - return null; - } - } - - public static class ActivityInfo - { - private NodeRef nodeRef; - private String parentPath; - private NodeRef parentNodeRef; - private String siteId; - private String fileName; - private boolean isFolder; - - public ActivityInfo(NodeRef nodeRef, String parentPath, NodeRef parentNodeRef, - String siteId, String fileName, boolean isFolder) - { - super(); - this.nodeRef = nodeRef; - this.parentPath = parentPath; - this.parentNodeRef = parentNodeRef; - this.siteId = siteId; - this.fileName = fileName; - this.isFolder = isFolder; - } - - public NodeRef getNodeRef() - { - return nodeRef; - } - - public String getParentPath() - { - return parentPath; - } - - public NodeRef getParentNodeRef() - { - return parentNodeRef; - } - - public String getSiteId() - { - return siteId; - } - - public String getFileName() - { - return fileName; - } - - public boolean isFolder() - { - return isFolder; - } - } +package org.alfresco.opencmis; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.Client; +import org.alfresco.repo.Client.ClientType; +import org.alfresco.repo.activities.ActivityType; +import org.alfresco.repo.model.filefolder.HiddenAspect; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.activities.ActivityPoster; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileFolderServiceType; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * OpenCMIS methods may use an instance of this class to post activity data. + * + * @see CmisActivityPoster + * @author sglover + */ +public class ActivityPosterImpl implements CmisActivityPoster, InitializingBean +{ + private static final String APP_TOOL = "CMIS"; + public static final char PathSeperatorChar = '/'; + + // Logging + private static Log logger = LogFactory.getLog(CmisActivityPoster.class); + + private ActivityPoster poster; + private SiteService siteService; + private TenantService tenantService; + private NodeService nodeService; + private FileFolderService fileFolderService; + private HiddenAspect hiddenAspect; + + private boolean activitiesEnabled = true; + + /** + * Constructor + */ + public ActivityPosterImpl() + { + } + + public void setHiddenAspect(HiddenAspect hiddenAspect) + { + this.hiddenAspect = hiddenAspect; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setActivitiesEnabled(boolean activitiesEnabled) + { + this.activitiesEnabled = activitiesEnabled; + } + + public void setPoster(ActivityPoster poster) + { + this.poster = poster; + } + + private final String getPathFromNode(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException + { + // Check if the nodes are valid, or equal + if (rootNodeRef == null || nodeRef == null) + throw new IllegalArgumentException("Invalid node(s) in getPathFromNode call"); + + // short cut if the path node is the root node + if (rootNodeRef.equals(nodeRef)) + return ""; + + // get the path elements + List pathInfos = fileFolderService.getNamePath(rootNodeRef, nodeRef); + + // build the path string + StringBuilder sb = new StringBuilder(pathInfos.size() * 20); + for (FileInfo fileInfo : pathInfos) + { + sb.append(PathSeperatorChar); + sb.append(fileInfo.getName()); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Build name path for node: \n" + + " root: " + rootNodeRef + "\n" + + " target: " + nodeRef + "\n" + + " path: " + sb); + } + return sb.toString(); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "poster", poster); + PropertyCheck.mandatory(this, "siteService", siteService); + PropertyCheck.mandatory(this, "tenantService", tenantService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); + } + + private String getCurrentTenantDomain() + { + String tenantDomain = tenantService.getCurrentUserDomain(); + if (tenantDomain == null) + { + return TenantService.DEFAULT_DOMAIN; + } + return tenantDomain; + } + + private boolean isFolder(NodeRef nodeRef) + { + QName typeQName = nodeService.getType(nodeRef); + FileFolderServiceType type = fileFolderService.getType(typeQName); + boolean isFolder = type.equals(FileFolderServiceType.FOLDER); + return isFolder; + } + + /** + * {@inheritDoc} + */ + @Override + public void postFileFolderAdded(NodeRef nodeRef) + { + if(activitiesEnabled && !hiddenAspect.hasHiddenAspect(nodeRef)) + { + SiteInfo siteInfo = siteService.getSite(nodeRef); + String siteId = (siteInfo != null ? siteInfo.getShortName() : null); + + if(siteId != null && !siteId.equals("")) + { + // post only for nodes within sites + NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); + + String path = null; + boolean isFolder = isFolder(nodeRef); + String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + + if(isFolder) + { + NodeRef documentLibrary = siteService.getContainer(siteId, SiteService.DOCUMENT_LIBRARY); + path = "/"; + try + { + path = getPathFromNode(documentLibrary, nodeRef); + } + catch (FileNotFoundException error) + { + if (logger.isDebugEnabled()) + { + logger.debug("No " + SiteService.DOCUMENT_LIBRARY + " container found."); + } + } + } + FileInfo fileInfo = fileFolderService.getFileInfo(nodeRef); + poster.postFileFolderActivity((isFolder ? ActivityType.FOLDER_ADDED : ActivityType.FILE_ADDED), path, getCurrentTenantDomain(), + siteId, parentNodeRef, nodeRef, name, APP_TOOL, Client.asType(ClientType.cmis), fileInfo); + + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void postFileFolderUpdated(boolean isFolder, NodeRef nodeRef) + { + if(activitiesEnabled && !hiddenAspect.hasHiddenAspect(nodeRef)) + { + SiteInfo siteInfo = siteService.getSite(nodeRef); + String siteId = (siteInfo != null ? siteInfo.getShortName() : null); + if(siteId != null && !siteId.equals("")) + { + // post only for nodes within sites + String fileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + + if (!isFolder) + { + FileInfo fileInfo = fileFolderService.getFileInfo(nodeRef); + poster.postFileFolderActivity(ActivityType.FILE_UPDATED, null, getCurrentTenantDomain(), + siteId, null, nodeRef, fileName, APP_TOOL, Client.asType(ClientType.cmis), fileInfo); + } + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void postFileFolderDeleted(ActivityInfo activityInfo) + { + if(activitiesEnabled && activityInfo.getSiteId() != null) + { + poster.postFileFolderActivity((activityInfo.isFolder() ? ActivityType.FOLDER_DELETED : ActivityType.FILE_DELETED), activityInfo.getParentPath(), getCurrentTenantDomain(), + activityInfo.getSiteId(), activityInfo.getParentNodeRef(), activityInfo.getNodeRef(), activityInfo.getFileName(), APP_TOOL, Client.asType(ClientType.cmis), null); + } + } + + public ActivityInfo getActivityInfo(NodeRef nodeRef) + { + SiteInfo siteInfo = siteService.getSite(nodeRef); + String siteId = (siteInfo != null ? siteInfo.getShortName() : null); + if(siteId != null && !siteId.equals("")) + { + NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); + FileInfo fileInfo = fileFolderService.getFileInfo(nodeRef); + String name = fileInfo.getName(); + boolean isFolder = fileInfo.isFolder(); + + NodeRef documentLibrary = siteService.getContainer(siteId, SiteService.DOCUMENT_LIBRARY); + String parentPath = "/"; + try + { + parentPath = getPathFromNode(documentLibrary, parentNodeRef); + } + catch (FileNotFoundException error) + { + if (logger.isDebugEnabled()) + { + logger.debug("No " + SiteService.DOCUMENT_LIBRARY + " container found."); + } + } + + return new ActivityInfo(nodeRef, parentPath, parentNodeRef, siteId, name, isFolder); + } + else + { + return null; + } + } + + public static class ActivityInfo + { + private NodeRef nodeRef; + private String parentPath; + private NodeRef parentNodeRef; + private String siteId; + private String fileName; + private boolean isFolder; + + public ActivityInfo(NodeRef nodeRef, String parentPath, NodeRef parentNodeRef, + String siteId, String fileName, boolean isFolder) + { + super(); + this.nodeRef = nodeRef; + this.parentPath = parentPath; + this.parentNodeRef = parentNodeRef; + this.siteId = siteId; + this.fileName = fileName; + this.isFolder = isFolder; + } + + public NodeRef getNodeRef() + { + return nodeRef; + } + + public String getParentPath() + { + return parentPath; + } + + public NodeRef getParentNodeRef() + { + return parentNodeRef; + } + + public String getSiteId() + { + return siteId; + } + + public String getFileName() + { + return fileName; + } + + public boolean isFolder() + { + return isFolder; + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisService.java b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java index a38fa80cbb..f7020e54b9 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisService.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java @@ -1,30 +1,30 @@ -package org.alfresco.opencmis; - -import org.apache.chemistry.opencmis.commons.server.CallContext; -import org.apache.chemistry.opencmis.commons.server.CmisService; - -/** - * Extended interface for lifecycle management - * - * @author Derek Hulley - * @since 4.0 - */ -public interface AlfrescoCmisService extends CmisService -{ - /** - * Called directly before any CMIS method is used - */ - void beforeCall(); - - /** - * Called directly after any CMIS method is used - */ - void afterCall(); - - /** - * Call before the work method and forms the opposite of {@link #close()}. - * - * @param context the context in which the service must operate - */ - void open(CallContext context); -} +package org.alfresco.opencmis; + +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.CmisService; + +/** + * Extended interface for lifecycle management + * + * @author Derek Hulley + * @since 4.0 + */ +public interface AlfrescoCmisService extends CmisService +{ + /** + * Called directly before any CMIS method is used + */ + void beforeCall(); + + /** + * Called directly after any CMIS method is used + */ + void afterCall(); + + /** + * Call before the work method and forms the opposite of {@link #close()}. + * + * @param context the context in which the service must operate + */ + void open(CallContext context); +} diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceCall.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceCall.java index febd0912c9..bf74fa891a 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceCall.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceCall.java @@ -1,23 +1,23 @@ -package org.alfresco.opencmis; - -import org.apache.chemistry.opencmis.commons.server.CallContext; - -public class AlfrescoCmisServiceCall -{ - private static ThreadLocal context = new ThreadLocal(); - - public static void set(CallContext newContext) - { - context.set(newContext); - } - - public static CallContext get() - { - return context.get(); - } - - public static void clear() - { - context.remove(); - } -} +package org.alfresco.opencmis; + +import org.apache.chemistry.opencmis.commons.server.CallContext; + +public class AlfrescoCmisServiceCall +{ + private static ThreadLocal context = new ThreadLocal(); + + public static void set(CallContext newContext) + { + context.set(newContext); + } + + public static CallContext get() + { + return context.get(); + } + + public static void clear() + { + context.remove(); + } +} diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java index ae037be19f..234b7deefe 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java @@ -1,160 +1,160 @@ -package org.alfresco.opencmis; - -import java.util.Map; - -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.RetryingTransactionInterceptor; -import org.alfresco.service.cmr.security.AuthorityService; -import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory; -import org.apache.chemistry.opencmis.commons.server.CallContext; -import org.apache.chemistry.opencmis.commons.server.CmisService; -import org.apache.chemistry.opencmis.server.support.CmisServiceWrapper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.aop.framework.ProxyFactory; - -/** - * Factory for OpenCMIS service objects. - * - * @author florian.mueller - * @author Derek Hulley - */ -public class AlfrescoCmisServiceFactory extends AbstractServiceFactory -{ - private static final Log logger = LogFactory.getLog(AlfrescoCmisServiceFactory.class); - - private CMISConnector connector; - private RetryingTransactionInterceptor cmisTransactions; - private AlfrescoCmisExceptionInterceptor cmisExceptions; - private AlfrescoCmisServiceInterceptor cmisControl; - private AlfrescoCmisStreamInterceptor cmisStreams; - private AuthorityService authorityService; - - /** - * Sets the Authority Service. - */ - public void setAuthorityService(AuthorityService authorityService) - { - this.authorityService = authorityService; - } - - /** - * Sets the CMIS connector. - */ - public void setCmisConnector(CMISConnector connector) - { - this.connector = connector; - } - - /** - * @param cmisTransactions the interceptor that applies appropriate transactions - */ - public void setCmisTransactions(RetryingTransactionInterceptor cmisTransactions) - { - this.cmisTransactions = cmisTransactions; - } - - /** - * @param cmisExceptions interceptor to translate exceptions - */ - public void setCmisExceptions(AlfrescoCmisExceptionInterceptor cmisExceptions) - { - this.cmisExceptions = cmisExceptions; - } - - /** - * @param cmisControl interceptor that provides logging and authentication checks - */ - public void setCmisControl(AlfrescoCmisServiceInterceptor cmisControl) - { - this.cmisControl = cmisControl; - } - - /** - * @param cmisStreams interceptor to create reusable ContentStreams - */ - public void setCmisStreams(AlfrescoCmisStreamInterceptor cmisStreams) - { - this.cmisStreams = cmisStreams; - } - - @Override - public void init(Map parameters) - { - } - - public void init() - { -// this.service = getCmisServiceTarget(connector); -// -// // Wrap it -// ProxyFactory proxyFactory = new ProxyFactory(service); -// proxyFactory.addInterface(AlfrescoCmisService.class); -// proxyFactory.addAdvice(cmisExceptions); -// proxyFactory.addAdvice(cmisControl); -// proxyFactory.addAdvice(cmisStreams); -// proxyFactory.addAdvice(cmisTransactions); -// AlfrescoCmisService cmisService = (AlfrescoCmisService) proxyFactory.getProxy(); -// -// this.serviceWrapper = new CmisServiceWrapper( -// cmisService, -// connector.getTypesDefaultMaxItems(), connector.getTypesDefaultDepth(), -// connector.getObjectsDefaultMaxItems(), connector.getObjectsDefaultDepth()); - } - - @Override - public void destroy() - { - } - - /** - * TODO: - * We are producing new instances each time. - */ - @Override - public CmisService getService(final CallContext context) - { - if (logger.isDebugEnabled()) - { - StringBuilder sb = new StringBuilder(); - sb.append("getService: ").append(AuthenticationUtil.getFullyAuthenticatedUser()) - .append(" [runAsUser=").append(AuthenticationUtil.getRunAsUser()) - .append(",ctxUserName=").append(context.getUsername()) - .append(",ctxRepoId=").append(context.getRepositoryId()).append("]"); - - logger.debug(sb.toString()); - } - - // Avoid using guest user if the user is provided in the context - if(AuthenticationUtil.getFullyAuthenticatedUser() != null && authorityService.isGuestAuthority(AuthenticationUtil.getFullyAuthenticatedUser())) - { - AuthenticationUtil.clearCurrentSecurityContext(); - } - - AlfrescoCmisService service = getCmisServiceTarget(connector); - - // Wrap it - ProxyFactory proxyFactory = new ProxyFactory(service); - proxyFactory.addInterface(AlfrescoCmisService.class); - proxyFactory.addAdvice(cmisExceptions); - proxyFactory.addAdvice(cmisControl); - proxyFactory.addAdvice(cmisStreams); - proxyFactory.addAdvice(cmisTransactions); - AlfrescoCmisService cmisService = (AlfrescoCmisService) proxyFactory.getProxy(); - - CmisServiceWrapper wrapperService = new CmisServiceWrapper( - cmisService, - connector.getTypesDefaultMaxItems(), connector.getTypesDefaultDepth(), - connector.getObjectsDefaultMaxItems(), connector.getObjectsDefaultDepth()); - - // We use our specific open method here because only we know about it - cmisService.open(context); - - return wrapperService; - } - - protected AlfrescoCmisService getCmisServiceTarget(CMISConnector connector) - { - return new AlfrescoCmisServiceImpl(connector); - } -} +package org.alfresco.opencmis; + +import java.util.Map; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionInterceptor; +import org.alfresco.service.cmr.security.AuthorityService; +import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory; +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.CmisService; +import org.apache.chemistry.opencmis.server.support.CmisServiceWrapper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.framework.ProxyFactory; + +/** + * Factory for OpenCMIS service objects. + * + * @author florian.mueller + * @author Derek Hulley + */ +public class AlfrescoCmisServiceFactory extends AbstractServiceFactory +{ + private static final Log logger = LogFactory.getLog(AlfrescoCmisServiceFactory.class); + + private CMISConnector connector; + private RetryingTransactionInterceptor cmisTransactions; + private AlfrescoCmisExceptionInterceptor cmisExceptions; + private AlfrescoCmisServiceInterceptor cmisControl; + private AlfrescoCmisStreamInterceptor cmisStreams; + private AuthorityService authorityService; + + /** + * Sets the Authority Service. + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * Sets the CMIS connector. + */ + public void setCmisConnector(CMISConnector connector) + { + this.connector = connector; + } + + /** + * @param cmisTransactions the interceptor that applies appropriate transactions + */ + public void setCmisTransactions(RetryingTransactionInterceptor cmisTransactions) + { + this.cmisTransactions = cmisTransactions; + } + + /** + * @param cmisExceptions interceptor to translate exceptions + */ + public void setCmisExceptions(AlfrescoCmisExceptionInterceptor cmisExceptions) + { + this.cmisExceptions = cmisExceptions; + } + + /** + * @param cmisControl interceptor that provides logging and authentication checks + */ + public void setCmisControl(AlfrescoCmisServiceInterceptor cmisControl) + { + this.cmisControl = cmisControl; + } + + /** + * @param cmisStreams interceptor to create reusable ContentStreams + */ + public void setCmisStreams(AlfrescoCmisStreamInterceptor cmisStreams) + { + this.cmisStreams = cmisStreams; + } + + @Override + public void init(Map parameters) + { + } + + public void init() + { +// this.service = getCmisServiceTarget(connector); +// +// // Wrap it +// ProxyFactory proxyFactory = new ProxyFactory(service); +// proxyFactory.addInterface(AlfrescoCmisService.class); +// proxyFactory.addAdvice(cmisExceptions); +// proxyFactory.addAdvice(cmisControl); +// proxyFactory.addAdvice(cmisStreams); +// proxyFactory.addAdvice(cmisTransactions); +// AlfrescoCmisService cmisService = (AlfrescoCmisService) proxyFactory.getProxy(); +// +// this.serviceWrapper = new CmisServiceWrapper( +// cmisService, +// connector.getTypesDefaultMaxItems(), connector.getTypesDefaultDepth(), +// connector.getObjectsDefaultMaxItems(), connector.getObjectsDefaultDepth()); + } + + @Override + public void destroy() + { + } + + /** + * TODO: + * We are producing new instances each time. + */ + @Override + public CmisService getService(final CallContext context) + { + if (logger.isDebugEnabled()) + { + StringBuilder sb = new StringBuilder(); + sb.append("getService: ").append(AuthenticationUtil.getFullyAuthenticatedUser()) + .append(" [runAsUser=").append(AuthenticationUtil.getRunAsUser()) + .append(",ctxUserName=").append(context.getUsername()) + .append(",ctxRepoId=").append(context.getRepositoryId()).append("]"); + + logger.debug(sb.toString()); + } + + // Avoid using guest user if the user is provided in the context + if(AuthenticationUtil.getFullyAuthenticatedUser() != null && authorityService.isGuestAuthority(AuthenticationUtil.getFullyAuthenticatedUser())) + { + AuthenticationUtil.clearCurrentSecurityContext(); + } + + AlfrescoCmisService service = getCmisServiceTarget(connector); + + // Wrap it + ProxyFactory proxyFactory = new ProxyFactory(service); + proxyFactory.addInterface(AlfrescoCmisService.class); + proxyFactory.addAdvice(cmisExceptions); + proxyFactory.addAdvice(cmisControl); + proxyFactory.addAdvice(cmisStreams); + proxyFactory.addAdvice(cmisTransactions); + AlfrescoCmisService cmisService = (AlfrescoCmisService) proxyFactory.getProxy(); + + CmisServiceWrapper wrapperService = new CmisServiceWrapper( + cmisService, + connector.getTypesDefaultMaxItems(), connector.getTypesDefaultDepth(), + connector.getObjectsDefaultMaxItems(), connector.getObjectsDefaultDepth()); + + // We use our specific open method here because only we know about it + cmisService.open(context); + + return wrapperService; + } + + protected AlfrescoCmisService getCmisServiceTarget(CMISConnector connector) + { + return new AlfrescoCmisServiceImpl(connector); + } +} diff --git a/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java b/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java index 285d44d53f..52503c02c6 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java +++ b/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java @@ -1,55 +1,55 @@ -package org.alfresco.opencmis; - -import java.util.Map; - -import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory; -import org.apache.chemistry.opencmis.commons.server.CallContext; -import org.apache.chemistry.opencmis.commons.server.CmisService; -import org.apache.chemistry.opencmis.server.support.CmisServiceWrapper; - -/** - * Factory for local OpenCMIS service objects. - * - * @author florian.mueller - */ -public class AlfrescoLocalCmisServiceFactory extends AbstractServiceFactory -{ - private static ThreadLocal> THREAD_LOCAL_SERVICE = new ThreadLocal>(); - - private static CMISConnector CMIS_CONNECTOR; - - @Override - public void init(Map parameters) - { - } - - /** - * Sets the CMIS connector. - */ - public static void setCmisConnector(CMISConnector connector) - { - CMIS_CONNECTOR = connector; - } - - @Override - public void destroy() - { - THREAD_LOCAL_SERVICE = null; - } - - @Override - public CmisService getService(CallContext context) - { - CmisServiceWrapper wrapperService = THREAD_LOCAL_SERVICE.get(); - if (wrapperService == null) - { - AlfrescoCmisService cmisService = new AlfrescoCmisServiceImpl(CMIS_CONNECTOR); - wrapperService = new CmisServiceWrapper(cmisService, - CMIS_CONNECTOR.getTypesDefaultMaxItems(), CMIS_CONNECTOR.getTypesDefaultDepth(), - CMIS_CONNECTOR.getObjectsDefaultMaxItems(), CMIS_CONNECTOR.getObjectsDefaultDepth()); - THREAD_LOCAL_SERVICE.set(wrapperService); - } - wrapperService.getWrappedService().open(context); - return wrapperService; - } -} +package org.alfresco.opencmis; + +import java.util.Map; + +import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory; +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.CmisService; +import org.apache.chemistry.opencmis.server.support.CmisServiceWrapper; + +/** + * Factory for local OpenCMIS service objects. + * + * @author florian.mueller + */ +public class AlfrescoLocalCmisServiceFactory extends AbstractServiceFactory +{ + private static ThreadLocal> THREAD_LOCAL_SERVICE = new ThreadLocal>(); + + private static CMISConnector CMIS_CONNECTOR; + + @Override + public void init(Map parameters) + { + } + + /** + * Sets the CMIS connector. + */ + public static void setCmisConnector(CMISConnector connector) + { + CMIS_CONNECTOR = connector; + } + + @Override + public void destroy() + { + THREAD_LOCAL_SERVICE = null; + } + + @Override + public CmisService getService(CallContext context) + { + CmisServiceWrapper wrapperService = THREAD_LOCAL_SERVICE.get(); + if (wrapperService == null) + { + AlfrescoCmisService cmisService = new AlfrescoCmisServiceImpl(CMIS_CONNECTOR); + wrapperService = new CmisServiceWrapper(cmisService, + CMIS_CONNECTOR.getTypesDefaultMaxItems(), CMIS_CONNECTOR.getTypesDefaultDepth(), + CMIS_CONNECTOR.getObjectsDefaultMaxItems(), CMIS_CONNECTOR.getObjectsDefaultDepth()); + THREAD_LOCAL_SERVICE.set(wrapperService); + } + wrapperService.getWrappedService().open(context); + return wrapperService; + } +} diff --git a/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java b/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java index fa54c81a97..bdaf506dee 100644 --- a/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java +++ b/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java @@ -1,106 +1,106 @@ -package org.alfresco.opencmis; - -import java.io.Serializable; -import java.util.HashMap; - -import org.alfresco.opencmis.dictionary.CMISDictionaryService; -import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; -import org.alfresco.repo.audit.extractor.AbstractDataExtractor; -import org.alfresco.service.cmr.model.FileInfo; -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.namespace.QName; -import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; - -/** - * An extractor that allows to filter data using the following rule: Audit - * records should only be created for items in the CMIS domain model. - * - * @author Stas Sokolovsky - */ -public class CMISChangeLogDataExtractor extends AbstractDataExtractor -{ - public static final String KEY_NODE_REF = "nodeRef"; - public static final String KEY_OBJECT_ID = "objectId"; - - private NodeService nodeService; - private CMISDictionaryService cmisDictionaryService; - private CMISConnector cmisConnector; - - /** - * Extracts relevant node refs and Ids from auditing data - * - * @see org.alfresco.repo.audit.extractor.DataExtractor#extractData(java.io.Serializable) - */ - public Serializable extractData(Serializable value) throws Throwable - { - NodeRef nodeRef = getNodeRef(value); - - HashMap result = new HashMap(5); - result.put(KEY_NODE_REF, nodeRef); - result.put(KEY_OBJECT_ID, cmisConnector.createObjectId(nodeRef, true)); - - return result; - } - - /** - * @return Returns true if items in the CMIS domain model - * @see org.alfresco.repo.audit.extractor.DataExtractor#isSupported(java.io.Serializable) - */ - public boolean isSupported(Serializable data) - { - if (data != null) - { - NodeRef nodeRef = getNodeRef(data); - if (nodeRef != null) - { - QName typeQName = nodeService.getType(nodeRef); - TypeDefinitionWrapper type = cmisDictionaryService.findNodeType(typeQName); - - return (type != null) - && (type.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT || type.getBaseTypeId() == BaseTypeId.CMIS_FOLDER); - } - } - return false; - } - - /** - * Gets the NodeRef from auditing data - * - * @param data - * audit data - * @return Node Reference - */ - private NodeRef getNodeRef(Serializable data) - { - NodeRef nodeRef = null; - if (data instanceof ChildAssociationRef) - { - nodeRef = ((ChildAssociationRef) data).getChildRef(); - } - else if (data instanceof FileInfo) - { - nodeRef = ((FileInfo) data).getNodeRef(); - } - else if (data instanceof NodeRef) - { - nodeRef = (NodeRef) data; - } - return nodeRef; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService) - { - this.cmisDictionaryService = cmisDictionaryService; - } - - public void setCmisConnector(CMISConnector cmisConnector) { - this.cmisConnector = cmisConnector; - } -} +package org.alfresco.opencmis; + +import java.io.Serializable; +import java.util.HashMap; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.repo.audit.extractor.AbstractDataExtractor; +import org.alfresco.service.cmr.model.FileInfo; +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.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; + +/** + * An extractor that allows to filter data using the following rule: Audit + * records should only be created for items in the CMIS domain model. + * + * @author Stas Sokolovsky + */ +public class CMISChangeLogDataExtractor extends AbstractDataExtractor +{ + public static final String KEY_NODE_REF = "nodeRef"; + public static final String KEY_OBJECT_ID = "objectId"; + + private NodeService nodeService; + private CMISDictionaryService cmisDictionaryService; + private CMISConnector cmisConnector; + + /** + * Extracts relevant node refs and Ids from auditing data + * + * @see org.alfresco.repo.audit.extractor.DataExtractor#extractData(java.io.Serializable) + */ + public Serializable extractData(Serializable value) throws Throwable + { + NodeRef nodeRef = getNodeRef(value); + + HashMap result = new HashMap(5); + result.put(KEY_NODE_REF, nodeRef); + result.put(KEY_OBJECT_ID, cmisConnector.createObjectId(nodeRef, true)); + + return result; + } + + /** + * @return Returns true if items in the CMIS domain model + * @see org.alfresco.repo.audit.extractor.DataExtractor#isSupported(java.io.Serializable) + */ + public boolean isSupported(Serializable data) + { + if (data != null) + { + NodeRef nodeRef = getNodeRef(data); + if (nodeRef != null) + { + QName typeQName = nodeService.getType(nodeRef); + TypeDefinitionWrapper type = cmisDictionaryService.findNodeType(typeQName); + + return (type != null) + && (type.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT || type.getBaseTypeId() == BaseTypeId.CMIS_FOLDER); + } + } + return false; + } + + /** + * Gets the NodeRef from auditing data + * + * @param data + * audit data + * @return Node Reference + */ + private NodeRef getNodeRef(Serializable data) + { + NodeRef nodeRef = null; + if (data instanceof ChildAssociationRef) + { + nodeRef = ((ChildAssociationRef) data).getChildRef(); + } + else if (data instanceof FileInfo) + { + nodeRef = ((FileInfo) data).getNodeRef(); + } + else if (data instanceof NodeRef) + { + nodeRef = (NodeRef) data; + } + return nodeRef; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService) + { + this.cmisDictionaryService = cmisDictionaryService; + } + + public void setCmisConnector(CMISConnector cmisConnector) { + this.cmisConnector = cmisConnector; + } +} diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index b0b476c33a..29cb08ab02 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -1,4041 +1,4041 @@ -package org.alfresco.opencmis; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.SequenceInputStream; -import java.io.Serializable; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.EnumSet; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TimeZone; -import java.util.TreeSet; - -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.events.types.ContentEvent; -import org.alfresco.events.types.ContentEventImpl; -import org.alfresco.events.types.ContentReadRangeEvent; -import org.alfresco.events.types.Event; -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.ActivityPosterImpl.ActivityInfo; -import org.alfresco.opencmis.dictionary.CMISActionEvaluator; -import org.alfresco.opencmis.dictionary.CMISAllowedActionEnum; -import org.alfresco.opencmis.dictionary.CMISDictionaryService; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.opencmis.dictionary.CMISObjectVariant; -import org.alfresco.opencmis.dictionary.CMISPropertyAccessor; -import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper; -import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; -import org.alfresco.opencmis.dictionary.ItemTypeDefinitionWrapper; -import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; -import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; -import org.alfresco.opencmis.search.CMISQueryOptions; -import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; -import org.alfresco.opencmis.search.CMISQueryService; -import org.alfresco.opencmis.search.CMISResultSet; -import org.alfresco.opencmis.search.CMISResultSetColumn; -import org.alfresco.opencmis.search.CMISResultSetRow; -import org.alfresco.repo.Client; -import org.alfresco.repo.Client.ClientType; -import org.alfresco.repo.action.executer.ContentMetadataExtracter; -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.events.EventPreparator; -import org.alfresco.repo.events.EventPublisher; -import org.alfresco.repo.model.filefolder.GetChildrenCannedQuery; -import org.alfresco.repo.model.filefolder.HiddenAspect; -import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility; -import org.alfresco.repo.policy.BehaviourFilter; -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.security.permissions.PermissionReference; -import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl; -import org.alfresco.repo.security.permissions.impl.ModelDAO; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.tenant.TenantDeployer; -import org.alfresco.repo.thumbnail.ThumbnailDefinition; -import org.alfresco.repo.thumbnail.ThumbnailHelper; -import org.alfresco.repo.thumbnail.ThumbnailRegistry; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.repo.version.VersionBaseModel; -import org.alfresco.repo.version.VersionModel; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.audit.AuditQueryParameters; -import org.alfresco.service.cmr.audit.AuditService; -import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.InvalidAspectException; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.model.FileExistsException; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.rendition.RenditionService; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.MimetypeService; -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.Path.ChildAssocElement; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AccessPermission; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.cmr.thumbnail.ThumbnailService; -import org.alfresco.service.cmr.version.VersionHistory; -import org.alfresco.service.cmr.version.VersionService; -import org.alfresco.service.cmr.version.VersionType; -import org.alfresco.service.descriptor.Descriptor; -import org.alfresco.service.descriptor.DescriptorService; -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.FileFilterMode; -import org.alfresco.util.Pair; -import org.alfresco.util.TempFileProvider; -import org.apache.chemistry.opencmis.commons.BasicPermissions; -import org.apache.chemistry.opencmis.commons.PropertyIds; -import org.apache.chemistry.opencmis.commons.data.Ace; -import org.apache.chemistry.opencmis.commons.data.Acl; -import org.apache.chemistry.opencmis.commons.data.AllowableActions; -import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement; -import org.apache.chemistry.opencmis.commons.data.ContentStream; -import org.apache.chemistry.opencmis.commons.data.ObjectData; -import org.apache.chemistry.opencmis.commons.data.ObjectList; -import org.apache.chemistry.opencmis.commons.data.PermissionMapping; -import org.apache.chemistry.opencmis.commons.data.Properties; -import org.apache.chemistry.opencmis.commons.data.PropertyData; -import org.apache.chemistry.opencmis.commons.data.PropertyId; -import org.apache.chemistry.opencmis.commons.data.PropertyString; -import org.apache.chemistry.opencmis.commons.data.RenditionData; -import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; -import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition; -import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition; -import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; -import org.apache.chemistry.opencmis.commons.enums.AclPropagation; -import org.apache.chemistry.opencmis.commons.enums.Action; -import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; -import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl; -import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges; -import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates; -import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; -import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; -import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions; -import org.apache.chemistry.opencmis.commons.enums.Cardinality; -import org.apache.chemistry.opencmis.commons.enums.ChangeType; -import org.apache.chemistry.opencmis.commons.enums.CmisVersion; -import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed; -import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; -import org.apache.chemistry.opencmis.commons.enums.PropertyType; -import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection; -import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions; -import org.apache.chemistry.opencmis.commons.enums.Updatability; -import org.apache.chemistry.opencmis.commons.enums.VersioningState; -import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyData; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ChangeEventInfoDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl; -import org.apache.chemistry.opencmis.commons.server.CallContext; -import org.apache.chemistry.opencmis.commons.server.CmisService; -import org.apache.chemistry.opencmis.commons.spi.Holder; -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ApplicationContextEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; -import org.springframework.util.StringUtils; - -/** - * Bridge connecting Alfresco and OpenCMIS. - *

- * This class provides many of the typical services that the {@link CmisService} implementation - * will need to use Alfresco. - * - * @author florian.mueller - * @author Derek Hulley - * @author steveglover - */ -public class CMISConnector implements ApplicationContextAware, ApplicationListener, TenantDeployer -{ - private static Log logger = LogFactory.getLog(CMISConnector.class); - - // mappings from cmis property names to their Alfresco property name counterparts (used by getChildren) - private static Map SORT_PROPERTY_MAPPINGS = new HashMap(); - - static - { - SORT_PROPERTY_MAPPINGS.put(PropertyIds.LAST_MODIFICATION_DATE, ContentModel.PROP_MODIFIED); - SORT_PROPERTY_MAPPINGS.put(PropertyIds.CREATION_DATE, ContentModel.PROP_CREATED); - } - - public static final char ID_SEPERATOR = ';'; - public static final char REPLACEMENT_CHAR = '_'; - public static final String ASSOC_ID_PREFIX = "assoc:"; - public static final String PWC_VERSION_LABEL = "pwc"; - public static final String UNVERSIONED_VERSION_LABEL = "1.0"; - - public static final String RENDITION_NONE = "cmis:none"; - - public static final String CMIS_CHANGELOG_AUDIT_APPLICATION = "CMISChangeLog"; - - public static final String ALFRESCO_EXTENSION_NAMESPACE = "http://www.alfresco.org"; - public static final String CMIS_NAMESPACE = "http://docs.oasis-open.org/ns/cmis/core/200908/"; - public static final String ASPECTS = "aspects"; - public static final String SET_ASPECTS = "setAspects"; - public static final String APPLIED_ASPECTS = "appliedAspects"; - public static final String ASPECTS_TO_ADD = "aspectsToAdd"; - public static final String ASPECTS_TO_REMOVE = "aspectsToRemove"; - public static final String PROPERTIES = "properties"; - - private static final BigInteger TYPES_DEFAULT_MAX_ITEMS = BigInteger.valueOf(50); - private static final BigInteger TYPES_DEFAULT_DEPTH = BigInteger.valueOf(-1); - private static final BigInteger OBJECTS_DEFAULT_MAX_ITEMS = BigInteger.valueOf(200); - private static final BigInteger OBJECTS_DEFAULT_DEPTH = BigInteger.valueOf(10); - - private static final String QUERY_NAME_OBJECT_ID = "cmis:objectId"; - private static final String QUERY_NAME_OBJECT_TYPE_ID = "cmis:objectTypeId"; - private static final String QUERY_NAME_BASE_TYPE_ID = "cmis:baseTypeId"; - - private static final String CMIS_USER = "cmis:user"; - - private static final BigInteger maxInt = BigInteger.valueOf(Integer.MAX_VALUE); - private static final BigInteger minInt = BigInteger.valueOf(Integer.MIN_VALUE); - private static final BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); - private static final BigInteger minLong = BigInteger.valueOf(Long.MIN_VALUE); - - // lifecycle - private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); - - // Alfresco objects - private DescriptorService descriptorService; - private NodeService nodeService; - private VersionService versionService; - private CheckOutCheckInService checkOutCheckInService; - private LockService lockService; - private ContentService contentService; - private RenditionService renditionService; - private FileFolderService fileFolderService; - private TenantAdminService tenantAdminService; - private TransactionService transactionService; - private AuthenticationService authenticationService; - private PermissionService permissionService; - private ModelDAO permissionModelDao; - private CMISDictionaryService cmisDictionaryService; - private CMISDictionaryService cmisDictionaryService11; - private CMISQueryService cmisQueryService; - private CMISQueryService cmisQueryService11; - private MimetypeService mimetypeService; - private AuditService auditService; - private NamespaceService namespaceService; - private SearchService searchService; - private DictionaryService dictionaryService; - private SiteService siteService; - private ActionService actionService; - private ThumbnailService thumbnailService; - private ServiceRegistry serviceRegistry; - private EventPublisher eventPublisher; - - private CmisActivityPoster activityPoster; - - private BehaviourFilter behaviourFilter; - - private HiddenAspect hiddenAspect; - - private StoreRef storeRef; - private String rootPath; - private Map> kindToRenditionNames; - - // note: cache is tenant-aware (if using TransctionalCache impl) - - private SimpleCache singletonCache; // eg. for cmisRootNodeRef, cmisRenditionMapping - private final String KEY_CMIS_ROOT_NODEREF = "key.cmisRoot.noderef"; - private final String KEY_CMIS_RENDITION_MAPPING_NODEREF = "key.cmisRenditionMapping.noderef"; - - private String proxyUser; - private boolean openHttpSession = false; - - // OpenCMIS objects - private BigInteger typesDefaultMaxItems = TYPES_DEFAULT_MAX_ITEMS; - private BigInteger typesDefaultDepth = TYPES_DEFAULT_DEPTH; - private BigInteger objectsDefaultMaxItems = OBJECTS_DEFAULT_MAX_ITEMS; - private BigInteger objectsDefaultDepth = OBJECTS_DEFAULT_DEPTH; - - private List repositoryPermissions; - private Map permissionMappings; - - private ObjectFilter objectFilter; - - // Bulk update properties - private int bulkMaxItems = 1000; - private int bulkBatchSize = 20; - private int bulkWorkerThreads = 2; - - // -------------------------------------------------------------- - // Configuration - // -------------------------------------------------------------- - - public void setObjectFilter(ObjectFilter objectFilter) - { - this.objectFilter = objectFilter; - } - - /** - * Sets the root store. - * - * @param store - * store_type://store_id - */ - public void setStore(String store) - { - this.storeRef = new StoreRef(store); - } - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - - public void setActivityPoster(CmisActivityPoster activityPoster) - { - this.activityPoster = activityPoster; - } - - public CmisActivityPoster getActivityPoster() - { - return activityPoster; - } - - public void setHiddenAspect(HiddenAspect hiddenAspect) - { - this.hiddenAspect = hiddenAspect; - } - - public boolean isHidden(NodeRef nodeRef) - { - final FileFilterMode.Client client = FileFilterMode.getClient(); - return (hiddenAspect.getVisibility(client, nodeRef) == Visibility.NotVisible); - } - - /** - * Sets the root path. - * - * @param path - * path within default store - */ - public void setRootPath(String path) - { - rootPath = path; - } - - public BigInteger getTypesDefaultMaxItems() - { - return typesDefaultMaxItems; - } - - public void setTypesDefaultMaxItems(BigInteger typesDefaultMaxItems) - { - this.typesDefaultMaxItems = typesDefaultMaxItems; - } - - public BigInteger getTypesDefaultDepth() - { - return typesDefaultDepth; - } - - public void setTypesDefaultDepth(BigInteger typesDefaultDepth) - { - this.typesDefaultDepth = typesDefaultDepth; - } - - public BigInteger getObjectsDefaultMaxItems() - { - return objectsDefaultMaxItems; - } - - public void setObjectsDefaultMaxItems(BigInteger objectsDefaultMaxItems) - { - this.objectsDefaultMaxItems = objectsDefaultMaxItems; - } - - public BigInteger getObjectsDefaultDepth() - { - return objectsDefaultDepth; - } - - public void setObjectsDefaultDepth(BigInteger objectsDefaultDepth) - { - this.objectsDefaultDepth = objectsDefaultDepth; - } - - /** - * Set rendition kind mapping. - */ - public void setRenditionKindMapping(Map> renditionKinds) - { - this.kindToRenditionNames = renditionKinds; - } - - public void setOpenHttpSession(boolean openHttpSession) - { - this.openHttpSession = openHttpSession; - } - - public boolean openHttpSession() - { - return openHttpSession; - } - - public void setThumbnailService(ThumbnailService thumbnailService) - { - this.thumbnailService = thumbnailService; - } - - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.serviceRegistry = serviceRegistry; - } - - /** - * Sets the descriptor service. - */ - public void setDescriptorService(DescriptorService descriptorService) - { - this.descriptorService = descriptorService; - } - - public DescriptorService getDescriptorService() - { - return descriptorService; - } - - public void setBehaviourFilter(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } - - /** - * Sets the node service. - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public NodeService getNodeService() - { - return nodeService; - } - - public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } - - /** - * Sets the version service. - */ - public void setVersionService(VersionService versionService) - { - this.versionService = versionService; - } - - public VersionService getVersionService() - { - return versionService; - } - - /** - * Sets the checkOut/checkIn service. - */ - public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService) - { - this.checkOutCheckInService = checkOutCheckInService; - } - - public CheckOutCheckInService getCheckOutCheckInService() - { - return checkOutCheckInService; - } - - /** - * Sets the lock service. - */ - public LockService getLockService() - { - return lockService; - } - - public void setLockService(LockService lockService) - { - this.lockService = lockService; - } - - /** - * Sets the content service. - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - public ContentService getContentService() - { - return contentService; - } - - /** - * Sets the event publisher - */ - public void setEventPublisher(EventPublisher eventPublisher) - { - this.eventPublisher = eventPublisher; - } - - /** - * Sets the rendition service. - */ - public void setrenditionService(RenditionService renditionService) - { - this.renditionService = renditionService; - } - - /** - * Sets the file folder service. - */ - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - public FileFolderService getFileFolderService() - { - return fileFolderService; - } - - /** - * Sets the tenant admin service. - */ - public void setTenantAdminService(TenantAdminService tenantAdminService) - { - this.tenantAdminService = tenantAdminService; - } - - public void setSingletonCache(SimpleCache singletonCache) - { - this.singletonCache = singletonCache; - } - - /** - * Sets the transaction service. - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public TransactionService getTransactionService() - { - return transactionService; - } - - /** - * Sets the authentication service. - */ - public void setAuthenticationService(AuthenticationService authenticationService) - { - this.authenticationService = authenticationService; - } - - public AuthenticationService getAuthenticationService() - { - return authenticationService; - } - - /** - * Sets the permission service. - */ - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - /** - * Sets the permission model DAO. - */ - public void setPermissionModelDao(ModelDAO permissionModelDao) - { - this.permissionModelDao = permissionModelDao; - } - - public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService) - { - this.cmisDictionaryService = cmisDictionaryService; - } - - public void setOpenCMISDictionaryService11(CMISDictionaryService cmisDictionaryService) - { - this.cmisDictionaryService11 = cmisDictionaryService; - } - - public CMISDictionaryService getOpenCMISDictionaryService() - { - CmisVersion cmisVersion = getRequestCmisVersion(); - if(cmisVersion.equals(CmisVersion.CMIS_1_0)) - { - return cmisDictionaryService; - } - else - { - return cmisDictionaryService11; - } - } - - /** - * Sets the OpenCMIS query service. - */ - public void setOpenCMISQueryService(CMISQueryService cmisQueryService) - { - this.cmisQueryService = cmisQueryService; - } - - public void setOpenCMISQueryService11(CMISQueryService cmisQueryService) - { - this.cmisQueryService11 = cmisQueryService; - } - - public CMISQueryService getOpenCMISQueryService() - { - CmisVersion cmisVersion = getRequestCmisVersion(); - if(cmisVersion.equals(CmisVersion.CMIS_1_0)) - { - return cmisQueryService; - } - else - { - return cmisQueryService11; - } - } - - /** - * Sets the MIME type service. - */ - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } - - public MimetypeService getMimetypeService() - { - return mimetypeService; - } - - /** - * Sets the audit service. - */ - public void setAuditService(AuditService auditService) - { - this.auditService = auditService; - } - - /** - * Sets the namespace service. - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * Sets the search service. - */ - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public SearchService getSearchService() - { - return searchService; - } - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public DictionaryService getDictionaryService() - { - return dictionaryService; - } - - /** - * Not implemented - * @throws UnsupportedOperationException always - */ - public void setProxyUser(String proxyUser) - { -// this.proxyUser = proxyUser; - throw new UnsupportedOperationException("proxyUser setting not implemented. Please raise a JIRA request."); - } - - public String getProxyUser() - { - return proxyUser; - } - - /** - * Sets bulk update properties. - */ - public void setBulkMaxItems(int size) - { - bulkMaxItems = size; - } - - public int getBulkMaxItems() - { - return bulkMaxItems; - } - - public void setBulkBatchSize(int size) - { - bulkBatchSize = size; - } - - public int getBulkBatchSize() - { - return bulkBatchSize; - } - - public void setBulkWorkerThreads(int threads) - { - bulkWorkerThreads = threads; - } - - public int getBulkWorkerThreads() - { - return bulkWorkerThreads; - } - - // -------------------------------------------------------------- - // Lifecycle methods - // -------------------------------------------------------------- - - private File tmp; - - public void setup() - { - File tempDir = TempFileProvider.getTempDir(); - this.tmp = new File(tempDir, "CMISAppend"); - if(!this.tmp.exists() && !this.tmp.mkdir()) - { - throw new AlfrescoRuntimeException("Failed to create CMIS temporary directory"); - } - } - - public void init() - { - // register as tenant deployer - tenantAdminService.register(this); - - // set up and cache rendition mapping - getRenditionMapping(); - - // cache root node ref - getRootNodeRef(); - - // cache permission definitions and permission mappings - repositoryPermissions = getRepositoryPermissions(); - permissionMappings = getPermissionMappings(); - } - - public void destroy() - { - singletonCache.remove(KEY_CMIS_ROOT_NODEREF); - singletonCache.remove(KEY_CMIS_RENDITION_MAPPING_NODEREF); - } - - public void onEnableTenant() - { - init(); - } - - public void onDisableTenant() - { - destroy(); - } - - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - lifecycle.setApplicationContext(applicationContext); - } - - public void onApplicationEvent(ApplicationContextEvent event) - { - lifecycle.onApplicationEvent(event); - } - - /** - * Hooks into Spring Application Lifecycle. - */ - private class ProcessorLifecycle extends AbstractLifecycleBean - { - @Override - protected void onBootstrap(ApplicationEvent event) - { - init(); - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - } - } - - // -------------------------------------------------------------- - // Alfresco methods - // -------------------------------------------------------------- - - /* - * For the given cmis property name get the corresponding Alfresco property name. - * - * Certain CMIS properties (e.g. cmis:creationDate and cmis:lastModifiedBy) don't - * have direct mappings to Alfresco properties through the CMIS dictionary, because - * these mappings should not be exposed outside the repository through CMIS. For these, - * however, this method provides the mapping so that the sort works. - * - */ - public Pair getSortProperty(String cmisPropertyName, String direction) - { - QName sortPropName = null; - Pair sortProp = null; - - PropertyDefinitionWrapper propDef = getOpenCMISDictionaryService().findPropertyByQueryName(cmisPropertyName); - if (propDef != null) - { - if (propDef.getPropertyId().equals(PropertyIds.BASE_TYPE_ID)) - { - // special-case (see also ALF-13968) - for getChildren, using "cmis:baseTypeId" allows sorting of folders first and vice-versa (cmis:folder <-> cmis:document) - sortPropName = GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER; - } - else - { - sortPropName = propDef.getPropertyAccessor().getMappedProperty(); - } - - if (sortPropName == null) - { - // ok to map these properties because we are always getting current versions of nodes - sortPropName = SORT_PROPERTY_MAPPINGS.get(cmisPropertyName); - } - } - - if (sortPropName != null) - { - boolean sortAsc = (direction == null ? true : direction.equalsIgnoreCase("asc")); - sortProp = new Pair(sortPropName, sortAsc); - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("Ignore sort property '" + cmisPropertyName + " - mapping not found"); - } - } - - return sortProp; - } - - /** - * Asynchronously generates thumbnails for the given node. - * - * @param nodeRef NodeRef - * @param thumbnailNames Set - */ - public void createThumbnails(NodeRef nodeRef, Set thumbnailNames) - { - if(thumbnailNames == null || thumbnailNames.size() == 0) - { - return; - } - - ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry(); - - // If there's nothing currently registered to generate thumbnails for the - // specified mimetype, then log a message and bail out - Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); - if (contentData == null) - { - logger.info("Unable to create thumbnails as there is no content"); - return; - } - - long size = contentData.getSize(); - String mimeType = contentData.getMimetype(); - - for(String thumbnailName : thumbnailNames) - { - // Use the thumbnail registy to get the details of the thumbail - ThumbnailDefinition details = registry.getThumbnailDefinition(thumbnailName); - if(details == null) - { - // Throw exception - logger.warn("The thumbnail name '" + thumbnailName + "' is not registered"); - continue; - } - else - { - if(registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), mimeType, size, nodeRef, details)) - { - org.alfresco.service.cmr.action.Action action = ThumbnailHelper.createCreateThumbnailAction(details, serviceRegistry); - - // Queue async creation of thumbnail - actionService.executeAction(action, nodeRef, true, true); - } - else - { - logger.info("Unable to create thumbnail '" + details.getName() + "' for " + - mimeType + " as no transformer is currently available"); - } - } - } - } - - /** - * Extracts metadata for the node. - * - * @param nodeRef NodeRef - */ - public void extractMetadata(NodeRef nodeRef) - { - org.alfresco.service.cmr.action.Action action = actionService.createAction(ContentMetadataExtracter.EXECUTOR_NAME); - actionService.executeAction(action, nodeRef, true, false); - } - - public SiteInfo getSite(NodeRef nodeRef) - { - return siteService.getSite(nodeRef); - } - - /** - * Should the node be filtered? - */ - public boolean filter(NodeRef nodeRef) - { - return objectFilter.filter(nodeRef); - } - - public boolean disableBehaviour(QName className) - { - boolean wasEnabled = behaviourFilter.isEnabled(className); - if(wasEnabled) - { - behaviourFilter.disableBehaviour(className); - } - return wasEnabled; - } - - public boolean enableBehaviour(QName className) - { - boolean isEnabled = behaviourFilter.isEnabled(className); - if(!isEnabled) - { - behaviourFilter.enableBehaviour(className); - } - return isEnabled; - } - - - public boolean disableBehaviour(QName className, NodeRef nodeRef) - { - boolean wasEnabled = behaviourFilter.isEnabled(nodeRef, className); - if(wasEnabled) - { - behaviourFilter.disableBehaviour(nodeRef, className); - } - return wasEnabled; - } - - public boolean enableBehaviour(QName className, NodeRef nodeRef) - { - boolean isEnabled = behaviourFilter.isEnabled(nodeRef, className); - if(!isEnabled) - { - behaviourFilter.enableBehaviour(nodeRef, className); - } - return isEnabled; - } - - public StoreRef getRootStoreRef() - { - return getRootNodeRef().getStoreRef(); - } - - public void deleteNode(NodeRef nodeRef, boolean postActivity) - { - ActivityInfo activityInfo = null; - - // post activity after removal of the node - postActivity &= !hiddenAspect.hasHiddenAspect(nodeRef); - if(postActivity) - { - // get this information before the node is deleted - activityInfo = activityPoster.getActivityInfo(nodeRef); - } - - getNodeService().deleteNode(nodeRef); - - // post activity after removal of the node - if(postActivity && activityInfo != null) - { - activityPoster.postFileFolderDeleted(activityInfo); - } - } - - public RetryingTransactionHelper getRetryingTransactionHelper() - { - return transactionService.getRetryingTransactionHelper(); - } - - /** - * Returns the root folder node ref. - */ - public NodeRef getRootNodeRef() - { - NodeRef rootNodeRef = (NodeRef)singletonCache.get(KEY_CMIS_ROOT_NODEREF); - if (rootNodeRef == null) - { - rootNodeRef = AuthenticationUtil.runAs(new RunAsWork() - { - public NodeRef doWork() throws Exception - { - return transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback() - { - public NodeRef execute() throws Exception - { - NodeRef root = nodeService.getRootNode(storeRef); - List rootNodes = searchService.selectNodes(root, rootPath, null, - namespaceService, false); - if (rootNodes.size() != 1) - { - throw new CmisRuntimeException("Unable to locate CMIS root path " + rootPath); - } - return rootNodes.get(0); - }; - }, true); - } - }, AuthenticationUtil.getSystemUserName()); - - if (rootNodeRef == null) - { - throw new CmisObjectNotFoundException("Root folder path '" + rootPath + "' not found!"); - } - - singletonCache.put(KEY_CMIS_ROOT_NODEREF, rootNodeRef); - } - - return rootNodeRef; - } - - public String getName(NodeRef nodeRef) - { - try - { - Object name = nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - return (name instanceof String ? (String) name : null); - } - catch(InvalidNodeRefException inre) - { - return null; - } - } - - /** - * Cuts of the version information from an object id. - */ - public String getCurrentVersionId(String objectId) - { - if (objectId == null) - { - return null; - } - - int sepIndex = objectId.lastIndexOf(ID_SEPERATOR); - if (sepIndex > -1) - { - return objectId.substring(0, sepIndex); - } - - return objectId; - } - - /** - * Creates an object info object. - */ - public CMISNodeInfoImpl createNodeInfo(String objectId) - { - return new CMISNodeInfoImpl(this, objectId); - } - - /** - * Creates an object info object. - */ - public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef) - { - return createNodeInfo(nodeRef, null, null, null, true); - } - - /** - * Creates an object info object. - */ - public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, QName nodeType, Map nodeProps, VersionHistory versionHistory, boolean checkExists) - { - return new CMISNodeInfoImpl(this, nodeRef, nodeType, nodeProps, versionHistory, checkExists); - } - - /** - * Creates an object info object. - */ - public CMISNodeInfoImpl createNodeInfo(AssociationRef assocRef) - { - return new CMISNodeInfoImpl(this, assocRef); - } - - /* - * Strip store ref from the id, if there is one. - */ - private String getGuid(String id) - { - int idx = id.lastIndexOf("/"); - if(idx != -1) - { - return id.substring(idx + 1); - } - else - { - return id; - } - } - - /* - * Construct an object id based on the incoming assocRef and versionLabel. The object id will always - * be the assocRef guid. - */ - public String constructObjectId(AssociationRef assocRef, String versionLabel) - { - return constructObjectId(assocRef, versionLabel, isPublicApi()); - } - - public String constructObjectId(AssociationRef assocRef, String versionLabel, boolean dropStoreRef) - { - StringBuilder sb = new StringBuilder(CMISConnector.ASSOC_ID_PREFIX); - if(dropStoreRef) - { - // always return the guid - sb.append(assocRef.getId()); - } - else - { - sb.append(assocRef.toString()); - } - - if(versionLabel != null) - { - sb.append(CMISConnector.ID_SEPERATOR); - sb.append(versionLabel); - } - return sb.toString(); - } - - /* - * Construct an object id based on the incoming incomingObjectId. The object id will always - * be the node guid. - */ - public String constructObjectId(String incomingObjectId) - { - return constructObjectId(incomingObjectId, null); - } - - /* - * Construct an object id based on the incoming incomingNodeId and versionLabel. The object id will always - * be the node guid. - */ - public String constructObjectId(String incomingNodeId, String versionLabel) - { - return constructObjectId(incomingNodeId, versionLabel, isPublicApi()); - } - - public String constructObjectId(String incomingNodeId, String versionLabel, boolean dropStoreRef) - { - StringBuilder sb = new StringBuilder(); - if(dropStoreRef) - { - // always return the guid - sb.append(getGuid(incomingNodeId)); - } - else - { - sb.append(incomingNodeId); - } - if(versionLabel != null) - { - sb.append(CMISConnector.ID_SEPERATOR); - sb.append(versionLabel); - } - return sb.toString(); - } - - public void createVersion(NodeRef nodeRef, VersionType versionType, String reason) - { - if(versionService.getVersionHistory(nodeRef) == null) - { - // no version history. Make sure we have an initial major version 1.0. - Map versionProperties = new HashMap(2); - versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); - versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial version"); - versionService.createVersion(nodeRef, versionProperties); - } - - Map versionProperties = new HashMap(2); - versionProperties.put(VersionModel.PROP_VERSION_TYPE, versionType); - versionProperties.put(VersionModel.PROP_DESCRIPTION, reason); - versionService.createVersion(nodeRef, versionProperties); - } - - public CmisVersion getRequestCmisVersion() - { - CallContext callContext = AlfrescoCmisServiceCall.get(); - CmisVersion cmisVersion = (callContext != null ? callContext.getCmisVersion() : CmisVersion.CMIS_1_0); - return cmisVersion; - } - - private boolean isPublicApi() - { - boolean isPublicApi = false; - CallContext callContext = AlfrescoCmisServiceCall.get(); - if(callContext != null) - { - String value = (String)callContext.get("isPublicApi"); - isPublicApi = (value == null ? false : Boolean.parseBoolean(value)); - } - return isPublicApi; - } - - /* - * Construct an object id based on the incoming incomingNodeRef and versionLabel. The object id will always - * be the incomingNodeRef guid. - */ - public String constructObjectId(NodeRef incomingNodeRef, String versionLabel) - { - return constructObjectId(incomingNodeRef, versionLabel, isPublicApi()); - } - - public String constructObjectId(NodeRef incomingNodeRef, String versionLabel, boolean dropStoreRef) - { - StringBuilder sb = new StringBuilder(); - sb.append(dropStoreRef ? incomingNodeRef.getId() : incomingNodeRef.toString()); - if(versionLabel != null) - { - sb.append(CMISConnector.ID_SEPERATOR); - sb.append(versionLabel); - } - return sb.toString(); - } - - /** - * Compiles a CMIS object if for a live node. - */ - public String createObjectId(NodeRef nodeRef) - { - return createObjectId(nodeRef, isPublicApi()); - } - - public String createObjectId(NodeRef nodeRef, boolean dropStoreRef) - { - QName typeQName = nodeService.getType(nodeRef); - TypeDefinitionWrapper type = getOpenCMISDictionaryService().findNodeType(typeQName); - - if(type instanceof ItemTypeDefinitionWrapper) - { - return constructObjectId(nodeRef, null); - } - - if(type instanceof FolderTypeDefintionWrapper) - { - return constructObjectId(nodeRef, null, dropStoreRef); - } - - Serializable versionLabel = getNodeService() - .getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - if (versionLabel == null) - { - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; - } - - return constructObjectId(nodeRef, (String)versionLabel, dropStoreRef); - } - - private boolean isFolder(NodeRef nodeRef) - { - return getType(nodeRef) instanceof FolderTypeDefintionWrapper; - } - - /** - * Returns the type definition of a node or null if no type - * definition could be found. - */ - public TypeDefinitionWrapper getType(NodeRef nodeRef) - { - try - { - QName typeQName = nodeService.getType(nodeRef); - return getOpenCMISDictionaryService().findNodeType(typeQName); - } - catch(InvalidNodeRefException inre) - { - return null; - } - } - - /** - * Returns the type definition of an association or null if no - * type definition could be found. - */ - public TypeDefinitionWrapper getType(AssociationRef assocRef) - { - QName typeQName = assocRef.getTypeQName(); - return getOpenCMISDictionaryService().findAssocType(typeQName); - } - - /** - * Returns the type definition of a node or null if no type - * definition could be found. - */ - public TypeDefinitionWrapper getType(String cmisTypeId) - { - return getOpenCMISDictionaryService().findType(cmisTypeId); - } - - /** - * Returns the definition after it has checked if the type can be used for - * object creation. - */ - public TypeDefinitionWrapper getTypeForCreate(String cmisTypeId, BaseTypeId baseTypeId) - { - TypeDefinitionWrapper type = getType(cmisTypeId); - if ((type == null) || (type.getBaseTypeId() != baseTypeId)) - { - switch (baseTypeId) - { - case CMIS_DOCUMENT: - throw new CmisConstraintException("Type is not a document type!"); - case CMIS_FOLDER: - throw new CmisConstraintException("Type is not a folder type!"); - case CMIS_RELATIONSHIP: - throw new CmisConstraintException("Type is not a relationship type!"); - case CMIS_POLICY: - throw new CmisConstraintException("Type is not a policy type!"); - case CMIS_ITEM: - throw new CmisConstraintException("Type is not an item type!"); - } - } - - if (!type.getTypeDefinition(false).isCreatable()) - { - throw new CmisConstraintException("Type is not creatable!"); - } - - return type; - } - - /** - * Applies a versioning state to a document. - */ - public void applyVersioningState(NodeRef nodeRef, VersioningState versioningState) - { - if (versioningState == VersioningState.CHECKEDOUT) - { - if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) - { - nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); - } - - // MNT-14687 : Creating a document as checkedout and then cancelling the checkout should delete the document - nodeService.addAspect(nodeRef, ContentModel.ASPECT_CMIS_CREATED_CHECKEDOUT, null); - - getCheckOutCheckInService().checkout(nodeRef); - } - else if ((versioningState == VersioningState.MAJOR) || (versioningState == VersioningState.MINOR)) - { - if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) - { - nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); - } - - Map versionProperties = new HashMap(5); - versionProperties.put( - VersionModel.PROP_VERSION_TYPE, - versioningState == VersioningState.MAJOR ? VersionType.MAJOR : VersionType.MINOR); - versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial Version"); - - versionService.createVersion(nodeRef, versionProperties); - } - } - - /** - * Checks if a child of a given type can be added to a given folder. - */ - @SuppressWarnings("unchecked") - public void checkChildObjectType(CMISNodeInfo folderInfo, String childType) - { - TypeDefinitionWrapper targetType = folderInfo.getType(); - PropertyDefinitionWrapper allowableChildObjectTypeProperty = targetType - .getPropertyById(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS); - List childTypes = (List) allowableChildObjectTypeProperty.getPropertyAccessor().getValue( - folderInfo); - - if ((childTypes == null) || childTypes.isEmpty()) - { - return; - } - - if (!childTypes.contains(childType)) - { - throw new CmisConstraintException("Objects of type '" + childType + "' cannot be added to this folder!"); - } - } - - /** - * Creates the CMIS object for a node. - */ - public ObjectData createCMISObject(CMISNodeInfo info, String filter, boolean includeAllowableActions, - IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds, - boolean includeAcl) - { - if (info.getType() == null) - { - throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); - } - - Properties nodeProps = (info.isRelationship() ? getAssocProperties(info, filter) : getNodeProperties(info, filter)); - - return createCMISObjectImpl(info, nodeProps, filter, includeAllowableActions, includeRelationships, - renditionFilter, includePolicyIds, includeAcl); - } - - @SuppressWarnings("unchecked") - private ObjectData createCMISObjectImpl(final CMISNodeInfo info, Properties nodeProps, String filter, - boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, - boolean includePolicyIds, boolean includeAcl) - { - final ObjectDataImpl result = new ObjectDataImpl(); - - // set allowable actions - if (includeAllowableActions) - { - result.setAllowableActions(getAllowableActions(info)); - } - - // set policy ids - if (includePolicyIds) - { - result.setPolicyIds(new PolicyIdListImpl()); - } - - if (info.isRelationship()) - { - // set properties - result.setProperties(getAssocProperties(info, filter)); - - // set ACL - if (includeAcl) - { - // association have no ACL - return an empty list of ACEs - result.setAcl(new AccessControlListImpl((List) Collections.EMPTY_LIST)); - result.setIsExactAcl(Boolean.FALSE); - } - } - else - { - // set properties - result.setProperties(nodeProps); - - // set relationships - if (includeRelationships != IncludeRelationships.NONE) - { - result.setRelationships(getRelationships(info.getNodeRef(), includeRelationships)); - } - - // set renditions - if (!RENDITION_NONE.equals(renditionFilter)) - { - List renditions = getRenditions(info.getNodeRef(), renditionFilter, null, null); - if ((renditions != null) && (!renditions.isEmpty())) - { - result.setRenditions(renditions); - } - else - { - result.setRenditions(Collections.EMPTY_LIST); - } - } - - // set ACL - if (includeAcl) - { - AuthenticationUtil.runAsSystem(new RunAsWork() - { - @Override - public Void doWork() throws Exception - { - Acl acl = getACL(info.getCurrentNodeNodeRef(), false); - if (acl != null) - { - result.setAcl(acl); - result.setIsExactAcl(acl.isExact()); - } - return null; - } - }); - } - - // add aspects - List extensions = getAspectExtensions(info, filter, result.getProperties() - .getProperties().keySet()); - - if (!extensions.isEmpty()) - { - result.getProperties().setExtensions( - Collections.singletonList((CmisExtensionElement) new CmisExtensionElementImpl( - ALFRESCO_EXTENSION_NAMESPACE, ASPECTS, null, extensions))); - } - } - return result; - } - - public String getPath(NodeRef nodeRef) - { - try - { - Path path = nodeService.getPath(nodeRef); - - // skip to CMIS root path - NodeRef rootNode = getRootNodeRef(); - int i = 0; - while (i < path.size()) - { - Path.Element element = path.get(i); - if (element instanceof ChildAssocElement) - { - ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); - NodeRef node = assocRef.getChildRef(); - if (node.equals(rootNode)) - { - break; - } - } - i++; - } - - StringBuilder displayPath = new StringBuilder(64); - - if (path.size() - i == 1) - { - // render root path - displayPath.append("/"); - } - else - { - // render CMIS scoped path - i++; - while (i < path.size()) - { - Path.Element element = path.get(i); - if (element instanceof ChildAssocElement) - { - ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); - NodeRef node = assocRef.getChildRef(); - displayPath.append("/"); - displayPath.append(nodeService.getProperty(node, ContentModel.PROP_NAME)); - } - i++; - } - } - - return displayPath.toString(); - } - catch(InvalidNodeRefException inre) - { - return null; - } - } - - /** - * Gets the content from the repository. - */ - public ContentStream getContentStream(CMISNodeInfo info, String streamId, BigInteger offset, BigInteger length) - { - // get the type and check if the object can have content - TypeDefinitionWrapper type = info.getType(); - checkDocumentTypeForContent(type); - - // looks like a document, now get the content - ContentStreamImpl result = new ContentStreamImpl(); - result.setFileName(info.getName()); - - // if streamId is set, fetch other content - NodeRef streamNodeRef = info.getNodeRef(); - if ((streamId != null) && (streamId.length() > 0)) - { - CMISNodeInfo streamInfo = createNodeInfo(streamId); - if (!streamInfo.isVariant(CMISObjectVariant.CURRENT_VERSION)) - { - throw new CmisInvalidArgumentException("Stream id is invalid: " + streamId + ", expected variant " + CMISObjectVariant.CURRENT_VERSION + ", got variant " + streamInfo.getObjectVariant()); - } - - streamNodeRef = streamInfo.getNodeRef(); - type = streamInfo.getType(); - checkDocumentTypeForContent(type); - } - - // get the stream now - try - { - ContentReader contentReader = contentService.getReader(streamNodeRef, ContentModel.PROP_CONTENT); - if (contentReader == null) - { - throw new CmisConstraintException("Document has no content!"); - } - - result.setMimeType(contentReader.getMimetype()); - long contentSize = contentReader.getSize(); - - if ((offset == null) && (length == null)) - { - result.setStream(contentReader.getContentInputStream()); - result.setLength(BigInteger.valueOf(contentSize)); - publishReadEvent(streamNodeRef, info.getName(), result.getMimeType(), contentSize, contentReader.getEncoding(), null); - } - else - { - long off = (offset == null ? 0 : offset.longValue()); - long len = (length == null ? contentSize : length.longValue()); - if (off + len > contentSize) - { - len = contentReader.getSize() - off; - } - - result.setStream(new RangeInputStream(contentReader.getContentInputStream(), off, len)); - result.setLength(BigInteger.valueOf(len)); - publishReadEvent(streamNodeRef, info.getName(), result.getMimeType(), contentSize, contentReader.getEncoding(), off+" - "+len); - } - } - catch (Exception e) - { - if (e instanceof CmisBaseException) - { - throw (CmisBaseException) e; - } - else - { - StringBuilder msg = new StringBuilder("Failed to retrieve content: " + e.getMessage()); - Throwable cause = e.getCause(); - if(cause != null) - { - // add the cause to the CMIS exception - msg.append(", "); - msg.append(cause.getMessage()); - } - throw new CmisRuntimeException(msg.toString(), e); - } - } - - return result; - } - - /** - * Notifies listeners that a read has taken place. - * - * @param nodeRef NodeRef - * @param name String - * @param mimeType String - * @param contentSize long - * @param encoding String - * @param range String - */ - protected void publishReadEvent(final NodeRef nodeRef, final String name, final String mimeType, final long contentSize, final String encoding, final String range) - { - final QName nodeType = nodeRef==null?null:nodeService.getType(nodeRef); - - eventPublisher.publishEvent(new EventPreparator(){ - @Override - public Event prepareEvent(String user, String networkId, String transactionId) - { - if (StringUtils.hasText(range)) - { - return new ContentReadRangeEvent(user, networkId, transactionId, - nodeRef.getId(), null, nodeType.toString(), Client.asType(ClientType.cmis), name, mimeType, contentSize, encoding, range); - } - else - { - return new ContentEventImpl(ContentEvent.DOWNLOAD, user, networkId, transactionId, - nodeRef.getId(), null, nodeType.toString(), Client.asType(ClientType.cmis), name, mimeType, contentSize, encoding); - } - } - }); - - - } - - public void appendContent(CMISNodeInfo nodeInfo, ContentStream contentStream, boolean isLastChunk) throws IOException - { - NodeRef nodeRef = nodeInfo.getNodeRef(); - - this.disableBehaviour(ContentModel.ASPECT_VERSIONABLE, nodeRef); - - if(!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT)) - { - Map props = new HashMap(); - props.put(ContentModel.PROP_GOT_FIRST_CHUNK, true); - nodeService.addAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT, props); - } - - ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); - InputStream existingContentInput = (reader != null ? reader.getContentInputStream() : null); - InputStream input = contentStream.getStream(); - - ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - OutputStream out = new BufferedOutputStream(writer.getContentOutputStream()); - - InputStream in = null; - if(existingContentInput != null) - { - in = new SequenceInputStream(existingContentInput, input); - } - else - { - in = input; - } - - try - { - IOUtils.copy(in, out); - - if(isLastChunk) - { - nodeService.removeAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT); - getActivityPoster().postFileFolderUpdated(nodeInfo.isFolder(), nodeRef); - - createVersion(nodeRef, VersionType.MINOR, "Appended content stream"); - } - } - finally - { - if(in != null) - { - in.close(); - } - if(out != null) - { - out.close(); - } - - this.enableBehaviour(ContentModel.ASPECT_VERSIONABLE, nodeRef); - } - } - - private void checkDocumentTypeForContent(TypeDefinitionWrapper type) - { - if (type == null) - { - throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); - } - if (!(type instanceof DocumentTypeDefinitionWrapper)) - { - throw new CmisStreamNotSupportedException("Object is not a document!"); - } - if (((DocumentTypeDefinition) type.getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) - { - throw new CmisConstraintException("Document cannot have content!"); - } - } - - private void addAspectProperties(CMISNodeInfo info, String filter, PropertiesImpl result) - { - if (getRequestCmisVersion().equals(CmisVersion.CMIS_1_1)) - { - Set propertyIds = new HashSet<>(); - Set filterSet = splitFilter(filter); - - Set aspects = info.getNodeAspects(); - for (QName aspect : aspects) - { - TypeDefinitionWrapper aspectType = getOpenCMISDictionaryService().findNodeType(aspect); - if (aspectType == null) - { - continue; - } - - for (PropertyDefinitionWrapper propDef : aspectType.getProperties()) - { - if (propertyIds.contains(propDef.getPropertyId())) - { - // skip properties that have already been added - continue; - } - - if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) - { - // skip properties that are not in the filter - continue; - } - - Serializable value = propDef.getPropertyAccessor().getValue(info); - result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value)); - - // mark property as 'added' - propertyIds.add(propDef.getPropertyId()); - } - } - } - } - - public Properties getNodeProperties(CMISNodeInfo info, String filter) - { - PropertiesImpl result = new PropertiesImpl(); - - Set filterSet = splitFilter(filter); - - for (PropertyDefinitionWrapper propDef : info.getType().getProperties()) - { - if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID)) - { - // don't filter the object id - if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) - { - // skip properties that are not in the filter - continue; - } - } - - Serializable value = propDef.getPropertyAccessor().getValue(info); - result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value)); - } - - addAspectProperties(info, filter, result); - - return result; - } - - public Properties getAssocProperties(CMISNodeInfo info, String filter) - { - PropertiesImpl result = new PropertiesImpl(); - - Set filterSet = splitFilter(filter); - - for (PropertyDefinitionWrapper propDefWrap : info.getType().getProperties()) - { - PropertyDefinition propDef = propDefWrap.getPropertyDefinition(); - if ((filterSet != null) && (!filterSet.contains(propDef.getQueryName()))) - { - // skip properties that are not in the filter - continue; - } - - CMISPropertyAccessor cmisPropertyAccessor = propDefWrap.getPropertyAccessor(); - Serializable value = cmisPropertyAccessor.getValue(info); - PropertyType propType = propDef.getPropertyType(); - PropertyData propertyData = getProperty(propType, propDefWrap, value); - result.addProperty(propertyData); - } - - return result; - } - - /** - * Builds aspect extension. - */ - public List getAspectExtensions(CMISNodeInfo info, String filter, Set alreadySetProperties) - { - List extensions = new ArrayList(); - Set propertyIds = new HashSet(alreadySetProperties); - List propertyExtensionList = new ArrayList(); - Set filterSet = splitFilter(filter); - - Set aspects = info.getNodeAspects(); - for (QName aspect : aspects) - { - TypeDefinitionWrapper aspectType = getOpenCMISDictionaryService().findNodeType(aspect); - if (aspectType == null) - { - continue; - } - - AspectDefinition aspectDefinition = dictionaryService.getAspect(aspect); - Map aspectProperties = aspectDefinition.getProperties(); - - extensions.add(new CmisExtensionElementImpl(ALFRESCO_EXTENSION_NAMESPACE, APPLIED_ASPECTS, null, aspectType - .getTypeId())); - - for (PropertyDefinitionWrapper propDef : aspectType.getProperties()) - { - boolean addPropertyToExtensionList = getRequestCmisVersion().equals(CmisVersion.CMIS_1_1) && aspectProperties.keySet().contains(propDef.getAlfrescoName()); - // MNT-11876 : add property to extension even if it has been returned (CMIS 1.1) - if (propertyIds.contains(propDef.getPropertyId()) && !addPropertyToExtensionList) - { - // skip properties that have already been added - continue; - } - - if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) - { - // skip properties that are not in the filter - continue; - } - - Serializable value = propDef.getPropertyAccessor().getValue(info); - propertyExtensionList.add(createAspectPropertyExtension(propDef.getPropertyDefinition(), value)); - - // mark property as 'added' - propertyIds.add(propDef.getPropertyId()); - } - } - - if (!propertyExtensionList.isEmpty()) - { - CmisExtensionElementImpl propertiesExtension = new CmisExtensionElementImpl( - ALFRESCO_EXTENSION_NAMESPACE, "properties", null, propertyExtensionList); - extensions.addAll(Collections.singletonList(propertiesExtension)); - } - - return extensions; - } - - /** - * Creates a property extension element. - */ - @SuppressWarnings("rawtypes") - private CmisExtensionElement createAspectPropertyExtension(PropertyDefinition propertyDefinition, Object value) - { - String name; - switch (propertyDefinition.getPropertyType()) - { - case BOOLEAN: - name = "propertyBoolean"; - break; - case DATETIME: - name = "propertyDateTime"; - break; - case DECIMAL: - name = "propertyDecimal"; - break; - case INTEGER: - name = "propertyInteger"; - break; - case ID: - name = "propertyId"; - break; - default: - name = "propertyString"; - } - - Map attributes = new HashMap(); - attributes.put("propertyDefinitionId", propertyDefinition.getId()); - attributes.put("queryName", propertyDefinition.getQueryName()); - // optional value - if (propertyDefinition.getDisplayName() !=null && propertyDefinition.getDisplayName().trim().length() > 0) - { - attributes.put("displayName", propertyDefinition.getDisplayName()); - } - // optional value - if (propertyDefinition.getLocalName() !=null && propertyDefinition.getLocalName().trim().length() > 0) - { - attributes.put("localName", propertyDefinition.getLocalName()); - } - - - List propertyValues = new ArrayList(); - if (value != null) - { - if (value instanceof List) - { - for (Object o : ((List) value)) - { - if(o != null) - { - propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null, - convertAspectPropertyValue(o))); - } - else - { - logger.warn("Unexpected null entry in list value for property " + propertyDefinition.getDisplayName() - + ", value = " + value); - } - } - } - else - { - propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null, - convertAspectPropertyValue(value))); - } - } - - return new CmisExtensionElementImpl(CMIS_NAMESPACE, name, attributes, propertyValues); - } - - private String convertAspectPropertyValue(Object value) - { - if (value instanceof Date) - { - GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); - cal.setTime((Date) value); - value = cal; - } - - if (value instanceof GregorianCalendar) - { - DatatypeFactory df; - try - { - df = DatatypeFactory.newInstance(); - } - catch (DatatypeConfigurationException e) - { - throw new IllegalArgumentException("Aspect conversation exception: " + e.getMessage(), e); - } - return df.newXMLGregorianCalendar((GregorianCalendar) value).toXMLFormat(); - } - - // MNT-12496 MNT-15044 - // Filter for AtomPub and Web services bindings only. Browser/json binding already encodes. - if (AlfrescoCmisServiceCall.get() != null && - (CallContext.BINDING_ATOMPUB.equals(AlfrescoCmisServiceCall.get().getBinding()) || - CallContext.BINDING_WEBSERVICES.equals(AlfrescoCmisServiceCall.get().getBinding()))) - { - return filterXmlRestrictedCharacters(value.toString()); - } - else - { - return value.toString(); - } - } - - @SuppressWarnings("unchecked") - private AbstractPropertyData getProperty(PropertyType propType, PropertyDefinitionWrapper propDef, - Serializable value) - { - AbstractPropertyData result = null; - switch (propType) - { - case BOOLEAN: - result = new PropertyBooleanImpl(); - if (value instanceof List) - { - ((PropertyBooleanImpl) result).setValues((List) value); - } - else - { - ((PropertyBooleanImpl) result).setValue((Boolean) value); - } - break; - case DATETIME: - result = new PropertyDateTimeImpl(); - if (value instanceof List) - { - ((PropertyDateTimeImpl) result).setValues((List) DefaultTypeConverter.INSTANCE - .convert(GregorianCalendar.class, (List) value)); - } - else - { - ((PropertyDateTimeImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(GregorianCalendar.class, - value)); - } - break; - case DECIMAL: - result = new PropertyDecimalImpl(); - if (value instanceof List) - { - ((PropertyDecimalImpl) result).setValues((List) DefaultTypeConverter.INSTANCE.convert( - BigDecimal.class, (List) value)); - } - else - { - ((PropertyDecimalImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigDecimal.class, value)); - } - break; - case HTML: - result = new PropertyHtmlImpl(); - if (value instanceof List) - { - ((PropertyHtmlImpl) result).setValues((List) value); - } - else - { - ((PropertyHtmlImpl) result).setValue((String) value); - } - break; - case ID: - result = new PropertyIdImpl(); - if (value instanceof List) - { - ((PropertyIdImpl) result).setValues((List) value); - } - else - { - if (value instanceof NodeRef) - { - ((PropertyIdImpl) result).setValue(constructObjectId((NodeRef)value, null)); - } - else - { - ((PropertyIdImpl) result).setValue((String) value); - } - } - break; - case INTEGER: - result = new PropertyIntegerImpl(); - if (value instanceof List) - { - ((PropertyIntegerImpl) result).setValues((List) DefaultTypeConverter.INSTANCE.convert( - BigInteger.class, (List) value)); - } - else - { - ((PropertyIntegerImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigInteger.class, value)); - } - break; - case STRING: - result = new PropertyStringImpl(); - if (value instanceof List) - { - ((PropertyStringImpl) result).setValues((List) value); - } - else - { - // MNT-12496 MNT-15044 - // Filter for AtomPub and Web services bindings only. Browser/json binding already encodes. - if (AlfrescoCmisServiceCall.get() != null && - (CallContext.BINDING_ATOMPUB.equals(AlfrescoCmisServiceCall.get().getBinding()) || - CallContext.BINDING_WEBSERVICES.equals(AlfrescoCmisServiceCall.get().getBinding()))) - { - ((PropertyStringImpl) result).setValue(filterXmlRestrictedCharacters((String) value)); - } - else - { - ((PropertyStringImpl) result).setValue((String) value); - } - } - break; - case URI: - result = new PropertyUriImpl(); - if (value instanceof List) - { - ((PropertyUriImpl) result).setValues((List) value); - } - else - { - ((PropertyUriImpl) result).setValue((String) value); - } - break; - default: - throw new RuntimeException("Unknown datatype! Spec change?"); - } - - if (propDef != null) - { - result.setId(propDef.getPropertyDefinition().getId()); - result.setQueryName(propDef.getPropertyDefinition().getQueryName()); - result.setDisplayName(propDef.getPropertyDefinition().getDisplayName()); - result.setLocalName(propDef.getPropertyDefinition().getLocalName()); - } - - return result; - } - - private String filterXmlRestrictedCharacters(String origValue) - { - if (origValue == null) - { - return origValue; - } - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < origValue.length(); i++) - { - char ch = origValue.charAt(i); - boolean restricted = (ch < '\u0020') && !(ch == '\t' || ch == '\n' || ch == '\r'); - sb.append(restricted ? REPLACEMENT_CHAR : ch); - } - - return sb.toString(); - } - - private Set splitFilter(String filter) - { - if (filter == null) - { - return null; - } - - if (filter.trim().length() == 0) - { - return null; - } - - Set result = new HashSet(); - for (String s : filter.split(",")) - { - s = s.trim(); - if (s.equals("*")) - { - return null; - } - else if (s.length() > 0) - { - result.add(s); - } - } - - // set a few base properties - result.add(QUERY_NAME_OBJECT_ID); - result.add(QUERY_NAME_OBJECT_TYPE_ID); - result.add(QUERY_NAME_BASE_TYPE_ID); - - return result; - } - - public AllowableActions getAllowableActions(CMISNodeInfo info) - { - AllowableActionsImpl result = new AllowableActionsImpl(); - Set allowableActions = new HashSet(); - result.setAllowableActions(allowableActions); - - for (CMISActionEvaluator evaluator : info.getType().getActionEvaluators().values()) - { - if (evaluator.isAllowed(info)) - { - allowableActions.add(evaluator.getAction()); - } - } - - return result; - } - - public List getRelationships(NodeRef nodeRef, IncludeRelationships includeRelationships/*, CmisVersion cmisVersion*/) - { - List result = new ArrayList(); - - if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) - { - // relationships from and to versions are not preserved - return result; - } - - // get relationships - List assocs = new ArrayList(); - if (includeRelationships == IncludeRelationships.SOURCE || includeRelationships == IncludeRelationships.BOTH) - { - assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); - } - if (includeRelationships == IncludeRelationships.TARGET || includeRelationships == IncludeRelationships.BOTH) - { - assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); - } - - // filter relationships that not map the CMIS domain model - for (AssociationRef assocRef : assocs) - { - TypeDefinitionWrapper assocTypeDef = getOpenCMISDictionaryService().findAssocType(assocRef.getTypeQName()); - if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null - || getType(assocRef.getTargetRef()) == null) - { - continue; - } - - try - { - result.add(createCMISObject(createNodeInfo(assocRef), null, false, IncludeRelationships.NONE, - RENDITION_NONE, false, false/*, cmisVersion*/)); - } - catch(AccessDeniedException e) - { - // PUBLICAPI-110 - // ok, just skip it - } - catch(CmisObjectNotFoundException e) - { - // ignore objects that have not been found (perhaps because their type is unknown to CMIS) - } - // TODO: Somewhere this has not been wrapped correctly - catch (net.sf.acegisecurity.AccessDeniedException e) - { - // skip - } - } - - return result; - } - - public ObjectList getObjectRelationships(NodeRef nodeRef, RelationshipDirection relationshipDirection, - String typeId, String filter, Boolean includeAllowableActions, BigInteger maxItems, BigInteger skipCount) - { - ObjectListImpl result = new ObjectListImpl(); - result.setHasMoreItems(false); - result.setNumItems(BigInteger.ZERO); - result.setObjects(new ArrayList()); - - if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) - { - // relationships from and to versions are not preserved - return result; - } - - // get relationships - List assocs = new ArrayList(); - if (relationshipDirection == RelationshipDirection.SOURCE - || relationshipDirection == RelationshipDirection.EITHER) - { - assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); - } - if (relationshipDirection == RelationshipDirection.TARGET - || relationshipDirection == RelationshipDirection.EITHER) - { - assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); - } - - int skip = (skipCount == null ? 0 : skipCount.intValue()); - int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); - int counter = 0; - boolean hasMore = false; - - if (max > 0) - { - // filter relationships that not map the CMIS domain model - for (AssociationRef assocRef : assocs) - { - TypeDefinitionWrapper assocTypeDef = getOpenCMISDictionaryService().findAssocType(assocRef.getTypeQName()); - if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null - || getType(assocRef.getTargetRef()) == null) - { - continue; - } - - if ((typeId != null) && !assocTypeDef.getTypeId().equals(typeId)) - { - continue; - } - - counter++; - - if (skip > 0) - { - skip--; - continue; - } - - max--; - if (max > 0) - { - try - { - result.getObjects().add( - createCMISObject(createNodeInfo(assocRef), filter, includeAllowableActions, - IncludeRelationships.NONE, RENDITION_NONE, false, false/*, cmisVersion*/)); - } - catch(CmisObjectNotFoundException e) - { - // ignore objects that have not been found (perhaps because their type is unknown to CMIS) - } - } - else - { - hasMore = true; - } - } - } - - result.setNumItems(BigInteger.valueOf(counter)); - result.setHasMoreItems(hasMore); - - return result; - } - - public List getRenditions(NodeRef nodeRef, String renditionFilter, BigInteger maxItems, - BigInteger skipCount) - { - CMISRenditionMapping mapping = getRenditionMapping(); - return mapping.getRenditions(nodeRef, renditionFilter, maxItems, skipCount); - } - - public Acl getACL(NodeRef nodeRef, boolean onlyBasicPermissions) - { - AccessControlListImpl result = new AccessControlListImpl(); - - // get the permissions and sort them - ArrayList ordered = new ArrayList( - permissionService.getAllSetPermissions(nodeRef)); - Collections.sort(ordered, new AccessPermissionComparator()); - - // remove denied permissions and create OpenCMIS objects - Map> aceMap = new HashMap>(); - for (AccessPermission entry : ordered) - { - if (entry.getAccessStatus() == AccessStatus.ALLOWED) - { - // add allowed entries - Map directAce = aceMap.get(entry.getAuthority()); - if (directAce == null) - { - directAce = new HashMap(); - aceMap.put(entry.getAuthority(), directAce); - } - - AccessControlEntryImpl ace = directAce.get(entry.isSetDirectly()); - if (ace == null) - { - ace = new AccessControlEntryImpl(); - ace.setPrincipal(new AccessControlPrincipalDataImpl(entry.getAuthority())); - ace.setPermissions(new ArrayList()); - ace.setDirect(entry.isSetDirectly()); - directAce.put(entry.isSetDirectly(), ace); - } - - ace.getPermissions().add(entry.getPermission()); - } - else if (entry.getAccessStatus() == AccessStatus.DENIED) - { - // remove denied entries - Map directAce = aceMap.get(entry.getAuthority()); - if (directAce != null) - { - for (AccessControlEntryImpl ace : directAce.values()) - { - ace.getPermissions().remove(entry.getPermission()); - } - } - } - } - - // adjust permissions, add CMIS permissions and add ACEs to ACL - List aces = new ArrayList(); - result.setAces(aces); - for (Map bothAces : aceMap.values()) - { - // get, translate and set direct ACE - AccessControlEntryImpl directAce = bothAces.get(true); - if ((directAce != null) && (!directAce.getPermissions().isEmpty())) - { - List permissions = translatePermissionsToCMIS(directAce.getPermissions(), onlyBasicPermissions); - if(permissions != null && !permissions.isEmpty()) - { - // tck doesn't like empty permissions list - directAce.setPermissions(permissions); - aces.add(directAce); - } - } - - // get, translate, remove duplicate permissions and set indirect ACE - AccessControlEntryImpl indirectAce = bothAces.get(false); - if ((indirectAce != null) && (!indirectAce.getPermissions().isEmpty())) - { - List permissions = translatePermissionsToCMIS(indirectAce.getPermissions(), onlyBasicPermissions); - indirectAce.setPermissions(permissions); - - // remove permissions that are already set in the direct ACE - if ((directAce != null) && (!directAce.getPermissions().isEmpty())) - { - indirectAce.getPermissions().removeAll(directAce.getPermissions()); - } - - if(!indirectAce.getPermissions().isEmpty()) - { - // tck doesn't like empty permissions list - aces.add(indirectAce); - } - } - } - - result.setExact(!onlyBasicPermissions); - - return result; - } - - private List translatePermissionsToCMIS(List permissions, boolean onlyBasicPermissions) - { - Set result = new TreeSet(); - - for (String permission : permissions) - { - PermissionReference permissionReference = permissionModelDao.getPermissionReference(null, permission); - - if (onlyBasicPermissions) - { - - // check for full permissions - if (permissionModelDao.hasFull(permissionReference)) - { - result.add(BasicPermissions.ALL); - } - - // check short forms - Set longForms = permissionModelDao.getGranteePermissions(permissionReference); - - HashSet shortForms = new HashSet(); - for (PermissionReference longForm : longForms) - { - shortForms.add(permissionModelDao.isUnique(longForm) ? longForm.getName() : longForm.toString()); - } - - for (String perm : shortForms) - { - if (PermissionService.READ.equals(perm)) - { - result.add(BasicPermissions.READ); - } - else if (PermissionService.WRITE.equals(perm)) - { - result.add(BasicPermissions.WRITE); - } - else if (PermissionService.ALL_PERMISSIONS.equals(perm)) - { - result.add(BasicPermissions.ALL); - } - } - - // check the permission - if (PermissionService.READ.equals(permission)) - { - result.add(BasicPermissions.READ); - } - else if (PermissionService.WRITE.equals(permission)) - { - result.add(BasicPermissions.WRITE); - } - else if (PermissionService.ALL_PERMISSIONS.equals(permission)) - { - result.add(BasicPermissions.ALL); - } - } - else - { - // ACE-2224: only repository specific permissions should be reported - if (permission.startsWith("{")) - { - result.add(permission); - } - // do revert conversion for basic permissions that have exact matching - else if (PermissionService.READ.equals(permission)) - { - result.add(BasicPermissions.READ); - } - else if (PermissionService.WRITE.equals(permission)) - { - result.add(BasicPermissions.WRITE); - } - else if (PermissionService.ALL_PERMISSIONS.equals(permission)) - { - result.add(BasicPermissions.ALL); - } - else - { - result.add(permissionReference.toString()); - } - } - } - - return new ArrayList(result); - } - - public static class AccessPermissionComparator implements Comparator - { - public int compare(AccessPermission left, AccessPermission right) - { - if (left.getPosition() != right.getPosition()) - { - return right.getPosition() - left.getPosition(); - } - else - { - if (left.getAccessStatus() != right.getAccessStatus()) - { - return (left.getAccessStatus() == AccessStatus.DENIED) ? -1 : 1; - } - else - { - int compare = left.getAuthority().compareTo(right.getAuthority()); - if (compare != 0) - { - return compare; - } - else - { - return (left.getPermission().compareTo(right.getPermission())); - } - - } - - } - } - } - - /** - * Applies the given ACLs. - */ - public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl addAces, Acl removeAces) - { - boolean hasAdd = (addAces != null) && (addAces.getAces() != null) && !addAces.getAces().isEmpty(); - boolean hasRemove = (removeAces != null) && (removeAces.getAces() != null) && !removeAces.getAces().isEmpty(); - - if (!hasAdd && !hasRemove) - { - return; - } - - if (!type.getTypeDefinition(false).isControllableAcl()) - { - throw new CmisConstraintException("Object is not ACL controllable!"); - } - - // remove permissions - if (hasRemove) - { - Set permissions = permissionService.getAllSetPermissions(nodeRef); - - // get only direct ACE since only those can be removed - Acl onlyDirectAcl = excludeInheritedAces(nodeRef, removeAces); - - for (Ace ace : onlyDirectAcl.getAces()) - { - String principalId = ace.getPrincipalId(); - if (CMIS_USER.equals(principalId)) - { - principalId = AuthenticationUtil.getFullyAuthenticatedUser(); - } - - for (String permission : translatePermissionsFromCMIS(ace.getPermissions())) - { - - AccessPermission toCheck = new AccessPermissionImpl(permission, AccessStatus.ALLOWED, principalId, - 0); - if (!permissions.contains(toCheck)) - { - throw new CmisConstraintException("No matching ACE found to remove!"); - } - - permissionService.deletePermission(nodeRef, principalId, permission); - } - } - } - - // add permissions - if (hasAdd) - { - for (Ace ace : addAces.getAces()) - { - String principalId = ace.getPrincipalId(); - if (CMIS_USER.equals(principalId)) - { - principalId = AuthenticationUtil.getFullyAuthenticatedUser(); - } - - for (String permission : translatePermissionsFromCMIS(ace.getPermissions())) - { - permissionService.setPermission(nodeRef, principalId, permission, true); - } - } - } - } - - /** - * Converts Acl to map and ignore the indirect ACEs - * - * @param acl Acl - * @return Map - */ - private Map> convertAclToMap(Acl acl) - { - Map> result = new HashMap>(); - - if (acl == null || acl.getAces() == null) - { - return result; - } - - for (Ace ace : acl.getAces()) - { - // don't consider indirect ACEs - we can't change them - if (!ace.isDirect()) - { - // ignore - continue; - } - - // although a principal must not be null, check it - if (ace.getPrincipal() == null || ace.getPrincipal().getId() == null) - { - // ignore - continue; - } - - Set permissions = result.get(ace.getPrincipal().getId()); - if (permissions == null) - { - permissions = new HashSet(); - result.put(ace.getPrincipal().getId(), permissions); - } - - if (ace.getPermissions() != null) - { - permissions.addAll(ace.getPermissions()); - } - } - - return result; - } - - /** - * Filter acl to ignore inherited ACEs - * - * @param nodeRef NodeRef - * @param acl Acl - * @return Acl - */ - protected Acl excludeInheritedAces(NodeRef nodeRef, Acl acl) - { - - List newAces = new ArrayList(); - Acl allACLs = getACL(nodeRef, false); - - Map> originalsAcls = convertAclToMap(allACLs); - Map> newAcls = convertAclToMap(acl); - - // iterate through the original ACEs - for (Map.Entry> ace : originalsAcls.entrySet()) - { - - // add permissions - Set addPermissions = newAcls.get(ace.getKey()); - if (addPermissions != null) - { - ace.getValue().addAll(addPermissions); - } - - // create new ACE - if (!ace.getValue().isEmpty()) - { - newAces.add(new AccessControlEntryImpl(new AccessControlPrincipalDataImpl(ace - .getKey()), new ArrayList(ace.getValue()))); - } - } - - return new AccessControlListImpl(newAces); - } - - /** - * Sets the given ACL. - */ - public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl aces) - { - boolean hasAces = (aces != null) && (aces.getAces() != null) && !aces.getAces().isEmpty(); - - if (!hasAces && !permissionService.getInheritParentPermissions(nodeRef)) - { - return; - } - - if (!type.getTypeDefinition(false).isControllableAcl()) - { - throw new CmisConstraintException("Object is not ACL controllable!"); - } - - // remove all permissions - permissionService.deletePermissions(nodeRef); - - // set new permissions - for (Ace ace : aces.getAces()) - { - String principalId = ace.getPrincipalId(); - if (CMIS_USER.equals(principalId)) - { - principalId = AuthenticationUtil.getFullyAuthenticatedUser(); - } - - List permissions = translatePermissionsFromCMIS(ace.getPermissions()); - for (String permission : permissions) - { - permissionService.setPermission(nodeRef, principalId, permission, true); - } - } - } - - private List translatePermissionsFromCMIS(List permissions) - { - List result = new ArrayList(); - - if (permissions == null) - { - return result; - } - - for (String permission : permissions) - { - if (permission == null) - { - throw new CmisConstraintException("Invalid null permission!"); - } - - if (BasicPermissions.READ.equals(permission)) - { - result.add(PermissionService.READ); - } - else if (BasicPermissions.WRITE.equals(permission)) - { - result.add(PermissionService.WRITE); - } - else if (BasicPermissions.ALL.equals(permission)) - { - result.add(PermissionService.ALL_PERMISSIONS); - } - else if (!permission.startsWith("{")) - { - result.add(permission); - } - else - { - int sepIndex = permission.lastIndexOf('.'); - if (sepIndex == -1) - { - result.add(permission); - } - else - { - result.add(permission.substring(sepIndex + 1)); - } - } - } - - return result; - } - - public void applyPolicies(NodeRef nodeRef, TypeDefinitionWrapper type, List policies) - { - if ((policies == null) || (policies.isEmpty())) - { - return; - } - - if (!type.getTypeDefinition(false).isControllablePolicy()) - { - throw new CmisConstraintException("Object is not policy controllable!"); - } - - // nothing else to do... - } - - @SuppressWarnings("unchecked") - public ObjectList query(String statement, Boolean includeAllowableActions, - IncludeRelationships includeRelationships, String renditionFilter, BigInteger maxItems, BigInteger skipCount/*, CmisVersion cmisVersion*/) - { - // prepare results - ObjectListImpl result = new ObjectListImpl(); - result.setObjects(new ArrayList()); - - // prepare query - CMISQueryOptions options = new CMISQueryOptions(statement, getRootStoreRef()); - CmisVersion cmisVersion = getRequestCmisVersion(); - options.setCmisVersion(cmisVersion); - options.setQueryMode(CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS); - - int skip = 0; - if ((skipCount != null) && (skipCount.intValue() >= 0)) - { - skip = skipCount.intValue(); - options.setSkipCount(skip); - } - - if ((maxItems != null) && (maxItems.intValue() >= 0)) - { - options.setMaxItems(maxItems.intValue()); - } - - boolean fetchObject = includeAllowableActions || (includeRelationships != IncludeRelationships.NONE) - || (!RENDITION_NONE.equals(renditionFilter)); - - // query - CMISResultSet rs = getOpenCMISQueryService().query(options); - try - { - CMISResultSetColumn[] columns = rs.getMetaData().getColumns(); - - for (CMISResultSetRow row : rs) - { - NodeRef nodeRef = row.getNodeRef(); - - if(!nodeService.exists(nodeRef) || filter(nodeRef)) - { - continue; - } - - TypeDefinitionWrapper type = getType(nodeRef); - if (type == null) - { - continue; - } - - ObjectDataImpl hit = new ObjectDataImpl(); - PropertiesImpl properties = new PropertiesImpl(); - hit.setProperties(properties); - - Map values = row.getValues(); - - for (CMISResultSetColumn column : columns) - { - AbstractPropertyData property = getProperty(column.getCMISDataType(), - column.getCMISPropertyDefinition(), values.get(column.getName())); - property.setQueryName(column.getName()); - properties.addProperty(property); - } - - if (fetchObject) - { - // set allowable actions - if (includeAllowableActions) - { - CMISNodeInfoImpl nodeInfo = createNodeInfo(nodeRef); - if(!nodeInfo.getObjectVariant().equals(CMISObjectVariant.NOT_EXISTING)) - { - hit.setAllowableActions(getAllowableActions(nodeInfo)); - } - } - - // set relationships - if (includeRelationships != IncludeRelationships.NONE) - { - hit.setRelationships(getRelationships(nodeRef, includeRelationships/*, cmisVersion*/)); - } - - // set renditions - if (!RENDITION_NONE.equals(renditionFilter)) - { - List renditions = getRenditions(nodeRef, renditionFilter, null, null); - if ((renditions != null) && (!renditions.isEmpty())) - { - hit.setRenditions(renditions); - } - else - { - hit.setRenditions(Collections.EMPTY_LIST); - } - } - } - - result.getObjects().add(hit); - } - - long numberFound = rs.getNumberFound(); - if(numberFound != -1) - { - result.setNumItems(BigInteger.valueOf(numberFound)); - } - result.setHasMoreItems(rs.hasMore()); - - } finally - { - rs.close(); - } - - return result; - } - - /** - * Sets property values. - */ - @SuppressWarnings({ "rawtypes" }) - public void setProperties(NodeRef nodeRef, TypeDefinitionWrapper type, Properties properties, String... exclude) - { - if (properties == null) - { - return; - } - - Map> incomingPropsMap = properties.getProperties(); - if (incomingPropsMap == null) - { - return; - } - - // extract property data into an easier to use form - Map> propsMap = new HashMap>(); - for (String propertyId : incomingPropsMap.keySet()) - { - PropertyData property = incomingPropsMap.get(propertyId); - PropertyDefinitionWrapper propDef = type.getPropertyById(property.getId()); - if (propDef == null) - { - propDef = getOpenCMISDictionaryService().findProperty(propertyId); - if (propDef == null) - { - throw new CmisInvalidArgumentException("Property " + property.getId() + " is unknown!"); - } - } - - Updatability updatability = propDef.getPropertyDefinition().getUpdatability(); - if ((updatability == Updatability.READONLY) - || (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef))) - { - throw new CmisInvalidArgumentException("Property " + property.getId() + " is read-only!"); - } - TypeDefinitionWrapper propType = propDef.getOwningType(); - Serializable value = getValue(property, propDef.getPropertyDefinition().getCardinality() == Cardinality.MULTI); - Pair pair = new Pair(propType, value); - propsMap.put(propertyId, pair); - } - - // Need to do deal with secondary types first - Pair pair = propsMap.get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS); - Serializable secondaryTypesProperty = (pair != null ? pair.getSecond() : null); - if(secondaryTypesProperty != null) - { - if (!(secondaryTypesProperty instanceof List)) - { - throw new CmisInvalidArgumentException("Secondary types must be a list!"); - } - List secondaryTypes = (List)secondaryTypesProperty; - if(secondaryTypes != null && secondaryTypes.size() > 0) - { - // add/remove secondary types/aspects - processSecondaryTypes(nodeRef, secondaryTypes, propsMap); - } - } - - for (String propertyId : propsMap.keySet()) - { - if(propertyId.equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) - { - // already handled above - continue; - } - - pair = propsMap.get(propertyId); - TypeDefinitionWrapper propType = pair.getFirst(); - Serializable value = pair.getSecond(); - if (Arrays.binarySearch(exclude, propertyId) < 0) - { - setProperty(nodeRef, propType, propertyId, value); - } - } - - List extensions = properties.getExtensions(); - if (extensions != null) - { - boolean isNameChanging = properties.getProperties().containsKey(PropertyIds.NAME); - - for (CmisExtensionElement extension : extensions) - { - if (ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace()) - && SET_ASPECTS.equals(extension.getName())) - { - setAspectProperties(nodeRef, isNameChanging, extension); - break; - } - } - } - } - - @SuppressWarnings("rawtypes") - private void processSecondaryTypes(NodeRef nodeRef, List secondaryTypes, Map> propsToAdd) - { - // diff existing aspects and secondaryTypes/aspects list - Set existingAspects = nodeService.getAspects(nodeRef); - Set secondaryTypeAspects = new HashSet(); - for(Object o : secondaryTypes) - { - String secondaryType = (String)o; - - TypeDefinitionWrapper wrapper = getOpenCMISDictionaryService().findType(secondaryType); - if(wrapper != null) - { - QName aspectQName = wrapper.getAlfrescoName(); - secondaryTypeAspects.add(aspectQName); - } - else - { - throw new CmisInvalidArgumentException("Invalid secondary type id " + secondaryType); - } - } - - Set ignore = new HashSet(); - ignore.add(ContentModel.ASPECT_REFERENCEABLE); - ignore.add(ContentModel.ASPECT_LOCALIZED); - - // aspects to add == the list of secondary types - existing aspects - ignored aspects - Set toAdd = new HashSet(secondaryTypeAspects); - toAdd.removeAll(existingAspects); - toAdd.removeAll(ignore); - - // aspects to remove == existing aspects - secondary types - Set aspectsToRemove = new HashSet(); - aspectsToRemove.addAll(existingAspects); - aspectsToRemove.removeAll(ignore); - Iterator it = aspectsToRemove.iterator(); - while(it.hasNext()) - { - QName aspectQName = it.next(); - TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName); - if(w == null || secondaryTypeAspects.contains(aspectQName)) - { - // the type is not exposed or is in the secondary types to set, so remove it from the to remove set - it.remove(); - } - } - - // first, remove aspects - for(QName aspectQName : aspectsToRemove) - { - nodeService.removeAspect(nodeRef, aspectQName); - // aspect is being removed so remove all of its properties from the propsToAdd map - TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName); - for(PropertyDefinitionWrapper wr : w.getProperties()) - { - String propertyId = wr.getPropertyId(); - propsToAdd.remove(propertyId); - } - } - - // add aspects and properties - for(QName aspectQName : toAdd) - { - nodeService.addAspect(nodeRef, aspectQName, null); - - // get aspect properties - AspectDefinition aspectDef = dictionaryService.getAspect(aspectQName); - Map aspectPropDefs = aspectDef.getProperties(); - TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName); - // for each aspect property... - for(QName propQName : aspectPropDefs.keySet()) - { - // find CMIS property id - PropertyDefinitionWrapper property = w.getPropertyByQName(propQName); - String propertyId = property.getPropertyId(); - if(!propsToAdd.containsKey(propertyId)) - { - TypeDefinitionWrapper propType = property.getOwningType(); - // CMIS 1.1 secondary types specification requires that all secondary type properties are set - // property not included in propsToAdd, add it with null value - Pair pair = new Pair(propType, null); - propsToAdd.put(propertyId, pair); - } - } - } - } - - public void addSecondaryTypes(NodeRef nodeRef, List secondaryTypes) - { - if(secondaryTypes != null && secondaryTypes.size() > 0) - { - for(String secondaryType : secondaryTypes) - { - TypeDefinitionWrapper type = getType(secondaryType); - if (type == null) - { - throw new CmisInvalidArgumentException("Invalid secondaryType: " + secondaryType); - } - nodeService.addAspect(nodeRef, type.getAlfrescoName(), null); - } - } - } - - public void removeSecondaryTypes(NodeRef nodeRef, List secondaryTypes) - { - if(secondaryTypes != null && secondaryTypes.size() > 0) - { - for(String secondaryType : secondaryTypes) - { - TypeDefinitionWrapper type = getType(secondaryType); - if (type == null) - { - throw new CmisInvalidArgumentException("Invalid secondaryType: " + secondaryType); - } - nodeService.removeAspect(nodeRef, type.getAlfrescoName()); - } - } - } - - private void setAspectProperties(NodeRef nodeRef, boolean isNameChanging, CmisExtensionElement aspectExtension) - { - if (aspectExtension.getChildren() == null) - { - return; - } - - List aspectsToAdd = new ArrayList(); - List aspectsToRemove = new ArrayList(); - Map> aspectProperties = new HashMap>(); - - for (CmisExtensionElement extension : aspectExtension.getChildren()) - { - if (!ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace())) - { - continue; - } - - if (ASPECTS_TO_ADD.equals(extension.getName()) && (extension.getValue() != null)) - { - aspectsToAdd.add(extension.getValue()); - } - else if (ASPECTS_TO_REMOVE.equals(extension.getName()) && (extension.getValue() != null)) - { - aspectsToRemove.add(extension.getValue()); - } - else if (PROPERTIES.equals(extension.getName()) && (extension.getChildren() != null)) - { - for (CmisExtensionElement property : extension.getChildren()) - { - if (!property.getName().startsWith("property")) - { - continue; - } - - String propertyId = (property.getAttributes() == null ? null : property.getAttributes().get( - "propertyDefinitionId")); - if ((propertyId == null) || (property.getChildren() == null)) - { - continue; - } - - PropertyType propertyType = PropertyType.STRING; - DatatypeFactory df = null; - if (property.getName().equals("propertyBoolean")) - { - propertyType = PropertyType.BOOLEAN; - } - else if (property.getName().equals("propertyInteger")) - { - propertyType = PropertyType.INTEGER; - } - else if (property.getName().equals("propertyDateTime")) - { - propertyType = PropertyType.DATETIME; - try - { - df = DatatypeFactory.newInstance(); - } - catch (DatatypeConfigurationException e) - { - throw new CmisRuntimeException("Aspect conversation exception: " + e.getMessage(), e); - } - } - else if (property.getName().equals("propertyDecimal")) - { - propertyType = PropertyType.DECIMAL; - } - - ArrayList values = new ArrayList(); - if (property.getChildren() != null) - { -// try -// { - for (CmisExtensionElement valueElement : property.getChildren()) - { - if ("value".equals(valueElement.getName())) - { - switch (propertyType) - { - case BOOLEAN: - try - { - values.add(Boolean.parseBoolean(valueElement.getValue())); - } - catch (Exception e) - { - throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e); - } - break; - case DATETIME: - try - { - values.add(df.newXMLGregorianCalendar(valueElement.getValue()) - .toGregorianCalendar()); - } - catch (Exception e) - { - throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e); - } - break; - case INTEGER: - BigInteger value = null; - try - { - value = new BigInteger(valueElement.getValue()); - } - catch (Exception e) - { - throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e); - } - - // overflow check - PropertyDefinitionWrapper propDef = getOpenCMISDictionaryService().findProperty(propertyId); - if(propDef == null) - { - throw new CmisInvalidArgumentException("Property " + propertyId + " is unknown!"); - } - - QName propertyQName = propDef.getPropertyAccessor().getMappedProperty(); - if (propertyQName == null) - { - throw new CmisConstraintException("Unable to set property " + propertyId + "!"); - } - - org.alfresco.service.cmr.dictionary.PropertyDefinition def = dictionaryService.getProperty(propertyQName); - QName dataDef = def.getDataType().getName(); - - if (dataDef.equals(DataTypeDefinition.INT) && (value.compareTo(maxInt) > 0 || value.compareTo(minInt) < 0)) - { - throw new CmisConstraintException("Value is out of range for property " + propertyId); - } - - if (dataDef.equals(DataTypeDefinition.LONG) && (value.compareTo(maxLong) > 0 || value.compareTo(minLong) < 0 )) - { - throw new CmisConstraintException("Value is out of range for property " + propertyId); - } - - values.add(value); - break; - case DECIMAL: - try - { - values.add(new BigDecimal(valueElement.getValue())); - } - catch (Exception e) - { - throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e); - } - break; - default: - values.add(valueElement.getValue()); - } - } - } - } - - aspectProperties.put(QName.createQName(propertyId, namespaceService), values); - } - } - } - - // remove and add aspects - String aspectType = null; - try - { - for (String aspect : aspectsToRemove) - { - aspectType = aspect; - - TypeDefinitionWrapper type = getType(aspect); - if (type == null) - { - throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType); - } - - QName typeName = type.getAlfrescoName(); - // if aspect is hidden aspect, remove only if hidden node is not client controlled - if(typeName.equals(ContentModel.ASPECT_HIDDEN)) - { - if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) - { - // manipulate hidden aspect only if client controlled - nodeService.removeAspect(nodeRef, typeName); - } - - - -// if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) -// { -// nodeService.removeAspect(nodeRef, typeName); -// } - } - else - { - nodeService.removeAspect(nodeRef, typeName); - } - } - - for (String aspect : aspectsToAdd) - { - aspectType = aspect; - - TypeDefinitionWrapper type = getType(aspect); - if (type == null) - { - throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType); - } - - QName typeName = type.getAlfrescoName(); - // if aspect is hidden aspect, remove only if hidden node is not client controlled - if(typeName.equals(ContentModel.ASPECT_HIDDEN)) - { - if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) - { - // manipulate hidden aspect only if client controlled - nodeService.addAspect(nodeRef, type.getAlfrescoName(), - Collections. emptyMap()); - } - -// if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) -// { -// nodeService.addAspect(nodeRef, type.getAlfrescoName(), -// Collections. emptyMap()); -// } - } - else - { - nodeService.addAspect(nodeRef, type.getAlfrescoName(), - Collections. emptyMap()); - } - } - } - catch (InvalidAspectException e) - { - throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType); - } - catch (InvalidNodeRefException e) - { - throw new CmisInvalidArgumentException("Invalid node: " + nodeRef); - } - - // set property - for (Map.Entry> property : aspectProperties.entrySet()) - { - QName propertyQName = property.getKey(); - - if (property.getValue().isEmpty()) - { - if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey())) - { - if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) - { - // manipulate hidden aspect property only if client controlled - nodeService.removeProperty(nodeRef, propertyQName); - } - } - else - { - nodeService.removeProperty(nodeRef, property.getKey()); - } - } - else - { - if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey())) - { - if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) - { - // manipulate hidden aspect property only if client controlled - nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property - .getValue().get(0) : (Serializable) property.getValue()); - } - } - else - { - Serializable value = (Serializable)property.getValue(); - nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property - .getValue().get(0) : value); - } - } - } - } - - /** - * Sets a property value. - */ - public void setProperty(NodeRef nodeRef, TypeDefinitionWrapper type, String propertyId, Serializable value) - { - if (propertyId == null) - { - throw new CmisInvalidArgumentException("Cannot process not null property!"); - } - - PropertyDefinitionWrapper propDef = type.getPropertyById(propertyId); - if (propDef == null) - { - throw new CmisInvalidArgumentException("Property " + propertyId + " is unknown!"); - } - - Updatability updatability = propDef.getPropertyDefinition().getUpdatability(); - if ((updatability == Updatability.READONLY) - || (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef))) - { - throw new CmisInvalidArgumentException("Property " + propertyId + " is read-only!"); - } - - if(propDef.getPropertyId().equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) - { - throw new IllegalArgumentException("Cannot process " + PropertyIds.SECONDARY_OBJECT_TYPE_IDS + " in setProperty"); - } - else - { - QName propertyQName = propDef.getPropertyAccessor().getMappedProperty(); - if (propertyQName == null) - { - throw new CmisConstraintException("Unable to set property " + propertyId + "!"); - } - - if (propertyId.equals(PropertyIds.NAME)) - { - if (!(value instanceof String)) - { - throw new CmisInvalidArgumentException("Object name must be a string!"); - } - - try - { - fileFolderService.rename(nodeRef, value.toString()); - } - catch (FileExistsException e) - { - throw new CmisContentAlreadyExistsException("An object with this name already exists!", e); - } - catch (FileNotFoundException e) - { - throw new CmisInvalidArgumentException("Object with id " + nodeRef.getId() + " not found!"); - } - } - else - { - // overflow check - if (propDef.getPropertyDefinition().getPropertyType() == PropertyType.INTEGER && value instanceof BigInteger) - { - org.alfresco.service.cmr.dictionary.PropertyDefinition def = dictionaryService.getProperty(propertyQName); - QName dataDef = def.getDataType().getName(); - BigInteger bigValue = (BigInteger) value; - - if ((bigValue.compareTo(maxInt) > 0 || bigValue.compareTo(minInt) < 0) && dataDef.equals(DataTypeDefinition.INT)) - { - throw new CmisConstraintException("Value is out of range for property " + propertyQName.getLocalName()); - } - - if ((bigValue.compareTo(maxLong) > 0 || bigValue.compareTo(minLong) < 0) && dataDef.equals(DataTypeDefinition.LONG)) - { - throw new CmisConstraintException("Value is out of range for property " + propertyQName.getLocalName()); - } - } - - nodeService.setProperty(nodeRef, propertyQName, value); - } - } - } - - private Serializable getValue(PropertyData property, boolean isMultiValue) - { - if ((property.getValues() == null) || (property.getValues().isEmpty())) - { - return null; - } - - if (isMultiValue) - { - return (Serializable) property.getValues(); - } - - return (Serializable) property.getValues().get(0); - } - - /** - * Returns content changes. - */ - public ObjectList getContentChanges(Holder changeLogToken, BigInteger maxItems) - { - final ObjectListImpl result = new ObjectListImpl(); - result.setObjects(new ArrayList()); - - EntryIdCallback changeLogCollectingCallback = new EntryIdCallback(true) - { - @Override - public boolean handleAuditEntry(Long entryId, String user, long time, Map values) - { - result.getObjects().addAll(createChangeEvents(time, values)); - return super.handleAuditEntry(entryId, user, time, values); - } - }; - - Long from = null; - if ((changeLogToken != null) && (changeLogToken.getValue() != null)) - { - try - { - from = Long.parseLong(changeLogToken.getValue()); - } - catch (NumberFormatException e) - { - throw new CmisInvalidArgumentException("Invalid change log token: " + changeLogToken); - } - } - - AuditQueryParameters params = new AuditQueryParameters(); - params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION); - params.setForward(true); - params.setFromId(from); - - int maxResults = (maxItems == null ? 0 : maxItems.intValue()); - maxResults = (maxResults < 1 ? 0 : maxResults + 1); - - auditService.auditQuery(changeLogCollectingCallback, params, maxResults); - - String newChangeLogToken = null; - if (maxResults > 0) - { - if (result.getObjects().size() >= maxResults) - { - StringBuilder clt = new StringBuilder(); - newChangeLogToken = (from == null ? clt.append(maxItems.intValue() + 1).toString() : clt.append(from.longValue() + maxItems.intValue()).toString()); - result.getObjects().remove(result.getObjects().size() - 1).getId(); - result.setHasMoreItems(true); - } - else - { - result.setHasMoreItems(false); - } - } - - if (changeLogToken != null) - { - changeLogToken.setValue(newChangeLogToken); - } - - return result; - } - - @SuppressWarnings("unchecked") - private List createChangeEvents(long time, Map values) - { - List result = new ArrayList(); - - if ((values == null) || (values.size() == 0)) - { - return result; - } - - GregorianCalendar changeTime = new GregorianCalendar(); - changeTime.setTimeInMillis(time); - - String appPath = "/" + CMIS_CHANGELOG_AUDIT_APPLICATION + "/"; - - for (Entry entry : values.entrySet()) - { - if ((entry.getKey() == null) || (!(entry.getValue() instanceof Map))) - { - continue; - } - - String path = entry.getKey(); - if (!path.startsWith(appPath)) - { - continue; - } - - ChangeType changeType = null; - String changePath = path.substring(appPath.length()).toLowerCase(); - for (ChangeType c : ChangeType.values()) - { - if (changePath.startsWith(c.value().toLowerCase())) - { - changeType = c; - break; - } - } - - if (changeType == null) - { - continue; - } - - Map valueMap = (Map) entry.getValue(); - String objectId = (String) valueMap.get(CMISChangeLogDataExtractor.KEY_OBJECT_ID); - - // build object - ObjectDataImpl object = new ObjectDataImpl(); - result.add(object); - - PropertiesImpl properties = new PropertiesImpl(); - object.setProperties(properties); - PropertyIdImpl objectIdProperty = new PropertyIdImpl(PropertyIds.OBJECT_ID, objectId); - properties.addProperty(objectIdProperty); - - ChangeEventInfoDataImpl changeEvent = new ChangeEventInfoDataImpl(); - object.setChangeEventInfo(changeEvent); - changeEvent.setChangeType(changeType); - changeEvent.setChangeTime(changeTime); - } - - return result; - } - - private class EntryIdCallback implements AuditQueryCallback - { - private final boolean valuesRequired; - private Long entryId; - - public EntryIdCallback(boolean valuesRequired) - { - this.valuesRequired = valuesRequired; - } - - public String getEntryId() - { - return entryId == null ? null : entryId.toString(); - } - - public boolean valuesRequired() - { - return this.valuesRequired; - } - - public final boolean handleAuditEntry(Long entryId, String applicationName, String user, long time, - Map values) - { - if (applicationName.equals(CMIS_CHANGELOG_AUDIT_APPLICATION)) - { - return handleAuditEntry(entryId, user, time, values); - } - return true; - } - - public boolean handleAuditEntry(Long entryId, String user, long time, Map values) - { - this.entryId = entryId; - return true; - } - - public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) - { - throw new CmisRuntimeException("Audit entry " + entryId + ": " + errorMsg, error); - } - }; - - // -------------------------------------------------------------- - // OpenCMIS methods - // -------------------------------------------------------------- - - /** - * Returns the value of the given property if it exists and is of the - * correct type. - */ - public String getStringProperty(Properties properties, String propertyId) - { - if ((properties == null) || (properties.getProperties() == null)) - { - return null; - } - - PropertyData property = properties.getProperties().get(propertyId); - if (!(property instanceof PropertyString)) - { - return null; - } - - return ((PropertyString) property).getFirstValue(); - } - - /** - * Returns the value of the given property if it exists and is of the - * correct type. - */ - public String getIdProperty(Properties properties, String propertyId) - { - if ((properties == null) || (properties.getProperties() == null)) - { - return null; - } - - PropertyData property = properties.getProperties().get(propertyId); - if (!(property instanceof PropertyId)) - { - return null; - } - - return ((PropertyId) property).getFirstValue(); - } - - public String getNameProperty(Properties properties, String fallback) - { - String name = getStringProperty(properties, PropertyIds.NAME); - if ((name == null) || (name.trim().length() == 0)) - { - if (fallback == null) - { - throw new CmisInvalidArgumentException("Property " + PropertyIds.NAME + " must be set!"); - } - else - { - name = fallback; - } - } - - return name; - } - - public String getObjectTypeIdProperty(Properties properties) - { - String objectTypeId = getIdProperty(properties, PropertyIds.OBJECT_TYPE_ID); - if ((objectTypeId == null) || (objectTypeId.trim().length() == 0)) - { - throw new CmisInvalidArgumentException("Property " + PropertyIds.OBJECT_TYPE_ID + " must be set!"); - } - - return objectTypeId; - } - - public String getSourceIdProperty(Properties properties) - { - String id = getIdProperty(properties, PropertyIds.SOURCE_ID); - if ((id == null) || (id.trim().length() == 0)) - { - throw new CmisInvalidArgumentException("Property " + PropertyIds.SOURCE_ID + " must be set!"); - } - - return id; - } - - public String getTargetIdProperty(Properties properties) - { - String id = getIdProperty(properties, PropertyIds.TARGET_ID); - if ((id == null) || (id.trim().length() == 0)) - { - throw new CmisInvalidArgumentException("Property " + PropertyIds.TARGET_ID + " must be set!"); - } - - return id; - } - - /** - * Returns the repository info object. - */ - public RepositoryInfo getRepositoryInfo(CmisVersion cmisVersion) - { - return createRepositoryInfo(cmisVersion); - } - - /** - * Returns the repository id. - */ - public String getRepositoryId() - { - return descriptorService.getCurrentRepositoryDescriptor().getId(); - } - - /** - * Creates the repository info object. - */ - private RepositoryInfo createRepositoryInfo(CmisVersion cmisVersion) - { - Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor(); - - // get change token - boolean auditEnabled = auditService.isAuditEnabled(CMIS_CHANGELOG_AUDIT_APPLICATION, "/" - + CMIS_CHANGELOG_AUDIT_APPLICATION); - String latestChangeLogToken = null; - - if (auditEnabled) - { - EntryIdCallback auditQueryCallback = new EntryIdCallback(false); - AuditQueryParameters params = new AuditQueryParameters(); - params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION); - params.setForward(false); - auditService.auditQuery(auditQueryCallback, params, 1); - String entryId = auditQueryCallback.getEntryId(); - // MNT-13529 - // add initial change log token - latestChangeLogToken = entryId == null ? "0" : entryId; - } - - // compile repository info - RepositoryInfoImpl ri = new RepositoryInfoImpl(); - - ri.setId(currentDescriptor.getId()); - ri.setName(currentDescriptor.getName()); - ri.setDescription(currentDescriptor.getName()); - ri.setVendorName("Alfresco"); - ri.setProductName("Alfresco " + descriptorService.getServerDescriptor().getEdition()); - ri.setProductVersion(currentDescriptor.getVersion()); - NodeRef rootNodeRef = getRootNodeRef(); - ri.setRootFolder(constructObjectId(rootNodeRef, null)); - ri.setCmisVersion(cmisVersion); - - ri.setChangesIncomplete(true); - ri.setChangesOnType(Arrays.asList(new BaseTypeId[] { BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER })); - ri.setLatestChangeLogToken(latestChangeLogToken); - ri.setPrincipalAnonymous(AuthenticationUtil.getGuestUserName()); - ri.setPrincipalAnyone(PermissionService.ALL_AUTHORITIES); - - RepositoryCapabilitiesImpl repCap = new RepositoryCapabilitiesImpl(); - ri.setCapabilities(repCap); - - repCap.setAllVersionsSearchable(false); - repCap.setCapabilityAcl(CapabilityAcl.MANAGE); - repCap.setCapabilityChanges(auditEnabled ? CapabilityChanges.OBJECTIDSONLY : CapabilityChanges.NONE); - repCap.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.ANYTIME); - repCap.setCapabilityJoin(CapabilityJoin.NONE); - repCap.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED); - repCap.setCapabilityRendition(CapabilityRenditions.READ); - repCap.setIsPwcSearchable(false); - repCap.setIsPwcUpdatable(true); - repCap.setSupportsGetDescendants(true); - repCap.setSupportsGetFolderTree(true); - repCap.setSupportsMultifiling(true); - repCap.setSupportsUnfiling(false); - repCap.setSupportsVersionSpecificFiling(false); - - AclCapabilitiesDataImpl aclCap = new AclCapabilitiesDataImpl(); - ri.setAclCapabilities(aclCap); - - aclCap.setAclPropagation(AclPropagation.PROPAGATE); - aclCap.setSupportedPermissions(SupportedPermissions.BOTH); - aclCap.setPermissionDefinitionData(repositoryPermissions); - aclCap.setPermissionMappingData(permissionMappings); - - return ri; - } - - private List getRepositoryPermissions() - { - ArrayList result = new ArrayList(); - - Set all = permissionModelDao.getAllPermissions(); - for (PermissionReference pr : all) - { - result.add(createPermissionDefinition(pr)); - } - - PermissionReference allPermission = permissionModelDao.getPermissionReference(null, - PermissionService.ALL_PERMISSIONS); - result.add(createPermissionDefinition(allPermission)); - - PermissionDefinitionDataImpl cmisPermission; - - cmisPermission = new PermissionDefinitionDataImpl(); - cmisPermission.setId(BasicPermissions.READ); - cmisPermission.setDescription("CMIS Read"); - result.add(cmisPermission); - - cmisPermission = new PermissionDefinitionDataImpl(); - cmisPermission.setId(BasicPermissions.WRITE); - cmisPermission.setDescription("CMIS Write"); - result.add(cmisPermission); - - cmisPermission = new PermissionDefinitionDataImpl(); - cmisPermission.setId(BasicPermissions.ALL); - cmisPermission.setDescription("CMIS All"); - result.add(cmisPermission); - - return result; - } - - private PermissionDefinition createPermissionDefinition(PermissionReference pr) - { - PermissionDefinitionDataImpl permission = new PermissionDefinitionDataImpl(); - permission.setId(pr.getQName().toString() + "." + pr.getName()); - permission.setDescription(permission.getId()); - - return permission; - } - - private Map getPermissionMappings() - { - Map result = new HashMap(); - - for (CMISAllowedActionEnum e : EnumSet.allOf(CMISAllowedActionEnum.class)) - { - for (Map.Entry> m : e.getPermissionMapping().entrySet()) - { - PermissionMappingDataImpl mapping = new PermissionMappingDataImpl(); - mapping.setKey(m.getKey()); - mapping.setPermissions(m.getValue()); - - result.put(mapping.getKey(), mapping); - } - } - - return result; - } - - private CMISRenditionMapping getRenditionMapping() - { - CMISRenditionMapping renditionMapping = (CMISRenditionMapping)singletonCache.get(KEY_CMIS_RENDITION_MAPPING_NODEREF); - if (renditionMapping == null) - { - renditionMapping = new CMISRenditionMapping(nodeService, contentService, renditionService, - transactionService, kindToRenditionNames); - - singletonCache.put(KEY_CMIS_RENDITION_MAPPING_NODEREF, renditionMapping); - } - return renditionMapping; - } -} +package org.alfresco.opencmis; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.events.types.ContentEvent; +import org.alfresco.events.types.ContentEventImpl; +import org.alfresco.events.types.ContentReadRangeEvent; +import org.alfresco.events.types.Event; +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.ActivityPosterImpl.ActivityInfo; +import org.alfresco.opencmis.dictionary.CMISActionEvaluator; +import org.alfresco.opencmis.dictionary.CMISAllowedActionEnum; +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.opencmis.dictionary.CMISObjectVariant; +import org.alfresco.opencmis.dictionary.CMISPropertyAccessor; +import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper; +import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; +import org.alfresco.opencmis.dictionary.ItemTypeDefinitionWrapper; +import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.opencmis.search.CMISQueryOptions; +import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; +import org.alfresco.opencmis.search.CMISQueryService; +import org.alfresco.opencmis.search.CMISResultSet; +import org.alfresco.opencmis.search.CMISResultSetColumn; +import org.alfresco.opencmis.search.CMISResultSetRow; +import org.alfresco.repo.Client; +import org.alfresco.repo.Client.ClientType; +import org.alfresco.repo.action.executer.ContentMetadataExtracter; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.events.EventPreparator; +import org.alfresco.repo.events.EventPublisher; +import org.alfresco.repo.model.filefolder.GetChildrenCannedQuery; +import org.alfresco.repo.model.filefolder.HiddenAspect; +import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility; +import org.alfresco.repo.policy.BehaviourFilter; +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.security.permissions.PermissionReference; +import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl; +import org.alfresco.repo.security.permissions.impl.ModelDAO; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantDeployer; +import org.alfresco.repo.thumbnail.ThumbnailDefinition; +import org.alfresco.repo.thumbnail.ThumbnailHelper; +import org.alfresco.repo.thumbnail.ThumbnailRegistry; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.version.VersionBaseModel; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.audit.AuditQueryParameters; +import org.alfresco.service.cmr.audit.AuditService; +import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.InvalidAspectException; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.MimetypeService; +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.Path.ChildAssocElement; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.descriptor.Descriptor; +import org.alfresco.service.descriptor.DescriptorService; +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.FileFilterMode; +import org.alfresco.util.Pair; +import org.alfresco.util.TempFileProvider; +import org.apache.chemistry.opencmis.commons.BasicPermissions; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.data.Ace; +import org.apache.chemistry.opencmis.commons.data.Acl; +import org.apache.chemistry.opencmis.commons.data.AllowableActions; +import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.data.ObjectData; +import org.apache.chemistry.opencmis.commons.data.ObjectList; +import org.apache.chemistry.opencmis.commons.data.PermissionMapping; +import org.apache.chemistry.opencmis.commons.data.Properties; +import org.apache.chemistry.opencmis.commons.data.PropertyData; +import org.apache.chemistry.opencmis.commons.data.PropertyId; +import org.apache.chemistry.opencmis.commons.data.PropertyString; +import org.apache.chemistry.opencmis.commons.data.RenditionData; +import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; +import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition; +import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition; +import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; +import org.apache.chemistry.opencmis.commons.enums.AclPropagation; +import org.apache.chemistry.opencmis.commons.enums.Action; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl; +import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges; +import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates; +import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; +import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; +import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions; +import org.apache.chemistry.opencmis.commons.enums.Cardinality; +import org.apache.chemistry.opencmis.commons.enums.ChangeType; +import org.apache.chemistry.opencmis.commons.enums.CmisVersion; +import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed; +import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; +import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection; +import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions; +import org.apache.chemistry.opencmis.commons.enums.Updatability; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; +import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyData; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ChangeEventInfoDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl; +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.CmisService; +import org.apache.chemistry.opencmis.commons.spi.Holder; +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.util.StringUtils; + +/** + * Bridge connecting Alfresco and OpenCMIS. + *

+ * This class provides many of the typical services that the {@link CmisService} implementation + * will need to use Alfresco. + * + * @author florian.mueller + * @author Derek Hulley + * @author steveglover + */ +public class CMISConnector implements ApplicationContextAware, ApplicationListener, TenantDeployer +{ + private static Log logger = LogFactory.getLog(CMISConnector.class); + + // mappings from cmis property names to their Alfresco property name counterparts (used by getChildren) + private static Map SORT_PROPERTY_MAPPINGS = new HashMap(); + + static + { + SORT_PROPERTY_MAPPINGS.put(PropertyIds.LAST_MODIFICATION_DATE, ContentModel.PROP_MODIFIED); + SORT_PROPERTY_MAPPINGS.put(PropertyIds.CREATION_DATE, ContentModel.PROP_CREATED); + } + + public static final char ID_SEPERATOR = ';'; + public static final char REPLACEMENT_CHAR = '_'; + public static final String ASSOC_ID_PREFIX = "assoc:"; + public static final String PWC_VERSION_LABEL = "pwc"; + public static final String UNVERSIONED_VERSION_LABEL = "1.0"; + + public static final String RENDITION_NONE = "cmis:none"; + + public static final String CMIS_CHANGELOG_AUDIT_APPLICATION = "CMISChangeLog"; + + public static final String ALFRESCO_EXTENSION_NAMESPACE = "http://www.alfresco.org"; + public static final String CMIS_NAMESPACE = "http://docs.oasis-open.org/ns/cmis/core/200908/"; + public static final String ASPECTS = "aspects"; + public static final String SET_ASPECTS = "setAspects"; + public static final String APPLIED_ASPECTS = "appliedAspects"; + public static final String ASPECTS_TO_ADD = "aspectsToAdd"; + public static final String ASPECTS_TO_REMOVE = "aspectsToRemove"; + public static final String PROPERTIES = "properties"; + + private static final BigInteger TYPES_DEFAULT_MAX_ITEMS = BigInteger.valueOf(50); + private static final BigInteger TYPES_DEFAULT_DEPTH = BigInteger.valueOf(-1); + private static final BigInteger OBJECTS_DEFAULT_MAX_ITEMS = BigInteger.valueOf(200); + private static final BigInteger OBJECTS_DEFAULT_DEPTH = BigInteger.valueOf(10); + + private static final String QUERY_NAME_OBJECT_ID = "cmis:objectId"; + private static final String QUERY_NAME_OBJECT_TYPE_ID = "cmis:objectTypeId"; + private static final String QUERY_NAME_BASE_TYPE_ID = "cmis:baseTypeId"; + + private static final String CMIS_USER = "cmis:user"; + + private static final BigInteger maxInt = BigInteger.valueOf(Integer.MAX_VALUE); + private static final BigInteger minInt = BigInteger.valueOf(Integer.MIN_VALUE); + private static final BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); + private static final BigInteger minLong = BigInteger.valueOf(Long.MIN_VALUE); + + // lifecycle + private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); + + // Alfresco objects + private DescriptorService descriptorService; + private NodeService nodeService; + private VersionService versionService; + private CheckOutCheckInService checkOutCheckInService; + private LockService lockService; + private ContentService contentService; + private RenditionService renditionService; + private FileFolderService fileFolderService; + private TenantAdminService tenantAdminService; + private TransactionService transactionService; + private AuthenticationService authenticationService; + private PermissionService permissionService; + private ModelDAO permissionModelDao; + private CMISDictionaryService cmisDictionaryService; + private CMISDictionaryService cmisDictionaryService11; + private CMISQueryService cmisQueryService; + private CMISQueryService cmisQueryService11; + private MimetypeService mimetypeService; + private AuditService auditService; + private NamespaceService namespaceService; + private SearchService searchService; + private DictionaryService dictionaryService; + private SiteService siteService; + private ActionService actionService; + private ThumbnailService thumbnailService; + private ServiceRegistry serviceRegistry; + private EventPublisher eventPublisher; + + private CmisActivityPoster activityPoster; + + private BehaviourFilter behaviourFilter; + + private HiddenAspect hiddenAspect; + + private StoreRef storeRef; + private String rootPath; + private Map> kindToRenditionNames; + + // note: cache is tenant-aware (if using TransctionalCache impl) + + private SimpleCache singletonCache; // eg. for cmisRootNodeRef, cmisRenditionMapping + private final String KEY_CMIS_ROOT_NODEREF = "key.cmisRoot.noderef"; + private final String KEY_CMIS_RENDITION_MAPPING_NODEREF = "key.cmisRenditionMapping.noderef"; + + private String proxyUser; + private boolean openHttpSession = false; + + // OpenCMIS objects + private BigInteger typesDefaultMaxItems = TYPES_DEFAULT_MAX_ITEMS; + private BigInteger typesDefaultDepth = TYPES_DEFAULT_DEPTH; + private BigInteger objectsDefaultMaxItems = OBJECTS_DEFAULT_MAX_ITEMS; + private BigInteger objectsDefaultDepth = OBJECTS_DEFAULT_DEPTH; + + private List repositoryPermissions; + private Map permissionMappings; + + private ObjectFilter objectFilter; + + // Bulk update properties + private int bulkMaxItems = 1000; + private int bulkBatchSize = 20; + private int bulkWorkerThreads = 2; + + // -------------------------------------------------------------- + // Configuration + // -------------------------------------------------------------- + + public void setObjectFilter(ObjectFilter objectFilter) + { + this.objectFilter = objectFilter; + } + + /** + * Sets the root store. + * + * @param store + * store_type://store_id + */ + public void setStore(String store) + { + this.storeRef = new StoreRef(store); + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setActivityPoster(CmisActivityPoster activityPoster) + { + this.activityPoster = activityPoster; + } + + public CmisActivityPoster getActivityPoster() + { + return activityPoster; + } + + public void setHiddenAspect(HiddenAspect hiddenAspect) + { + this.hiddenAspect = hiddenAspect; + } + + public boolean isHidden(NodeRef nodeRef) + { + final FileFilterMode.Client client = FileFilterMode.getClient(); + return (hiddenAspect.getVisibility(client, nodeRef) == Visibility.NotVisible); + } + + /** + * Sets the root path. + * + * @param path + * path within default store + */ + public void setRootPath(String path) + { + rootPath = path; + } + + public BigInteger getTypesDefaultMaxItems() + { + return typesDefaultMaxItems; + } + + public void setTypesDefaultMaxItems(BigInteger typesDefaultMaxItems) + { + this.typesDefaultMaxItems = typesDefaultMaxItems; + } + + public BigInteger getTypesDefaultDepth() + { + return typesDefaultDepth; + } + + public void setTypesDefaultDepth(BigInteger typesDefaultDepth) + { + this.typesDefaultDepth = typesDefaultDepth; + } + + public BigInteger getObjectsDefaultMaxItems() + { + return objectsDefaultMaxItems; + } + + public void setObjectsDefaultMaxItems(BigInteger objectsDefaultMaxItems) + { + this.objectsDefaultMaxItems = objectsDefaultMaxItems; + } + + public BigInteger getObjectsDefaultDepth() + { + return objectsDefaultDepth; + } + + public void setObjectsDefaultDepth(BigInteger objectsDefaultDepth) + { + this.objectsDefaultDepth = objectsDefaultDepth; + } + + /** + * Set rendition kind mapping. + */ + public void setRenditionKindMapping(Map> renditionKinds) + { + this.kindToRenditionNames = renditionKinds; + } + + public void setOpenHttpSession(boolean openHttpSession) + { + this.openHttpSession = openHttpSession; + } + + public boolean openHttpSession() + { + return openHttpSession; + } + + public void setThumbnailService(ThumbnailService thumbnailService) + { + this.thumbnailService = thumbnailService; + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * Sets the descriptor service. + */ + public void setDescriptorService(DescriptorService descriptorService) + { + this.descriptorService = descriptorService; + } + + public DescriptorService getDescriptorService() + { + return descriptorService; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + /** + * Sets the node service. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public NodeService getNodeService() + { + return nodeService; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + /** + * Sets the version service. + */ + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + + public VersionService getVersionService() + { + return versionService; + } + + /** + * Sets the checkOut/checkIn service. + */ + public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService) + { + this.checkOutCheckInService = checkOutCheckInService; + } + + public CheckOutCheckInService getCheckOutCheckInService() + { + return checkOutCheckInService; + } + + /** + * Sets the lock service. + */ + public LockService getLockService() + { + return lockService; + } + + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + + /** + * Sets the content service. + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public ContentService getContentService() + { + return contentService; + } + + /** + * Sets the event publisher + */ + public void setEventPublisher(EventPublisher eventPublisher) + { + this.eventPublisher = eventPublisher; + } + + /** + * Sets the rendition service. + */ + public void setrenditionService(RenditionService renditionService) + { + this.renditionService = renditionService; + } + + /** + * Sets the file folder service. + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public FileFolderService getFileFolderService() + { + return fileFolderService; + } + + /** + * Sets the tenant admin service. + */ + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + public void setSingletonCache(SimpleCache singletonCache) + { + this.singletonCache = singletonCache; + } + + /** + * Sets the transaction service. + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public TransactionService getTransactionService() + { + return transactionService; + } + + /** + * Sets the authentication service. + */ + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public AuthenticationService getAuthenticationService() + { + return authenticationService; + } + + /** + * Sets the permission service. + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * Sets the permission model DAO. + */ + public void setPermissionModelDao(ModelDAO permissionModelDao) + { + this.permissionModelDao = permissionModelDao; + } + + public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService) + { + this.cmisDictionaryService = cmisDictionaryService; + } + + public void setOpenCMISDictionaryService11(CMISDictionaryService cmisDictionaryService) + { + this.cmisDictionaryService11 = cmisDictionaryService; + } + + public CMISDictionaryService getOpenCMISDictionaryService() + { + CmisVersion cmisVersion = getRequestCmisVersion(); + if(cmisVersion.equals(CmisVersion.CMIS_1_0)) + { + return cmisDictionaryService; + } + else + { + return cmisDictionaryService11; + } + } + + /** + * Sets the OpenCMIS query service. + */ + public void setOpenCMISQueryService(CMISQueryService cmisQueryService) + { + this.cmisQueryService = cmisQueryService; + } + + public void setOpenCMISQueryService11(CMISQueryService cmisQueryService) + { + this.cmisQueryService11 = cmisQueryService; + } + + public CMISQueryService getOpenCMISQueryService() + { + CmisVersion cmisVersion = getRequestCmisVersion(); + if(cmisVersion.equals(CmisVersion.CMIS_1_0)) + { + return cmisQueryService; + } + else + { + return cmisQueryService11; + } + } + + /** + * Sets the MIME type service. + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + public MimetypeService getMimetypeService() + { + return mimetypeService; + } + + /** + * Sets the audit service. + */ + public void setAuditService(AuditService auditService) + { + this.auditService = auditService; + } + + /** + * Sets the namespace service. + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Sets the search service. + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public SearchService getSearchService() + { + return searchService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public DictionaryService getDictionaryService() + { + return dictionaryService; + } + + /** + * Not implemented + * @throws UnsupportedOperationException always + */ + public void setProxyUser(String proxyUser) + { +// this.proxyUser = proxyUser; + throw new UnsupportedOperationException("proxyUser setting not implemented. Please raise a JIRA request."); + } + + public String getProxyUser() + { + return proxyUser; + } + + /** + * Sets bulk update properties. + */ + public void setBulkMaxItems(int size) + { + bulkMaxItems = size; + } + + public int getBulkMaxItems() + { + return bulkMaxItems; + } + + public void setBulkBatchSize(int size) + { + bulkBatchSize = size; + } + + public int getBulkBatchSize() + { + return bulkBatchSize; + } + + public void setBulkWorkerThreads(int threads) + { + bulkWorkerThreads = threads; + } + + public int getBulkWorkerThreads() + { + return bulkWorkerThreads; + } + + // -------------------------------------------------------------- + // Lifecycle methods + // -------------------------------------------------------------- + + private File tmp; + + public void setup() + { + File tempDir = TempFileProvider.getTempDir(); + this.tmp = new File(tempDir, "CMISAppend"); + if(!this.tmp.exists() && !this.tmp.mkdir()) + { + throw new AlfrescoRuntimeException("Failed to create CMIS temporary directory"); + } + } + + public void init() + { + // register as tenant deployer + tenantAdminService.register(this); + + // set up and cache rendition mapping + getRenditionMapping(); + + // cache root node ref + getRootNodeRef(); + + // cache permission definitions and permission mappings + repositoryPermissions = getRepositoryPermissions(); + permissionMappings = getPermissionMappings(); + } + + public void destroy() + { + singletonCache.remove(KEY_CMIS_ROOT_NODEREF); + singletonCache.remove(KEY_CMIS_RENDITION_MAPPING_NODEREF); + } + + public void onEnableTenant() + { + init(); + } + + public void onDisableTenant() + { + destroy(); + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + lifecycle.setApplicationContext(applicationContext); + } + + public void onApplicationEvent(ApplicationContextEvent event) + { + lifecycle.onApplicationEvent(event); + } + + /** + * Hooks into Spring Application Lifecycle. + */ + private class ProcessorLifecycle extends AbstractLifecycleBean + { + @Override + protected void onBootstrap(ApplicationEvent event) + { + init(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } + } + + // -------------------------------------------------------------- + // Alfresco methods + // -------------------------------------------------------------- + + /* + * For the given cmis property name get the corresponding Alfresco property name. + * + * Certain CMIS properties (e.g. cmis:creationDate and cmis:lastModifiedBy) don't + * have direct mappings to Alfresco properties through the CMIS dictionary, because + * these mappings should not be exposed outside the repository through CMIS. For these, + * however, this method provides the mapping so that the sort works. + * + */ + public Pair getSortProperty(String cmisPropertyName, String direction) + { + QName sortPropName = null; + Pair sortProp = null; + + PropertyDefinitionWrapper propDef = getOpenCMISDictionaryService().findPropertyByQueryName(cmisPropertyName); + if (propDef != null) + { + if (propDef.getPropertyId().equals(PropertyIds.BASE_TYPE_ID)) + { + // special-case (see also ALF-13968) - for getChildren, using "cmis:baseTypeId" allows sorting of folders first and vice-versa (cmis:folder <-> cmis:document) + sortPropName = GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER; + } + else + { + sortPropName = propDef.getPropertyAccessor().getMappedProperty(); + } + + if (sortPropName == null) + { + // ok to map these properties because we are always getting current versions of nodes + sortPropName = SORT_PROPERTY_MAPPINGS.get(cmisPropertyName); + } + } + + if (sortPropName != null) + { + boolean sortAsc = (direction == null ? true : direction.equalsIgnoreCase("asc")); + sortProp = new Pair(sortPropName, sortAsc); + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + cmisPropertyName + " - mapping not found"); + } + } + + return sortProp; + } + + /** + * Asynchronously generates thumbnails for the given node. + * + * @param nodeRef NodeRef + * @param thumbnailNames Set + */ + public void createThumbnails(NodeRef nodeRef, Set thumbnailNames) + { + if(thumbnailNames == null || thumbnailNames.size() == 0) + { + return; + } + + ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry(); + + // If there's nothing currently registered to generate thumbnails for the + // specified mimetype, then log a message and bail out + Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); + if (contentData == null) + { + logger.info("Unable to create thumbnails as there is no content"); + return; + } + + long size = contentData.getSize(); + String mimeType = contentData.getMimetype(); + + for(String thumbnailName : thumbnailNames) + { + // Use the thumbnail registy to get the details of the thumbail + ThumbnailDefinition details = registry.getThumbnailDefinition(thumbnailName); + if(details == null) + { + // Throw exception + logger.warn("The thumbnail name '" + thumbnailName + "' is not registered"); + continue; + } + else + { + if(registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), mimeType, size, nodeRef, details)) + { + org.alfresco.service.cmr.action.Action action = ThumbnailHelper.createCreateThumbnailAction(details, serviceRegistry); + + // Queue async creation of thumbnail + actionService.executeAction(action, nodeRef, true, true); + } + else + { + logger.info("Unable to create thumbnail '" + details.getName() + "' for " + + mimeType + " as no transformer is currently available"); + } + } + } + } + + /** + * Extracts metadata for the node. + * + * @param nodeRef NodeRef + */ + public void extractMetadata(NodeRef nodeRef) + { + org.alfresco.service.cmr.action.Action action = actionService.createAction(ContentMetadataExtracter.EXECUTOR_NAME); + actionService.executeAction(action, nodeRef, true, false); + } + + public SiteInfo getSite(NodeRef nodeRef) + { + return siteService.getSite(nodeRef); + } + + /** + * Should the node be filtered? + */ + public boolean filter(NodeRef nodeRef) + { + return objectFilter.filter(nodeRef); + } + + public boolean disableBehaviour(QName className) + { + boolean wasEnabled = behaviourFilter.isEnabled(className); + if(wasEnabled) + { + behaviourFilter.disableBehaviour(className); + } + return wasEnabled; + } + + public boolean enableBehaviour(QName className) + { + boolean isEnabled = behaviourFilter.isEnabled(className); + if(!isEnabled) + { + behaviourFilter.enableBehaviour(className); + } + return isEnabled; + } + + + public boolean disableBehaviour(QName className, NodeRef nodeRef) + { + boolean wasEnabled = behaviourFilter.isEnabled(nodeRef, className); + if(wasEnabled) + { + behaviourFilter.disableBehaviour(nodeRef, className); + } + return wasEnabled; + } + + public boolean enableBehaviour(QName className, NodeRef nodeRef) + { + boolean isEnabled = behaviourFilter.isEnabled(nodeRef, className); + if(!isEnabled) + { + behaviourFilter.enableBehaviour(nodeRef, className); + } + return isEnabled; + } + + public StoreRef getRootStoreRef() + { + return getRootNodeRef().getStoreRef(); + } + + public void deleteNode(NodeRef nodeRef, boolean postActivity) + { + ActivityInfo activityInfo = null; + + // post activity after removal of the node + postActivity &= !hiddenAspect.hasHiddenAspect(nodeRef); + if(postActivity) + { + // get this information before the node is deleted + activityInfo = activityPoster.getActivityInfo(nodeRef); + } + + getNodeService().deleteNode(nodeRef); + + // post activity after removal of the node + if(postActivity && activityInfo != null) + { + activityPoster.postFileFolderDeleted(activityInfo); + } + } + + public RetryingTransactionHelper getRetryingTransactionHelper() + { + return transactionService.getRetryingTransactionHelper(); + } + + /** + * Returns the root folder node ref. + */ + public NodeRef getRootNodeRef() + { + NodeRef rootNodeRef = (NodeRef)singletonCache.get(KEY_CMIS_ROOT_NODEREF); + if (rootNodeRef == null) + { + rootNodeRef = AuthenticationUtil.runAs(new RunAsWork() + { + public NodeRef doWork() throws Exception + { + return transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + NodeRef root = nodeService.getRootNode(storeRef); + List rootNodes = searchService.selectNodes(root, rootPath, null, + namespaceService, false); + if (rootNodes.size() != 1) + { + throw new CmisRuntimeException("Unable to locate CMIS root path " + rootPath); + } + return rootNodes.get(0); + }; + }, true); + } + }, AuthenticationUtil.getSystemUserName()); + + if (rootNodeRef == null) + { + throw new CmisObjectNotFoundException("Root folder path '" + rootPath + "' not found!"); + } + + singletonCache.put(KEY_CMIS_ROOT_NODEREF, rootNodeRef); + } + + return rootNodeRef; + } + + public String getName(NodeRef nodeRef) + { + try + { + Object name = nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + return (name instanceof String ? (String) name : null); + } + catch(InvalidNodeRefException inre) + { + return null; + } + } + + /** + * Cuts of the version information from an object id. + */ + public String getCurrentVersionId(String objectId) + { + if (objectId == null) + { + return null; + } + + int sepIndex = objectId.lastIndexOf(ID_SEPERATOR); + if (sepIndex > -1) + { + return objectId.substring(0, sepIndex); + } + + return objectId; + } + + /** + * Creates an object info object. + */ + public CMISNodeInfoImpl createNodeInfo(String objectId) + { + return new CMISNodeInfoImpl(this, objectId); + } + + /** + * Creates an object info object. + */ + public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef) + { + return createNodeInfo(nodeRef, null, null, null, true); + } + + /** + * Creates an object info object. + */ + public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, QName nodeType, Map nodeProps, VersionHistory versionHistory, boolean checkExists) + { + return new CMISNodeInfoImpl(this, nodeRef, nodeType, nodeProps, versionHistory, checkExists); + } + + /** + * Creates an object info object. + */ + public CMISNodeInfoImpl createNodeInfo(AssociationRef assocRef) + { + return new CMISNodeInfoImpl(this, assocRef); + } + + /* + * Strip store ref from the id, if there is one. + */ + private String getGuid(String id) + { + int idx = id.lastIndexOf("/"); + if(idx != -1) + { + return id.substring(idx + 1); + } + else + { + return id; + } + } + + /* + * Construct an object id based on the incoming assocRef and versionLabel. The object id will always + * be the assocRef guid. + */ + public String constructObjectId(AssociationRef assocRef, String versionLabel) + { + return constructObjectId(assocRef, versionLabel, isPublicApi()); + } + + public String constructObjectId(AssociationRef assocRef, String versionLabel, boolean dropStoreRef) + { + StringBuilder sb = new StringBuilder(CMISConnector.ASSOC_ID_PREFIX); + if(dropStoreRef) + { + // always return the guid + sb.append(assocRef.getId()); + } + else + { + sb.append(assocRef.toString()); + } + + if(versionLabel != null) + { + sb.append(CMISConnector.ID_SEPERATOR); + sb.append(versionLabel); + } + return sb.toString(); + } + + /* + * Construct an object id based on the incoming incomingObjectId. The object id will always + * be the node guid. + */ + public String constructObjectId(String incomingObjectId) + { + return constructObjectId(incomingObjectId, null); + } + + /* + * Construct an object id based on the incoming incomingNodeId and versionLabel. The object id will always + * be the node guid. + */ + public String constructObjectId(String incomingNodeId, String versionLabel) + { + return constructObjectId(incomingNodeId, versionLabel, isPublicApi()); + } + + public String constructObjectId(String incomingNodeId, String versionLabel, boolean dropStoreRef) + { + StringBuilder sb = new StringBuilder(); + if(dropStoreRef) + { + // always return the guid + sb.append(getGuid(incomingNodeId)); + } + else + { + sb.append(incomingNodeId); + } + if(versionLabel != null) + { + sb.append(CMISConnector.ID_SEPERATOR); + sb.append(versionLabel); + } + return sb.toString(); + } + + public void createVersion(NodeRef nodeRef, VersionType versionType, String reason) + { + if(versionService.getVersionHistory(nodeRef) == null) + { + // no version history. Make sure we have an initial major version 1.0. + Map versionProperties = new HashMap(2); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); + versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial version"); + versionService.createVersion(nodeRef, versionProperties); + } + + Map versionProperties = new HashMap(2); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, versionType); + versionProperties.put(VersionModel.PROP_DESCRIPTION, reason); + versionService.createVersion(nodeRef, versionProperties); + } + + public CmisVersion getRequestCmisVersion() + { + CallContext callContext = AlfrescoCmisServiceCall.get(); + CmisVersion cmisVersion = (callContext != null ? callContext.getCmisVersion() : CmisVersion.CMIS_1_0); + return cmisVersion; + } + + private boolean isPublicApi() + { + boolean isPublicApi = false; + CallContext callContext = AlfrescoCmisServiceCall.get(); + if(callContext != null) + { + String value = (String)callContext.get("isPublicApi"); + isPublicApi = (value == null ? false : Boolean.parseBoolean(value)); + } + return isPublicApi; + } + + /* + * Construct an object id based on the incoming incomingNodeRef and versionLabel. The object id will always + * be the incomingNodeRef guid. + */ + public String constructObjectId(NodeRef incomingNodeRef, String versionLabel) + { + return constructObjectId(incomingNodeRef, versionLabel, isPublicApi()); + } + + public String constructObjectId(NodeRef incomingNodeRef, String versionLabel, boolean dropStoreRef) + { + StringBuilder sb = new StringBuilder(); + sb.append(dropStoreRef ? incomingNodeRef.getId() : incomingNodeRef.toString()); + if(versionLabel != null) + { + sb.append(CMISConnector.ID_SEPERATOR); + sb.append(versionLabel); + } + return sb.toString(); + } + + /** + * Compiles a CMIS object if for a live node. + */ + public String createObjectId(NodeRef nodeRef) + { + return createObjectId(nodeRef, isPublicApi()); + } + + public String createObjectId(NodeRef nodeRef, boolean dropStoreRef) + { + QName typeQName = nodeService.getType(nodeRef); + TypeDefinitionWrapper type = getOpenCMISDictionaryService().findNodeType(typeQName); + + if(type instanceof ItemTypeDefinitionWrapper) + { + return constructObjectId(nodeRef, null); + } + + if(type instanceof FolderTypeDefintionWrapper) + { + return constructObjectId(nodeRef, null, dropStoreRef); + } + + Serializable versionLabel = getNodeService() + .getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + if (versionLabel == null) + { + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + } + + return constructObjectId(nodeRef, (String)versionLabel, dropStoreRef); + } + + private boolean isFolder(NodeRef nodeRef) + { + return getType(nodeRef) instanceof FolderTypeDefintionWrapper; + } + + /** + * Returns the type definition of a node or null if no type + * definition could be found. + */ + public TypeDefinitionWrapper getType(NodeRef nodeRef) + { + try + { + QName typeQName = nodeService.getType(nodeRef); + return getOpenCMISDictionaryService().findNodeType(typeQName); + } + catch(InvalidNodeRefException inre) + { + return null; + } + } + + /** + * Returns the type definition of an association or null if no + * type definition could be found. + */ + public TypeDefinitionWrapper getType(AssociationRef assocRef) + { + QName typeQName = assocRef.getTypeQName(); + return getOpenCMISDictionaryService().findAssocType(typeQName); + } + + /** + * Returns the type definition of a node or null if no type + * definition could be found. + */ + public TypeDefinitionWrapper getType(String cmisTypeId) + { + return getOpenCMISDictionaryService().findType(cmisTypeId); + } + + /** + * Returns the definition after it has checked if the type can be used for + * object creation. + */ + public TypeDefinitionWrapper getTypeForCreate(String cmisTypeId, BaseTypeId baseTypeId) + { + TypeDefinitionWrapper type = getType(cmisTypeId); + if ((type == null) || (type.getBaseTypeId() != baseTypeId)) + { + switch (baseTypeId) + { + case CMIS_DOCUMENT: + throw new CmisConstraintException("Type is not a document type!"); + case CMIS_FOLDER: + throw new CmisConstraintException("Type is not a folder type!"); + case CMIS_RELATIONSHIP: + throw new CmisConstraintException("Type is not a relationship type!"); + case CMIS_POLICY: + throw new CmisConstraintException("Type is not a policy type!"); + case CMIS_ITEM: + throw new CmisConstraintException("Type is not an item type!"); + } + } + + if (!type.getTypeDefinition(false).isCreatable()) + { + throw new CmisConstraintException("Type is not creatable!"); + } + + return type; + } + + /** + * Applies a versioning state to a document. + */ + public void applyVersioningState(NodeRef nodeRef, VersioningState versioningState) + { + if (versioningState == VersioningState.CHECKEDOUT) + { + if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + } + + // MNT-14687 : Creating a document as checkedout and then cancelling the checkout should delete the document + nodeService.addAspect(nodeRef, ContentModel.ASPECT_CMIS_CREATED_CHECKEDOUT, null); + + getCheckOutCheckInService().checkout(nodeRef); + } + else if ((versioningState == VersioningState.MAJOR) || (versioningState == VersioningState.MINOR)) + { + if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + } + + Map versionProperties = new HashMap(5); + versionProperties.put( + VersionModel.PROP_VERSION_TYPE, + versioningState == VersioningState.MAJOR ? VersionType.MAJOR : VersionType.MINOR); + versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial Version"); + + versionService.createVersion(nodeRef, versionProperties); + } + } + + /** + * Checks if a child of a given type can be added to a given folder. + */ + @SuppressWarnings("unchecked") + public void checkChildObjectType(CMISNodeInfo folderInfo, String childType) + { + TypeDefinitionWrapper targetType = folderInfo.getType(); + PropertyDefinitionWrapper allowableChildObjectTypeProperty = targetType + .getPropertyById(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS); + List childTypes = (List) allowableChildObjectTypeProperty.getPropertyAccessor().getValue( + folderInfo); + + if ((childTypes == null) || childTypes.isEmpty()) + { + return; + } + + if (!childTypes.contains(childType)) + { + throw new CmisConstraintException("Objects of type '" + childType + "' cannot be added to this folder!"); + } + } + + /** + * Creates the CMIS object for a node. + */ + public ObjectData createCMISObject(CMISNodeInfo info, String filter, boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds, + boolean includeAcl) + { + if (info.getType() == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + Properties nodeProps = (info.isRelationship() ? getAssocProperties(info, filter) : getNodeProperties(info, filter)); + + return createCMISObjectImpl(info, nodeProps, filter, includeAllowableActions, includeRelationships, + renditionFilter, includePolicyIds, includeAcl); + } + + @SuppressWarnings("unchecked") + private ObjectData createCMISObjectImpl(final CMISNodeInfo info, Properties nodeProps, String filter, + boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + boolean includePolicyIds, boolean includeAcl) + { + final ObjectDataImpl result = new ObjectDataImpl(); + + // set allowable actions + if (includeAllowableActions) + { + result.setAllowableActions(getAllowableActions(info)); + } + + // set policy ids + if (includePolicyIds) + { + result.setPolicyIds(new PolicyIdListImpl()); + } + + if (info.isRelationship()) + { + // set properties + result.setProperties(getAssocProperties(info, filter)); + + // set ACL + if (includeAcl) + { + // association have no ACL - return an empty list of ACEs + result.setAcl(new AccessControlListImpl((List) Collections.EMPTY_LIST)); + result.setIsExactAcl(Boolean.FALSE); + } + } + else + { + // set properties + result.setProperties(nodeProps); + + // set relationships + if (includeRelationships != IncludeRelationships.NONE) + { + result.setRelationships(getRelationships(info.getNodeRef(), includeRelationships)); + } + + // set renditions + if (!RENDITION_NONE.equals(renditionFilter)) + { + List renditions = getRenditions(info.getNodeRef(), renditionFilter, null, null); + if ((renditions != null) && (!renditions.isEmpty())) + { + result.setRenditions(renditions); + } + else + { + result.setRenditions(Collections.EMPTY_LIST); + } + } + + // set ACL + if (includeAcl) + { + AuthenticationUtil.runAsSystem(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + Acl acl = getACL(info.getCurrentNodeNodeRef(), false); + if (acl != null) + { + result.setAcl(acl); + result.setIsExactAcl(acl.isExact()); + } + return null; + } + }); + } + + // add aspects + List extensions = getAspectExtensions(info, filter, result.getProperties() + .getProperties().keySet()); + + if (!extensions.isEmpty()) + { + result.getProperties().setExtensions( + Collections.singletonList((CmisExtensionElement) new CmisExtensionElementImpl( + ALFRESCO_EXTENSION_NAMESPACE, ASPECTS, null, extensions))); + } + } + return result; + } + + public String getPath(NodeRef nodeRef) + { + try + { + Path path = nodeService.getPath(nodeRef); + + // skip to CMIS root path + NodeRef rootNode = getRootNodeRef(); + int i = 0; + while (i < path.size()) + { + Path.Element element = path.get(i); + if (element instanceof ChildAssocElement) + { + ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); + NodeRef node = assocRef.getChildRef(); + if (node.equals(rootNode)) + { + break; + } + } + i++; + } + + StringBuilder displayPath = new StringBuilder(64); + + if (path.size() - i == 1) + { + // render root path + displayPath.append("/"); + } + else + { + // render CMIS scoped path + i++; + while (i < path.size()) + { + Path.Element element = path.get(i); + if (element instanceof ChildAssocElement) + { + ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); + NodeRef node = assocRef.getChildRef(); + displayPath.append("/"); + displayPath.append(nodeService.getProperty(node, ContentModel.PROP_NAME)); + } + i++; + } + } + + return displayPath.toString(); + } + catch(InvalidNodeRefException inre) + { + return null; + } + } + + /** + * Gets the content from the repository. + */ + public ContentStream getContentStream(CMISNodeInfo info, String streamId, BigInteger offset, BigInteger length) + { + // get the type and check if the object can have content + TypeDefinitionWrapper type = info.getType(); + checkDocumentTypeForContent(type); + + // looks like a document, now get the content + ContentStreamImpl result = new ContentStreamImpl(); + result.setFileName(info.getName()); + + // if streamId is set, fetch other content + NodeRef streamNodeRef = info.getNodeRef(); + if ((streamId != null) && (streamId.length() > 0)) + { + CMISNodeInfo streamInfo = createNodeInfo(streamId); + if (!streamInfo.isVariant(CMISObjectVariant.CURRENT_VERSION)) + { + throw new CmisInvalidArgumentException("Stream id is invalid: " + streamId + ", expected variant " + CMISObjectVariant.CURRENT_VERSION + ", got variant " + streamInfo.getObjectVariant()); + } + + streamNodeRef = streamInfo.getNodeRef(); + type = streamInfo.getType(); + checkDocumentTypeForContent(type); + } + + // get the stream now + try + { + ContentReader contentReader = contentService.getReader(streamNodeRef, ContentModel.PROP_CONTENT); + if (contentReader == null) + { + throw new CmisConstraintException("Document has no content!"); + } + + result.setMimeType(contentReader.getMimetype()); + long contentSize = contentReader.getSize(); + + if ((offset == null) && (length == null)) + { + result.setStream(contentReader.getContentInputStream()); + result.setLength(BigInteger.valueOf(contentSize)); + publishReadEvent(streamNodeRef, info.getName(), result.getMimeType(), contentSize, contentReader.getEncoding(), null); + } + else + { + long off = (offset == null ? 0 : offset.longValue()); + long len = (length == null ? contentSize : length.longValue()); + if (off + len > contentSize) + { + len = contentReader.getSize() - off; + } + + result.setStream(new RangeInputStream(contentReader.getContentInputStream(), off, len)); + result.setLength(BigInteger.valueOf(len)); + publishReadEvent(streamNodeRef, info.getName(), result.getMimeType(), contentSize, contentReader.getEncoding(), off+" - "+len); + } + } + catch (Exception e) + { + if (e instanceof CmisBaseException) + { + throw (CmisBaseException) e; + } + else + { + StringBuilder msg = new StringBuilder("Failed to retrieve content: " + e.getMessage()); + Throwable cause = e.getCause(); + if(cause != null) + { + // add the cause to the CMIS exception + msg.append(", "); + msg.append(cause.getMessage()); + } + throw new CmisRuntimeException(msg.toString(), e); + } + } + + return result; + } + + /** + * Notifies listeners that a read has taken place. + * + * @param nodeRef NodeRef + * @param name String + * @param mimeType String + * @param contentSize long + * @param encoding String + * @param range String + */ + protected void publishReadEvent(final NodeRef nodeRef, final String name, final String mimeType, final long contentSize, final String encoding, final String range) + { + final QName nodeType = nodeRef==null?null:nodeService.getType(nodeRef); + + eventPublisher.publishEvent(new EventPreparator(){ + @Override + public Event prepareEvent(String user, String networkId, String transactionId) + { + if (StringUtils.hasText(range)) + { + return new ContentReadRangeEvent(user, networkId, transactionId, + nodeRef.getId(), null, nodeType.toString(), Client.asType(ClientType.cmis), name, mimeType, contentSize, encoding, range); + } + else + { + return new ContentEventImpl(ContentEvent.DOWNLOAD, user, networkId, transactionId, + nodeRef.getId(), null, nodeType.toString(), Client.asType(ClientType.cmis), name, mimeType, contentSize, encoding); + } + } + }); + + + } + + public void appendContent(CMISNodeInfo nodeInfo, ContentStream contentStream, boolean isLastChunk) throws IOException + { + NodeRef nodeRef = nodeInfo.getNodeRef(); + + this.disableBehaviour(ContentModel.ASPECT_VERSIONABLE, nodeRef); + + if(!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT)) + { + Map props = new HashMap(); + props.put(ContentModel.PROP_GOT_FIRST_CHUNK, true); + nodeService.addAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT, props); + } + + ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + InputStream existingContentInput = (reader != null ? reader.getContentInputStream() : null); + InputStream input = contentStream.getStream(); + + ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + OutputStream out = new BufferedOutputStream(writer.getContentOutputStream()); + + InputStream in = null; + if(existingContentInput != null) + { + in = new SequenceInputStream(existingContentInput, input); + } + else + { + in = input; + } + + try + { + IOUtils.copy(in, out); + + if(isLastChunk) + { + nodeService.removeAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT); + getActivityPoster().postFileFolderUpdated(nodeInfo.isFolder(), nodeRef); + + createVersion(nodeRef, VersionType.MINOR, "Appended content stream"); + } + } + finally + { + if(in != null) + { + in.close(); + } + if(out != null) + { + out.close(); + } + + this.enableBehaviour(ContentModel.ASPECT_VERSIONABLE, nodeRef); + } + } + + private void checkDocumentTypeForContent(TypeDefinitionWrapper type) + { + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + if (!(type instanceof DocumentTypeDefinitionWrapper)) + { + throw new CmisStreamNotSupportedException("Object is not a document!"); + } + if (((DocumentTypeDefinition) type.getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) + { + throw new CmisConstraintException("Document cannot have content!"); + } + } + + private void addAspectProperties(CMISNodeInfo info, String filter, PropertiesImpl result) + { + if (getRequestCmisVersion().equals(CmisVersion.CMIS_1_1)) + { + Set propertyIds = new HashSet<>(); + Set filterSet = splitFilter(filter); + + Set aspects = info.getNodeAspects(); + for (QName aspect : aspects) + { + TypeDefinitionWrapper aspectType = getOpenCMISDictionaryService().findNodeType(aspect); + if (aspectType == null) + { + continue; + } + + for (PropertyDefinitionWrapper propDef : aspectType.getProperties()) + { + if (propertyIds.contains(propDef.getPropertyId())) + { + // skip properties that have already been added + continue; + } + + if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) + { + // skip properties that are not in the filter + continue; + } + + Serializable value = propDef.getPropertyAccessor().getValue(info); + result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value)); + + // mark property as 'added' + propertyIds.add(propDef.getPropertyId()); + } + } + } + } + + public Properties getNodeProperties(CMISNodeInfo info, String filter) + { + PropertiesImpl result = new PropertiesImpl(); + + Set filterSet = splitFilter(filter); + + for (PropertyDefinitionWrapper propDef : info.getType().getProperties()) + { + if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID)) + { + // don't filter the object id + if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) + { + // skip properties that are not in the filter + continue; + } + } + + Serializable value = propDef.getPropertyAccessor().getValue(info); + result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value)); + } + + addAspectProperties(info, filter, result); + + return result; + } + + public Properties getAssocProperties(CMISNodeInfo info, String filter) + { + PropertiesImpl result = new PropertiesImpl(); + + Set filterSet = splitFilter(filter); + + for (PropertyDefinitionWrapper propDefWrap : info.getType().getProperties()) + { + PropertyDefinition propDef = propDefWrap.getPropertyDefinition(); + if ((filterSet != null) && (!filterSet.contains(propDef.getQueryName()))) + { + // skip properties that are not in the filter + continue; + } + + CMISPropertyAccessor cmisPropertyAccessor = propDefWrap.getPropertyAccessor(); + Serializable value = cmisPropertyAccessor.getValue(info); + PropertyType propType = propDef.getPropertyType(); + PropertyData propertyData = getProperty(propType, propDefWrap, value); + result.addProperty(propertyData); + } + + return result; + } + + /** + * Builds aspect extension. + */ + public List getAspectExtensions(CMISNodeInfo info, String filter, Set alreadySetProperties) + { + List extensions = new ArrayList(); + Set propertyIds = new HashSet(alreadySetProperties); + List propertyExtensionList = new ArrayList(); + Set filterSet = splitFilter(filter); + + Set aspects = info.getNodeAspects(); + for (QName aspect : aspects) + { + TypeDefinitionWrapper aspectType = getOpenCMISDictionaryService().findNodeType(aspect); + if (aspectType == null) + { + continue; + } + + AspectDefinition aspectDefinition = dictionaryService.getAspect(aspect); + Map aspectProperties = aspectDefinition.getProperties(); + + extensions.add(new CmisExtensionElementImpl(ALFRESCO_EXTENSION_NAMESPACE, APPLIED_ASPECTS, null, aspectType + .getTypeId())); + + for (PropertyDefinitionWrapper propDef : aspectType.getProperties()) + { + boolean addPropertyToExtensionList = getRequestCmisVersion().equals(CmisVersion.CMIS_1_1) && aspectProperties.keySet().contains(propDef.getAlfrescoName()); + // MNT-11876 : add property to extension even if it has been returned (CMIS 1.1) + if (propertyIds.contains(propDef.getPropertyId()) && !addPropertyToExtensionList) + { + // skip properties that have already been added + continue; + } + + if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) + { + // skip properties that are not in the filter + continue; + } + + Serializable value = propDef.getPropertyAccessor().getValue(info); + propertyExtensionList.add(createAspectPropertyExtension(propDef.getPropertyDefinition(), value)); + + // mark property as 'added' + propertyIds.add(propDef.getPropertyId()); + } + } + + if (!propertyExtensionList.isEmpty()) + { + CmisExtensionElementImpl propertiesExtension = new CmisExtensionElementImpl( + ALFRESCO_EXTENSION_NAMESPACE, "properties", null, propertyExtensionList); + extensions.addAll(Collections.singletonList(propertiesExtension)); + } + + return extensions; + } + + /** + * Creates a property extension element. + */ + @SuppressWarnings("rawtypes") + private CmisExtensionElement createAspectPropertyExtension(PropertyDefinition propertyDefinition, Object value) + { + String name; + switch (propertyDefinition.getPropertyType()) + { + case BOOLEAN: + name = "propertyBoolean"; + break; + case DATETIME: + name = "propertyDateTime"; + break; + case DECIMAL: + name = "propertyDecimal"; + break; + case INTEGER: + name = "propertyInteger"; + break; + case ID: + name = "propertyId"; + break; + default: + name = "propertyString"; + } + + Map attributes = new HashMap(); + attributes.put("propertyDefinitionId", propertyDefinition.getId()); + attributes.put("queryName", propertyDefinition.getQueryName()); + // optional value + if (propertyDefinition.getDisplayName() !=null && propertyDefinition.getDisplayName().trim().length() > 0) + { + attributes.put("displayName", propertyDefinition.getDisplayName()); + } + // optional value + if (propertyDefinition.getLocalName() !=null && propertyDefinition.getLocalName().trim().length() > 0) + { + attributes.put("localName", propertyDefinition.getLocalName()); + } + + + List propertyValues = new ArrayList(); + if (value != null) + { + if (value instanceof List) + { + for (Object o : ((List) value)) + { + if(o != null) + { + propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null, + convertAspectPropertyValue(o))); + } + else + { + logger.warn("Unexpected null entry in list value for property " + propertyDefinition.getDisplayName() + + ", value = " + value); + } + } + } + else + { + propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null, + convertAspectPropertyValue(value))); + } + } + + return new CmisExtensionElementImpl(CMIS_NAMESPACE, name, attributes, propertyValues); + } + + private String convertAspectPropertyValue(Object value) + { + if (value instanceof Date) + { + GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + cal.setTime((Date) value); + value = cal; + } + + if (value instanceof GregorianCalendar) + { + DatatypeFactory df; + try + { + df = DatatypeFactory.newInstance(); + } + catch (DatatypeConfigurationException e) + { + throw new IllegalArgumentException("Aspect conversation exception: " + e.getMessage(), e); + } + return df.newXMLGregorianCalendar((GregorianCalendar) value).toXMLFormat(); + } + + // MNT-12496 MNT-15044 + // Filter for AtomPub and Web services bindings only. Browser/json binding already encodes. + if (AlfrescoCmisServiceCall.get() != null && + (CallContext.BINDING_ATOMPUB.equals(AlfrescoCmisServiceCall.get().getBinding()) || + CallContext.BINDING_WEBSERVICES.equals(AlfrescoCmisServiceCall.get().getBinding()))) + { + return filterXmlRestrictedCharacters(value.toString()); + } + else + { + return value.toString(); + } + } + + @SuppressWarnings("unchecked") + private AbstractPropertyData getProperty(PropertyType propType, PropertyDefinitionWrapper propDef, + Serializable value) + { + AbstractPropertyData result = null; + switch (propType) + { + case BOOLEAN: + result = new PropertyBooleanImpl(); + if (value instanceof List) + { + ((PropertyBooleanImpl) result).setValues((List) value); + } + else + { + ((PropertyBooleanImpl) result).setValue((Boolean) value); + } + break; + case DATETIME: + result = new PropertyDateTimeImpl(); + if (value instanceof List) + { + ((PropertyDateTimeImpl) result).setValues((List) DefaultTypeConverter.INSTANCE + .convert(GregorianCalendar.class, (List) value)); + } + else + { + ((PropertyDateTimeImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(GregorianCalendar.class, + value)); + } + break; + case DECIMAL: + result = new PropertyDecimalImpl(); + if (value instanceof List) + { + ((PropertyDecimalImpl) result).setValues((List) DefaultTypeConverter.INSTANCE.convert( + BigDecimal.class, (List) value)); + } + else + { + ((PropertyDecimalImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigDecimal.class, value)); + } + break; + case HTML: + result = new PropertyHtmlImpl(); + if (value instanceof List) + { + ((PropertyHtmlImpl) result).setValues((List) value); + } + else + { + ((PropertyHtmlImpl) result).setValue((String) value); + } + break; + case ID: + result = new PropertyIdImpl(); + if (value instanceof List) + { + ((PropertyIdImpl) result).setValues((List) value); + } + else + { + if (value instanceof NodeRef) + { + ((PropertyIdImpl) result).setValue(constructObjectId((NodeRef)value, null)); + } + else + { + ((PropertyIdImpl) result).setValue((String) value); + } + } + break; + case INTEGER: + result = new PropertyIntegerImpl(); + if (value instanceof List) + { + ((PropertyIntegerImpl) result).setValues((List) DefaultTypeConverter.INSTANCE.convert( + BigInteger.class, (List) value)); + } + else + { + ((PropertyIntegerImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigInteger.class, value)); + } + break; + case STRING: + result = new PropertyStringImpl(); + if (value instanceof List) + { + ((PropertyStringImpl) result).setValues((List) value); + } + else + { + // MNT-12496 MNT-15044 + // Filter for AtomPub and Web services bindings only. Browser/json binding already encodes. + if (AlfrescoCmisServiceCall.get() != null && + (CallContext.BINDING_ATOMPUB.equals(AlfrescoCmisServiceCall.get().getBinding()) || + CallContext.BINDING_WEBSERVICES.equals(AlfrescoCmisServiceCall.get().getBinding()))) + { + ((PropertyStringImpl) result).setValue(filterXmlRestrictedCharacters((String) value)); + } + else + { + ((PropertyStringImpl) result).setValue((String) value); + } + } + break; + case URI: + result = new PropertyUriImpl(); + if (value instanceof List) + { + ((PropertyUriImpl) result).setValues((List) value); + } + else + { + ((PropertyUriImpl) result).setValue((String) value); + } + break; + default: + throw new RuntimeException("Unknown datatype! Spec change?"); + } + + if (propDef != null) + { + result.setId(propDef.getPropertyDefinition().getId()); + result.setQueryName(propDef.getPropertyDefinition().getQueryName()); + result.setDisplayName(propDef.getPropertyDefinition().getDisplayName()); + result.setLocalName(propDef.getPropertyDefinition().getLocalName()); + } + + return result; + } + + private String filterXmlRestrictedCharacters(String origValue) + { + if (origValue == null) + { + return origValue; + } + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < origValue.length(); i++) + { + char ch = origValue.charAt(i); + boolean restricted = (ch < '\u0020') && !(ch == '\t' || ch == '\n' || ch == '\r'); + sb.append(restricted ? REPLACEMENT_CHAR : ch); + } + + return sb.toString(); + } + + private Set splitFilter(String filter) + { + if (filter == null) + { + return null; + } + + if (filter.trim().length() == 0) + { + return null; + } + + Set result = new HashSet(); + for (String s : filter.split(",")) + { + s = s.trim(); + if (s.equals("*")) + { + return null; + } + else if (s.length() > 0) + { + result.add(s); + } + } + + // set a few base properties + result.add(QUERY_NAME_OBJECT_ID); + result.add(QUERY_NAME_OBJECT_TYPE_ID); + result.add(QUERY_NAME_BASE_TYPE_ID); + + return result; + } + + public AllowableActions getAllowableActions(CMISNodeInfo info) + { + AllowableActionsImpl result = new AllowableActionsImpl(); + Set allowableActions = new HashSet(); + result.setAllowableActions(allowableActions); + + for (CMISActionEvaluator evaluator : info.getType().getActionEvaluators().values()) + { + if (evaluator.isAllowed(info)) + { + allowableActions.add(evaluator.getAction()); + } + } + + return result; + } + + public List getRelationships(NodeRef nodeRef, IncludeRelationships includeRelationships/*, CmisVersion cmisVersion*/) + { + List result = new ArrayList(); + + if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + // relationships from and to versions are not preserved + return result; + } + + // get relationships + List assocs = new ArrayList(); + if (includeRelationships == IncludeRelationships.SOURCE || includeRelationships == IncludeRelationships.BOTH) + { + assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); + } + if (includeRelationships == IncludeRelationships.TARGET || includeRelationships == IncludeRelationships.BOTH) + { + assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); + } + + // filter relationships that not map the CMIS domain model + for (AssociationRef assocRef : assocs) + { + TypeDefinitionWrapper assocTypeDef = getOpenCMISDictionaryService().findAssocType(assocRef.getTypeQName()); + if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null + || getType(assocRef.getTargetRef()) == null) + { + continue; + } + + try + { + result.add(createCMISObject(createNodeInfo(assocRef), null, false, IncludeRelationships.NONE, + RENDITION_NONE, false, false/*, cmisVersion*/)); + } + catch(AccessDeniedException e) + { + // PUBLICAPI-110 + // ok, just skip it + } + catch(CmisObjectNotFoundException e) + { + // ignore objects that have not been found (perhaps because their type is unknown to CMIS) + } + // TODO: Somewhere this has not been wrapped correctly + catch (net.sf.acegisecurity.AccessDeniedException e) + { + // skip + } + } + + return result; + } + + public ObjectList getObjectRelationships(NodeRef nodeRef, RelationshipDirection relationshipDirection, + String typeId, String filter, Boolean includeAllowableActions, BigInteger maxItems, BigInteger skipCount) + { + ObjectListImpl result = new ObjectListImpl(); + result.setHasMoreItems(false); + result.setNumItems(BigInteger.ZERO); + result.setObjects(new ArrayList()); + + if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + // relationships from and to versions are not preserved + return result; + } + + // get relationships + List assocs = new ArrayList(); + if (relationshipDirection == RelationshipDirection.SOURCE + || relationshipDirection == RelationshipDirection.EITHER) + { + assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); + } + if (relationshipDirection == RelationshipDirection.TARGET + || relationshipDirection == RelationshipDirection.EITHER) + { + assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); + } + + int skip = (skipCount == null ? 0 : skipCount.intValue()); + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int counter = 0; + boolean hasMore = false; + + if (max > 0) + { + // filter relationships that not map the CMIS domain model + for (AssociationRef assocRef : assocs) + { + TypeDefinitionWrapper assocTypeDef = getOpenCMISDictionaryService().findAssocType(assocRef.getTypeQName()); + if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null + || getType(assocRef.getTargetRef()) == null) + { + continue; + } + + if ((typeId != null) && !assocTypeDef.getTypeId().equals(typeId)) + { + continue; + } + + counter++; + + if (skip > 0) + { + skip--; + continue; + } + + max--; + if (max > 0) + { + try + { + result.getObjects().add( + createCMISObject(createNodeInfo(assocRef), filter, includeAllowableActions, + IncludeRelationships.NONE, RENDITION_NONE, false, false/*, cmisVersion*/)); + } + catch(CmisObjectNotFoundException e) + { + // ignore objects that have not been found (perhaps because their type is unknown to CMIS) + } + } + else + { + hasMore = true; + } + } + } + + result.setNumItems(BigInteger.valueOf(counter)); + result.setHasMoreItems(hasMore); + + return result; + } + + public List getRenditions(NodeRef nodeRef, String renditionFilter, BigInteger maxItems, + BigInteger skipCount) + { + CMISRenditionMapping mapping = getRenditionMapping(); + return mapping.getRenditions(nodeRef, renditionFilter, maxItems, skipCount); + } + + public Acl getACL(NodeRef nodeRef, boolean onlyBasicPermissions) + { + AccessControlListImpl result = new AccessControlListImpl(); + + // get the permissions and sort them + ArrayList ordered = new ArrayList( + permissionService.getAllSetPermissions(nodeRef)); + Collections.sort(ordered, new AccessPermissionComparator()); + + // remove denied permissions and create OpenCMIS objects + Map> aceMap = new HashMap>(); + for (AccessPermission entry : ordered) + { + if (entry.getAccessStatus() == AccessStatus.ALLOWED) + { + // add allowed entries + Map directAce = aceMap.get(entry.getAuthority()); + if (directAce == null) + { + directAce = new HashMap(); + aceMap.put(entry.getAuthority(), directAce); + } + + AccessControlEntryImpl ace = directAce.get(entry.isSetDirectly()); + if (ace == null) + { + ace = new AccessControlEntryImpl(); + ace.setPrincipal(new AccessControlPrincipalDataImpl(entry.getAuthority())); + ace.setPermissions(new ArrayList()); + ace.setDirect(entry.isSetDirectly()); + directAce.put(entry.isSetDirectly(), ace); + } + + ace.getPermissions().add(entry.getPermission()); + } + else if (entry.getAccessStatus() == AccessStatus.DENIED) + { + // remove denied entries + Map directAce = aceMap.get(entry.getAuthority()); + if (directAce != null) + { + for (AccessControlEntryImpl ace : directAce.values()) + { + ace.getPermissions().remove(entry.getPermission()); + } + } + } + } + + // adjust permissions, add CMIS permissions and add ACEs to ACL + List aces = new ArrayList(); + result.setAces(aces); + for (Map bothAces : aceMap.values()) + { + // get, translate and set direct ACE + AccessControlEntryImpl directAce = bothAces.get(true); + if ((directAce != null) && (!directAce.getPermissions().isEmpty())) + { + List permissions = translatePermissionsToCMIS(directAce.getPermissions(), onlyBasicPermissions); + if(permissions != null && !permissions.isEmpty()) + { + // tck doesn't like empty permissions list + directAce.setPermissions(permissions); + aces.add(directAce); + } + } + + // get, translate, remove duplicate permissions and set indirect ACE + AccessControlEntryImpl indirectAce = bothAces.get(false); + if ((indirectAce != null) && (!indirectAce.getPermissions().isEmpty())) + { + List permissions = translatePermissionsToCMIS(indirectAce.getPermissions(), onlyBasicPermissions); + indirectAce.setPermissions(permissions); + + // remove permissions that are already set in the direct ACE + if ((directAce != null) && (!directAce.getPermissions().isEmpty())) + { + indirectAce.getPermissions().removeAll(directAce.getPermissions()); + } + + if(!indirectAce.getPermissions().isEmpty()) + { + // tck doesn't like empty permissions list + aces.add(indirectAce); + } + } + } + + result.setExact(!onlyBasicPermissions); + + return result; + } + + private List translatePermissionsToCMIS(List permissions, boolean onlyBasicPermissions) + { + Set result = new TreeSet(); + + for (String permission : permissions) + { + PermissionReference permissionReference = permissionModelDao.getPermissionReference(null, permission); + + if (onlyBasicPermissions) + { + + // check for full permissions + if (permissionModelDao.hasFull(permissionReference)) + { + result.add(BasicPermissions.ALL); + } + + // check short forms + Set longForms = permissionModelDao.getGranteePermissions(permissionReference); + + HashSet shortForms = new HashSet(); + for (PermissionReference longForm : longForms) + { + shortForms.add(permissionModelDao.isUnique(longForm) ? longForm.getName() : longForm.toString()); + } + + for (String perm : shortForms) + { + if (PermissionService.READ.equals(perm)) + { + result.add(BasicPermissions.READ); + } + else if (PermissionService.WRITE.equals(perm)) + { + result.add(BasicPermissions.WRITE); + } + else if (PermissionService.ALL_PERMISSIONS.equals(perm)) + { + result.add(BasicPermissions.ALL); + } + } + + // check the permission + if (PermissionService.READ.equals(permission)) + { + result.add(BasicPermissions.READ); + } + else if (PermissionService.WRITE.equals(permission)) + { + result.add(BasicPermissions.WRITE); + } + else if (PermissionService.ALL_PERMISSIONS.equals(permission)) + { + result.add(BasicPermissions.ALL); + } + } + else + { + // ACE-2224: only repository specific permissions should be reported + if (permission.startsWith("{")) + { + result.add(permission); + } + // do revert conversion for basic permissions that have exact matching + else if (PermissionService.READ.equals(permission)) + { + result.add(BasicPermissions.READ); + } + else if (PermissionService.WRITE.equals(permission)) + { + result.add(BasicPermissions.WRITE); + } + else if (PermissionService.ALL_PERMISSIONS.equals(permission)) + { + result.add(BasicPermissions.ALL); + } + else + { + result.add(permissionReference.toString()); + } + } + } + + return new ArrayList(result); + } + + public static class AccessPermissionComparator implements Comparator + { + public int compare(AccessPermission left, AccessPermission right) + { + if (left.getPosition() != right.getPosition()) + { + return right.getPosition() - left.getPosition(); + } + else + { + if (left.getAccessStatus() != right.getAccessStatus()) + { + return (left.getAccessStatus() == AccessStatus.DENIED) ? -1 : 1; + } + else + { + int compare = left.getAuthority().compareTo(right.getAuthority()); + if (compare != 0) + { + return compare; + } + else + { + return (left.getPermission().compareTo(right.getPermission())); + } + + } + + } + } + } + + /** + * Applies the given ACLs. + */ + public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl addAces, Acl removeAces) + { + boolean hasAdd = (addAces != null) && (addAces.getAces() != null) && !addAces.getAces().isEmpty(); + boolean hasRemove = (removeAces != null) && (removeAces.getAces() != null) && !removeAces.getAces().isEmpty(); + + if (!hasAdd && !hasRemove) + { + return; + } + + if (!type.getTypeDefinition(false).isControllableAcl()) + { + throw new CmisConstraintException("Object is not ACL controllable!"); + } + + // remove permissions + if (hasRemove) + { + Set permissions = permissionService.getAllSetPermissions(nodeRef); + + // get only direct ACE since only those can be removed + Acl onlyDirectAcl = excludeInheritedAces(nodeRef, removeAces); + + for (Ace ace : onlyDirectAcl.getAces()) + { + String principalId = ace.getPrincipalId(); + if (CMIS_USER.equals(principalId)) + { + principalId = AuthenticationUtil.getFullyAuthenticatedUser(); + } + + for (String permission : translatePermissionsFromCMIS(ace.getPermissions())) + { + + AccessPermission toCheck = new AccessPermissionImpl(permission, AccessStatus.ALLOWED, principalId, + 0); + if (!permissions.contains(toCheck)) + { + throw new CmisConstraintException("No matching ACE found to remove!"); + } + + permissionService.deletePermission(nodeRef, principalId, permission); + } + } + } + + // add permissions + if (hasAdd) + { + for (Ace ace : addAces.getAces()) + { + String principalId = ace.getPrincipalId(); + if (CMIS_USER.equals(principalId)) + { + principalId = AuthenticationUtil.getFullyAuthenticatedUser(); + } + + for (String permission : translatePermissionsFromCMIS(ace.getPermissions())) + { + permissionService.setPermission(nodeRef, principalId, permission, true); + } + } + } + } + + /** + * Converts Acl to map and ignore the indirect ACEs + * + * @param acl Acl + * @return Map + */ + private Map> convertAclToMap(Acl acl) + { + Map> result = new HashMap>(); + + if (acl == null || acl.getAces() == null) + { + return result; + } + + for (Ace ace : acl.getAces()) + { + // don't consider indirect ACEs - we can't change them + if (!ace.isDirect()) + { + // ignore + continue; + } + + // although a principal must not be null, check it + if (ace.getPrincipal() == null || ace.getPrincipal().getId() == null) + { + // ignore + continue; + } + + Set permissions = result.get(ace.getPrincipal().getId()); + if (permissions == null) + { + permissions = new HashSet(); + result.put(ace.getPrincipal().getId(), permissions); + } + + if (ace.getPermissions() != null) + { + permissions.addAll(ace.getPermissions()); + } + } + + return result; + } + + /** + * Filter acl to ignore inherited ACEs + * + * @param nodeRef NodeRef + * @param acl Acl + * @return Acl + */ + protected Acl excludeInheritedAces(NodeRef nodeRef, Acl acl) + { + + List newAces = new ArrayList(); + Acl allACLs = getACL(nodeRef, false); + + Map> originalsAcls = convertAclToMap(allACLs); + Map> newAcls = convertAclToMap(acl); + + // iterate through the original ACEs + for (Map.Entry> ace : originalsAcls.entrySet()) + { + + // add permissions + Set addPermissions = newAcls.get(ace.getKey()); + if (addPermissions != null) + { + ace.getValue().addAll(addPermissions); + } + + // create new ACE + if (!ace.getValue().isEmpty()) + { + newAces.add(new AccessControlEntryImpl(new AccessControlPrincipalDataImpl(ace + .getKey()), new ArrayList(ace.getValue()))); + } + } + + return new AccessControlListImpl(newAces); + } + + /** + * Sets the given ACL. + */ + public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl aces) + { + boolean hasAces = (aces != null) && (aces.getAces() != null) && !aces.getAces().isEmpty(); + + if (!hasAces && !permissionService.getInheritParentPermissions(nodeRef)) + { + return; + } + + if (!type.getTypeDefinition(false).isControllableAcl()) + { + throw new CmisConstraintException("Object is not ACL controllable!"); + } + + // remove all permissions + permissionService.deletePermissions(nodeRef); + + // set new permissions + for (Ace ace : aces.getAces()) + { + String principalId = ace.getPrincipalId(); + if (CMIS_USER.equals(principalId)) + { + principalId = AuthenticationUtil.getFullyAuthenticatedUser(); + } + + List permissions = translatePermissionsFromCMIS(ace.getPermissions()); + for (String permission : permissions) + { + permissionService.setPermission(nodeRef, principalId, permission, true); + } + } + } + + private List translatePermissionsFromCMIS(List permissions) + { + List result = new ArrayList(); + + if (permissions == null) + { + return result; + } + + for (String permission : permissions) + { + if (permission == null) + { + throw new CmisConstraintException("Invalid null permission!"); + } + + if (BasicPermissions.READ.equals(permission)) + { + result.add(PermissionService.READ); + } + else if (BasicPermissions.WRITE.equals(permission)) + { + result.add(PermissionService.WRITE); + } + else if (BasicPermissions.ALL.equals(permission)) + { + result.add(PermissionService.ALL_PERMISSIONS); + } + else if (!permission.startsWith("{")) + { + result.add(permission); + } + else + { + int sepIndex = permission.lastIndexOf('.'); + if (sepIndex == -1) + { + result.add(permission); + } + else + { + result.add(permission.substring(sepIndex + 1)); + } + } + } + + return result; + } + + public void applyPolicies(NodeRef nodeRef, TypeDefinitionWrapper type, List policies) + { + if ((policies == null) || (policies.isEmpty())) + { + return; + } + + if (!type.getTypeDefinition(false).isControllablePolicy()) + { + throw new CmisConstraintException("Object is not policy controllable!"); + } + + // nothing else to do... + } + + @SuppressWarnings("unchecked") + public ObjectList query(String statement, Boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, BigInteger maxItems, BigInteger skipCount/*, CmisVersion cmisVersion*/) + { + // prepare results + ObjectListImpl result = new ObjectListImpl(); + result.setObjects(new ArrayList()); + + // prepare query + CMISQueryOptions options = new CMISQueryOptions(statement, getRootStoreRef()); + CmisVersion cmisVersion = getRequestCmisVersion(); + options.setCmisVersion(cmisVersion); + options.setQueryMode(CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS); + + int skip = 0; + if ((skipCount != null) && (skipCount.intValue() >= 0)) + { + skip = skipCount.intValue(); + options.setSkipCount(skip); + } + + if ((maxItems != null) && (maxItems.intValue() >= 0)) + { + options.setMaxItems(maxItems.intValue()); + } + + boolean fetchObject = includeAllowableActions || (includeRelationships != IncludeRelationships.NONE) + || (!RENDITION_NONE.equals(renditionFilter)); + + // query + CMISResultSet rs = getOpenCMISQueryService().query(options); + try + { + CMISResultSetColumn[] columns = rs.getMetaData().getColumns(); + + for (CMISResultSetRow row : rs) + { + NodeRef nodeRef = row.getNodeRef(); + + if(!nodeService.exists(nodeRef) || filter(nodeRef)) + { + continue; + } + + TypeDefinitionWrapper type = getType(nodeRef); + if (type == null) + { + continue; + } + + ObjectDataImpl hit = new ObjectDataImpl(); + PropertiesImpl properties = new PropertiesImpl(); + hit.setProperties(properties); + + Map values = row.getValues(); + + for (CMISResultSetColumn column : columns) + { + AbstractPropertyData property = getProperty(column.getCMISDataType(), + column.getCMISPropertyDefinition(), values.get(column.getName())); + property.setQueryName(column.getName()); + properties.addProperty(property); + } + + if (fetchObject) + { + // set allowable actions + if (includeAllowableActions) + { + CMISNodeInfoImpl nodeInfo = createNodeInfo(nodeRef); + if(!nodeInfo.getObjectVariant().equals(CMISObjectVariant.NOT_EXISTING)) + { + hit.setAllowableActions(getAllowableActions(nodeInfo)); + } + } + + // set relationships + if (includeRelationships != IncludeRelationships.NONE) + { + hit.setRelationships(getRelationships(nodeRef, includeRelationships/*, cmisVersion*/)); + } + + // set renditions + if (!RENDITION_NONE.equals(renditionFilter)) + { + List renditions = getRenditions(nodeRef, renditionFilter, null, null); + if ((renditions != null) && (!renditions.isEmpty())) + { + hit.setRenditions(renditions); + } + else + { + hit.setRenditions(Collections.EMPTY_LIST); + } + } + } + + result.getObjects().add(hit); + } + + long numberFound = rs.getNumberFound(); + if(numberFound != -1) + { + result.setNumItems(BigInteger.valueOf(numberFound)); + } + result.setHasMoreItems(rs.hasMore()); + + } finally + { + rs.close(); + } + + return result; + } + + /** + * Sets property values. + */ + @SuppressWarnings({ "rawtypes" }) + public void setProperties(NodeRef nodeRef, TypeDefinitionWrapper type, Properties properties, String... exclude) + { + if (properties == null) + { + return; + } + + Map> incomingPropsMap = properties.getProperties(); + if (incomingPropsMap == null) + { + return; + } + + // extract property data into an easier to use form + Map> propsMap = new HashMap>(); + for (String propertyId : incomingPropsMap.keySet()) + { + PropertyData property = incomingPropsMap.get(propertyId); + PropertyDefinitionWrapper propDef = type.getPropertyById(property.getId()); + if (propDef == null) + { + propDef = getOpenCMISDictionaryService().findProperty(propertyId); + if (propDef == null) + { + throw new CmisInvalidArgumentException("Property " + property.getId() + " is unknown!"); + } + } + + Updatability updatability = propDef.getPropertyDefinition().getUpdatability(); + if ((updatability == Updatability.READONLY) + || (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef))) + { + throw new CmisInvalidArgumentException("Property " + property.getId() + " is read-only!"); + } + TypeDefinitionWrapper propType = propDef.getOwningType(); + Serializable value = getValue(property, propDef.getPropertyDefinition().getCardinality() == Cardinality.MULTI); + Pair pair = new Pair(propType, value); + propsMap.put(propertyId, pair); + } + + // Need to do deal with secondary types first + Pair pair = propsMap.get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS); + Serializable secondaryTypesProperty = (pair != null ? pair.getSecond() : null); + if(secondaryTypesProperty != null) + { + if (!(secondaryTypesProperty instanceof List)) + { + throw new CmisInvalidArgumentException("Secondary types must be a list!"); + } + List secondaryTypes = (List)secondaryTypesProperty; + if(secondaryTypes != null && secondaryTypes.size() > 0) + { + // add/remove secondary types/aspects + processSecondaryTypes(nodeRef, secondaryTypes, propsMap); + } + } + + for (String propertyId : propsMap.keySet()) + { + if(propertyId.equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) + { + // already handled above + continue; + } + + pair = propsMap.get(propertyId); + TypeDefinitionWrapper propType = pair.getFirst(); + Serializable value = pair.getSecond(); + if (Arrays.binarySearch(exclude, propertyId) < 0) + { + setProperty(nodeRef, propType, propertyId, value); + } + } + + List extensions = properties.getExtensions(); + if (extensions != null) + { + boolean isNameChanging = properties.getProperties().containsKey(PropertyIds.NAME); + + for (CmisExtensionElement extension : extensions) + { + if (ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace()) + && SET_ASPECTS.equals(extension.getName())) + { + setAspectProperties(nodeRef, isNameChanging, extension); + break; + } + } + } + } + + @SuppressWarnings("rawtypes") + private void processSecondaryTypes(NodeRef nodeRef, List secondaryTypes, Map> propsToAdd) + { + // diff existing aspects and secondaryTypes/aspects list + Set existingAspects = nodeService.getAspects(nodeRef); + Set secondaryTypeAspects = new HashSet(); + for(Object o : secondaryTypes) + { + String secondaryType = (String)o; + + TypeDefinitionWrapper wrapper = getOpenCMISDictionaryService().findType(secondaryType); + if(wrapper != null) + { + QName aspectQName = wrapper.getAlfrescoName(); + secondaryTypeAspects.add(aspectQName); + } + else + { + throw new CmisInvalidArgumentException("Invalid secondary type id " + secondaryType); + } + } + + Set ignore = new HashSet(); + ignore.add(ContentModel.ASPECT_REFERENCEABLE); + ignore.add(ContentModel.ASPECT_LOCALIZED); + + // aspects to add == the list of secondary types - existing aspects - ignored aspects + Set toAdd = new HashSet(secondaryTypeAspects); + toAdd.removeAll(existingAspects); + toAdd.removeAll(ignore); + + // aspects to remove == existing aspects - secondary types + Set aspectsToRemove = new HashSet(); + aspectsToRemove.addAll(existingAspects); + aspectsToRemove.removeAll(ignore); + Iterator it = aspectsToRemove.iterator(); + while(it.hasNext()) + { + QName aspectQName = it.next(); + TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName); + if(w == null || secondaryTypeAspects.contains(aspectQName)) + { + // the type is not exposed or is in the secondary types to set, so remove it from the to remove set + it.remove(); + } + } + + // first, remove aspects + for(QName aspectQName : aspectsToRemove) + { + nodeService.removeAspect(nodeRef, aspectQName); + // aspect is being removed so remove all of its properties from the propsToAdd map + TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName); + for(PropertyDefinitionWrapper wr : w.getProperties()) + { + String propertyId = wr.getPropertyId(); + propsToAdd.remove(propertyId); + } + } + + // add aspects and properties + for(QName aspectQName : toAdd) + { + nodeService.addAspect(nodeRef, aspectQName, null); + + // get aspect properties + AspectDefinition aspectDef = dictionaryService.getAspect(aspectQName); + Map aspectPropDefs = aspectDef.getProperties(); + TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName); + // for each aspect property... + for(QName propQName : aspectPropDefs.keySet()) + { + // find CMIS property id + PropertyDefinitionWrapper property = w.getPropertyByQName(propQName); + String propertyId = property.getPropertyId(); + if(!propsToAdd.containsKey(propertyId)) + { + TypeDefinitionWrapper propType = property.getOwningType(); + // CMIS 1.1 secondary types specification requires that all secondary type properties are set + // property not included in propsToAdd, add it with null value + Pair pair = new Pair(propType, null); + propsToAdd.put(propertyId, pair); + } + } + } + } + + public void addSecondaryTypes(NodeRef nodeRef, List secondaryTypes) + { + if(secondaryTypes != null && secondaryTypes.size() > 0) + { + for(String secondaryType : secondaryTypes) + { + TypeDefinitionWrapper type = getType(secondaryType); + if (type == null) + { + throw new CmisInvalidArgumentException("Invalid secondaryType: " + secondaryType); + } + nodeService.addAspect(nodeRef, type.getAlfrescoName(), null); + } + } + } + + public void removeSecondaryTypes(NodeRef nodeRef, List secondaryTypes) + { + if(secondaryTypes != null && secondaryTypes.size() > 0) + { + for(String secondaryType : secondaryTypes) + { + TypeDefinitionWrapper type = getType(secondaryType); + if (type == null) + { + throw new CmisInvalidArgumentException("Invalid secondaryType: " + secondaryType); + } + nodeService.removeAspect(nodeRef, type.getAlfrescoName()); + } + } + } + + private void setAspectProperties(NodeRef nodeRef, boolean isNameChanging, CmisExtensionElement aspectExtension) + { + if (aspectExtension.getChildren() == null) + { + return; + } + + List aspectsToAdd = new ArrayList(); + List aspectsToRemove = new ArrayList(); + Map> aspectProperties = new HashMap>(); + + for (CmisExtensionElement extension : aspectExtension.getChildren()) + { + if (!ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace())) + { + continue; + } + + if (ASPECTS_TO_ADD.equals(extension.getName()) && (extension.getValue() != null)) + { + aspectsToAdd.add(extension.getValue()); + } + else if (ASPECTS_TO_REMOVE.equals(extension.getName()) && (extension.getValue() != null)) + { + aspectsToRemove.add(extension.getValue()); + } + else if (PROPERTIES.equals(extension.getName()) && (extension.getChildren() != null)) + { + for (CmisExtensionElement property : extension.getChildren()) + { + if (!property.getName().startsWith("property")) + { + continue; + } + + String propertyId = (property.getAttributes() == null ? null : property.getAttributes().get( + "propertyDefinitionId")); + if ((propertyId == null) || (property.getChildren() == null)) + { + continue; + } + + PropertyType propertyType = PropertyType.STRING; + DatatypeFactory df = null; + if (property.getName().equals("propertyBoolean")) + { + propertyType = PropertyType.BOOLEAN; + } + else if (property.getName().equals("propertyInteger")) + { + propertyType = PropertyType.INTEGER; + } + else if (property.getName().equals("propertyDateTime")) + { + propertyType = PropertyType.DATETIME; + try + { + df = DatatypeFactory.newInstance(); + } + catch (DatatypeConfigurationException e) + { + throw new CmisRuntimeException("Aspect conversation exception: " + e.getMessage(), e); + } + } + else if (property.getName().equals("propertyDecimal")) + { + propertyType = PropertyType.DECIMAL; + } + + ArrayList values = new ArrayList(); + if (property.getChildren() != null) + { +// try +// { + for (CmisExtensionElement valueElement : property.getChildren()) + { + if ("value".equals(valueElement.getName())) + { + switch (propertyType) + { + case BOOLEAN: + try + { + values.add(Boolean.parseBoolean(valueElement.getValue())); + } + catch (Exception e) + { + throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e); + } + break; + case DATETIME: + try + { + values.add(df.newXMLGregorianCalendar(valueElement.getValue()) + .toGregorianCalendar()); + } + catch (Exception e) + { + throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e); + } + break; + case INTEGER: + BigInteger value = null; + try + { + value = new BigInteger(valueElement.getValue()); + } + catch (Exception e) + { + throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e); + } + + // overflow check + PropertyDefinitionWrapper propDef = getOpenCMISDictionaryService().findProperty(propertyId); + if(propDef == null) + { + throw new CmisInvalidArgumentException("Property " + propertyId + " is unknown!"); + } + + QName propertyQName = propDef.getPropertyAccessor().getMappedProperty(); + if (propertyQName == null) + { + throw new CmisConstraintException("Unable to set property " + propertyId + "!"); + } + + org.alfresco.service.cmr.dictionary.PropertyDefinition def = dictionaryService.getProperty(propertyQName); + QName dataDef = def.getDataType().getName(); + + if (dataDef.equals(DataTypeDefinition.INT) && (value.compareTo(maxInt) > 0 || value.compareTo(minInt) < 0)) + { + throw new CmisConstraintException("Value is out of range for property " + propertyId); + } + + if (dataDef.equals(DataTypeDefinition.LONG) && (value.compareTo(maxLong) > 0 || value.compareTo(minLong) < 0 )) + { + throw new CmisConstraintException("Value is out of range for property " + propertyId); + } + + values.add(value); + break; + case DECIMAL: + try + { + values.add(new BigDecimal(valueElement.getValue())); + } + catch (Exception e) + { + throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e); + } + break; + default: + values.add(valueElement.getValue()); + } + } + } + } + + aspectProperties.put(QName.createQName(propertyId, namespaceService), values); + } + } + } + + // remove and add aspects + String aspectType = null; + try + { + for (String aspect : aspectsToRemove) + { + aspectType = aspect; + + TypeDefinitionWrapper type = getType(aspect); + if (type == null) + { + throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType); + } + + QName typeName = type.getAlfrescoName(); + // if aspect is hidden aspect, remove only if hidden node is not client controlled + if(typeName.equals(ContentModel.ASPECT_HIDDEN)) + { + if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) + { + // manipulate hidden aspect only if client controlled + nodeService.removeAspect(nodeRef, typeName); + } + + + +// if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) +// { +// nodeService.removeAspect(nodeRef, typeName); +// } + } + else + { + nodeService.removeAspect(nodeRef, typeName); + } + } + + for (String aspect : aspectsToAdd) + { + aspectType = aspect; + + TypeDefinitionWrapper type = getType(aspect); + if (type == null) + { + throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType); + } + + QName typeName = type.getAlfrescoName(); + // if aspect is hidden aspect, remove only if hidden node is not client controlled + if(typeName.equals(ContentModel.ASPECT_HIDDEN)) + { + if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) + { + // manipulate hidden aspect only if client controlled + nodeService.addAspect(nodeRef, type.getAlfrescoName(), + Collections. emptyMap()); + } + +// if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) +// { +// nodeService.addAspect(nodeRef, type.getAlfrescoName(), +// Collections. emptyMap()); +// } + } + else + { + nodeService.addAspect(nodeRef, type.getAlfrescoName(), + Collections. emptyMap()); + } + } + } + catch (InvalidAspectException e) + { + throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType); + } + catch (InvalidNodeRefException e) + { + throw new CmisInvalidArgumentException("Invalid node: " + nodeRef); + } + + // set property + for (Map.Entry> property : aspectProperties.entrySet()) + { + QName propertyQName = property.getKey(); + + if (property.getValue().isEmpty()) + { + if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey())) + { + if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) + { + // manipulate hidden aspect property only if client controlled + nodeService.removeProperty(nodeRef, propertyQName); + } + } + else + { + nodeService.removeProperty(nodeRef, property.getKey()); + } + } + else + { + if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey())) + { + if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) + { + // manipulate hidden aspect property only if client controlled + nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property + .getValue().get(0) : (Serializable) property.getValue()); + } + } + else + { + Serializable value = (Serializable)property.getValue(); + nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property + .getValue().get(0) : value); + } + } + } + } + + /** + * Sets a property value. + */ + public void setProperty(NodeRef nodeRef, TypeDefinitionWrapper type, String propertyId, Serializable value) + { + if (propertyId == null) + { + throw new CmisInvalidArgumentException("Cannot process not null property!"); + } + + PropertyDefinitionWrapper propDef = type.getPropertyById(propertyId); + if (propDef == null) + { + throw new CmisInvalidArgumentException("Property " + propertyId + " is unknown!"); + } + + Updatability updatability = propDef.getPropertyDefinition().getUpdatability(); + if ((updatability == Updatability.READONLY) + || (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef))) + { + throw new CmisInvalidArgumentException("Property " + propertyId + " is read-only!"); + } + + if(propDef.getPropertyId().equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) + { + throw new IllegalArgumentException("Cannot process " + PropertyIds.SECONDARY_OBJECT_TYPE_IDS + " in setProperty"); + } + else + { + QName propertyQName = propDef.getPropertyAccessor().getMappedProperty(); + if (propertyQName == null) + { + throw new CmisConstraintException("Unable to set property " + propertyId + "!"); + } + + if (propertyId.equals(PropertyIds.NAME)) + { + if (!(value instanceof String)) + { + throw new CmisInvalidArgumentException("Object name must be a string!"); + } + + try + { + fileFolderService.rename(nodeRef, value.toString()); + } + catch (FileExistsException e) + { + throw new CmisContentAlreadyExistsException("An object with this name already exists!", e); + } + catch (FileNotFoundException e) + { + throw new CmisInvalidArgumentException("Object with id " + nodeRef.getId() + " not found!"); + } + } + else + { + // overflow check + if (propDef.getPropertyDefinition().getPropertyType() == PropertyType.INTEGER && value instanceof BigInteger) + { + org.alfresco.service.cmr.dictionary.PropertyDefinition def = dictionaryService.getProperty(propertyQName); + QName dataDef = def.getDataType().getName(); + BigInteger bigValue = (BigInteger) value; + + if ((bigValue.compareTo(maxInt) > 0 || bigValue.compareTo(minInt) < 0) && dataDef.equals(DataTypeDefinition.INT)) + { + throw new CmisConstraintException("Value is out of range for property " + propertyQName.getLocalName()); + } + + if ((bigValue.compareTo(maxLong) > 0 || bigValue.compareTo(minLong) < 0) && dataDef.equals(DataTypeDefinition.LONG)) + { + throw new CmisConstraintException("Value is out of range for property " + propertyQName.getLocalName()); + } + } + + nodeService.setProperty(nodeRef, propertyQName, value); + } + } + } + + private Serializable getValue(PropertyData property, boolean isMultiValue) + { + if ((property.getValues() == null) || (property.getValues().isEmpty())) + { + return null; + } + + if (isMultiValue) + { + return (Serializable) property.getValues(); + } + + return (Serializable) property.getValues().get(0); + } + + /** + * Returns content changes. + */ + public ObjectList getContentChanges(Holder changeLogToken, BigInteger maxItems) + { + final ObjectListImpl result = new ObjectListImpl(); + result.setObjects(new ArrayList()); + + EntryIdCallback changeLogCollectingCallback = new EntryIdCallback(true) + { + @Override + public boolean handleAuditEntry(Long entryId, String user, long time, Map values) + { + result.getObjects().addAll(createChangeEvents(time, values)); + return super.handleAuditEntry(entryId, user, time, values); + } + }; + + Long from = null; + if ((changeLogToken != null) && (changeLogToken.getValue() != null)) + { + try + { + from = Long.parseLong(changeLogToken.getValue()); + } + catch (NumberFormatException e) + { + throw new CmisInvalidArgumentException("Invalid change log token: " + changeLogToken); + } + } + + AuditQueryParameters params = new AuditQueryParameters(); + params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION); + params.setForward(true); + params.setFromId(from); + + int maxResults = (maxItems == null ? 0 : maxItems.intValue()); + maxResults = (maxResults < 1 ? 0 : maxResults + 1); + + auditService.auditQuery(changeLogCollectingCallback, params, maxResults); + + String newChangeLogToken = null; + if (maxResults > 0) + { + if (result.getObjects().size() >= maxResults) + { + StringBuilder clt = new StringBuilder(); + newChangeLogToken = (from == null ? clt.append(maxItems.intValue() + 1).toString() : clt.append(from.longValue() + maxItems.intValue()).toString()); + result.getObjects().remove(result.getObjects().size() - 1).getId(); + result.setHasMoreItems(true); + } + else + { + result.setHasMoreItems(false); + } + } + + if (changeLogToken != null) + { + changeLogToken.setValue(newChangeLogToken); + } + + return result; + } + + @SuppressWarnings("unchecked") + private List createChangeEvents(long time, Map values) + { + List result = new ArrayList(); + + if ((values == null) || (values.size() == 0)) + { + return result; + } + + GregorianCalendar changeTime = new GregorianCalendar(); + changeTime.setTimeInMillis(time); + + String appPath = "/" + CMIS_CHANGELOG_AUDIT_APPLICATION + "/"; + + for (Entry entry : values.entrySet()) + { + if ((entry.getKey() == null) || (!(entry.getValue() instanceof Map))) + { + continue; + } + + String path = entry.getKey(); + if (!path.startsWith(appPath)) + { + continue; + } + + ChangeType changeType = null; + String changePath = path.substring(appPath.length()).toLowerCase(); + for (ChangeType c : ChangeType.values()) + { + if (changePath.startsWith(c.value().toLowerCase())) + { + changeType = c; + break; + } + } + + if (changeType == null) + { + continue; + } + + Map valueMap = (Map) entry.getValue(); + String objectId = (String) valueMap.get(CMISChangeLogDataExtractor.KEY_OBJECT_ID); + + // build object + ObjectDataImpl object = new ObjectDataImpl(); + result.add(object); + + PropertiesImpl properties = new PropertiesImpl(); + object.setProperties(properties); + PropertyIdImpl objectIdProperty = new PropertyIdImpl(PropertyIds.OBJECT_ID, objectId); + properties.addProperty(objectIdProperty); + + ChangeEventInfoDataImpl changeEvent = new ChangeEventInfoDataImpl(); + object.setChangeEventInfo(changeEvent); + changeEvent.setChangeType(changeType); + changeEvent.setChangeTime(changeTime); + } + + return result; + } + + private class EntryIdCallback implements AuditQueryCallback + { + private final boolean valuesRequired; + private Long entryId; + + public EntryIdCallback(boolean valuesRequired) + { + this.valuesRequired = valuesRequired; + } + + public String getEntryId() + { + return entryId == null ? null : entryId.toString(); + } + + public boolean valuesRequired() + { + return this.valuesRequired; + } + + public final boolean handleAuditEntry(Long entryId, String applicationName, String user, long time, + Map values) + { + if (applicationName.equals(CMIS_CHANGELOG_AUDIT_APPLICATION)) + { + return handleAuditEntry(entryId, user, time, values); + } + return true; + } + + public boolean handleAuditEntry(Long entryId, String user, long time, Map values) + { + this.entryId = entryId; + return true; + } + + public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) + { + throw new CmisRuntimeException("Audit entry " + entryId + ": " + errorMsg, error); + } + }; + + // -------------------------------------------------------------- + // OpenCMIS methods + // -------------------------------------------------------------- + + /** + * Returns the value of the given property if it exists and is of the + * correct type. + */ + public String getStringProperty(Properties properties, String propertyId) + { + if ((properties == null) || (properties.getProperties() == null)) + { + return null; + } + + PropertyData property = properties.getProperties().get(propertyId); + if (!(property instanceof PropertyString)) + { + return null; + } + + return ((PropertyString) property).getFirstValue(); + } + + /** + * Returns the value of the given property if it exists and is of the + * correct type. + */ + public String getIdProperty(Properties properties, String propertyId) + { + if ((properties == null) || (properties.getProperties() == null)) + { + return null; + } + + PropertyData property = properties.getProperties().get(propertyId); + if (!(property instanceof PropertyId)) + { + return null; + } + + return ((PropertyId) property).getFirstValue(); + } + + public String getNameProperty(Properties properties, String fallback) + { + String name = getStringProperty(properties, PropertyIds.NAME); + if ((name == null) || (name.trim().length() == 0)) + { + if (fallback == null) + { + throw new CmisInvalidArgumentException("Property " + PropertyIds.NAME + " must be set!"); + } + else + { + name = fallback; + } + } + + return name; + } + + public String getObjectTypeIdProperty(Properties properties) + { + String objectTypeId = getIdProperty(properties, PropertyIds.OBJECT_TYPE_ID); + if ((objectTypeId == null) || (objectTypeId.trim().length() == 0)) + { + throw new CmisInvalidArgumentException("Property " + PropertyIds.OBJECT_TYPE_ID + " must be set!"); + } + + return objectTypeId; + } + + public String getSourceIdProperty(Properties properties) + { + String id = getIdProperty(properties, PropertyIds.SOURCE_ID); + if ((id == null) || (id.trim().length() == 0)) + { + throw new CmisInvalidArgumentException("Property " + PropertyIds.SOURCE_ID + " must be set!"); + } + + return id; + } + + public String getTargetIdProperty(Properties properties) + { + String id = getIdProperty(properties, PropertyIds.TARGET_ID); + if ((id == null) || (id.trim().length() == 0)) + { + throw new CmisInvalidArgumentException("Property " + PropertyIds.TARGET_ID + " must be set!"); + } + + return id; + } + + /** + * Returns the repository info object. + */ + public RepositoryInfo getRepositoryInfo(CmisVersion cmisVersion) + { + return createRepositoryInfo(cmisVersion); + } + + /** + * Returns the repository id. + */ + public String getRepositoryId() + { + return descriptorService.getCurrentRepositoryDescriptor().getId(); + } + + /** + * Creates the repository info object. + */ + private RepositoryInfo createRepositoryInfo(CmisVersion cmisVersion) + { + Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor(); + + // get change token + boolean auditEnabled = auditService.isAuditEnabled(CMIS_CHANGELOG_AUDIT_APPLICATION, "/" + + CMIS_CHANGELOG_AUDIT_APPLICATION); + String latestChangeLogToken = null; + + if (auditEnabled) + { + EntryIdCallback auditQueryCallback = new EntryIdCallback(false); + AuditQueryParameters params = new AuditQueryParameters(); + params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION); + params.setForward(false); + auditService.auditQuery(auditQueryCallback, params, 1); + String entryId = auditQueryCallback.getEntryId(); + // MNT-13529 + // add initial change log token + latestChangeLogToken = entryId == null ? "0" : entryId; + } + + // compile repository info + RepositoryInfoImpl ri = new RepositoryInfoImpl(); + + ri.setId(currentDescriptor.getId()); + ri.setName(currentDescriptor.getName()); + ri.setDescription(currentDescriptor.getName()); + ri.setVendorName("Alfresco"); + ri.setProductName("Alfresco " + descriptorService.getServerDescriptor().getEdition()); + ri.setProductVersion(currentDescriptor.getVersion()); + NodeRef rootNodeRef = getRootNodeRef(); + ri.setRootFolder(constructObjectId(rootNodeRef, null)); + ri.setCmisVersion(cmisVersion); + + ri.setChangesIncomplete(true); + ri.setChangesOnType(Arrays.asList(new BaseTypeId[] { BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER })); + ri.setLatestChangeLogToken(latestChangeLogToken); + ri.setPrincipalAnonymous(AuthenticationUtil.getGuestUserName()); + ri.setPrincipalAnyone(PermissionService.ALL_AUTHORITIES); + + RepositoryCapabilitiesImpl repCap = new RepositoryCapabilitiesImpl(); + ri.setCapabilities(repCap); + + repCap.setAllVersionsSearchable(false); + repCap.setCapabilityAcl(CapabilityAcl.MANAGE); + repCap.setCapabilityChanges(auditEnabled ? CapabilityChanges.OBJECTIDSONLY : CapabilityChanges.NONE); + repCap.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.ANYTIME); + repCap.setCapabilityJoin(CapabilityJoin.NONE); + repCap.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED); + repCap.setCapabilityRendition(CapabilityRenditions.READ); + repCap.setIsPwcSearchable(false); + repCap.setIsPwcUpdatable(true); + repCap.setSupportsGetDescendants(true); + repCap.setSupportsGetFolderTree(true); + repCap.setSupportsMultifiling(true); + repCap.setSupportsUnfiling(false); + repCap.setSupportsVersionSpecificFiling(false); + + AclCapabilitiesDataImpl aclCap = new AclCapabilitiesDataImpl(); + ri.setAclCapabilities(aclCap); + + aclCap.setAclPropagation(AclPropagation.PROPAGATE); + aclCap.setSupportedPermissions(SupportedPermissions.BOTH); + aclCap.setPermissionDefinitionData(repositoryPermissions); + aclCap.setPermissionMappingData(permissionMappings); + + return ri; + } + + private List getRepositoryPermissions() + { + ArrayList result = new ArrayList(); + + Set all = permissionModelDao.getAllPermissions(); + for (PermissionReference pr : all) + { + result.add(createPermissionDefinition(pr)); + } + + PermissionReference allPermission = permissionModelDao.getPermissionReference(null, + PermissionService.ALL_PERMISSIONS); + result.add(createPermissionDefinition(allPermission)); + + PermissionDefinitionDataImpl cmisPermission; + + cmisPermission = new PermissionDefinitionDataImpl(); + cmisPermission.setId(BasicPermissions.READ); + cmisPermission.setDescription("CMIS Read"); + result.add(cmisPermission); + + cmisPermission = new PermissionDefinitionDataImpl(); + cmisPermission.setId(BasicPermissions.WRITE); + cmisPermission.setDescription("CMIS Write"); + result.add(cmisPermission); + + cmisPermission = new PermissionDefinitionDataImpl(); + cmisPermission.setId(BasicPermissions.ALL); + cmisPermission.setDescription("CMIS All"); + result.add(cmisPermission); + + return result; + } + + private PermissionDefinition createPermissionDefinition(PermissionReference pr) + { + PermissionDefinitionDataImpl permission = new PermissionDefinitionDataImpl(); + permission.setId(pr.getQName().toString() + "." + pr.getName()); + permission.setDescription(permission.getId()); + + return permission; + } + + private Map getPermissionMappings() + { + Map result = new HashMap(); + + for (CMISAllowedActionEnum e : EnumSet.allOf(CMISAllowedActionEnum.class)) + { + for (Map.Entry> m : e.getPermissionMapping().entrySet()) + { + PermissionMappingDataImpl mapping = new PermissionMappingDataImpl(); + mapping.setKey(m.getKey()); + mapping.setPermissions(m.getValue()); + + result.put(mapping.getKey(), mapping); + } + } + + return result; + } + + private CMISRenditionMapping getRenditionMapping() + { + CMISRenditionMapping renditionMapping = (CMISRenditionMapping)singletonCache.get(KEY_CMIS_RENDITION_MAPPING_NODEREF); + if (renditionMapping == null) + { + renditionMapping = new CMISRenditionMapping(nodeService, contentService, renditionService, + transactionService, kindToRenditionNames); + + singletonCache.put(KEY_CMIS_RENDITION_MAPPING_NODEREF, renditionMapping); + } + return renditionMapping; + } +} diff --git a/source/java/org/alfresco/opencmis/CMISLifecycleBean.java b/source/java/org/alfresco/opencmis/CMISLifecycleBean.java index 0c26484900..91d9b845d5 100644 --- a/source/java/org/alfresco/opencmis/CMISLifecycleBean.java +++ b/source/java/org/alfresco/opencmis/CMISLifecycleBean.java @@ -1,52 +1,52 @@ -package org.alfresco.opencmis; - -import java.util.HashMap; - -import javax.servlet.ServletContext; - -import org.apache.chemistry.opencmis.commons.server.CmisServiceFactory; -import org.apache.chemistry.opencmis.server.impl.CmisRepositoryContextListener; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.web.context.ServletContextAware; - -/** - * This bean controls the lifecycle of the CMIS factory. - * - * @author florian.mueller - */ -public class CMISLifecycleBean implements ServletContextAware, InitializingBean, DisposableBean -{ - private ServletContext servletContext; - private CmisServiceFactory factory; - - @Override - public void setServletContext(ServletContext servletContext) - { - this.servletContext = servletContext; - } - - public void setCmisServiceFactory(CmisServiceFactory factory) - { - this.factory = factory; - } - - @Override - public void afterPropertiesSet() throws Exception - { - if (factory != null && servletContext != null) - { - factory.init(new HashMap()); - servletContext.setAttribute(CmisRepositoryContextListener.SERVICES_FACTORY, factory); - } - } - - @Override - public void destroy() throws Exception - { - if (factory != null) - { - factory.destroy(); - } - } -} +package org.alfresco.opencmis; + +import java.util.HashMap; + +import javax.servlet.ServletContext; + +import org.apache.chemistry.opencmis.commons.server.CmisServiceFactory; +import org.apache.chemistry.opencmis.server.impl.CmisRepositoryContextListener; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.context.ServletContextAware; + +/** + * This bean controls the lifecycle of the CMIS factory. + * + * @author florian.mueller + */ +public class CMISLifecycleBean implements ServletContextAware, InitializingBean, DisposableBean +{ + private ServletContext servletContext; + private CmisServiceFactory factory; + + @Override + public void setServletContext(ServletContext servletContext) + { + this.servletContext = servletContext; + } + + public void setCmisServiceFactory(CmisServiceFactory factory) + { + this.factory = factory; + } + + @Override + public void afterPropertiesSet() throws Exception + { + if (factory != null && servletContext != null) + { + factory.init(new HashMap()); + servletContext.setAttribute(CmisRepositoryContextListener.SERVICES_FACTORY, factory); + } + } + + @Override + public void destroy() throws Exception + { + if (factory != null) + { + factory.destroy(); + } + } +} diff --git a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java index db7c960dae..05ce054c0e 100644 --- a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java +++ b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java @@ -1,1105 +1,1105 @@ -package org.alfresco.opencmis; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.opencmis.dictionary.CMISObjectVariant; -import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper; -import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; -import org.alfresco.opencmis.dictionary.ItemTypeDefinitionWrapper; -import org.alfresco.opencmis.dictionary.RelationshipTypeDefintionWrapper; -import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.repo.version.Version2Model; -import org.alfresco.repo.version.VersionBaseModel; -import org.alfresco.repo.version.VersionModel; -import org.alfresco.repo.version.common.VersionUtil; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.Path; -import org.alfresco.service.cmr.repository.Path.ChildAssocElement; -import org.alfresco.service.cmr.version.Version; -import org.alfresco.service.cmr.version.VersionDoesNotExistException; -import org.alfresco.service.cmr.version.VersionHistory; -import org.alfresco.service.cmr.version.VersionType; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException; - -/** - * CMIS representation of a node. - * - * Tries to avoid getting the node's version history where possible (because it's not very performant). - * - */ -public class CMISNodeInfoImpl implements CMISNodeInfo -{ - private static final GregorianCalendar DUMMY_DATE = new GregorianCalendar(2010, 4, 1); - - private CMISConnector connector; - private String objectId; // CMIS object id - private String currentObjectId; // CMIS object id of the latest version - private String currentNodeId; // node ref id of the latest version - private CMISObjectVariant objecVariant; // object variant - private NodeRef nodeRef; // object node ref - private String versionLabel; // version label - private AssociationRef associationRef; // association ref - private TypeDefinitionWrapper type; // CMIS type - private String name; - private boolean hasPWC; - private Boolean isRootFolder; - - private String cmisPath; - private VersionHistory versionHistory; - private Version version; - private Boolean isLatestMajorVersion; - private Map properties; - private List parents; - - private Map nodeProps; // for nodeRef - private Set nodeAspects; // for nodeRef - - public CMISNodeInfoImpl() - { - } - - public CMISNodeInfoImpl(CMISConnector connector, String objectId) - { - this.connector = connector; - this.objectId = connector.constructObjectId(objectId); - - analyseObjectId(); - } - - public CMISNodeInfoImpl(CMISConnector connector, NodeRef nodeRef, QName nodeType, Map nodeProps, VersionHistory versionHistory, boolean checkExists) - { - this.connector = connector; - this.nodeRef = nodeRef; - this.versionHistory = versionHistory; - - if (nodeType != null) - { - determineType(nodeType); - } - - this.nodeProps = nodeProps; - - analyseNodeRef(checkExists); - } - - public CMISNodeInfoImpl(CMISConnector connector, NodeRef nodeRef) - { - this.connector = connector; - this.nodeRef = nodeRef; - - analyseNodeRef(true); - } - - public CMISNodeInfoImpl(CMISConnector connector, AssociationRef associationRef) - { - this.connector = connector; - this.associationRef = associationRef; - - analyseAssociationRef(); - } - - private boolean isCurrentNode() - { - return objecVariant != CMISObjectVariant.VERSION; - } - - protected void analyseVersionNode() - { - // check version - versionHistory = getVersionHistory(); - if (versionHistory == null) - { - objecVariant = CMISObjectVariant.CURRENT_VERSION; - objectId = connector.constructObjectId(nodeRef, CMISConnector.UNVERSIONED_VERSION_LABEL); - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; - currentObjectId = objectId; - hasPWC = isNodeCheckedOut(); - } - else - { - versionLabel = (String)getNodeProps().get(ContentModel.PROP_VERSION_LABEL); - - Version headVersion = versionHistory.getHeadVersion(); - - objectId = connector.constructObjectId(headVersion.getVersionedNodeRef(), versionLabel); - currentObjectId = connector.constructObjectId(headVersion.getVersionedNodeRef(), headVersion.getVersionLabel()); - currentNodeId = headVersion.getVersionedNodeRef().toString(); - - objecVariant = (headVersion.getVersionLabel().equals(versionLabel) ? CMISObjectVariant.CURRENT_VERSION - : CMISObjectVariant.VERSION); - hasPWC = connector.getCheckOutCheckInService().isCheckedOut(headVersion.getVersionedNodeRef()); - } - } - - protected void analyseCurrentVersion() - { - if (isNodeVersioned(nodeRef)) - { - versionLabel = (String) connector.getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - if(versionLabel == null) - { - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; - } - objectId = connector.constructObjectId(nodeRef, versionLabel); - currentObjectId = objectId; - currentNodeId = nodeRef.toString(); - objecVariant = CMISObjectVariant.CURRENT_VERSION; - hasPWC = isNodeCheckedOut(); - } - else - { - setUnversioned(); - } - } - - protected void setUnversioned() - { - objecVariant = CMISObjectVariant.CURRENT_VERSION; - objectId = connector.constructObjectId(nodeRef, CMISConnector.UNVERSIONED_VERSION_LABEL); - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; - currentObjectId = objectId; - hasPWC = isNodeCheckedOut(); - } - - protected void analyseObjectId() - { - currentNodeId = objectId; - currentObjectId = objectId; - versionLabel = null; - nodeRef = null; - hasPWC = false; - - if (objectId == null) - { - objecVariant = CMISObjectVariant.INVALID_ID; - return; - } - - try - { - // is it a version? - int sepIndex = objectId.lastIndexOf(CMISConnector.ID_SEPERATOR); - if (sepIndex > -1) - { - currentNodeId = objectId.substring(0, sepIndex); - versionLabel = objectId.substring(sepIndex + 1); - } - - if (objectId.startsWith(CMISConnector.ASSOC_ID_PREFIX)) - { - // check the association id - Long assocId = null; - try - { - assocId = new Long(objectId.substring(CMISConnector.ASSOC_ID_PREFIX.length())); - } catch (NumberFormatException nfe) - { - objecVariant = CMISObjectVariant.INVALID_ID; - return; - } - - // check the association - associationRef = connector.getNodeService().getAssoc(assocId); - if (associationRef == null) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } else - { - objecVariant = CMISObjectVariant.ASSOC; - } - } - else - { - if(NodeRef.isNodeRef(objectId)) - { - NodeRef tmpNodeRef = new NodeRef(objectId); - objectId = connector.constructObjectId(tmpNodeRef, null); - } - - if(!NodeRef.isNodeRef(currentNodeId)) - { - currentNodeId = connector.getRootStoreRef() + "/" + currentNodeId; - } - - // nodeRef is a "live" node, the version label identifies the specific version of the node - nodeRef = new NodeRef(currentNodeId); - - // check for existence - if (!connector.getNodeService().exists(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - return; - } - - // check PWC - if (isNodeWorkingCopy()) - { - NodeRef checkedOut = connector.getCheckOutCheckInService().getCheckedOut(nodeRef); - if(connector.filter(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - else - { - objecVariant = CMISObjectVariant.PWC; - } - currentObjectId = connector.createObjectId(checkedOut); - currentNodeId = checkedOut.toString(); - versionLabel = CMISConnector.PWC_VERSION_LABEL; - hasPWC = true; - return; - } - - if (isFolder()) - { - // folders can't be versioned, so no need to check - if(connector.filter(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - else - { - objecVariant = CMISObjectVariant.FOLDER; - } - return; - } - - if(isItem()) - { - objecVariant = CMISObjectVariant.ITEM; - return; - } - - if (versionLabel == null) - { - if (isDocument()) - { - // for a document, absence of a version label implies the current (head) version - if(connector.filter(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - else - { - objecVariant = CMISObjectVariant.CURRENT_VERSION; - } - - // Is it un-versioned, or currently versioned? - Version currentVersion = connector.getVersionService().getCurrentVersion(nodeRef); - if (currentVersion != null) - { - versionLabel = currentVersion.getVersionLabel(); - versionHistory = connector.getVersionService().getVersionHistory(nodeRef); - } - else - { - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; - } - - objectId = connector.constructObjectId(objectId, versionLabel); - currentObjectId = objectId; - hasPWC = isNodeCheckedOut(); - } - else - { - objecVariant = CMISObjectVariant.NOT_A_CMIS_OBJECT; - } - return; - } - - // check if it has PWC label - if (versionLabel.equals(CMISConnector.PWC_VERSION_LABEL)) - { - NodeRef pwcNodeRef = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); - if (pwcNodeRef == null) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - return; - } - else if(connector.filter(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - else - { - objecVariant = CMISObjectVariant.PWC; - } - currentObjectId = connector.createObjectId(nodeRef); - currentNodeId = nodeRef.toString(); - hasPWC = true; - nodeRef = pwcNodeRef; - return; - } - - // check version - if(! isNodeVersioned(nodeRef)) - { - // the node isn't versioned - if(connector.filter(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - else if (versionLabel.equals(CMISConnector.UNVERSIONED_VERSION_LABEL)) - { - objecVariant = CMISObjectVariant.CURRENT_VERSION; - } else - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - } - else - { - // the node is versioned, determine whether the versionLabel refers to the head version or a - // specific non-head version - String headVersionLabel = (String)connector.getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - currentObjectId = connector.constructObjectId(currentNodeId, headVersionLabel); - - if (versionLabel.equals(headVersionLabel)) - { - // the version label refers to the current head version - objecVariant = CMISObjectVariant.CURRENT_VERSION; - } - else - { - // the version label refers to a specific non-head version, find the nodeRef - // of the version node from the version history - versionHistory = connector.getVersionService().getVersionHistory(nodeRef); - if (versionHistory == null) - { - // unexpected null versionHistory, assume not versioned - if (versionLabel.equals(CMISConnector.UNVERSIONED_VERSION_LABEL)) - { - objecVariant = CMISObjectVariant.CURRENT_VERSION; - - } - else - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - } - else - { - try - { - version = versionHistory.getVersion(versionLabel); - nodeRef = version.getFrozenStateNodeRef(); - objecVariant = CMISObjectVariant.VERSION; - } - catch (VersionDoesNotExistException e) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - } - } - } - - // check if current node(not specified version) checked out - hasPWC = connector.getCheckOutCheckInService().isCheckedOut(getCurrentNodeNodeRef()); - } - } - catch (AccessDeniedException e) - { - objecVariant = CMISObjectVariant.PERMISSION_DENIED; - } - // TODO: Somewhere this has not been wrapped correctly - catch (net.sf.acegisecurity.AccessDeniedException e) - { - objecVariant = CMISObjectVariant.PERMISSION_DENIED; - } - } - - protected void analyseNodeRef(boolean checkExists) - { - objectId = null; - currentNodeId = nodeRef.toString(); - currentObjectId = null; - versionLabel = null; - hasPWC = false; - - // check for existence - if (checkExists && (!connector.getNodeService().exists(nodeRef))) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - return; - } - - if (connector.filter(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - return; - } - - if (isFolder()) - { - objecVariant = CMISObjectVariant.FOLDER; - objectId = connector.constructObjectId(nodeRef, null); - currentObjectId = objectId; - return; - } - else if (isItem()) - { - objecVariant = CMISObjectVariant.ITEM; - objectId = connector.constructObjectId(nodeRef, null); - currentObjectId = objectId; - return; - } - else if (getType() == null) - { - objecVariant = CMISObjectVariant.NOT_A_CMIS_OBJECT; - return; - } - - // check PWC - if (isNodeWorkingCopy()) - { - NodeRef checkedOut = connector.getCheckOutCheckInService().getCheckedOut(nodeRef); - if (checkedOut == null) - { - // catch a rare audit case - checkedOut = nodeRef; - } - - objecVariant = CMISObjectVariant.PWC; - - objectId = connector.constructObjectId(checkedOut, CMISConnector.PWC_VERSION_LABEL); - - versionLabel = CMISConnector.PWC_VERSION_LABEL; - currentObjectId = connector.createObjectId(checkedOut); - currentNodeId = checkedOut.toString(); - hasPWC = true; - return; - } - - // check version - if (isNodeAVersion(nodeRef)) - { - analyseVersionNode(); - } - else - { - analyseCurrentVersion(); - } - } - - private boolean isNodeWorkingCopy() - { - return getNodeAspects().contains(ContentModel.ASPECT_WORKING_COPY); - } - - private boolean isNodeCheckedOut() - { - return getNodeAspects().contains(ContentModel.ASPECT_CHECKED_OUT); - } - - private boolean isNodeAVersion(NodeRef nodeRef) - { - if(nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) - { - NodeRef realNodeRef = VersionUtil.convertNodeRef(nodeRef); - return connector.getVersionService().isAVersion(realNodeRef); - } - return getNodeAspects().contains(Version2Model.ASPECT_VERSION); - } - - private boolean isNodeVersioned(NodeRef nodeRef) - { - if(nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) - { - NodeRef realNodeRef = VersionUtil.convertNodeRef(nodeRef); - return connector.getVersionService().isVersioned(realNodeRef); - } - return getNodeAspects().contains(ContentModel.ASPECT_VERSIONABLE); - } - - protected void analyseAssociationRef() - { - objectId = null; - currentNodeId = null; - currentObjectId = null; - versionLabel = null; - hasPWC = false; - - if (associationRef == null) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - return; - } - - objecVariant = CMISObjectVariant.ASSOC; - objectId = CMISConnector.ASSOC_ID_PREFIX + associationRef.getId(); - } - - private void determineType(QName nodeType) - { - type = null; - - if((objecVariant == CMISObjectVariant.INVALID_ID) || (objecVariant == CMISObjectVariant.NOT_A_CMIS_OBJECT) || (objecVariant == CMISObjectVariant.NOT_EXISTING) || (objecVariant == CMISObjectVariant.PERMISSION_DENIED)) - { - return; - } - - if (nodeRef != null) - { - QName typeQName = (nodeType != null ? nodeType : connector.getNodeService().getType(nodeRef)); - type = connector.getOpenCMISDictionaryService().findNodeType(typeQName); - } else if (associationRef != null) - { - QName typeQName = associationRef.getTypeQName(); - type = connector.getOpenCMISDictionaryService().findAssocType(typeQName); - } - } - - public String getObjectId() - { - return objectId; - } - - public CMISObjectVariant getObjectVariant() - { - return objecVariant; - } - - public boolean isVariant(CMISObjectVariant var) - { - return objecVariant == var; - } - - public NodeRef getNodeRef() - { - return nodeRef; - } - - public String getCurrentNodeId() - { - return currentNodeId; - } - - public NodeRef getCurrentNodeNodeRef() - { - return new NodeRef(currentNodeId); - } - - public String getCurrentObjectId() - { - return currentObjectId; - } - - public boolean isCurrentVersion() - { - return objecVariant == CMISObjectVariant.CURRENT_VERSION; - } - - public boolean isPWC() - { - return objecVariant == CMISObjectVariant.PWC; - } - - public boolean hasPWC() - { - return hasPWC; - } - - public boolean isVersion() - { - return objecVariant == CMISObjectVariant.VERSION; - } - - public boolean isLatestVersion() - { - return isCurrentVersion(); - } - - public boolean isLatestMajorVersion() - { - if (isLatestMajorVersion == null) - { - isLatestMajorVersion = Boolean.FALSE; - if (!isPWC()) - { - // MNT-10223. It should not be a version and not a minor version - Version version = getVersion(); - if (isCurrentNode() && version != null && version.getVersionType() == VersionType.MAJOR) - { - isLatestMajorVersion = Boolean.TRUE; - } - else - { - VersionHistory versionHistory = getVersionHistory(); - if (versionHistory == null) - { - isLatestMajorVersion = Boolean.TRUE; - } else - { - Version currentVersion = versionHistory.getHeadVersion(); - while (currentVersion != null) - { - if (currentVersion.getVersionType() == VersionType.MAJOR) - { - // ALF-11116: the current node (in the main store) and the frozen node (in the version store) are both represented as CMISNodeInfos - // but are indistinguishable apart from their storeRef (their objectVariant can be the same). - if (nodeRef.getStoreRef().getIdentifier().equals(Version2Model.STORE_ID) || nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) - { - isLatestMajorVersion = currentVersion.getFrozenStateNodeRef().equals(nodeRef); - } - break; - } - currentVersion = versionHistory.getPredecessor(currentVersion); - } - } - } - } - } - - return isLatestMajorVersion.booleanValue(); - } - - public boolean isMajorVersion() - { - if (isPWC()) - { - return false; - } - if (CMISConnector.UNVERSIONED_VERSION_LABEL.equals(versionLabel)) - { - return true; - } - - Version version = getVersion(); - if (version == null) - { - return true; - } - - return version.getVersionType() == VersionType.MAJOR; - } - - public String getVersionLabel() - { - return versionLabel; - } - - public String getCheckinComment() - { - if (!isDocument() || isPWC()) - { - return null; - } - - Version version = getVersion(); - if (version != null) - { - return getVersion().getDescription(); - } - - return null; - } - - public AssociationRef getAssociationRef() - { - return associationRef; - } - - public TypeDefinitionWrapper getType() - { - if (type == null) - { - determineType(null); - } - - return type; - } - - public boolean isFolder() - { - return getType() instanceof FolderTypeDefintionWrapper; - } - - public boolean isItem() - { - return getType() instanceof ItemTypeDefinitionWrapper; - } - - public boolean isRootFolder() - { - if (isRootFolder == null) - { - isRootFolder = isFolder() && connector.getRootNodeRef().equals(nodeRef); - } - - return isRootFolder.booleanValue(); - } - - public boolean isDocument() - { - return getType() instanceof DocumentTypeDefinitionWrapper; - } - - public boolean isRelationship() - { - return getType() instanceof RelationshipTypeDefintionWrapper; - } - - public String getName() - { - if((objecVariant == CMISObjectVariant.INVALID_ID) || (objecVariant == CMISObjectVariant.NOT_A_CMIS_OBJECT) || (objecVariant == CMISObjectVariant.NOT_EXISTING) || (objecVariant == CMISObjectVariant.PERMISSION_DENIED)) - { - return null; - } - - if (name == null) - { - if (isRelationship()) - { - name = associationRef.toString(); - } else - { - Object nameObj = getNodeProps().get(ContentModel.PROP_NAME); - name = (nameObj instanceof String ? (String) nameObj : ""); - } - } - - return name; - } - - public String getPath() - { - if((objecVariant == CMISObjectVariant.INVALID_ID) || (objecVariant == CMISObjectVariant.NOT_A_CMIS_OBJECT) || (objecVariant == CMISObjectVariant.NOT_EXISTING) || (objecVariant == CMISObjectVariant.PERMISSION_DENIED)) - { - return null; - } - - if (cmisPath == null) - { - StringBuilder displayPath = new StringBuilder(64); - - Path path = connector.getNodeService().getPath(nodeRef); - NodeRef rootNode = connector.getRootNodeRef(); - int i = 0; - while (i < path.size()) - { - Path.Element element = path.get(i); - if (element instanceof ChildAssocElement) - { - ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); - NodeRef node = assocRef.getChildRef(); - if (node.equals(rootNode)) - { - break; - } - } - i++; - } - - if (i == path.size()) - { - // TODO: - // throw new AlfrescoRuntimeException("Path " + path + - // " not in CMIS root node scope"); - } - - if (path.size() - i == 1) - { - // render root path - displayPath.append("/"); - } else - { - // render CMIS scoped path - i++; - while (i < path.size() - 1) - { - Path.Element element = path.get(i); - if (element instanceof ChildAssocElement) - { - ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); - NodeRef node = assocRef.getChildRef(); - displayPath.append("/"); - try - { - String propertyName = (String) connector.getNodeService().getProperty(node, ContentModel.PROP_NAME); - displayPath.append(propertyName); - } - catch (AccessDeniedException e) - { - // if the user does not have enough permissions to construct the entire path then the object - // should have a null path - return null; - } - // Somewhere this has not been wrapped correctly - catch (net.sf.acegisecurity.AccessDeniedException e) - { - // if the user does not have enough permissions to construct the entire path then the object - // should have a null path - return null; - } - } - i++; - } - displayPath.append("/"); - displayPath.append(getName()); - } - - cmisPath = displayPath.toString(); - } - - return cmisPath; - } - - public Serializable getCreationDate() - { - // MNT-12680 we should return always creation date of original node - if (isDocument() || isFolder()) - { - return getNodeProps().get(ContentModel.PROP_CREATED); - } - else - { - return DUMMY_DATE; - } - } - - public Serializable getModificationDate() - { - if (isDocument()) - { - if (isCurrentVersion() || isPWC()) - { - return getNodeProps().get(ContentModel.PROP_MODIFIED); - } else - { - return getVersion().getVersionProperty(ContentModel.PROP_MODIFIED.getLocalName()); - } - } else if (isFolder() || isItem()) - { - return getNodeProps().get(ContentModel.PROP_MODIFIED); - } else - { - return DUMMY_DATE; - } - } - - public NodeRef getLatestVersionNodeRef(boolean major) - { - if (!major) - { - return getLatestNonMajorVersionNodeRef(); - } - - VersionHistory versionHistory = getVersionHistory(); - - // if there is no history, return the current version - if (versionHistory == null) - { - // there are no versions - return getLatestNonMajorVersionNodeRef(); - } - - // find the latest major version - for (Version version : versionHistory.getAllVersions()) - { - if (version.getVersionType() == VersionType.MAJOR) - { - return version.getFrozenStateNodeRef(); - } - } - - throw new CmisObjectNotFoundException("There is no major version!"); - } - - private NodeRef getLatestNonMajorVersionNodeRef() - { -// if (isPWC()) -// { -// return nodeRef; -// } else if (hasPWC()) -// { -// return connector.getCheckOutCheckInService().getWorkingCopy(getCurrentNodeNodeRef()); -// } else -// { - return getCurrentNodeNodeRef(); -// } - } - - // TODO lock here?? - public VersionHistory getVersionHistory() - { - if (versionHistory == null && isDocument()) - { - try - { - versionHistory = connector.getVersionService().getVersionHistory(nodeRef); - } catch (Exception e) - { - } - } - - return versionHistory; - } - - public void deleteNode() - { - Version version = getVersion(); - - if (getVersionHistory().getPredecessor(version) == null) - { - connector.getNodeService().deleteNode(nodeRef); - } - else - { - connector.getVersionService().deleteVersion(nodeRef, version); - } - } - - public void deleteVersion() - { - Version version = getVersion(); - connector.getVersionService().deleteVersion(nodeRef, version); - } - - protected Version getVersion() - { - if (version == null && isDocument()) - { - try - { - VersionHistory versionHistory = getVersionHistory(); - if (versionHistory == null) // Avoid unnecessary NPE - { - return null; - } - version = versionHistory.getVersion(versionLabel); - } catch (Exception e) - { - } - } - - return version; - } - - public void checkIfUseful(String what) - { - switch (objecVariant) - { - case INVALID_ID: - throw new CmisInvalidArgumentException(what + " id is invalid: " + objectId); - case NOT_EXISTING: - throw new CmisObjectNotFoundException(what + " not found: " + objectId); - case NOT_A_CMIS_OBJECT: - throw new CmisObjectNotFoundException(what + " is not a CMIS object: " + objectId); - case PERMISSION_DENIED: - throw new CmisPermissionDeniedException("Permission denied!"); - } - } - - public void checkIfFolder(String what) - { - checkIfUseful(what); - if (objecVariant != CMISObjectVariant.FOLDER) - { - throw new CmisInvalidArgumentException(what + " is not a folder!"); - } - } - - @Override - public Serializable getPropertyValue(String id) - { - if (properties == null) - { - return null; - } - - return properties.get(id); - } - - @Override - public boolean containsPropertyValue(String id) - { - if (properties == null) - { - return false; - } - - return properties.containsKey(id); - } - - @Override - public void putPropertyValue(String id, Serializable value) - { - if (properties == null) - { - properties = new HashMap(); - } - - properties.put(id, value); - } - - @Override - public String toString() - { - return getObjectId() + " (" + getNodeRef() + ")"; - } - - @Override - public List getParents() - { - if((objecVariant == CMISObjectVariant.INVALID_ID) || (objecVariant == CMISObjectVariant.NOT_A_CMIS_OBJECT) || (objecVariant == CMISObjectVariant.NOT_EXISTING) || (objecVariant == CMISObjectVariant.PERMISSION_DENIED)) - { - return Collections.emptyList(); - } - - if (parents == null) - { - parents = new ArrayList(); - - NodeRef nodeRefForParent = (isCurrentVersion() ? getCurrentNodeNodeRef() : nodeRef); - - List nodeParents = connector.getNodeService().getParentAssocs(nodeRefForParent, - ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); - if (nodeParents != null) - { - for (ChildAssociationRef parent : nodeParents) - { - if (connector.getType(parent.getParentRef()) instanceof FolderTypeDefintionWrapper) - { - parents.add(new CMISNodeInfoImpl(connector, parent.getParentRef())); - } - } - } - } - - return parents; - } - - public Map getNodeProps() - { - if ((nodeProps == null) && (nodeRef != null)) - { - nodeProps = connector.getNodeService().getProperties(nodeRef); - } - return nodeProps; - } - - public Set getNodeAspects() - { - if ((nodeAspects == null) && (nodeRef != null)) - { - nodeAspects = connector.getNodeService().getAspects(nodeRef); - } - return nodeAspects; - } -} +package org.alfresco.opencmis; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.opencmis.dictionary.CMISObjectVariant; +import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper; +import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; +import org.alfresco.opencmis.dictionary.ItemTypeDefinitionWrapper; +import org.alfresco.opencmis.dictionary.RelationshipTypeDefintionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.version.Version2Model; +import org.alfresco.repo.version.VersionBaseModel; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.repo.version.common.VersionUtil; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.Path.ChildAssocElement; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionDoesNotExistException; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException; + +/** + * CMIS representation of a node. + * + * Tries to avoid getting the node's version history where possible (because it's not very performant). + * + */ +public class CMISNodeInfoImpl implements CMISNodeInfo +{ + private static final GregorianCalendar DUMMY_DATE = new GregorianCalendar(2010, 4, 1); + + private CMISConnector connector; + private String objectId; // CMIS object id + private String currentObjectId; // CMIS object id of the latest version + private String currentNodeId; // node ref id of the latest version + private CMISObjectVariant objecVariant; // object variant + private NodeRef nodeRef; // object node ref + private String versionLabel; // version label + private AssociationRef associationRef; // association ref + private TypeDefinitionWrapper type; // CMIS type + private String name; + private boolean hasPWC; + private Boolean isRootFolder; + + private String cmisPath; + private VersionHistory versionHistory; + private Version version; + private Boolean isLatestMajorVersion; + private Map properties; + private List parents; + + private Map nodeProps; // for nodeRef + private Set nodeAspects; // for nodeRef + + public CMISNodeInfoImpl() + { + } + + public CMISNodeInfoImpl(CMISConnector connector, String objectId) + { + this.connector = connector; + this.objectId = connector.constructObjectId(objectId); + + analyseObjectId(); + } + + public CMISNodeInfoImpl(CMISConnector connector, NodeRef nodeRef, QName nodeType, Map nodeProps, VersionHistory versionHistory, boolean checkExists) + { + this.connector = connector; + this.nodeRef = nodeRef; + this.versionHistory = versionHistory; + + if (nodeType != null) + { + determineType(nodeType); + } + + this.nodeProps = nodeProps; + + analyseNodeRef(checkExists); + } + + public CMISNodeInfoImpl(CMISConnector connector, NodeRef nodeRef) + { + this.connector = connector; + this.nodeRef = nodeRef; + + analyseNodeRef(true); + } + + public CMISNodeInfoImpl(CMISConnector connector, AssociationRef associationRef) + { + this.connector = connector; + this.associationRef = associationRef; + + analyseAssociationRef(); + } + + private boolean isCurrentNode() + { + return objecVariant != CMISObjectVariant.VERSION; + } + + protected void analyseVersionNode() + { + // check version + versionHistory = getVersionHistory(); + if (versionHistory == null) + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + objectId = connector.constructObjectId(nodeRef, CMISConnector.UNVERSIONED_VERSION_LABEL); + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + currentObjectId = objectId; + hasPWC = isNodeCheckedOut(); + } + else + { + versionLabel = (String)getNodeProps().get(ContentModel.PROP_VERSION_LABEL); + + Version headVersion = versionHistory.getHeadVersion(); + + objectId = connector.constructObjectId(headVersion.getVersionedNodeRef(), versionLabel); + currentObjectId = connector.constructObjectId(headVersion.getVersionedNodeRef(), headVersion.getVersionLabel()); + currentNodeId = headVersion.getVersionedNodeRef().toString(); + + objecVariant = (headVersion.getVersionLabel().equals(versionLabel) ? CMISObjectVariant.CURRENT_VERSION + : CMISObjectVariant.VERSION); + hasPWC = connector.getCheckOutCheckInService().isCheckedOut(headVersion.getVersionedNodeRef()); + } + } + + protected void analyseCurrentVersion() + { + if (isNodeVersioned(nodeRef)) + { + versionLabel = (String) connector.getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + if(versionLabel == null) + { + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + } + objectId = connector.constructObjectId(nodeRef, versionLabel); + currentObjectId = objectId; + currentNodeId = nodeRef.toString(); + objecVariant = CMISObjectVariant.CURRENT_VERSION; + hasPWC = isNodeCheckedOut(); + } + else + { + setUnversioned(); + } + } + + protected void setUnversioned() + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + objectId = connector.constructObjectId(nodeRef, CMISConnector.UNVERSIONED_VERSION_LABEL); + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + currentObjectId = objectId; + hasPWC = isNodeCheckedOut(); + } + + protected void analyseObjectId() + { + currentNodeId = objectId; + currentObjectId = objectId; + versionLabel = null; + nodeRef = null; + hasPWC = false; + + if (objectId == null) + { + objecVariant = CMISObjectVariant.INVALID_ID; + return; + } + + try + { + // is it a version? + int sepIndex = objectId.lastIndexOf(CMISConnector.ID_SEPERATOR); + if (sepIndex > -1) + { + currentNodeId = objectId.substring(0, sepIndex); + versionLabel = objectId.substring(sepIndex + 1); + } + + if (objectId.startsWith(CMISConnector.ASSOC_ID_PREFIX)) + { + // check the association id + Long assocId = null; + try + { + assocId = new Long(objectId.substring(CMISConnector.ASSOC_ID_PREFIX.length())); + } catch (NumberFormatException nfe) + { + objecVariant = CMISObjectVariant.INVALID_ID; + return; + } + + // check the association + associationRef = connector.getNodeService().getAssoc(assocId); + if (associationRef == null) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } else + { + objecVariant = CMISObjectVariant.ASSOC; + } + } + else + { + if(NodeRef.isNodeRef(objectId)) + { + NodeRef tmpNodeRef = new NodeRef(objectId); + objectId = connector.constructObjectId(tmpNodeRef, null); + } + + if(!NodeRef.isNodeRef(currentNodeId)) + { + currentNodeId = connector.getRootStoreRef() + "/" + currentNodeId; + } + + // nodeRef is a "live" node, the version label identifies the specific version of the node + nodeRef = new NodeRef(currentNodeId); + + // check for existence + if (!connector.getNodeService().exists(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + return; + } + + // check PWC + if (isNodeWorkingCopy()) + { + NodeRef checkedOut = connector.getCheckOutCheckInService().getCheckedOut(nodeRef); + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.PWC; + } + currentObjectId = connector.createObjectId(checkedOut); + currentNodeId = checkedOut.toString(); + versionLabel = CMISConnector.PWC_VERSION_LABEL; + hasPWC = true; + return; + } + + if (isFolder()) + { + // folders can't be versioned, so no need to check + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.FOLDER; + } + return; + } + + if(isItem()) + { + objecVariant = CMISObjectVariant.ITEM; + return; + } + + if (versionLabel == null) + { + if (isDocument()) + { + // for a document, absence of a version label implies the current (head) version + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + } + + // Is it un-versioned, or currently versioned? + Version currentVersion = connector.getVersionService().getCurrentVersion(nodeRef); + if (currentVersion != null) + { + versionLabel = currentVersion.getVersionLabel(); + versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + } + else + { + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + } + + objectId = connector.constructObjectId(objectId, versionLabel); + currentObjectId = objectId; + hasPWC = isNodeCheckedOut(); + } + else + { + objecVariant = CMISObjectVariant.NOT_A_CMIS_OBJECT; + } + return; + } + + // check if it has PWC label + if (versionLabel.equals(CMISConnector.PWC_VERSION_LABEL)) + { + NodeRef pwcNodeRef = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); + if (pwcNodeRef == null) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + return; + } + else if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.PWC; + } + currentObjectId = connector.createObjectId(nodeRef); + currentNodeId = nodeRef.toString(); + hasPWC = true; + nodeRef = pwcNodeRef; + return; + } + + // check version + if(! isNodeVersioned(nodeRef)) + { + // the node isn't versioned + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else if (versionLabel.equals(CMISConnector.UNVERSIONED_VERSION_LABEL)) + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + } else + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + } + else + { + // the node is versioned, determine whether the versionLabel refers to the head version or a + // specific non-head version + String headVersionLabel = (String)connector.getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + currentObjectId = connector.constructObjectId(currentNodeId, headVersionLabel); + + if (versionLabel.equals(headVersionLabel)) + { + // the version label refers to the current head version + objecVariant = CMISObjectVariant.CURRENT_VERSION; + } + else + { + // the version label refers to a specific non-head version, find the nodeRef + // of the version node from the version history + versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + if (versionHistory == null) + { + // unexpected null versionHistory, assume not versioned + if (versionLabel.equals(CMISConnector.UNVERSIONED_VERSION_LABEL)) + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + + } + else + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + } + else + { + try + { + version = versionHistory.getVersion(versionLabel); + nodeRef = version.getFrozenStateNodeRef(); + objecVariant = CMISObjectVariant.VERSION; + } + catch (VersionDoesNotExistException e) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + } + } + } + + // check if current node(not specified version) checked out + hasPWC = connector.getCheckOutCheckInService().isCheckedOut(getCurrentNodeNodeRef()); + } + } + catch (AccessDeniedException e) + { + objecVariant = CMISObjectVariant.PERMISSION_DENIED; + } + // TODO: Somewhere this has not been wrapped correctly + catch (net.sf.acegisecurity.AccessDeniedException e) + { + objecVariant = CMISObjectVariant.PERMISSION_DENIED; + } + } + + protected void analyseNodeRef(boolean checkExists) + { + objectId = null; + currentNodeId = nodeRef.toString(); + currentObjectId = null; + versionLabel = null; + hasPWC = false; + + // check for existence + if (checkExists && (!connector.getNodeService().exists(nodeRef))) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + return; + } + + if (connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + return; + } + + if (isFolder()) + { + objecVariant = CMISObjectVariant.FOLDER; + objectId = connector.constructObjectId(nodeRef, null); + currentObjectId = objectId; + return; + } + else if (isItem()) + { + objecVariant = CMISObjectVariant.ITEM; + objectId = connector.constructObjectId(nodeRef, null); + currentObjectId = objectId; + return; + } + else if (getType() == null) + { + objecVariant = CMISObjectVariant.NOT_A_CMIS_OBJECT; + return; + } + + // check PWC + if (isNodeWorkingCopy()) + { + NodeRef checkedOut = connector.getCheckOutCheckInService().getCheckedOut(nodeRef); + if (checkedOut == null) + { + // catch a rare audit case + checkedOut = nodeRef; + } + + objecVariant = CMISObjectVariant.PWC; + + objectId = connector.constructObjectId(checkedOut, CMISConnector.PWC_VERSION_LABEL); + + versionLabel = CMISConnector.PWC_VERSION_LABEL; + currentObjectId = connector.createObjectId(checkedOut); + currentNodeId = checkedOut.toString(); + hasPWC = true; + return; + } + + // check version + if (isNodeAVersion(nodeRef)) + { + analyseVersionNode(); + } + else + { + analyseCurrentVersion(); + } + } + + private boolean isNodeWorkingCopy() + { + return getNodeAspects().contains(ContentModel.ASPECT_WORKING_COPY); + } + + private boolean isNodeCheckedOut() + { + return getNodeAspects().contains(ContentModel.ASPECT_CHECKED_OUT); + } + + private boolean isNodeAVersion(NodeRef nodeRef) + { + if(nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + NodeRef realNodeRef = VersionUtil.convertNodeRef(nodeRef); + return connector.getVersionService().isAVersion(realNodeRef); + } + return getNodeAspects().contains(Version2Model.ASPECT_VERSION); + } + + private boolean isNodeVersioned(NodeRef nodeRef) + { + if(nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + NodeRef realNodeRef = VersionUtil.convertNodeRef(nodeRef); + return connector.getVersionService().isVersioned(realNodeRef); + } + return getNodeAspects().contains(ContentModel.ASPECT_VERSIONABLE); + } + + protected void analyseAssociationRef() + { + objectId = null; + currentNodeId = null; + currentObjectId = null; + versionLabel = null; + hasPWC = false; + + if (associationRef == null) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + return; + } + + objecVariant = CMISObjectVariant.ASSOC; + objectId = CMISConnector.ASSOC_ID_PREFIX + associationRef.getId(); + } + + private void determineType(QName nodeType) + { + type = null; + + if((objecVariant == CMISObjectVariant.INVALID_ID) || (objecVariant == CMISObjectVariant.NOT_A_CMIS_OBJECT) || (objecVariant == CMISObjectVariant.NOT_EXISTING) || (objecVariant == CMISObjectVariant.PERMISSION_DENIED)) + { + return; + } + + if (nodeRef != null) + { + QName typeQName = (nodeType != null ? nodeType : connector.getNodeService().getType(nodeRef)); + type = connector.getOpenCMISDictionaryService().findNodeType(typeQName); + } else if (associationRef != null) + { + QName typeQName = associationRef.getTypeQName(); + type = connector.getOpenCMISDictionaryService().findAssocType(typeQName); + } + } + + public String getObjectId() + { + return objectId; + } + + public CMISObjectVariant getObjectVariant() + { + return objecVariant; + } + + public boolean isVariant(CMISObjectVariant var) + { + return objecVariant == var; + } + + public NodeRef getNodeRef() + { + return nodeRef; + } + + public String getCurrentNodeId() + { + return currentNodeId; + } + + public NodeRef getCurrentNodeNodeRef() + { + return new NodeRef(currentNodeId); + } + + public String getCurrentObjectId() + { + return currentObjectId; + } + + public boolean isCurrentVersion() + { + return objecVariant == CMISObjectVariant.CURRENT_VERSION; + } + + public boolean isPWC() + { + return objecVariant == CMISObjectVariant.PWC; + } + + public boolean hasPWC() + { + return hasPWC; + } + + public boolean isVersion() + { + return objecVariant == CMISObjectVariant.VERSION; + } + + public boolean isLatestVersion() + { + return isCurrentVersion(); + } + + public boolean isLatestMajorVersion() + { + if (isLatestMajorVersion == null) + { + isLatestMajorVersion = Boolean.FALSE; + if (!isPWC()) + { + // MNT-10223. It should not be a version and not a minor version + Version version = getVersion(); + if (isCurrentNode() && version != null && version.getVersionType() == VersionType.MAJOR) + { + isLatestMajorVersion = Boolean.TRUE; + } + else + { + VersionHistory versionHistory = getVersionHistory(); + if (versionHistory == null) + { + isLatestMajorVersion = Boolean.TRUE; + } else + { + Version currentVersion = versionHistory.getHeadVersion(); + while (currentVersion != null) + { + if (currentVersion.getVersionType() == VersionType.MAJOR) + { + // ALF-11116: the current node (in the main store) and the frozen node (in the version store) are both represented as CMISNodeInfos + // but are indistinguishable apart from their storeRef (their objectVariant can be the same). + if (nodeRef.getStoreRef().getIdentifier().equals(Version2Model.STORE_ID) || nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + isLatestMajorVersion = currentVersion.getFrozenStateNodeRef().equals(nodeRef); + } + break; + } + currentVersion = versionHistory.getPredecessor(currentVersion); + } + } + } + } + } + + return isLatestMajorVersion.booleanValue(); + } + + public boolean isMajorVersion() + { + if (isPWC()) + { + return false; + } + if (CMISConnector.UNVERSIONED_VERSION_LABEL.equals(versionLabel)) + { + return true; + } + + Version version = getVersion(); + if (version == null) + { + return true; + } + + return version.getVersionType() == VersionType.MAJOR; + } + + public String getVersionLabel() + { + return versionLabel; + } + + public String getCheckinComment() + { + if (!isDocument() || isPWC()) + { + return null; + } + + Version version = getVersion(); + if (version != null) + { + return getVersion().getDescription(); + } + + return null; + } + + public AssociationRef getAssociationRef() + { + return associationRef; + } + + public TypeDefinitionWrapper getType() + { + if (type == null) + { + determineType(null); + } + + return type; + } + + public boolean isFolder() + { + return getType() instanceof FolderTypeDefintionWrapper; + } + + public boolean isItem() + { + return getType() instanceof ItemTypeDefinitionWrapper; + } + + public boolean isRootFolder() + { + if (isRootFolder == null) + { + isRootFolder = isFolder() && connector.getRootNodeRef().equals(nodeRef); + } + + return isRootFolder.booleanValue(); + } + + public boolean isDocument() + { + return getType() instanceof DocumentTypeDefinitionWrapper; + } + + public boolean isRelationship() + { + return getType() instanceof RelationshipTypeDefintionWrapper; + } + + public String getName() + { + if((objecVariant == CMISObjectVariant.INVALID_ID) || (objecVariant == CMISObjectVariant.NOT_A_CMIS_OBJECT) || (objecVariant == CMISObjectVariant.NOT_EXISTING) || (objecVariant == CMISObjectVariant.PERMISSION_DENIED)) + { + return null; + } + + if (name == null) + { + if (isRelationship()) + { + name = associationRef.toString(); + } else + { + Object nameObj = getNodeProps().get(ContentModel.PROP_NAME); + name = (nameObj instanceof String ? (String) nameObj : ""); + } + } + + return name; + } + + public String getPath() + { + if((objecVariant == CMISObjectVariant.INVALID_ID) || (objecVariant == CMISObjectVariant.NOT_A_CMIS_OBJECT) || (objecVariant == CMISObjectVariant.NOT_EXISTING) || (objecVariant == CMISObjectVariant.PERMISSION_DENIED)) + { + return null; + } + + if (cmisPath == null) + { + StringBuilder displayPath = new StringBuilder(64); + + Path path = connector.getNodeService().getPath(nodeRef); + NodeRef rootNode = connector.getRootNodeRef(); + int i = 0; + while (i < path.size()) + { + Path.Element element = path.get(i); + if (element instanceof ChildAssocElement) + { + ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); + NodeRef node = assocRef.getChildRef(); + if (node.equals(rootNode)) + { + break; + } + } + i++; + } + + if (i == path.size()) + { + // TODO: + // throw new AlfrescoRuntimeException("Path " + path + + // " not in CMIS root node scope"); + } + + if (path.size() - i == 1) + { + // render root path + displayPath.append("/"); + } else + { + // render CMIS scoped path + i++; + while (i < path.size() - 1) + { + Path.Element element = path.get(i); + if (element instanceof ChildAssocElement) + { + ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); + NodeRef node = assocRef.getChildRef(); + displayPath.append("/"); + try + { + String propertyName = (String) connector.getNodeService().getProperty(node, ContentModel.PROP_NAME); + displayPath.append(propertyName); + } + catch (AccessDeniedException e) + { + // if the user does not have enough permissions to construct the entire path then the object + // should have a null path + return null; + } + // Somewhere this has not been wrapped correctly + catch (net.sf.acegisecurity.AccessDeniedException e) + { + // if the user does not have enough permissions to construct the entire path then the object + // should have a null path + return null; + } + } + i++; + } + displayPath.append("/"); + displayPath.append(getName()); + } + + cmisPath = displayPath.toString(); + } + + return cmisPath; + } + + public Serializable getCreationDate() + { + // MNT-12680 we should return always creation date of original node + if (isDocument() || isFolder()) + { + return getNodeProps().get(ContentModel.PROP_CREATED); + } + else + { + return DUMMY_DATE; + } + } + + public Serializable getModificationDate() + { + if (isDocument()) + { + if (isCurrentVersion() || isPWC()) + { + return getNodeProps().get(ContentModel.PROP_MODIFIED); + } else + { + return getVersion().getVersionProperty(ContentModel.PROP_MODIFIED.getLocalName()); + } + } else if (isFolder() || isItem()) + { + return getNodeProps().get(ContentModel.PROP_MODIFIED); + } else + { + return DUMMY_DATE; + } + } + + public NodeRef getLatestVersionNodeRef(boolean major) + { + if (!major) + { + return getLatestNonMajorVersionNodeRef(); + } + + VersionHistory versionHistory = getVersionHistory(); + + // if there is no history, return the current version + if (versionHistory == null) + { + // there are no versions + return getLatestNonMajorVersionNodeRef(); + } + + // find the latest major version + for (Version version : versionHistory.getAllVersions()) + { + if (version.getVersionType() == VersionType.MAJOR) + { + return version.getFrozenStateNodeRef(); + } + } + + throw new CmisObjectNotFoundException("There is no major version!"); + } + + private NodeRef getLatestNonMajorVersionNodeRef() + { +// if (isPWC()) +// { +// return nodeRef; +// } else if (hasPWC()) +// { +// return connector.getCheckOutCheckInService().getWorkingCopy(getCurrentNodeNodeRef()); +// } else +// { + return getCurrentNodeNodeRef(); +// } + } + + // TODO lock here?? + public VersionHistory getVersionHistory() + { + if (versionHistory == null && isDocument()) + { + try + { + versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + } catch (Exception e) + { + } + } + + return versionHistory; + } + + public void deleteNode() + { + Version version = getVersion(); + + if (getVersionHistory().getPredecessor(version) == null) + { + connector.getNodeService().deleteNode(nodeRef); + } + else + { + connector.getVersionService().deleteVersion(nodeRef, version); + } + } + + public void deleteVersion() + { + Version version = getVersion(); + connector.getVersionService().deleteVersion(nodeRef, version); + } + + protected Version getVersion() + { + if (version == null && isDocument()) + { + try + { + VersionHistory versionHistory = getVersionHistory(); + if (versionHistory == null) // Avoid unnecessary NPE + { + return null; + } + version = versionHistory.getVersion(versionLabel); + } catch (Exception e) + { + } + } + + return version; + } + + public void checkIfUseful(String what) + { + switch (objecVariant) + { + case INVALID_ID: + throw new CmisInvalidArgumentException(what + " id is invalid: " + objectId); + case NOT_EXISTING: + throw new CmisObjectNotFoundException(what + " not found: " + objectId); + case NOT_A_CMIS_OBJECT: + throw new CmisObjectNotFoundException(what + " is not a CMIS object: " + objectId); + case PERMISSION_DENIED: + throw new CmisPermissionDeniedException("Permission denied!"); + } + } + + public void checkIfFolder(String what) + { + checkIfUseful(what); + if (objecVariant != CMISObjectVariant.FOLDER) + { + throw new CmisInvalidArgumentException(what + " is not a folder!"); + } + } + + @Override + public Serializable getPropertyValue(String id) + { + if (properties == null) + { + return null; + } + + return properties.get(id); + } + + @Override + public boolean containsPropertyValue(String id) + { + if (properties == null) + { + return false; + } + + return properties.containsKey(id); + } + + @Override + public void putPropertyValue(String id, Serializable value) + { + if (properties == null) + { + properties = new HashMap(); + } + + properties.put(id, value); + } + + @Override + public String toString() + { + return getObjectId() + " (" + getNodeRef() + ")"; + } + + @Override + public List getParents() + { + if((objecVariant == CMISObjectVariant.INVALID_ID) || (objecVariant == CMISObjectVariant.NOT_A_CMIS_OBJECT) || (objecVariant == CMISObjectVariant.NOT_EXISTING) || (objecVariant == CMISObjectVariant.PERMISSION_DENIED)) + { + return Collections.emptyList(); + } + + if (parents == null) + { + parents = new ArrayList(); + + NodeRef nodeRefForParent = (isCurrentVersion() ? getCurrentNodeNodeRef() : nodeRef); + + List nodeParents = connector.getNodeService().getParentAssocs(nodeRefForParent, + ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + if (nodeParents != null) + { + for (ChildAssociationRef parent : nodeParents) + { + if (connector.getType(parent.getParentRef()) instanceof FolderTypeDefintionWrapper) + { + parents.add(new CMISNodeInfoImpl(connector, parent.getParentRef())); + } + } + } + } + + return parents; + } + + public Map getNodeProps() + { + if ((nodeProps == null) && (nodeRef != null)) + { + nodeProps = connector.getNodeService().getProperties(nodeRef); + } + return nodeProps; + } + + public Set getNodeAspects() + { + if ((nodeAspects == null) && (nodeRef != null)) + { + nodeAspects = connector.getNodeService().getAspects(nodeRef); + } + return nodeAspects; + } +} diff --git a/source/java/org/alfresco/opencmis/CMISRenditionMapping.java b/source/java/org/alfresco/opencmis/CMISRenditionMapping.java index ceed7bf03d..d767bbf1c0 100644 --- a/source/java/org/alfresco/opencmis/CMISRenditionMapping.java +++ b/source/java/org/alfresco/opencmis/CMISRenditionMapping.java @@ -1,291 +1,291 @@ -package org.alfresco.opencmis; - -import java.io.Serializable; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.rendition.executer.ImageRenderingEngine; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.rendition.RenditionDefinition; -import org.alfresco.service.cmr.rendition.RenditionService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.apache.chemistry.opencmis.commons.data.RenditionData; -import org.apache.chemistry.opencmis.commons.exceptions.CmisFilterNotValidException; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.RenditionDataImpl; - -public class CMISRenditionMapping -{ - private NodeService nodeService; - private ContentService contentService; - private RenditionService renditionService; - private TransactionService transactionService; - - private Map> kindToRenditionNames; - private Map renditionNamesToKind; - private Map renditionNameToSize; - - public CMISRenditionMapping(NodeService nodeService, ContentService contentService, - RenditionService renditionService, TransactionService transactionService, - Map> renditionKinds) - { - this.nodeService = nodeService; - this.contentService = contentService; - this.renditionService = renditionService; - this.transactionService = transactionService; - - if (renditionKinds == null) - { - this.kindToRenditionNames = new HashMap>(); - } else - { - this.kindToRenditionNames = renditionKinds; - } - renditionNamesToKind = new HashMap(); - for (Entry> entry : renditionKinds.entrySet()) - { - for (String renditionName : entry.getValue()) - { - renditionNamesToKind.put(renditionName, entry.getKey()); - } - } - - cacheRenditionSizes(); - } - - private void cacheRenditionSizes() - { - renditionNameToSize = AuthenticationUtil.runAs(new RunAsWork>() - { - public Map doWork() throws Exception - { - return transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback>() - { - public Map execute() throws Exception - { - Map rn2s = new HashMap(); - - List allRenditionDefs = renditionService - .loadRenditionDefinitions(); - for (RenditionDefinition rd : allRenditionDefs) - { - QName renditionDefinitionName = rd.getRenditionName(); - - Number width = (Number) rd - .getParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH); - Number height = (Number) rd - .getParameterValue(ImageRenderingEngine.PARAM_RESIZE_HEIGHT); - - if ((width != null) || (height != null)) - { - BigInteger[] size = new BigInteger[2]; - size[0] = (width == null ? null : BigInteger.valueOf(width.longValue())); - size[1] = (height == null ? null : BigInteger.valueOf(height.longValue())); - - rn2s.put(renditionDefinitionName.getLocalName(), size); - } - } - - return rn2s; - }; - }, true); - } - }, AuthenticationUtil.getSystemUserName()); - } - - public List getRenditions(NodeRef nodeRef, String renditionFilter, BigInteger maxItems, - BigInteger skipCount) - { - List result = new ArrayList(); - - // split the filter - Set filterSet = splitRenditionFilter(renditionFilter); - if ((filterSet != null) && (filterSet.contains(CMISConnector.RENDITION_NONE))) - { - // "cmis:none" found -> no renditions - return result; - } - - // convert BigIntegers to int - int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); - int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); - - if (max > 0) - { - // find all renditions and filter them - List renditionList = renditionService.getRenditions(nodeRef); - - int lastIndex = (max + skip > renditionList.size() ? renditionList.size() : max + skip) - 1; - for (int i = skip; i <= lastIndex; i++) - { - ChildAssociationRef rendition = renditionList.get(i); - NodeRef rendNodeRef = rendition.getChildRef(); - String rendName = rendition.getQName().getLocalName(); - - // get and check content - QName contentProperty = ContentModel.PROP_CONTENT; - Serializable contentPropertyName = nodeService.getProperty(rendNodeRef, - ContentModel.PROP_CONTENT_PROPERTY_NAME); - if (contentPropertyName != null) - { - contentProperty = (QName) contentPropertyName; - } - - ContentReader reader = contentService.getReader(rendNodeRef, contentProperty); - if ((reader == null) || (!reader.exists())) - { - // no content -> no rendition - continue; - } - - // get and clean MIME type - String mimeType = reader.getMimetype(); - if (mimeType.indexOf(';') > 3) - { - mimeType = mimeType.substring(0, mimeType.indexOf(';')).trim(); - } - - // if a filter is set, check it - if (filterSet != null) - { - boolean include = false; - for (String f : filterSet) - { - if (f.indexOf('/') == -1) - { - // found a kind, not a MIME type - List renditionNames = kindToRenditionNames.get(f); - if (renditionNames != null && renditionNames.contains(rendName)) - { - include = true; - break; - } - } else if (f.endsWith("*")) - { - // found MIME type with wildcard - if (mimeType.startsWith(f.substring(0, f.length() - 2))) - { - include = true; - break; - } - } else - { - // found complete MIME type - if (mimeType.equals(f)) - { - include = true; - break; - } - } - } - - // if no filter matches, skip this rendition - if (!include) - { - continue; - } - } - - // gather rendition data - String title = rendName; - String kind = (renditionNamesToKind.containsKey(rendName) ? renditionNamesToKind.get(rendName) - : rendName); - BigInteger length = BigInteger.valueOf(reader.getSize()); - - BigInteger width = null; - BigInteger height = null; - if (renditionNameToSize.containsKey(rendName)) - { - BigInteger[] size = renditionNameToSize.get(rendName); - width = size[0]; - height = size[1]; - } - - // finally add this rendition - result.add(createRenditionData(rendNodeRef, mimeType, title, kind, length, width, height)); - } - } - - if (filterSet == null || filterSet.contains("cmis:thumbnail")) - { - - } - - return result; - } - - private Set splitRenditionFilter(String filter) - { - if (filter == null) - { - return null; - } - - if (filter.trim().length() == 0) - { - return null; - } - - Set result = new HashSet(); - for (String s : filter.split(",")) - { - s = s.trim(); - if (s.equals("*")) - { - return null; - } else if (s.indexOf('*') > -1) - { - if (!s.endsWith("*")) - { - throw new CmisFilterNotValidException("Rendition filter is invalid: " + s); - } - result.add(s); - } else if (s.equalsIgnoreCase(CMISConnector.RENDITION_NONE)) - { - result.clear(); - result.add(CMISConnector.RENDITION_NONE); - break; - } else if (s.length() > 0) - { - result.add(s); - } - } - - return result; - } - - private RenditionData createRenditionData(NodeRef rendNodeRef, String mimeType, String title, String kind, - BigInteger length, BigInteger width, BigInteger height) - { - RenditionDataImpl result = new RenditionDataImpl(); - - result.setStreamId(rendNodeRef.getId()); - result.setMimeType(mimeType); - - result.setTitle(title); - result.setKind(kind); - result.setBigLength(length); - - result.setBigWidth(width); - result.setBigHeight(height); - - result.setRenditionDocumentId(rendNodeRef.getId()); - - return result; - } -} +package org.alfresco.opencmis; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.rendition.executer.ImageRenderingEngine; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.apache.chemistry.opencmis.commons.data.RenditionData; +import org.apache.chemistry.opencmis.commons.exceptions.CmisFilterNotValidException; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.RenditionDataImpl; + +public class CMISRenditionMapping +{ + private NodeService nodeService; + private ContentService contentService; + private RenditionService renditionService; + private TransactionService transactionService; + + private Map> kindToRenditionNames; + private Map renditionNamesToKind; + private Map renditionNameToSize; + + public CMISRenditionMapping(NodeService nodeService, ContentService contentService, + RenditionService renditionService, TransactionService transactionService, + Map> renditionKinds) + { + this.nodeService = nodeService; + this.contentService = contentService; + this.renditionService = renditionService; + this.transactionService = transactionService; + + if (renditionKinds == null) + { + this.kindToRenditionNames = new HashMap>(); + } else + { + this.kindToRenditionNames = renditionKinds; + } + renditionNamesToKind = new HashMap(); + for (Entry> entry : renditionKinds.entrySet()) + { + for (String renditionName : entry.getValue()) + { + renditionNamesToKind.put(renditionName, entry.getKey()); + } + } + + cacheRenditionSizes(); + } + + private void cacheRenditionSizes() + { + renditionNameToSize = AuthenticationUtil.runAs(new RunAsWork>() + { + public Map doWork() throws Exception + { + return transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback>() + { + public Map execute() throws Exception + { + Map rn2s = new HashMap(); + + List allRenditionDefs = renditionService + .loadRenditionDefinitions(); + for (RenditionDefinition rd : allRenditionDefs) + { + QName renditionDefinitionName = rd.getRenditionName(); + + Number width = (Number) rd + .getParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH); + Number height = (Number) rd + .getParameterValue(ImageRenderingEngine.PARAM_RESIZE_HEIGHT); + + if ((width != null) || (height != null)) + { + BigInteger[] size = new BigInteger[2]; + size[0] = (width == null ? null : BigInteger.valueOf(width.longValue())); + size[1] = (height == null ? null : BigInteger.valueOf(height.longValue())); + + rn2s.put(renditionDefinitionName.getLocalName(), size); + } + } + + return rn2s; + }; + }, true); + } + }, AuthenticationUtil.getSystemUserName()); + } + + public List getRenditions(NodeRef nodeRef, String renditionFilter, BigInteger maxItems, + BigInteger skipCount) + { + List result = new ArrayList(); + + // split the filter + Set filterSet = splitRenditionFilter(renditionFilter); + if ((filterSet != null) && (filterSet.contains(CMISConnector.RENDITION_NONE))) + { + // "cmis:none" found -> no renditions + return result; + } + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + if (max > 0) + { + // find all renditions and filter them + List renditionList = renditionService.getRenditions(nodeRef); + + int lastIndex = (max + skip > renditionList.size() ? renditionList.size() : max + skip) - 1; + for (int i = skip; i <= lastIndex; i++) + { + ChildAssociationRef rendition = renditionList.get(i); + NodeRef rendNodeRef = rendition.getChildRef(); + String rendName = rendition.getQName().getLocalName(); + + // get and check content + QName contentProperty = ContentModel.PROP_CONTENT; + Serializable contentPropertyName = nodeService.getProperty(rendNodeRef, + ContentModel.PROP_CONTENT_PROPERTY_NAME); + if (contentPropertyName != null) + { + contentProperty = (QName) contentPropertyName; + } + + ContentReader reader = contentService.getReader(rendNodeRef, contentProperty); + if ((reader == null) || (!reader.exists())) + { + // no content -> no rendition + continue; + } + + // get and clean MIME type + String mimeType = reader.getMimetype(); + if (mimeType.indexOf(';') > 3) + { + mimeType = mimeType.substring(0, mimeType.indexOf(';')).trim(); + } + + // if a filter is set, check it + if (filterSet != null) + { + boolean include = false; + for (String f : filterSet) + { + if (f.indexOf('/') == -1) + { + // found a kind, not a MIME type + List renditionNames = kindToRenditionNames.get(f); + if (renditionNames != null && renditionNames.contains(rendName)) + { + include = true; + break; + } + } else if (f.endsWith("*")) + { + // found MIME type with wildcard + if (mimeType.startsWith(f.substring(0, f.length() - 2))) + { + include = true; + break; + } + } else + { + // found complete MIME type + if (mimeType.equals(f)) + { + include = true; + break; + } + } + } + + // if no filter matches, skip this rendition + if (!include) + { + continue; + } + } + + // gather rendition data + String title = rendName; + String kind = (renditionNamesToKind.containsKey(rendName) ? renditionNamesToKind.get(rendName) + : rendName); + BigInteger length = BigInteger.valueOf(reader.getSize()); + + BigInteger width = null; + BigInteger height = null; + if (renditionNameToSize.containsKey(rendName)) + { + BigInteger[] size = renditionNameToSize.get(rendName); + width = size[0]; + height = size[1]; + } + + // finally add this rendition + result.add(createRenditionData(rendNodeRef, mimeType, title, kind, length, width, height)); + } + } + + if (filterSet == null || filterSet.contains("cmis:thumbnail")) + { + + } + + return result; + } + + private Set splitRenditionFilter(String filter) + { + if (filter == null) + { + return null; + } + + if (filter.trim().length() == 0) + { + return null; + } + + Set result = new HashSet(); + for (String s : filter.split(",")) + { + s = s.trim(); + if (s.equals("*")) + { + return null; + } else if (s.indexOf('*') > -1) + { + if (!s.endsWith("*")) + { + throw new CmisFilterNotValidException("Rendition filter is invalid: " + s); + } + result.add(s); + } else if (s.equalsIgnoreCase(CMISConnector.RENDITION_NONE)) + { + result.clear(); + result.add(CMISConnector.RENDITION_NONE); + break; + } else if (s.length() > 0) + { + result.add(s); + } + } + + return result; + } + + private RenditionData createRenditionData(NodeRef rendNodeRef, String mimeType, String title, String kind, + BigInteger length, BigInteger width, BigInteger height) + { + RenditionDataImpl result = new RenditionDataImpl(); + + result.setStreamId(rendNodeRef.getId()); + result.setMimeType(mimeType); + + result.setTitle(title); + result.setKind(kind); + result.setBigLength(length); + + result.setBigWidth(width); + result.setBigHeight(height); + + result.setRenditionDocumentId(rendNodeRef.getId()); + + return result; + } +} diff --git a/source/java/org/alfresco/opencmis/CmisActivityPoster.java b/source/java/org/alfresco/opencmis/CmisActivityPoster.java index 18d14332e2..61e99a13a0 100644 --- a/source/java/org/alfresco/opencmis/CmisActivityPoster.java +++ b/source/java/org/alfresco/opencmis/CmisActivityPoster.java @@ -1,20 +1,20 @@ -package org.alfresco.opencmis; - -import org.alfresco.opencmis.ActivityPosterImpl.ActivityInfo; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * OpenCMIS methods can ActivityPoster to create entries in the activity feed. - * - * @author sglover - */ -public interface CmisActivityPoster -{ - void postFileFolderAdded(NodeRef nodeRef); - - void postFileFolderUpdated(boolean isFolder, NodeRef nodeRef); - - void postFileFolderDeleted(ActivityInfo activityInfo); - - ActivityInfo getActivityInfo(NodeRef nodeRef); -} +package org.alfresco.opencmis; + +import org.alfresco.opencmis.ActivityPosterImpl.ActivityInfo; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * OpenCMIS methods can ActivityPoster to create entries in the activity feed. + * + * @author sglover + */ +public interface CmisActivityPoster +{ + void postFileFolderAdded(NodeRef nodeRef); + + void postFileFolderUpdated(boolean isFolder, NodeRef nodeRef); + + void postFileFolderDeleted(ActivityInfo activityInfo); + + ActivityInfo getActivityInfo(NodeRef nodeRef); +} diff --git a/source/java/org/alfresco/opencmis/ObjectFilter.java b/source/java/org/alfresco/opencmis/ObjectFilter.java index e7ba25d597..e32f4eadbc 100644 --- a/source/java/org/alfresco/opencmis/ObjectFilter.java +++ b/source/java/org/alfresco/opencmis/ObjectFilter.java @@ -1,8 +1,8 @@ -package org.alfresco.opencmis; - -import org.alfresco.service.cmr.repository.NodeRef; - -public interface ObjectFilter -{ - public boolean filter(NodeRef nodeRef); -} +package org.alfresco.opencmis; + +import org.alfresco.service.cmr.repository.NodeRef; + +public interface ObjectFilter +{ + public boolean filter(NodeRef nodeRef); +} diff --git a/source/java/org/alfresco/opencmis/PassthroughObjectFilter.java b/source/java/org/alfresco/opencmis/PassthroughObjectFilter.java index 7db5595f32..ee7d020205 100644 --- a/source/java/org/alfresco/opencmis/PassthroughObjectFilter.java +++ b/source/java/org/alfresco/opencmis/PassthroughObjectFilter.java @@ -1,13 +1,13 @@ -package org.alfresco.opencmis; - -import org.alfresco.service.cmr.repository.NodeRef; - -public class PassthroughObjectFilter implements ObjectFilter -{ - @Override - public boolean filter(NodeRef nodeRef) - { - return false; - } - -} +package org.alfresco.opencmis; + +import org.alfresco.service.cmr.repository.NodeRef; + +public class PassthroughObjectFilter implements ObjectFilter +{ + @Override + public boolean filter(NodeRef nodeRef) + { + return false; + } + +} diff --git a/source/java/org/alfresco/opencmis/PathObjectFilter.java b/source/java/org/alfresco/opencmis/PathObjectFilter.java index 594f750736..adfcbf33f2 100644 --- a/source/java/org/alfresco/opencmis/PathObjectFilter.java +++ b/source/java/org/alfresco/opencmis/PathObjectFilter.java @@ -1,69 +1,69 @@ -package org.alfresco.opencmis; - -import java.util.List; - -import org.alfresco.repo.security.permissions.AccessDeniedException; -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.namespace.NamespaceService; - -public class PathObjectFilter implements ObjectFilter -{ - private String rootPath; - private NodeService nodeService; - private NamespaceService namespaceService; - - private List excludedPaths; - - public void setExcludedPaths(List excludedPaths) - { - this.excludedPaths = excludedPaths; - } - - /** - * Sets the root path. - * - * @param path - * path within default store - */ - public void setRootPath(String path) - { - rootPath = path; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - @Override - public boolean filter(NodeRef nodeRef) - { - try - { - Path path = nodeService.getPath(nodeRef); - String s = path.toPrefixString(this.namespaceService); - return filter(s); - } - catch(AccessDeniedException e) - { - return true; - } - } - - public boolean filter(String path) - { - if(path.startsWith(rootPath)) - { - path = path.substring(rootPath.length()); - } - return excludedPaths.contains(path); - } - -} +package org.alfresco.opencmis; + +import java.util.List; + +import org.alfresco.repo.security.permissions.AccessDeniedException; +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.namespace.NamespaceService; + +public class PathObjectFilter implements ObjectFilter +{ + private String rootPath; + private NodeService nodeService; + private NamespaceService namespaceService; + + private List excludedPaths; + + public void setExcludedPaths(List excludedPaths) + { + this.excludedPaths = excludedPaths; + } + + /** + * Sets the root path. + * + * @param path + * path within default store + */ + public void setRootPath(String path) + { + rootPath = path; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + @Override + public boolean filter(NodeRef nodeRef) + { + try + { + Path path = nodeService.getPath(nodeRef); + String s = path.toPrefixString(this.namespaceService); + return filter(s); + } + catch(AccessDeniedException e) + { + return true; + } + } + + public boolean filter(String path) + { + if(path.startsWith(rootPath)) + { + path = path.substring(rootPath.length()); + } + return excludedPaths.contains(path); + } + +} diff --git a/source/java/org/alfresco/opencmis/PersistedContentStream.java b/source/java/org/alfresco/opencmis/PersistedContentStream.java index f8612d45c0..ae9416690b 100644 --- a/source/java/org/alfresco/opencmis/PersistedContentStream.java +++ b/source/java/org/alfresco/opencmis/PersistedContentStream.java @@ -1,131 +1,131 @@ -package org.alfresco.opencmis; - - -public class PersistedContentStream { -// implements ContentStream -//} -//{ -// private File tempFile = null; -// private ContentStream stream; -// -// public PersistedContentStream(ContentStream stream) -// { -// this.stream = stream; -// copyToTempFile(); -// } -// -// @Override -// public List getExtensions() -// { -// return stream.getExtensions(); -// } -// -// @Override -// public void setExtensions(List extensions) -// { -// stream.setExtensions(extensions); -// } -// -// @Override -// public long getLength() -// { -// return stream.getLength(); -// } -// -// @Override -// public BigInteger getBigLength() -// { -// return stream.getBigLength(); -// } -// -// @Override -// public String getMimeType() -// { -// return stream.getMimeType(); -// } -// -// @Override -// public String getFileName() -// { -// return stream.getFileName(); -// } -// -// @Override -// public InputStream getStream() -// { -// try -// { -// if(tempFile != null) -// { -// InputStream stream = new BufferedInputStream(new FileInputStream(tempFile)); -// return stream; -// } -// else -// { -// throw new CmisStorageException("Stream is null"); -// } -// } -// catch (FileNotFoundException e) -// { -// throw new ContentIOException("Failed to copy content from input stream: \n" + -// " writer: " + this, -// e); -// } -// } -// -// private void copyToTempFile() -// { -// int bufferSize = 40 * 1014; -// long count = 0; -// -// try -// { -// tempFile = TempFileProvider.createTempFile("cmis", "content"); -// if (stream.getStream() != null) -// { -// OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile), bufferSize); -// InputStream in = new BufferedInputStream(stream.getStream(), bufferSize); -// -// byte[] buffer = new byte[bufferSize]; -// int i; -// while ((i = in.read(buffer)) > -1) -// { -// out.write(buffer, 0, i); -// count += i; -// } -// -// in.close(); -// out.close(); -// } -// } -// catch (Exception e) -// { -// cleanup(); -// throw new CmisStorageException("Unable to store content: " + e.getMessage(), e); -// } -// -// if (stream.getLength() > -1 && stream.getLength() != count) -// { -// cleanup(); -// throw new CmisStorageException( -// "Expected " + stream.getLength() + " bytes but retrieved " + count + "bytes!"); -// } -// } -// -// public void cleanup() -// { -// if (tempFile == null) -// { -// return; -// } -// -// try -// { -// tempFile.delete(); -// } -// catch (Exception e) -// { -// // ignore - file will be removed by TempFileProvider -// } -// } -} +package org.alfresco.opencmis; + + +public class PersistedContentStream { +// implements ContentStream +//} +//{ +// private File tempFile = null; +// private ContentStream stream; +// +// public PersistedContentStream(ContentStream stream) +// { +// this.stream = stream; +// copyToTempFile(); +// } +// +// @Override +// public List getExtensions() +// { +// return stream.getExtensions(); +// } +// +// @Override +// public void setExtensions(List extensions) +// { +// stream.setExtensions(extensions); +// } +// +// @Override +// public long getLength() +// { +// return stream.getLength(); +// } +// +// @Override +// public BigInteger getBigLength() +// { +// return stream.getBigLength(); +// } +// +// @Override +// public String getMimeType() +// { +// return stream.getMimeType(); +// } +// +// @Override +// public String getFileName() +// { +// return stream.getFileName(); +// } +// +// @Override +// public InputStream getStream() +// { +// try +// { +// if(tempFile != null) +// { +// InputStream stream = new BufferedInputStream(new FileInputStream(tempFile)); +// return stream; +// } +// else +// { +// throw new CmisStorageException("Stream is null"); +// } +// } +// catch (FileNotFoundException e) +// { +// throw new ContentIOException("Failed to copy content from input stream: \n" + +// " writer: " + this, +// e); +// } +// } +// +// private void copyToTempFile() +// { +// int bufferSize = 40 * 1014; +// long count = 0; +// +// try +// { +// tempFile = TempFileProvider.createTempFile("cmis", "content"); +// if (stream.getStream() != null) +// { +// OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile), bufferSize); +// InputStream in = new BufferedInputStream(stream.getStream(), bufferSize); +// +// byte[] buffer = new byte[bufferSize]; +// int i; +// while ((i = in.read(buffer)) > -1) +// { +// out.write(buffer, 0, i); +// count += i; +// } +// +// in.close(); +// out.close(); +// } +// } +// catch (Exception e) +// { +// cleanup(); +// throw new CmisStorageException("Unable to store content: " + e.getMessage(), e); +// } +// +// if (stream.getLength() > -1 && stream.getLength() != count) +// { +// cleanup(); +// throw new CmisStorageException( +// "Expected " + stream.getLength() + " bytes but retrieved " + count + "bytes!"); +// } +// } +// +// public void cleanup() +// { +// if (tempFile == null) +// { +// return; +// } +// +// try +// { +// tempFile.delete(); +// } +// catch (Exception e) +// { +// // ignore - file will be removed by TempFileProvider +// } +// } +} diff --git a/source/java/org/alfresco/opencmis/PublicApiCallContextHandler.java b/source/java/org/alfresco/opencmis/PublicApiCallContextHandler.java index fc501554c6..ee7a1459f7 100644 --- a/source/java/org/alfresco/opencmis/PublicApiCallContextHandler.java +++ b/source/java/org/alfresco/opencmis/PublicApiCallContextHandler.java @@ -1,21 +1,21 @@ -package org.alfresco.opencmis; - -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; - -import org.apache.chemistry.opencmis.server.shared.BasicAuthCallContextHandler; - -public class PublicApiCallContextHandler extends BasicAuthCallContextHandler -{ - private static final long serialVersionUID = 8877878113507734452L; - - @Override - public Map getCallContextMap(HttpServletRequest request) - { - Map map = new HashMap(); - map.put("isPublicApi", "true"); - return map; - } -} +package org.alfresco.opencmis; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.chemistry.opencmis.server.shared.BasicAuthCallContextHandler; + +public class PublicApiCallContextHandler extends BasicAuthCallContextHandler +{ + private static final long serialVersionUID = 8877878113507734452L; + + @Override + public Map getCallContextMap(HttpServletRequest request) + { + Map map = new HashMap(); + map.put("isPublicApi", "true"); + return map; + } +} diff --git a/source/java/org/alfresco/opencmis/RangeInputStream.java b/source/java/org/alfresco/opencmis/RangeInputStream.java index 1d2b8dc87b..198edcb845 100644 --- a/source/java/org/alfresco/opencmis/RangeInputStream.java +++ b/source/java/org/alfresco/opencmis/RangeInputStream.java @@ -1,89 +1,89 @@ -package org.alfresco.opencmis; - -import java.io.IOException; -import java.io.InputStream; - -public class RangeInputStream extends InputStream -{ - - private InputStream inputStream; - private long bytesRead; - private long length; - - public RangeInputStream(InputStream inputStream, long offset, long length) throws IOException - { - super(); - - this.inputStream = inputStream; - this.length = length; - this.bytesRead = 0; - - long l = this.inputStream.skip(offset); - if (l < offset) - { - this.inputStream.skip(offset); - } - } - - @Override - public int read() throws IOException - { - if (bytesRead < length) - { - bytesRead++; - return inputStream.read(); - } else - { - return -1; - } - } - - @Override - public int read(byte[] b) throws IOException - { - return read(b, 0, b.length); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException - { - if(length - bytesRead == 0) - { - return -1; - } - - if (len > length - bytesRead) - { - len = (int) (length - bytesRead); - } - int readed = inputStream.read(b, off, len); - bytesRead += readed; - return readed; - } - - @Override - public int available() throws IOException - { - return (int) (length - bytesRead + 1); - } - - @Override - public void close() throws IOException - { - inputStream.close(); - } - - @Override - public long skip(long n) throws IOException - { - if (bytesRead + n > length) - { - n = (length - n) > 0 ? (length - n) : length - bytesRead; - } - n = inputStream.skip(n); - bytesRead += n; - - return n; - } - -} +package org.alfresco.opencmis; + +import java.io.IOException; +import java.io.InputStream; + +public class RangeInputStream extends InputStream +{ + + private InputStream inputStream; + private long bytesRead; + private long length; + + public RangeInputStream(InputStream inputStream, long offset, long length) throws IOException + { + super(); + + this.inputStream = inputStream; + this.length = length; + this.bytesRead = 0; + + long l = this.inputStream.skip(offset); + if (l < offset) + { + this.inputStream.skip(offset); + } + } + + @Override + public int read() throws IOException + { + if (bytesRead < length) + { + bytesRead++; + return inputStream.read(); + } else + { + return -1; + } + } + + @Override + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + if(length - bytesRead == 0) + { + return -1; + } + + if (len > length - bytesRead) + { + len = (int) (length - bytesRead); + } + int readed = inputStream.read(b, off, len); + bytesRead += readed; + return readed; + } + + @Override + public int available() throws IOException + { + return (int) (length - bytesRead + 1); + } + + @Override + public void close() throws IOException + { + inputStream.close(); + } + + @Override + public long skip(long n) throws IOException + { + if (bytesRead + n > length) + { + n = (length - n) > 0 ? (length - n) : length - bytesRead; + } + n = inputStream.skip(n); + bytesRead += n; + + return n; + } + +} diff --git a/source/java/org/alfresco/opencmis/dictionary/CMISAllowedActionEnum.java b/source/java/org/alfresco/opencmis/dictionary/CMISAllowedActionEnum.java index 48fdf089db..1914df70bd 100644 --- a/source/java/org/alfresco/opencmis/dictionary/CMISAllowedActionEnum.java +++ b/source/java/org/alfresco/opencmis/dictionary/CMISAllowedActionEnum.java @@ -1,100 +1,100 @@ -package org.alfresco.opencmis.dictionary; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.chemistry.opencmis.commons.BasicPermissions; - -/** - * CMIS Allowed Action Enum - * - * @author davidc - */ -public enum CMISAllowedActionEnum -{ - - // navigation services - CAN_GET_DESCENDANTS("canGetDescendants", "canGetDescendents.Folder", BasicPermissions.READ, "canGetDescendents.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadChildren"), - //CAN_GET_FOLDER_TREE("canGetFolderTree", "canGetFolderTree.Folder", BasicPermissions.READ, "canGetFolderTree.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadChildren"), - CAN_GET_CHILDREN("canGetChildren", "canGetChildren.Folder", BasicPermissions.READ, "canGetChildren.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadChildren"), - CAN_GET_FOLDER_PARENT("canGetFolderParent", "canGetParents.Folder", BasicPermissions.READ, "canGetParents.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), - CAN_GET_OBJECT_PARENTS("canGetObjectParents", "canGetFolderParent.Object", BasicPermissions.READ, "canGetFolderParent.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), - - // object services - CAN_CREATE_DOCUMENT("canCreateDocument", "canCreateDocument.Folder", BasicPermissions.ALL, "canCreateDocument.Folder", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), - CAN_CREATE_FOLDER("canCreateFolder", "canCreateFolder.Folder", BasicPermissions.ALL, "canCreateFolder.Folder", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), - CAN_CREATE_RELATIONSHIP("canCreateRelationship"), - CAN_GET_PROPERTIES("canGetProperties", "canGetProperties.Object", BasicPermissions.READ, "canGetProperties.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), - CAN_GET_RENDITIONS("canGetRenditions"/*, "canGetRenditions.Object", BasicPermissions.READ, "canGetRenditions.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"*/), - CAN_GET_CONTENT_STREAM("canGetContentStream", "canViewContent.Object", BasicPermissions.READ, "canViewContent.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadContent"), - CAN_UPDATE_PROPERTIES("canUpdateProperties", "canUpdateProperties.Object", BasicPermissions.WRITE, "canUpdateProperties.Object", "{http://www.alfresco.org/model/system/1.0}base.WriteProperties"), - CAN_MOVE_OBJECT("canMoveObject", "canMove.Object", BasicPermissions.ALL, "canMove.Target", BasicPermissions.ALL, "canMove.Object", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode", "canMove.Target", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), - CAN_DELETE_OBJECT("canDeleteObject", "canDelete.Object", BasicPermissions.ALL, "canDelete.Object", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode"), - CAN_SET_CONTENT_STREAM("canSetContentStream", "canSetContent.Document", BasicPermissions.WRITE, "canSetContent.Document", "{http://www.alfresco.org/model/system/1.0}base.WriteContent"), - CAN_DELETE_CONTENT_STREAM("canDeleteContentStream", "canDeleteContent.Document", BasicPermissions.WRITE, "canDeleteContent.Document", "{http://www.alfresco.org/model/system/1.0}base.WriteContent"), - CAN_DELETE_TREE("canDeleteTree", "canDeleteTree.Folder", BasicPermissions.ALL, "canDeleteTree.Folder", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode"), - - // multi-filing services - CAN_ADD_OBJECT_TO_FOLDER("canAddObjectToFolder", "canAddToFolder.Object", BasicPermissions.READ, "canAddToFolder.Folder", BasicPermissions.ALL, "canAddToFolder.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties", "canAddToFolder.Folder", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), - CAN_REMOVE_OBJECT_FROM_FOLDER("canRemoveObjectFromFolder", "canRemoveFromFolder.Object", BasicPermissions.ALL, "canRemoveFromFolder.Object", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode"), - - // versioning services - CAN_CHECKOUT("canCheckOut", "canCheckout.Document", BasicPermissions.ALL, "canCheckout.Document", "{http://www.alfresco.org/model/content/1.0}lockable.CheckOut"), - CAN_CANCEL_CHECKOUT("canCancelCheckOut", "canCancelCheckout.Document", BasicPermissions.ALL, "canCancelCheckout.Document", "{http://www.alfresco.org/model/content/1.0}lockable.CancelCheckOut"), - CAN_CHECKIN("canCheckIn", "canCheckin.Document", BasicPermissions.ALL, "canCheckin.Document", "{http://www.alfresco.org/model/content/1.0}lockable.CheckIn"), - CAN_GET_ALL_VERSIONS("canGetAllVersions", "canGetAllVersions.VersionSeries", BasicPermissions.READ, "canGetAllVersions.VersionSeries", "{http://www.alfresco.org/model/system/1.0}base.Read"), - - // relationship services - CAN_GET_OBJECT_RELATIONSHIPS("canGetObjectRelationships"), - - // policy services - CAN_APPLY_POLICY("canApplyPolicy", "canAddPolicy.Object", BasicPermissions.WRITE, "canAddPolicy.Policy", BasicPermissions.READ, "canAddPolicy.Object", "{http://www.alfresco.org/model/system/1.0}base.Write"), - CAN_REMOVE_POLICY("canRemovePolicy", "canRemovePolicy.Object", BasicPermissions.WRITE, "canRemovePolicy.Policy", BasicPermissions.READ, "canRemovePolicy.Object", "{http://www.alfresco.org/model/system/1.0}base.Write"), - CAN_GET_APPLIED_POLICIES("canGetAppliedPolicies", "canGetAppliedPolicies.Object", BasicPermissions.READ, "canGetAppliedPolicies.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), - - // acl services - CAN_GET_ACL("canGetACL", "canGetACL.Object", BasicPermissions.ALL, "canGetACL.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadPermissions"), - CAN_APPLY_ACL("canApplyACL", "canApplyACL.Object", BasicPermissions.ALL, "canApplyACL.Object", "{http://www.alfresco.org/model/system/1.0}base.ChangePermissions"); - - - private String label; - - private Map> mapping = new HashMap>(); - - /** - * Construct - * - * @param label String - * @param keysAndPermissions String ... - */ - CMISAllowedActionEnum(String label, String ... keysAndPermissions) - { - this.label = label; - assert(keysAndPermissions.length % 2 == 0); - for(int i = 0; i < keysAndPermissions.length; i++) - { - String key = keysAndPermissions[i]; - String permission = keysAndPermissions[++i]; - List permissions = mapping.get(key); - if(permissions == null) - { - permissions = new ArrayList(1); - mapping.put(key, permissions); - } - permissions.add(permission); - } - } - - - public String getLabel() - { - return label; - } - - public Map> getPermissionMapping() - { - return mapping; - } -} +package org.alfresco.opencmis.dictionary; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.chemistry.opencmis.commons.BasicPermissions; + +/** + * CMIS Allowed Action Enum + * + * @author davidc + */ +public enum CMISAllowedActionEnum +{ + + // navigation services + CAN_GET_DESCENDANTS("canGetDescendants", "canGetDescendents.Folder", BasicPermissions.READ, "canGetDescendents.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadChildren"), + //CAN_GET_FOLDER_TREE("canGetFolderTree", "canGetFolderTree.Folder", BasicPermissions.READ, "canGetFolderTree.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadChildren"), + CAN_GET_CHILDREN("canGetChildren", "canGetChildren.Folder", BasicPermissions.READ, "canGetChildren.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadChildren"), + CAN_GET_FOLDER_PARENT("canGetFolderParent", "canGetParents.Folder", BasicPermissions.READ, "canGetParents.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), + CAN_GET_OBJECT_PARENTS("canGetObjectParents", "canGetFolderParent.Object", BasicPermissions.READ, "canGetFolderParent.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), + + // object services + CAN_CREATE_DOCUMENT("canCreateDocument", "canCreateDocument.Folder", BasicPermissions.ALL, "canCreateDocument.Folder", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), + CAN_CREATE_FOLDER("canCreateFolder", "canCreateFolder.Folder", BasicPermissions.ALL, "canCreateFolder.Folder", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), + CAN_CREATE_RELATIONSHIP("canCreateRelationship"), + CAN_GET_PROPERTIES("canGetProperties", "canGetProperties.Object", BasicPermissions.READ, "canGetProperties.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), + CAN_GET_RENDITIONS("canGetRenditions"/*, "canGetRenditions.Object", BasicPermissions.READ, "canGetRenditions.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"*/), + CAN_GET_CONTENT_STREAM("canGetContentStream", "canViewContent.Object", BasicPermissions.READ, "canViewContent.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadContent"), + CAN_UPDATE_PROPERTIES("canUpdateProperties", "canUpdateProperties.Object", BasicPermissions.WRITE, "canUpdateProperties.Object", "{http://www.alfresco.org/model/system/1.0}base.WriteProperties"), + CAN_MOVE_OBJECT("canMoveObject", "canMove.Object", BasicPermissions.ALL, "canMove.Target", BasicPermissions.ALL, "canMove.Object", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode", "canMove.Target", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), + CAN_DELETE_OBJECT("canDeleteObject", "canDelete.Object", BasicPermissions.ALL, "canDelete.Object", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode"), + CAN_SET_CONTENT_STREAM("canSetContentStream", "canSetContent.Document", BasicPermissions.WRITE, "canSetContent.Document", "{http://www.alfresco.org/model/system/1.0}base.WriteContent"), + CAN_DELETE_CONTENT_STREAM("canDeleteContentStream", "canDeleteContent.Document", BasicPermissions.WRITE, "canDeleteContent.Document", "{http://www.alfresco.org/model/system/1.0}base.WriteContent"), + CAN_DELETE_TREE("canDeleteTree", "canDeleteTree.Folder", BasicPermissions.ALL, "canDeleteTree.Folder", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode"), + + // multi-filing services + CAN_ADD_OBJECT_TO_FOLDER("canAddObjectToFolder", "canAddToFolder.Object", BasicPermissions.READ, "canAddToFolder.Folder", BasicPermissions.ALL, "canAddToFolder.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties", "canAddToFolder.Folder", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), + CAN_REMOVE_OBJECT_FROM_FOLDER("canRemoveObjectFromFolder", "canRemoveFromFolder.Object", BasicPermissions.ALL, "canRemoveFromFolder.Object", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode"), + + // versioning services + CAN_CHECKOUT("canCheckOut", "canCheckout.Document", BasicPermissions.ALL, "canCheckout.Document", "{http://www.alfresco.org/model/content/1.0}lockable.CheckOut"), + CAN_CANCEL_CHECKOUT("canCancelCheckOut", "canCancelCheckout.Document", BasicPermissions.ALL, "canCancelCheckout.Document", "{http://www.alfresco.org/model/content/1.0}lockable.CancelCheckOut"), + CAN_CHECKIN("canCheckIn", "canCheckin.Document", BasicPermissions.ALL, "canCheckin.Document", "{http://www.alfresco.org/model/content/1.0}lockable.CheckIn"), + CAN_GET_ALL_VERSIONS("canGetAllVersions", "canGetAllVersions.VersionSeries", BasicPermissions.READ, "canGetAllVersions.VersionSeries", "{http://www.alfresco.org/model/system/1.0}base.Read"), + + // relationship services + CAN_GET_OBJECT_RELATIONSHIPS("canGetObjectRelationships"), + + // policy services + CAN_APPLY_POLICY("canApplyPolicy", "canAddPolicy.Object", BasicPermissions.WRITE, "canAddPolicy.Policy", BasicPermissions.READ, "canAddPolicy.Object", "{http://www.alfresco.org/model/system/1.0}base.Write"), + CAN_REMOVE_POLICY("canRemovePolicy", "canRemovePolicy.Object", BasicPermissions.WRITE, "canRemovePolicy.Policy", BasicPermissions.READ, "canRemovePolicy.Object", "{http://www.alfresco.org/model/system/1.0}base.Write"), + CAN_GET_APPLIED_POLICIES("canGetAppliedPolicies", "canGetAppliedPolicies.Object", BasicPermissions.READ, "canGetAppliedPolicies.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), + + // acl services + CAN_GET_ACL("canGetACL", "canGetACL.Object", BasicPermissions.ALL, "canGetACL.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadPermissions"), + CAN_APPLY_ACL("canApplyACL", "canApplyACL.Object", BasicPermissions.ALL, "canApplyACL.Object", "{http://www.alfresco.org/model/system/1.0}base.ChangePermissions"); + + + private String label; + + private Map> mapping = new HashMap>(); + + /** + * Construct + * + * @param label String + * @param keysAndPermissions String ... + */ + CMISAllowedActionEnum(String label, String ... keysAndPermissions) + { + this.label = label; + assert(keysAndPermissions.length % 2 == 0); + for(int i = 0; i < keysAndPermissions.length; i++) + { + String key = keysAndPermissions[i]; + String permission = keysAndPermissions[++i]; + List permissions = mapping.get(key); + if(permissions == null) + { + permissions = new ArrayList(1); + mapping.put(key, permissions); + } + permissions.add(permission); + } + } + + + public String getLabel() + { + return label; + } + + public Map> getPermissionMapping() + { + return mapping; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/AbstractActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/AbstractActionEvaluator.java index 582ef6c3f4..8f3d78d45b 100644 --- a/source/java/org/alfresco/opencmis/mapping/AbstractActionEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/AbstractActionEvaluator.java @@ -1,47 +1,47 @@ -package org.alfresco.opencmis.mapping; - -import org.alfresco.opencmis.dictionary.CMISActionEvaluator; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.enums.Action; - -/** - * Base class for all action evaluators - * - * @author davidc - * - */ -public abstract class AbstractActionEvaluator implements CMISActionEvaluator -{ - private ServiceRegistry serviceRegistry; - private Action action; - - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param action Action - */ - protected AbstractActionEvaluator(ServiceRegistry serviceRegistry, Action action) - { - this.serviceRegistry = serviceRegistry; - this.action = action; - } - - /** - * @return service registry - */ - protected ServiceRegistry getServiceRegistry() - { - return serviceRegistry; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.opencmis.CMISActionEvaluator#getAction() - */ - public Action getAction() - { - return action; - } -} +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.dictionary.CMISActionEvaluator; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Base class for all action evaluators + * + * @author davidc + * + */ +public abstract class AbstractActionEvaluator implements CMISActionEvaluator +{ + private ServiceRegistry serviceRegistry; + private Action action; + + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param action Action + */ + protected AbstractActionEvaluator(ServiceRegistry serviceRegistry, Action action) + { + this.serviceRegistry = serviceRegistry; + this.action = action; + } + + /** + * @return service registry + */ + protected ServiceRegistry getServiceRegistry() + { + return serviceRegistry; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.opencmis.CMISActionEvaluator#getAction() + */ + public Action getAction() + { + return action; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java b/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java index 216b9c1955..8bbc20a204 100644 --- a/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java @@ -1,142 +1,142 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.opencmis.dictionary.CMISPropertyAccessor; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.namespace.QName; - -/** - * Base class for all property accessors - * - * @author andyh - * - */ -public abstract class AbstractProperty implements CMISPropertyAccessor -{ - public static final String CONTENT_PROPERTY = "::content"; - - private ServiceRegistry serviceRegistry; - protected CMISConnector connector; - private String propertyName; - - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - * @param propertyName String - */ - protected AbstractProperty(ServiceRegistry serviceRegistry, CMISConnector connector, String propertyName) - { - this.serviceRegistry = serviceRegistry; - this.connector = connector; - this.propertyName = propertyName; - } - - protected String getGuid(String nodeId) - { - int idx = nodeId.lastIndexOf("/"); - if(idx != -1) - { - return nodeId.substring(idx+1); - } - else - { - return nodeId; - } - } - - /** - * @return service registry - */ - protected ServiceRegistry getServiceRegistry() - { - return serviceRegistry; - } - - public String getName() - { - return propertyName; - } - - public QName getMappedProperty() - { - return null; - } - - @Override - public void setValue(NodeRef nodeRef, Serializable value) - { - throw new UnsupportedOperationException(); - } - - public Serializable getValue(NodeRef nodeRef) - { - return getValue(createNodeInfo(nodeRef)); - } - - public Serializable getValue(AssociationRef assocRef) - { - return getValue(createNodeInfo(assocRef)); - } - - public Serializable getValue(CMISNodeInfo nodeInfo) - { - if (nodeInfo.containsPropertyValue(propertyName)) - { - Serializable value = nodeInfo.getPropertyValue(propertyName); - return value; - } else - { - Serializable value = getValueInternal(nodeInfo); - nodeInfo.putPropertyValue(propertyName, value); - return value; - } - } - - protected abstract Serializable getValueInternal(CMISNodeInfo nodeInfo); - - public CMISNodeInfo createNodeInfo(NodeRef nodeRef) - { - return connector.createNodeInfo(nodeRef); - } - - public CMISNodeInfo createNodeInfo(AssociationRef assocRef) - { - return connector.createNodeInfo(assocRef); - } - - protected ContentData getContentData(CMISNodeInfo nodeInfo) - { - if (!nodeInfo.isDocument()) - { - return null; - } - - if (nodeInfo.containsPropertyValue(CONTENT_PROPERTY)) - { - return (ContentData) nodeInfo.getPropertyValue(CONTENT_PROPERTY); - } else - { - ContentData contentData = null; - - Serializable value = nodeInfo.getNodeProps().get(ContentModel.PROP_CONTENT); - - if (value != null) - { - contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); - } - - nodeInfo.putPropertyValue(CONTENT_PROPERTY, contentData); - return contentData; - } - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.opencmis.dictionary.CMISPropertyAccessor; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; + +/** + * Base class for all property accessors + * + * @author andyh + * + */ +public abstract class AbstractProperty implements CMISPropertyAccessor +{ + public static final String CONTENT_PROPERTY = "::content"; + + private ServiceRegistry serviceRegistry; + protected CMISConnector connector; + private String propertyName; + + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + * @param propertyName String + */ + protected AbstractProperty(ServiceRegistry serviceRegistry, CMISConnector connector, String propertyName) + { + this.serviceRegistry = serviceRegistry; + this.connector = connector; + this.propertyName = propertyName; + } + + protected String getGuid(String nodeId) + { + int idx = nodeId.lastIndexOf("/"); + if(idx != -1) + { + return nodeId.substring(idx+1); + } + else + { + return nodeId; + } + } + + /** + * @return service registry + */ + protected ServiceRegistry getServiceRegistry() + { + return serviceRegistry; + } + + public String getName() + { + return propertyName; + } + + public QName getMappedProperty() + { + return null; + } + + @Override + public void setValue(NodeRef nodeRef, Serializable value) + { + throw new UnsupportedOperationException(); + } + + public Serializable getValue(NodeRef nodeRef) + { + return getValue(createNodeInfo(nodeRef)); + } + + public Serializable getValue(AssociationRef assocRef) + { + return getValue(createNodeInfo(assocRef)); + } + + public Serializable getValue(CMISNodeInfo nodeInfo) + { + if (nodeInfo.containsPropertyValue(propertyName)) + { + Serializable value = nodeInfo.getPropertyValue(propertyName); + return value; + } else + { + Serializable value = getValueInternal(nodeInfo); + nodeInfo.putPropertyValue(propertyName, value); + return value; + } + } + + protected abstract Serializable getValueInternal(CMISNodeInfo nodeInfo); + + public CMISNodeInfo createNodeInfo(NodeRef nodeRef) + { + return connector.createNodeInfo(nodeRef); + } + + public CMISNodeInfo createNodeInfo(AssociationRef assocRef) + { + return connector.createNodeInfo(assocRef); + } + + protected ContentData getContentData(CMISNodeInfo nodeInfo) + { + if (!nodeInfo.isDocument()) + { + return null; + } + + if (nodeInfo.containsPropertyValue(CONTENT_PROPERTY)) + { + return (ContentData) nodeInfo.getPropertyValue(CONTENT_PROPERTY); + } else + { + ContentData contentData = null; + + Serializable value = nodeInfo.getNodeProps().get(ContentModel.PROP_CONTENT); + + if (value != null) + { + contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); + } + + nodeInfo.putPropertyValue(CONTENT_PROPERTY, contentData); + return contentData; + } + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java b/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java index 29c94bfe8b..e6a8ee112c 100644 --- a/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java @@ -1,66 +1,66 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS allowedChildObjectTypeIds property. - * - * @author florian.mueller - */ -public class AllowedChildObjectTypeIdsProperty extends AbstractProperty -{ - private CMISMapping cmisMapping; - - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - * @param cmisMapping CMISMapping - */ - public AllowedChildObjectTypeIdsProperty(ServiceRegistry serviceRegistry, CMISConnector connector, - CMISMapping cmisMapping) - { - super(serviceRegistry, connector, PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS); - this.cmisMapping = cmisMapping; - } - - @Override - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - if(nodeInfo.getType() == null) - { - //If the type is null, we can't handle it so return an empty list - return (Serializable) Collections.emptyList(); - } - - TypeDefinition type = getServiceRegistry().getDictionaryService() - .getType(nodeInfo.getType().getAlfrescoClass()); - if ((type != null) && (type.getChildAssociations() != null) && (!type.getChildAssociations().isEmpty())) - { - ArrayList result = new ArrayList(); - - for (ChildAssociationDefinition cad : type.getChildAssociations().values()) - { - String typeId = cmisMapping.getCmisTypeId(cad.getTargetClass().getName()); - if (typeId != null) - { - result.add(typeId); - } - } - - return result; - } - - return (Serializable) Collections.emptyList(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS allowedChildObjectTypeIds property. + * + * @author florian.mueller + */ +public class AllowedChildObjectTypeIdsProperty extends AbstractProperty +{ + private CMISMapping cmisMapping; + + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + * @param cmisMapping CMISMapping + */ + public AllowedChildObjectTypeIdsProperty(ServiceRegistry serviceRegistry, CMISConnector connector, + CMISMapping cmisMapping) + { + super(serviceRegistry, connector, PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS); + this.cmisMapping = cmisMapping; + } + + @Override + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + if(nodeInfo.getType() == null) + { + //If the type is null, we can't handle it so return an empty list + return (Serializable) Collections.emptyList(); + } + + TypeDefinition type = getServiceRegistry().getDictionaryService() + .getType(nodeInfo.getType().getAlfrescoClass()); + if ((type != null) && (type.getChildAssociations() != null) && (!type.getChildAssociations().isEmpty())) + { + ArrayList result = new ArrayList(); + + for (ChildAssociationDefinition cad : type.getChildAssociations().values()) + { + String typeId = cmisMapping.getCmisTypeId(cad.getTargetClass().getName()); + if (typeId != null) + { + result.add(typeId); + } + } + + return result; + } + + return (Serializable) Collections.emptyList(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/BaseTypeIdProperty.java b/source/java/org/alfresco/opencmis/mapping/BaseTypeIdProperty.java index fbd4114d78..9346376331 100644 --- a/source/java/org/alfresco/opencmis/mapping/BaseTypeIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/BaseTypeIdProperty.java @@ -1,46 +1,46 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; -import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; - -/** - * Get the CMIS object type id property - * - * @author florian.mueller - */ -public class BaseTypeIdProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public BaseTypeIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.BASE_TYPE_ID); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - if (nodeInfo.isFolder()) - { - return BaseTypeId.CMIS_FOLDER.value(); - } - else if (nodeInfo.isRelationship()) - { - return BaseTypeId.CMIS_RELATIONSHIP.value(); - } - else if(nodeInfo.isItem()) - { - return BaseTypeId.CMIS_ITEM.value(); - } - - return BaseTypeId.CMIS_DOCUMENT.value(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; + +/** + * Get the CMIS object type id property + * + * @author florian.mueller + */ +public class BaseTypeIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public BaseTypeIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.BASE_TYPE_ID); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + if (nodeInfo.isFolder()) + { + return BaseTypeId.CMIS_FOLDER.value(); + } + else if (nodeInfo.isRelationship()) + { + return BaseTypeId.CMIS_RELATIONSHIP.value(); + } + else if(nodeInfo.isItem()) + { + return BaseTypeId.CMIS_ITEM.value(); + } + + return BaseTypeId.CMIS_DOCUMENT.value(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/CanCancelCheckOutActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/CanCancelCheckOutActionEvaluator.java index f2dcdb0f6c..9d6e17e2d8 100644 --- a/source/java/org/alfresco/opencmis/mapping/CanCancelCheckOutActionEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/CanCancelCheckOutActionEvaluator.java @@ -1,38 +1,38 @@ -package org.alfresco.opencmis.mapping; - -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.security.PermissionService; -import org.apache.chemistry.opencmis.commons.enums.Action; - -/** - * Alfresco Permission based Action Evaluator - * - * @author florian.mueller - */ -public class CanCancelCheckOutActionEvaluator extends AbstractActionEvaluator -{ - private PermissionActionEvaluator permissionEvaluator; - - /** - * Construct - */ - protected CanCancelCheckOutActionEvaluator(ServiceRegistry serviceRegistry) - { - super(serviceRegistry, Action.CAN_CANCEL_CHECK_OUT); - permissionEvaluator = new PermissionActionEvaluator( - serviceRegistry, - Action.CAN_CANCEL_CHECK_OUT, - PermissionService.CANCEL_CHECK_OUT); - } - - public boolean isAllowed(CMISNodeInfo nodeInfo) - { - if (nodeInfo.isPWC()) - { - return permissionEvaluator.isAllowed(nodeInfo); - } - - return false; - } -} +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.security.PermissionService; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Alfresco Permission based Action Evaluator + * + * @author florian.mueller + */ +public class CanCancelCheckOutActionEvaluator extends AbstractActionEvaluator +{ + private PermissionActionEvaluator permissionEvaluator; + + /** + * Construct + */ + protected CanCancelCheckOutActionEvaluator(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, Action.CAN_CANCEL_CHECK_OUT); + permissionEvaluator = new PermissionActionEvaluator( + serviceRegistry, + Action.CAN_CANCEL_CHECK_OUT, + PermissionService.CANCEL_CHECK_OUT); + } + + public boolean isAllowed(CMISNodeInfo nodeInfo) + { + if (nodeInfo.isPWC()) + { + return permissionEvaluator.isAllowed(nodeInfo); + } + + return false; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/CanCheckInActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/CanCheckInActionEvaluator.java index 13864ef662..11717ea5b5 100644 --- a/source/java/org/alfresco/opencmis/mapping/CanCheckInActionEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/CanCheckInActionEvaluator.java @@ -1,38 +1,38 @@ -package org.alfresco.opencmis.mapping; - -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.security.PermissionService; -import org.apache.chemistry.opencmis.commons.enums.Action; - -/** - * Alfresco Permission based Action Evaluator - * - * @author davidc - */ -public class CanCheckInActionEvaluator extends AbstractActionEvaluator -{ - private PermissionActionEvaluator permissionEvaluator; - - /** - * Construct - */ - protected CanCheckInActionEvaluator(ServiceRegistry serviceRegistry) - { - super(serviceRegistry, Action.CAN_CHECK_IN); - permissionEvaluator = new PermissionActionEvaluator( - serviceRegistry, - Action.CAN_CHECK_IN, - PermissionService.CHECK_IN); - } - - public boolean isAllowed(CMISNodeInfo nodeInfo) - { - if (nodeInfo.isPWC()) - { - return permissionEvaluator.isAllowed(nodeInfo); - } - - return false; - } -} +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.security.PermissionService; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Alfresco Permission based Action Evaluator + * + * @author davidc + */ +public class CanCheckInActionEvaluator extends AbstractActionEvaluator +{ + private PermissionActionEvaluator permissionEvaluator; + + /** + * Construct + */ + protected CanCheckInActionEvaluator(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, Action.CAN_CHECK_IN); + permissionEvaluator = new PermissionActionEvaluator( + serviceRegistry, + Action.CAN_CHECK_IN, + PermissionService.CHECK_IN); + } + + public boolean isAllowed(CMISNodeInfo nodeInfo) + { + if (nodeInfo.isPWC()) + { + return permissionEvaluator.isAllowed(nodeInfo); + } + + return false; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/CanCheckOutActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/CanCheckOutActionEvaluator.java index a67f096e7e..0496b9f47b 100644 --- a/source/java/org/alfresco/opencmis/mapping/CanCheckOutActionEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/CanCheckOutActionEvaluator.java @@ -1,47 +1,47 @@ -package org.alfresco.opencmis.mapping; - -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockType; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.PermissionService; -import org.apache.chemistry.opencmis.commons.enums.Action; - -/** - * Alfresco Permission based Action Evaluator - * - * @author florian.mueller - */ -public class CanCheckOutActionEvaluator extends AbstractActionEvaluator -{ - private PermissionActionEvaluator permissionEvaluator; - private LockService lockService; - - /** - * Construct - */ - protected CanCheckOutActionEvaluator(ServiceRegistry serviceRegistry) - { - super(serviceRegistry, Action.CAN_CHECK_OUT); - permissionEvaluator = new PermissionActionEvaluator( - serviceRegistry, - Action.CAN_CHECK_OUT, - PermissionService.CHECK_OUT); - lockService = serviceRegistry.getLockService(); - } - - /** - * Node must be versionable, must not have a Private Working Copy and must not be locked. - */ - public boolean isAllowed(CMISNodeInfo nodeInfo) - { - NodeRef nodeRef = nodeInfo.getNodeRef(); - if (nodeInfo.hasPWC() || lockService.getLockType(nodeRef) == LockType.READ_ONLY_LOCK) - { - return false; - } - - return permissionEvaluator.isAllowed(nodeInfo); - } -} +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PermissionService; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Alfresco Permission based Action Evaluator + * + * @author florian.mueller + */ +public class CanCheckOutActionEvaluator extends AbstractActionEvaluator +{ + private PermissionActionEvaluator permissionEvaluator; + private LockService lockService; + + /** + * Construct + */ + protected CanCheckOutActionEvaluator(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, Action.CAN_CHECK_OUT); + permissionEvaluator = new PermissionActionEvaluator( + serviceRegistry, + Action.CAN_CHECK_OUT, + PermissionService.CHECK_OUT); + lockService = serviceRegistry.getLockService(); + } + + /** + * Node must be versionable, must not have a Private Working Copy and must not be locked. + */ + public boolean isAllowed(CMISNodeInfo nodeInfo) + { + NodeRef nodeRef = nodeInfo.getNodeRef(); + if (nodeInfo.hasPWC() || lockService.getLockType(nodeRef) == LockType.READ_ONLY_LOCK) + { + return false; + } + + return permissionEvaluator.isAllowed(nodeInfo); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/CanDeleteDocumentEvaluator.java b/source/java/org/alfresco/opencmis/mapping/CanDeleteDocumentEvaluator.java index d3061f37ff..0e0c04910b 100644 --- a/source/java/org/alfresco/opencmis/mapping/CanDeleteDocumentEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/CanDeleteDocumentEvaluator.java @@ -1,42 +1,42 @@ -package org.alfresco.opencmis.mapping; - -import org.alfresco.opencmis.dictionary.CMISActionEvaluator; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.security.PermissionService; -import org.apache.chemistry.opencmis.commons.enums.Action; - -public class CanDeleteDocumentEvaluator extends AbstractActionEvaluator -{ - private CMISActionEvaluator currentVersionEvaluator; - - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - */ - protected CanDeleteDocumentEvaluator(ServiceRegistry serviceRegistry) - { - super(serviceRegistry, Action.CAN_DELETE_OBJECT); - this.currentVersionEvaluator = new PermissionActionEvaluator(serviceRegistry, - Action.CAN_DELETE_OBJECT, PermissionService.DELETE_NODE); - } - - public boolean isAllowed(CMISNodeInfo nodeInfo) - { - boolean isAllowed = true; - - if(!nodeInfo.isCurrentVersion() || nodeInfo.hasPWC()) - { - // not allowed if not current version or is checked out - isAllowed = false; - } - else - { - isAllowed = currentVersionEvaluator.isAllowed(nodeInfo); - } - - return isAllowed; - } - -} +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.dictionary.CMISActionEvaluator; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.security.PermissionService; +import org.apache.chemistry.opencmis.commons.enums.Action; + +public class CanDeleteDocumentEvaluator extends AbstractActionEvaluator +{ + private CMISActionEvaluator currentVersionEvaluator; + + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + */ + protected CanDeleteDocumentEvaluator(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, Action.CAN_DELETE_OBJECT); + this.currentVersionEvaluator = new PermissionActionEvaluator(serviceRegistry, + Action.CAN_DELETE_OBJECT, PermissionService.DELETE_NODE); + } + + public boolean isAllowed(CMISNodeInfo nodeInfo) + { + boolean isAllowed = true; + + if(!nodeInfo.isCurrentVersion() || nodeInfo.hasPWC()) + { + // not allowed if not current version or is checked out + isAllowed = false; + } + else + { + isAllowed = currentVersionEvaluator.isAllowed(nodeInfo); + } + + return isAllowed; + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/CheckinCommentProperty.java b/source/java/org/alfresco/opencmis/mapping/CheckinCommentProperty.java index 907177b0c6..53d333f911 100644 --- a/source/java/org/alfresco/opencmis/mapping/CheckinCommentProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/CheckinCommentProperty.java @@ -1,32 +1,32 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Accessor for the CMIS Checkin Comment - * - * @author florian.mueller - */ -public class CheckinCommentProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public CheckinCommentProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.CHECKIN_COMMENT); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.getCheckinComment(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for the CMIS Checkin Comment + * + * @author florian.mueller + */ +public class CheckinCommentProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public CheckinCommentProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.CHECKIN_COMMENT); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.getCheckinComment(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ContentStreamIdProperty.java b/source/java/org/alfresco/opencmis/mapping/ContentStreamIdProperty.java index 18746a240f..43699a85d2 100644 --- a/source/java/org/alfresco/opencmis/mapping/ContentStreamIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/ContentStreamIdProperty.java @@ -1,39 +1,39 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.ContentData; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Accessor for CMIS content stream property id - * - * @author florian.mueller - */ -public class ContentStreamIdProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public ContentStreamIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.CONTENT_STREAM_ID); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - ContentData contentData = getContentData(nodeInfo); - - if (contentData != null) - { - return contentData.getContentUrl(); - } - return null; - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentData; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS content stream property id + * + * @author florian.mueller + */ +public class ContentStreamIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public ContentStreamIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.CONTENT_STREAM_ID); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + ContentData contentData = getContentData(nodeInfo); + + if (contentData != null) + { + return contentData.getContentUrl(); + } + return null; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ContentStreamLengthProperty.java b/source/java/org/alfresco/opencmis/mapping/ContentStreamLengthProperty.java index c3f4a928ea..67fe805205 100644 --- a/source/java/org/alfresco/opencmis/mapping/ContentStreamLengthProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/ContentStreamLengthProperty.java @@ -1,47 +1,47 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.namespace.QName; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Accessor for CMIS content stream length property - * - * @author florian.mueller - */ -public class ContentStreamLengthProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public ContentStreamLengthProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.CONTENT_STREAM_LENGTH); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - ContentData contentData = getContentData(nodeInfo); - - if (contentData != null) - { - return contentData.getSize(); - } - return null; - } - - public QName getMappedProperty() - { - // spoof - return GetChildrenCannedQuery.SORT_QNAME_CONTENT_SIZE; - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS content stream length property + * + * @author florian.mueller + */ +public class ContentStreamLengthProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public ContentStreamLengthProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.CONTENT_STREAM_LENGTH); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + ContentData contentData = getContentData(nodeInfo); + + if (contentData != null) + { + return contentData.getSize(); + } + return null; + } + + public QName getMappedProperty() + { + // spoof + return GetChildrenCannedQuery.SORT_QNAME_CONTENT_SIZE; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ContentStreamMimetypeProperty.java b/source/java/org/alfresco/opencmis/mapping/ContentStreamMimetypeProperty.java index 48fc376d9e..be3a4b8acd 100644 --- a/source/java/org/alfresco/opencmis/mapping/ContentStreamMimetypeProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/ContentStreamMimetypeProperty.java @@ -1,77 +1,77 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.namespace.QName; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Accessor for CMIS content stream mimetype property - * - * @author florian.mueller - */ -public class ContentStreamMimetypeProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public ContentStreamMimetypeProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.CONTENT_STREAM_MIME_TYPE); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - ContentData contentData = getContentData(nodeInfo); - - if (contentData != null) - { - return contentData.getMimetype(); - } - return null; - } - - public String getLuceneFieldName() - { - StringBuilder field = new StringBuilder(128); - field.append("@"); - field.append(ContentModel.PROP_CONTENT); - field.append(".mimetype"); - return field.toString(); - } - - public QName getMappedProperty() - { - // spoof - return GetChildrenCannedQuery.SORT_QNAME_CONTENT_MIMETYPE; - } - - protected String getValueAsString(Serializable value) - { - Object converted = DefaultTypeConverter.INSTANCE.convert(getServiceRegistry().getDictionaryService() - .getDataType(DataTypeDefinition.TEXT), value); - String asString = DefaultTypeConverter.INSTANCE.convert(String.class, converted); - return asString; - } - - protected QName getQNameForExists() - { - return ContentModel.PROP_CONTENT; - } - - protected DataTypeDefinition getInDataType() - { - return getServiceRegistry().getDictionaryService().getDataType(DataTypeDefinition.TEXT); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS content stream mimetype property + * + * @author florian.mueller + */ +public class ContentStreamMimetypeProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public ContentStreamMimetypeProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.CONTENT_STREAM_MIME_TYPE); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + ContentData contentData = getContentData(nodeInfo); + + if (contentData != null) + { + return contentData.getMimetype(); + } + return null; + } + + public String getLuceneFieldName() + { + StringBuilder field = new StringBuilder(128); + field.append("@"); + field.append(ContentModel.PROP_CONTENT); + field.append(".mimetype"); + return field.toString(); + } + + public QName getMappedProperty() + { + // spoof + return GetChildrenCannedQuery.SORT_QNAME_CONTENT_MIMETYPE; + } + + protected String getValueAsString(Serializable value) + { + Object converted = DefaultTypeConverter.INSTANCE.convert(getServiceRegistry().getDictionaryService() + .getDataType(DataTypeDefinition.TEXT), value); + String asString = DefaultTypeConverter.INSTANCE.convert(String.class, converted); + return asString; + } + + protected QName getQNameForExists() + { + return ContentModel.PROP_CONTENT; + } + + protected DataTypeDefinition getInDataType() + { + return getServiceRegistry().getDictionaryService().getDataType(DataTypeDefinition.TEXT); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/CreationDateProperty.java b/source/java/org/alfresco/opencmis/mapping/CreationDateProperty.java index feec61238c..c01e9c105e 100644 --- a/source/java/org/alfresco/opencmis/mapping/CreationDateProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/CreationDateProperty.java @@ -1,32 +1,32 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS object creation date property. - * - * @author florian.mueller - */ -public class CreationDateProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public CreationDateProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.CREATION_DATE); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.getCreationDate(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS object creation date property. + * + * @author florian.mueller + */ +public class CreationDateProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public CreationDateProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.CREATION_DATE); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.getCreationDate(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/CurrentVersionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/CurrentVersionEvaluator.java index 20b97c6d67..0f631d8dd0 100644 --- a/source/java/org/alfresco/opencmis/mapping/CurrentVersionEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/CurrentVersionEvaluator.java @@ -1,63 +1,63 @@ -package org.alfresco.opencmis.mapping; - -import org.alfresco.opencmis.dictionary.CMISActionEvaluator; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.enums.Action; - -public class CurrentVersionEvaluator extends AbstractActionEvaluator -{ - private CMISActionEvaluator currentVersionEvaluator; - private boolean currentVersionValue; - private boolean nonCurrentVersionValue; - - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param action Action - * @param currentVersionValue boolean - * @param nonCurrentVersionValue boolean - */ - protected CurrentVersionEvaluator(ServiceRegistry serviceRegistry, Action action, boolean currentVersionValue, - boolean nonCurrentVersionValue) - { - super(serviceRegistry, action); - this.currentVersionValue = currentVersionValue; - this.nonCurrentVersionValue = nonCurrentVersionValue; - } - - /** - * Construct - * - * @param serviceRegistry - */ - protected CurrentVersionEvaluator(ServiceRegistry serviceRegistry, CMISActionEvaluator currentVersionEvaluator, - boolean nonCurrentVersionValue) - { - super(serviceRegistry, currentVersionEvaluator.getAction()); - this.currentVersionEvaluator = currentVersionEvaluator; - this.nonCurrentVersionValue = nonCurrentVersionValue; - } - - public boolean isAllowed(CMISNodeInfo nodeInfo) - { - if(nodeInfo.hasPWC()) - { - if(!nodeInfo.isPWC()) - { - return nonCurrentVersionValue; - } - } - else - { - if (!nodeInfo.isCurrentVersion()) - { - return nonCurrentVersionValue; - } - } - - - return currentVersionEvaluator == null ? currentVersionValue : currentVersionEvaluator.isAllowed(nodeInfo); - } -} +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.dictionary.CMISActionEvaluator; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.enums.Action; + +public class CurrentVersionEvaluator extends AbstractActionEvaluator +{ + private CMISActionEvaluator currentVersionEvaluator; + private boolean currentVersionValue; + private boolean nonCurrentVersionValue; + + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param action Action + * @param currentVersionValue boolean + * @param nonCurrentVersionValue boolean + */ + protected CurrentVersionEvaluator(ServiceRegistry serviceRegistry, Action action, boolean currentVersionValue, + boolean nonCurrentVersionValue) + { + super(serviceRegistry, action); + this.currentVersionValue = currentVersionValue; + this.nonCurrentVersionValue = nonCurrentVersionValue; + } + + /** + * Construct + * + * @param serviceRegistry + */ + protected CurrentVersionEvaluator(ServiceRegistry serviceRegistry, CMISActionEvaluator currentVersionEvaluator, + boolean nonCurrentVersionValue) + { + super(serviceRegistry, currentVersionEvaluator.getAction()); + this.currentVersionEvaluator = currentVersionEvaluator; + this.nonCurrentVersionValue = nonCurrentVersionValue; + } + + public boolean isAllowed(CMISNodeInfo nodeInfo) + { + if(nodeInfo.hasPWC()) + { + if(!nodeInfo.isPWC()) + { + return nonCurrentVersionValue; + } + } + else + { + if (!nodeInfo.isCurrentVersion()) + { + return nonCurrentVersionValue; + } + } + + + return currentVersionEvaluator == null ? currentVersionValue : currentVersionEvaluator.isAllowed(nodeInfo); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/DirectProperty.java b/source/java/org/alfresco/opencmis/mapping/DirectProperty.java index fa485485b1..803da9d637 100644 --- a/source/java/org/alfresco/opencmis/mapping/DirectProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/DirectProperty.java @@ -1,76 +1,76 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; -import java.util.List; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.CMISNodeInfoImpl; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; - -/** - * A simple 1-1 property mapping from a CMIS property name to an alfresco property - * - * @author florian.mueller - */ -public class DirectProperty extends AbstractProperty -{ - private QName alfrescoName; - - /** - * Construct - */ - public DirectProperty(ServiceRegistry serviceRegistry, CMISConnector connector, String propertyName, - QName alfrescoName) - { - super(serviceRegistry, connector, propertyName); - this.alfrescoName = alfrescoName; - } - - public QName getMappedProperty() - { - return alfrescoName; - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - if (nodeInfo.getType() == null) - { - // Invalid node - return null; - } - - if (nodeInfo.getNodeRef() != null) - { - Serializable result = nodeInfo.getNodeProps().get(alfrescoName); - - /* MNT-10548 fix */ - if (result instanceof List) - { - @SuppressWarnings("unchecked") - List resultList = (List)result; - for (int index = 0; index < resultList.size(); index++) - { - Object element = resultList.get(index); - if (element instanceof NodeRef) - { - NodeRef nodeRef = (NodeRef)element; - resultList.set(index, nodeRef.getId()); - } - } - } - - return result; - } - else if (nodeInfo.getAssociationRef() != null) - { - return getServiceRegistry().getNodeService().getProperty( - nodeInfo.getAssociationRef().getSourceRef(), - alfrescoName); - } - - return null; - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.List; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.CMISNodeInfoImpl; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * A simple 1-1 property mapping from a CMIS property name to an alfresco property + * + * @author florian.mueller + */ +public class DirectProperty extends AbstractProperty +{ + private QName alfrescoName; + + /** + * Construct + */ + public DirectProperty(ServiceRegistry serviceRegistry, CMISConnector connector, String propertyName, + QName alfrescoName) + { + super(serviceRegistry, connector, propertyName); + this.alfrescoName = alfrescoName; + } + + public QName getMappedProperty() + { + return alfrescoName; + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + if (nodeInfo.getType() == null) + { + // Invalid node + return null; + } + + if (nodeInfo.getNodeRef() != null) + { + Serializable result = nodeInfo.getNodeProps().get(alfrescoName); + + /* MNT-10548 fix */ + if (result instanceof List) + { + @SuppressWarnings("unchecked") + List resultList = (List)result; + for (int index = 0; index < resultList.size(); index++) + { + Object element = resultList.get(index); + if (element instanceof NodeRef) + { + NodeRef nodeRef = (NodeRef)element; + resultList.set(index, nodeRef.getId()); + } + } + } + + return result; + } + else if (nodeInfo.getAssociationRef() != null) + { + return getServiceRegistry().getNodeService().getProperty( + nodeInfo.getAssociationRef().getSourceRef(), + alfrescoName); + } + + return null; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/FixedValueActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/FixedValueActionEvaluator.java index 441653713e..726e09445e 100644 --- a/source/java/org/alfresco/opencmis/mapping/FixedValueActionEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/FixedValueActionEvaluator.java @@ -1,43 +1,43 @@ -package org.alfresco.opencmis.mapping; - -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.enums.Action; - -/** - * Action Evaluator whose evaluation is fixed - * - * @author florian.mueller - * - */ -public class FixedValueActionEvaluator extends AbstractActionEvaluator -{ - private boolean allowed; - - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param action Action - * @param allowed boolean - */ - protected FixedValueActionEvaluator(ServiceRegistry serviceRegistry, Action action, boolean allowed) - { - super(serviceRegistry, action); - this.allowed = allowed; - } - - public boolean isAllowed(CMISNodeInfo nodeInfo) - { - return allowed; - } - - @Override - public String toString() - { - StringBuilder builder = new StringBuilder(); - builder.append("FixedValueActionEvaluator[action=").append(getAction()); - builder.append(", allowed=").append(allowed).append("]"); - return builder.toString(); - } -} +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Action Evaluator whose evaluation is fixed + * + * @author florian.mueller + * + */ +public class FixedValueActionEvaluator extends AbstractActionEvaluator +{ + private boolean allowed; + + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param action Action + * @param allowed boolean + */ + protected FixedValueActionEvaluator(ServiceRegistry serviceRegistry, Action action, boolean allowed) + { + super(serviceRegistry, action); + this.allowed = allowed; + } + + public boolean isAllowed(CMISNodeInfo nodeInfo) + { + return allowed; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("FixedValueActionEvaluator[action=").append(getAction()); + builder.append(", allowed=").append(allowed).append("]"); + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/FixedValueProperty.java b/source/java/org/alfresco/opencmis/mapping/FixedValueProperty.java index 155110f38a..48456a299b 100644 --- a/source/java/org/alfresco/opencmis/mapping/FixedValueProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/FixedValueProperty.java @@ -1,37 +1,37 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; - -/** - * Property accessor for fixed value mapping (eg to null, true, etc) - * - * @author florian.mueller - */ -public class FixedValueProperty extends AbstractProperty -{ - private Serializable value; - - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - * @param propertyName String - * @param value Serializable - */ - public FixedValueProperty(ServiceRegistry serviceRegistry, CMISConnector connector, String propertyName, - Serializable value) - { - super(serviceRegistry, connector, propertyName); - this.value = value; - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return value; - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; + +/** + * Property accessor for fixed value mapping (eg to null, true, etc) + * + * @author florian.mueller + */ +public class FixedValueProperty extends AbstractProperty +{ + private Serializable value; + + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + * @param propertyName String + * @param value Serializable + */ + public FixedValueProperty(ServiceRegistry serviceRegistry, CMISConnector connector, String propertyName, + Serializable value) + { + super(serviceRegistry, connector, propertyName); + this.value = value; + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return value; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsImmutableProperty.java b/source/java/org/alfresco/opencmis/mapping/IsImmutableProperty.java index 01bdf6d691..a3f8ab1c70 100644 --- a/source/java/org/alfresco/opencmis/mapping/IsImmutableProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/IsImmutableProperty.java @@ -1,42 +1,42 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockType; -import org.alfresco.service.cmr.repository.NodeRef; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Property accessor for CMIS is immutable property - * - * @author florian.mueller - */ -public class IsImmutableProperty extends AbstractProperty -{ - private LockService lockService; - - /** - * Construct - */ - public IsImmutableProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.IS_IMMUTABLE); - lockService = serviceRegistry.getLockService(); - } - - @Override - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - NodeRef nodeRef = nodeInfo.getNodeRef(); - if (nodeInfo.isVersion() || (nodeInfo.hasPWC() && !nodeInfo.isPWC()) || (lockService.getLockType(nodeRef) == LockType.READ_ONLY_LOCK)) - { - return true; - } - - return false; - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Property accessor for CMIS is immutable property + * + * @author florian.mueller + */ +public class IsImmutableProperty extends AbstractProperty +{ + private LockService lockService; + + /** + * Construct + */ + public IsImmutableProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.IS_IMMUTABLE); + lockService = serviceRegistry.getLockService(); + } + + @Override + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + NodeRef nodeRef = nodeInfo.getNodeRef(); + if (nodeInfo.isVersion() || (nodeInfo.hasPWC() && !nodeInfo.isPWC()) || (lockService.getLockType(nodeRef) == LockType.READ_ONLY_LOCK)) + { + return true; + } + + return false; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsLatestMajorVersionProperty.java b/source/java/org/alfresco/opencmis/mapping/IsLatestMajorVersionProperty.java index 777503f263..0a234bc9a1 100644 --- a/source/java/org/alfresco/opencmis/mapping/IsLatestMajorVersionProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/IsLatestMajorVersionProperty.java @@ -1,32 +1,32 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Accessor for CMIS is latest major version property - * - * @author florian.mueller - */ -public class IsLatestMajorVersionProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public IsLatestMajorVersionProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.IS_LATEST_MAJOR_VERSION); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.isLatestMajorVersion(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS is latest major version property + * + * @author florian.mueller + */ +public class IsLatestMajorVersionProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public IsLatestMajorVersionProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.IS_LATEST_MAJOR_VERSION); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.isLatestMajorVersion(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsLatestVersionProperty.java b/source/java/org/alfresco/opencmis/mapping/IsLatestVersionProperty.java index 8ebba7e069..a4a247bcec 100644 --- a/source/java/org/alfresco/opencmis/mapping/IsLatestVersionProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/IsLatestVersionProperty.java @@ -1,30 +1,30 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Accesser for CMIS is latest version property - * - * @author florian.mueller - */ -public class IsLatestVersionProperty extends AbstractProperty -{ - /** - * Construct - */ - public IsLatestVersionProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.IS_LATEST_VERSION); - } - - @Override - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.isLatestVersion(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accesser for CMIS is latest version property + * + * @author florian.mueller + */ +public class IsLatestVersionProperty extends AbstractProperty +{ + /** + * Construct + */ + public IsLatestVersionProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.IS_LATEST_VERSION); + } + + @Override + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.isLatestVersion(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsMajorVersionProperty.java b/source/java/org/alfresco/opencmis/mapping/IsMajorVersionProperty.java index 1d3b887882..6e4ff41f7d 100644 --- a/source/java/org/alfresco/opencmis/mapping/IsMajorVersionProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/IsMajorVersionProperty.java @@ -1,32 +1,32 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Accessor for CMIS is major version property - * - * @author florian.mueller - */ -public class IsMajorVersionProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public IsMajorVersionProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.IS_MAJOR_VERSION); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.isMajorVersion(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS is major version property + * + * @author florian.mueller + */ +public class IsMajorVersionProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public IsMajorVersionProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.IS_MAJOR_VERSION); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.isMajorVersion(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsVersionSeriesCheckedOutProperty.java b/source/java/org/alfresco/opencmis/mapping/IsVersionSeriesCheckedOutProperty.java index 176869b647..f4d054a92a 100644 --- a/source/java/org/alfresco/opencmis/mapping/IsVersionSeriesCheckedOutProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/IsVersionSeriesCheckedOutProperty.java @@ -1,30 +1,30 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS version series checked out property - * - * @author florian.mueller - */ -public class IsVersionSeriesCheckedOutProperty extends AbstractProperty -{ - /** - * Construct - */ - public IsVersionSeriesCheckedOutProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.IS_VERSION_SERIES_CHECKED_OUT); - } - - @Override - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.hasPWC(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS version series checked out property + * + * @author florian.mueller + */ +public class IsVersionSeriesCheckedOutProperty extends AbstractProperty +{ + /** + * Construct + */ + public IsVersionSeriesCheckedOutProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.IS_VERSION_SERIES_CHECKED_OUT); + } + + @Override + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.hasPWC(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ModificationDateProperty.java b/source/java/org/alfresco/opencmis/mapping/ModificationDateProperty.java index c9e1e885d8..997a3bf692 100644 --- a/source/java/org/alfresco/opencmis/mapping/ModificationDateProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/ModificationDateProperty.java @@ -1,32 +1,32 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS object modification date property. - * - * @author florian.mueller - */ -public class ModificationDateProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public ModificationDateProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.LAST_MODIFICATION_DATE); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.getModificationDate(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS object modification date property. + * + * @author florian.mueller + */ +public class ModificationDateProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public ModificationDateProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.LAST_MODIFICATION_DATE); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.getModificationDate(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/NameProperty.java b/source/java/org/alfresco/opencmis/mapping/NameProperty.java index 77b90dac84..f7bd8e02ce 100644 --- a/source/java/org/alfresco/opencmis/mapping/NameProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/NameProperty.java @@ -1,38 +1,38 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.namespace.QName; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS object name property. - * - * @author florian.mueller - */ -public class NameProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - */ - public NameProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.NAME); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.getName(); - } - - public QName getMappedProperty() - { - return ContentModel.PROP_NAME; - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS object name property. + * + * @author florian.mueller + */ +public class NameProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + */ + public NameProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.NAME); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.getName(); + } + + public QName getMappedProperty() + { + return ContentModel.PROP_NAME; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/NodeRefProperty.java b/source/java/org/alfresco/opencmis/mapping/NodeRefProperty.java index eba4d405a3..0c5d49424d 100644 --- a/source/java/org/alfresco/opencmis/mapping/NodeRefProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/NodeRefProperty.java @@ -1,45 +1,45 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; - -/** - * Get the CMIS object id property. - */ -public class NodeRefProperty extends AbstractProperty -{ - public static final String NodeRefPropertyId = "alfcmis:nodeRef"; - - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - */ - public NodeRefProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, NodeRefPropertyId); - } - - @Override - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - if (nodeInfo.getNodeRef() != null) - { - if (nodeInfo.isCurrentVersion()) - { - return nodeInfo.getCurrentNodeNodeRef().toString(); - } else - { - return nodeInfo.getNodeRef().toString(); - } - } else if (nodeInfo.getAssociationRef() != null) - { - return nodeInfo.getAssociationRef().toString(); - } - - return null; - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; + +/** + * Get the CMIS object id property. + */ +public class NodeRefProperty extends AbstractProperty +{ + public static final String NodeRefPropertyId = "alfcmis:nodeRef"; + + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + */ + public NodeRefProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, NodeRefPropertyId); + } + + @Override + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + if (nodeInfo.getNodeRef() != null) + { + if (nodeInfo.isCurrentVersion()) + { + return nodeInfo.getCurrentNodeNodeRef().toString(); + } else + { + return nodeInfo.getNodeRef().toString(); + } + } else if (nodeInfo.getAssociationRef() != null) + { + return nodeInfo.getAssociationRef().toString(); + } + + return null; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ObjectIdProperty.java b/source/java/org/alfresco/opencmis/mapping/ObjectIdProperty.java index a2dbf7164f..c222ca2354 100644 --- a/source/java/org/alfresco/opencmis/mapping/ObjectIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/ObjectIdProperty.java @@ -1,32 +1,32 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS object id property. - * - * @author florian.mueller - */ -public class ObjectIdProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public ObjectIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.OBJECT_ID); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.getObjectId(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS object id property. + * + * @author florian.mueller + */ +public class ObjectIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public ObjectIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.OBJECT_ID); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.getObjectId(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ObjectTypeIdProperty.java b/source/java/org/alfresco/opencmis/mapping/ObjectTypeIdProperty.java index f9da51dc06..bdd4a04886 100644 --- a/source/java/org/alfresco/opencmis/mapping/ObjectTypeIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/ObjectTypeIdProperty.java @@ -1,41 +1,41 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; -import java.util.Collections; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISDictionaryService; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS object type id property - * - * @author florian.mueller - */ -public class ObjectTypeIdProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - * @param dictionaryService CMISDictionaryService - */ - public ObjectTypeIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector, - CMISDictionaryService dictionaryService) - { - super(serviceRegistry, connector, PropertyIds.OBJECT_TYPE_ID); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - if(nodeInfo.getType() == null) - { - return (Serializable) Collections.emptyList(); - } - - return nodeInfo.getType().getTypeId(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.Collections; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS object type id property + * + * @author florian.mueller + */ +public class ObjectTypeIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + * @param dictionaryService CMISDictionaryService + */ + public ObjectTypeIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector, + CMISDictionaryService dictionaryService) + { + super(serviceRegistry, connector, PropertyIds.OBJECT_TYPE_ID); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + if(nodeInfo.getType() == null) + { + return (Serializable) Collections.emptyList(); + } + + return nodeInfo.getType().getTypeId(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ParentActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/ParentActionEvaluator.java index e76ba9a763..f6d3bbcde7 100644 --- a/source/java/org/alfresco/opencmis/mapping/ParentActionEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/ParentActionEvaluator.java @@ -1,50 +1,50 @@ -package org.alfresco.opencmis.mapping; - -import java.util.List; - -import org.alfresco.opencmis.dictionary.CMISNodeInfo; - -/** - * Action Evaluator whose evaluation takes place on parent - * - * @author florian.mueller - */ -public class ParentActionEvaluator extends AbstractActionEvaluator -{ - private AbstractActionEvaluator evaluator; - - /** - * Construct - * - * @param evaluator AbstractActionEvaluator - */ - protected ParentActionEvaluator(AbstractActionEvaluator evaluator) - { - super(evaluator.getServiceRegistry(), evaluator.getAction()); - this.evaluator = evaluator; - } - - public boolean isAllowed(CMISNodeInfo nodeInfo) - { - if (nodeInfo.isRootFolder()) - { - return false; - } - - List parents = nodeInfo.getParents(); - if (!parents.isEmpty()) - { - return evaluator.isAllowed(parents.get(0)); - } - - return false; - } - - @Override - public String toString() - { - StringBuilder builder = new StringBuilder(); - builder.append("ParentActionEvaluator[evaluator=").append(evaluator).append("]"); - return builder.toString(); - } -} +package org.alfresco.opencmis.mapping; + +import java.util.List; + +import org.alfresco.opencmis.dictionary.CMISNodeInfo; + +/** + * Action Evaluator whose evaluation takes place on parent + * + * @author florian.mueller + */ +public class ParentActionEvaluator extends AbstractActionEvaluator +{ + private AbstractActionEvaluator evaluator; + + /** + * Construct + * + * @param evaluator AbstractActionEvaluator + */ + protected ParentActionEvaluator(AbstractActionEvaluator evaluator) + { + super(evaluator.getServiceRegistry(), evaluator.getAction()); + this.evaluator = evaluator; + } + + public boolean isAllowed(CMISNodeInfo nodeInfo) + { + if (nodeInfo.isRootFolder()) + { + return false; + } + + List parents = nodeInfo.getParents(); + if (!parents.isEmpty()) + { + return evaluator.isAllowed(parents.get(0)); + } + + return false; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("ParentActionEvaluator[evaluator=").append(evaluator).append("]"); + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ParentProperty.java b/source/java/org/alfresco/opencmis/mapping/ParentProperty.java index 4d4f9becd5..e67d48526f 100644 --- a/source/java/org/alfresco/opencmis/mapping/ParentProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/ParentProperty.java @@ -1,45 +1,45 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; -import java.util.List; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS parent property - * - * @author florian.mueller - * - */ -public class ParentProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public ParentProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.PARENT_ID); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - if (nodeInfo.isRootFolder()) - { - return null; - } - - List parents = nodeInfo.getParents(); - if (!parents.isEmpty()) - { - return parents.get(0).getObjectId(); - } - - return null; - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.List; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS parent property + * + * @author florian.mueller + * + */ +public class ParentProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public ParentProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.PARENT_ID); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + if (nodeInfo.isRootFolder()) + { + return null; + } + + List parents = nodeInfo.getParents(); + if (!parents.isEmpty()) + { + return parents.get(0).getObjectId(); + } + + return null; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/PathProperty.java b/source/java/org/alfresco/opencmis/mapping/PathProperty.java index d620f40435..8781c38815 100644 --- a/source/java/org/alfresco/opencmis/mapping/PathProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/PathProperty.java @@ -1,32 +1,32 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS path property. - * - * @author florian.mueller - */ -public class PathProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public PathProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.PATH); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.getPath(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS path property. + * + * @author florian.mueller + */ +public class PathProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public PathProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.PATH); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.getPath(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/PermissionActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/PermissionActionEvaluator.java index add6abbdc9..397fc4aab4 100644 --- a/source/java/org/alfresco/opencmis/mapping/PermissionActionEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/PermissionActionEvaluator.java @@ -1,59 +1,59 @@ -package org.alfresco.opencmis.mapping; - -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.apache.chemistry.opencmis.commons.enums.Action; - -/** - * Alfresco Permission based Action Evaluator - * - * @author davidc - */ -public class PermissionActionEvaluator extends AbstractActionEvaluator -{ - private String[] permissions; - private PermissionService permissionService; - - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param action Action - * @param permission String... - */ - protected PermissionActionEvaluator(ServiceRegistry serviceRegistry, Action action, String... permission) - { - super(serviceRegistry, action); - this.permissions = permission; - this.permissionService = serviceRegistry.getPermissionService(); - } - - public boolean isAllowed(CMISNodeInfo nodeInfo) - { - for (String permission : permissions) - { - if (permissionService.hasPermission(nodeInfo.getNodeRef(), permission) == AccessStatus.DENIED) - { - return false; - } - } - return true; - } - - @Override - public String toString() - { - StringBuilder builder = new StringBuilder(); - builder.append("PermissionActionEvaluator[action=").append(getAction()); - builder.append(", permissions="); - for (String permission : permissions) - { - builder.append(permission).append(","); - } - builder.append("]"); - return builder.toString(); - } - -} +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Alfresco Permission based Action Evaluator + * + * @author davidc + */ +public class PermissionActionEvaluator extends AbstractActionEvaluator +{ + private String[] permissions; + private PermissionService permissionService; + + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param action Action + * @param permission String... + */ + protected PermissionActionEvaluator(ServiceRegistry serviceRegistry, Action action, String... permission) + { + super(serviceRegistry, action); + this.permissions = permission; + this.permissionService = serviceRegistry.getPermissionService(); + } + + public boolean isAllowed(CMISNodeInfo nodeInfo) + { + for (String permission : permissions) + { + if (permissionService.hasPermission(nodeInfo.getNodeRef(), permission) == AccessStatus.DENIED) + { + return false; + } + } + return true; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("PermissionActionEvaluator[action=").append(getAction()); + builder.append(", permissions="); + for (String permission : permissions) + { + builder.append(permission).append(","); + } + builder.append("]"); + return builder.toString(); + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/RootFolderEvaluator.java b/source/java/org/alfresco/opencmis/mapping/RootFolderEvaluator.java index 3d3cbc5302..214a9d04a5 100644 --- a/source/java/org/alfresco/opencmis/mapping/RootFolderEvaluator.java +++ b/source/java/org/alfresco/opencmis/mapping/RootFolderEvaluator.java @@ -1,29 +1,29 @@ -package org.alfresco.opencmis.mapping; - -import org.alfresco.opencmis.dictionary.CMISActionEvaluator; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; - -public class RootFolderEvaluator extends AbstractActionEvaluator -{ - private CMISActionEvaluator folderEvaluator; - private boolean rootFolderValue; - - protected RootFolderEvaluator(ServiceRegistry serviceRegistry, CMISActionEvaluator folderEvaluator, - boolean rootFolderValue) - { - super(serviceRegistry, folderEvaluator.getAction()); - this.folderEvaluator = folderEvaluator; - this.rootFolderValue = rootFolderValue; - } - - public boolean isAllowed(CMISNodeInfo nodeInfo) - { - if (nodeInfo.isRootFolder()) - { - return rootFolderValue; - } - - return folderEvaluator.isAllowed(nodeInfo); - } -} +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.dictionary.CMISActionEvaluator; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; + +public class RootFolderEvaluator extends AbstractActionEvaluator +{ + private CMISActionEvaluator folderEvaluator; + private boolean rootFolderValue; + + protected RootFolderEvaluator(ServiceRegistry serviceRegistry, CMISActionEvaluator folderEvaluator, + boolean rootFolderValue) + { + super(serviceRegistry, folderEvaluator.getAction()); + this.folderEvaluator = folderEvaluator; + this.rootFolderValue = rootFolderValue; + } + + public boolean isAllowed(CMISNodeInfo nodeInfo) + { + if (nodeInfo.isRootFolder()) + { + return rootFolderValue; + } + + return folderEvaluator.isAllowed(nodeInfo); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/SecondaryTypesProperty.java b/source/java/org/alfresco/opencmis/mapping/SecondaryTypesProperty.java index a159e0cb4f..d28ad16e5f 100644 --- a/source/java/org/alfresco/opencmis/mapping/SecondaryTypesProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/SecondaryTypesProperty.java @@ -1,56 +1,56 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Set; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * - * @author steveglover - * - */ -public class SecondaryTypesProperty extends AbstractProperty -{ - private CMISMapping cmisMapping; - - /** - * Construct - */ - public SecondaryTypesProperty(ServiceRegistry serviceRegistry, CMISConnector connector, CMISMapping cmisMapping) - { - super(serviceRegistry, connector, PropertyIds.SECONDARY_OBJECT_TYPE_IDS); - this.cmisMapping = cmisMapping; - } - - @Override - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - NodeRef nodeRef = nodeInfo.getNodeRef(); - - if(nodeRef == null || nodeInfo.getType() == null) - { - // If the nodeRef or type is null, we can't handle it so return an empty list - return (Serializable) Collections.emptyList(); - } - - Set aspects = nodeInfo.getNodeAspects(); - ArrayList results = new ArrayList(aspects.size()); - for (QName aspect : aspects) - { - String typeId = cmisMapping.getCmisTypeId(aspect); - if (typeId != null) - { - results.add(typeId); - } - } - return results; - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * + * @author steveglover + * + */ +public class SecondaryTypesProperty extends AbstractProperty +{ + private CMISMapping cmisMapping; + + /** + * Construct + */ + public SecondaryTypesProperty(ServiceRegistry serviceRegistry, CMISConnector connector, CMISMapping cmisMapping) + { + super(serviceRegistry, connector, PropertyIds.SECONDARY_OBJECT_TYPE_IDS); + this.cmisMapping = cmisMapping; + } + + @Override + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + NodeRef nodeRef = nodeInfo.getNodeRef(); + + if(nodeRef == null || nodeInfo.getType() == null) + { + // If the nodeRef or type is null, we can't handle it so return an empty list + return (Serializable) Collections.emptyList(); + } + + Set aspects = nodeInfo.getNodeAspects(); + ArrayList results = new ArrayList(aspects.size()); + for (QName aspect : aspects) + { + String typeId = cmisMapping.getCmisTypeId(aspect); + if (typeId != null) + { + results.add(typeId); + } + } + return results; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/SourceIdProperty.java b/source/java/org/alfresco/opencmis/mapping/SourceIdProperty.java index 641a3a631f..4ce1d0a2e8 100644 --- a/source/java/org/alfresco/opencmis/mapping/SourceIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/SourceIdProperty.java @@ -1,32 +1,32 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Accessor for the Source Id (relationship) - * - * @author florian.mueller - */ -public class SourceIdProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public SourceIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.SOURCE_ID); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return createNodeInfo(nodeInfo.getAssociationRef().getSourceRef()).getObjectId(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for the Source Id (relationship) + * + * @author florian.mueller + */ +public class SourceIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public SourceIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.SOURCE_ID); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return createNodeInfo(nodeInfo.getAssociationRef().getSourceRef()).getObjectId(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/TargetIdProperty.java b/source/java/org/alfresco/opencmis/mapping/TargetIdProperty.java index a4848c4845..6df3aef893 100644 --- a/source/java/org/alfresco/opencmis/mapping/TargetIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/TargetIdProperty.java @@ -1,34 +1,34 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.NodeRef; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Accessor for the Target Id (relationship) - * - * @author florian.mueller - */ -public class TargetIdProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public TargetIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.TARGET_ID); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - NodeRef targetNodeRef = nodeInfo.getAssociationRef().getTargetRef(); - return createNodeInfo(targetNodeRef).getObjectId(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for the Target Id (relationship) + * + * @author florian.mueller + */ +public class TargetIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public TargetIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.TARGET_ID); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + NodeRef targetNodeRef = nodeInfo.getAssociationRef().getTargetRef(); + return createNodeInfo(targetNodeRef).getObjectId(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/VersionLabelProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionLabelProperty.java index 33eaac8527..015f7fc706 100644 --- a/source/java/org/alfresco/opencmis/mapping/VersionLabelProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/VersionLabelProperty.java @@ -1,30 +1,30 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * @author florian.mueller - */ -public class VersionLabelProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public VersionLabelProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.VERSION_LABEL); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return nodeInfo.getVersionLabel(); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * @author florian.mueller + */ +public class VersionLabelProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public VersionLabelProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.VERSION_LABEL); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return nodeInfo.getVersionLabel(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutByProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutByProperty.java index 81f8aa05dc..808b8d069e 100644 --- a/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutByProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutByProperty.java @@ -1,45 +1,45 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS version series checked out by property - * - * @author florian.mueller - */ -public class VersionSeriesCheckedOutByProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public VersionSeriesCheckedOutByProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.VERSION_SERIES_CHECKED_OUT_BY); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - if (!nodeInfo.hasPWC()) - { - return null; - } - - if (nodeInfo.isPWC()) - { - return nodeInfo.getNodeProps().get(ContentModel.PROP_WORKING_COPY_OWNER); - } else - { - return getServiceRegistry().getNodeService().getProperty(nodeInfo.getCurrentNodeNodeRef(), - ContentModel.PROP_LOCK_OWNER); - } - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS version series checked out by property + * + * @author florian.mueller + */ +public class VersionSeriesCheckedOutByProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public VersionSeriesCheckedOutByProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.VERSION_SERIES_CHECKED_OUT_BY); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + if (!nodeInfo.hasPWC()) + { + return null; + } + + if (nodeInfo.isPWC()) + { + return nodeInfo.getNodeProps().get(ContentModel.PROP_WORKING_COPY_OWNER); + } else + { + return getServiceRegistry().getNodeService().getProperty(nodeInfo.getCurrentNodeNodeRef(), + ContentModel.PROP_LOCK_OWNER); + } + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java index 25f543155d..00c2ffab48 100644 --- a/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java @@ -1,37 +1,37 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * Get the CMIS version series checked out id property - * - * @author florian.mueller - */ -public class VersionSeriesCheckedOutIdProperty extends AbstractProperty -{ - /** - * Construct - * - * @param serviceRegistry ServiceRegistry - * @param connector CMISConnector - */ - public VersionSeriesCheckedOutIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID); - } - - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - if (!nodeInfo.hasPWC()) - { - return null; - } - - return connector.constructObjectId(nodeInfo.getCurrentNodeId(), CMISConnector.PWC_VERSION_LABEL); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS version series checked out id property + * + * @author florian.mueller + */ +public class VersionSeriesCheckedOutIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry ServiceRegistry + * @param connector CMISConnector + */ + public VersionSeriesCheckedOutIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID); + } + + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + if (!nodeInfo.hasPWC()) + { + return null; + } + + return connector.constructObjectId(nodeInfo.getCurrentNodeId(), CMISConnector.PWC_VERSION_LABEL); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java index f1dfb9319e..d552787094 100644 --- a/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java @@ -1,28 +1,28 @@ -package org.alfresco.opencmis.mapping; - -import java.io.Serializable; - -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.service.ServiceRegistry; -import org.apache.chemistry.opencmis.commons.PropertyIds; - -/** - * @author florian.mueller - */ -public class VersionSeriesIdProperty extends AbstractProperty -{ - /** - * Construct - */ - public VersionSeriesIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) - { - super(serviceRegistry, connector, PropertyIds.VERSION_SERIES_ID); - } - - @Override - public Serializable getValueInternal(CMISNodeInfo nodeInfo) - { - return connector.constructObjectId(nodeInfo.getCurrentNodeId(), null); - } -} +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * @author florian.mueller + */ +public class VersionSeriesIdProperty extends AbstractProperty +{ + /** + * Construct + */ + public VersionSeriesIdProperty(ServiceRegistry serviceRegistry, CMISConnector connector) + { + super(serviceRegistry, connector, PropertyIds.VERSION_SERIES_ID); + } + + @Override + public Serializable getValueInternal(CMISNodeInfo nodeInfo) + { + return connector.constructObjectId(nodeInfo.getCurrentNodeId(), null); + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISQueryService.java b/source/java/org/alfresco/opencmis/search/CMISQueryService.java index 3185c3c4b0..ddd19431dc 100644 --- a/source/java/org/alfresco/opencmis/search/CMISQueryService.java +++ b/source/java/org/alfresco/opencmis/search/CMISQueryService.java @@ -1,20 +1,20 @@ -package org.alfresco.opencmis.search; - -import org.alfresco.service.cmr.repository.StoreRef; -import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; -import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; - -public interface CMISQueryService -{ - CMISResultSet query(CMISQueryOptions options); - - CMISResultSet query(String query, StoreRef storeRef); - - boolean getPwcSearchable(); - - boolean getAllVersionsSearchable(); - - CapabilityQuery getQuerySupport(); - - CapabilityJoin getJoinSupport(); +package org.alfresco.opencmis.search; + +import org.alfresco.service.cmr.repository.StoreRef; +import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; +import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; + +public interface CMISQueryService +{ + CMISResultSet query(CMISQueryOptions options); + + CMISResultSet query(String query, StoreRef storeRef); + + boolean getPwcSearchable(); + + boolean getAllVersionsSearchable(); + + CapabilityQuery getQuerySupport(); + + CapabilityJoin getJoinSupport(); } \ No newline at end of file diff --git a/source/java/org/alfresco/opencmis/search/CMISQueryServiceImpl.java b/source/java/org/alfresco/opencmis/search/CMISQueryServiceImpl.java index 2d65f55fec..25fda188cd 100644 --- a/source/java/org/alfresco/opencmis/search/CMISQueryServiceImpl.java +++ b/source/java/org/alfresco/opencmis/search/CMISQueryServiceImpl.java @@ -1,220 +1,220 @@ -package org.alfresco.opencmis.search; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.alfresco.opencmis.dictionary.CMISDictionaryService; -import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; -import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet; -import org.alfresco.repo.search.impl.querymodel.Query; -import org.alfresco.repo.search.impl.querymodel.QueryEngine; -import org.alfresco.repo.search.impl.querymodel.QueryEngineResults; -import org.alfresco.repo.search.impl.querymodel.QueryModelException; -import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.LimitBy; -import org.alfresco.service.cmr.search.QueryConsistency; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.util.Pair; -import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; -import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; -import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; - -/** - * @author andyh - */ -public class CMISQueryServiceImpl implements CMISQueryService -{ - private CMISDictionaryService cmisDictionaryService; - - private QueryEngine luceneQueryEngine; - private QueryEngine dbQueryEngine; - - private NodeService nodeService; - - private DictionaryService alfrescoDictionaryService; - - public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService) - { - this.cmisDictionaryService = cmisDictionaryService; - } - - /** - * @param queryEngine - * the luceneQueryEngine to set - */ - public void setLuceneQueryEngine(QueryEngine queryEngine) - { - this.luceneQueryEngine = queryEngine; - } - - /** - * @param queryEngine - * the dbQueryEngine to set - */ - public void setDbQueryEngine(QueryEngine queryEngine) - { - this.dbQueryEngine = queryEngine; - } - - /** - * @param nodeService - * the nodeService to set - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param alfrescoDictionaryService - * the Alfresco Dictionary Service to set - */ - public void setAlfrescoDictionaryService(DictionaryService alfrescoDictionaryService) - { - this.alfrescoDictionaryService = alfrescoDictionaryService; - } - - public CMISResultSet query(CMISQueryOptions options) - { - Pair resultPair = executeQuerySwitchingImpl(options); - - Query query = resultPair.getFirst(); - QueryEngineResults results = resultPair.getSecond(); - - Map wrapped = new HashMap(); - Map, ResultSet> map = results.getResults(); - for (Set group : map.keySet()) - { - ResultSet current = map.get(group); - for (String selector : group) - { - wrapped.put(selector, filterNotExistingNodes(current)); - } - } - LimitBy limitBy = null; - if ((null != results.getResults()) && !results.getResults().isEmpty() - && (null != results.getResults().values()) && !results.getResults().values().isEmpty()) - { - limitBy = results.getResults().values().iterator().next().getResultSetMetaData().getLimitedBy(); - } - CMISResultSet cmis = new CMISResultSet(wrapped, options, limitBy, nodeService, query, cmisDictionaryService, - alfrescoDictionaryService); - return cmis; - } - - private Pair executeQuerySwitchingImpl(CMISQueryOptions options) - { - switch (options.getQueryConsistency()) - { - case TRANSACTIONAL_IF_POSSIBLE : - { - try - { - return executeQueryUsingEngine(dbQueryEngine, options); - } - catch(QueryModelException qme) - { - return executeQueryUsingEngine(luceneQueryEngine, options); - } - } - case TRANSACTIONAL : - { - return executeQueryUsingEngine(dbQueryEngine, options); - } - case EVENTUAL : - case DEFAULT : - default : - { - return executeQueryUsingEngine(luceneQueryEngine, options); - } - } - } - - private Pair executeQueryUsingEngine(QueryEngine queryEngine, CMISQueryOptions options) - { - CapabilityJoin joinSupport = getJoinSupport(); - if (options.getQueryMode() == CMISQueryOptions.CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS) - { - joinSupport = CapabilityJoin.INNERONLY; - } - - // TODO: Refactor to avoid duplication of valid scopes here and in - // CMISQueryParser - - BaseTypeId[] validScopes = (options.getQueryMode() == CMISQueryMode.CMS_STRICT) ? CmisFunctionEvaluationContext.STRICT_SCOPES - : CmisFunctionEvaluationContext.ALFRESCO_SCOPES; - CmisFunctionEvaluationContext functionContext = new CmisFunctionEvaluationContext(); - functionContext.setCmisDictionaryService(cmisDictionaryService); - functionContext.setNodeService(nodeService); - functionContext.setValidScopes(validScopes); - - CMISQueryParser parser = new CMISQueryParser(options, cmisDictionaryService, joinSupport); - QueryConsistency queryConsistency = options.getQueryConsistency(); - if (queryConsistency == QueryConsistency.DEFAULT) - { - options.setQueryConsistency(QueryConsistency.EVENTUAL); - } - - Query query = parser.parse(queryEngine.getQueryModelFactory(), functionContext); - QueryEngineResults queryEngineResults = queryEngine.executeQuery(query, options, functionContext); - - return new Pair(query, queryEngineResults); - } - - /* MNT-8804 filter ResultSet for nodes with corrupted indexes */ - private ResultSet filterNotExistingNodes(ResultSet resultSet) - { - if (resultSet instanceof PagingLuceneResultSet) - { - ResultSet wrapped = ((PagingLuceneResultSet)resultSet).getWrapped(); - - if (wrapped instanceof FilteringResultSet) - { - FilteringResultSet filteringResultSet = (FilteringResultSet)wrapped; - - for (int i = 0; i < filteringResultSet.length(); i++) - { - NodeRef nodeRef = filteringResultSet.getNodeRef(i); - /* filter node if it does not exist */ - if (!nodeService.exists(nodeRef)) - { - filteringResultSet.setIncluded(i, false); - } - } - } - } - - return resultSet; - } - - public CMISResultSet query(String query, StoreRef storeRef) - { - CMISQueryOptions options = new CMISQueryOptions(query, storeRef); - return query(options); - } - - public boolean getPwcSearchable() - { - return true; - } - - public boolean getAllVersionsSearchable() - { - return false; - } - - public CapabilityQuery getQuerySupport() - { - return CapabilityQuery.BOTHCOMBINED; - } - - public CapabilityJoin getJoinSupport() - { - return CapabilityJoin.NONE; - } -} +package org.alfresco.opencmis.search; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; +import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet; +import org.alfresco.repo.search.impl.querymodel.Query; +import org.alfresco.repo.search.impl.querymodel.QueryEngine; +import org.alfresco.repo.search.impl.querymodel.QueryEngineResults; +import org.alfresco.repo.search.impl.querymodel.QueryModelException; +import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.QueryConsistency; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.util.Pair; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; +import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; + +/** + * @author andyh + */ +public class CMISQueryServiceImpl implements CMISQueryService +{ + private CMISDictionaryService cmisDictionaryService; + + private QueryEngine luceneQueryEngine; + private QueryEngine dbQueryEngine; + + private NodeService nodeService; + + private DictionaryService alfrescoDictionaryService; + + public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService) + { + this.cmisDictionaryService = cmisDictionaryService; + } + + /** + * @param queryEngine + * the luceneQueryEngine to set + */ + public void setLuceneQueryEngine(QueryEngine queryEngine) + { + this.luceneQueryEngine = queryEngine; + } + + /** + * @param queryEngine + * the dbQueryEngine to set + */ + public void setDbQueryEngine(QueryEngine queryEngine) + { + this.dbQueryEngine = queryEngine; + } + + /** + * @param nodeService + * the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param alfrescoDictionaryService + * the Alfresco Dictionary Service to set + */ + public void setAlfrescoDictionaryService(DictionaryService alfrescoDictionaryService) + { + this.alfrescoDictionaryService = alfrescoDictionaryService; + } + + public CMISResultSet query(CMISQueryOptions options) + { + Pair resultPair = executeQuerySwitchingImpl(options); + + Query query = resultPair.getFirst(); + QueryEngineResults results = resultPair.getSecond(); + + Map wrapped = new HashMap(); + Map, ResultSet> map = results.getResults(); + for (Set group : map.keySet()) + { + ResultSet current = map.get(group); + for (String selector : group) + { + wrapped.put(selector, filterNotExistingNodes(current)); + } + } + LimitBy limitBy = null; + if ((null != results.getResults()) && !results.getResults().isEmpty() + && (null != results.getResults().values()) && !results.getResults().values().isEmpty()) + { + limitBy = results.getResults().values().iterator().next().getResultSetMetaData().getLimitedBy(); + } + CMISResultSet cmis = new CMISResultSet(wrapped, options, limitBy, nodeService, query, cmisDictionaryService, + alfrescoDictionaryService); + return cmis; + } + + private Pair executeQuerySwitchingImpl(CMISQueryOptions options) + { + switch (options.getQueryConsistency()) + { + case TRANSACTIONAL_IF_POSSIBLE : + { + try + { + return executeQueryUsingEngine(dbQueryEngine, options); + } + catch(QueryModelException qme) + { + return executeQueryUsingEngine(luceneQueryEngine, options); + } + } + case TRANSACTIONAL : + { + return executeQueryUsingEngine(dbQueryEngine, options); + } + case EVENTUAL : + case DEFAULT : + default : + { + return executeQueryUsingEngine(luceneQueryEngine, options); + } + } + } + + private Pair executeQueryUsingEngine(QueryEngine queryEngine, CMISQueryOptions options) + { + CapabilityJoin joinSupport = getJoinSupport(); + if (options.getQueryMode() == CMISQueryOptions.CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS) + { + joinSupport = CapabilityJoin.INNERONLY; + } + + // TODO: Refactor to avoid duplication of valid scopes here and in + // CMISQueryParser + + BaseTypeId[] validScopes = (options.getQueryMode() == CMISQueryMode.CMS_STRICT) ? CmisFunctionEvaluationContext.STRICT_SCOPES + : CmisFunctionEvaluationContext.ALFRESCO_SCOPES; + CmisFunctionEvaluationContext functionContext = new CmisFunctionEvaluationContext(); + functionContext.setCmisDictionaryService(cmisDictionaryService); + functionContext.setNodeService(nodeService); + functionContext.setValidScopes(validScopes); + + CMISQueryParser parser = new CMISQueryParser(options, cmisDictionaryService, joinSupport); + QueryConsistency queryConsistency = options.getQueryConsistency(); + if (queryConsistency == QueryConsistency.DEFAULT) + { + options.setQueryConsistency(QueryConsistency.EVENTUAL); + } + + Query query = parser.parse(queryEngine.getQueryModelFactory(), functionContext); + QueryEngineResults queryEngineResults = queryEngine.executeQuery(query, options, functionContext); + + return new Pair(query, queryEngineResults); + } + + /* MNT-8804 filter ResultSet for nodes with corrupted indexes */ + private ResultSet filterNotExistingNodes(ResultSet resultSet) + { + if (resultSet instanceof PagingLuceneResultSet) + { + ResultSet wrapped = ((PagingLuceneResultSet)resultSet).getWrapped(); + + if (wrapped instanceof FilteringResultSet) + { + FilteringResultSet filteringResultSet = (FilteringResultSet)wrapped; + + for (int i = 0; i < filteringResultSet.length(); i++) + { + NodeRef nodeRef = filteringResultSet.getNodeRef(i); + /* filter node if it does not exist */ + if (!nodeService.exists(nodeRef)) + { + filteringResultSet.setIncluded(i, false); + } + } + } + } + + return resultSet; + } + + public CMISResultSet query(String query, StoreRef storeRef) + { + CMISQueryOptions options = new CMISQueryOptions(query, storeRef); + return query(options); + } + + public boolean getPwcSearchable() + { + return true; + } + + public boolean getAllVersionsSearchable() + { + return false; + } + + public CapabilityQuery getQuerySupport() + { + return CapabilityQuery.BOTHCOMBINED; + } + + public CapabilityJoin getJoinSupport() + { + return CapabilityJoin.NONE; + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSet.java b/source/java/org/alfresco/opencmis/search/CMISResultSet.java index e3a34f5eec..65fd04ba06 100644 --- a/source/java/org/alfresco/opencmis/search/CMISResultSet.java +++ b/source/java/org/alfresco/opencmis/search/CMISResultSet.java @@ -1,351 +1,351 @@ -package org.alfresco.opencmis.search; - -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.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.opencmis.dictionary.CMISDictionaryService; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.repo.search.impl.querymodel.Query; -import org.alfresco.service.cmr.dictionary.DictionaryService; -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.search.LimitBy; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.ResultSetRow; -import org.alfresco.service.cmr.search.ResultSetSPI; -import org.alfresco.service.cmr.search.SpellCheckResult; -import org.alfresco.util.Pair; - -/** - * @author andyh - */ -public class CMISResultSet implements ResultSetSPI, Serializable -{ - private static final long serialVersionUID = 2014688399588268994L; - - private Map wrapped; - - private Map nodeInfos; - - private LimitBy limitBy; - - CMISQueryOptions options; - - NodeService nodeService; - - Query query; - - CMISDictionaryService cmisDictionaryService; - - DictionaryService alfrescoDictionaryService; - - public CMISResultSet(Map wrapped, CMISQueryOptions options, LimitBy limitBy, - NodeService nodeService, Query query, CMISDictionaryService cmisDictionaryService, - DictionaryService alfrescoDictionaryService) - { - this.wrapped = wrapped; - this.options = options; - this.limitBy = limitBy; - this.nodeService = nodeService; - this.query = query; - this.cmisDictionaryService = cmisDictionaryService; - this.alfrescoDictionaryService = alfrescoDictionaryService; - this.nodeInfos = new HashMap(); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSet#close() - */ - public void close() - { - // results sets can be used for more than one selector so we need to - // keep track of what we have closed - Set closed = new HashSet(); - for (ResultSet resultSet : wrapped.values()) - { - if (!closed.contains(resultSet)) - { - resultSet.close(); - closed.add(resultSet); - } - } - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSet#getMetaData() - */ - public CMISResultSetMetaData getMetaData() - { - return new CMISResultSetMetaData(options, query, limitBy, cmisDictionaryService, alfrescoDictionaryService); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSet#getRow(int) - */ - public CMISResultSetRow getRow(int i) - { - return new CMISResultSetRow(this, i, getScores(i), nodeService, getNodeRefs(i), nodeInfos, query, cmisDictionaryService); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSet#hasMore() - */ - public boolean hasMore() - { - for (ResultSet resultSet : wrapped.values()) - { - if (resultSet.hasMore()) - { - return true; - } - } - return false; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSet#length() - */ - public int getLength() - { - for (ResultSet resultSet : wrapped.values()) - { - return resultSet.length(); - } - throw new IllegalStateException(); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSet#start() - */ - public int getStart() - { - return options.getSkipCount(); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Iterable#iterator() - */ - public Iterator iterator() - { - return new CMISResultSetRowIterator(this); - } - - private Map getNodeRefs(int i) - { - HashMap refs = new HashMap(); - for (String selector : wrapped.keySet()) - { - ResultSet rs = wrapped.get(selector); - refs.put(selector, rs.getNodeRef(i)); - } - return refs; - } - - private Map getScores(int i) - { - HashMap scores = new HashMap(); - for (String selector : wrapped.keySet()) - { - ResultSet rs = wrapped.get(selector); - scores.put(selector, Float.valueOf(rs.getScore(i))); - } - return scores; - } - - public ChildAssociationRef getChildAssocRef(int n) - { - NodeRef nodeRef = getNodeRef(n); - return nodeService.getPrimaryParent(nodeRef); - } - - public List getChildAssocRefs() - { - ArrayList cars = new ArrayList(length()); - for (ResultSetRow row : this) - { - cars.add(row.getChildAssocRef()); - } - return cars; - } - - public NodeRef getNodeRef(int n) - { - Map refs = getNodeRefs(n); - if (refs.size() == 1) - { - return refs.values().iterator().next(); - } else if (allNodeRefsEqual(refs)) - { - return refs.values().iterator().next(); - } else - { - throw new IllegalStateException("Ambiguous selector"); - } - } - - private boolean allNodeRefsEqual(Map selected) - { - NodeRef last = null; - for (NodeRef current : selected.values()) - { - if (last == null) - { - last = current; - } else - { - if (!last.equals(current)) - { - return false; - } - } - } - return true; - } - - public List getNodeRefs() - { - ArrayList nodeRefs = new ArrayList(length()); - for (ResultSetRow row : this) - { - nodeRefs.add(row.getNodeRef()); - } - return nodeRefs; - } - - public CMISResultSetMetaData getResultSetMetaData() - { - return getMetaData(); - } - - public float getScore(int n) - { - Map scores = getScores(n); - if (scores.size() == 1) - { - return scores.values().iterator().next(); - } else if (allScoresEqual(scores)) - { - return scores.values().iterator().next(); - } else - { - throw new IllegalStateException("Ambiguous selector"); - } - } - - private boolean allScoresEqual(Map scores) - { - Float last = null; - for (Float current : scores.values()) - { - if (last == null) - { - last = current; - } else - { - if (!last.equals(current)) - { - return false; - } - } - } - return true; - } - - public int length() - { - return getLength(); - } - - /** - * Bulk fetch results in the cache - not supported here - * - * @param bulkFetch boolean - */ - public boolean setBulkFetch(boolean bulkFetch) - { - return false; - } - - /** - * Do we bulk fetch - not supported here - * - * @return - true if we do - */ - public boolean getBulkFetch() - { - return false; - } - - /** - * Set the bulk fetch size - * - * @param bulkFetchSize int - */ - public int setBulkFetchSize(int bulkFetchSize) - { - return 0; - } - - /** - * Get the bulk fetch size. - * - * @return the fetch size - */ - public int getBulkFetchSize() - { - return 0; - } - - @Override - public List> getFieldFacet(String field) - { - return Collections.>emptyList(); - } - - /* (non-Javadoc) - * @see org.alfresco.service.cmr.search.ResultSetSPI#getNumberFound() - */ - @Override - public long getNumberFound() - { - for (ResultSet resultSet : wrapped.values()) - { - return resultSet.getNumberFound(); - } - throw new IllegalStateException(); - } - - @Override - public Map getFacetQueries() - { - return Collections.emptyMap(); - } - - @Override - public SpellCheckResult getSpellCheckResult() - { - return new SpellCheckResult(null, null, false); - } -} +package org.alfresco.opencmis.search; + +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.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.repo.search.impl.querymodel.Query; +import org.alfresco.service.cmr.dictionary.DictionaryService; +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.search.LimitBy; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.ResultSetSPI; +import org.alfresco.service.cmr.search.SpellCheckResult; +import org.alfresco.util.Pair; + +/** + * @author andyh + */ +public class CMISResultSet implements ResultSetSPI, Serializable +{ + private static final long serialVersionUID = 2014688399588268994L; + + private Map wrapped; + + private Map nodeInfos; + + private LimitBy limitBy; + + CMISQueryOptions options; + + NodeService nodeService; + + Query query; + + CMISDictionaryService cmisDictionaryService; + + DictionaryService alfrescoDictionaryService; + + public CMISResultSet(Map wrapped, CMISQueryOptions options, LimitBy limitBy, + NodeService nodeService, Query query, CMISDictionaryService cmisDictionaryService, + DictionaryService alfrescoDictionaryService) + { + this.wrapped = wrapped; + this.options = options; + this.limitBy = limitBy; + this.nodeService = nodeService; + this.query = query; + this.cmisDictionaryService = cmisDictionaryService; + this.alfrescoDictionaryService = alfrescoDictionaryService; + this.nodeInfos = new HashMap(); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#close() + */ + public void close() + { + // results sets can be used for more than one selector so we need to + // keep track of what we have closed + Set closed = new HashSet(); + for (ResultSet resultSet : wrapped.values()) + { + if (!closed.contains(resultSet)) + { + resultSet.close(); + closed.add(resultSet); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#getMetaData() + */ + public CMISResultSetMetaData getMetaData() + { + return new CMISResultSetMetaData(options, query, limitBy, cmisDictionaryService, alfrescoDictionaryService); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#getRow(int) + */ + public CMISResultSetRow getRow(int i) + { + return new CMISResultSetRow(this, i, getScores(i), nodeService, getNodeRefs(i), nodeInfos, query, cmisDictionaryService); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#hasMore() + */ + public boolean hasMore() + { + for (ResultSet resultSet : wrapped.values()) + { + if (resultSet.hasMore()) + { + return true; + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#length() + */ + public int getLength() + { + for (ResultSet resultSet : wrapped.values()) + { + return resultSet.length(); + } + throw new IllegalStateException(); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#start() + */ + public int getStart() + { + return options.getSkipCount(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() + { + return new CMISResultSetRowIterator(this); + } + + private Map getNodeRefs(int i) + { + HashMap refs = new HashMap(); + for (String selector : wrapped.keySet()) + { + ResultSet rs = wrapped.get(selector); + refs.put(selector, rs.getNodeRef(i)); + } + return refs; + } + + private Map getScores(int i) + { + HashMap scores = new HashMap(); + for (String selector : wrapped.keySet()) + { + ResultSet rs = wrapped.get(selector); + scores.put(selector, Float.valueOf(rs.getScore(i))); + } + return scores; + } + + public ChildAssociationRef getChildAssocRef(int n) + { + NodeRef nodeRef = getNodeRef(n); + return nodeService.getPrimaryParent(nodeRef); + } + + public List getChildAssocRefs() + { + ArrayList cars = new ArrayList(length()); + for (ResultSetRow row : this) + { + cars.add(row.getChildAssocRef()); + } + return cars; + } + + public NodeRef getNodeRef(int n) + { + Map refs = getNodeRefs(n); + if (refs.size() == 1) + { + return refs.values().iterator().next(); + } else if (allNodeRefsEqual(refs)) + { + return refs.values().iterator().next(); + } else + { + throw new IllegalStateException("Ambiguous selector"); + } + } + + private boolean allNodeRefsEqual(Map selected) + { + NodeRef last = null; + for (NodeRef current : selected.values()) + { + if (last == null) + { + last = current; + } else + { + if (!last.equals(current)) + { + return false; + } + } + } + return true; + } + + public List getNodeRefs() + { + ArrayList nodeRefs = new ArrayList(length()); + for (ResultSetRow row : this) + { + nodeRefs.add(row.getNodeRef()); + } + return nodeRefs; + } + + public CMISResultSetMetaData getResultSetMetaData() + { + return getMetaData(); + } + + public float getScore(int n) + { + Map scores = getScores(n); + if (scores.size() == 1) + { + return scores.values().iterator().next(); + } else if (allScoresEqual(scores)) + { + return scores.values().iterator().next(); + } else + { + throw new IllegalStateException("Ambiguous selector"); + } + } + + private boolean allScoresEqual(Map scores) + { + Float last = null; + for (Float current : scores.values()) + { + if (last == null) + { + last = current; + } else + { + if (!last.equals(current)) + { + return false; + } + } + } + return true; + } + + public int length() + { + return getLength(); + } + + /** + * Bulk fetch results in the cache - not supported here + * + * @param bulkFetch boolean + */ + public boolean setBulkFetch(boolean bulkFetch) + { + return false; + } + + /** + * Do we bulk fetch - not supported here + * + * @return - true if we do + */ + public boolean getBulkFetch() + { + return false; + } + + /** + * Set the bulk fetch size + * + * @param bulkFetchSize int + */ + public int setBulkFetchSize(int bulkFetchSize) + { + return 0; + } + + /** + * Get the bulk fetch size. + * + * @return the fetch size + */ + public int getBulkFetchSize() + { + return 0; + } + + @Override + public List> getFieldFacet(String field) + { + return Collections.>emptyList(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.search.ResultSetSPI#getNumberFound() + */ + @Override + public long getNumberFound() + { + for (ResultSet resultSet : wrapped.values()) + { + return resultSet.getNumberFound(); + } + throw new IllegalStateException(); + } + + @Override + public Map getFacetQueries() + { + return Collections.emptyMap(); + } + + @Override + public SpellCheckResult getSpellCheckResult() + { + return new SpellCheckResult(null, null, false); + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetColumn.java b/source/java/org/alfresco/opencmis/search/CMISResultSetColumn.java index fd85f07c9b..8ccd279767 100644 --- a/source/java/org/alfresco/opencmis/search/CMISResultSetColumn.java +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetColumn.java @@ -1,59 +1,59 @@ -package org.alfresco.opencmis.search; - -import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; -import org.alfresco.service.cmr.search.ResultSetColumn; -import org.alfresco.service.namespace.QName; -import org.apache.chemistry.opencmis.commons.enums.PropertyType; - -/** - * @author andyh - * - */ -public class CMISResultSetColumn implements ResultSetColumn -{ - - private String name; - - private PropertyDefinitionWrapper propertyDefinition; - - private PropertyType dataType; - - private QName alfrescoPropertyQName; - - private QName alfrescoDataTypeQName; - - CMISResultSetColumn(String name, PropertyDefinitionWrapper propertyDefinition, PropertyType dataType, - QName alfrescoPropertyQName, QName alfrescoDataTypeQName) - { - this.name = name; - this.propertyDefinition = propertyDefinition; - this.dataType = dataType; - this.alfrescoPropertyQName = alfrescoPropertyQName; - this.alfrescoDataTypeQName = alfrescoDataTypeQName; - } - - public String getName() - { - return name; - } - - public PropertyDefinitionWrapper getCMISPropertyDefinition() - { - return propertyDefinition; - } - - public PropertyType getCMISDataType() - { - return dataType; - } - - public QName getDataType() - { - return alfrescoDataTypeQName; - } - - public QName getPropertyType() - { - return alfrescoPropertyQName; - } -} +package org.alfresco.opencmis.search; + +import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; +import org.alfresco.service.cmr.search.ResultSetColumn; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; + +/** + * @author andyh + * + */ +public class CMISResultSetColumn implements ResultSetColumn +{ + + private String name; + + private PropertyDefinitionWrapper propertyDefinition; + + private PropertyType dataType; + + private QName alfrescoPropertyQName; + + private QName alfrescoDataTypeQName; + + CMISResultSetColumn(String name, PropertyDefinitionWrapper propertyDefinition, PropertyType dataType, + QName alfrescoPropertyQName, QName alfrescoDataTypeQName) + { + this.name = name; + this.propertyDefinition = propertyDefinition; + this.dataType = dataType; + this.alfrescoPropertyQName = alfrescoPropertyQName; + this.alfrescoDataTypeQName = alfrescoDataTypeQName; + } + + public String getName() + { + return name; + } + + public PropertyDefinitionWrapper getCMISPropertyDefinition() + { + return propertyDefinition; + } + + public PropertyType getCMISDataType() + { + return dataType; + } + + public QName getDataType() + { + return alfrescoDataTypeQName; + } + + public QName getPropertyType() + { + return alfrescoPropertyQName; + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetMetaData.java b/source/java/org/alfresco/opencmis/search/CMISResultSetMetaData.java index b1c9902ee6..d2b18d86c0 100644 --- a/source/java/org/alfresco/opencmis/search/CMISResultSetMetaData.java +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetMetaData.java @@ -1,183 +1,183 @@ -package org.alfresco.opencmis.search; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.alfresco.opencmis.dictionary.CMISDictionaryService; -import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; -import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; -import org.alfresco.repo.search.impl.querymodel.Column; -import org.alfresco.repo.search.impl.querymodel.PropertyArgument; -import org.alfresco.repo.search.impl.querymodel.Query; -import org.alfresco.repo.search.impl.querymodel.Selector; -import org.alfresco.repo.search.impl.querymodel.impl.functions.PropertyAccessor; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.search.LimitBy; -import org.alfresco.service.cmr.search.PermissionEvaluationMode; -import org.alfresco.service.cmr.search.ResultSetMetaData; -import org.alfresco.service.cmr.search.ResultSetType; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.namespace.QName; -import org.apache.chemistry.opencmis.commons.enums.PropertyType; - -/** - * @author andyh - */ -public class CMISResultSetMetaData implements ResultSetMetaData -{ - private CMISQueryOptions options; - private SearchParameters searchParams; - private LimitBy limitBy; - - private Map columnMetaData; - - private Map selectorMetaData; - - public CMISResultSetMetaData(CMISQueryOptions options, Query query, LimitBy limitBy, - CMISDictionaryService cmisDictionaryService, DictionaryService alfrescoDictionaryService) - { - this.options = options; - this.searchParams = new SearchParameters(options); - this.limitBy = limitBy; - Map selectors = query.getSource().getSelectors(); - selectorMetaData = new LinkedHashMap(); - for (Selector selector : selectors.values()) - { - TypeDefinitionWrapper type = cmisDictionaryService.findTypeForClass(selector.getType()); - CMISResultSetSelector smd = new CMISResultSetSelector(selector.getAlias(), type); - selectorMetaData.put(smd.getName(), smd); - } - - columnMetaData = new LinkedHashMap(); - for (Column column : query.getColumns()) - { - PropertyDefinitionWrapper propertyDefinition = null; - PropertyType type = null; - QName alfrescoPropertyQName = null; - QName alfrescoDataTypeQName = null; - if (column.getFunction().getName().equals(PropertyAccessor.NAME)) - { - PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( - PropertyAccessor.ARG_PROPERTY); - String propertyName = arg.getPropertyName(); - alfrescoPropertyQName = QName.createQName(propertyName); - PropertyDefinition alfPropDef = alfrescoDictionaryService.getProperty(alfrescoPropertyQName); - if (alfPropDef == null) - { - alfrescoPropertyQName = null; - } else - { - alfrescoDataTypeQName = alfPropDef.getDataType().getName(); - } - propertyDefinition = cmisDictionaryService.findProperty(propertyName); - type = propertyDefinition.getPropertyDefinition().getPropertyType(); - } - if (type == null) - { - type = cmisDictionaryService.findDataType(column.getFunction().getReturnType()); - } - if (alfrescoDataTypeQName == null) - { - alfrescoDataTypeQName = cmisDictionaryService.findAlfrescoDataType(type); - } - CMISResultSetColumn cmd = new CMISResultSetColumn(column.getAlias(), propertyDefinition, type, - alfrescoPropertyQName, alfrescoDataTypeQName); - columnMetaData.put(cmd.getName(), cmd); - } - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetMetaData#getColumnNames() - */ - public String[] getColumnNames() - { - return columnMetaData.keySet().toArray(new String[0]); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetMetaData#getColumns() - */ - public CMISResultSetColumn[] getColumns() - { - return columnMetaData.values().toArray(new CMISResultSetColumn[0]); - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.cmis.search.CMISResultSetMetaData#getCoumn(java.lang.String) - */ - public CMISResultSetColumn getColumn(String name) - { - return columnMetaData.get(name); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetMetaData#getQueryOptions() - */ - public CMISQueryOptions getQueryOptions() - { - return options; - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.cmis.search.CMISResultSetMetaData#getSelector(java.lang. - * String) - */ - public CMISResultSetSelector getSelector(String name) - { - return selectorMetaData.get(name); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetMetaData#getSelectorNames() - */ - public String[] getSelectorNames() - { - return selectorMetaData.keySet().toArray(new String[0]); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetMetaData#getSelectors() - */ - public CMISResultSetSelector[] getSelectors() - { - return selectorMetaData.values().toArray(new CMISResultSetSelector[0]); - } - - public LimitBy getLimitedBy() - { - return limitBy; - } - - public PermissionEvaluationMode getPermissionEvaluationMode() - { - throw new UnsupportedOperationException(); - } - - public ResultSetType getResultSetType() - { - return ResultSetType.COLUMN_AND_NODE_REF; - } - - public SearchParameters getSearchParameters() - { - return searchParams; - } - -} +package org.alfresco.opencmis.search; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.repo.search.impl.querymodel.Column; +import org.alfresco.repo.search.impl.querymodel.PropertyArgument; +import org.alfresco.repo.search.impl.querymodel.Query; +import org.alfresco.repo.search.impl.querymodel.Selector; +import org.alfresco.repo.search.impl.querymodel.impl.functions.PropertyAccessor; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.PermissionEvaluationMode; +import org.alfresco.service.cmr.search.ResultSetMetaData; +import org.alfresco.service.cmr.search.ResultSetType; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; + +/** + * @author andyh + */ +public class CMISResultSetMetaData implements ResultSetMetaData +{ + private CMISQueryOptions options; + private SearchParameters searchParams; + private LimitBy limitBy; + + private Map columnMetaData; + + private Map selectorMetaData; + + public CMISResultSetMetaData(CMISQueryOptions options, Query query, LimitBy limitBy, + CMISDictionaryService cmisDictionaryService, DictionaryService alfrescoDictionaryService) + { + this.options = options; + this.searchParams = new SearchParameters(options); + this.limitBy = limitBy; + Map selectors = query.getSource().getSelectors(); + selectorMetaData = new LinkedHashMap(); + for (Selector selector : selectors.values()) + { + TypeDefinitionWrapper type = cmisDictionaryService.findTypeForClass(selector.getType()); + CMISResultSetSelector smd = new CMISResultSetSelector(selector.getAlias(), type); + selectorMetaData.put(smd.getName(), smd); + } + + columnMetaData = new LinkedHashMap(); + for (Column column : query.getColumns()) + { + PropertyDefinitionWrapper propertyDefinition = null; + PropertyType type = null; + QName alfrescoPropertyQName = null; + QName alfrescoDataTypeQName = null; + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + String propertyName = arg.getPropertyName(); + alfrescoPropertyQName = QName.createQName(propertyName); + PropertyDefinition alfPropDef = alfrescoDictionaryService.getProperty(alfrescoPropertyQName); + if (alfPropDef == null) + { + alfrescoPropertyQName = null; + } else + { + alfrescoDataTypeQName = alfPropDef.getDataType().getName(); + } + propertyDefinition = cmisDictionaryService.findProperty(propertyName); + type = propertyDefinition.getPropertyDefinition().getPropertyType(); + } + if (type == null) + { + type = cmisDictionaryService.findDataType(column.getFunction().getReturnType()); + } + if (alfrescoDataTypeQName == null) + { + alfrescoDataTypeQName = cmisDictionaryService.findAlfrescoDataType(type); + } + CMISResultSetColumn cmd = new CMISResultSetColumn(column.getAlias(), propertyDefinition, type, + alfrescoPropertyQName, alfrescoDataTypeQName); + columnMetaData.put(cmd.getName(), cmd); + } + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getColumnNames() + */ + public String[] getColumnNames() + { + return columnMetaData.keySet().toArray(new String[0]); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getColumns() + */ + public CMISResultSetColumn[] getColumns() + { + return columnMetaData.values().toArray(new CMISResultSetColumn[0]); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.search.CMISResultSetMetaData#getCoumn(java.lang.String) + */ + public CMISResultSetColumn getColumn(String name) + { + return columnMetaData.get(name); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getQueryOptions() + */ + public CMISQueryOptions getQueryOptions() + { + return options; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.search.CMISResultSetMetaData#getSelector(java.lang. + * String) + */ + public CMISResultSetSelector getSelector(String name) + { + return selectorMetaData.get(name); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getSelectorNames() + */ + public String[] getSelectorNames() + { + return selectorMetaData.keySet().toArray(new String[0]); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getSelectors() + */ + public CMISResultSetSelector[] getSelectors() + { + return selectorMetaData.values().toArray(new CMISResultSetSelector[0]); + } + + public LimitBy getLimitedBy() + { + return limitBy; + } + + public PermissionEvaluationMode getPermissionEvaluationMode() + { + throw new UnsupportedOperationException(); + } + + public ResultSetType getResultSetType() + { + return ResultSetType.COLUMN_AND_NODE_REF; + } + + public SearchParameters getSearchParameters() + { + return searchParams; + } + +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetRow.java b/source/java/org/alfresco/opencmis/search/CMISResultSetRow.java index c9a804600c..7d386bcf06 100644 --- a/source/java/org/alfresco/opencmis/search/CMISResultSetRow.java +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetRow.java @@ -1,267 +1,267 @@ -package org.alfresco.opencmis.search; - -import java.io.Serializable; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.alfresco.opencmis.dictionary.CMISDictionaryService; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.repo.search.impl.querymodel.Column; -import org.alfresco.repo.search.impl.querymodel.PropertyArgument; -import org.alfresco.repo.search.impl.querymodel.Query; -import org.alfresco.repo.search.impl.querymodel.impl.functions.PropertyAccessor; -import org.alfresco.repo.search.results.ResultSetSPIWrapper; -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.search.ResultSet; -import org.alfresco.service.cmr.search.ResultSetRow; -import org.alfresco.service.namespace.QName; - -/** - * @author andyh - */ -public class CMISResultSetRow implements ResultSetRow -{ - /** - * The containing result set - */ - private CMISResultSet resultSet; - - /** - * The current position in the containing result set - */ - private int index; - - private Map scores; - - private NodeService nodeService; - - private Map nodeRefs; - - private Map nodeInfos; - - private Query query; - - private CMISDictionaryService cmisDictionaryService; - - public CMISResultSetRow(CMISResultSet resultSet, int index, Map scores, NodeService nodeService, - Map nodeRefs, Map nodeInfos, Query query, - CMISDictionaryService cmisDictionaryService) - { - this.resultSet = resultSet; - this.index = index; - this.scores = scores; - this.nodeService = nodeService; - this.nodeRefs = nodeRefs; - this.query = query; - this.cmisDictionaryService = cmisDictionaryService; - this.nodeInfos = nodeInfos; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetRow#getIndex() - */ - public int getIndex() - { - return index; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetRow#getResultSet() - */ - public ResultSet getResultSet() - { - return new ResultSetSPIWrapper(resultSet); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetRow#getScore() - */ - public float getScore() - { - float count = 0; - float overall = 0; - for (Float score : scores.values()) - { - overall = (overall * (count / (count + 1.0f))) + (score / (count + 1.0f)); - } - return overall; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetRow#getScore(java.lang.String) - */ - public float getScore(String selectorName) - { - return scores.get(selectorName); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetRow#getScores() - */ - public Map getScores() - { - return scores; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetRow#getScore(java.lang.String) - */ - public NodeRef getNodeRef(String selectorName) - { - return nodeRefs.get(selectorName); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetRow#getScores() - */ - public Map getNodeRefs() - { - return nodeRefs; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetRow#getValue(java.lang.String) - */ - public Serializable getValue(String columnName) - { - CmisFunctionEvaluationContext context = new CmisFunctionEvaluationContext(); - context.setCmisDictionaryService(cmisDictionaryService); - context.setNodeRefs(nodeRefs); - context.setNodeInfos(nodeInfos); - context.setNodeService(nodeService); - context.setScores(scores); - context.setScore(getScore()); - for (Column column : query.getColumns()) - { - if (column.getAlias().equals(columnName)) - { - return column.getFunction().getValue(column.getFunctionArguments(), context); - } - // Special case for one selector - ignore any table aliases - // also allows look up direct and not by alias - // Perhaps we should add the duplicates instead - // TODO: check SQL 92 for single alias table behaviour for selectors - if (nodeRefs.size() == 1) - { - if (column.getFunction().getName().equals(PropertyAccessor.NAME)) - { - PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( - PropertyAccessor.ARG_PROPERTY); - String propertyName = arg.getPropertyName(); - if (propertyName.equals(columnName)) - { - return column.getFunction().getValue(column.getFunctionArguments(), context); - } - StringBuilder builder = new StringBuilder(); - builder.append(arg.getSelector()).append(".").append(propertyName); - propertyName = builder.toString(); - if (propertyName.equals(columnName)) - { - return column.getFunction().getValue(column.getFunctionArguments(), context); - } - } - } else - { - if (column.getFunction().getName().equals(PropertyAccessor.NAME)) - { - PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( - PropertyAccessor.ARG_PROPERTY); - StringBuilder builder = new StringBuilder(); - builder.append(arg.getSelector()).append(".").append(arg.getPropertyName()); - String propertyName = builder.toString(); - if (propertyName.equals(columnName)) - { - return column.getFunction().getValue(column.getFunctionArguments(), context); - } - } - } - } - return null; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.cmis.search.CMISResultSetRow#getValues() - */ - public Map getValues() - { - LinkedHashMap answer = new LinkedHashMap(); - for (String column : resultSet.getMetaData().getColumnNames()) - { - answer.put(column, getValue(column)); - } - return answer; - } - - public CMISResultSet getCMISResultSet() - { - return resultSet; - } - - public ChildAssociationRef getChildAssocRef() - { - NodeRef nodeRef = getNodeRef(); - return nodeService.getPrimaryParent(nodeRef); - } - - public NodeRef getNodeRef() - { - if (nodeRefs.size() == 1) - { - return nodeRefs.values().iterator().next(); - } else if (allNodeRefsEqual(nodeRefs)) - { - return nodeRefs.values().iterator().next(); - } - throw new UnsupportedOperationException("Ambiguous selector"); - } - - private boolean allNodeRefsEqual(Map selected) - { - NodeRef last = null; - for (NodeRef current : selected.values()) - { - if (last == null) - { - last = current; - } else - { - if (!last.equals(current)) - { - return false; - } - } - } - return true; - } - - public QName getQName() - { - return getChildAssocRef().getQName(); - } - - public Serializable getValue(QName qname) - { - throw new UnsupportedOperationException(); - } - -} +package org.alfresco.opencmis.search; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.repo.search.impl.querymodel.Column; +import org.alfresco.repo.search.impl.querymodel.PropertyArgument; +import org.alfresco.repo.search.impl.querymodel.Query; +import org.alfresco.repo.search.impl.querymodel.impl.functions.PropertyAccessor; +import org.alfresco.repo.search.results.ResultSetSPIWrapper; +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.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.namespace.QName; + +/** + * @author andyh + */ +public class CMISResultSetRow implements ResultSetRow +{ + /** + * The containing result set + */ + private CMISResultSet resultSet; + + /** + * The current position in the containing result set + */ + private int index; + + private Map scores; + + private NodeService nodeService; + + private Map nodeRefs; + + private Map nodeInfos; + + private Query query; + + private CMISDictionaryService cmisDictionaryService; + + public CMISResultSetRow(CMISResultSet resultSet, int index, Map scores, NodeService nodeService, + Map nodeRefs, Map nodeInfos, Query query, + CMISDictionaryService cmisDictionaryService) + { + this.resultSet = resultSet; + this.index = index; + this.scores = scores; + this.nodeService = nodeService; + this.nodeRefs = nodeRefs; + this.query = query; + this.cmisDictionaryService = cmisDictionaryService; + this.nodeInfos = nodeInfos; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getIndex() + */ + public int getIndex() + { + return index; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getResultSet() + */ + public ResultSet getResultSet() + { + return new ResultSetSPIWrapper(resultSet); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScore() + */ + public float getScore() + { + float count = 0; + float overall = 0; + for (Float score : scores.values()) + { + overall = (overall * (count / (count + 1.0f))) + (score / (count + 1.0f)); + } + return overall; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScore(java.lang.String) + */ + public float getScore(String selectorName) + { + return scores.get(selectorName); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScores() + */ + public Map getScores() + { + return scores; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScore(java.lang.String) + */ + public NodeRef getNodeRef(String selectorName) + { + return nodeRefs.get(selectorName); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScores() + */ + public Map getNodeRefs() + { + return nodeRefs; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getValue(java.lang.String) + */ + public Serializable getValue(String columnName) + { + CmisFunctionEvaluationContext context = new CmisFunctionEvaluationContext(); + context.setCmisDictionaryService(cmisDictionaryService); + context.setNodeRefs(nodeRefs); + context.setNodeInfos(nodeInfos); + context.setNodeService(nodeService); + context.setScores(scores); + context.setScore(getScore()); + for (Column column : query.getColumns()) + { + if (column.getAlias().equals(columnName)) + { + return column.getFunction().getValue(column.getFunctionArguments(), context); + } + // Special case for one selector - ignore any table aliases + // also allows look up direct and not by alias + // Perhaps we should add the duplicates instead + // TODO: check SQL 92 for single alias table behaviour for selectors + if (nodeRefs.size() == 1) + { + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + String propertyName = arg.getPropertyName(); + if (propertyName.equals(columnName)) + { + return column.getFunction().getValue(column.getFunctionArguments(), context); + } + StringBuilder builder = new StringBuilder(); + builder.append(arg.getSelector()).append(".").append(propertyName); + propertyName = builder.toString(); + if (propertyName.equals(columnName)) + { + return column.getFunction().getValue(column.getFunctionArguments(), context); + } + } + } else + { + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + StringBuilder builder = new StringBuilder(); + builder.append(arg.getSelector()).append(".").append(arg.getPropertyName()); + String propertyName = builder.toString(); + if (propertyName.equals(columnName)) + { + return column.getFunction().getValue(column.getFunctionArguments(), context); + } + } + } + } + return null; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getValues() + */ + public Map getValues() + { + LinkedHashMap answer = new LinkedHashMap(); + for (String column : resultSet.getMetaData().getColumnNames()) + { + answer.put(column, getValue(column)); + } + return answer; + } + + public CMISResultSet getCMISResultSet() + { + return resultSet; + } + + public ChildAssociationRef getChildAssocRef() + { + NodeRef nodeRef = getNodeRef(); + return nodeService.getPrimaryParent(nodeRef); + } + + public NodeRef getNodeRef() + { + if (nodeRefs.size() == 1) + { + return nodeRefs.values().iterator().next(); + } else if (allNodeRefsEqual(nodeRefs)) + { + return nodeRefs.values().iterator().next(); + } + throw new UnsupportedOperationException("Ambiguous selector"); + } + + private boolean allNodeRefsEqual(Map selected) + { + NodeRef last = null; + for (NodeRef current : selected.values()) + { + if (last == null) + { + last = current; + } else + { + if (!last.equals(current)) + { + return false; + } + } + } + return true; + } + + public QName getQName() + { + return getChildAssocRef().getQName(); + } + + public Serializable getValue(QName qname) + { + throw new UnsupportedOperationException(); + } + +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetRowIterator.java b/source/java/org/alfresco/opencmis/search/CMISResultSetRowIterator.java index 519a8ca467..b82201634d 100644 --- a/source/java/org/alfresco/opencmis/search/CMISResultSetRowIterator.java +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetRowIterator.java @@ -1,112 +1,112 @@ -package org.alfresco.opencmis.search; - -import java.util.ListIterator; - -/** - * @author andyh - */ -public class CMISResultSetRowIterator implements ListIterator -{ - /** - * The result set - */ - private CMISResultSet resultSet; - - /** - * The current position - */ - private int position = -1; - - /** - * The maximum position - */ - private int max; - - /** - * Create an iterator over the result set. Follows stadard ListIterator - * conventions - * - * @param resultSet CMISResultSet - */ - public CMISResultSetRowIterator(CMISResultSet resultSet) - { - this.resultSet = resultSet; - this.max = resultSet.getLength(); - } - - public CMISResultSet getResultSet() - { - return resultSet; - } - - /* - * ListIterator implementation - */ - public boolean hasNext() - { - return position < (max - 1); - } - - public boolean allowsReverse() - { - return true; - } - - public boolean hasPrevious() - { - return position > 0; - } - - public CMISResultSetRow next() - { - return resultSet.getRow(moveToNextPosition()); - } - - protected int moveToNextPosition() - { - return ++position; - } - - public CMISResultSetRow previous() - { - return resultSet.getRow(moveToPreviousPosition()); - } - - protected int moveToPreviousPosition() - { - return --position; - } - - public int nextIndex() - { - return position + 1; - } - - public int previousIndex() - { - return position - 1; - } - - /* - * Mutation is not supported - */ - - public void remove() - { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - public void set(CMISResultSetRow o) - { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - public void add(CMISResultSetRow o) - { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - -} +package org.alfresco.opencmis.search; + +import java.util.ListIterator; + +/** + * @author andyh + */ +public class CMISResultSetRowIterator implements ListIterator +{ + /** + * The result set + */ + private CMISResultSet resultSet; + + /** + * The current position + */ + private int position = -1; + + /** + * The maximum position + */ + private int max; + + /** + * Create an iterator over the result set. Follows stadard ListIterator + * conventions + * + * @param resultSet CMISResultSet + */ + public CMISResultSetRowIterator(CMISResultSet resultSet) + { + this.resultSet = resultSet; + this.max = resultSet.getLength(); + } + + public CMISResultSet getResultSet() + { + return resultSet; + } + + /* + * ListIterator implementation + */ + public boolean hasNext() + { + return position < (max - 1); + } + + public boolean allowsReverse() + { + return true; + } + + public boolean hasPrevious() + { + return position > 0; + } + + public CMISResultSetRow next() + { + return resultSet.getRow(moveToNextPosition()); + } + + protected int moveToNextPosition() + { + return ++position; + } + + public CMISResultSetRow previous() + { + return resultSet.getRow(moveToPreviousPosition()); + } + + protected int moveToPreviousPosition() + { + return --position; + } + + public int nextIndex() + { + return position + 1; + } + + public int previousIndex() + { + return position - 1; + } + + /* + * Mutation is not supported + */ + + public void remove() + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + public void set(CMISResultSetRow o) + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + public void add(CMISResultSetRow o) + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetSelector.java b/source/java/org/alfresco/opencmis/search/CMISResultSetSelector.java index ed07fb3b20..3535904278 100644 --- a/source/java/org/alfresco/opencmis/search/CMISResultSetSelector.java +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetSelector.java @@ -1,38 +1,38 @@ -package org.alfresco.opencmis.search; - -import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; -import org.alfresco.service.cmr.search.ResultSetSelector; -import org.alfresco.service.namespace.QName; - -/** - * @author andyh - * - */ -public class CMISResultSetSelector implements ResultSetSelector -{ - private String name; - - private TypeDefinitionWrapper typeDefinition; - - public CMISResultSetSelector(String name, TypeDefinitionWrapper typeDefinition) - { - this.name = name; - this.typeDefinition = typeDefinition; - } - - public String getName() - { - return name; - } - - public TypeDefinitionWrapper getTypeDefinition() - { - return typeDefinition; - } - - public QName getType() - { - return typeDefinition.getAlfrescoName(); - } - -} +package org.alfresco.opencmis.search; + +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.service.cmr.search.ResultSetSelector; +import org.alfresco.service.namespace.QName; + +/** + * @author andyh + * + */ +public class CMISResultSetSelector implements ResultSetSelector +{ + private String name; + + private TypeDefinitionWrapper typeDefinition; + + public CMISResultSetSelector(String name, TypeDefinitionWrapper typeDefinition) + { + this.name = name; + this.typeDefinition = typeDefinition; + } + + public String getName() + { + return name; + } + + public TypeDefinitionWrapper getTypeDefinition() + { + return typeDefinition; + } + + public QName getType() + { + return typeDefinition.getAlfrescoName(); + } + +} diff --git a/source/java/org/alfresco/repo/action/ActionModel.java b/source/java/org/alfresco/repo/action/ActionModel.java index 9e8e993c1c..2640d35467 100644 --- a/source/java/org/alfresco/repo/action/ActionModel.java +++ b/source/java/org/alfresco/repo/action/ActionModel.java @@ -1,55 +1,55 @@ -package org.alfresco.repo.action; - -import org.alfresco.service.namespace.QName; - -public interface ActionModel -{ - static final String ACTION_MODEL_URI = "http://www.alfresco.org/model/action/1.0"; - static final String ACTION_MODEL_PREFIX = "act"; - static final QName TYPE_ACTION_BASE = QName.createQName(ACTION_MODEL_URI, "actionbase"); - static final QName TYPE_ACTION = QName.createQName(ACTION_MODEL_URI, "action"); - static final QName PROP_DEFINITION_NAME = QName.createQName(ACTION_MODEL_URI, "definitionName"); - static final QName PROP_ACTION_TITLE = QName.createQName(ACTION_MODEL_URI, "actionTitle"); - static final QName PROP_ACTION_DESCRIPTION = QName.createQName(ACTION_MODEL_URI, "actionDescription"); - static final QName PROP_TRACK_STATUS = QName.createQName(ACTION_MODEL_URI, "trackStatus"); - static final QName PROP_EXECUTE_ASYNCHRONOUSLY = QName.createQName(ACTION_MODEL_URI, "executeAsynchronously"); - static final QName PROP_EXECUTION_START_DATE = QName.createQName(ACTION_MODEL_URI, "executionStartDate"); - static final QName PROP_EXECUTION_END_DATE = QName.createQName(ACTION_MODEL_URI, "executionEndDate"); - static final QName PROP_EXECUTION_ACTION_STATUS = QName.createQName(ACTION_MODEL_URI, "executionActionStatus"); - static final QName PROP_EXECUTION_FAILURE_MESSAGE = QName.createQName(ACTION_MODEL_URI, "executionFailureMessage"); - static final QName ASSOC_CONDITIONS = QName.createQName(ACTION_MODEL_URI, "conditions"); - - static final QName ASSOC_COMPENSATING_ACTION = QName.createQName(ACTION_MODEL_URI, "compensatingAction"); - static final QName ASSOC_PARAMETERS = QName.createQName(ACTION_MODEL_URI, "parameters"); - static final QName TYPE_ACTION_CONDITION = QName.createQName(ACTION_MODEL_URI, "actioncondition"); - static final QName TYPE_COMPOSITE_ACTION_CONDITION = QName.createQName(ACTION_MODEL_URI, "compositeactioncondition"); - - static final QName TYPE_ACTION_PARAMETER = QName.createQName(ACTION_MODEL_URI, "actionparameter"); - static final QName PROP_PARAMETER_NAME = QName.createQName(ACTION_MODEL_URI, "parameterName"); - static final QName PROP_PARAMETER_VALUE = QName.createQName(ACTION_MODEL_URI, "parameterValue"); - static final QName TYPE_COMPOSITE_ACTION = QName.createQName(ACTION_MODEL_URI, "compositeaction"); - static final QName ASSOC_ACTIONS = QName.createQName(ACTION_MODEL_URI, "actions"); - static final QName ASSOC_COMPOSITE_ACTION_CONDITION = QName.createQName(ACTION_MODEL_URI, "compositeconditions"); - - static final QName ASPECT_ACTIONS = QName.createQName(ACTION_MODEL_URI, "actions"); - static final QName ASSOC_ACTION_FOLDER = QName.createQName(ACTION_MODEL_URI, "actionFolder"); - - static final QName TYPE_ACTION_SCHEDULE = QName.createQName(ACTION_MODEL_URI, "actionSchedule"); - static final QName PROP_START_DATE = QName.createQName(ACTION_MODEL_URI, "startDate"); - static final QName PROP_INTERVAL_COUNT = QName.createQName(ACTION_MODEL_URI, "intervalCount"); - static final QName PROP_INTERVAL_PERIOD = QName.createQName(ACTION_MODEL_URI, "intervalPeriod"); - static final QName PROP_LAST_EXECUTED_AT = QName.createQName(ACTION_MODEL_URI, "lastExecutedAt"); - static final QName ASSOC_SCHEDULED_ACTION = QName.createQName(ACTION_MODEL_URI, "scheduledAction"); - - //static final QName ASPECT_ACTIONABLE = QName.createQName(ACTION_MODEL_URI, "actionable"); - //static final QName ASSOC_SAVED_ACTION_FOLDERS = QName.createQName(ACTION_MODEL_URI, "savedActionFolders"); - //static final QName TYPE_SAVED_ACTION_FOLDER = QName.createQName(ACTION_MODEL_URI, "savedactionfolder"); - //static final QName ASSOC_SAVED_ACTIONS = QName.createQName(ACTION_MODEL_URI, "savedActions"); - - static final QName PROP_CONDITION_INVERT = QName.createQName(ACTION_MODEL_URI, "invert"); - static final QName PROP_CONDITION_ANDOR = QName.createQName(ACTION_MODEL_URI, "or"); - - /** Action assoc name */ - public static final QName ASSOC_NAME_ACTIONS = QName.createQName(ACTION_MODEL_URI, "actions"); - -} +package org.alfresco.repo.action; + +import org.alfresco.service.namespace.QName; + +public interface ActionModel +{ + static final String ACTION_MODEL_URI = "http://www.alfresco.org/model/action/1.0"; + static final String ACTION_MODEL_PREFIX = "act"; + static final QName TYPE_ACTION_BASE = QName.createQName(ACTION_MODEL_URI, "actionbase"); + static final QName TYPE_ACTION = QName.createQName(ACTION_MODEL_URI, "action"); + static final QName PROP_DEFINITION_NAME = QName.createQName(ACTION_MODEL_URI, "definitionName"); + static final QName PROP_ACTION_TITLE = QName.createQName(ACTION_MODEL_URI, "actionTitle"); + static final QName PROP_ACTION_DESCRIPTION = QName.createQName(ACTION_MODEL_URI, "actionDescription"); + static final QName PROP_TRACK_STATUS = QName.createQName(ACTION_MODEL_URI, "trackStatus"); + static final QName PROP_EXECUTE_ASYNCHRONOUSLY = QName.createQName(ACTION_MODEL_URI, "executeAsynchronously"); + static final QName PROP_EXECUTION_START_DATE = QName.createQName(ACTION_MODEL_URI, "executionStartDate"); + static final QName PROP_EXECUTION_END_DATE = QName.createQName(ACTION_MODEL_URI, "executionEndDate"); + static final QName PROP_EXECUTION_ACTION_STATUS = QName.createQName(ACTION_MODEL_URI, "executionActionStatus"); + static final QName PROP_EXECUTION_FAILURE_MESSAGE = QName.createQName(ACTION_MODEL_URI, "executionFailureMessage"); + static final QName ASSOC_CONDITIONS = QName.createQName(ACTION_MODEL_URI, "conditions"); + + static final QName ASSOC_COMPENSATING_ACTION = QName.createQName(ACTION_MODEL_URI, "compensatingAction"); + static final QName ASSOC_PARAMETERS = QName.createQName(ACTION_MODEL_URI, "parameters"); + static final QName TYPE_ACTION_CONDITION = QName.createQName(ACTION_MODEL_URI, "actioncondition"); + static final QName TYPE_COMPOSITE_ACTION_CONDITION = QName.createQName(ACTION_MODEL_URI, "compositeactioncondition"); + + static final QName TYPE_ACTION_PARAMETER = QName.createQName(ACTION_MODEL_URI, "actionparameter"); + static final QName PROP_PARAMETER_NAME = QName.createQName(ACTION_MODEL_URI, "parameterName"); + static final QName PROP_PARAMETER_VALUE = QName.createQName(ACTION_MODEL_URI, "parameterValue"); + static final QName TYPE_COMPOSITE_ACTION = QName.createQName(ACTION_MODEL_URI, "compositeaction"); + static final QName ASSOC_ACTIONS = QName.createQName(ACTION_MODEL_URI, "actions"); + static final QName ASSOC_COMPOSITE_ACTION_CONDITION = QName.createQName(ACTION_MODEL_URI, "compositeconditions"); + + static final QName ASPECT_ACTIONS = QName.createQName(ACTION_MODEL_URI, "actions"); + static final QName ASSOC_ACTION_FOLDER = QName.createQName(ACTION_MODEL_URI, "actionFolder"); + + static final QName TYPE_ACTION_SCHEDULE = QName.createQName(ACTION_MODEL_URI, "actionSchedule"); + static final QName PROP_START_DATE = QName.createQName(ACTION_MODEL_URI, "startDate"); + static final QName PROP_INTERVAL_COUNT = QName.createQName(ACTION_MODEL_URI, "intervalCount"); + static final QName PROP_INTERVAL_PERIOD = QName.createQName(ACTION_MODEL_URI, "intervalPeriod"); + static final QName PROP_LAST_EXECUTED_AT = QName.createQName(ACTION_MODEL_URI, "lastExecutedAt"); + static final QName ASSOC_SCHEDULED_ACTION = QName.createQName(ACTION_MODEL_URI, "scheduledAction"); + + //static final QName ASPECT_ACTIONABLE = QName.createQName(ACTION_MODEL_URI, "actionable"); + //static final QName ASSOC_SAVED_ACTION_FOLDERS = QName.createQName(ACTION_MODEL_URI, "savedActionFolders"); + //static final QName TYPE_SAVED_ACTION_FOLDER = QName.createQName(ACTION_MODEL_URI, "savedactionfolder"); + //static final QName ASSOC_SAVED_ACTIONS = QName.createQName(ACTION_MODEL_URI, "savedActions"); + + static final QName PROP_CONDITION_INVERT = QName.createQName(ACTION_MODEL_URI, "invert"); + static final QName PROP_CONDITION_ANDOR = QName.createQName(ACTION_MODEL_URI, "or"); + + /** Action assoc name */ + public static final QName ASSOC_NAME_ACTIONS = QName.createQName(ACTION_MODEL_URI, "actions"); + +} diff --git a/source/java/org/alfresco/repo/action/ActionTransactionListener.java b/source/java/org/alfresco/repo/action/ActionTransactionListener.java index 0965554907..2a64cf3cf5 100644 --- a/source/java/org/alfresco/repo/action/ActionTransactionListener.java +++ b/source/java/org/alfresco/repo/action/ActionTransactionListener.java @@ -1,99 +1,99 @@ -package org.alfresco.repo.action; - -import org.alfresco.repo.transaction.TransactionListener; -import org.alfresco.util.GUID; - -/** - * The action service transaction listener - * - * @author Roy Wetherall - */ -public class ActionTransactionListener implements TransactionListener -{ - /** - * Id used in equals and hash - */ - private String id = GUID.generate(); - - /** - * The action service (runtime interface) - */ - private RuntimeActionService actionService; - - /** - * Constructor - * - * @param actionService the action service - */ - public ActionTransactionListener(RuntimeActionService actionService) - { - this.actionService = actionService; - } - - /** - * @see org.alfresco.repo.transaction.TransactionListener#flush() - */ - public void flush() - { - } - - /** - * @see org.alfresco.repo.transaction.TransactionListener#beforeCommit(boolean) - */ - public void beforeCommit(boolean readOnly) - { - } - - /** - * @see org.alfresco.repo.transaction.TransactionListener#beforeCompletion() - */ - public void beforeCompletion() - { - } - - /** - * @see org.alfresco.repo.transaction.TransactionListener#afterCommit() - */ - public void afterCommit() - { - this.actionService.postCommit(); - } - - /** - * @see org.alfresco.repo.transaction.TransactionListener#afterRollback() - */ - public void afterRollback() - { - } - - /** - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() - { - return this.id.hashCode(); - } - - /** - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (obj instanceof ActionTransactionListener) - { - ActionTransactionListener that = (ActionTransactionListener) obj; - return (this.id.equals(that.id)); - } - else - { - return false; - } - } - -} +package org.alfresco.repo.action; + +import org.alfresco.repo.transaction.TransactionListener; +import org.alfresco.util.GUID; + +/** + * The action service transaction listener + * + * @author Roy Wetherall + */ +public class ActionTransactionListener implements TransactionListener +{ + /** + * Id used in equals and hash + */ + private String id = GUID.generate(); + + /** + * The action service (runtime interface) + */ + private RuntimeActionService actionService; + + /** + * Constructor + * + * @param actionService the action service + */ + public ActionTransactionListener(RuntimeActionService actionService) + { + this.actionService = actionService; + } + + /** + * @see org.alfresco.repo.transaction.TransactionListener#flush() + */ + public void flush() + { + } + + /** + * @see org.alfresco.repo.transaction.TransactionListener#beforeCommit(boolean) + */ + public void beforeCommit(boolean readOnly) + { + } + + /** + * @see org.alfresco.repo.transaction.TransactionListener#beforeCompletion() + */ + public void beforeCompletion() + { + } + + /** + * @see org.alfresco.repo.transaction.TransactionListener#afterCommit() + */ + public void afterCommit() + { + this.actionService.postCommit(); + } + + /** + * @see org.alfresco.repo.transaction.TransactionListener#afterRollback() + */ + public void afterRollback() + { + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() + { + return this.id.hashCode(); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj instanceof ActionTransactionListener) + { + ActionTransactionListener that = (ActionTransactionListener) obj; + return (this.id.equals(that.id)); + } + else + { + return false; + } + } + +} diff --git a/source/java/org/alfresco/repo/action/ActionsAspect.java b/source/java/org/alfresco/repo/action/ActionsAspect.java index d1f5e2eb84..0deb1df5cd 100644 --- a/source/java/org/alfresco/repo/action/ActionsAspect.java +++ b/source/java/org/alfresco/repo/action/ActionsAspect.java @@ -1,192 +1,192 @@ -package org.alfresco.repo.action; - -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.copy.CopyBehaviourCallback; -import org.alfresco.repo.copy.CopyDetails; -import org.alfresco.repo.copy.CopyServicePolicies; -import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; -import org.alfresco.repo.node.NodeServicePolicies; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.service.cmr.repository.AssociationRef; -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.rule.RuleService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.PropertyCheck; - -/** - * Class containing behaviour for the actions aspect - * - * @author Roy Wetherall - */ -public class ActionsAspect implements CopyServicePolicies.OnCopyNodePolicy, CopyServicePolicies.OnCopyCompletePolicy, NodeServicePolicies.OnDeleteAssociationPolicy -{ - private PolicyComponent policyComponent; - private BehaviourFilter behaviourFilter; - private RuleService ruleService; - private NodeService nodeService; - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public void setBehaviourFilter(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setRuleService(RuleService ruleService) - { - this.ruleService = ruleService; - } - - public void init() - { - PropertyCheck.mandatory(this, "policyComponent", policyComponent); - PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter); - PropertyCheck.mandatory(this, "ruleService", ruleService); - PropertyCheck.mandatory(this, "nodeService", nodeService); - - this.policyComponent.bindAssociationBehaviour( - NodeServicePolicies.OnDeleteAssociationPolicy.QNAME, - ActionModel.TYPE_ACTION_SCHEDULE, - ActionModel.ASSOC_SCHEDULED_ACTION, - new JavaBehaviour(this, "onDeleteAssociation")); - - this.policyComponent.bindClassBehaviour( - CopyServicePolicies.OnCopyNodePolicy.QNAME, - ActionModel.ASPECT_ACTIONS, - new JavaBehaviour(this, "getCopyCallback")); - this.policyComponent.bindClassBehaviour( - CopyServicePolicies.OnCopyCompletePolicy.QNAME, - ActionModel.ASPECT_ACTIONS, - new JavaBehaviour(this, "onCopyComplete")); - - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), - ActionModel.ASPECT_ACTIONS, - new JavaBehaviour(this, "onAddAspect")); - } - - @Override - public void onDeleteAssociation(AssociationRef nodeAssocRef) - { - // The act:actionSchedule type must have the association, so remove the source when the - // association is deleted. - NodeRef actionScheduleNodeRef = nodeAssocRef.getSourceRef(); - if (nodeService.exists(actionScheduleNodeRef) && !nodeService.hasAspect(actionScheduleNodeRef, ContentModel.ASPECT_PENDING_DELETE)) - { - // Delete the source - nodeService.deleteNode(actionScheduleNodeRef); - } - } - - /** - * On add aspect policy behaviour - * - * @param nodeRef NodeRef - * @param aspectTypeQName QName - */ - public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) - { - this.ruleService.disableRules(nodeRef); - try - { - this.nodeService.createNode( - nodeRef, - ActionModel.ASSOC_ACTION_FOLDER, - ActionModel.ASSOC_ACTION_FOLDER, - ContentModel.TYPE_SYSTEM_FOLDER); - } - finally - { - this.ruleService.enableRules(nodeRef); - } - } - - /** - * @return Returns {@code ActionsAspectCopyBehaviourCallback} - */ - public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) - { - return new ActionsAspectCopyBehaviourCallback(behaviourFilter); - } - - /** - * Extends the default copy behaviour to include cascading to action folders. - * - * @author Derek Hulley - * @since 3.2 - */ - private static class ActionsAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback - { - private final BehaviourFilter behaviourFilter; - private ActionsAspectCopyBehaviourCallback(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } - - /** - * Disables the aspect behaviour for this node - * - * @return Returns true - */ - @Override - public boolean getMustCopy(QName classQName, CopyDetails copyDetails) - { - NodeRef targetNodeRef = copyDetails.getTargetNodeRef(); - behaviourFilter.disableBehaviour(targetNodeRef, ActionModel.ASPECT_ACTIONS); - // Always copy - return true; - } - - /** - * Always cascades to the action folders - */ - @Override - public ChildAssocCopyAction getChildAssociationCopyAction( - QName classQName, - CopyDetails copyDetails, - CopyChildAssociationDetails childAssocCopyDetails) - { - ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef(); - if (childAssocRef.getTypeQName().equals(ActionModel.ASSOC_ACTION_FOLDER)) - { - return ChildAssocCopyAction.COPY_CHILD; - } - else - { - throw new IllegalStateException( - "Behaviour should have been invoked: \n" + - " Aspect: " + this.getClass().getName() + "\n" + - " " + childAssocCopyDetails + "\n" + - " " + copyDetails); - } - } - } - - /** - * Re-enable aspect behaviour for the source node - */ - public void onCopyComplete( - QName classRef, - NodeRef sourceNodeRef, - NodeRef destinationRef, - boolean copyToNewNode, - Map copyMap) - { - behaviourFilter.enableBehaviour(sourceNodeRef, ActionModel.ASPECT_ACTIONS); - } -} +package org.alfresco.repo.action; + +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.repository.AssociationRef; +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.rule.RuleService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyCheck; + +/** + * Class containing behaviour for the actions aspect + * + * @author Roy Wetherall + */ +public class ActionsAspect implements CopyServicePolicies.OnCopyNodePolicy, CopyServicePolicies.OnCopyCompletePolicy, NodeServicePolicies.OnDeleteAssociationPolicy +{ + private PolicyComponent policyComponent; + private BehaviourFilter behaviourFilter; + private RuleService ruleService; + private NodeService nodeService; + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setRuleService(RuleService ruleService) + { + this.ruleService = ruleService; + } + + public void init() + { + PropertyCheck.mandatory(this, "policyComponent", policyComponent); + PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter); + PropertyCheck.mandatory(this, "ruleService", ruleService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + + this.policyComponent.bindAssociationBehaviour( + NodeServicePolicies.OnDeleteAssociationPolicy.QNAME, + ActionModel.TYPE_ACTION_SCHEDULE, + ActionModel.ASSOC_SCHEDULED_ACTION, + new JavaBehaviour(this, "onDeleteAssociation")); + + this.policyComponent.bindClassBehaviour( + CopyServicePolicies.OnCopyNodePolicy.QNAME, + ActionModel.ASPECT_ACTIONS, + new JavaBehaviour(this, "getCopyCallback")); + this.policyComponent.bindClassBehaviour( + CopyServicePolicies.OnCopyCompletePolicy.QNAME, + ActionModel.ASPECT_ACTIONS, + new JavaBehaviour(this, "onCopyComplete")); + + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), + ActionModel.ASPECT_ACTIONS, + new JavaBehaviour(this, "onAddAspect")); + } + + @Override + public void onDeleteAssociation(AssociationRef nodeAssocRef) + { + // The act:actionSchedule type must have the association, so remove the source when the + // association is deleted. + NodeRef actionScheduleNodeRef = nodeAssocRef.getSourceRef(); + if (nodeService.exists(actionScheduleNodeRef) && !nodeService.hasAspect(actionScheduleNodeRef, ContentModel.ASPECT_PENDING_DELETE)) + { + // Delete the source + nodeService.deleteNode(actionScheduleNodeRef); + } + } + + /** + * On add aspect policy behaviour + * + * @param nodeRef NodeRef + * @param aspectTypeQName QName + */ + public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) + { + this.ruleService.disableRules(nodeRef); + try + { + this.nodeService.createNode( + nodeRef, + ActionModel.ASSOC_ACTION_FOLDER, + ActionModel.ASSOC_ACTION_FOLDER, + ContentModel.TYPE_SYSTEM_FOLDER); + } + finally + { + this.ruleService.enableRules(nodeRef); + } + } + + /** + * @return Returns {@code ActionsAspectCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return new ActionsAspectCopyBehaviourCallback(behaviourFilter); + } + + /** + * Extends the default copy behaviour to include cascading to action folders. + * + * @author Derek Hulley + * @since 3.2 + */ + private static class ActionsAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private final BehaviourFilter behaviourFilter; + private ActionsAspectCopyBehaviourCallback(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + /** + * Disables the aspect behaviour for this node + * + * @return Returns true + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + NodeRef targetNodeRef = copyDetails.getTargetNodeRef(); + behaviourFilter.disableBehaviour(targetNodeRef, ActionModel.ASPECT_ACTIONS); + // Always copy + return true; + } + + /** + * Always cascades to the action folders + */ + @Override + public ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef(); + if (childAssocRef.getTypeQName().equals(ActionModel.ASSOC_ACTION_FOLDER)) + { + return ChildAssocCopyAction.COPY_CHILD; + } + else + { + throw new IllegalStateException( + "Behaviour should have been invoked: \n" + + " Aspect: " + this.getClass().getName() + "\n" + + " " + childAssocCopyDetails + "\n" + + " " + copyDetails); + } + } + } + + /** + * Re-enable aspect behaviour for the source node + */ + public void onCopyComplete( + QName classRef, + NodeRef sourceNodeRef, + NodeRef destinationRef, + boolean copyToNewNode, + Map copyMap) + { + behaviourFilter.enableBehaviour(sourceNodeRef, ActionModel.ASPECT_ACTIONS); + } +} diff --git a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueue.java b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueue.java index 07c2a69839..49cbe88d08 100644 --- a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueue.java +++ b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueue.java @@ -1,29 +1,29 @@ -package org.alfresco.repo.action; - -import java.util.Set; - -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Asynchronous action execution queue - * - * @author Roy Wetherall - */ -public interface AsynchronousActionExecutionQueue -{ - /** - * @param actionService RuntimeActionService - * @param action Action - * @param actionedUponNodeRef NodeRef - * @param checkConditions boolean - * @param actionChain Set - */ - void executeAction( - RuntimeActionService actionService, - Action action, - NodeRef actionedUponNodeRef, - boolean checkConditions, - Set actionChain); - -} +package org.alfresco.repo.action; + +import java.util.Set; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Asynchronous action execution queue + * + * @author Roy Wetherall + */ +public interface AsynchronousActionExecutionQueue +{ + /** + * @param actionService RuntimeActionService + * @param action Action + * @param actionedUponNodeRef NodeRef + * @param checkConditions boolean + * @param actionChain Set + */ + void executeAction( + RuntimeActionService actionService, + Action action, + NodeRef actionedUponNodeRef, + boolean checkConditions, + Set actionChain); + +} diff --git a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java index b551b39355..3fd6a1dee5 100644 --- a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java +++ b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java @@ -1,433 +1,433 @@ -package org.alfresco.repo.action; - -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.Vector; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ThreadPoolExecutor; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.error.StackTraceUtil; -import org.alfresco.repo.action.AsynchronousActionExecutionQueuePolicies.OnAsyncActionExecute; -import org.alfresco.repo.policy.ClassPolicyDelegate; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.rule.RuleServiceImpl; -import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.repo.tenant.TenantUtil; -import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.repo.transaction.TransactionListenerAdapter; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * The asynchronous action execution queue implementation - * - * @author Roy Wetherall - */ -public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionExecutionQueue -{ - private static Log logger = LogFactory.getLog(AsynchronousActionExecutionQueueImpl.class); - - /** Services */ - private ActionServiceImpl actionServiceImpl; - private ThreadPoolExecutor threadPoolExecutor; - private TransactionService transactionService; - private PolicyComponent policyComponent; - private Map - actionFilters = new ConcurrentHashMap(); - private String id; - - /** - * We keep a record of ongoing asynchronous actions (this includes those being executed and - * those that are in the queue). - * This needs to be thread-safe - hence the Vector. - */ - List ongoingActions = new Vector(); - - // Policy delegates - private ClassPolicyDelegate onAsyncActionExecuteDelegate; - - /** - * Default constructor - */ - public AsynchronousActionExecutionQueueImpl() - { - } - - /** - * Init method. Registers the policies. - */ - public void init() - { - // Register the execution queue with the ActionService - actionServiceImpl.registerAsynchronousActionExecutionQueue(id, this); - - // Register the policies - onAsyncActionExecuteDelegate = policyComponent.registerClassPolicy(OnAsyncActionExecute.class); - } - - /** - * @since Thor Phase 2 Sprint 2 - */ - public void setActionServiceImpl(ActionServiceImpl serviceImpl) - { - this.actionServiceImpl = serviceImpl; - } - - /** - * @since Thor Phase 2 Sprint 2 - */ - public void setId(String id) - { - this.id = id; - } - - /** - * Set the thread pool, which may be shared with other components, that will be used - * to run the actions. - * - * @param threadPoolExecutor the thread pool - */ - public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) - { - this.threadPoolExecutor = threadPoolExecutor; - } - - /** - * Set the transaction service - * - * @param transactionService the transaction service - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - /** - * @deprecated Not used since 3.4 - */ - public void setAuthenticationContext(AuthenticationContext authenticationContext) - { - logger.warn("Property 'authenticationContext' is no longer required."); - } - - /** - * Set the policy component - * - * @param policyComponent policy component - */ - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - private void invokeOnAsyncActionExecutePolicy(Action action, NodeRef actionedUponNodeRef) - { - // Execute the policy, passing it all details, firing as a general action case - AsynchronousActionExecutionQueuePolicies.OnAsyncActionExecute policy = - onAsyncActionExecuteDelegate.get(actionedUponNodeRef, ActionModel.TYPE_ACTION); - policy.onAsyncActionExecute(action, actionedUponNodeRef); - } - - /** - * This method registers an action filter, which can be used to prevent unwanted or unnecessary - * asynchronous actions from being scheduled for execution. - * - * @param filter the filter implementation. - */ - public void registerActionFilter(AbstractAsynchronousActionFilter filter) - { - String filterName = filter.getName(); - - if (logger.isDebugEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Registered asynchronous action filter ") - .append(filter.getName()).append(" for action ") - .append(filter.getActionDefinitionName()); - logger.debug(msg.toString()); - } - - AbstractAsynchronousActionFilter existingFilter = actionFilters.get(filterName); - if (logger.isDebugEnabled() && existingFilter != null) - { - StringBuilder msg = new StringBuilder(); - msg.append("This replaces previous filter ") - .append(existingFilter.getName()); - logger.debug(msg.toString()); - } - - this.actionFilters.put(filter.getName(), filter); - } - - /** - * {@inheritDoc} - */ - public void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef, - boolean checkConditions, Set actionChain) - { - executeAction(actionService, action, actionedUponNodeRef, checkConditions, actionChain, null); - } - - @SuppressWarnings("unchecked") - public void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef, - boolean checkConditions, Set actionChain, NodeRef actionExecutionHistoryNodeRef) - { - if (logger.isDebugEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Received request to execute async action ").append(action.getActionDefinitionName()) - .append(" on ").append(actionedUponNodeRef); - logger.debug(msg.toString()); - - msg = new StringBuilder(); - msg.append("ThreadPool's active count = ").append(this.threadPoolExecutor.getActiveCount()); - logger.debug(msg.toString()); - - msg = new StringBuilder(); - msg.append("ThreadPool's queue size = ").append(this.threadPoolExecutor.getQueue().size()); - logger.debug(msg.toString()); - } - - Set executedRules = - (Set) AlfrescoTransactionSupport.getResource("RuleServiceImpl.ExecutedRules"); - Runnable runnable = new ActionExecutionWrapper( - actionService, - action, - actionedUponNodeRef, - checkConditions, - actionChain, - executedRules); - - // Consider whether this action should be filtered out by one of the registered filters. - boolean newActionShouldBeFilteredOut = false; - OngoingAsyncAction nodeBeingNewlyActioned = new OngoingAsyncAction(actionedUponNodeRef, action); - - for (Entry entry : actionFilters.entrySet()) - { - AbstractAsynchronousActionFilter comparator = entry.getValue(); - String actionDefinitionName = comparator.getActionDefinitionName(); - - if (actionDefinitionName.equals(action.getActionDefinitionName()) == false) - { - // We're only interested in registered actions with the same name as this one. - continue; - } - else - { - // Now we've found a registered action that matches the current one. - // So we'll go through the actions that are ongoing and consider them for matches with this one. - // Need to synchronize to prevent changes to ongoingActions whilst iterating. Assume that ongoingActions - // is not going to be too big and the loop will execute quite quickly, so that the synchronization - // will not impact concurrency too much. - synchronized(this.ongoingActions) - { - for (OngoingAsyncAction ongoingAction : this.ongoingActions) - { - if (comparator.compare(ongoingAction, nodeBeingNewlyActioned) == 0) - { - newActionShouldBeFilteredOut = true; - break; - } - } - } - } - } - if (newActionShouldBeFilteredOut) - { - if (logger.isDebugEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Dropping action ").append(action).append(" as equivalent is ongoing."); - logger.debug(msg.toString()); - } - return; - } - else - { - if (logger.isDebugEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Executing action ").append(action); - logger.debug(msg.toString()); - } - - // Queue it and do it. - ongoingActions.add(nodeBeingNewlyActioned); - threadPoolExecutor.execute(runnable); - } - - // Done - if (logger.isDebugEnabled()) - { - // get the stack trace - Exception e = new Exception(); - e.fillInStackTrace(); - StackTraceElement[] trace = e.getStackTrace(); - StringBuilder sb = new StringBuilder(); - sb.append("\n") - .append("Placed action on execution queue: \n") - .append(" Action: " + action); - String msg = sb.toString(); - sb = new StringBuilder(); - StackTraceUtil.buildStackTrace(msg, trace, sb, -1); - logger.debug(sb); - } - } - - private void handleAsyncActionIsCompleted(NodeRef n, Action action) { - if (logger.isDebugEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Completed action ").append(action); - logger.debug(msg.toString()); - } - OngoingAsyncAction ongoing = new OngoingAsyncAction(n, action); - ongoingActions.remove(ongoing); - } - - /** - * Transaction listener used to invoke callback policies - */ - public class CallbackTransactionListener extends TransactionListenerAdapter - { - private Action action; - private NodeRef actionedUponNodeRef; - - /** - * Constructor - * - * @param action action - * @param actionedUponNodeRef actioned upon node reference - */ - public CallbackTransactionListener(Action action, NodeRef actionedUponNodeRef) - { - this.action = action; - this.actionedUponNodeRef = actionedUponNodeRef; - } - - /** - * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() - */ - @Override - public void afterCommit() - { - // Invoke the execute complete policy - invokeOnAsyncActionExecutePolicy(action, actionedUponNodeRef); - } - } - - /** - * Runnable class to wrap the execution of the action. - */ - private class ActionExecutionWrapper implements Runnable - { - private RuntimeActionService actionService; - - private Action action; - private NodeRef actionedUponNodeRef; - private boolean checkConditions; - private Set actionChain; - private Set executedRules; - - /** - * @param actionService the action service - * @param action the action to perform - * @param actionedUponNodeRef the node to perform the action on - * @param checkConditions the check conditions - * @param actionChain the action chain - * @param executedRules list of executions done to helps to prevent loop scenarios with async rules - */ - public ActionExecutionWrapper( - RuntimeActionService actionService, - Action action, - NodeRef actionedUponNodeRef, - boolean checkConditions, - Set actionChain, - Set executedRules) - { - this.actionService = actionService; - this.actionedUponNodeRef = actionedUponNodeRef; - this.action = action; - this.checkConditions = checkConditions; - this.actionChain = actionChain; - this.executedRules = executedRules; - } - - /** - * Executes the action via the action runtime service - * - * @see java.lang.Runnable#run() - */ - public void run() - { - try - { - // Get the run as user name - final String userName = ((ActionImpl)ActionExecutionWrapper.this.action).getRunAsUser(); - if (userName == null) - { - throw new ActionServiceException("Cannot execute action asynchronously since run as user is 'null'"); - } - // Get the tenant the action was submitted from - final String tenantId = ((ActionImpl)ActionExecutionWrapper.this.action).getTenantId(); - - // import the content - TenantRunAsWork actionRunAs = new TenantRunAsWork() - { - public Object doWork() throws Exception - { - RetryingTransactionCallback actionCallback = new RetryingTransactionCallback() - { - public Object execute() - { - // If we have rules, apply them - if (ActionExecutionWrapper.this.executedRules != null) - { - AlfrescoTransactionSupport.bindResource("RuleServiceImpl.ExecutedRules", ActionExecutionWrapper.this.executedRules); - } - - // Allow other classes to know when this action completes - AlfrescoTransactionSupport.bindListener(new CallbackTransactionListener( - ActionExecutionWrapper.this.action, - ActionExecutionWrapper.this.actionedUponNodeRef - )); - - // Have the action run - ActionExecutionWrapper.this.actionService.executeActionImpl( - ActionExecutionWrapper.this.action, - ActionExecutionWrapper.this.actionedUponNodeRef, - ActionExecutionWrapper.this.checkConditions, true, - ActionExecutionWrapper.this.actionChain); - - return null; - } - }; - return transactionService.getRetryingTransactionHelper().doInTransaction(actionCallback); - } - }; - TenantUtil.runAsUserTenant(actionRunAs, userName, tenantId); - } - catch (Throwable e) - { - Throwable rootCause = (e instanceof AlfrescoRuntimeException) ? ((AlfrescoRuntimeException)e).getRootCause() : null; - String message = (rootCause == null ? null : rootCause.getMessage()); - message = "Failed to execute asynchronous action: " + action+ (message == null ? "" : ": "+message); - if(!ActionExecutionWrapper.this.actionService.onLogException(action, logger, rootCause, message)) - { - //if not handled by the executor just show in the log - logger.error(message, e); - } - } - handleAsyncActionIsCompleted(actionedUponNodeRef, action); - } - } +package org.alfresco.repo.action; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadPoolExecutor; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.error.StackTraceUtil; +import org.alfresco.repo.action.AsynchronousActionExecutionQueuePolicies.OnAsyncActionExecute; +import org.alfresco.repo.policy.ClassPolicyDelegate; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.rule.RuleServiceImpl; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The asynchronous action execution queue implementation + * + * @author Roy Wetherall + */ +public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionExecutionQueue +{ + private static Log logger = LogFactory.getLog(AsynchronousActionExecutionQueueImpl.class); + + /** Services */ + private ActionServiceImpl actionServiceImpl; + private ThreadPoolExecutor threadPoolExecutor; + private TransactionService transactionService; + private PolicyComponent policyComponent; + private Map + actionFilters = new ConcurrentHashMap(); + private String id; + + /** + * We keep a record of ongoing asynchronous actions (this includes those being executed and + * those that are in the queue). + * This needs to be thread-safe - hence the Vector. + */ + List ongoingActions = new Vector(); + + // Policy delegates + private ClassPolicyDelegate onAsyncActionExecuteDelegate; + + /** + * Default constructor + */ + public AsynchronousActionExecutionQueueImpl() + { + } + + /** + * Init method. Registers the policies. + */ + public void init() + { + // Register the execution queue with the ActionService + actionServiceImpl.registerAsynchronousActionExecutionQueue(id, this); + + // Register the policies + onAsyncActionExecuteDelegate = policyComponent.registerClassPolicy(OnAsyncActionExecute.class); + } + + /** + * @since Thor Phase 2 Sprint 2 + */ + public void setActionServiceImpl(ActionServiceImpl serviceImpl) + { + this.actionServiceImpl = serviceImpl; + } + + /** + * @since Thor Phase 2 Sprint 2 + */ + public void setId(String id) + { + this.id = id; + } + + /** + * Set the thread pool, which may be shared with other components, that will be used + * to run the actions. + * + * @param threadPoolExecutor the thread pool + */ + public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) + { + this.threadPoolExecutor = threadPoolExecutor; + } + + /** + * Set the transaction service + * + * @param transactionService the transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * @deprecated Not used since 3.4 + */ + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + logger.warn("Property 'authenticationContext' is no longer required."); + } + + /** + * Set the policy component + * + * @param policyComponent policy component + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + private void invokeOnAsyncActionExecutePolicy(Action action, NodeRef actionedUponNodeRef) + { + // Execute the policy, passing it all details, firing as a general action case + AsynchronousActionExecutionQueuePolicies.OnAsyncActionExecute policy = + onAsyncActionExecuteDelegate.get(actionedUponNodeRef, ActionModel.TYPE_ACTION); + policy.onAsyncActionExecute(action, actionedUponNodeRef); + } + + /** + * This method registers an action filter, which can be used to prevent unwanted or unnecessary + * asynchronous actions from being scheduled for execution. + * + * @param filter the filter implementation. + */ + public void registerActionFilter(AbstractAsynchronousActionFilter filter) + { + String filterName = filter.getName(); + + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Registered asynchronous action filter ") + .append(filter.getName()).append(" for action ") + .append(filter.getActionDefinitionName()); + logger.debug(msg.toString()); + } + + AbstractAsynchronousActionFilter existingFilter = actionFilters.get(filterName); + if (logger.isDebugEnabled() && existingFilter != null) + { + StringBuilder msg = new StringBuilder(); + msg.append("This replaces previous filter ") + .append(existingFilter.getName()); + logger.debug(msg.toString()); + } + + this.actionFilters.put(filter.getName(), filter); + } + + /** + * {@inheritDoc} + */ + public void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef, + boolean checkConditions, Set actionChain) + { + executeAction(actionService, action, actionedUponNodeRef, checkConditions, actionChain, null); + } + + @SuppressWarnings("unchecked") + public void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef, + boolean checkConditions, Set actionChain, NodeRef actionExecutionHistoryNodeRef) + { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Received request to execute async action ").append(action.getActionDefinitionName()) + .append(" on ").append(actionedUponNodeRef); + logger.debug(msg.toString()); + + msg = new StringBuilder(); + msg.append("ThreadPool's active count = ").append(this.threadPoolExecutor.getActiveCount()); + logger.debug(msg.toString()); + + msg = new StringBuilder(); + msg.append("ThreadPool's queue size = ").append(this.threadPoolExecutor.getQueue().size()); + logger.debug(msg.toString()); + } + + Set executedRules = + (Set) AlfrescoTransactionSupport.getResource("RuleServiceImpl.ExecutedRules"); + Runnable runnable = new ActionExecutionWrapper( + actionService, + action, + actionedUponNodeRef, + checkConditions, + actionChain, + executedRules); + + // Consider whether this action should be filtered out by one of the registered filters. + boolean newActionShouldBeFilteredOut = false; + OngoingAsyncAction nodeBeingNewlyActioned = new OngoingAsyncAction(actionedUponNodeRef, action); + + for (Entry entry : actionFilters.entrySet()) + { + AbstractAsynchronousActionFilter comparator = entry.getValue(); + String actionDefinitionName = comparator.getActionDefinitionName(); + + if (actionDefinitionName.equals(action.getActionDefinitionName()) == false) + { + // We're only interested in registered actions with the same name as this one. + continue; + } + else + { + // Now we've found a registered action that matches the current one. + // So we'll go through the actions that are ongoing and consider them for matches with this one. + // Need to synchronize to prevent changes to ongoingActions whilst iterating. Assume that ongoingActions + // is not going to be too big and the loop will execute quite quickly, so that the synchronization + // will not impact concurrency too much. + synchronized(this.ongoingActions) + { + for (OngoingAsyncAction ongoingAction : this.ongoingActions) + { + if (comparator.compare(ongoingAction, nodeBeingNewlyActioned) == 0) + { + newActionShouldBeFilteredOut = true; + break; + } + } + } + } + } + if (newActionShouldBeFilteredOut) + { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Dropping action ").append(action).append(" as equivalent is ongoing."); + logger.debug(msg.toString()); + } + return; + } + else + { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Executing action ").append(action); + logger.debug(msg.toString()); + } + + // Queue it and do it. + ongoingActions.add(nodeBeingNewlyActioned); + threadPoolExecutor.execute(runnable); + } + + // Done + if (logger.isDebugEnabled()) + { + // get the stack trace + Exception e = new Exception(); + e.fillInStackTrace(); + StackTraceElement[] trace = e.getStackTrace(); + StringBuilder sb = new StringBuilder(); + sb.append("\n") + .append("Placed action on execution queue: \n") + .append(" Action: " + action); + String msg = sb.toString(); + sb = new StringBuilder(); + StackTraceUtil.buildStackTrace(msg, trace, sb, -1); + logger.debug(sb); + } + } + + private void handleAsyncActionIsCompleted(NodeRef n, Action action) { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Completed action ").append(action); + logger.debug(msg.toString()); + } + OngoingAsyncAction ongoing = new OngoingAsyncAction(n, action); + ongoingActions.remove(ongoing); + } + + /** + * Transaction listener used to invoke callback policies + */ + public class CallbackTransactionListener extends TransactionListenerAdapter + { + private Action action; + private NodeRef actionedUponNodeRef; + + /** + * Constructor + * + * @param action action + * @param actionedUponNodeRef actioned upon node reference + */ + public CallbackTransactionListener(Action action, NodeRef actionedUponNodeRef) + { + this.action = action; + this.actionedUponNodeRef = actionedUponNodeRef; + } + + /** + * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() + */ + @Override + public void afterCommit() + { + // Invoke the execute complete policy + invokeOnAsyncActionExecutePolicy(action, actionedUponNodeRef); + } + } + + /** + * Runnable class to wrap the execution of the action. + */ + private class ActionExecutionWrapper implements Runnable + { + private RuntimeActionService actionService; + + private Action action; + private NodeRef actionedUponNodeRef; + private boolean checkConditions; + private Set actionChain; + private Set executedRules; + + /** + * @param actionService the action service + * @param action the action to perform + * @param actionedUponNodeRef the node to perform the action on + * @param checkConditions the check conditions + * @param actionChain the action chain + * @param executedRules list of executions done to helps to prevent loop scenarios with async rules + */ + public ActionExecutionWrapper( + RuntimeActionService actionService, + Action action, + NodeRef actionedUponNodeRef, + boolean checkConditions, + Set actionChain, + Set executedRules) + { + this.actionService = actionService; + this.actionedUponNodeRef = actionedUponNodeRef; + this.action = action; + this.checkConditions = checkConditions; + this.actionChain = actionChain; + this.executedRules = executedRules; + } + + /** + * Executes the action via the action runtime service + * + * @see java.lang.Runnable#run() + */ + public void run() + { + try + { + // Get the run as user name + final String userName = ((ActionImpl)ActionExecutionWrapper.this.action).getRunAsUser(); + if (userName == null) + { + throw new ActionServiceException("Cannot execute action asynchronously since run as user is 'null'"); + } + // Get the tenant the action was submitted from + final String tenantId = ((ActionImpl)ActionExecutionWrapper.this.action).getTenantId(); + + // import the content + TenantRunAsWork actionRunAs = new TenantRunAsWork() + { + public Object doWork() throws Exception + { + RetryingTransactionCallback actionCallback = new RetryingTransactionCallback() + { + public Object execute() + { + // If we have rules, apply them + if (ActionExecutionWrapper.this.executedRules != null) + { + AlfrescoTransactionSupport.bindResource("RuleServiceImpl.ExecutedRules", ActionExecutionWrapper.this.executedRules); + } + + // Allow other classes to know when this action completes + AlfrescoTransactionSupport.bindListener(new CallbackTransactionListener( + ActionExecutionWrapper.this.action, + ActionExecutionWrapper.this.actionedUponNodeRef + )); + + // Have the action run + ActionExecutionWrapper.this.actionService.executeActionImpl( + ActionExecutionWrapper.this.action, + ActionExecutionWrapper.this.actionedUponNodeRef, + ActionExecutionWrapper.this.checkConditions, true, + ActionExecutionWrapper.this.actionChain); + + return null; + } + }; + return transactionService.getRetryingTransactionHelper().doInTransaction(actionCallback); + } + }; + TenantUtil.runAsUserTenant(actionRunAs, userName, tenantId); + } + catch (Throwable e) + { + Throwable rootCause = (e instanceof AlfrescoRuntimeException) ? ((AlfrescoRuntimeException)e).getRootCause() : null; + String message = (rootCause == null ? null : rootCause.getMessage()); + message = "Failed to execute asynchronous action: " + action+ (message == null ? "" : ": "+message); + if(!ActionExecutionWrapper.this.actionService.onLogException(action, logger, rootCause, message)) + { + //if not handled by the executor just show in the log + logger.error(message, e); + } + } + handleAsyncActionIsCompleted(actionedUponNodeRef, action); + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/action/evaluator/CompareMimeTypeEvaluator.java b/source/java/org/alfresco/repo/action/evaluator/CompareMimeTypeEvaluator.java index 3dbb7c478b..65f3cfa7a9 100644 --- a/source/java/org/alfresco/repo/action/evaluator/CompareMimeTypeEvaluator.java +++ b/source/java/org/alfresco/repo/action/evaluator/CompareMimeTypeEvaluator.java @@ -1,83 +1,83 @@ -package org.alfresco.repo.action.evaluator; - -import java.util.List; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation; -import org.alfresco.repo.action.evaluator.compare.ContentPropertyName; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; - -/** - * Compare mime type evaluator - * - * @author Roy Wetherall - */ -public class CompareMimeTypeEvaluator extends ComparePropertyValueEvaluator -{ - /** - * Evaluator constants - */ - public static final String NAME = "compare-mime-type"; - - /** - * - */ - private static final String ERRID_NOT_A_CONTENT_TYPE = "compare_mime_type_evaluator.not_a_content_type"; - private static final String ERRID_NO_PROPERTY_DEFINTION_FOUND = "compare_mime_type_evaluator.no_property_definition_found"; - - /** - * @see org.alfresco.repo.action.evaluator.ActionConditionEvaluatorAbstractBase#evaluateImpl(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean evaluateImpl(ActionCondition actionCondition, NodeRef actionedUponNodeRef) - { - QName propertyQName = (QName)actionCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY); - if (propertyQName == null) - { - // Default to the standard content property - actionCondition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, ContentModel.PROP_CONTENT); - } - else - { - // Ensure that we are dealing with a content property - QName propertyTypeQName = null; - PropertyDefinition propertyDefintion = this.dictionaryService.getProperty(propertyQName); - if (propertyDefintion != null) - { - propertyTypeQName = propertyDefintion.getDataType().getName(); - if (DataTypeDefinition.CONTENT.equals(propertyTypeQName) == false) - { - throw new ActionServiceException(ERRID_NOT_A_CONTENT_TYPE); - } - } - else - { - throw new ActionServiceException(ERRID_NO_PROPERTY_DEFINTION_FOUND); - } - } - - // Set the operation to equals - actionCondition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); - - // Set the content property to be MIMETYPE - actionCondition.setParameterValue(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, ContentPropertyName.MIME_TYPE.toString()); - - return super.evaluateImpl(actionCondition, actionedUponNodeRef); - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_PROPERTY, DataTypeDefinition.QNAME, false, getParamDisplayLabel(PARAM_PROPERTY))); - paramList.add(new ParameterDefinitionImpl(PARAM_VALUE, DataTypeDefinition.ANY, true, getParamDisplayLabel(PARAM_VALUE), false, "ac-mimetypes")); - } -} +package org.alfresco.repo.action.evaluator; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation; +import org.alfresco.repo.action.evaluator.compare.ContentPropertyName; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * Compare mime type evaluator + * + * @author Roy Wetherall + */ +public class CompareMimeTypeEvaluator extends ComparePropertyValueEvaluator +{ + /** + * Evaluator constants + */ + public static final String NAME = "compare-mime-type"; + + /** + * + */ + private static final String ERRID_NOT_A_CONTENT_TYPE = "compare_mime_type_evaluator.not_a_content_type"; + private static final String ERRID_NO_PROPERTY_DEFINTION_FOUND = "compare_mime_type_evaluator.no_property_definition_found"; + + /** + * @see org.alfresco.repo.action.evaluator.ActionConditionEvaluatorAbstractBase#evaluateImpl(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean evaluateImpl(ActionCondition actionCondition, NodeRef actionedUponNodeRef) + { + QName propertyQName = (QName)actionCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY); + if (propertyQName == null) + { + // Default to the standard content property + actionCondition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, ContentModel.PROP_CONTENT); + } + else + { + // Ensure that we are dealing with a content property + QName propertyTypeQName = null; + PropertyDefinition propertyDefintion = this.dictionaryService.getProperty(propertyQName); + if (propertyDefintion != null) + { + propertyTypeQName = propertyDefintion.getDataType().getName(); + if (DataTypeDefinition.CONTENT.equals(propertyTypeQName) == false) + { + throw new ActionServiceException(ERRID_NOT_A_CONTENT_TYPE); + } + } + else + { + throw new ActionServiceException(ERRID_NO_PROPERTY_DEFINTION_FOUND); + } + } + + // Set the operation to equals + actionCondition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); + + // Set the content property to be MIMETYPE + actionCondition.setParameterValue(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, ContentPropertyName.MIME_TYPE.toString()); + + return super.evaluateImpl(actionCondition, actionedUponNodeRef); + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_PROPERTY, DataTypeDefinition.QNAME, false, getParamDisplayLabel(PARAM_PROPERTY))); + paramList.add(new ParameterDefinitionImpl(PARAM_VALUE, DataTypeDefinition.ANY, true, getParamDisplayLabel(PARAM_VALUE), false, "ac-mimetypes")); + } +} diff --git a/source/java/org/alfresco/repo/action/evaluator/ComparePropertyValueEvaluator.java b/source/java/org/alfresco/repo/action/evaluator/ComparePropertyValueEvaluator.java index a49356847c..739bdcc0b4 100644 --- a/source/java/org/alfresco/repo/action/evaluator/ComparePropertyValueEvaluator.java +++ b/source/java/org/alfresco/repo/action/evaluator/ComparePropertyValueEvaluator.java @@ -1,299 +1,299 @@ -package org.alfresco.repo.action.evaluator; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation; -import org.alfresco.repo.action.evaluator.compare.ContentPropertyName; -import org.alfresco.repo.action.evaluator.compare.PropertyValueComparator; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Compare property value evaluator - * - * @author Roy Wetherall - */ -public class ComparePropertyValueEvaluator extends ActionConditionEvaluatorAbstractBase -{ - private static Log logger = LogFactory.getLog(ComparePropertyValueEvaluator.class); - - /** - * Evaluator constants - */ - public final static String NAME = "compare-property-value"; - - public final static String PARAM_PROPERTY = "property"; - public final static String PARAM_CONTENT_PROPERTY = "content-property"; - public final static String PARAM_VALUE = "value"; - public final static String PARAM_OPERATION = "operation"; - - /** - * The default property to check if none is specified in the properties - */ - private final static QName DEFAULT_PROPERTY = ContentModel.PROP_NAME; - - /** - * I18N message ID's - */ - private static final String MSGID_INVALID_OPERATION = "compare_property_value_evaluator.invalid_operation"; - private static final String MSGID_NO_CONTENT_PROPERTY = "compare_property_value_evaluator.no_content_property"; - - /** - * Map of comparators used by different property types - */ - private Map comparators = new HashMap(); - - /** - * The node service - */ - protected NodeService nodeService; - - /** - * The content service - */ - protected ContentService contentService; - - /** - * The dictionary service - */ - protected DictionaryService dictionaryService; - - /** - * Set node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the content service - * - * @param contentService the content service - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * Set the dictionary service - * - * @param dictionaryService the dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Set the list of property value comparators - * - * @param comparators the list of property value comparators - */ - public void setPropertyValueComparators(List comparators) - { - for (PropertyValueComparator comparator : comparators) - { - comparator.registerComparator(this); - } - } - - /** - * Registers a comparator for a given property data type. - * - * @param dataType property data type - * @param comparator property value comparator - */ - public void registerComparator(QName dataType, PropertyValueComparator comparator) - { - this.comparators.put(dataType, comparator); - } - - /** - * Add parameter definitions - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_PROPERTY, DataTypeDefinition.QNAME, false, getParamDisplayLabel(PARAM_PROPERTY))); - paramList.add(new ParameterDefinitionImpl(PARAM_CONTENT_PROPERTY, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_CONTENT_PROPERTY), false, "ac-content-properties")); - paramList.add(new ParameterDefinitionImpl(PARAM_VALUE, DataTypeDefinition.ANY, true, getParamDisplayLabel(PARAM_VALUE))); - paramList.add(new ParameterDefinitionImpl(PARAM_OPERATION, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_OPERATION), false, "ac-compare-operations")); - } - - /** - * @see ActionConditionEvaluatorAbstractBase#evaluateImpl(ActionCondition, NodeRef) - */ - public boolean evaluateImpl( - ActionCondition ruleCondition, - NodeRef actionedUponNodeRef) - { - boolean result = false; - - if (this.nodeService.exists(actionedUponNodeRef) == true) - { - // Get the name value of the node - QName propertyQName = (QName)ruleCondition.getParameterValue(PARAM_PROPERTY); - if (propertyQName == null) - { - if (logger.isWarnEnabled()) - logger.warn("ComparePropertyValue - Property is NULL. Setting to " + DEFAULT_PROPERTY); - - propertyQName = DEFAULT_PROPERTY; - } - - // Get the original value and the value to match - Serializable propertyValue = this.nodeService.getProperty(actionedUponNodeRef, propertyQName); - Serializable compareValue = ruleCondition.getParameterValue(PARAM_VALUE); - - // Get the operation - ComparePropertyValueOperation operation = null; - String operationString = (String)ruleCondition.getParameterValue(PARAM_OPERATION); - if (operationString != null) - { - operation = ComparePropertyValueOperation.valueOf(operationString); - } - - // Look at the type of the property (assume to be ANY if none found in dictionary) - QName propertyTypeQName = DataTypeDefinition.ANY; - PropertyDefinition propertyDefintion = this.dictionaryService.getProperty(propertyQName); - if (propertyDefintion != null) - { - propertyTypeQName = propertyDefintion.getDataType().getName(); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Evaluating Property Parameters, propertyQName - [" + propertyQName + - "] getInverted? [" + ruleCondition.getInvertCondition() +"] operation [" + - operation + "]"); - logger.debug("Compare Value [" + compareValue + "] Actual Value [" + propertyValue + "]"); - } - - // Sort out what to do if the property is a content property - if (DataTypeDefinition.CONTENT.equals(propertyTypeQName) == true) - { - // Get the content property name - ContentPropertyName contentProperty = null; - String contentPropertyString = (String)ruleCondition.getParameterValue(PARAM_CONTENT_PROPERTY); - if (contentPropertyString == null) - { - // Error if no content property has been set - throw new ActionServiceException(MSGID_NO_CONTENT_PROPERTY); - } - else - { - contentProperty = ContentPropertyName.valueOf(contentPropertyString); - } - - // Get the content data - if (propertyValue != null) - { - ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, propertyValue); - switch (contentProperty) - { - case ENCODING: - { - propertyTypeQName = DataTypeDefinition.TEXT; - propertyValue = contentData.getEncoding(); - break; - } - case SIZE: - { - propertyTypeQName = DataTypeDefinition.LONG; - propertyValue = contentData.getSize(); - break; - } - case MIME_TYPE: - { - propertyTypeQName = DataTypeDefinition.TEXT; - propertyValue = contentData.getMimetype(); - break; - } - } - } - } - - if (propertyValue != null) - { - // Try and get a matching comparator - PropertyValueComparator comparator = this.comparators.get(propertyTypeQName); - if (comparator != null) - { - // Figure out if property is multivalued, compare all of the entries till finding a match - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - if (propertyDef.isMultiValued()) - { - for(Serializable value : ((ArrayList) propertyValue)) - { - boolean success = comparator.compare(value, compareValue, operation); - if (success) - { - result = true; - break; - } - } - } - else - { - // Call the comparator for the property type - result = comparator.compare(propertyValue, compareValue, operation); - } - } - else - { - if (logger.isWarnEnabled()) - { - logger.warn("Comparator not found for property type " + propertyTypeQName); - } - // The default behaviour is to assume the property can only be compared using equals - if (operation != null && operation != ComparePropertyValueOperation.EQUALS) - { - // Error since only the equals operation is valid - throw new ActionServiceException( - MSGID_INVALID_OPERATION, - new Object[]{operation.toString(), propertyTypeQName.toString()}); - } - - // Use equals to compare the values - result = compareValue.equals(propertyValue); - } - } - else - { - if (logger.isInfoEnabled()) - { - logger.info("Condition Comparator encountered null value for property [" + propertyTypeQName +"]"); - } - } - } - - if (logger.isDebugEnabled()) - { - logger.debug("Returning result " + result); - } - - return result; - } -} +package org.alfresco.repo.action.evaluator; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation; +import org.alfresco.repo.action.evaluator.compare.ContentPropertyName; +import org.alfresco.repo.action.evaluator.compare.PropertyValueComparator; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Compare property value evaluator + * + * @author Roy Wetherall + */ +public class ComparePropertyValueEvaluator extends ActionConditionEvaluatorAbstractBase +{ + private static Log logger = LogFactory.getLog(ComparePropertyValueEvaluator.class); + + /** + * Evaluator constants + */ + public final static String NAME = "compare-property-value"; + + public final static String PARAM_PROPERTY = "property"; + public final static String PARAM_CONTENT_PROPERTY = "content-property"; + public final static String PARAM_VALUE = "value"; + public final static String PARAM_OPERATION = "operation"; + + /** + * The default property to check if none is specified in the properties + */ + private final static QName DEFAULT_PROPERTY = ContentModel.PROP_NAME; + + /** + * I18N message ID's + */ + private static final String MSGID_INVALID_OPERATION = "compare_property_value_evaluator.invalid_operation"; + private static final String MSGID_NO_CONTENT_PROPERTY = "compare_property_value_evaluator.no_content_property"; + + /** + * Map of comparators used by different property types + */ + private Map comparators = new HashMap(); + + /** + * The node service + */ + protected NodeService nodeService; + + /** + * The content service + */ + protected ContentService contentService; + + /** + * The dictionary service + */ + protected DictionaryService dictionaryService; + + /** + * Set node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the content service + * + * @param contentService the content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Set the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Set the list of property value comparators + * + * @param comparators the list of property value comparators + */ + public void setPropertyValueComparators(List comparators) + { + for (PropertyValueComparator comparator : comparators) + { + comparator.registerComparator(this); + } + } + + /** + * Registers a comparator for a given property data type. + * + * @param dataType property data type + * @param comparator property value comparator + */ + public void registerComparator(QName dataType, PropertyValueComparator comparator) + { + this.comparators.put(dataType, comparator); + } + + /** + * Add parameter definitions + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_PROPERTY, DataTypeDefinition.QNAME, false, getParamDisplayLabel(PARAM_PROPERTY))); + paramList.add(new ParameterDefinitionImpl(PARAM_CONTENT_PROPERTY, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_CONTENT_PROPERTY), false, "ac-content-properties")); + paramList.add(new ParameterDefinitionImpl(PARAM_VALUE, DataTypeDefinition.ANY, true, getParamDisplayLabel(PARAM_VALUE))); + paramList.add(new ParameterDefinitionImpl(PARAM_OPERATION, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_OPERATION), false, "ac-compare-operations")); + } + + /** + * @see ActionConditionEvaluatorAbstractBase#evaluateImpl(ActionCondition, NodeRef) + */ + public boolean evaluateImpl( + ActionCondition ruleCondition, + NodeRef actionedUponNodeRef) + { + boolean result = false; + + if (this.nodeService.exists(actionedUponNodeRef) == true) + { + // Get the name value of the node + QName propertyQName = (QName)ruleCondition.getParameterValue(PARAM_PROPERTY); + if (propertyQName == null) + { + if (logger.isWarnEnabled()) + logger.warn("ComparePropertyValue - Property is NULL. Setting to " + DEFAULT_PROPERTY); + + propertyQName = DEFAULT_PROPERTY; + } + + // Get the original value and the value to match + Serializable propertyValue = this.nodeService.getProperty(actionedUponNodeRef, propertyQName); + Serializable compareValue = ruleCondition.getParameterValue(PARAM_VALUE); + + // Get the operation + ComparePropertyValueOperation operation = null; + String operationString = (String)ruleCondition.getParameterValue(PARAM_OPERATION); + if (operationString != null) + { + operation = ComparePropertyValueOperation.valueOf(operationString); + } + + // Look at the type of the property (assume to be ANY if none found in dictionary) + QName propertyTypeQName = DataTypeDefinition.ANY; + PropertyDefinition propertyDefintion = this.dictionaryService.getProperty(propertyQName); + if (propertyDefintion != null) + { + propertyTypeQName = propertyDefintion.getDataType().getName(); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Evaluating Property Parameters, propertyQName - [" + propertyQName + + "] getInverted? [" + ruleCondition.getInvertCondition() +"] operation [" + + operation + "]"); + logger.debug("Compare Value [" + compareValue + "] Actual Value [" + propertyValue + "]"); + } + + // Sort out what to do if the property is a content property + if (DataTypeDefinition.CONTENT.equals(propertyTypeQName) == true) + { + // Get the content property name + ContentPropertyName contentProperty = null; + String contentPropertyString = (String)ruleCondition.getParameterValue(PARAM_CONTENT_PROPERTY); + if (contentPropertyString == null) + { + // Error if no content property has been set + throw new ActionServiceException(MSGID_NO_CONTENT_PROPERTY); + } + else + { + contentProperty = ContentPropertyName.valueOf(contentPropertyString); + } + + // Get the content data + if (propertyValue != null) + { + ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, propertyValue); + switch (contentProperty) + { + case ENCODING: + { + propertyTypeQName = DataTypeDefinition.TEXT; + propertyValue = contentData.getEncoding(); + break; + } + case SIZE: + { + propertyTypeQName = DataTypeDefinition.LONG; + propertyValue = contentData.getSize(); + break; + } + case MIME_TYPE: + { + propertyTypeQName = DataTypeDefinition.TEXT; + propertyValue = contentData.getMimetype(); + break; + } + } + } + } + + if (propertyValue != null) + { + // Try and get a matching comparator + PropertyValueComparator comparator = this.comparators.get(propertyTypeQName); + if (comparator != null) + { + // Figure out if property is multivalued, compare all of the entries till finding a match + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + if (propertyDef.isMultiValued()) + { + for(Serializable value : ((ArrayList) propertyValue)) + { + boolean success = comparator.compare(value, compareValue, operation); + if (success) + { + result = true; + break; + } + } + } + else + { + // Call the comparator for the property type + result = comparator.compare(propertyValue, compareValue, operation); + } + } + else + { + if (logger.isWarnEnabled()) + { + logger.warn("Comparator not found for property type " + propertyTypeQName); + } + // The default behaviour is to assume the property can only be compared using equals + if (operation != null && operation != ComparePropertyValueOperation.EQUALS) + { + // Error since only the equals operation is valid + throw new ActionServiceException( + MSGID_INVALID_OPERATION, + new Object[]{operation.toString(), propertyTypeQName.toString()}); + } + + // Use equals to compare the values + result = compareValue.equals(propertyValue); + } + } + else + { + if (logger.isInfoEnabled()) + { + logger.info("Condition Comparator encountered null value for property [" + propertyTypeQName +"]"); + } + } + } + + if (logger.isDebugEnabled()) + { + logger.debug("Returning result " + result); + } + + return result; + } +} diff --git a/source/java/org/alfresco/repo/action/evaluator/CompositeConditionEvaluator.java b/source/java/org/alfresco/repo/action/evaluator/CompositeConditionEvaluator.java index ca494d4883..7950065c3b 100644 --- a/source/java/org/alfresco/repo/action/evaluator/CompositeConditionEvaluator.java +++ b/source/java/org/alfresco/repo/action/evaluator/CompositeConditionEvaluator.java @@ -1,34 +1,34 @@ -package org.alfresco.repo.action.evaluator; - -import java.util.List; - -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This class is needed to provide some infrastructure, but the actual evaluation of - * Composite Conditions happens inside the ActionServiceImpl as a special case. - * - * @author Jean Barmash - */ -public class CompositeConditionEvaluator extends ActionConditionEvaluatorAbstractBase -{ - - private static Log logger = LogFactory.getLog(CompositeConditionEvaluator.class); - - @Override - protected boolean evaluateImpl(ActionCondition actionCondition, - NodeRef actionedUponNodeRef) - { - logger.error("Evaluating composite condition. Should not be called."); - return false; - } - - @Override - protected void addParameterDefinitions(List paramList) - { - } -} +package org.alfresco.repo.action.evaluator; + +import java.util.List; + +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This class is needed to provide some infrastructure, but the actual evaluation of + * Composite Conditions happens inside the ActionServiceImpl as a special case. + * + * @author Jean Barmash + */ +public class CompositeConditionEvaluator extends ActionConditionEvaluatorAbstractBase +{ + + private static Log logger = LogFactory.getLog(CompositeConditionEvaluator.class); + + @Override + protected boolean evaluateImpl(ActionCondition actionCondition, + NodeRef actionedUponNodeRef) + { + logger.error("Evaluating composite condition. Should not be called."); + return false; + } + + @Override + protected void addParameterDefinitions(List paramList) + { + } +} diff --git a/source/java/org/alfresco/repo/action/evaluator/HasAspectEvaluator.java b/source/java/org/alfresco/repo/action/evaluator/HasAspectEvaluator.java index 4a5d476702..f6dfb8845e 100644 --- a/source/java/org/alfresco/repo/action/evaluator/HasAspectEvaluator.java +++ b/source/java/org/alfresco/repo/action/evaluator/HasAspectEvaluator.java @@ -1,68 +1,68 @@ -package org.alfresco.repo.action.evaluator; - -import java.util.List; - -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -/** - * Has aspect evaluator - * - * @author Roy Wetherall - */ -public class HasAspectEvaluator extends ActionConditionEvaluatorAbstractBase -{ - /** - * Evaluator constants - */ - public static final String NAME = "has-aspect"; - public static final String PARAM_ASPECT = "aspect"; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * Set node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @see org.alfresco.repo.action.evaluator.ActionConditionEvaluatorAbstractBase#evaluateImpl(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean evaluateImpl(ActionCondition ruleCondition, NodeRef actionedUponNodeRef) - { - boolean result = false; - - if (this.nodeService.exists(actionedUponNodeRef) == true) - { - if (this.nodeService.hasAspect(actionedUponNodeRef, (QName)ruleCondition.getParameterValue(PARAM_ASPECT)) == true) - { - result = true; - } - } - - return result; - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_ASPECT, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASPECT), false, "ac-aspects")); - } - -} +package org.alfresco.repo.action.evaluator; + +import java.util.List; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Has aspect evaluator + * + * @author Roy Wetherall + */ +public class HasAspectEvaluator extends ActionConditionEvaluatorAbstractBase +{ + /** + * Evaluator constants + */ + public static final String NAME = "has-aspect"; + public static final String PARAM_ASPECT = "aspect"; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * Set node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @see org.alfresco.repo.action.evaluator.ActionConditionEvaluatorAbstractBase#evaluateImpl(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean evaluateImpl(ActionCondition ruleCondition, NodeRef actionedUponNodeRef) + { + boolean result = false; + + if (this.nodeService.exists(actionedUponNodeRef) == true) + { + if (this.nodeService.hasAspect(actionedUponNodeRef, (QName)ruleCondition.getParameterValue(PARAM_ASPECT)) == true) + { + result = true; + } + } + + return result; + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_ASPECT, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASPECT), false, "ac-aspects")); + } + +} diff --git a/source/java/org/alfresco/repo/action/evaluator/HasVersionHistoryEvaluator.java b/source/java/org/alfresco/repo/action/evaluator/HasVersionHistoryEvaluator.java index 9a1c7e2d91..6d2a63398e 100644 --- a/source/java/org/alfresco/repo/action/evaluator/HasVersionHistoryEvaluator.java +++ b/source/java/org/alfresco/repo/action/evaluator/HasVersionHistoryEvaluator.java @@ -1,75 +1,75 @@ -package org.alfresco.repo.action.evaluator; - -import java.util.List; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.version.VersionHistory; -import org.alfresco.service.cmr.version.VersionService; - -/** - * Has version history evaluator - * - * @author Roy Wetherall - */ -public class HasVersionHistoryEvaluator extends ActionConditionEvaluatorAbstractBase -{ - /** - * Evaluator constants - */ - public static final String NAME = "has-version-history"; - - /** - * The node service - */ - private NodeService nodeService; - - private VersionService versionService; - - /** - * Set node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setVersionService(VersionService versionService) - { - this.versionService = versionService; - } - - /** - * @see org.alfresco.repo.action.evaluator.ActionConditionEvaluatorAbstractBase#evaluateImpl(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean evaluateImpl(ActionCondition ruleCondition, NodeRef actionedUponNodeRef) - { - boolean result = false; - - if (this.nodeService.exists(actionedUponNodeRef) == true && - this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE) == true) - { - VersionHistory versionHistory = this.versionService.getVersionHistory(actionedUponNodeRef); - if (versionHistory != null && versionHistory.getAllVersions().size() != 0) - { - result = true; - } - } - - return result; - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - @Override - protected void addParameterDefinitions(List paramList) - { - } - -} +package org.alfresco.repo.action.evaluator; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; + +/** + * Has version history evaluator + * + * @author Roy Wetherall + */ +public class HasVersionHistoryEvaluator extends ActionConditionEvaluatorAbstractBase +{ + /** + * Evaluator constants + */ + public static final String NAME = "has-version-history"; + + /** + * The node service + */ + private NodeService nodeService; + + private VersionService versionService; + + /** + * Set node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + + /** + * @see org.alfresco.repo.action.evaluator.ActionConditionEvaluatorAbstractBase#evaluateImpl(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean evaluateImpl(ActionCondition ruleCondition, NodeRef actionedUponNodeRef) + { + boolean result = false; + + if (this.nodeService.exists(actionedUponNodeRef) == true && + this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE) == true) + { + VersionHistory versionHistory = this.versionService.getVersionHistory(actionedUponNodeRef); + if (versionHistory != null && versionHistory.getAllVersions().size() != 0) + { + result = true; + } + } + + return result; + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + } + +} diff --git a/source/java/org/alfresco/repo/action/evaluator/IsSubTypeEvaluator.java b/source/java/org/alfresco/repo/action/evaluator/IsSubTypeEvaluator.java index 014660f7fe..2f19b68c38 100644 --- a/source/java/org/alfresco/repo/action/evaluator/IsSubTypeEvaluator.java +++ b/source/java/org/alfresco/repo/action/evaluator/IsSubTypeEvaluator.java @@ -1,86 +1,86 @@ -package org.alfresco.repo.action.evaluator; - -import java.util.List; - -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -/** - * No condition evaluator implementation. - * - * @author Roy Wetherall - */ -public class IsSubTypeEvaluator extends ActionConditionEvaluatorAbstractBase -{ - /** - * Evaluator constants - */ - public static final String NAME = "is-subtype"; - public static final String PARAM_TYPE = "type"; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * The dictionary service - */ - private DictionaryService dictionaryService; - - /** - * Set node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set dictionary service - * - * @param dictionaryService the dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * @see org.alfresco.repo.action.evaluator.ActionConditionEvaluatorAbstractBase#evaluateImpl(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean evaluateImpl(ActionCondition ruleCondition, NodeRef actionedUponNodeRef) - { - boolean result = false; - - if (this.nodeService.exists(actionedUponNodeRef) == true) - { - // TODO: Move this type check into its own Class Evaluator - QName nodeType = nodeService.getType(actionedUponNodeRef); - if (dictionaryService.isSubClass(nodeType, (QName)ruleCondition.getParameterValue(PARAM_TYPE))) - { - result = true; - } - } - - return result; - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_TYPE, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_TYPE), false, "ac-types")); - } - -} +package org.alfresco.repo.action.evaluator; + +import java.util.List; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * No condition evaluator implementation. + * + * @author Roy Wetherall + */ +public class IsSubTypeEvaluator extends ActionConditionEvaluatorAbstractBase +{ + /** + * Evaluator constants + */ + public static final String NAME = "is-subtype"; + public static final String PARAM_TYPE = "type"; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * The dictionary service + */ + private DictionaryService dictionaryService; + + /** + * Set node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @see org.alfresco.repo.action.evaluator.ActionConditionEvaluatorAbstractBase#evaluateImpl(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean evaluateImpl(ActionCondition ruleCondition, NodeRef actionedUponNodeRef) + { + boolean result = false; + + if (this.nodeService.exists(actionedUponNodeRef) == true) + { + // TODO: Move this type check into its own Class Evaluator + QName nodeType = nodeService.getType(actionedUponNodeRef); + if (dictionaryService.isSubClass(nodeType, (QName)ruleCondition.getParameterValue(PARAM_TYPE))) + { + result = true; + } + } + + return result; + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_TYPE, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_TYPE), false, "ac-types")); + } + +} diff --git a/source/java/org/alfresco/repo/action/evaluator/compare/ComparePropertyValueOperation.java b/source/java/org/alfresco/repo/action/evaluator/compare/ComparePropertyValueOperation.java index 9e036f3946..9c3a95c5d1 100644 --- a/source/java/org/alfresco/repo/action/evaluator/compare/ComparePropertyValueOperation.java +++ b/source/java/org/alfresco/repo/action/evaluator/compare/ComparePropertyValueOperation.java @@ -1,22 +1,22 @@ -package org.alfresco.repo.action.evaluator.compare; - -/** - * ComparePropertyValueOperation enum. - *

- * Contains the operations that can be used when evaluating whether the value of a property - * matches the value set. - *

- * Some operations can only be used with specific types. If a mismatch is encountered an error will - * be raised. - */ -public enum ComparePropertyValueOperation -{ - EQUALS, // All property types - CONTAINS, // String properties only - BEGINS, // String properties only - ENDS, // String properties only - GREATER_THAN, // Numeric and date properties only - GREATER_THAN_EQUAL, // Numeric and date properties only - LESS_THAN, // Numeric and date properties only - LESS_THAN_EQUAL // Numeric and date properties only +package org.alfresco.repo.action.evaluator.compare; + +/** + * ComparePropertyValueOperation enum. + *

+ * Contains the operations that can be used when evaluating whether the value of a property + * matches the value set. + *

+ * Some operations can only be used with specific types. If a mismatch is encountered an error will + * be raised. + */ +public enum ComparePropertyValueOperation +{ + EQUALS, // All property types + CONTAINS, // String properties only + BEGINS, // String properties only + ENDS, // String properties only + GREATER_THAN, // Numeric and date properties only + GREATER_THAN_EQUAL, // Numeric and date properties only + LESS_THAN, // Numeric and date properties only + LESS_THAN_EQUAL // Numeric and date properties only } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/action/evaluator/compare/ContentPropertyName.java b/source/java/org/alfresco/repo/action/evaluator/compare/ContentPropertyName.java index 3b8ac93de3..dcd6f49c99 100644 --- a/source/java/org/alfresco/repo/action/evaluator/compare/ContentPropertyName.java +++ b/source/java/org/alfresco/repo/action/evaluator/compare/ContentPropertyName.java @@ -1,11 +1,11 @@ -package org.alfresco.repo.action.evaluator.compare; - -/** - * @author Roy Wetherall - */ -public enum ContentPropertyName -{ - MIME_TYPE, - ENCODING, - SIZE -} +package org.alfresco.repo.action.evaluator.compare; + +/** + * @author Roy Wetherall + */ +public enum ContentPropertyName +{ + MIME_TYPE, + ENCODING, + SIZE +} diff --git a/source/java/org/alfresco/repo/action/evaluator/compare/DatePropertyValueComparator.java b/source/java/org/alfresco/repo/action/evaluator/compare/DatePropertyValueComparator.java index 72fcdd97da..7c37b31eab 100644 --- a/source/java/org/alfresco/repo/action/evaluator/compare/DatePropertyValueComparator.java +++ b/source/java/org/alfresco/repo/action/evaluator/compare/DatePropertyValueComparator.java @@ -1,100 +1,100 @@ -package org.alfresco.repo.action.evaluator.compare; - -import java.io.Serializable; -import java.util.Date; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; -import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.util.ISO8601DateFormat; - -/** - * Date property value comparator - * - * @author Roy Wetherall - */ -public class DatePropertyValueComparator implements PropertyValueComparator -{ - /** - * I18N message ids - */ - private static final String MSGID_INVALID_OPERATION = "date_property_value_comparator.invalid_operation"; - - /** - * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#compare(java.io.Serializable, java.io.Serializable, org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation) - */ - public boolean compare(Serializable propertyValue, Serializable compareValue, ComparePropertyValueOperation operation) - { - boolean result = false; - - if (operation == null) - { - operation = ComparePropertyValueOperation.EQUALS; - } - - Date propertyDate = getDate(propertyValue); - Date compareDate = getDate(compareValue); - - switch (operation) - { - case EQUALS: - { - result = propertyDate.equals(compareDate); - break; - } - case LESS_THAN: - { - result = propertyDate.before(compareDate); - break; - } - case LESS_THAN_EQUAL: - { - result = (propertyDate.equals(compareDate) || propertyDate.before(compareDate)); - break; - } - case GREATER_THAN: - { - result = propertyDate.after(compareDate); - break; - } - case GREATER_THAN_EQUAL: - { - result = (propertyDate.equals(compareDate) || propertyDate.after(compareDate)); - break; - } - default: - { - // Raise an invalid operation exception - throw new ActionServiceException( - MSGID_INVALID_OPERATION, - new Object[]{operation.toString()}); - } - } - - return result; - } - - private Date getDate(Serializable value) - { - if(value instanceof Date) - { - return (Date) value; - } - else if(value instanceof String) - { - return ISO8601DateFormat.parse((String) value); - } - throw new AlfrescoRuntimeException("Parameter 'compareValue' must be of type java.util.Date!"); - } - - /** - * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#registerComparator(org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator) - */ - public void registerComparator(ComparePropertyValueEvaluator evaluator) - { - evaluator.registerComparator(DataTypeDefinition.DATE, this); - evaluator.registerComparator(DataTypeDefinition.DATETIME, this); - } - -} +package org.alfresco.repo.action.evaluator.compare; + +import java.io.Serializable; +import java.util.Date; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.util.ISO8601DateFormat; + +/** + * Date property value comparator + * + * @author Roy Wetherall + */ +public class DatePropertyValueComparator implements PropertyValueComparator +{ + /** + * I18N message ids + */ + private static final String MSGID_INVALID_OPERATION = "date_property_value_comparator.invalid_operation"; + + /** + * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#compare(java.io.Serializable, java.io.Serializable, org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation) + */ + public boolean compare(Serializable propertyValue, Serializable compareValue, ComparePropertyValueOperation operation) + { + boolean result = false; + + if (operation == null) + { + operation = ComparePropertyValueOperation.EQUALS; + } + + Date propertyDate = getDate(propertyValue); + Date compareDate = getDate(compareValue); + + switch (operation) + { + case EQUALS: + { + result = propertyDate.equals(compareDate); + break; + } + case LESS_THAN: + { + result = propertyDate.before(compareDate); + break; + } + case LESS_THAN_EQUAL: + { + result = (propertyDate.equals(compareDate) || propertyDate.before(compareDate)); + break; + } + case GREATER_THAN: + { + result = propertyDate.after(compareDate); + break; + } + case GREATER_THAN_EQUAL: + { + result = (propertyDate.equals(compareDate) || propertyDate.after(compareDate)); + break; + } + default: + { + // Raise an invalid operation exception + throw new ActionServiceException( + MSGID_INVALID_OPERATION, + new Object[]{operation.toString()}); + } + } + + return result; + } + + private Date getDate(Serializable value) + { + if(value instanceof Date) + { + return (Date) value; + } + else if(value instanceof String) + { + return ISO8601DateFormat.parse((String) value); + } + throw new AlfrescoRuntimeException("Parameter 'compareValue' must be of type java.util.Date!"); + } + + /** + * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#registerComparator(org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator) + */ + public void registerComparator(ComparePropertyValueEvaluator evaluator) + { + evaluator.registerComparator(DataTypeDefinition.DATE, this); + evaluator.registerComparator(DataTypeDefinition.DATETIME, this); + } + +} diff --git a/source/java/org/alfresco/repo/action/evaluator/compare/NumericPropertyValueComparator.java b/source/java/org/alfresco/repo/action/evaluator/compare/NumericPropertyValueComparator.java index 3db4a2abcb..9eb97be9ad 100644 --- a/source/java/org/alfresco/repo/action/evaluator/compare/NumericPropertyValueComparator.java +++ b/source/java/org/alfresco/repo/action/evaluator/compare/NumericPropertyValueComparator.java @@ -1,90 +1,90 @@ -package org.alfresco.repo.action.evaluator.compare; - -import java.io.Serializable; - -import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; -import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; - -/** - * Numeric property value comparator. - * - * @author Roy Wetherall - */ -public class NumericPropertyValueComparator implements PropertyValueComparator -{ - /** - * I18N message ids - */ - private static final String MSGID_INVALID_OPERATION = "numeric_property_value_comparator.invalid_operation"; - - /** - * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#compare(java.io.Serializable, java.io.Serializable, org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation) - */ - public boolean compare( - Serializable propertyValue, - Serializable compareValue, - ComparePropertyValueOperation operation) - { - boolean result = false; - if (operation == null) - { - operation = ComparePropertyValueOperation.EQUALS; - } - - // TODO need to check that doing this potential conversion does not cause a problem - double property = ((Number)propertyValue).doubleValue(); - double compare = ((Number)compareValue).doubleValue(); - - switch (operation) - { - case EQUALS: - { - result = (property == compare); - break; - } - case GREATER_THAN: - { - result = (property > compare); - break; - } - case GREATER_THAN_EQUAL: - { - result = (property >= compare); - break; - } - case LESS_THAN: - { - result = (property < compare); - break; - } - case LESS_THAN_EQUAL: - { - result = (property <= compare); - break; - } - default: - { - // Raise an invalid operation exception - throw new ActionServiceException( - MSGID_INVALID_OPERATION, - new Object[]{operation.toString()}); - } - } - - return result; - } - - /** - * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#registerComparator(org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator) - */ - public void registerComparator(ComparePropertyValueEvaluator evaluator) - { - evaluator.registerComparator(DataTypeDefinition.DOUBLE, this); - evaluator.registerComparator(DataTypeDefinition.FLOAT, this); - evaluator.registerComparator(DataTypeDefinition.INT, this); - evaluator.registerComparator(DataTypeDefinition.LONG, this); - - } - -} +package org.alfresco.repo.action.evaluator.compare; + +import java.io.Serializable; + +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; + +/** + * Numeric property value comparator. + * + * @author Roy Wetherall + */ +public class NumericPropertyValueComparator implements PropertyValueComparator +{ + /** + * I18N message ids + */ + private static final String MSGID_INVALID_OPERATION = "numeric_property_value_comparator.invalid_operation"; + + /** + * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#compare(java.io.Serializable, java.io.Serializable, org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation) + */ + public boolean compare( + Serializable propertyValue, + Serializable compareValue, + ComparePropertyValueOperation operation) + { + boolean result = false; + if (operation == null) + { + operation = ComparePropertyValueOperation.EQUALS; + } + + // TODO need to check that doing this potential conversion does not cause a problem + double property = ((Number)propertyValue).doubleValue(); + double compare = ((Number)compareValue).doubleValue(); + + switch (operation) + { + case EQUALS: + { + result = (property == compare); + break; + } + case GREATER_THAN: + { + result = (property > compare); + break; + } + case GREATER_THAN_EQUAL: + { + result = (property >= compare); + break; + } + case LESS_THAN: + { + result = (property < compare); + break; + } + case LESS_THAN_EQUAL: + { + result = (property <= compare); + break; + } + default: + { + // Raise an invalid operation exception + throw new ActionServiceException( + MSGID_INVALID_OPERATION, + new Object[]{operation.toString()}); + } + } + + return result; + } + + /** + * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#registerComparator(org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator) + */ + public void registerComparator(ComparePropertyValueEvaluator evaluator) + { + evaluator.registerComparator(DataTypeDefinition.DOUBLE, this); + evaluator.registerComparator(DataTypeDefinition.FLOAT, this); + evaluator.registerComparator(DataTypeDefinition.INT, this); + evaluator.registerComparator(DataTypeDefinition.LONG, this); + + } + +} diff --git a/source/java/org/alfresco/repo/action/evaluator/compare/PropertyValueComparator.java b/source/java/org/alfresco/repo/action/evaluator/compare/PropertyValueComparator.java index 31e2c9ae59..d43d1723d8 100644 --- a/source/java/org/alfresco/repo/action/evaluator/compare/PropertyValueComparator.java +++ b/source/java/org/alfresco/repo/action/evaluator/compare/PropertyValueComparator.java @@ -1,33 +1,33 @@ -package org.alfresco.repo.action.evaluator.compare; - -import java.io.Serializable; - -import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; - -/** - * Property value comparator interface - * - * @author Roy Wetherall - */ -public interface PropertyValueComparator -{ - /** - * Callback method to register this comparator with the evaluator. - * - * @param evaluator the compare property value evaluator - */ - void registerComparator(ComparePropertyValueEvaluator evaluator); - - /** - * Compares the value of a property with the compare value, using the operator passed. - * - * @param propertyValue the property value - * @param compareValue the compare value - * @param operation the operation used to compare the two values - * @return the result of the comparison, true if successful false otherwise - */ - boolean compare( - Serializable propertyValue, - Serializable compareValue, - ComparePropertyValueOperation operation); -} +package org.alfresco.repo.action.evaluator.compare; + +import java.io.Serializable; + +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; + +/** + * Property value comparator interface + * + * @author Roy Wetherall + */ +public interface PropertyValueComparator +{ + /** + * Callback method to register this comparator with the evaluator. + * + * @param evaluator the compare property value evaluator + */ + void registerComparator(ComparePropertyValueEvaluator evaluator); + + /** + * Compares the value of a property with the compare value, using the operator passed. + * + * @param propertyValue the property value + * @param compareValue the compare value + * @param operation the operation used to compare the two values + * @return the result of the comparison, true if successful false otherwise + */ + boolean compare( + Serializable propertyValue, + Serializable compareValue, + ComparePropertyValueOperation operation); +} diff --git a/source/java/org/alfresco/repo/action/executer/BlogAction.java b/source/java/org/alfresco/repo/action/executer/BlogAction.java index dbb798627f..71a8abe6d3 100644 --- a/source/java/org/alfresco/repo/action/executer/BlogAction.java +++ b/source/java/org/alfresco/repo/action/executer/BlogAction.java @@ -1,147 +1,147 @@ - -package org.alfresco.repo.action.executer; - -import java.util.List; - -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.repo.blog.BlogDetails; -import org.alfresco.repo.blog.BlogIntegrationRuntimeException; -import org.alfresco.repo.blog.BlogIntegrationService; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -import org.alfresco.model.BlogIntegrationModel; -import org.alfresco.model.ContentModel; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Post blog repository action - * - * @author mikeh - */ -public class BlogAction extends ActionExecuterAbstractBase implements BlogIntegrationModel -{ - public static final String NAME = "blog-post"; - public static final String PARAM_BLOG_ACTION = "action"; - - private static Log logger = LogFactory.getLog(BlogAction.class); - - private DictionaryService dictionaryService; - private NodeService nodeService; - private BlogIntegrationService blogIntegrationService; - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the dictionary service - * - * @param dictionaryService the dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Set the blog integration service - * - * @param blogIntegrationService the blog integration service - */ - public void setBlogIntegrationService(BlogIntegrationService blogIntegrationService) - { - this.blogIntegrationService = blogIntegrationService; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) - */ - @Override - protected void executeImpl(Action action, NodeRef actionedUponNodeRef) - { - String blogAction = (String)action.getParameterValue(PARAM_BLOG_ACTION); - try - { - if ("post".equals(blogAction) == true) - { - QName type = this.nodeService.getType(actionedUponNodeRef); - if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT) == true) - { - List list = this.blogIntegrationService.getBlogDetails(actionedUponNodeRef); - if (list.size() != 0) - { - // Take the 'nearest' blog details - BlogDetails blogDetails = list.get(0); - this.blogIntegrationService.newPost(blogDetails, actionedUponNodeRef, ContentModel.PROP_CONTENT, true); - } - } - } - else if ("update".equals(blogAction) == true) - { - QName type = this.nodeService.getType(actionedUponNodeRef); - if (this.nodeService.hasAspect(actionedUponNodeRef, ASPECT_BLOG_POST) == true && - this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT) == true) - { - this.blogIntegrationService.updatePost(actionedUponNodeRef, ContentModel.PROP_CONTENT, true); - } - } - else if ("remove".equals(blogAction) == true) - { - QName type = this.nodeService.getType(actionedUponNodeRef); - if (this.nodeService.hasAspect(actionedUponNodeRef, ASPECT_BLOG_POST) == true && - this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT) == true) - { - this.blogIntegrationService.deletePost(actionedUponNodeRef); - } - } - else - { - throw new BlogIntegrationRuntimeException("Invalid action has been specified '" + blogAction + "'"); - } - - action.setParameterValue(PARAM_RESULT, ""); - } - catch (BlogIntegrationRuntimeException ex) - { - action.setParameterValue(PARAM_RESULT, ex.getMessage()); - } - catch (Exception ex) - { - action.setParameterValue(PARAM_RESULT, "Action failed. Please check blog configuration parameters."); - } - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - @Override - protected void addParameterDefinitions(List paramList) - { - // Add definitions for action parameters - paramList.add( - new ParameterDefinitionImpl(PARAM_BLOG_ACTION, - DataTypeDefinition.TEXT, - true, - getParamDisplayLabel(PARAM_BLOG_ACTION))); - - paramList.add( - new ParameterDefinitionImpl(PARAM_RESULT, - DataTypeDefinition.TEXT, - false, - getParamDisplayLabel(PARAM_RESULT))); - - } -} + +package org.alfresco.repo.action.executer; + +import java.util.List; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.blog.BlogDetails; +import org.alfresco.repo.blog.BlogIntegrationRuntimeException; +import org.alfresco.repo.blog.BlogIntegrationService; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +import org.alfresco.model.BlogIntegrationModel; +import org.alfresco.model.ContentModel; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Post blog repository action + * + * @author mikeh + */ +public class BlogAction extends ActionExecuterAbstractBase implements BlogIntegrationModel +{ + public static final String NAME = "blog-post"; + public static final String PARAM_BLOG_ACTION = "action"; + + private static Log logger = LogFactory.getLog(BlogAction.class); + + private DictionaryService dictionaryService; + private NodeService nodeService; + private BlogIntegrationService blogIntegrationService; + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Set the blog integration service + * + * @param blogIntegrationService the blog integration service + */ + public void setBlogIntegrationService(BlogIntegrationService blogIntegrationService) + { + this.blogIntegrationService = blogIntegrationService; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + @Override + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + String blogAction = (String)action.getParameterValue(PARAM_BLOG_ACTION); + try + { + if ("post".equals(blogAction) == true) + { + QName type = this.nodeService.getType(actionedUponNodeRef); + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT) == true) + { + List list = this.blogIntegrationService.getBlogDetails(actionedUponNodeRef); + if (list.size() != 0) + { + // Take the 'nearest' blog details + BlogDetails blogDetails = list.get(0); + this.blogIntegrationService.newPost(blogDetails, actionedUponNodeRef, ContentModel.PROP_CONTENT, true); + } + } + } + else if ("update".equals(blogAction) == true) + { + QName type = this.nodeService.getType(actionedUponNodeRef); + if (this.nodeService.hasAspect(actionedUponNodeRef, ASPECT_BLOG_POST) == true && + this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT) == true) + { + this.blogIntegrationService.updatePost(actionedUponNodeRef, ContentModel.PROP_CONTENT, true); + } + } + else if ("remove".equals(blogAction) == true) + { + QName type = this.nodeService.getType(actionedUponNodeRef); + if (this.nodeService.hasAspect(actionedUponNodeRef, ASPECT_BLOG_POST) == true && + this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT) == true) + { + this.blogIntegrationService.deletePost(actionedUponNodeRef); + } + } + else + { + throw new BlogIntegrationRuntimeException("Invalid action has been specified '" + blogAction + "'"); + } + + action.setParameterValue(PARAM_RESULT, ""); + } + catch (BlogIntegrationRuntimeException ex) + { + action.setParameterValue(PARAM_RESULT, ex.getMessage()); + } + catch (Exception ex) + { + action.setParameterValue(PARAM_RESULT, "Action failed. Please check blog configuration parameters."); + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + // Add definitions for action parameters + paramList.add( + new ParameterDefinitionImpl(PARAM_BLOG_ACTION, + DataTypeDefinition.TEXT, + true, + getParamDisplayLabel(PARAM_BLOG_ACTION))); + + paramList.add( + new ParameterDefinitionImpl(PARAM_RESULT, + DataTypeDefinition.TEXT, + false, + getParamDisplayLabel(PARAM_RESULT))); + + } +} diff --git a/source/java/org/alfresco/repo/action/executer/CompositeActionExecuter.java b/source/java/org/alfresco/repo/action/executer/CompositeActionExecuter.java index 5856f2380c..4e9e9e6883 100644 --- a/source/java/org/alfresco/repo/action/executer/CompositeActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/CompositeActionExecuter.java @@ -1,62 +1,62 @@ -package org.alfresco.repo.action.executer; - -import java.util.List; - -import org.alfresco.repo.action.RuntimeActionService; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.CompositeAction; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Add features action executor implementation. - * - * @author Roy Wetherall - */ -public class CompositeActionExecuter extends ActionExecuterAbstractBase -{ - /** - * Action constants - */ - public static final String NAME = "composite-action"; - - /** - * The action service - */ - private RuntimeActionService actionService; - - /** - * Set the action service - * - * @param actionService the action service - */ - public void setActionService(RuntimeActionService actionService) - { - this.actionService = actionService; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) - */ - public void executeImpl(Action action, NodeRef actionedUponNodeRef) - { - if (action instanceof CompositeAction) - { - for (Action subAction : ((CompositeAction)action).getActions()) - { - // We don't check the conditions of sub-actions and they don't have an execution history - this.actionService.directActionExecution(subAction, actionedUponNodeRef); - } - } - } - - /** - * Add parameter definitions - */ - @Override - protected void addParameterDefinitions(List paramList) - { - // No parameters - } - -} +package org.alfresco.repo.action.executer; + +import java.util.List; + +import org.alfresco.repo.action.RuntimeActionService; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.CompositeAction; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Add features action executor implementation. + * + * @author Roy Wetherall + */ +public class CompositeActionExecuter extends ActionExecuterAbstractBase +{ + /** + * Action constants + */ + public static final String NAME = "composite-action"; + + /** + * The action service + */ + private RuntimeActionService actionService; + + /** + * Set the action service + * + * @param actionService the action service + */ + public void setActionService(RuntimeActionService actionService) + { + this.actionService = actionService; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) + */ + public void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + if (action instanceof CompositeAction) + { + for (Action subAction : ((CompositeAction)action).getActions()) + { + // We don't check the conditions of sub-actions and they don't have an execution history + this.actionService.directActionExecution(subAction, actionedUponNodeRef); + } + } + } + + /** + * Add parameter definitions + */ + @Override + protected void addParameterDefinitions(List paramList) + { + // No parameters + } + +} diff --git a/source/java/org/alfresco/repo/action/executer/ContentMetadataEmbedder.java b/source/java/org/alfresco/repo/action/executer/ContentMetadataEmbedder.java index d5b9823af6..38140e9a25 100644 --- a/source/java/org/alfresco/repo/action/executer/ContentMetadataEmbedder.java +++ b/source/java/org/alfresco/repo/action/executer/ContentMetadataEmbedder.java @@ -1,147 +1,147 @@ -package org.alfresco.repo.action.executer; - -import java.io.Serializable; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.content.metadata.MetadataEmbedder; -import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Embed metadata in any content. - *

- * The metadata is embedded in the content from the current - * property values. - * - * @author Jesper Steen Møller, Ray Gauss II - */ -public class ContentMetadataEmbedder extends ActionExecuterAbstractBase -{ - private static Log logger = LogFactory.getLog(ContentMetadataEmbedder.class); - - public static final String EXECUTOR_NAME = "embed-metadata"; - - private NodeService nodeService; - private ContentService contentService; - private MetadataExtracterRegistry metadataExtracterRegistry; - - /** - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param contentService The contentService to set. - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * @param metadataExtracterRegistry The metadataExtracterRegistry to set. - */ - public void setMetadataExtracterRegistry(MetadataExtracterRegistry metadataExtracterRegistry) - { - this.metadataExtracterRegistry = metadataExtracterRegistry; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) - */ - public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) - { - if (!nodeService.exists(actionedUponNodeRef)) - { - // Node is gone - return; - } - ContentReader reader = contentService.getReader(actionedUponNodeRef, ContentModel.PROP_CONTENT); - // The reader may be null, e.g. for folders and the like - if (reader == null || reader.getMimetype() == null) - { - if(logger.isDebugEnabled()) - { - logger.debug("no content or mimetype - do nothing"); - } - // No content to extract data from - return; - } - String mimetype = reader.getMimetype(); - MetadataEmbedder embedder = metadataExtracterRegistry.getEmbedder(mimetype); - if (embedder == null) - { - if(logger.isDebugEnabled()) - { - logger.debug("no embedder for mimetype:" + mimetype); - } - // There is no embedder to use - return; - } - - ContentWriter writer = contentService.getWriter(actionedUponNodeRef, ContentModel.PROP_CONTENT, true); - // The writer may be null, e.g. for folders and the like - if (writer == null || writer.getMimetype() == null) - { - if(logger.isDebugEnabled()) - { - logger.debug("no content or mimetype - do nothing"); - } - // No content to embed data in - return; - } - - // Get all the node's properties - Map nodeProperties = nodeService.getProperties(actionedUponNodeRef); - - try - { - embedder.embed(nodeProperties, reader, writer); - } - catch (Throwable e) - { - // Extracters should attempt to handle all error conditions and embed - // as much as they can. If, however, one should fail, we don't want the - // action itself to fail. We absorb and report the exception here. - if (logger.isDebugEnabled()) - { - logger.debug( - "Metadata embedding failed: \n" + - " Extracter: " + this + "\n" + - " Node: " + actionedUponNodeRef + "\n" + - " Content: " + writer, - e); - } - else - { - logger.warn( - "Metadata embedding failed (turn on DEBUG for full error): \n" + - " Extracter: " + this + "\n" + - " Node: " + actionedUponNodeRef + "\n" + - " Content: " + writer + "\n" + - " Failure: " + e.getMessage()); - } - } - } - - @Override - protected void addParameterDefinitions(List arg0) - { - // None! - } -} +package org.alfresco.repo.action.executer; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.content.metadata.MetadataEmbedder; +import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Embed metadata in any content. + *

+ * The metadata is embedded in the content from the current + * property values. + * + * @author Jesper Steen Møller, Ray Gauss II + */ +public class ContentMetadataEmbedder extends ActionExecuterAbstractBase +{ + private static Log logger = LogFactory.getLog(ContentMetadataEmbedder.class); + + public static final String EXECUTOR_NAME = "embed-metadata"; + + private NodeService nodeService; + private ContentService contentService; + private MetadataExtracterRegistry metadataExtracterRegistry; + + /** + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param contentService The contentService to set. + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @param metadataExtracterRegistry The metadataExtracterRegistry to set. + */ + public void setMetadataExtracterRegistry(MetadataExtracterRegistry metadataExtracterRegistry) + { + this.metadataExtracterRegistry = metadataExtracterRegistry; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) + */ + public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + if (!nodeService.exists(actionedUponNodeRef)) + { + // Node is gone + return; + } + ContentReader reader = contentService.getReader(actionedUponNodeRef, ContentModel.PROP_CONTENT); + // The reader may be null, e.g. for folders and the like + if (reader == null || reader.getMimetype() == null) + { + if(logger.isDebugEnabled()) + { + logger.debug("no content or mimetype - do nothing"); + } + // No content to extract data from + return; + } + String mimetype = reader.getMimetype(); + MetadataEmbedder embedder = metadataExtracterRegistry.getEmbedder(mimetype); + if (embedder == null) + { + if(logger.isDebugEnabled()) + { + logger.debug("no embedder for mimetype:" + mimetype); + } + // There is no embedder to use + return; + } + + ContentWriter writer = contentService.getWriter(actionedUponNodeRef, ContentModel.PROP_CONTENT, true); + // The writer may be null, e.g. for folders and the like + if (writer == null || writer.getMimetype() == null) + { + if(logger.isDebugEnabled()) + { + logger.debug("no content or mimetype - do nothing"); + } + // No content to embed data in + return; + } + + // Get all the node's properties + Map nodeProperties = nodeService.getProperties(actionedUponNodeRef); + + try + { + embedder.embed(nodeProperties, reader, writer); + } + catch (Throwable e) + { + // Extracters should attempt to handle all error conditions and embed + // as much as they can. If, however, one should fail, we don't want the + // action itself to fail. We absorb and report the exception here. + if (logger.isDebugEnabled()) + { + logger.debug( + "Metadata embedding failed: \n" + + " Extracter: " + this + "\n" + + " Node: " + actionedUponNodeRef + "\n" + + " Content: " + writer, + e); + } + else + { + logger.warn( + "Metadata embedding failed (turn on DEBUG for full error): \n" + + " Extracter: " + this + "\n" + + " Node: " + actionedUponNodeRef + "\n" + + " Content: " + writer + "\n" + + " Failure: " + e.getMessage()); + } + } + } + + @Override + protected void addParameterDefinitions(List arg0) + { + // None! + } +} diff --git a/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java b/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java index 6e69f9b1d5..0dcaf4949d 100644 --- a/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java +++ b/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java @@ -1,391 +1,391 @@ -/* - * Copyright (C) 2005 Jesper Steen Møller - * - * 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 . - */ -package org.alfresco.repo.action.executer; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter; -import org.alfresco.repo.content.metadata.MetadataExtracter; -import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -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.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.tagging.TaggingService; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Extract metadata from any added content. - *

- * Currently, the default {@linkplain org.alfresco.repo.content.metadata.MetadataExtracter.OverwritePolicy overwrite policy} - * for each extracter is used. (TODO: Add overwrite policy as a parameter.) - * - * @see org.alfresco.repo.content.metadata.MetadataExtracter.OverwritePolicy - * - * @author Jesper Steen Møller - */ -public class ContentMetadataExtracter extends ActionExecuterAbstractBase -{ - private static Log logger = LogFactory.getLog(ContentMetadataExtracter.class); - - public static final String EXECUTOR_NAME = "extract-metadata"; - - private NodeService nodeService; - private ContentService contentService; - private DictionaryService dictionaryService; - private TaggingService taggingService; - private MetadataExtracterRegistry metadataExtracterRegistry; - private boolean carryAspectProperties = true; - private boolean enableStringTagging = false; - - public ContentMetadataExtracter() - { - } - - /** - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param contentService The contentService to set. - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * @param dictService The DictionaryService to set. - */ - public void setDictionaryService(DictionaryService dictService) - { - this.dictionaryService = dictService; - } - - /** - * @param taggingService The TaggingService to set. - */ - public void setTaggingService(TaggingService taggingService) - { - this.taggingService = taggingService; - } - - /** - * @param metadataExtracterRegistry The metadataExtracterRegistry to set. - */ - public void setMetadataExtracterRegistry(MetadataExtracterRegistry metadataExtracterRegistry) - { - this.metadataExtracterRegistry = metadataExtracterRegistry; - } - - /** - * Whether or not aspect-related properties must be carried to the new version of the node - * - * @param carryAspectProperties true (default) to carry all aspect-linked - * properties forward. false will clean the - * aspect of any unextracted values. - */ - public void setCarryAspectProperties(boolean carryAspectProperties) - { - this.carryAspectProperties = carryAspectProperties; - } - - /** - * Whether or not to enable mapping of simple strings to cm:taggable tags - * - * @param enableStringTagging true find or create tags for each string - * mapped to cm:taggable. false (default) - * ignore mapping strings to tags. - */ - public void setEnableStringTagging(boolean enableStringTagging) - { - this.enableStringTagging = enableStringTagging; - } - - /** - * Iterates the values of the taggable property which the metadata - * extractor should have already attempted to convert values to {@link NodeRef}s. - *

- * If conversion by the metadata extracter failed due to a MalformedNodeRefException - * the taggable property should still contain raw string values. - *

- * Mixing of NodeRefs and string values is permitted so each raw value is - * checked for a valid NodeRef representation and if so, converts to a NodeRef, - * if not, adds as a tag via the {@link TaggingService}. - * - * @param actionedUponNodeRef The NodeRef being actioned upon - * @param propertyDef the PropertyDefinition of the taggable property - * @param rawValue the raw value from the metadata extracter - */ - @SuppressWarnings("unchecked") - protected void addTags(NodeRef actionedUponNodeRef, PropertyDefinition propertyDef, Serializable rawValue) - { - List tags = new ArrayList(); - if (logger.isDebugEnabled()) - { - logger.debug("converting " + rawValue.toString() + " of type " + - rawValue.getClass().getCanonicalName() + " to tags"); - } - if (rawValue instanceof Collection) - { - for (Object singleValue : (Collection) rawValue) - { - if (singleValue instanceof String) - { - if (NodeRef.isNodeRef((String) singleValue)) - { - // Convert to a NodeRef - Serializable convertedPropertyValue = (Serializable) DefaultTypeConverter.INSTANCE.convert( - propertyDef.getDataType(), - (String) singleValue); - try { - String tagName = (String) nodeService.getProperty((NodeRef) convertedPropertyValue, ContentModel.PROP_NAME); - if (logger.isTraceEnabled()) - { - logger.trace("found tag '" + tagName + "' from tag nodeRef '" + (String) singleValue + "', " + - "adding to " + actionedUponNodeRef.toString()); - } - if (tagName != null && !tagName.equals("")) - { - tags.add(tagName); - } - } - catch (InvalidNodeRefException e) - { - if (logger.isWarnEnabled()) - { - logger.warn("tag nodeRef Invalid: " + e.getMessage()); - } - } - } - else - { - // Must be a simple string - if (logger.isTraceEnabled()) - { - logger.trace("adding string tag '" + (String) singleValue + "' to " + actionedUponNodeRef.toString()); - } - tags.add((String) singleValue); - } - } - else if (singleValue instanceof NodeRef) - { - String tagName = (String) nodeService.getProperty((NodeRef) singleValue, ContentModel.PROP_NAME); - tags.add(tagName); - } - } - } - else if (rawValue instanceof String) - { - if (logger.isTraceEnabled()) - { - logger.trace("adding tag '" + (String) rawValue + "' to " + actionedUponNodeRef.toString()); - } - tags.add((String) rawValue); - } - taggingService.addTags(actionedUponNodeRef, tags); - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, - * NodeRef) - */ - public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) - { - if (!nodeService.exists(actionedUponNodeRef)) - { - // Node is gone - return; - } - ContentReader reader = contentService.getReader(actionedUponNodeRef, ContentModel.PROP_CONTENT); - // The reader may be null, e.g. for folders and the like - if (reader == null || reader.getMimetype() == null) - { - if(logger.isDebugEnabled()) - { - logger.debug("no content or mimetype - do nothing"); - } - // No content to extract data from - return; - } - String mimetype = reader.getMimetype(); - MetadataExtracter extracter = metadataExtracterRegistry.getExtracter(mimetype); - if (extracter == null) - { - if(logger.isDebugEnabled()) - { - logger.debug("no extracter for mimetype:" + mimetype); - } - // There is no extracter to use - return; - } - if (enableStringTagging && (extracter instanceof AbstractMappingMetadataExtracter)) - { - ((AbstractMappingMetadataExtracter) extracter).setEnableStringTagging(enableStringTagging); - } - - // Get all the node's properties - Map nodeProperties = nodeService.getProperties(actionedUponNodeRef); - - // TODO: The override policy should be a parameter here. Instead, we'll use the default policy - // set on the extracter. - // Give the node's properties to the extracter to be modified - Map modifiedProperties = null; - try - { - modifiedProperties = extracter.extract( - reader, - /*OverwritePolicy.PRAGMATIC,*/ - nodeProperties); - } - catch (Throwable e) - { - // Extracters should attempt to handle all error conditions and extract - // as much as they can. If, however, one should fail, we don't want the - // action itself to fail. We absorb and report the exception here to - // solve ETHREEOH-1936 and ALFCOM-2889. - if (logger.isDebugEnabled()) - { - logger.debug( - "Raw metadata extraction failed: \n" + - " Extracter: " + this + "\n" + - " Node: " + actionedUponNodeRef + "\n" + - " Content: " + reader, - e); - } - else - { - logger.warn( - "Raw metadata extraction failed (turn on DEBUG for full error): \n" + - " Extracter: " + this + "\n" + - " Node: " + actionedUponNodeRef + "\n" + - " Content: " + reader + "\n" + - " Failure: " + e.getMessage()); - } - modifiedProperties = new HashMap(0); - } - - // If none of the properties where changed, then there is nothing more to do - if (modifiedProperties.size() == 0) - { - return; - } - - // Check that all properties have the appropriate aspect applied - Set requiredAspectQNames = new HashSet(3); - Set aspectPropertyQNames = new HashSet(17); - - /** - * The modified properties contain null values as well. As we are only interested - * in the keys, this will force aspect aspect properties to be removed even if there - * are no settable properties pertaining to the aspect. - */ - for (QName propertyQName : modifiedProperties.keySet()) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - if (propertyDef == null) - { - // The property is not defined in the model - continue; - } - ClassDefinition propertyContainerDef = propertyDef.getContainerClass(); - if (propertyContainerDef.isAspect()) - { - if (enableStringTagging && propertyContainerDef.getName().equals(ContentModel.ASPECT_TAGGABLE)) - { - Serializable oldValue = nodeProperties.get(propertyQName); - addTags(actionedUponNodeRef, propertyDef, oldValue); - // Replace the raw value with the created tag NodeRefs - nodeProperties.put(ContentModel.PROP_TAGS, - nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_TAGS)); - } - else - { - QName aspectQName = propertyContainerDef.getName(); - requiredAspectQNames.add(aspectQName); - // Get all properties associated with the aspect - Set aspectProperties = propertyContainerDef.getProperties().keySet(); - aspectPropertyQNames.addAll(aspectProperties); - } - } - } - - if (!carryAspectProperties) - { - // Remove any node properties that are defined on the aspects but were not extracted - for (QName aspectPropertyQName : aspectPropertyQNames) - { - if (!modifiedProperties.containsKey(aspectPropertyQName)) - { - // Simple case: This property was not extracted - nodeProperties.remove(aspectPropertyQName); - } - else if (modifiedProperties.get(aspectPropertyQName) == null) - { - // Trickier (ALF-1823): The property was extracted as 'null' - nodeProperties.remove(aspectPropertyQName); - } - } - } - - // Add all the properties to the node BEFORE we add the aspects - nodeService.setProperties(actionedUponNodeRef, nodeProperties); - - // Add each of the aspects, as required - for (QName requiredAspectQName : requiredAspectQNames) - { - if (nodeService.hasAspect(actionedUponNodeRef, requiredAspectQName)) - { - // The node has the aspect already - continue; - } - else - { - nodeService.addAspect(actionedUponNodeRef, requiredAspectQName, null); - } - } - } - - @Override - protected void addParameterDefinitions(List arg0) - { - // None! - } +/* + * Copyright (C) 2005 Jesper Steen Møller + * + * 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 . + */ +package org.alfresco.repo.action.executer; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter; +import org.alfresco.repo.content.metadata.MetadataExtracter; +import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +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.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.tagging.TaggingService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Extract metadata from any added content. + *

+ * Currently, the default {@linkplain org.alfresco.repo.content.metadata.MetadataExtracter.OverwritePolicy overwrite policy} + * for each extracter is used. (TODO: Add overwrite policy as a parameter.) + * + * @see org.alfresco.repo.content.metadata.MetadataExtracter.OverwritePolicy + * + * @author Jesper Steen Møller + */ +public class ContentMetadataExtracter extends ActionExecuterAbstractBase +{ + private static Log logger = LogFactory.getLog(ContentMetadataExtracter.class); + + public static final String EXECUTOR_NAME = "extract-metadata"; + + private NodeService nodeService; + private ContentService contentService; + private DictionaryService dictionaryService; + private TaggingService taggingService; + private MetadataExtracterRegistry metadataExtracterRegistry; + private boolean carryAspectProperties = true; + private boolean enableStringTagging = false; + + public ContentMetadataExtracter() + { + } + + /** + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param contentService The contentService to set. + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @param dictService The DictionaryService to set. + */ + public void setDictionaryService(DictionaryService dictService) + { + this.dictionaryService = dictService; + } + + /** + * @param taggingService The TaggingService to set. + */ + public void setTaggingService(TaggingService taggingService) + { + this.taggingService = taggingService; + } + + /** + * @param metadataExtracterRegistry The metadataExtracterRegistry to set. + */ + public void setMetadataExtracterRegistry(MetadataExtracterRegistry metadataExtracterRegistry) + { + this.metadataExtracterRegistry = metadataExtracterRegistry; + } + + /** + * Whether or not aspect-related properties must be carried to the new version of the node + * + * @param carryAspectProperties true (default) to carry all aspect-linked + * properties forward. false will clean the + * aspect of any unextracted values. + */ + public void setCarryAspectProperties(boolean carryAspectProperties) + { + this.carryAspectProperties = carryAspectProperties; + } + + /** + * Whether or not to enable mapping of simple strings to cm:taggable tags + * + * @param enableStringTagging true find or create tags for each string + * mapped to cm:taggable. false (default) + * ignore mapping strings to tags. + */ + public void setEnableStringTagging(boolean enableStringTagging) + { + this.enableStringTagging = enableStringTagging; + } + + /** + * Iterates the values of the taggable property which the metadata + * extractor should have already attempted to convert values to {@link NodeRef}s. + *

+ * If conversion by the metadata extracter failed due to a MalformedNodeRefException + * the taggable property should still contain raw string values. + *

+ * Mixing of NodeRefs and string values is permitted so each raw value is + * checked for a valid NodeRef representation and if so, converts to a NodeRef, + * if not, adds as a tag via the {@link TaggingService}. + * + * @param actionedUponNodeRef The NodeRef being actioned upon + * @param propertyDef the PropertyDefinition of the taggable property + * @param rawValue the raw value from the metadata extracter + */ + @SuppressWarnings("unchecked") + protected void addTags(NodeRef actionedUponNodeRef, PropertyDefinition propertyDef, Serializable rawValue) + { + List tags = new ArrayList(); + if (logger.isDebugEnabled()) + { + logger.debug("converting " + rawValue.toString() + " of type " + + rawValue.getClass().getCanonicalName() + " to tags"); + } + if (rawValue instanceof Collection) + { + for (Object singleValue : (Collection) rawValue) + { + if (singleValue instanceof String) + { + if (NodeRef.isNodeRef((String) singleValue)) + { + // Convert to a NodeRef + Serializable convertedPropertyValue = (Serializable) DefaultTypeConverter.INSTANCE.convert( + propertyDef.getDataType(), + (String) singleValue); + try { + String tagName = (String) nodeService.getProperty((NodeRef) convertedPropertyValue, ContentModel.PROP_NAME); + if (logger.isTraceEnabled()) + { + logger.trace("found tag '" + tagName + "' from tag nodeRef '" + (String) singleValue + "', " + + "adding to " + actionedUponNodeRef.toString()); + } + if (tagName != null && !tagName.equals("")) + { + tags.add(tagName); + } + } + catch (InvalidNodeRefException e) + { + if (logger.isWarnEnabled()) + { + logger.warn("tag nodeRef Invalid: " + e.getMessage()); + } + } + } + else + { + // Must be a simple string + if (logger.isTraceEnabled()) + { + logger.trace("adding string tag '" + (String) singleValue + "' to " + actionedUponNodeRef.toString()); + } + tags.add((String) singleValue); + } + } + else if (singleValue instanceof NodeRef) + { + String tagName = (String) nodeService.getProperty((NodeRef) singleValue, ContentModel.PROP_NAME); + tags.add(tagName); + } + } + } + else if (rawValue instanceof String) + { + if (logger.isTraceEnabled()) + { + logger.trace("adding tag '" + (String) rawValue + "' to " + actionedUponNodeRef.toString()); + } + tags.add((String) rawValue); + } + taggingService.addTags(actionedUponNodeRef, tags); + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, + * NodeRef) + */ + public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + if (!nodeService.exists(actionedUponNodeRef)) + { + // Node is gone + return; + } + ContentReader reader = contentService.getReader(actionedUponNodeRef, ContentModel.PROP_CONTENT); + // The reader may be null, e.g. for folders and the like + if (reader == null || reader.getMimetype() == null) + { + if(logger.isDebugEnabled()) + { + logger.debug("no content or mimetype - do nothing"); + } + // No content to extract data from + return; + } + String mimetype = reader.getMimetype(); + MetadataExtracter extracter = metadataExtracterRegistry.getExtracter(mimetype); + if (extracter == null) + { + if(logger.isDebugEnabled()) + { + logger.debug("no extracter for mimetype:" + mimetype); + } + // There is no extracter to use + return; + } + if (enableStringTagging && (extracter instanceof AbstractMappingMetadataExtracter)) + { + ((AbstractMappingMetadataExtracter) extracter).setEnableStringTagging(enableStringTagging); + } + + // Get all the node's properties + Map nodeProperties = nodeService.getProperties(actionedUponNodeRef); + + // TODO: The override policy should be a parameter here. Instead, we'll use the default policy + // set on the extracter. + // Give the node's properties to the extracter to be modified + Map modifiedProperties = null; + try + { + modifiedProperties = extracter.extract( + reader, + /*OverwritePolicy.PRAGMATIC,*/ + nodeProperties); + } + catch (Throwable e) + { + // Extracters should attempt to handle all error conditions and extract + // as much as they can. If, however, one should fail, we don't want the + // action itself to fail. We absorb and report the exception here to + // solve ETHREEOH-1936 and ALFCOM-2889. + if (logger.isDebugEnabled()) + { + logger.debug( + "Raw metadata extraction failed: \n" + + " Extracter: " + this + "\n" + + " Node: " + actionedUponNodeRef + "\n" + + " Content: " + reader, + e); + } + else + { + logger.warn( + "Raw metadata extraction failed (turn on DEBUG for full error): \n" + + " Extracter: " + this + "\n" + + " Node: " + actionedUponNodeRef + "\n" + + " Content: " + reader + "\n" + + " Failure: " + e.getMessage()); + } + modifiedProperties = new HashMap(0); + } + + // If none of the properties where changed, then there is nothing more to do + if (modifiedProperties.size() == 0) + { + return; + } + + // Check that all properties have the appropriate aspect applied + Set requiredAspectQNames = new HashSet(3); + Set aspectPropertyQNames = new HashSet(17); + + /** + * The modified properties contain null values as well. As we are only interested + * in the keys, this will force aspect aspect properties to be removed even if there + * are no settable properties pertaining to the aspect. + */ + for (QName propertyQName : modifiedProperties.keySet()) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + if (propertyDef == null) + { + // The property is not defined in the model + continue; + } + ClassDefinition propertyContainerDef = propertyDef.getContainerClass(); + if (propertyContainerDef.isAspect()) + { + if (enableStringTagging && propertyContainerDef.getName().equals(ContentModel.ASPECT_TAGGABLE)) + { + Serializable oldValue = nodeProperties.get(propertyQName); + addTags(actionedUponNodeRef, propertyDef, oldValue); + // Replace the raw value with the created tag NodeRefs + nodeProperties.put(ContentModel.PROP_TAGS, + nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_TAGS)); + } + else + { + QName aspectQName = propertyContainerDef.getName(); + requiredAspectQNames.add(aspectQName); + // Get all properties associated with the aspect + Set aspectProperties = propertyContainerDef.getProperties().keySet(); + aspectPropertyQNames.addAll(aspectProperties); + } + } + } + + if (!carryAspectProperties) + { + // Remove any node properties that are defined on the aspects but were not extracted + for (QName aspectPropertyQName : aspectPropertyQNames) + { + if (!modifiedProperties.containsKey(aspectPropertyQName)) + { + // Simple case: This property was not extracted + nodeProperties.remove(aspectPropertyQName); + } + else if (modifiedProperties.get(aspectPropertyQName) == null) + { + // Trickier (ALF-1823): The property was extracted as 'null' + nodeProperties.remove(aspectPropertyQName); + } + } + } + + // Add all the properties to the node BEFORE we add the aspects + nodeService.setProperties(actionedUponNodeRef, nodeProperties); + + // Add each of the aspects, as required + for (QName requiredAspectQName : requiredAspectQNames) + { + if (nodeService.hasAspect(actionedUponNodeRef, requiredAspectQName)) + { + // The node has the aspect already + continue; + } + else + { + nodeService.addAspect(actionedUponNodeRef, requiredAspectQName, null); + } + } + } + + @Override + protected void addParameterDefinitions(List arg0) + { + // None! + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/action/executer/CounterIncrementActionExecuter.java b/source/java/org/alfresco/repo/action/executer/CounterIncrementActionExecuter.java index 317329889b..d8e3c48527 100644 --- a/source/java/org/alfresco/repo/action/executer/CounterIncrementActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/CounterIncrementActionExecuter.java @@ -1,70 +1,70 @@ -package org.alfresco.repo.action.executer; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -/** - * Simple action to increment an integer value. The runtime NodeService is used so any user - * can increment the counter value on a node. - * - * @author Kevin Roast - */ -public class CounterIncrementActionExecuter extends ActionExecuterAbstractBase -{ - public static final String NAME = "counter"; - - /** Runtime NodeService with no permissions protection */ - private NodeService nodeService; - - - /** - * @param nodeService The Runtime NodeService to set. - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) - */ - protected void executeImpl(Action action, NodeRef actionedUponNodeRef) - { - // add the cm:countable aspect as required - if (this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_COUNTABLE) == false) - { - // set the value to 1 by default - Map props = new HashMap(1); - props.put(ContentModel.PROP_COUNTER, 1); - this.nodeService.addAspect(actionedUponNodeRef, ContentModel.ASPECT_COUNTABLE, props); - } - else - { - // increment the value and handle possibility that no value has been set yet - int resultValue = 1; - Integer value = (Integer)this.nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_COUNTER); - if (value != null) - { - resultValue = value.intValue() + 1; - } - this.nodeService.setProperty(actionedUponNodeRef, ContentModel.PROP_COUNTER, resultValue); - } - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - protected void addParameterDefinitions(List paramList) - { - // none required - } -} +package org.alfresco.repo.action.executer; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Simple action to increment an integer value. The runtime NodeService is used so any user + * can increment the counter value on a node. + * + * @author Kevin Roast + */ +public class CounterIncrementActionExecuter extends ActionExecuterAbstractBase +{ + public static final String NAME = "counter"; + + /** Runtime NodeService with no permissions protection */ + private NodeService nodeService; + + + /** + * @param nodeService The Runtime NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + // add the cm:countable aspect as required + if (this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_COUNTABLE) == false) + { + // set the value to 1 by default + Map props = new HashMap(1); + props.put(ContentModel.PROP_COUNTER, 1); + this.nodeService.addAspect(actionedUponNodeRef, ContentModel.ASPECT_COUNTABLE, props); + } + else + { + // increment the value and handle possibility that no value has been set yet + int resultValue = 1; + Integer value = (Integer)this.nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_COUNTER); + if (value != null) + { + resultValue = value.intValue() + 1; + } + this.nodeService.setProperty(actionedUponNodeRef, ContentModel.PROP_COUNTER, resultValue); + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + protected void addParameterDefinitions(List paramList) + { + // none required + } +} diff --git a/source/java/org/alfresco/repo/action/executer/CreateVersionActionExecuter.java b/source/java/org/alfresco/repo/action/executer/CreateVersionActionExecuter.java index c09f327bab..f7c4720901 100644 --- a/source/java/org/alfresco/repo/action/executer/CreateVersionActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/CreateVersionActionExecuter.java @@ -1,103 +1,103 @@ -package org.alfresco.repo.action.executer; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.repo.version.VersionModel; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.version.Version; -import org.alfresco.service.cmr.version.VersionService; -import org.alfresco.service.cmr.version.VersionType; - -/** - * Add features action executor implementation. - * - * @author Roy Wetherall - */ -public class CreateVersionActionExecuter extends ActionExecuterAbstractBase -{ - /** - * Action constants - */ - public static final String NAME = "create-version"; - public static final String PARAM_DESCRIPTION = "description"; - public static final String PARAM_MINOR_CHANGE = "minor-change"; - - /** Node service */ - public NodeService nodeService; - - /** Version service */ - public VersionService versionService; - - /** - * Set node service - * - * @param nodeService node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set version service - * - * @param versionService version service - */ - public void setVersionService(VersionService versionService) - { - this.versionService = versionService; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) - */ - public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) - { - if (this.nodeService.exists(actionedUponNodeRef) == true && - this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE) == true) - { - Map versionProperties = new HashMap(2); - - // Get the version description - String description = (String)ruleAction.getParameterValue(PARAM_DESCRIPTION); - if (description != null && description.length() != 0) - { - versionProperties.put(Version.PROP_DESCRIPTION, description); - } - - // determine whether the change is minor or major - Boolean minorChange = (Boolean)ruleAction.getParameterValue(PARAM_MINOR_CHANGE); - if (minorChange != null && minorChange.booleanValue() == false) - { - versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); - } - else - { - versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MINOR); - } - - // Create the version - this.versionService.createVersion(actionedUponNodeRef, versionProperties); - } - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_MINOR_CHANGE, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_MINOR_CHANGE))); - paramList.add(new ParameterDefinitionImpl(PARAM_DESCRIPTION, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_DESCRIPTION))); - } - -} +package org.alfresco.repo.action.executer; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.cmr.version.VersionType; + +/** + * Add features action executor implementation. + * + * @author Roy Wetherall + */ +public class CreateVersionActionExecuter extends ActionExecuterAbstractBase +{ + /** + * Action constants + */ + public static final String NAME = "create-version"; + public static final String PARAM_DESCRIPTION = "description"; + public static final String PARAM_MINOR_CHANGE = "minor-change"; + + /** Node service */ + public NodeService nodeService; + + /** Version service */ + public VersionService versionService; + + /** + * Set node service + * + * @param nodeService node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set version service + * + * @param versionService version service + */ + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) + */ + public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + if (this.nodeService.exists(actionedUponNodeRef) == true && + this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE) == true) + { + Map versionProperties = new HashMap(2); + + // Get the version description + String description = (String)ruleAction.getParameterValue(PARAM_DESCRIPTION); + if (description != null && description.length() != 0) + { + versionProperties.put(Version.PROP_DESCRIPTION, description); + } + + // determine whether the change is minor or major + Boolean minorChange = (Boolean)ruleAction.getParameterValue(PARAM_MINOR_CHANGE); + if (minorChange != null && minorChange.booleanValue() == false) + { + versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); + } + else + { + versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MINOR); + } + + // Create the version + this.versionService.createVersion(actionedUponNodeRef, versionProperties); + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_MINOR_CHANGE, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_MINOR_CHANGE))); + paramList.add(new ParameterDefinitionImpl(PARAM_DESCRIPTION, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_DESCRIPTION))); + } + +} diff --git a/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuter.java index 00b42973ff..36b526432a 100644 --- a/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuter.java @@ -1,163 +1,163 @@ -package org.alfresco.repo.action.executer; - -import java.util.List; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.repo.rule.RuntimeRuleService; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -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.rule.Rule; -import org.alfresco.service.cmr.rule.RuleService; -import org.alfresco.service.namespace.QName; - -/** - * This action executes all rules present on the actioned upon node - * - * @author Roy Wetherall - */ -public class ExecuteAllRulesActionExecuter extends ActionExecuterAbstractBase -{ - /** - * Action constants - */ - public static final String NAME = "execute-all-rules"; - public static final String PARAM_EXECUTE_INHERITED_RULES = "execute-inherited-rules"; - public static final String PARAM_RUN_ALL_RULES_ON_CHILDREN = "run-all-rules-on-children"; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * The rule service - */ - private RuleService ruleService; - - /** - * The runtime rule service - */ - private RuntimeRuleService runtimeRuleService; - - /** The dictionary Service */ - private DictionaryService dictionaryService; - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the rule service - * - * @param ruleService the rule service - */ - public void setRuleService(RuleService ruleService) - { - this.ruleService = ruleService; - } - - /** - * Set the runtime rule service - * - * @param runtimeRuleService the runtime rule service - */ - public void setRuntimeRuleService(RuntimeRuleService runtimeRuleService) - { - this.runtimeRuleService = runtimeRuleService; - } - - /** - * Sets the dictionary service - * - * @param dictionaryService the dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) - */ - public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) - { - if (this.nodeService.exists(actionedUponNodeRef) == true) - { - // Get the parameter value - boolean includeInherited = false; - Boolean includeInheritedValue = (Boolean)ruleAction.getParameterValue(PARAM_EXECUTE_INHERITED_RULES); - if (includeInheritedValue != null) - { - includeInherited = includeInheritedValue.booleanValue(); - } - - boolean runAllChildren = false; - Boolean runAllChildrenValue = (Boolean)ruleAction.getParameterValue(PARAM_RUN_ALL_RULES_ON_CHILDREN); - if (runAllChildrenValue != null) - { - runAllChildren = runAllChildrenValue.booleanValue(); - } - - // Get the rules - List rules = ruleService.getRules(actionedUponNodeRef, includeInherited); - if (rules != null && rules.isEmpty() == false) - { - // Get the child nodes for the actioned upon node - List children = nodeService.getChildAssocs(actionedUponNodeRef); - for (ChildAssociationRef childAssoc : children) - { - // Get the child node reference - NodeRef child = childAssoc.getChildRef(); - - // Only execute rules on non-system folders - QName childType = nodeService.getType(child); - if (dictionaryService.isSubClass(childType, ContentModel.TYPE_SYSTEM_FOLDER) == false) - { - for (Rule rule : rules) - { - // Only re-apply rules that are enabled - if (rule.getRuleDisabled() == false) - { - Action action = rule.getAction(); - if (action != null) - { - runtimeRuleService.addRulePendingExecution(actionedUponNodeRef, child, rule); - } - } - } - - // If the child is a folder and we have asked to run rules on children - if (runAllChildren == true && - dictionaryService.isSubClass(childType, ContentModel.TYPE_FOLDER) == true) - { - // Recurse with the child folder - executeImpl(ruleAction, child); - } - } - } - } - } - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_EXECUTE_INHERITED_RULES, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_EXECUTE_INHERITED_RULES))); - paramList.add(new ParameterDefinitionImpl(PARAM_RUN_ALL_RULES_ON_CHILDREN, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_RUN_ALL_RULES_ON_CHILDREN))); - } -} +package org.alfresco.repo.action.executer; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.rule.RuntimeRuleService; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +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.rule.Rule; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.namespace.QName; + +/** + * This action executes all rules present on the actioned upon node + * + * @author Roy Wetherall + */ +public class ExecuteAllRulesActionExecuter extends ActionExecuterAbstractBase +{ + /** + * Action constants + */ + public static final String NAME = "execute-all-rules"; + public static final String PARAM_EXECUTE_INHERITED_RULES = "execute-inherited-rules"; + public static final String PARAM_RUN_ALL_RULES_ON_CHILDREN = "run-all-rules-on-children"; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * The rule service + */ + private RuleService ruleService; + + /** + * The runtime rule service + */ + private RuntimeRuleService runtimeRuleService; + + /** The dictionary Service */ + private DictionaryService dictionaryService; + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the rule service + * + * @param ruleService the rule service + */ + public void setRuleService(RuleService ruleService) + { + this.ruleService = ruleService; + } + + /** + * Set the runtime rule service + * + * @param runtimeRuleService the runtime rule service + */ + public void setRuntimeRuleService(RuntimeRuleService runtimeRuleService) + { + this.runtimeRuleService = runtimeRuleService; + } + + /** + * Sets the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) + */ + public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + if (this.nodeService.exists(actionedUponNodeRef) == true) + { + // Get the parameter value + boolean includeInherited = false; + Boolean includeInheritedValue = (Boolean)ruleAction.getParameterValue(PARAM_EXECUTE_INHERITED_RULES); + if (includeInheritedValue != null) + { + includeInherited = includeInheritedValue.booleanValue(); + } + + boolean runAllChildren = false; + Boolean runAllChildrenValue = (Boolean)ruleAction.getParameterValue(PARAM_RUN_ALL_RULES_ON_CHILDREN); + if (runAllChildrenValue != null) + { + runAllChildren = runAllChildrenValue.booleanValue(); + } + + // Get the rules + List rules = ruleService.getRules(actionedUponNodeRef, includeInherited); + if (rules != null && rules.isEmpty() == false) + { + // Get the child nodes for the actioned upon node + List children = nodeService.getChildAssocs(actionedUponNodeRef); + for (ChildAssociationRef childAssoc : children) + { + // Get the child node reference + NodeRef child = childAssoc.getChildRef(); + + // Only execute rules on non-system folders + QName childType = nodeService.getType(child); + if (dictionaryService.isSubClass(childType, ContentModel.TYPE_SYSTEM_FOLDER) == false) + { + for (Rule rule : rules) + { + // Only re-apply rules that are enabled + if (rule.getRuleDisabled() == false) + { + Action action = rule.getAction(); + if (action != null) + { + runtimeRuleService.addRulePendingExecution(actionedUponNodeRef, child, rule); + } + } + } + + // If the child is a folder and we have asked to run rules on children + if (runAllChildren == true && + dictionaryService.isSubClass(childType, ContentModel.TYPE_FOLDER) == true) + { + // Recurse with the child folder + executeImpl(ruleAction, child); + } + } + } + } + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_EXECUTE_INHERITED_RULES, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_EXECUTE_INHERITED_RULES))); + paramList.add(new ParameterDefinitionImpl(PARAM_RUN_ALL_RULES_ON_CHILDREN, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_RUN_ALL_RULES_ON_CHILDREN))); + } +} diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuterMonitor.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuterMonitor.java index 57f46dd130..510d3dde63 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuterMonitor.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuterMonitor.java @@ -1,39 +1,39 @@ -package org.alfresco.repo.action.executer; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.springframework.extensions.surf.util.I18NUtil; - -public class MailActionExecuterMonitor -{ - private MailActionExecuter mailActionExceuter; - - public String sendTestMessage() - { - try - { - mailActionExceuter.sendTestMessage(); - Object[] params = {mailActionExceuter.getTestMessageTo()}; - String message = I18NUtil.getMessage("email.outbound.test.send.success", params); - return message; - } - catch - (AlfrescoRuntimeException are) - { - return (are.getMessage()); - } - } - public int getNumberFailedSends() - { - return mailActionExceuter.getNumberFailedSends(); - } - - public int getNumberSuccessfulSends() - { - return mailActionExceuter.getNumberSuccessfulSends(); - } - - public void setMailActionExecuter(MailActionExecuter mailActionExceuter) - { - this.mailActionExceuter = mailActionExceuter; - } -} +package org.alfresco.repo.action.executer; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.springframework.extensions.surf.util.I18NUtil; + +public class MailActionExecuterMonitor +{ + private MailActionExecuter mailActionExceuter; + + public String sendTestMessage() + { + try + { + mailActionExceuter.sendTestMessage(); + Object[] params = {mailActionExceuter.getTestMessageTo()}; + String message = I18NUtil.getMessage("email.outbound.test.send.success", params); + return message; + } + catch + (AlfrescoRuntimeException are) + { + return (are.getMessage()); + } + } + public int getNumberFailedSends() + { + return mailActionExceuter.getNumberFailedSends(); + } + + public int getNumberSuccessfulSends() + { + return mailActionExceuter.getNumberSuccessfulSends(); + } + + public void setMailActionExecuter(MailActionExecuter mailActionExceuter) + { + this.mailActionExceuter = mailActionExceuter; + } +} diff --git a/source/java/org/alfresco/repo/action/executer/RemoveFeaturesActionExecuter.java b/source/java/org/alfresco/repo/action/executer/RemoveFeaturesActionExecuter.java index 4bcbfeb4b3..fa05e8f170 100644 --- a/source/java/org/alfresco/repo/action/executer/RemoveFeaturesActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/RemoveFeaturesActionExecuter.java @@ -1,63 +1,63 @@ -package org.alfresco.repo.action.executer; - -import java.util.List; - -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -/** - * Remove features action executor implementation. - * - * @author Roy Wetherall - */ -public class RemoveFeaturesActionExecuter extends ActionExecuterAbstractBase -{ - /** - * Action constants - */ - public static final String NAME = "remove-features"; - public static final String PARAM_ASPECT_NAME = "aspect-name"; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) - */ - public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) - { - if (this.nodeService.exists(actionedUponNodeRef) == true) - { - // Remove the aspect - QName aspectQName = (QName)ruleAction.getParameterValue(PARAM_ASPECT_NAME); - this.nodeService.removeAspect(actionedUponNodeRef, aspectQName); - } - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_ASPECT_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASPECT_NAME), false, "ac-aspects")); - } - -} +package org.alfresco.repo.action.executer; + +import java.util.List; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Remove features action executor implementation. + * + * @author Roy Wetherall + */ +public class RemoveFeaturesActionExecuter extends ActionExecuterAbstractBase +{ + /** + * Action constants + */ + public static final String NAME = "remove-features"; + public static final String PARAM_ASPECT_NAME = "aspect-name"; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) + */ + public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + if (this.nodeService.exists(actionedUponNodeRef) == true) + { + // Remove the aspect + QName aspectQName = (QName)ruleAction.getParameterValue(PARAM_ASPECT_NAME); + this.nodeService.removeAspect(actionedUponNodeRef, aspectQName); + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_ASPECT_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASPECT_NAME), false, "ac-aspects")); + } + +} diff --git a/source/java/org/alfresco/repo/action/executer/RepositoryExporterActionExecuter.java b/source/java/org/alfresco/repo/action/executer/RepositoryExporterActionExecuter.java index 0acd29fe8d..0434f2d9a6 100644 --- a/source/java/org/alfresco/repo/action/executer/RepositoryExporterActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/RepositoryExporterActionExecuter.java @@ -1,59 +1,59 @@ -package org.alfresco.repo.action.executer; - -import java.util.List; - -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.view.RepositoryExporterService; - -/** - * Repository Exporter action executor - * - * @author davidc - */ -public class RepositoryExporterActionExecuter extends ActionExecuterAbstractBase -{ - public static final String NAME = "repository-export"; - public static final String PARAM_PACKAGE_NAME = "package-name"; - public static final String PARAM_DESTINATION_FOLDER = "destination"; - - /** - * The exporter service - */ - private RepositoryExporterService exporterService; - - /** - * Sets the ExporterService to use - * - * @param exporterService The ExporterService - */ - public void setRepositoryExporterService(RepositoryExporterService exporterService) - { - this.exporterService = exporterService; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) - */ - public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) - { - String packageName = (String)ruleAction.getParameterValue(PARAM_PACKAGE_NAME); - NodeRef repoDestination = (NodeRef)ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER); - exporterService.export(repoDestination, packageName); - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_PACKAGE_NAME, DataTypeDefinition.TEXT, true, - getParamDisplayLabel(PARAM_PACKAGE_NAME))); - paramList.add(new ParameterDefinitionImpl(PARAM_DESTINATION_FOLDER, DataTypeDefinition.NODE_REF, true, - getParamDisplayLabel(PARAM_DESTINATION_FOLDER))); - } - -} +package org.alfresco.repo.action.executer; + +import java.util.List; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.view.RepositoryExporterService; + +/** + * Repository Exporter action executor + * + * @author davidc + */ +public class RepositoryExporterActionExecuter extends ActionExecuterAbstractBase +{ + public static final String NAME = "repository-export"; + public static final String PARAM_PACKAGE_NAME = "package-name"; + public static final String PARAM_DESTINATION_FOLDER = "destination"; + + /** + * The exporter service + */ + private RepositoryExporterService exporterService; + + /** + * Sets the ExporterService to use + * + * @param exporterService The ExporterService + */ + public void setRepositoryExporterService(RepositoryExporterService exporterService) + { + this.exporterService = exporterService; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) + */ + public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + String packageName = (String)ruleAction.getParameterValue(PARAM_PACKAGE_NAME); + NodeRef repoDestination = (NodeRef)ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER); + exporterService.export(repoDestination, packageName); + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_PACKAGE_NAME, DataTypeDefinition.TEXT, true, + getParamDisplayLabel(PARAM_PACKAGE_NAME))); + paramList.add(new ParameterDefinitionImpl(PARAM_DESTINATION_FOLDER, DataTypeDefinition.NODE_REF, true, + getParamDisplayLabel(PARAM_DESTINATION_FOLDER))); + } + +} diff --git a/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java index 57eaed4313..fcff686871 100644 --- a/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java @@ -1,200 +1,200 @@ -package org.alfresco.repo.action.executer; - -import java.io.Serializable; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.repo.admin.SysAdminParams; -import org.alfresco.repo.jscript.ScriptAction; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.ScriptLocation; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.util.UrlUtil; - -/** - * Action to execute a JavaScript. The script has access to the default model. - * The actionedUponNodeRef is added to the default model as the 'document' and the owning - * NodeRef is added as the 'space'. - * - * @author Kevin Roast - */ -public class ScriptActionExecuter extends ActionExecuterAbstractBase -{ - public static final String NAME = "script"; - public static final String PARAM_SCRIPTREF = "script-ref"; - - private ServiceRegistry serviceRegistry; - private SysAdminParams sysAdminParams; - private PersonService personService; - private String companyHomePath; - private StoreRef storeRef; - private ScriptLocation scriptLocation; - - /** - * @param serviceRegistry The serviceRegistry to set. - */ - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.serviceRegistry = serviceRegistry; - } - - /** - * @param sysAdminParams The sysAdminParams to set. - */ - public void setSysAdminParams(SysAdminParams sysAdminParams) - { - this.sysAdminParams = sysAdminParams; - } - - /** - * @param personService The personService to set. - */ - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - public void setStoreUrl(String storeUrl) - { - this.storeRef = new StoreRef(storeUrl); - } - - public void setCompanyHomePath(String companyHomePath) - { - this.companyHomePath = companyHomePath; - } - - /** - * Set the script location from Spring - * - * @param scriptLocation the script location - */ - public void setScriptLocation(ScriptLocation scriptLocation) - { - this.scriptLocation = scriptLocation; - } - - /** - * Allow adhoc properties to be passed to this action - * - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#getAdhocPropertiesAllowed() - */ - protected boolean getAdhocPropertiesAllowed() - { - return true; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) - */ - protected void executeImpl(Action action, NodeRef actionedUponNodeRef) - { - NodeService nodeService = this.serviceRegistry.getNodeService(); - if (nodeService.exists(actionedUponNodeRef)) - { - NodeRef scriptRef = (NodeRef)action.getParameterValue(PARAM_SCRIPTREF); - NodeRef spaceRef = this.serviceRegistry.getRuleService().getOwningNodeRef(action); - if (spaceRef == null) - { - // the actionedUponNodeRef may actually be a space - if (this.serviceRegistry.getDictionaryService().isSubClass( - nodeService.getType(actionedUponNodeRef), ContentModel.TYPE_FOLDER)) - { - spaceRef = actionedUponNodeRef; - } - else - { - spaceRef = nodeService.getPrimaryParent(actionedUponNodeRef).getParentRef(); - } - } - - if (this.scriptLocation != null || (scriptRef != null && nodeService.exists(scriptRef) == true)) - { - // get the references we need to build the default scripting data-model - String userName = this.serviceRegistry.getAuthenticationService().getCurrentUserName(); - NodeRef personRef = this.personService.getPerson(userName); - NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER); - - // the default scripting model provides access to well known objects and searching - // facilities - it also provides basic create/update/delete/copy/move services - Map model = this.serviceRegistry.getScriptService().buildDefaultModel( - personRef, - getCompanyHome(), - homeSpaceRef, - scriptRef, - actionedUponNodeRef, - spaceRef); - - // Add the action to the default model - ScriptAction scriptAction = new ScriptAction(this.serviceRegistry, action, this.actionDefinition); - model.put("action", scriptAction); - - model.put("webApplicationContextUrl", UrlUtil.getAlfrescoUrl(sysAdminParams)); - - Object result = null; - if (this.scriptLocation == null) - { - // execute the script against the default model - result = this.serviceRegistry.getScriptService().executeScript( - scriptRef, - ContentModel.PROP_CONTENT, - model); - } - else - { - // execute the script at the specified script location - result = this.serviceRegistry.getScriptService().executeScript(this.scriptLocation, model); - } - - // Set the result - if (result != null) - { - action.setParameterValue(PARAM_RESULT, (Serializable)result); - } - } - } - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - protected void addParameterDefinitions(List paramList) - { - if (scriptLocation == null) - { - paramList.add(new ParameterDefinitionImpl(PARAM_SCRIPTREF, DataTypeDefinition.NODE_REF, true, getParamDisplayLabel(PARAM_SCRIPTREF), false, "ac-scripts")); - } - } - - /** - * Gets the company home node - * - * @return the company home node ref - */ - private NodeRef getCompanyHome() - { - NodeRef companyHomeRef; - - List refs = this.serviceRegistry.getSearchService().selectNodes( - this.serviceRegistry.getNodeService().getRootNode(storeRef), - companyHomePath, - null, - this.serviceRegistry.getNamespaceService(), - false); - if (refs.size() != 1) - { - throw new IllegalStateException("Invalid company home path: " + companyHomePath + " - found: " + refs.size()); - } - companyHomeRef = refs.get(0); - - return companyHomeRef; - } -} +package org.alfresco.repo.action.executer; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.jscript.ScriptAction; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.UrlUtil; + +/** + * Action to execute a JavaScript. The script has access to the default model. + * The actionedUponNodeRef is added to the default model as the 'document' and the owning + * NodeRef is added as the 'space'. + * + * @author Kevin Roast + */ +public class ScriptActionExecuter extends ActionExecuterAbstractBase +{ + public static final String NAME = "script"; + public static final String PARAM_SCRIPTREF = "script-ref"; + + private ServiceRegistry serviceRegistry; + private SysAdminParams sysAdminParams; + private PersonService personService; + private String companyHomePath; + private StoreRef storeRef; + private ScriptLocation scriptLocation; + + /** + * @param serviceRegistry The serviceRegistry to set. + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * @param sysAdminParams The sysAdminParams to set. + */ + public void setSysAdminParams(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; + } + + /** + * @param personService The personService to set. + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setStoreUrl(String storeUrl) + { + this.storeRef = new StoreRef(storeUrl); + } + + public void setCompanyHomePath(String companyHomePath) + { + this.companyHomePath = companyHomePath; + } + + /** + * Set the script location from Spring + * + * @param scriptLocation the script location + */ + public void setScriptLocation(ScriptLocation scriptLocation) + { + this.scriptLocation = scriptLocation; + } + + /** + * Allow adhoc properties to be passed to this action + * + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#getAdhocPropertiesAllowed() + */ + protected boolean getAdhocPropertiesAllowed() + { + return true; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + NodeService nodeService = this.serviceRegistry.getNodeService(); + if (nodeService.exists(actionedUponNodeRef)) + { + NodeRef scriptRef = (NodeRef)action.getParameterValue(PARAM_SCRIPTREF); + NodeRef spaceRef = this.serviceRegistry.getRuleService().getOwningNodeRef(action); + if (spaceRef == null) + { + // the actionedUponNodeRef may actually be a space + if (this.serviceRegistry.getDictionaryService().isSubClass( + nodeService.getType(actionedUponNodeRef), ContentModel.TYPE_FOLDER)) + { + spaceRef = actionedUponNodeRef; + } + else + { + spaceRef = nodeService.getPrimaryParent(actionedUponNodeRef).getParentRef(); + } + } + + if (this.scriptLocation != null || (scriptRef != null && nodeService.exists(scriptRef) == true)) + { + // get the references we need to build the default scripting data-model + String userName = this.serviceRegistry.getAuthenticationService().getCurrentUserName(); + NodeRef personRef = this.personService.getPerson(userName); + NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER); + + // the default scripting model provides access to well known objects and searching + // facilities - it also provides basic create/update/delete/copy/move services + Map model = this.serviceRegistry.getScriptService().buildDefaultModel( + personRef, + getCompanyHome(), + homeSpaceRef, + scriptRef, + actionedUponNodeRef, + spaceRef); + + // Add the action to the default model + ScriptAction scriptAction = new ScriptAction(this.serviceRegistry, action, this.actionDefinition); + model.put("action", scriptAction); + + model.put("webApplicationContextUrl", UrlUtil.getAlfrescoUrl(sysAdminParams)); + + Object result = null; + if (this.scriptLocation == null) + { + // execute the script against the default model + result = this.serviceRegistry.getScriptService().executeScript( + scriptRef, + ContentModel.PROP_CONTENT, + model); + } + else + { + // execute the script at the specified script location + result = this.serviceRegistry.getScriptService().executeScript(this.scriptLocation, model); + } + + // Set the result + if (result != null) + { + action.setParameterValue(PARAM_RESULT, (Serializable)result); + } + } + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + protected void addParameterDefinitions(List paramList) + { + if (scriptLocation == null) + { + paramList.add(new ParameterDefinitionImpl(PARAM_SCRIPTREF, DataTypeDefinition.NODE_REF, true, getParamDisplayLabel(PARAM_SCRIPTREF), false, "ac-scripts")); + } + } + + /** + * Gets the company home node + * + * @return the company home node ref + */ + private NodeRef getCompanyHome() + { + NodeRef companyHomeRef; + + List refs = this.serviceRegistry.getSearchService().selectNodes( + this.serviceRegistry.getNodeService().getRootNode(storeRef), + companyHomePath, + null, + this.serviceRegistry.getNamespaceService(), + false); + if (refs.size() != 1) + { + throw new IllegalStateException("Invalid company home path: " + companyHomePath + " - found: " + refs.size()); + } + companyHomeRef = refs.get(0); + + return companyHomeRef; + } +} diff --git a/source/java/org/alfresco/repo/action/executer/SetPropertyValueActionExecuter.java b/source/java/org/alfresco/repo/action/executer/SetPropertyValueActionExecuter.java index 0211b49e62..b392fa2040 100644 --- a/source/java/org/alfresco/repo/action/executer/SetPropertyValueActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/SetPropertyValueActionExecuter.java @@ -1,67 +1,67 @@ -package org.alfresco.repo.action.executer; - -import java.util.List; - -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -/** - * Add features action executor implementation. - * - * @author Roy Wetherall - */ -public class SetPropertyValueActionExecuter extends ActionExecuterAbstractBase -{ - /** - * Action constants - */ - public static final String NAME = "set-property-value"; - public static final String PARAM_PROPERTY = "property"; - public static final String PARAM_VALUE = "value"; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) - */ - public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) - { - if (this.nodeService.exists(actionedUponNodeRef) == true) - { - // Set the value of the property - this.nodeService.setProperty( - actionedUponNodeRef, - (QName)ruleAction.getParameterValue(PARAM_PROPERTY), - ruleAction.getParameterValue(PARAM_VALUE)); - } - } - - /** - * Add parameter definitions - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_PROPERTY, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_PROPERTY), false, "ac-properties")); - paramList.add(new ParameterDefinitionImpl(PARAM_VALUE, DataTypeDefinition.ANY, true, getParamDisplayLabel(PARAM_VALUE))); - } - -} +package org.alfresco.repo.action.executer; + +import java.util.List; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Add features action executor implementation. + * + * @author Roy Wetherall + */ +public class SetPropertyValueActionExecuter extends ActionExecuterAbstractBase +{ + /** + * Action constants + */ + public static final String NAME = "set-property-value"; + public static final String PARAM_PROPERTY = "property"; + public static final String PARAM_VALUE = "value"; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) + */ + public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + if (this.nodeService.exists(actionedUponNodeRef) == true) + { + // Set the value of the property + this.nodeService.setProperty( + actionedUponNodeRef, + (QName)ruleAction.getParameterValue(PARAM_PROPERTY), + ruleAction.getParameterValue(PARAM_VALUE)); + } + } + + /** + * Add parameter definitions + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_PROPERTY, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_PROPERTY), false, "ac-properties")); + paramList.add(new ParameterDefinitionImpl(PARAM_VALUE, DataTypeDefinition.ANY, true, getParamDisplayLabel(PARAM_VALUE))); + } + +} diff --git a/source/java/org/alfresco/repo/action/executer/SpecialiseTypeActionExecuter.java b/source/java/org/alfresco/repo/action/executer/SpecialiseTypeActionExecuter.java index 7599112b9b..771d7533cd 100644 --- a/source/java/org/alfresco/repo/action/executer/SpecialiseTypeActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/SpecialiseTypeActionExecuter.java @@ -1,87 +1,87 @@ -package org.alfresco.repo.action.executer; - -import java.util.List; - -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -/** - * Add features action executor implementation. - * - * @author Roy Wetherall - */ -public class SpecialiseTypeActionExecuter extends ActionExecuterAbstractBase -{ - /** - * Action constants - */ - public static final String NAME = "specialise-type"; - public static final String PARAM_TYPE_NAME = "type-name"; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * The dictionary service - */ - private DictionaryService dictionaryService; - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the dictionary service - * - * @param dictionaryService the dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) - */ - public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) - { - if (this.nodeService.exists(actionedUponNodeRef) == true) - { - // Get the type of the node - QName currentType = this.nodeService.getType(actionedUponNodeRef); - QName destinationType = (QName)ruleAction.getParameterValue(PARAM_TYPE_NAME); - - // Ensure that we are performing a specialise - if (currentType.equals(destinationType) == false && - this.dictionaryService.isSubClass(destinationType, currentType) == true) - { - // Specialise the type of the node - this.nodeService.setType(actionedUponNodeRef, destinationType); - } - } - } - - /** - * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_TYPE_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_TYPE_NAME), false, "ac-types")); - } - -} +package org.alfresco.repo.action.executer; + +import java.util.List; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Add features action executor implementation. + * + * @author Roy Wetherall + */ +public class SpecialiseTypeActionExecuter extends ActionExecuterAbstractBase +{ + /** + * Action constants + */ + public static final String NAME = "specialise-type"; + public static final String PARAM_TYPE_NAME = "type-name"; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * The dictionary service + */ + private DictionaryService dictionaryService; + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(Action, NodeRef) + */ + public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + if (this.nodeService.exists(actionedUponNodeRef) == true) + { + // Get the type of the node + QName currentType = this.nodeService.getType(actionedUponNodeRef); + QName destinationType = (QName)ruleAction.getParameterValue(PARAM_TYPE_NAME); + + // Ensure that we are performing a specialise + if (currentType.equals(destinationType) == false && + this.dictionaryService.isSubClass(destinationType, currentType) == true) + { + // Specialise the type of the node + this.nodeService.setType(actionedUponNodeRef, destinationType); + } + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_TYPE_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_TYPE_NAME), false, "ac-types")); + } + +} diff --git a/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java index edc44248af..c35ff66bf0 100644 --- a/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java @@ -1,211 +1,211 @@ -package org.alfresco.repo.activities.feed; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.activities.ActivityFeedEntity; -import org.alfresco.service.cmr.activities.ActivityService; -import org.alfresco.service.cmr.admin.RepoAdminService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.TemplateService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.namespace.NamespaceException; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.ModelUtil; -import org.alfresco.util.Pair; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONException; -import org.springframework.beans.factory.ObjectFactory; - -/** - * @since 4.0 - * - * @author Alex Miller - */ -public abstract class AbstractUserNotifier implements UserNotifier -{ - protected static Log logger = LogFactory.getLog(FeedNotifier.class); - - protected ActivityService activityService; - protected NamespaceService namespaceService; - protected RepoAdminService repoAdminService; - protected NodeService nodeService; - protected SiteService siteService; - protected ObjectFactory activitiesFeedModelBuilderFactory; - - public void setActivityService(ActivityService activityService) - { - this.activityService = activityService; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setRepoAdminService(RepoAdminService repoAdminService) - { - this.repoAdminService = repoAdminService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - - public void setActivitiesFeedModelBuilderFactory(ObjectFactory activitiesFeedModelBuilder) - { - this.activitiesFeedModelBuilderFactory = activitiesFeedModelBuilder; - } - - /** - * Perform basic checks to ensure that the necessary dependencies were injected. - */ - protected void checkProperties() - { - PropertyCheck.mandatory(this, "activitiesFeedModelBuilderFactory", activitiesFeedModelBuilderFactory); - PropertyCheck.mandatory(this, "activityService", activityService); - PropertyCheck.mandatory(this, "nodeService", nodeService); - PropertyCheck.mandatory(this, "namespaceService", namespaceService); - PropertyCheck.mandatory(this, "siteService", siteService); - } - - protected abstract boolean skipUser(NodeRef personNodeRef); - protected abstract Long getFeedId(NodeRef personNodeRef); - protected abstract void notifyUser(NodeRef personNodeRef, String subjectLine, Object[] subjectParams, Map model, String templateNodeRef); - - private void addSiteName(String siteId, Map siteNames) - { - if (siteId == null) - { - return; - } - - String siteName = siteNames.get(siteId); - if (siteName == null) - { - SiteInfo site = siteService.getSite(siteId); - if (site == null) - { - return; - } - - String siteTitle = site.getTitle(); - if (siteTitle != null && siteTitle.length() > 0) - { - siteName = siteTitle; - } - else - { - siteName = siteId; - } - - siteNames.put(siteId, siteName); - } - } - - public Pair notifyUser(final NodeRef personNodeRef, String subject, Object[] subjectParams, Map siteNames, - String shareUrl, int repeatIntervalMins, String templateNodeRef) - { - Map personProps = nodeService.getProperties(personNodeRef); - - String feedUserId = (String)personProps.get(ContentModel.PROP_USERNAME); - - if (skipUser(personNodeRef)) - { - // skip - return null; - } - - // where did we get up to ? - Long feedDBID = getFeedId(personNodeRef); - - // own + others (note: template can be changed to filter out user's own activities if needed) - if (logger.isDebugEnabled()) - { - logger.debug("Get user feed entries: " + feedUserId + ", " + feedDBID); - } - List feedEntries = activityService.getUserFeedEntries(feedUserId, null, false, false, null, null, feedDBID); - - if (feedEntries.size() > 0) - { - ActivitiesFeedModelBuilder modelBuilder; - try - { - modelBuilder = activitiesFeedModelBuilderFactory.getObject(); - } - catch (Exception error) - { - logger.warn("Unable to create model builder: " + error.getMessage()); - return null; - } - - for (ActivityFeedEntity feedEntry : feedEntries) - { - try - { - modelBuilder.addActivityFeedEntry(feedEntry); - - String siteId = feedEntry.getSiteNetwork(); - addSiteName(siteId, siteNames); - } - catch (JSONException je) - { - // skip this feed entry - logger.warn("Skip feed entry for user ("+feedUserId+"): " + je.getMessage()); - continue; - } - } - - final int activityCount = modelBuilder.activityCount(); - if (activityCount > 0) - { - Map model = modelBuilder.buildModel(); - - model.put("siteTitles", siteNames); - model.put("repeatIntervalMins", repeatIntervalMins); - model.put("feedItemsMax", activityService.getMaxFeedItems()); - - // add Share info to model - model.put(TemplateService.KEY_PRODUCT_NAME, ModelUtil.getProductName(repoAdminService)); - - Map personPrefixProps = new HashMap(personProps.size()); - for (QName propQName : personProps.keySet()) - { - try - { - String propPrefix = propQName.toPrefixString(namespaceService); - personPrefixProps.put(propPrefix, personProps.get(propQName)); - } - catch (NamespaceException ne) - { - // ignore properties that do not have a registered namespace - logger.warn("Ignoring property '" + propQName + "' as it's namespace is not registered"); - } - } - - model.put("personProps", personPrefixProps); - - // send - notifyUser(personNodeRef, subject, subjectParams, model, templateNodeRef); - - return new Pair(activityCount, modelBuilder.getMaxFeedId()); - } - } - - return null; - } -} +package org.alfresco.repo.activities.feed; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.activities.ActivityFeedEntity; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ModelUtil; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.springframework.beans.factory.ObjectFactory; + +/** + * @since 4.0 + * + * @author Alex Miller + */ +public abstract class AbstractUserNotifier implements UserNotifier +{ + protected static Log logger = LogFactory.getLog(FeedNotifier.class); + + protected ActivityService activityService; + protected NamespaceService namespaceService; + protected RepoAdminService repoAdminService; + protected NodeService nodeService; + protected SiteService siteService; + protected ObjectFactory activitiesFeedModelBuilderFactory; + + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setRepoAdminService(RepoAdminService repoAdminService) + { + this.repoAdminService = repoAdminService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setActivitiesFeedModelBuilderFactory(ObjectFactory activitiesFeedModelBuilder) + { + this.activitiesFeedModelBuilderFactory = activitiesFeedModelBuilder; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + protected void checkProperties() + { + PropertyCheck.mandatory(this, "activitiesFeedModelBuilderFactory", activitiesFeedModelBuilderFactory); + PropertyCheck.mandatory(this, "activityService", activityService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "namespaceService", namespaceService); + PropertyCheck.mandatory(this, "siteService", siteService); + } + + protected abstract boolean skipUser(NodeRef personNodeRef); + protected abstract Long getFeedId(NodeRef personNodeRef); + protected abstract void notifyUser(NodeRef personNodeRef, String subjectLine, Object[] subjectParams, Map model, String templateNodeRef); + + private void addSiteName(String siteId, Map siteNames) + { + if (siteId == null) + { + return; + } + + String siteName = siteNames.get(siteId); + if (siteName == null) + { + SiteInfo site = siteService.getSite(siteId); + if (site == null) + { + return; + } + + String siteTitle = site.getTitle(); + if (siteTitle != null && siteTitle.length() > 0) + { + siteName = siteTitle; + } + else + { + siteName = siteId; + } + + siteNames.put(siteId, siteName); + } + } + + public Pair notifyUser(final NodeRef personNodeRef, String subject, Object[] subjectParams, Map siteNames, + String shareUrl, int repeatIntervalMins, String templateNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + + String feedUserId = (String)personProps.get(ContentModel.PROP_USERNAME); + + if (skipUser(personNodeRef)) + { + // skip + return null; + } + + // where did we get up to ? + Long feedDBID = getFeedId(personNodeRef); + + // own + others (note: template can be changed to filter out user's own activities if needed) + if (logger.isDebugEnabled()) + { + logger.debug("Get user feed entries: " + feedUserId + ", " + feedDBID); + } + List feedEntries = activityService.getUserFeedEntries(feedUserId, null, false, false, null, null, feedDBID); + + if (feedEntries.size() > 0) + { + ActivitiesFeedModelBuilder modelBuilder; + try + { + modelBuilder = activitiesFeedModelBuilderFactory.getObject(); + } + catch (Exception error) + { + logger.warn("Unable to create model builder: " + error.getMessage()); + return null; + } + + for (ActivityFeedEntity feedEntry : feedEntries) + { + try + { + modelBuilder.addActivityFeedEntry(feedEntry); + + String siteId = feedEntry.getSiteNetwork(); + addSiteName(siteId, siteNames); + } + catch (JSONException je) + { + // skip this feed entry + logger.warn("Skip feed entry for user ("+feedUserId+"): " + je.getMessage()); + continue; + } + } + + final int activityCount = modelBuilder.activityCount(); + if (activityCount > 0) + { + Map model = modelBuilder.buildModel(); + + model.put("siteTitles", siteNames); + model.put("repeatIntervalMins", repeatIntervalMins); + model.put("feedItemsMax", activityService.getMaxFeedItems()); + + // add Share info to model + model.put(TemplateService.KEY_PRODUCT_NAME, ModelUtil.getProductName(repoAdminService)); + + Map personPrefixProps = new HashMap(personProps.size()); + for (QName propQName : personProps.keySet()) + { + try + { + String propPrefix = propQName.toPrefixString(namespaceService); + personPrefixProps.put(propPrefix, personProps.get(propQName)); + } + catch (NamespaceException ne) + { + // ignore properties that do not have a registered namespace + logger.warn("Ignoring property '" + propQName + "' as it's namespace is not registered"); + } + } + + model.put("personProps", personPrefixProps); + + // send + notifyUser(personNodeRef, subject, subjectParams, model, templateNodeRef); + + return new Pair(activityCount, modelBuilder.getMaxFeedId()); + } + } + + return null; + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java index f7fb7d369a..7b0c1e238b 100644 --- a/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java @@ -1,165 +1,165 @@ -package org.alfresco.repo.activities.feed; - -import java.io.Serializable; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.executer.MailActionExecuter; -import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.ParameterCheck; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.springframework.beans.factory.InitializingBean; - -/** - * Notifies the given user by sending activity feed information to their registered email address. - * - * @since 4.0 - */ -public class EmailUserNotifier extends AbstractUserNotifier implements InitializingBean -{ - private List excludedEmailSuffixes; - - private AuthenticationContext authenticationContext; - private ActionService actionService; - - public void setAuthenticationContext(AuthenticationContext authenticationContext) - { - this.authenticationContext = authenticationContext; - } - - public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } - - public static Log getLogger() - { - return logger; - } - - public static void setLogger(Log logger) - { - EmailUserNotifier.logger = logger; - } - - public List getExcludedEmailSuffixes() - { - return excludedEmailSuffixes; - } - - public void setExcludedEmailSuffixes(List excludedEmailSuffixes) - { - this.excludedEmailSuffixes = excludedEmailSuffixes; - } - - /** - * Perform basic checks to ensure that the necessary dependencies were injected. - */ - protected void checkProperties() - { - super.checkProperties(); - - PropertyCheck.mandatory(this, "authenticationContext", authenticationContext); - PropertyCheck.mandatory(this, "actionService", actionService); - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() throws Exception - { - checkProperties(); - } - - protected boolean skipUser(NodeRef personNodeRef) - { - Map personProps = nodeService.getProperties(personNodeRef); - String feedUserId = (String)personProps.get(ContentModel.PROP_USERNAME); - String emailAddress = (String)personProps.get(ContentModel.PROP_EMAIL); - Boolean emailFeedDisabled = (Boolean)personProps.get(ContentModel.PROP_EMAIL_FEED_DISABLED); - - if ((emailFeedDisabled != null) && (emailFeedDisabled == true)) - { - return true; - } - - if (authenticationContext.isSystemUserName(feedUserId) || authenticationContext.isGuestUserName(feedUserId)) - { - // skip "guest" or "System" user - return true; - } - - if ((emailAddress == null) || (emailAddress.length() <= 0)) - { - // skip user that does not have an email address - if (logger.isDebugEnabled()) - { - logger.debug("Skip for '"+feedUserId+"' since they have no email address set"); - } - return true; - } - - String lowerEmailAddress = emailAddress.toLowerCase(); - for (String excludedEmailSuffix : excludedEmailSuffixes) - { - if (lowerEmailAddress.endsWith(excludedEmailSuffix.toLowerCase())) - { - // skip user whose email matches exclude suffix - if (logger.isDebugEnabled()) - { - logger.debug("Skip for '"+feedUserId+"' since email address is excluded ("+emailAddress+")"); - } - return true; - } - } - - return false; - } - - protected Long getFeedId(NodeRef personNodeRef) - { - Map personProps = nodeService.getProperties(personNodeRef); - - // where did we get up to ? - Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); - if (emailFeedDBID != null) - { - // increment min feed id - emailFeedDBID++; - } - else - { - emailFeedDBID = -1L; - } - - return emailFeedDBID; - } - - protected void notifyUser(NodeRef personNodeRef, String subjectText, Object[] subjectParams, Map model, String templateNodeRef) - { - ParameterCheck.mandatory("personNodeRef", personNodeRef); - - Map personProps = nodeService.getProperties(personNodeRef); - String emailAddress = (String)personProps.get(ContentModel.PROP_EMAIL); - - Action mail = actionService.createAction(MailActionExecuter.NAME); - - mail.setParameterValue(MailActionExecuter.PARAM_TO, emailAddress); - mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subjectText); - mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT_PARAMS, subjectParams); - - //mail.setParameterValue(MailActionExecuter.PARAM_TEXT, buildMailText(emailTemplateRef, model)); - mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, templateNodeRef); - mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)model); - - actionService.executeAction(mail, null); - } - -} +package org.alfresco.repo.activities.feed; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.springframework.beans.factory.InitializingBean; + +/** + * Notifies the given user by sending activity feed information to their registered email address. + * + * @since 4.0 + */ +public class EmailUserNotifier extends AbstractUserNotifier implements InitializingBean +{ + private List excludedEmailSuffixes; + + private AuthenticationContext authenticationContext; + private ActionService actionService; + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + public static Log getLogger() + { + return logger; + } + + public static void setLogger(Log logger) + { + EmailUserNotifier.logger = logger; + } + + public List getExcludedEmailSuffixes() + { + return excludedEmailSuffixes; + } + + public void setExcludedEmailSuffixes(List excludedEmailSuffixes) + { + this.excludedEmailSuffixes = excludedEmailSuffixes; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + protected void checkProperties() + { + super.checkProperties(); + + PropertyCheck.mandatory(this, "authenticationContext", authenticationContext); + PropertyCheck.mandatory(this, "actionService", actionService); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + checkProperties(); + } + + protected boolean skipUser(NodeRef personNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + String feedUserId = (String)personProps.get(ContentModel.PROP_USERNAME); + String emailAddress = (String)personProps.get(ContentModel.PROP_EMAIL); + Boolean emailFeedDisabled = (Boolean)personProps.get(ContentModel.PROP_EMAIL_FEED_DISABLED); + + if ((emailFeedDisabled != null) && (emailFeedDisabled == true)) + { + return true; + } + + if (authenticationContext.isSystemUserName(feedUserId) || authenticationContext.isGuestUserName(feedUserId)) + { + // skip "guest" or "System" user + return true; + } + + if ((emailAddress == null) || (emailAddress.length() <= 0)) + { + // skip user that does not have an email address + if (logger.isDebugEnabled()) + { + logger.debug("Skip for '"+feedUserId+"' since they have no email address set"); + } + return true; + } + + String lowerEmailAddress = emailAddress.toLowerCase(); + for (String excludedEmailSuffix : excludedEmailSuffixes) + { + if (lowerEmailAddress.endsWith(excludedEmailSuffix.toLowerCase())) + { + // skip user whose email matches exclude suffix + if (logger.isDebugEnabled()) + { + logger.debug("Skip for '"+feedUserId+"' since email address is excluded ("+emailAddress+")"); + } + return true; + } + } + + return false; + } + + protected Long getFeedId(NodeRef personNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + + // where did we get up to ? + Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); + if (emailFeedDBID != null) + { + // increment min feed id + emailFeedDBID++; + } + else + { + emailFeedDBID = -1L; + } + + return emailFeedDBID; + } + + protected void notifyUser(NodeRef personNodeRef, String subjectText, Object[] subjectParams, Map model, String templateNodeRef) + { + ParameterCheck.mandatory("personNodeRef", personNodeRef); + + Map personProps = nodeService.getProperties(personNodeRef); + String emailAddress = (String)personProps.get(ContentModel.PROP_EMAIL); + + Action mail = actionService.createAction(MailActionExecuter.NAME); + + mail.setParameterValue(MailActionExecuter.PARAM_TO, emailAddress); + mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subjectText); + mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT_PARAMS, subjectParams); + + //mail.setParameterValue(MailActionExecuter.PARAM_TEXT, buildMailText(emailTemplateRef, model)); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, templateNodeRef); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)model); + + actionService.executeAction(mail, null); + } + +} diff --git a/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java b/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java index a5d49c4d34..4efed33b83 100644 --- a/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java +++ b/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java @@ -1,97 +1,97 @@ -package org.alfresco.repo.activities.feed; - -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.action.executer.TestModeable; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -public class ErrorProneActionExecutor extends ActionExecuterAbstractBase - implements InitializingBean, TestModeable -{ - private static Log logger = LogFactory.getLog(ErrorProneActionExecutor.class); - - public static final String PARAM_FAILING_PERSON_NODEREF = "failingPersonNodeRef"; - public static final String PARAM_PERSON_NODEREF = "personNodeRef"; - public static final String PARAM_USERNAME = "userName"; - - public static final String NAME = "errorProneActionExecutor"; - - // count of number of successful notifications - private AtomicInteger numSuccessful = new AtomicInteger(); - - // count of number of failed notifications - private AtomicInteger numFailed = new AtomicInteger(); - - public int getNumSuccess() - { - return numSuccessful.get(); - } - - public int getNumFailed() - { - return numFailed.get(); - } - - /** - * Send an email message - * - * @throws AlfrescoRuntimeException - */ - @Override - protected void executeImpl( - final Action ruleAction, - final NodeRef actionedUponNodeRef) - { - NodeRef failingPersonNodeRef = (NodeRef)ruleAction.getParameterValue(PARAM_FAILING_PERSON_NODEREF); - NodeRef personNodeRef = (NodeRef)ruleAction.getParameterValue(PARAM_PERSON_NODEREF); - String userName = (String)ruleAction.getParameterValue(PARAM_USERNAME); - - System.out.println("userName = " + userName); - - if(personNodeRef.equals(failingPersonNodeRef)) - { - numFailed.incrementAndGet(); - throw new AlfrescoRuntimeException(""); - } - - numSuccessful.incrementAndGet(); - } - - /** - * Add the parameter definitions - */ - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_FAILING_PERSON_NODEREF, DataTypeDefinition.NODE_REF, true, "Failing Person NodeRef")); - paramList.add(new ParameterDefinitionImpl(PARAM_PERSON_NODEREF, DataTypeDefinition.NODE_REF, true, "Person NodeRef")); - paramList.add(new ParameterDefinitionImpl(PARAM_USERNAME, DataTypeDefinition.TEXT, true, "Username")); - } - - @Override - public boolean isTestMode() - { - return true; - } - - @Override - public void setTestMode(boolean testMode) - { - } - - @Override - public void afterPropertiesSet() throws Exception - { - - } -} +package org.alfresco.repo.activities.feed; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.action.executer.TestModeable; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +public class ErrorProneActionExecutor extends ActionExecuterAbstractBase + implements InitializingBean, TestModeable +{ + private static Log logger = LogFactory.getLog(ErrorProneActionExecutor.class); + + public static final String PARAM_FAILING_PERSON_NODEREF = "failingPersonNodeRef"; + public static final String PARAM_PERSON_NODEREF = "personNodeRef"; + public static final String PARAM_USERNAME = "userName"; + + public static final String NAME = "errorProneActionExecutor"; + + // count of number of successful notifications + private AtomicInteger numSuccessful = new AtomicInteger(); + + // count of number of failed notifications + private AtomicInteger numFailed = new AtomicInteger(); + + public int getNumSuccess() + { + return numSuccessful.get(); + } + + public int getNumFailed() + { + return numFailed.get(); + } + + /** + * Send an email message + * + * @throws AlfrescoRuntimeException + */ + @Override + protected void executeImpl( + final Action ruleAction, + final NodeRef actionedUponNodeRef) + { + NodeRef failingPersonNodeRef = (NodeRef)ruleAction.getParameterValue(PARAM_FAILING_PERSON_NODEREF); + NodeRef personNodeRef = (NodeRef)ruleAction.getParameterValue(PARAM_PERSON_NODEREF); + String userName = (String)ruleAction.getParameterValue(PARAM_USERNAME); + + System.out.println("userName = " + userName); + + if(personNodeRef.equals(failingPersonNodeRef)) + { + numFailed.incrementAndGet(); + throw new AlfrescoRuntimeException(""); + } + + numSuccessful.incrementAndGet(); + } + + /** + * Add the parameter definitions + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_FAILING_PERSON_NODEREF, DataTypeDefinition.NODE_REF, true, "Failing Person NodeRef")); + paramList.add(new ParameterDefinitionImpl(PARAM_PERSON_NODEREF, DataTypeDefinition.NODE_REF, true, "Person NodeRef")); + paramList.add(new ParameterDefinitionImpl(PARAM_USERNAME, DataTypeDefinition.TEXT, true, "Username")); + } + + @Override + public boolean isTestMode() + { + return true; + } + + @Override + public void setTestMode(boolean testMode) + { + } + + @Override + public void afterPropertiesSet() throws Exception + { + + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java index afbba0f2a5..ec8037a707 100644 --- a/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java @@ -1,93 +1,93 @@ -package org.alfresco.repo.activities.feed; - -import java.io.Serializable; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; - -public class ErrorProneUserNotifier extends AbstractUserNotifier -{ -// private AtomicInteger numSuccessful = new AtomicInteger(); -// private AtomicInteger numFailed = new AtomicInteger(); - - private NodeRef failingPersonNodeRef; - private ActionService actionService; - - public ErrorProneUserNotifier(NodeRef failingPersonNodeRef) - { - this.failingPersonNodeRef = failingPersonNodeRef; - } - - public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } - - @Override - protected boolean skipUser(NodeRef personNodeRef) - { - return false; - } - - @Override - protected Long getFeedId(NodeRef personNodeRef) - { - Map personProps = nodeService.getProperties(personNodeRef); - - // where did we get up to ? - Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); - if (emailFeedDBID != null) - { - // increment min feed id - emailFeedDBID++; - } - else - { - emailFeedDBID = -1L; - } - - return emailFeedDBID; - } - -// public int getNumSuccess() -// { -// return numSuccessful.get(); -// } -// -// public int getNumFailed() -// { -// return numFailed.get(); -// } - - @Override - protected void notifyUser(NodeRef personNodeRef, String subjectText, Object[] subjectParams, - Map model, String templateNodeRef) - { -// super.notifyUser(personNodeRef, subjectText, model, templateNodeRef); - - String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); - - Action action = actionService.createAction(ErrorProneActionExecutor.NAME); - - action.setParameterValue(ErrorProneActionExecutor.PARAM_FAILING_PERSON_NODEREF, failingPersonNodeRef); - action.setParameterValue(ErrorProneActionExecutor.PARAM_PERSON_NODEREF, personNodeRef); - action.setParameterValue(ErrorProneActionExecutor.PARAM_USERNAME, userName); - - actionService.executeAction(action, null); -// String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); -// -// System.out.println("userName = " + userName); -// -// if(personNodeRef.equals(failingPersonNodeRef)) -// { -// numFailed.incrementAndGet(); -// throw new AlfrescoRuntimeException(""); -// } -// -// numSuccessful.incrementAndGet(); - } -} +package org.alfresco.repo.activities.feed; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +public class ErrorProneUserNotifier extends AbstractUserNotifier +{ +// private AtomicInteger numSuccessful = new AtomicInteger(); +// private AtomicInteger numFailed = new AtomicInteger(); + + private NodeRef failingPersonNodeRef; + private ActionService actionService; + + public ErrorProneUserNotifier(NodeRef failingPersonNodeRef) + { + this.failingPersonNodeRef = failingPersonNodeRef; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + @Override + protected boolean skipUser(NodeRef personNodeRef) + { + return false; + } + + @Override + protected Long getFeedId(NodeRef personNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + + // where did we get up to ? + Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); + if (emailFeedDBID != null) + { + // increment min feed id + emailFeedDBID++; + } + else + { + emailFeedDBID = -1L; + } + + return emailFeedDBID; + } + +// public int getNumSuccess() +// { +// return numSuccessful.get(); +// } +// +// public int getNumFailed() +// { +// return numFailed.get(); +// } + + @Override + protected void notifyUser(NodeRef personNodeRef, String subjectText, Object[] subjectParams, + Map model, String templateNodeRef) + { +// super.notifyUser(personNodeRef, subjectText, model, templateNodeRef); + + String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + + Action action = actionService.createAction(ErrorProneActionExecutor.NAME); + + action.setParameterValue(ErrorProneActionExecutor.PARAM_FAILING_PERSON_NODEREF, failingPersonNodeRef); + action.setParameterValue(ErrorProneActionExecutor.PARAM_PERSON_NODEREF, personNodeRef); + action.setParameterValue(ErrorProneActionExecutor.PARAM_USERNAME, userName); + + actionService.executeAction(action, null); +// String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); +// +// System.out.println("userName = " + userName); +// +// if(personNodeRef.equals(failingPersonNodeRef)) +// { +// numFailed.incrementAndGet(); +// throw new AlfrescoRuntimeException(""); +// } +// +// numSuccessful.incrementAndGet(); + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java index 43c5fffb34..ab28ab63e8 100644 --- a/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java @@ -1,92 +1,92 @@ -package org.alfresco.repo.activities.feed; - -import java.io.Serializable; -import java.util.BitSet; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; - -/** - * A test user notifier. - * - * @since 4.0 - */ -public class MockUserNotifier extends AbstractUserNotifier -{ - /** - * Default alfresco installation url - */ - private BitSet notifiedPersonsTracker = new BitSet(); - private AtomicInteger count = new AtomicInteger(0); - - @Override - protected boolean skipUser(NodeRef personNodeRef) - { - return false; - } - - @Override - protected Long getFeedId(NodeRef personNodeRef) - { - Map personProps = nodeService.getProperties(personNodeRef); - - // where did we get up to ? - Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); - if (emailFeedDBID != null) - { - // increment min feed id - emailFeedDBID++; - } - else - { - emailFeedDBID = -1L; - } - - return emailFeedDBID; - } - - @Override - protected void notifyUser(NodeRef personNodeRef, String subjectText, Object[] subjectParams, Map model, String templateNodeRef) - { - String username = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); - if(username.startsWith("user")) - { - int id = Integer.parseInt(username.substring(4)); - - boolean b = false; - synchronized(notifiedPersonsTracker) - { - b = notifiedPersonsTracker.get(id); - } - if(b) - { - System.out.println("Already set: " + id); - } - else - { - synchronized(notifiedPersonsTracker) - { - notifiedPersonsTracker.set(id); - } - } - } - - count.incrementAndGet(); - } - - public int countNotifications() - { - return count.get(); - } - - public int nextUserId() - { - synchronized(notifiedPersonsTracker) - { - return notifiedPersonsTracker.nextClearBit(1); - } - } -} +package org.alfresco.repo.activities.feed; + +import java.io.Serializable; +import java.util.BitSet; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * A test user notifier. + * + * @since 4.0 + */ +public class MockUserNotifier extends AbstractUserNotifier +{ + /** + * Default alfresco installation url + */ + private BitSet notifiedPersonsTracker = new BitSet(); + private AtomicInteger count = new AtomicInteger(0); + + @Override + protected boolean skipUser(NodeRef personNodeRef) + { + return false; + } + + @Override + protected Long getFeedId(NodeRef personNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + + // where did we get up to ? + Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); + if (emailFeedDBID != null) + { + // increment min feed id + emailFeedDBID++; + } + else + { + emailFeedDBID = -1L; + } + + return emailFeedDBID; + } + + @Override + protected void notifyUser(NodeRef personNodeRef, String subjectText, Object[] subjectParams, Map model, String templateNodeRef) + { + String username = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + if(username.startsWith("user")) + { + int id = Integer.parseInt(username.substring(4)); + + boolean b = false; + synchronized(notifiedPersonsTracker) + { + b = notifiedPersonsTracker.get(id); + } + if(b) + { + System.out.println("Already set: " + id); + } + else + { + synchronized(notifiedPersonsTracker) + { + notifiedPersonsTracker.set(id); + } + } + } + + count.incrementAndGet(); + } + + public int countNotifications() + { + return count.get(); + } + + public int nextUserId() + { + synchronized(notifiedPersonsTracker) + { + return notifiedPersonsTracker.nextClearBit(1); + } + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/UserNotifier.java b/source/java/org/alfresco/repo/activities/feed/UserNotifier.java index f80bf86125..36c9266e65 100644 --- a/source/java/org/alfresco/repo/activities/feed/UserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/UserNotifier.java @@ -1,17 +1,17 @@ -package org.alfresco.repo.activities.feed; - -import java.util.Map; - -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.util.Pair; - -/** - * Notifies the given user by sending their activity feed information to their email address (or potentially some other destination) - * - * @since 4.0 - */ -public interface UserNotifier -{ - public Pair notifyUser(final NodeRef personNodeRef, String subjectText, Object[] subjectParams, Map siteNames, - String shareUrl, int repeatIntervalMins, String templateNodeRef); -} +package org.alfresco.repo.activities.feed; + +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.Pair; + +/** + * Notifies the given user by sending their activity feed information to their email address (or potentially some other destination) + * + * @since 4.0 + */ +public interface UserNotifier +{ + public Pair notifyUser(final NodeRef personNodeRef, String subjectText, Object[] subjectParams, Map siteNames, + String shareUrl, int repeatIntervalMins, String templateNodeRef); +} diff --git a/source/java/org/alfresco/repo/admin/DummyIndexConfigurationCheckerImpl.java b/source/java/org/alfresco/repo/admin/DummyIndexConfigurationCheckerImpl.java index 7ec52f8df8..6a1b9aee68 100644 --- a/source/java/org/alfresco/repo/admin/DummyIndexConfigurationCheckerImpl.java +++ b/source/java/org/alfresco/repo/admin/DummyIndexConfigurationCheckerImpl.java @@ -1,24 +1,24 @@ -package org.alfresco.repo.admin; - -import java.util.Collections; -import java.util.List; - -import org.alfresco.service.cmr.repository.StoreRef; - -/** - * @author Andy - * - */ -public class DummyIndexConfigurationCheckerImpl implements IndexConfigurationChecker -{ - - /* (non-Javadoc) - * @see org.alfresco.repo.admin.IndexConfigurationChecker#checkIndexConfiguration() - */ - @Override - public List checkIndexConfiguration() - { - return Collections.emptyList(); - } - -} +package org.alfresco.repo.admin; + +import java.util.Collections; +import java.util.List; + +import org.alfresco.service.cmr.repository.StoreRef; + +/** + * @author Andy + * + */ +public class DummyIndexConfigurationCheckerImpl implements IndexConfigurationChecker +{ + + /* (non-Javadoc) + * @see org.alfresco.repo.admin.IndexConfigurationChecker#checkIndexConfiguration() + */ + @Override + public List checkIndexConfiguration() + { + return Collections.emptyList(); + } + +} diff --git a/source/java/org/alfresco/repo/admin/IndexConfigurationChecker.java b/source/java/org/alfresco/repo/admin/IndexConfigurationChecker.java index 508ee86d5f..c05127906a 100644 --- a/source/java/org/alfresco/repo/admin/IndexConfigurationChecker.java +++ b/source/java/org/alfresco/repo/admin/IndexConfigurationChecker.java @@ -1,18 +1,18 @@ -package org.alfresco.repo.admin; - -import java.util.List; - -import org.alfresco.service.cmr.repository.StoreRef; - -/** - * @author Andy - * - */ -public interface IndexConfigurationChecker -{ - /** - * Check that the index contains root entries for all the stores that would be expected - * @return - the stores with missing indexes - */ - public List checkIndexConfiguration(); -} +package org.alfresco.repo.admin; + +import java.util.List; + +import org.alfresco.service.cmr.repository.StoreRef; + +/** + * @author Andy + * + */ +public interface IndexConfigurationChecker +{ + /** + * Check that the index contains root entries for all the stores that would be expected + * @return - the stores with missing indexes + */ + public List checkIndexConfiguration(); +} diff --git a/source/java/org/alfresco/repo/admin/IndexConfigurationCheckerBootstrapBean.java b/source/java/org/alfresco/repo/admin/IndexConfigurationCheckerBootstrapBean.java index e0bde66a63..e4a0acfc8a 100644 --- a/source/java/org/alfresco/repo/admin/IndexConfigurationCheckerBootstrapBean.java +++ b/source/java/org/alfresco/repo/admin/IndexConfigurationCheckerBootstrapBean.java @@ -1,128 +1,128 @@ -package org.alfresco.repo.admin; - -import java.io.File; -import java.util.List; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; -import org.springframework.extensions.surf.util.I18NUtil; - -public class IndexConfigurationCheckerBootstrapBean extends AbstractLifecycleBean -{ - private static Log logger = LogFactory.getLog(IndexConfigurationCheckerBootstrapBean.class); - - private IndexConfigurationChecker indexConfigurationChecker; - - private TransactionService transactionService; - - private boolean strict; - - private String dirRoot; - - @Override - protected void onBootstrap(ApplicationEvent event) - { - RetryingTransactionCallback checkWork = new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - // reindex - - log.info("Checking/Recovering indexes ..."); - check(); - - return null; - } - }; - transactionService.getRetryingTransactionHelper().doInTransaction(checkWork, true); - - - - } - - private void check() - { - if (logger.isDebugEnabled()) - { - logger.debug("Starting index configuration check: " + this); - } - - - File dirRootFile = new File(dirRoot); - - - List missingIndexStoreRefs = indexConfigurationChecker.checkIndexConfiguration(); - - // check for missing indexes - int missingStoreIndexes = missingIndexStoreRefs.size(); - if (missingStoreIndexes > 0) - { - String msg = I18NUtil.getMessage(ConfigurationChecker.ERR_MISSING_INDEXES, missingStoreIndexes); - logger.error(msg); - String msgRecover = I18NUtil.getMessage(ConfigurationChecker.MSG_HOWTO_INDEX_RECOVER); - logger.info(msgRecover); - } - - // handle either content or indexes missing - if (missingStoreIndexes > 0) - { - String msg = I18NUtil.getMessage(ConfigurationChecker.ERR_FIX_DIR_ROOT, dirRootFile); - logger.error(msg); - - // Now determine the failure behaviour - if (strict) - { - throw new AlfrescoRuntimeException(msg); - } - else - { - String warn = I18NUtil.getMessage(ConfigurationChecker.WARN_STARTING_WITH_ERRORS); - logger.warn(warn); - } - } - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - // Nothing to do - } - - - - public IndexConfigurationChecker getIndexConfigurationChecker() - { - return indexConfigurationChecker; - } - - public void setIndexConfigurationChecker(IndexConfigurationChecker indexConfigurationChecker) - { - this.indexConfigurationChecker = indexConfigurationChecker; - } - - public void setStrict(boolean strict) - { - this.strict = strict; - } - - public void setDirRoot(String dirRoot) - { - this.dirRoot = dirRoot; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - -} +package org.alfresco.repo.admin; + +import java.io.File; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.extensions.surf.util.I18NUtil; + +public class IndexConfigurationCheckerBootstrapBean extends AbstractLifecycleBean +{ + private static Log logger = LogFactory.getLog(IndexConfigurationCheckerBootstrapBean.class); + + private IndexConfigurationChecker indexConfigurationChecker; + + private TransactionService transactionService; + + private boolean strict; + + private String dirRoot; + + @Override + protected void onBootstrap(ApplicationEvent event) + { + RetryingTransactionCallback checkWork = new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + // reindex + + log.info("Checking/Recovering indexes ..."); + check(); + + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(checkWork, true); + + + + } + + private void check() + { + if (logger.isDebugEnabled()) + { + logger.debug("Starting index configuration check: " + this); + } + + + File dirRootFile = new File(dirRoot); + + + List missingIndexStoreRefs = indexConfigurationChecker.checkIndexConfiguration(); + + // check for missing indexes + int missingStoreIndexes = missingIndexStoreRefs.size(); + if (missingStoreIndexes > 0) + { + String msg = I18NUtil.getMessage(ConfigurationChecker.ERR_MISSING_INDEXES, missingStoreIndexes); + logger.error(msg); + String msgRecover = I18NUtil.getMessage(ConfigurationChecker.MSG_HOWTO_INDEX_RECOVER); + logger.info(msgRecover); + } + + // handle either content or indexes missing + if (missingStoreIndexes > 0) + { + String msg = I18NUtil.getMessage(ConfigurationChecker.ERR_FIX_DIR_ROOT, dirRootFile); + logger.error(msg); + + // Now determine the failure behaviour + if (strict) + { + throw new AlfrescoRuntimeException(msg); + } + else + { + String warn = I18NUtil.getMessage(ConfigurationChecker.WARN_STARTING_WITH_ERRORS); + logger.warn(warn); + } + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // Nothing to do + } + + + + public IndexConfigurationChecker getIndexConfigurationChecker() + { + return indexConfigurationChecker; + } + + public void setIndexConfigurationChecker(IndexConfigurationChecker indexConfigurationChecker) + { + this.indexConfigurationChecker = indexConfigurationChecker; + } + + public void setStrict(boolean strict) + { + this.strict = strict; + } + + public void setDirRoot(String dirRoot) + { + this.dirRoot = dirRoot; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + +} diff --git a/source/java/org/alfresco/repo/admin/IndexConfigurationCheckerImpl.java b/source/java/org/alfresco/repo/admin/IndexConfigurationCheckerImpl.java index 68bedc6dda..0cd35686a5 100644 --- a/source/java/org/alfresco/repo/admin/IndexConfigurationCheckerImpl.java +++ b/source/java/org/alfresco/repo/admin/IndexConfigurationCheckerImpl.java @@ -1,176 +1,176 @@ -package org.alfresco.repo.admin; - -import java.util.ArrayList; -import java.util.List; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.node.index.FullIndexRecoveryComponent.RecoveryMode; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.InvalidStoreRefException; -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.SearchParameters; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * @author Andy - * - */ -public class IndexConfigurationCheckerImpl implements IndexConfigurationChecker -{ - private static Log logger = LogFactory.getLog(IndexConfigurationCheckerImpl.class); - - private static final String ERR_DUPLICATE_ROOT_NODE = "system.config_check.err.indexes.duplicate_root_node"; - - private RecoveryMode indexRecoveryMode; - private NodeService nodeService; - private SearchService searchService; - - /** - * Set the index recovert mode - * @param indexRecoveryMode RecoveryMode - */ - public void setIndexRecoveryMode(RecoveryMode indexRecoveryMode) - { - this.indexRecoveryMode = indexRecoveryMode; - } - - - - /** - * Set the node service - * @param nodeService NodeService - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - - /** - * Set the search service - * @param searchService SearchService - */ - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - @Override - public List checkIndexConfiguration() - { - // get all root nodes from the NodeService, i.e. database - List storeRefs = nodeService.getStores(); - List missingIndexStoreRefs = new ArrayList(0); - for (StoreRef storeRef : storeRefs) - { - NodeRef rootNodeRef = null; - try - { - rootNodeRef = nodeService.getRootNode(storeRef); - } - catch (InvalidStoreRefException e) - { - // the store is invalid and will therefore not have a root node entry - continue; - } - - // Are we creating the store - in which case we do not check - // See MNT-11612 - int countChildAssoc = 0; - if (storeRef.getProtocol().equals(StoreRef.PROTOCOL_AVM)) - { - // AVM does not support nodeService.countChildAssocs() - long start = 0; - if (logger.isDebugEnabled()) - { - logger.debug("Counting childAssocs for store: " + storeRef); - start = System.currentTimeMillis(); - } - List childAssocs = nodeService.getChildAssocs(rootNodeRef, - RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, 1, false); - countChildAssoc = childAssocs.size(); - if (logger.isDebugEnabled()) - { - logger.debug("Time for counting childAssocs for : " + storeRef + " time=" - + (System.currentTimeMillis() - start)); - } - } - else - { - long start = 0; - if (logger.isDebugEnabled()) - { - logger.debug("Counting childAssocs for store: " + storeRef); - start = System.currentTimeMillis(); - } - countChildAssoc = nodeService.countChildAssocs(rootNodeRef, true); - if (logger.isDebugEnabled()) - { - logger.debug("Time for counting childAssocs for : " + storeRef + " time=" - + (System.currentTimeMillis() - start)); - } - } - if (logger.isDebugEnabled()) - { - logger.debug("Counting childAssocs for store: " + storeRef + " countChildAssoc = " + countChildAssoc); - } - if (countChildAssoc == 0) - { - continue; - } - - if (indexRecoveryMode != RecoveryMode.FULL) - { - if (logger.isDebugEnabled()) - { - logger.debug("Checking index for store: " + storeRef); - } - - // perform a Lucene query for the root node - SearchParameters sp = new SearchParameters(); - sp.addStore(storeRef); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("ISROOT:T"); - - ResultSet results = null; - int size = 0; - try - { - results = searchService.query(sp); - size = results.length(); - } - finally - { - try { results.close(); } catch (Throwable e) {} - } - - if (size == 0) - { - // indexes missing for root node - missingIndexStoreRefs.add(storeRef); - // debug - if (logger.isDebugEnabled()) - { - logger.debug("Index missing for store: \n" + - " store: " + storeRef); - } - } - else if (size > 1) - { - // there are duplicates - String msg = I18NUtil.getMessage(ERR_DUPLICATE_ROOT_NODE, storeRef); - throw new AlfrescoRuntimeException(msg); - } - } - } - return missingIndexStoreRefs; - } - -} +package org.alfresco.repo.admin; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.node.index.FullIndexRecoveryComponent.RecoveryMode; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.InvalidStoreRefException; +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.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * @author Andy + * + */ +public class IndexConfigurationCheckerImpl implements IndexConfigurationChecker +{ + private static Log logger = LogFactory.getLog(IndexConfigurationCheckerImpl.class); + + private static final String ERR_DUPLICATE_ROOT_NODE = "system.config_check.err.indexes.duplicate_root_node"; + + private RecoveryMode indexRecoveryMode; + private NodeService nodeService; + private SearchService searchService; + + /** + * Set the index recovert mode + * @param indexRecoveryMode RecoveryMode + */ + public void setIndexRecoveryMode(RecoveryMode indexRecoveryMode) + { + this.indexRecoveryMode = indexRecoveryMode; + } + + + + /** + * Set the node service + * @param nodeService NodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + + /** + * Set the search service + * @param searchService SearchService + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + @Override + public List checkIndexConfiguration() + { + // get all root nodes from the NodeService, i.e. database + List storeRefs = nodeService.getStores(); + List missingIndexStoreRefs = new ArrayList(0); + for (StoreRef storeRef : storeRefs) + { + NodeRef rootNodeRef = null; + try + { + rootNodeRef = nodeService.getRootNode(storeRef); + } + catch (InvalidStoreRefException e) + { + // the store is invalid and will therefore not have a root node entry + continue; + } + + // Are we creating the store - in which case we do not check + // See MNT-11612 + int countChildAssoc = 0; + if (storeRef.getProtocol().equals(StoreRef.PROTOCOL_AVM)) + { + // AVM does not support nodeService.countChildAssocs() + long start = 0; + if (logger.isDebugEnabled()) + { + logger.debug("Counting childAssocs for store: " + storeRef); + start = System.currentTimeMillis(); + } + List childAssocs = nodeService.getChildAssocs(rootNodeRef, + RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, 1, false); + countChildAssoc = childAssocs.size(); + if (logger.isDebugEnabled()) + { + logger.debug("Time for counting childAssocs for : " + storeRef + " time=" + + (System.currentTimeMillis() - start)); + } + } + else + { + long start = 0; + if (logger.isDebugEnabled()) + { + logger.debug("Counting childAssocs for store: " + storeRef); + start = System.currentTimeMillis(); + } + countChildAssoc = nodeService.countChildAssocs(rootNodeRef, true); + if (logger.isDebugEnabled()) + { + logger.debug("Time for counting childAssocs for : " + storeRef + " time=" + + (System.currentTimeMillis() - start)); + } + } + if (logger.isDebugEnabled()) + { + logger.debug("Counting childAssocs for store: " + storeRef + " countChildAssoc = " + countChildAssoc); + } + if (countChildAssoc == 0) + { + continue; + } + + if (indexRecoveryMode != RecoveryMode.FULL) + { + if (logger.isDebugEnabled()) + { + logger.debug("Checking index for store: " + storeRef); + } + + // perform a Lucene query for the root node + SearchParameters sp = new SearchParameters(); + sp.addStore(storeRef); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("ISROOT:T"); + + ResultSet results = null; + int size = 0; + try + { + results = searchService.query(sp); + size = results.length(); + } + finally + { + try { results.close(); } catch (Throwable e) {} + } + + if (size == 0) + { + // indexes missing for root node + missingIndexStoreRefs.add(storeRef); + // debug + if (logger.isDebugEnabled()) + { + logger.debug("Index missing for store: \n" + + " store: " + storeRef); + } + } + else if (size > 1) + { + // there are duplicates + String msg = I18NUtil.getMessage(ERR_DUPLICATE_ROOT_NODE, storeRef); + throw new AlfrescoRuntimeException(msg); + } + } + } + return missingIndexStoreRefs; + } + +} diff --git a/source/java/org/alfresco/repo/admin/RepositoryEndBootstrapBean.java b/source/java/org/alfresco/repo/admin/RepositoryEndBootstrapBean.java index 98ace7f037..8431c797e6 100644 --- a/source/java/org/alfresco/repo/admin/RepositoryEndBootstrapBean.java +++ b/source/java/org/alfresco/repo/admin/RepositoryEndBootstrapBean.java @@ -1,36 +1,36 @@ -package org.alfresco.repo.admin; - -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; - -/** - * Track repo bootstrap so sub systems do not duplciate stuff or do it too early ... eg index rebuild/check - * - * @author andyh - */ -public class RepositoryEndBootstrapBean extends AbstractLifecycleBean -{ - private RepositoryState repositoryState; - - public RepositoryState getRepositoryState() - { - return repositoryState; - } - - public void setRepositoryState(RepositoryState repositoryState) - { - this.repositoryState = repositoryState; - } - - @Override - protected void onBootstrap(ApplicationEvent event) - { - repositoryState.setBootstrapping(false); - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - // NOOP - } +package org.alfresco.repo.admin; + +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * Track repo bootstrap so sub systems do not duplciate stuff or do it too early ... eg index rebuild/check + * + * @author andyh + */ +public class RepositoryEndBootstrapBean extends AbstractLifecycleBean +{ + private RepositoryState repositoryState; + + public RepositoryState getRepositoryState() + { + return repositoryState; + } + + public void setRepositoryState(RepositoryState repositoryState) + { + this.repositoryState = repositoryState; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + repositoryState.setBootstrapping(false); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/admin/RepositoryStartBootstrapBean.java b/source/java/org/alfresco/repo/admin/RepositoryStartBootstrapBean.java index 167794193e..30bbce67e0 100644 --- a/source/java/org/alfresco/repo/admin/RepositoryStartBootstrapBean.java +++ b/source/java/org/alfresco/repo/admin/RepositoryStartBootstrapBean.java @@ -1,36 +1,36 @@ -package org.alfresco.repo.admin; - -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; - -/** - * Track repo bootstrap so sub systems do not duplciate stuff or do it too early ... eg index rebuild/check - * - * @author andyh - */ -public class RepositoryStartBootstrapBean extends AbstractLifecycleBean -{ - private RepositoryState repositoryState; - - public RepositoryState getRepositoryState() - { - return repositoryState; - } - - public void setRepositoryState(RepositoryState repositoryState) - { - this.repositoryState = repositoryState; - } - - @Override - protected void onBootstrap(ApplicationEvent event) - { - repositoryState.setBootstrapping(true); - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - // NOOP - } +package org.alfresco.repo.admin; + +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * Track repo bootstrap so sub systems do not duplciate stuff or do it too early ... eg index rebuild/check + * + * @author andyh + */ +public class RepositoryStartBootstrapBean extends AbstractLifecycleBean +{ + private RepositoryState repositoryState; + + public RepositoryState getRepositoryState() + { + return repositoryState; + } + + public void setRepositoryState(RepositoryState repositoryState) + { + this.repositoryState = repositoryState; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + repositoryState.setBootstrapping(true); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/admin/RepositoryState.java b/source/java/org/alfresco/repo/admin/RepositoryState.java index f75213728c..f4b44fed12 100644 --- a/source/java/org/alfresco/repo/admin/RepositoryState.java +++ b/source/java/org/alfresco/repo/admin/RepositoryState.java @@ -1,48 +1,48 @@ -package org.alfresco.repo.admin; - -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * A class that maintains a thread-safe ready indicator on the current bootstrap state of the repository. - * - * @author Andy - * - */ -public class RepositoryState -{ - private boolean bootstrapping; - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - /** - * Determine if the repository is ready to use. - * - * @return true if the repository bootstrap process is still going, - * or false if the repository is ready to use - */ - public boolean isBootstrapping() - { - this.lock.readLock().lock(); - try - { - return bootstrapping; - } - finally - { - this.lock.readLock().unlock(); - } - } - - public void setBootstrapping(boolean bootstrapping) - { - this.lock.writeLock().lock(); - try - { - this.bootstrapping = bootstrapping; - } - finally - { - this.lock.writeLock().unlock(); - } - } - -} +package org.alfresco.repo.admin; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * A class that maintains a thread-safe ready indicator on the current bootstrap state of the repository. + * + * @author Andy + * + */ +public class RepositoryState +{ + private boolean bootstrapping; + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + /** + * Determine if the repository is ready to use. + * + * @return true if the repository bootstrap process is still going, + * or false if the repository is ready to use + */ + public boolean isBootstrapping() + { + this.lock.readLock().lock(); + try + { + return bootstrapping; + } + finally + { + this.lock.readLock().unlock(); + } + } + + public void setBootstrapping(boolean bootstrapping) + { + this.lock.writeLock().lock(); + try + { + this.bootstrapping = bootstrapping; + } + finally + { + this.lock.writeLock().unlock(); + } + } + +} diff --git a/source/java/org/alfresco/repo/admin/patch/OptionalPatchApplicationCheckBootstrapBean.java b/source/java/org/alfresco/repo/admin/patch/OptionalPatchApplicationCheckBootstrapBean.java index b46654257a..c6c2b7a39d 100644 --- a/source/java/org/alfresco/repo/admin/patch/OptionalPatchApplicationCheckBootstrapBean.java +++ b/source/java/org/alfresco/repo/admin/patch/OptionalPatchApplicationCheckBootstrapBean.java @@ -1,100 +1,100 @@ -package org.alfresco.repo.admin.patch; - -import org.alfresco.service.descriptor.Descriptor; -import org.alfresco.service.descriptor.DescriptorService; -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; - -/** - * @author Andy - */ -public class OptionalPatchApplicationCheckBootstrapBean extends AbstractLifecycleBean -{ - PatchService patchService; - - Patch patch; - - DescriptorService descriptorService; - - volatile boolean patchApplied = false; - - /** - * @param patchService - * the patchService to set - */ - public void setPatchService(PatchService patchService) - { - this.patchService = patchService; - } - - /** - * @param patch - * the patch to set - */ - public void setPatch(Patch patch) - { - this.patch = patch; - } - - /** - * @param descriptorService - * the descriptorService to set - */ - public void setDescriptorService(DescriptorService descriptorService) - { - this.descriptorService = descriptorService; - } - - /* - * (non-Javadoc) - * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.context. - * ApplicationEvent) - */ - @Override - protected void onBootstrap(ApplicationEvent event) - { - Descriptor descriptor = descriptorService.getInstalledRepositoryDescriptor(); - if (patch == null) - { - patchApplied = true; - } - else - { - AppliedPatch appliedPatch = patchService.getPatch(patch.getId()); - if (appliedPatch == null) - { - patchApplied = patch.getFixesToSchema() < descriptor.getSchema(); - } - else - { - patchApplied = appliedPatch.getSucceeded(); - } - } - } - - /* - * (non-Javadoc) - * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.context. - * ApplicationEvent) - */ - @Override - protected void onShutdown(ApplicationEvent event) - { - - } - - /** - * Was the patch applied - or was it not applied - * - * @return boolean - */ - public boolean getPatchApplied() - { - return patchApplied; - } - - public String getPatchId() - { - return patch.getId(); - } -} +package org.alfresco.repo.admin.patch; + +import org.alfresco.service.descriptor.Descriptor; +import org.alfresco.service.descriptor.DescriptorService; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * @author Andy + */ +public class OptionalPatchApplicationCheckBootstrapBean extends AbstractLifecycleBean +{ + PatchService patchService; + + Patch patch; + + DescriptorService descriptorService; + + volatile boolean patchApplied = false; + + /** + * @param patchService + * the patchService to set + */ + public void setPatchService(PatchService patchService) + { + this.patchService = patchService; + } + + /** + * @param patch + * the patch to set + */ + public void setPatch(Patch patch) + { + this.patch = patch; + } + + /** + * @param descriptorService + * the descriptorService to set + */ + public void setDescriptorService(DescriptorService descriptorService) + { + this.descriptorService = descriptorService; + } + + /* + * (non-Javadoc) + * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.context. + * ApplicationEvent) + */ + @Override + protected void onBootstrap(ApplicationEvent event) + { + Descriptor descriptor = descriptorService.getInstalledRepositoryDescriptor(); + if (patch == null) + { + patchApplied = true; + } + else + { + AppliedPatch appliedPatch = patchService.getPatch(patch.getId()); + if (appliedPatch == null) + { + patchApplied = patch.getFixesToSchema() < descriptor.getSchema(); + } + else + { + patchApplied = appliedPatch.getSucceeded(); + } + } + } + + /* + * (non-Javadoc) + * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.context. + * ApplicationEvent) + */ + @Override + protected void onShutdown(ApplicationEvent event) + { + + } + + /** + * Was the patch applied - or was it not applied + * + * @return boolean + */ + public boolean getPatchApplied() + { + return patchApplied; + } + + public String getPatchId() + { + return patch.getId(); + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/SimplePatch.java b/source/java/org/alfresco/repo/admin/patch/SimplePatch.java index 458f5ee6f0..321faf9667 100644 --- a/source/java/org/alfresco/repo/admin/patch/SimplePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/SimplePatch.java @@ -1,52 +1,52 @@ -package org.alfresco.repo.admin.patch; - -import org.alfresco.service.transaction.TransactionService; - -public class SimplePatch extends AbstractPatch -{ - public static final String MSG_SUCCESS = "SimplePatch applied successfully"; - - /** - * Default constructor for Spring config - */ - public SimplePatch() - { - } - - /** - * Overrides the base class version to do nothing, i.e. it does not self-register - */ - @Override - public void init() - { - } - - /** - * Helper constructor for some tests. Default properties are set automatically. - * - * @param transactionService TransactionService - * @param requiresTransaction true if transaction required - */ - /* protected */ SimplePatch(TransactionService transactionService, boolean requiresTransaction) - { - setTransactionService(transactionService); - setId("SimplePatch"); - setDescription("This is a simple patch"); - setFixesFromSchema(0); - setFixesToSchema(1000); - setTargetSchema(1001); - setRequiresTransaction(requiresTransaction); - } - - /** - * Does nothing - * - * @return Returns a success or failure message dependent on the constructor used - */ - @Override - protected String applyInternal() throws Exception - { - return MSG_SUCCESS; - } - -} +package org.alfresco.repo.admin.patch; + +import org.alfresco.service.transaction.TransactionService; + +public class SimplePatch extends AbstractPatch +{ + public static final String MSG_SUCCESS = "SimplePatch applied successfully"; + + /** + * Default constructor for Spring config + */ + public SimplePatch() + { + } + + /** + * Overrides the base class version to do nothing, i.e. it does not self-register + */ + @Override + public void init() + { + } + + /** + * Helper constructor for some tests. Default properties are set automatically. + * + * @param transactionService TransactionService + * @param requiresTransaction true if transaction required + */ + /* protected */ SimplePatch(TransactionService transactionService, boolean requiresTransaction) + { + setTransactionService(transactionService); + setId("SimplePatch"); + setDescription("This is a simple patch"); + setFixesFromSchema(0); + setFixesToSchema(1000); + setTargetSchema(1001); + setRequiresTransaction(requiresTransaction); + } + + /** + * Does nothing + * + * @return Returns a success or failure message dependent on the constructor used + */ + @Override + protected String applyInternal() throws Exception + { + return MSG_SUCCESS; + } + +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AliasableAspectPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AliasableAspectPatch.java index dc92dff6bd..91f746127e 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/AliasableAspectPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/AliasableAspectPatch.java @@ -1,245 +1,245 @@ -package org.alfresco.repo.admin.patch.impl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.alfresco.email.server.AliasableAspect; -import org.alfresco.email.server.EmailServerModel; -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.repo.batch.BatchProcessWorkProvider; -import org.alfresco.repo.batch.BatchProcessor; -import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker; -import org.alfresco.repo.domain.node.NodeDAO; -import org.alfresco.repo.domain.patch.PatchDAO; -import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.service.cmr.attributes.AttributeService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.Pair; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * Patch to duplicate the AliasableAspect into the attributes service. - * - * Inbound email. - * - * @author mrogers - * - */ -public class AliasableAspectPatch extends AbstractPatch -{ - private static final String MSG_SUCCESS = "patch.emailAliasableAspect.result"; - - private AttributeService attributeService; - private NodeDAO nodeDAO; - private PatchDAO patchDAO; - private QNameDAO qnameDAO; - private BehaviourFilter behaviourFilter; - - private final int batchThreads = 3; - private final int batchSize = 40; - private final long count = batchThreads * batchSize; - - private static Log logger = LogFactory.getLog(AliasableAspectPatch.class); - - - @Override - protected String applyInternal() throws Exception - { - BatchProcessWorkProvider workProvider = new BatchProcessWorkProvider() - { - final List result = new ArrayList(); - - Long aspectQNameId = 0L; - long maxNodeId = getPatchDAO().getMaxAdmNodeID(); - - long minSearchNodeId = 1; - long maxSearchNodeId = count; - - Pair val = getQnameDAO().getQName(EmailServerModel.ASPECT_ALIASABLE ); - - public int getTotalEstimatedWorkSize() - { - return result.size(); - } - - public Collection getNextWork() - { - if(val != null) - { - Long aspectQNameId = val.getFirst(); - - result.clear(); - - while (result.isEmpty() && minSearchNodeId < maxNodeId) - { - List nodeids = getPatchDAO().getNodesByAspectQNameId(aspectQNameId, minSearchNodeId, maxSearchNodeId); - - for(Long nodeid : nodeids) - { - NodeRef.Status status = getNodeDAO().getNodeIdStatus(nodeid); - if(!status.isDeleted()) - { - result.add(status.getNodeRef()); - } - } - minSearchNodeId = minSearchNodeId + count; - maxSearchNodeId = maxSearchNodeId + count; - } - } - - return result; - } - }; - - RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); - // Configure the helper to run in read-only mode - // MNT-10764 - txnHelper.setForceWritable(true); - - BatchProcessor batchProcessor = new BatchProcessor( - "AliasableAspectPatch", - txnHelper, - workProvider, - batchThreads, - batchSize, - applicationEventPublisher, - logger, - 1000); - - BatchProcessWorker worker = new BatchProcessWorker() - { - - public void afterProcess() throws Throwable - { - } - - public void beforeProcess() throws Throwable - { - } - - public String getIdentifier(NodeRef entry) - { - return entry.toString(); - } - - public void process(NodeRef entry) throws Throwable - { - String alias = (String)nodeService.getProperty(entry, EmailServerModel.PROP_ALIAS); - if(alias != null) - { - NodeRef existing = (NodeRef) getAttributeService().getAttribute(AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_1, - AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_2, - AliasableAspect.normaliseAlias(alias)); - - if(existing != null) - { - if(!existing.equals(entry)) - { - // alias is used by more than one node - warning of some sort? - if(logger.isWarnEnabled()) - { - logger.warn("Email alias is not unique, alias:" + alias + " nodeRef:" + entry); - } - - try - { - behaviourFilter.disableBehaviour(EmailServerModel.ASPECT_ALIASABLE); - nodeService.removeAspect(entry, EmailServerModel.ASPECT_ALIASABLE); - - } - finally - { - behaviourFilter.enableBehaviour(EmailServerModel.ASPECT_ALIASABLE); - } - } - - // else do nothing - attribute already exists. - } - else - { - if(logger.isDebugEnabled()) - { - logger.debug("creating email alias attribute for " + alias); - } - getAttributeService().createAttribute(entry, AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_1, AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_2, AliasableAspect.normaliseAlias(alias)); - } - } - } - - }; - - // Now set the batch processor to work - - batchProcessor.process(worker, true); - - return I18NUtil.getMessage(MSG_SUCCESS); - } - - - public void setAttributeService(AttributeService attributeService) - { - this.attributeService = attributeService; - } - - - public AttributeService getAttributeService() - { - return attributeService; - } - - - public void setNodeDAO(NodeDAO nodeDAO) - { - this.nodeDAO = nodeDAO; - } - - - public NodeDAO getNodeDAO() - { - return nodeDAO; - } - - - public void setPatchDAO(PatchDAO patchDAO) - { - this.patchDAO = patchDAO; - } - - - public PatchDAO getPatchDAO() - { - return patchDAO; - } - - - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - - - public QNameDAO getQnameDAO() - { - return qnameDAO; - } - - - public void setBehaviourFilter(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } - - - public BehaviourFilter getBehaviourFilter() - { - return behaviourFilter; - } - - -} +package org.alfresco.repo.admin.patch.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.alfresco.email.server.AliasableAspect; +import org.alfresco.email.server.EmailServerModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.batch.BatchProcessWorkProvider; +import org.alfresco.repo.batch.BatchProcessor; +import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.patch.PatchDAO; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Patch to duplicate the AliasableAspect into the attributes service. + * + * Inbound email. + * + * @author mrogers + * + */ +public class AliasableAspectPatch extends AbstractPatch +{ + private static final String MSG_SUCCESS = "patch.emailAliasableAspect.result"; + + private AttributeService attributeService; + private NodeDAO nodeDAO; + private PatchDAO patchDAO; + private QNameDAO qnameDAO; + private BehaviourFilter behaviourFilter; + + private final int batchThreads = 3; + private final int batchSize = 40; + private final long count = batchThreads * batchSize; + + private static Log logger = LogFactory.getLog(AliasableAspectPatch.class); + + + @Override + protected String applyInternal() throws Exception + { + BatchProcessWorkProvider workProvider = new BatchProcessWorkProvider() + { + final List result = new ArrayList(); + + Long aspectQNameId = 0L; + long maxNodeId = getPatchDAO().getMaxAdmNodeID(); + + long minSearchNodeId = 1; + long maxSearchNodeId = count; + + Pair val = getQnameDAO().getQName(EmailServerModel.ASPECT_ALIASABLE ); + + public int getTotalEstimatedWorkSize() + { + return result.size(); + } + + public Collection getNextWork() + { + if(val != null) + { + Long aspectQNameId = val.getFirst(); + + result.clear(); + + while (result.isEmpty() && minSearchNodeId < maxNodeId) + { + List nodeids = getPatchDAO().getNodesByAspectQNameId(aspectQNameId, minSearchNodeId, maxSearchNodeId); + + for(Long nodeid : nodeids) + { + NodeRef.Status status = getNodeDAO().getNodeIdStatus(nodeid); + if(!status.isDeleted()) + { + result.add(status.getNodeRef()); + } + } + minSearchNodeId = minSearchNodeId + count; + maxSearchNodeId = maxSearchNodeId + count; + } + } + + return result; + } + }; + + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + // Configure the helper to run in read-only mode + // MNT-10764 + txnHelper.setForceWritable(true); + + BatchProcessor batchProcessor = new BatchProcessor( + "AliasableAspectPatch", + txnHelper, + workProvider, + batchThreads, + batchSize, + applicationEventPublisher, + logger, + 1000); + + BatchProcessWorker worker = new BatchProcessWorker() + { + + public void afterProcess() throws Throwable + { + } + + public void beforeProcess() throws Throwable + { + } + + public String getIdentifier(NodeRef entry) + { + return entry.toString(); + } + + public void process(NodeRef entry) throws Throwable + { + String alias = (String)nodeService.getProperty(entry, EmailServerModel.PROP_ALIAS); + if(alias != null) + { + NodeRef existing = (NodeRef) getAttributeService().getAttribute(AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_1, + AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_2, + AliasableAspect.normaliseAlias(alias)); + + if(existing != null) + { + if(!existing.equals(entry)) + { + // alias is used by more than one node - warning of some sort? + if(logger.isWarnEnabled()) + { + logger.warn("Email alias is not unique, alias:" + alias + " nodeRef:" + entry); + } + + try + { + behaviourFilter.disableBehaviour(EmailServerModel.ASPECT_ALIASABLE); + nodeService.removeAspect(entry, EmailServerModel.ASPECT_ALIASABLE); + + } + finally + { + behaviourFilter.enableBehaviour(EmailServerModel.ASPECT_ALIASABLE); + } + } + + // else do nothing - attribute already exists. + } + else + { + if(logger.isDebugEnabled()) + { + logger.debug("creating email alias attribute for " + alias); + } + getAttributeService().createAttribute(entry, AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_1, AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_2, AliasableAspect.normaliseAlias(alias)); + } + } + } + + }; + + // Now set the batch processor to work + + batchProcessor.process(worker, true); + + return I18NUtil.getMessage(MSG_SUCCESS); + } + + + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + + public AttributeService getAttributeService() + { + return attributeService; + } + + + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + + public NodeDAO getNodeDAO() + { + return nodeDAO; + } + + + public void setPatchDAO(PatchDAO patchDAO) + { + this.patchDAO = patchDAO; + } + + + public PatchDAO getPatchDAO() + { + return patchDAO; + } + + + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + + public QNameDAO getQnameDAO() + { + return qnameDAO; + } + + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + + public BehaviourFilter getBehaviourFilter() + { + return behaviourFilter; + } + + +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/GenericWorkflowPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/GenericWorkflowPatch.java index 842ac365d8..d03db9913e 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/GenericWorkflowPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/GenericWorkflowPatch.java @@ -1,130 +1,130 @@ -package org.alfresco.repo.admin.patch.impl; - -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.repo.workflow.BPMEngineRegistry; -import org.alfresco.repo.workflow.WorkflowDeployer; -import org.alfresco.service.cmr.admin.PatchException; -import org.alfresco.service.cmr.workflow.WorkflowAdminService; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * Generic patch that re-deploys a workflow definition - * - * @author David Caruana - */ -public class GenericWorkflowPatch extends AbstractPatch implements ApplicationContextAware -{ - private static final String MSG_DEPLOYED = "patch.genericWorkflow.result.deployed"; - private static final String MSG_UNDEPLOYED = "patch.genericWorkflow.result.undeployed"; - private static final String ERR_PROPERTY_REQUIRED = "patch.genericWorkflow.property_required"; - private static final String MSG_ERROR_ENGINE_DEACTIVATED = "patch.genericWorkflow.error_engine_deactivated"; - - private ApplicationContext applicationContext; - private List workflowDefinitions; - private List undeployWorkflowNames; - - - /* (non-Javadoc) - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) - */ - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException - { - this.applicationContext = applicationContext; - } - - /** - * Sets the Workflow Definitions - * - * @param workflowDefinitions List - */ - public void setWorkflowDefinitions(List workflowDefinitions) - { - this.workflowDefinitions = workflowDefinitions; - } - - /** - * Sets the Workflow Names to be undeployed - * - * @param undeployWorkflowNames list with names - */ - public void setUndeployWorkflowNames(List undeployWorkflowNames) - { - this.undeployWorkflowNames = undeployWorkflowNames; - } - - @Override - protected void checkProperties() - { - if ( (workflowDefinitions == null) && (undeployWorkflowNames == null) ) - { - throw new PatchException(ERR_PROPERTY_REQUIRED, "workflowDefinitions", "undeployWorkflowNames", this); - } - super.checkProperties(); - } - - @Override - protected String applyInternal() throws Exception - { - WorkflowDeployer deployer = (WorkflowDeployer)applicationContext.getBean("workflowPatchDeployer"); - WorkflowAdminService workflowAdminService = (WorkflowAdminService)applicationContext.getBean("workflowAdminService"); - - if(workflowDefinitions != null) - { - for (Properties props : workflowDefinitions) - { - props.put(WorkflowDeployer.REDEPLOY, "true"); - } - deployer.setWorkflowDefinitions(workflowDefinitions); - deployer.init(); - } - - int undeployed = 0; - StringBuilder errorMessages = new StringBuilder(); - if(undeployWorkflowNames != null) - { - List undeployableWorkflows = new ArrayList(undeployWorkflowNames); - for(String workflowName : undeployWorkflowNames) - { - String engineId = BPMEngineRegistry.getEngineId(workflowName); - if (workflowAdminService.isEngineEnabled(engineId)) - { - undeployableWorkflows.add(workflowName); - } - else - { - errorMessages.append(I18NUtil.getMessage(MSG_ERROR_ENGINE_DEACTIVATED, workflowName, engineId)); - } - } - undeployed = deployer.undeploy(undeployableWorkflows); - } - - // done - StringBuilder msg = new StringBuilder(); - if(workflowDefinitions != null) - { - msg.append(I18NUtil.getMessage(MSG_DEPLOYED, workflowDefinitions.size())); - } - if(undeployWorkflowNames != null) - { - if(msg.length() > 0) - { - msg.append(' '); - } - msg.append(I18NUtil.getMessage(MSG_UNDEPLOYED, undeployed)); - } - if(errorMessages.length() > 0) - { - msg.append(errorMessages); - } - return msg.toString(); - } - -} +package org.alfresco.repo.admin.patch.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.WorkflowDeployer; +import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.workflow.WorkflowAdminService; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Generic patch that re-deploys a workflow definition + * + * @author David Caruana + */ +public class GenericWorkflowPatch extends AbstractPatch implements ApplicationContextAware +{ + private static final String MSG_DEPLOYED = "patch.genericWorkflow.result.deployed"; + private static final String MSG_UNDEPLOYED = "patch.genericWorkflow.result.undeployed"; + private static final String ERR_PROPERTY_REQUIRED = "patch.genericWorkflow.property_required"; + private static final String MSG_ERROR_ENGINE_DEACTIVATED = "patch.genericWorkflow.error_engine_deactivated"; + + private ApplicationContext applicationContext; + private List workflowDefinitions; + private List undeployWorkflowNames; + + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException + { + this.applicationContext = applicationContext; + } + + /** + * Sets the Workflow Definitions + * + * @param workflowDefinitions List + */ + public void setWorkflowDefinitions(List workflowDefinitions) + { + this.workflowDefinitions = workflowDefinitions; + } + + /** + * Sets the Workflow Names to be undeployed + * + * @param undeployWorkflowNames list with names + */ + public void setUndeployWorkflowNames(List undeployWorkflowNames) + { + this.undeployWorkflowNames = undeployWorkflowNames; + } + + @Override + protected void checkProperties() + { + if ( (workflowDefinitions == null) && (undeployWorkflowNames == null) ) + { + throw new PatchException(ERR_PROPERTY_REQUIRED, "workflowDefinitions", "undeployWorkflowNames", this); + } + super.checkProperties(); + } + + @Override + protected String applyInternal() throws Exception + { + WorkflowDeployer deployer = (WorkflowDeployer)applicationContext.getBean("workflowPatchDeployer"); + WorkflowAdminService workflowAdminService = (WorkflowAdminService)applicationContext.getBean("workflowAdminService"); + + if(workflowDefinitions != null) + { + for (Properties props : workflowDefinitions) + { + props.put(WorkflowDeployer.REDEPLOY, "true"); + } + deployer.setWorkflowDefinitions(workflowDefinitions); + deployer.init(); + } + + int undeployed = 0; + StringBuilder errorMessages = new StringBuilder(); + if(undeployWorkflowNames != null) + { + List undeployableWorkflows = new ArrayList(undeployWorkflowNames); + for(String workflowName : undeployWorkflowNames) + { + String engineId = BPMEngineRegistry.getEngineId(workflowName); + if (workflowAdminService.isEngineEnabled(engineId)) + { + undeployableWorkflows.add(workflowName); + } + else + { + errorMessages.append(I18NUtil.getMessage(MSG_ERROR_ENGINE_DEACTIVATED, workflowName, engineId)); + } + } + undeployed = deployer.undeploy(undeployableWorkflows); + } + + // done + StringBuilder msg = new StringBuilder(); + if(workflowDefinitions != null) + { + msg.append(I18NUtil.getMessage(MSG_DEPLOYED, workflowDefinitions.size())); + } + if(undeployWorkflowNames != null) + { + if(msg.length() > 0) + { + msg.append(' '); + } + msg.append(I18NUtil.getMessage(MSG_UNDEPLOYED, undeployed)); + } + if(errorMessages.length() > 0) + { + msg.append(errorMessages); + } + return msg.toString(); + } + +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/SWSDPPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/SWSDPPatch.java index d858c74b71..ade8a4abcb 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/SWSDPPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/SWSDPPatch.java @@ -1,65 +1,65 @@ -package org.alfresco.repo.admin.patch.impl; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.repo.model.filefolder.HiddenAspect; -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.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.springframework.extensions.surf.util.I18NUtil; - -public class SWSDPPatch extends AbstractPatch -{ - private static final String MSG_SITE_PATCHED = "patch.swsdpPatch.success"; - private static final String MSG_SKIPPED = "patch.swsdpPatch.skipped"; - private static final String MSG_MISSING_SURFCONFIG = "patch.swsdpPatch.missingSurfConfig"; - - private SiteService siteService; - private HiddenAspect hiddenAspect; - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setHiddenAspect(HiddenAspect hiddenAspect) - { - this.hiddenAspect = hiddenAspect; - } - - @Override - protected String applyInternal() throws Exception - { - SiteInfo siteInfo = siteService.getSite("swsdp"); - if(siteInfo != null) - { - NodeRef nodeRef = siteInfo.getNodeRef(); - NodeRef surfConfigNodeRef = nodeService.getChildByName(nodeRef, ContentModel.ASSOC_CONTAINS, "surf-config"); - if(surfConfigNodeRef == null) - { - return I18NUtil.getMessage(MSG_MISSING_SURFCONFIG); - } - else - { - for(ChildAssociationRef childRef : nodeService.getChildAssocs(surfConfigNodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL)) - { - hiddenAspect.showNode(childRef.getChildRef(), true); - } - } - - return I18NUtil.getMessage(MSG_SITE_PATCHED); - } - else - { - return I18NUtil.getMessage(MSG_SKIPPED); - } - } -} +package org.alfresco.repo.admin.patch.impl; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.model.filefolder.HiddenAspect; +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.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.springframework.extensions.surf.util.I18NUtil; + +public class SWSDPPatch extends AbstractPatch +{ + private static final String MSG_SITE_PATCHED = "patch.swsdpPatch.success"; + private static final String MSG_SKIPPED = "patch.swsdpPatch.skipped"; + private static final String MSG_MISSING_SURFCONFIG = "patch.swsdpPatch.missingSurfConfig"; + + private SiteService siteService; + private HiddenAspect hiddenAspect; + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setHiddenAspect(HiddenAspect hiddenAspect) + { + this.hiddenAspect = hiddenAspect; + } + + @Override + protected String applyInternal() throws Exception + { + SiteInfo siteInfo = siteService.getSite("swsdp"); + if(siteInfo != null) + { + NodeRef nodeRef = siteInfo.getNodeRef(); + NodeRef surfConfigNodeRef = nodeService.getChildByName(nodeRef, ContentModel.ASSOC_CONTAINS, "surf-config"); + if(surfConfigNodeRef == null) + { + return I18NUtil.getMessage(MSG_MISSING_SURFCONFIG); + } + else + { + for(ChildAssociationRef childRef : nodeService.getChildAssocs(surfConfigNodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL)) + { + hiddenAspect.showNode(childRef.getChildRef(), true); + } + } + + return I18NUtil.getMessage(MSG_SITE_PATCHED); + } + else + { + return I18NUtil.getMessage(MSG_SKIPPED); + } + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/SharedFolderPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/SharedFolderPatch.java index 3be26845fa..fb46ad48b8 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/SharedFolderPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/SharedFolderPatch.java @@ -1,235 +1,235 @@ -package org.alfresco.repo.admin.patch.impl; - -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.lock.JobLockService; -import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.service.cmr.admin.PatchException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.quartz.Job; -import org.quartz.JobDataMap; -import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * The SharedFolderPatch is a Generic Bootstrap Patch with the extra ability to - * rename an existing folder that is in the way (in a different namespace). - *

- * The first use-case is when there is a child called cm:shared and we want to patch a folder with app:shared - * - * @author mrogers - */ -public class SharedFolderPatch extends GenericBootstrapPatch -{ - private JobLockService jobLockService; - - private long LOCK_TIME_TO_LIVE=10000; - private long LOCK_REFRESH_TIME=5000; - - private String renamePath; - - private Log logger = LogFactory.getLog(SharedFolderPatch.class); - - private static final String MSG_RENAMED = "patch.sharedFolder.result.renamed"; - - /** - * Run the Shared Folder Patch asynchronously after bootstrap. - */ - public void executeAsync() - { - // Lock the push - QName lockQName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "patch.sharedFolder"); - String lockToken = jobLockService.getLock(lockQName, LOCK_TIME_TO_LIVE, 0, 1); - SharedFolderPatchCallback callback = new SharedFolderPatchCallback(); - jobLockService.refreshLock(lockToken, lockQName, LOCK_REFRESH_TIME, callback); - - try - { - if (logger.isDebugEnabled()) - { - logger.debug("SharedFolderPatch: job lock held"); - } - - AuthenticationUtil.runAsSystem(new RunAsWork() - { - public Void doWork() throws Exception - { - applyAsync(); - return null; - } - }); - } - finally - { - if (logger.isTraceEnabled()) - { - logger.trace("PUSH: job finished"); - } - - // Release the locks on the job and stop refreshing - callback.isActive = false; - jobLockService.releaseLock(lockToken, lockQName); - } - } - - @Override - protected String applyInternal() throws Exception - { - StoreRef storeRef = importerBootstrap.getStoreRef(); - NodeRef rootNodeRef = nodeService.getRootNode(storeRef); - if (getRenamePath() != null) - { - List results = searchService.selectNodes( - rootNodeRef, - getRenamePath(), - null, - namespaceService, - false); - - if (results.size() > 1) - { - throw new PatchException(ERR_MULTIPLE_FOUND, renamePath); - } - else if (results.size() == 1) - { - if(logger.isDebugEnabled()) - { - logger.debug("There is an existing node in the way path:" + getRenamePath()); - } - // A node already exists that we must rename. - NodeRef existingNodeRef = results.get(0); - - // get the path of the parent node e.g. company_home - LinkedList folderElements = new LinkedList(Arrays.asList(getRenamePath().split("/"))); - folderElements.removeLast(); - - StringBuffer parentPath = new StringBuffer(); - - for(String folder : folderElements) - { - parentPath.append("/"); - parentPath.append(folder); - } - - List parentResults = searchService.selectNodes( - rootNodeRef, - parentPath.toString(), - null, - namespaceService, - false); - - if(parentResults.size()==1) - { - - NodeRef parentNodeRef = parentResults.get(0); - - if(logger.isDebugEnabled()) - { - logger.debug("Found the parent node - doing a move parentNodeRef:" + parentNodeRef); - } - - // rename the existing node - nodeService.moveNode(existingNodeRef, parentNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName( NamespaceService.APP_MODEL_1_0_URI, "shared")); - return I18NUtil.getMessage(MSG_RENAMED, renamePath); - } - else - { - // Something has gone horribly wrong if we get here - we have multiple parents, or none despite finding the node earlier - throw new PatchException(ERR_MULTIPLE_FOUND, parentPath.toString()); - } - } - } - - // Else run the normal GenericBootstrapPatch implementation - - if(logger.isDebugEnabled()) - { - logger.debug("Node does not already exist, Running the Generic Bootstrap Patch"); - } - return super.applyInternal(); - } - - public void setRenamePath(String renamePath) - { - this.renamePath = renamePath; - } - - public String getRenamePath() - { - return renamePath; - } - - public void setJobLockService(JobLockService jobLockService) - { - this.jobLockService = jobLockService; - } - - public JobLockService getJobLockService() - { - return jobLockService; - } - - /** - * Job to initiate the {@link SharedFolderPatch} if it has been deferred - * - * @author Mark Rogers - * @since 4.2 - */ - public static class SharedFolderPatchJob implements Job - { - public SharedFolderPatchJob() - { - } - - /** - * Calls the cleaner to do its work - */ - public void execute(JobExecutionContext context) throws JobExecutionException - { - JobDataMap jobData = context.getJobDetail().getJobDataMap(); - // extract the content cleaner to use - Object sharedFolderPatchObj = jobData.get("sharedFolderPatch"); - if (sharedFolderPatchObj == null || !(sharedFolderPatchObj instanceof SharedFolderPatch)) - { - throw new AlfrescoRuntimeException( - "'sharedFolderPatch' data must contain valid 'SharedFolderPatch' reference"); - } - - // Job Lock Here - should probably move into the patch service at some time. - SharedFolderPatch sharedFolderPatch = (SharedFolderPatch) sharedFolderPatchObj; - sharedFolderPatch.executeAsync(); - } - } - - private class SharedFolderPatchCallback implements JobLockRefreshCallback - { - public boolean isActive = true; - - @Override - public boolean isActive() - { - return isActive; - } - - @Override - public void lockReleased() - { - if (logger.isTraceEnabled()) - { - logger.trace("lock released"); - } - } - }; -} +package org.alfresco.repo.admin.patch.impl; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * The SharedFolderPatch is a Generic Bootstrap Patch with the extra ability to + * rename an existing folder that is in the way (in a different namespace). + *

+ * The first use-case is when there is a child called cm:shared and we want to patch a folder with app:shared + * + * @author mrogers + */ +public class SharedFolderPatch extends GenericBootstrapPatch +{ + private JobLockService jobLockService; + + private long LOCK_TIME_TO_LIVE=10000; + private long LOCK_REFRESH_TIME=5000; + + private String renamePath; + + private Log logger = LogFactory.getLog(SharedFolderPatch.class); + + private static final String MSG_RENAMED = "patch.sharedFolder.result.renamed"; + + /** + * Run the Shared Folder Patch asynchronously after bootstrap. + */ + public void executeAsync() + { + // Lock the push + QName lockQName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "patch.sharedFolder"); + String lockToken = jobLockService.getLock(lockQName, LOCK_TIME_TO_LIVE, 0, 1); + SharedFolderPatchCallback callback = new SharedFolderPatchCallback(); + jobLockService.refreshLock(lockToken, lockQName, LOCK_REFRESH_TIME, callback); + + try + { + if (logger.isDebugEnabled()) + { + logger.debug("SharedFolderPatch: job lock held"); + } + + AuthenticationUtil.runAsSystem(new RunAsWork() + { + public Void doWork() throws Exception + { + applyAsync(); + return null; + } + }); + } + finally + { + if (logger.isTraceEnabled()) + { + logger.trace("PUSH: job finished"); + } + + // Release the locks on the job and stop refreshing + callback.isActive = false; + jobLockService.releaseLock(lockToken, lockQName); + } + } + + @Override + protected String applyInternal() throws Exception + { + StoreRef storeRef = importerBootstrap.getStoreRef(); + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + if (getRenamePath() != null) + { + List results = searchService.selectNodes( + rootNodeRef, + getRenamePath(), + null, + namespaceService, + false); + + if (results.size() > 1) + { + throw new PatchException(ERR_MULTIPLE_FOUND, renamePath); + } + else if (results.size() == 1) + { + if(logger.isDebugEnabled()) + { + logger.debug("There is an existing node in the way path:" + getRenamePath()); + } + // A node already exists that we must rename. + NodeRef existingNodeRef = results.get(0); + + // get the path of the parent node e.g. company_home + LinkedList folderElements = new LinkedList(Arrays.asList(getRenamePath().split("/"))); + folderElements.removeLast(); + + StringBuffer parentPath = new StringBuffer(); + + for(String folder : folderElements) + { + parentPath.append("/"); + parentPath.append(folder); + } + + List parentResults = searchService.selectNodes( + rootNodeRef, + parentPath.toString(), + null, + namespaceService, + false); + + if(parentResults.size()==1) + { + + NodeRef parentNodeRef = parentResults.get(0); + + if(logger.isDebugEnabled()) + { + logger.debug("Found the parent node - doing a move parentNodeRef:" + parentNodeRef); + } + + // rename the existing node + nodeService.moveNode(existingNodeRef, parentNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName( NamespaceService.APP_MODEL_1_0_URI, "shared")); + return I18NUtil.getMessage(MSG_RENAMED, renamePath); + } + else + { + // Something has gone horribly wrong if we get here - we have multiple parents, or none despite finding the node earlier + throw new PatchException(ERR_MULTIPLE_FOUND, parentPath.toString()); + } + } + } + + // Else run the normal GenericBootstrapPatch implementation + + if(logger.isDebugEnabled()) + { + logger.debug("Node does not already exist, Running the Generic Bootstrap Patch"); + } + return super.applyInternal(); + } + + public void setRenamePath(String renamePath) + { + this.renamePath = renamePath; + } + + public String getRenamePath() + { + return renamePath; + } + + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + + public JobLockService getJobLockService() + { + return jobLockService; + } + + /** + * Job to initiate the {@link SharedFolderPatch} if it has been deferred + * + * @author Mark Rogers + * @since 4.2 + */ + public static class SharedFolderPatchJob implements Job + { + public SharedFolderPatchJob() + { + } + + /** + * Calls the cleaner to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + // extract the content cleaner to use + Object sharedFolderPatchObj = jobData.get("sharedFolderPatch"); + if (sharedFolderPatchObj == null || !(sharedFolderPatchObj instanceof SharedFolderPatch)) + { + throw new AlfrescoRuntimeException( + "'sharedFolderPatch' data must contain valid 'SharedFolderPatch' reference"); + } + + // Job Lock Here - should probably move into the patch service at some time. + SharedFolderPatch sharedFolderPatch = (SharedFolderPatch) sharedFolderPatchObj; + sharedFolderPatch.executeAsync(); + } + } + + private class SharedFolderPatchCallback implements JobLockRefreshCallback + { + public boolean isActive = true; + + @Override + public boolean isActive() + { + return isActive; + } + + @Override + public void lockReleased() + { + if (logger.isTraceEnabled()) + { + logger.trace("lock released"); + } + } + }; +} diff --git a/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java b/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java index ca4a6767e5..207ec0e39b 100644 --- a/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java +++ b/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java @@ -1,616 +1,616 @@ -package org.alfresco.repo.admin.patch.util; - -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.ActionModel; -import org.alfresco.repo.rule.RuleModel; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.util.GUID; -import org.dom4j.io.OutputFormat; -import org.dom4j.io.XMLWriter; -import org.xml.sax.helpers.AttributesImpl; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -/** - * Updates a XML import file to be compatable with the current version of the repository. - * - * @author royw - */ -public class ImportFileUpdater -{ - /** Indent size **/ - private static int INDENT_SIZE = 2; - - /** The destination export version number **/ - private static String EXPORT_VERSION = "1.4.0"; - - /** Default encoding **/ - private static String DEFAULT_ENCODING = "UTF-8"; - - /** File encoding */ - private String fileEncoding = DEFAULT_ENCODING; - - /** Element names **/ - private static String NAME_EXPORTER_VERSION = "exporterVersion"; - private static String NAME_RULE = "rule"; - - /** The current import version number **/ - private String version; - private boolean shownWarning = false; - - /** - * Set the file encoding. - * - * @param fileEncoding the file encoding - */ - public void setFileEncoding(String fileEncoding) - { - this.fileEncoding = fileEncoding; - } - - /** - * Updates the passed import file into the equivalent 1.4 format. - * - * @param source the source import file - * @param destination the destination import file - */ - public void updateImportFile(String source, String destination) - { - XmlPullParser reader = getReader(source); - XMLWriter writer = getWriter(destination); - this.shownWarning = false; - - try - { - // Start the documentation - writer.startDocument(); - - // Start reading the document - int eventType = reader.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) - { - if (eventType == XmlPullParser.START_TAG) - { - ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); - } - eventType = reader.next(); - } - - // End and close the document - writer.endDocument(); - writer.close(); - } - catch (Exception exception) - { - throw new AlfrescoRuntimeException("Unable to update import file.", exception); - } - - } - - /** - * Get the reader for the source import file - * - * @param source the source import file - * @return the XML pull parser used to read the file - */ - private XmlPullParser getReader(String source) - { - try - { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); - factory.setNamespaceAware(true); - - InputStream inputStream = new FileInputStream(source); - Reader inputReader = new InputStreamReader(inputStream, this.fileEncoding); - - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(new BufferedReader(inputReader)); - - return xpp; - } - catch (XmlPullParserException exception) - { - throw new AlfrescoRuntimeException("Unable to update import file.", exception); - } - catch (FileNotFoundException fileNotFound) - { - throw new AlfrescoRuntimeException("The source file could not be loaded.", fileNotFound); - } - catch (UnsupportedEncodingException exception) - { - throw new AlfrescoRuntimeException("Unsupported encoding", exception); - } - } - - /** - * Get the writer for the import file - * - * @param destination the destination XML import file - * @return the XML writer - */ - private XMLWriter getWriter(String destination) - { - try - { - // Define output format - OutputFormat format = OutputFormat.createPrettyPrint(); - format.setNewLineAfterDeclaration(false); - format.setIndentSize(INDENT_SIZE); - format.setEncoding(this.fileEncoding); - - return new XMLWriter(new FileOutputStream(destination), format); - } - catch (Exception exception) - { - throw new AlfrescoRuntimeException("Unable to create XML writer.", exception); - } - } - - private void outputCurrentElement(XmlPullParser reader, XMLWriter writer, Work work) - throws Exception - { - outputCurrentElement(reader, writer, work, true); - } - - private void outputCurrentElement(XmlPullParser reader, XMLWriter writer, Work work, boolean checkForCallbacks) - throws Exception - { - if (checkForCallbacks == false || checkForCallbacks(reader, writer) == false) - { - // Get the name details of the element - String name = reader.getName(); - String namespace = reader.getNamespace(); - String prefix = reader.getPrefix(); - - // Sort out namespaces - Map nss = new HashMap(); - int nsStart = reader.getNamespaceCount(reader.getDepth()-1); - int nsEnd = reader.getNamespaceCount(reader.getDepth()); - for (int i = nsStart; i < nsEnd; i++) - { - String nsPrefix = reader.getNamespacePrefix(i); - String ns = reader.getNamespaceUri(i); - nss.put(nsPrefix, ns); - } - - // Sort out attributes - AttributesImpl attributes = new AttributesImpl(); - for (int i = 0; i < reader.getAttributeCount(); i++) - { - String attributeName = reader.getAttributeName(i); - String attributeNamespace = reader.getAttributeNamespace(i); - String attributePrefix = reader.getAttributePrefix(i); - String attributeType = reader.getAttributeType(i); - String attributeValue = reader.getAttributeValue(i); - - attributes.addAttribute(attributeNamespace, attributeName, attributePrefix+":"+attributeName, attributeType, attributeValue); - } - - // Start the namespace prefixes - for (Map.Entry entry : nss.entrySet()) - { - writer.startPrefixMapping(entry.getKey(), entry.getValue()); - } - - // Write the start of the element - writer.startElement(namespace, name, prefix+":"+name, attributes); - - // Do the work - work.doWork(reader, writer); - - // Write the end of the element - writer.endElement(namespace, name, prefix+":"+name); - - // End the namespace prefixes - for (String nsPrefix : nss.keySet()) - { - writer.endPrefixMapping(nsPrefix); - } - } - } - - private boolean checkForCallbacks(XmlPullParser reader, XMLWriter writer) - throws Exception - { - boolean result = false; - if (reader.getName().equals(NAME_EXPORTER_VERSION) == true) - { - new ImportVersionLabelCallback().doCallback(reader, writer); - result = true; - } - else if (reader.getName().equals(NAME_RULE) == true) - { - if (this.shownWarning == false && this.version == null) - { - System.out.println("WARNING: No version information has been found in this import file. It will be presumed it has been exported from 1.3"); - this.shownWarning = true; - } - if (this.version == null || this.version.startsWith("1.3") == true || this.version.startsWith("1.2") == true) - { - new RuleCallback().doCallback(reader, writer); - result = true; - } - else - { - throw new RuntimeException("Import files of version " + this.version + " are not supported by this tool."); - } - } - return result; - } - - private interface Work - { - void doWork(XmlPullParser reader, XMLWriter writer) - throws Exception; - } - - private class OutputChildren implements Work - { - public void doWork(XmlPullParser reader, XMLWriter writer) - throws Exception - { - // Deal with the contents of the tag - int eventType = reader.getEventType(); - while (eventType != XmlPullParser.END_TAG) - { - eventType = reader.next(); - if (eventType == XmlPullParser.START_TAG) - { - ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); - } - else if (eventType == XmlPullParser.TEXT) - { - // Write the text to the output file - writer.write(reader.getText()); - } - } - } - } - - @SuppressWarnings("unused") - private class IgnoreChildren implements Work - { - public void doWork(XmlPullParser reader, XMLWriter writer) - throws Exception - { - int eventType = reader.getEventType(); - while (eventType != XmlPullParser.END_TAG) - { - eventType = reader.next(); - if (eventType == XmlPullParser.START_TAG) - { - doWork(reader, writer); - } - } - } - } - - private interface ImportUpdaterCallback - { - void doCallback(XmlPullParser reader, XMLWriter writer) - throws Exception; - } - - private class ImportVersionLabelCallback implements ImportUpdaterCallback - { - public void doCallback(XmlPullParser reader, XMLWriter writer) - throws Exception - { - ImportFileUpdater.this.outputCurrentElement(reader, writer, - new Work() - { - public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception - { - reader.next(); - ImportFileUpdater.this.version = reader.getText(); - writer.write(EXPORT_VERSION); - reader.next(); - } - }, false); - } - } - - private class RuleCallback implements ImportUpdaterCallback - { - public void doCallback(XmlPullParser reader, XMLWriter writer) - throws Exception - { - // Get the name details of the element - String name = reader.getName(); - String namespace = reader.getNamespace(); - String prefix = reader.getPrefix(); - - // Rename the child assoc appropriately - AttributesImpl attributes = new AttributesImpl(); - String attributeName = reader.getAttributeName(0); - String attributeNamespace = reader.getAttributeNamespace(0); - String attributePrefix = reader.getAttributePrefix(0); - String attributeType = reader.getAttributeType(0); - String attributeValue = reader.getAttributeValue(0) + GUID.generate(); - attributes.addAttribute(attributeNamespace, attributeName, attributePrefix+":"+attributeName, attributeType, attributeValue); - - // Output the rules element - writer.startElement(namespace, name, prefix+":"+name, attributes); - - int eventType = reader.getEventType(); - while (eventType != XmlPullParser.END_TAG) - { - eventType = reader.next(); - if (eventType == XmlPullParser.START_TAG) - { - String childName = reader.getName(); - if (childName.equals("aspects") == true) - { - ImportFileUpdater.this.outputCurrentElement(reader, writer, - new Work() - { - public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception - { - // Add titled aspect - writer.startElement( - ContentModel.ASPECT_TITLED.getNamespaceURI(), - ContentModel.ASPECT_TITLED.getLocalName(), - NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.ASPECT_TITLED.getLocalName(), - new AttributesImpl()); - writer.endElement( - ContentModel.ASPECT_TITLED.getNamespaceURI(), - ContentModel.ASPECT_TITLED.getLocalName(), - NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.ASPECT_TITLED.getLocalName()); - - // Read the rest of the elements and output - int eventType = reader.getEventType(); - while (eventType != XmlPullParser.END_TAG) - { - eventType = reader.next(); - if (eventType == XmlPullParser.START_TAG) - { - ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); - } - } - } - - }, false); - } - else if (childName.equals("properties") == true) - { - ImportFileUpdater.this.outputCurrentElement(reader, writer, - new Work() - { - public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception - { - int eventType = reader.getEventType(); - while (eventType != XmlPullParser.END_TAG) - { - eventType = reader.next(); - if (eventType == XmlPullParser.START_TAG) - { - String propName = reader.getName(); - if (propName.equals("actionDescription") == true) - { - writer.startElement( - ContentModel.PROP_DESCRIPTION.getNamespaceURI(), - ContentModel.PROP_DESCRIPTION.getLocalName(), - NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_DESCRIPTION.getLocalName(), - new AttributesImpl()); - - // Output the value within - new OutputChildren().doWork(reader, writer); - - writer.endElement( - ContentModel.PROP_DESCRIPTION.getNamespaceURI(), - ContentModel.PROP_DESCRIPTION.getLocalName(), - NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_DESCRIPTION.getLocalName()); - eventType = reader.next(); - - } - else if (propName.equals("actionTitle") == true) - { - writer.startElement( - ContentModel.PROP_TITLE.getNamespaceURI(), - ContentModel.PROP_TITLE.getLocalName(), - NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_TITLE.getLocalName(), - new AttributesImpl()); - - // Output the value within - new OutputChildren().doWork(reader, writer); - - writer.endElement( - ContentModel.PROP_TITLE.getNamespaceURI(), - ContentModel.PROP_TITLE.getLocalName(), - NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_TITLE.getLocalName()); - eventType = reader.next(); - } - else if (propName.equals("executeAsynchronously") == true) - { - writer.startElement( - RuleModel.PROP_EXECUTE_ASYNC.getNamespaceURI(), - RuleModel.PROP_EXECUTE_ASYNC.getLocalName(), - RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_EXECUTE_ASYNC.getLocalName(), - new AttributesImpl()); - - // Output the value within - new OutputChildren().doWork(reader, writer); - - writer.endElement( - RuleModel.PROP_EXECUTE_ASYNC.getNamespaceURI(), - RuleModel.PROP_EXECUTE_ASYNC.getLocalName(), - RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_EXECUTE_ASYNC.getLocalName()); - eventType = reader.next(); - } - else if (propName.equals("ruleType") == true) - { - ImportFileUpdater.this.outputCurrentElement(reader, writer, - new Work() - { - public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception - { - // Output the elements that contain a multi values property - writer.startElement(NamespaceService.REPOSITORY_VIEW_1_0_URI, "values", "view:values", new AttributesImpl()); - writer.startElement(NamespaceService.REPOSITORY_VIEW_1_0_URI, "value", "view:value", new AttributesImpl()); - - // Output the value within - new OutputChildren().doWork(reader, writer); - - // End the multi values elements - writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "value", "view:value"); - writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "values", "view:values"); - } - }, false); - } - else if (propName.equals("definitionName") == true) - { - // Skip past next end - while (eventType != XmlPullParser.END_TAG) - { - eventType = reader.next(); - } - eventType = reader.next(); - } - else - { - ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); - } - } - } - - // Output value for the disabled property - writer.startElement( - RuleModel.PROP_DISABLED.getNamespaceURI(), - RuleModel.PROP_DISABLED.getLocalName(), - RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_DISABLED.getLocalName(), - new AttributesImpl()); - writer.write("false"); - writer.endElement( - RuleModel.PROP_DISABLED.getNamespaceURI(), - RuleModel.PROP_DISABLED.getLocalName(), - RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_DISABLED.getLocalName()); - } - }, false); - } - else if (childName.equals("associations") == true) - { - ImportFileUpdater.this.outputCurrentElement(reader, writer, - new Work() - { - public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception - { - // - writer.startElement( - RuleModel.ASSOC_ACTION.getNamespaceURI(), - RuleModel.ASSOC_ACTION.getLocalName(), - RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.ASSOC_ACTION.getLocalName(), - new AttributesImpl()); - - // - AttributesImpl attributes = new AttributesImpl(); - attributes.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, "childName", "view:childName", null, "rule:action"); - writer.startElement( - ActionModel.TYPE_COMPOSITE_ACTION.getNamespaceURI(), - ActionModel.TYPE_COMPOSITE_ACTION.getLocalName(), - ActionModel.ACTION_MODEL_PREFIX+ ":" + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName(), - attributes); - - // - writer.startElement( - NamespaceService.REPOSITORY_VIEW_1_0_URI, - "properties", - "view:properties", - new AttributesImpl()); - - // composite-action - writer.startElement( - ActionModel.PROP_DEFINITION_NAME.getNamespaceURI(), - ActionModel.PROP_DEFINITION_NAME.getLocalName(), - ActionModel.ACTION_MODEL_PREFIX + ":" + ActionModel.PROP_DEFINITION_NAME.getLocalName(), - new AttributesImpl()); - writer.write("composite-action"); - writer.endElement( - ActionModel.PROP_DEFINITION_NAME.getNamespaceURI(), - ActionModel.PROP_DEFINITION_NAME.getLocalName(), - ActionModel.ACTION_MODEL_PREFIX + ":" + ActionModel.PROP_DEFINITION_NAME.getLocalName()); - - // - writer.endElement( - NamespaceService.REPOSITORY_VIEW_1_0_URI, - "properties", - "view:properties"); - - // - writer.startElement( - NamespaceService.REPOSITORY_VIEW_1_0_URI, - "associations", - "view:associations", - new AttributesImpl()); - - // Output the association details - new OutputChildren().doWork(reader, writer); - - // - writer.endElement( - NamespaceService.REPOSITORY_VIEW_1_0_URI, - "associations", - "view:associations"); - - // - writer.endElement( - ActionModel.TYPE_COMPOSITE_ACTION.getNamespaceURI(), - ActionModel.TYPE_COMPOSITE_ACTION.getLocalName(), - ActionModel.ACTION_MODEL_PREFIX+ ":" + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName()); - - // - writer.endElement( - RuleModel.ASSOC_ACTION.getNamespaceURI(), - RuleModel.ASSOC_ACTION.getLocalName(), - RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.ASSOC_ACTION.getLocalName()); - } - }, false); - } - else - { - // Output anything else that might be hanging araound - ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); - } - } - } - - // End the rules element - writer.endElement(namespace, name, prefix+":"+name); - } - } - - public static void main(String[] args) - { - if (args.length == 2) - { - ImportFileUpdater util = new ImportFileUpdater(); - util.updateImportFile(args[0], args[1]); - } - else if (args.length == 3) - { - ImportFileUpdater util = new ImportFileUpdater(); - util.setFileEncoding(args[2]); - util.updateImportFile(args[0], args[1]); - } - else - { - System.out.println(" ImportFileUpdater "); - System.out.println(" source - 1.3 import file name to be updated"); - System.out.println(" destination - name of the generated 1.4 import file"); - System.out.println(" file encoding (optional) - the file encoding, default is UTF-8"); - } - } - -} +package org.alfresco.repo.admin.patch.util; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionModel; +import org.alfresco.repo.rule.RuleModel; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.util.GUID; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.xml.sax.helpers.AttributesImpl; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +/** + * Updates a XML import file to be compatable with the current version of the repository. + * + * @author royw + */ +public class ImportFileUpdater +{ + /** Indent size **/ + private static int INDENT_SIZE = 2; + + /** The destination export version number **/ + private static String EXPORT_VERSION = "1.4.0"; + + /** Default encoding **/ + private static String DEFAULT_ENCODING = "UTF-8"; + + /** File encoding */ + private String fileEncoding = DEFAULT_ENCODING; + + /** Element names **/ + private static String NAME_EXPORTER_VERSION = "exporterVersion"; + private static String NAME_RULE = "rule"; + + /** The current import version number **/ + private String version; + private boolean shownWarning = false; + + /** + * Set the file encoding. + * + * @param fileEncoding the file encoding + */ + public void setFileEncoding(String fileEncoding) + { + this.fileEncoding = fileEncoding; + } + + /** + * Updates the passed import file into the equivalent 1.4 format. + * + * @param source the source import file + * @param destination the destination import file + */ + public void updateImportFile(String source, String destination) + { + XmlPullParser reader = getReader(source); + XMLWriter writer = getWriter(destination); + this.shownWarning = false; + + try + { + // Start the documentation + writer.startDocument(); + + // Start reading the document + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) + { + if (eventType == XmlPullParser.START_TAG) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + eventType = reader.next(); + } + + // End and close the document + writer.endDocument(); + writer.close(); + } + catch (Exception exception) + { + throw new AlfrescoRuntimeException("Unable to update import file.", exception); + } + + } + + /** + * Get the reader for the source import file + * + * @param source the source import file + * @return the XML pull parser used to read the file + */ + private XmlPullParser getReader(String source) + { + try + { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); + factory.setNamespaceAware(true); + + InputStream inputStream = new FileInputStream(source); + Reader inputReader = new InputStreamReader(inputStream, this.fileEncoding); + + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(new BufferedReader(inputReader)); + + return xpp; + } + catch (XmlPullParserException exception) + { + throw new AlfrescoRuntimeException("Unable to update import file.", exception); + } + catch (FileNotFoundException fileNotFound) + { + throw new AlfrescoRuntimeException("The source file could not be loaded.", fileNotFound); + } + catch (UnsupportedEncodingException exception) + { + throw new AlfrescoRuntimeException("Unsupported encoding", exception); + } + } + + /** + * Get the writer for the import file + * + * @param destination the destination XML import file + * @return the XML writer + */ + private XMLWriter getWriter(String destination) + { + try + { + // Define output format + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewLineAfterDeclaration(false); + format.setIndentSize(INDENT_SIZE); + format.setEncoding(this.fileEncoding); + + return new XMLWriter(new FileOutputStream(destination), format); + } + catch (Exception exception) + { + throw new AlfrescoRuntimeException("Unable to create XML writer.", exception); + } + } + + private void outputCurrentElement(XmlPullParser reader, XMLWriter writer, Work work) + throws Exception + { + outputCurrentElement(reader, writer, work, true); + } + + private void outputCurrentElement(XmlPullParser reader, XMLWriter writer, Work work, boolean checkForCallbacks) + throws Exception + { + if (checkForCallbacks == false || checkForCallbacks(reader, writer) == false) + { + // Get the name details of the element + String name = reader.getName(); + String namespace = reader.getNamespace(); + String prefix = reader.getPrefix(); + + // Sort out namespaces + Map nss = new HashMap(); + int nsStart = reader.getNamespaceCount(reader.getDepth()-1); + int nsEnd = reader.getNamespaceCount(reader.getDepth()); + for (int i = nsStart; i < nsEnd; i++) + { + String nsPrefix = reader.getNamespacePrefix(i); + String ns = reader.getNamespaceUri(i); + nss.put(nsPrefix, ns); + } + + // Sort out attributes + AttributesImpl attributes = new AttributesImpl(); + for (int i = 0; i < reader.getAttributeCount(); i++) + { + String attributeName = reader.getAttributeName(i); + String attributeNamespace = reader.getAttributeNamespace(i); + String attributePrefix = reader.getAttributePrefix(i); + String attributeType = reader.getAttributeType(i); + String attributeValue = reader.getAttributeValue(i); + + attributes.addAttribute(attributeNamespace, attributeName, attributePrefix+":"+attributeName, attributeType, attributeValue); + } + + // Start the namespace prefixes + for (Map.Entry entry : nss.entrySet()) + { + writer.startPrefixMapping(entry.getKey(), entry.getValue()); + } + + // Write the start of the element + writer.startElement(namespace, name, prefix+":"+name, attributes); + + // Do the work + work.doWork(reader, writer); + + // Write the end of the element + writer.endElement(namespace, name, prefix+":"+name); + + // End the namespace prefixes + for (String nsPrefix : nss.keySet()) + { + writer.endPrefixMapping(nsPrefix); + } + } + } + + private boolean checkForCallbacks(XmlPullParser reader, XMLWriter writer) + throws Exception + { + boolean result = false; + if (reader.getName().equals(NAME_EXPORTER_VERSION) == true) + { + new ImportVersionLabelCallback().doCallback(reader, writer); + result = true; + } + else if (reader.getName().equals(NAME_RULE) == true) + { + if (this.shownWarning == false && this.version == null) + { + System.out.println("WARNING: No version information has been found in this import file. It will be presumed it has been exported from 1.3"); + this.shownWarning = true; + } + if (this.version == null || this.version.startsWith("1.3") == true || this.version.startsWith("1.2") == true) + { + new RuleCallback().doCallback(reader, writer); + result = true; + } + else + { + throw new RuntimeException("Import files of version " + this.version + " are not supported by this tool."); + } + } + return result; + } + + private interface Work + { + void doWork(XmlPullParser reader, XMLWriter writer) + throws Exception; + } + + private class OutputChildren implements Work + { + public void doWork(XmlPullParser reader, XMLWriter writer) + throws Exception + { + // Deal with the contents of the tag + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + else if (eventType == XmlPullParser.TEXT) + { + // Write the text to the output file + writer.write(reader.getText()); + } + } + } + } + + @SuppressWarnings("unused") + private class IgnoreChildren implements Work + { + public void doWork(XmlPullParser reader, XMLWriter writer) + throws Exception + { + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + doWork(reader, writer); + } + } + } + } + + private interface ImportUpdaterCallback + { + void doCallback(XmlPullParser reader, XMLWriter writer) + throws Exception; + } + + private class ImportVersionLabelCallback implements ImportUpdaterCallback + { + public void doCallback(XmlPullParser reader, XMLWriter writer) + throws Exception + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + reader.next(); + ImportFileUpdater.this.version = reader.getText(); + writer.write(EXPORT_VERSION); + reader.next(); + } + }, false); + } + } + + private class RuleCallback implements ImportUpdaterCallback + { + public void doCallback(XmlPullParser reader, XMLWriter writer) + throws Exception + { + // Get the name details of the element + String name = reader.getName(); + String namespace = reader.getNamespace(); + String prefix = reader.getPrefix(); + + // Rename the child assoc appropriately + AttributesImpl attributes = new AttributesImpl(); + String attributeName = reader.getAttributeName(0); + String attributeNamespace = reader.getAttributeNamespace(0); + String attributePrefix = reader.getAttributePrefix(0); + String attributeType = reader.getAttributeType(0); + String attributeValue = reader.getAttributeValue(0) + GUID.generate(); + attributes.addAttribute(attributeNamespace, attributeName, attributePrefix+":"+attributeName, attributeType, attributeValue); + + // Output the rules element + writer.startElement(namespace, name, prefix+":"+name, attributes); + + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + String childName = reader.getName(); + if (childName.equals("aspects") == true) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + // Add titled aspect + writer.startElement( + ContentModel.ASPECT_TITLED.getNamespaceURI(), + ContentModel.ASPECT_TITLED.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.ASPECT_TITLED.getLocalName(), + new AttributesImpl()); + writer.endElement( + ContentModel.ASPECT_TITLED.getNamespaceURI(), + ContentModel.ASPECT_TITLED.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.ASPECT_TITLED.getLocalName()); + + // Read the rest of the elements and output + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + } + } + + }, false); + } + else if (childName.equals("properties") == true) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + String propName = reader.getName(); + if (propName.equals("actionDescription") == true) + { + writer.startElement( + ContentModel.PROP_DESCRIPTION.getNamespaceURI(), + ContentModel.PROP_DESCRIPTION.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_DESCRIPTION.getLocalName(), + new AttributesImpl()); + + // Output the value within + new OutputChildren().doWork(reader, writer); + + writer.endElement( + ContentModel.PROP_DESCRIPTION.getNamespaceURI(), + ContentModel.PROP_DESCRIPTION.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_DESCRIPTION.getLocalName()); + eventType = reader.next(); + + } + else if (propName.equals("actionTitle") == true) + { + writer.startElement( + ContentModel.PROP_TITLE.getNamespaceURI(), + ContentModel.PROP_TITLE.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_TITLE.getLocalName(), + new AttributesImpl()); + + // Output the value within + new OutputChildren().doWork(reader, writer); + + writer.endElement( + ContentModel.PROP_TITLE.getNamespaceURI(), + ContentModel.PROP_TITLE.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_TITLE.getLocalName()); + eventType = reader.next(); + } + else if (propName.equals("executeAsynchronously") == true) + { + writer.startElement( + RuleModel.PROP_EXECUTE_ASYNC.getNamespaceURI(), + RuleModel.PROP_EXECUTE_ASYNC.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_EXECUTE_ASYNC.getLocalName(), + new AttributesImpl()); + + // Output the value within + new OutputChildren().doWork(reader, writer); + + writer.endElement( + RuleModel.PROP_EXECUTE_ASYNC.getNamespaceURI(), + RuleModel.PROP_EXECUTE_ASYNC.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_EXECUTE_ASYNC.getLocalName()); + eventType = reader.next(); + } + else if (propName.equals("ruleType") == true) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + // Output the elements that contain a multi values property + writer.startElement(NamespaceService.REPOSITORY_VIEW_1_0_URI, "values", "view:values", new AttributesImpl()); + writer.startElement(NamespaceService.REPOSITORY_VIEW_1_0_URI, "value", "view:value", new AttributesImpl()); + + // Output the value within + new OutputChildren().doWork(reader, writer); + + // End the multi values elements + writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "value", "view:value"); + writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "values", "view:values"); + } + }, false); + } + else if (propName.equals("definitionName") == true) + { + // Skip past next end + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + } + eventType = reader.next(); + } + else + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + } + } + + // Output value for the disabled property + writer.startElement( + RuleModel.PROP_DISABLED.getNamespaceURI(), + RuleModel.PROP_DISABLED.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_DISABLED.getLocalName(), + new AttributesImpl()); + writer.write("false"); + writer.endElement( + RuleModel.PROP_DISABLED.getNamespaceURI(), + RuleModel.PROP_DISABLED.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_DISABLED.getLocalName()); + } + }, false); + } + else if (childName.equals("associations") == true) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + // + writer.startElement( + RuleModel.ASSOC_ACTION.getNamespaceURI(), + RuleModel.ASSOC_ACTION.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.ASSOC_ACTION.getLocalName(), + new AttributesImpl()); + + // + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, "childName", "view:childName", null, "rule:action"); + writer.startElement( + ActionModel.TYPE_COMPOSITE_ACTION.getNamespaceURI(), + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName(), + ActionModel.ACTION_MODEL_PREFIX+ ":" + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName(), + attributes); + + // + writer.startElement( + NamespaceService.REPOSITORY_VIEW_1_0_URI, + "properties", + "view:properties", + new AttributesImpl()); + + // composite-action + writer.startElement( + ActionModel.PROP_DEFINITION_NAME.getNamespaceURI(), + ActionModel.PROP_DEFINITION_NAME.getLocalName(), + ActionModel.ACTION_MODEL_PREFIX + ":" + ActionModel.PROP_DEFINITION_NAME.getLocalName(), + new AttributesImpl()); + writer.write("composite-action"); + writer.endElement( + ActionModel.PROP_DEFINITION_NAME.getNamespaceURI(), + ActionModel.PROP_DEFINITION_NAME.getLocalName(), + ActionModel.ACTION_MODEL_PREFIX + ":" + ActionModel.PROP_DEFINITION_NAME.getLocalName()); + + // + writer.endElement( + NamespaceService.REPOSITORY_VIEW_1_0_URI, + "properties", + "view:properties"); + + // + writer.startElement( + NamespaceService.REPOSITORY_VIEW_1_0_URI, + "associations", + "view:associations", + new AttributesImpl()); + + // Output the association details + new OutputChildren().doWork(reader, writer); + + // + writer.endElement( + NamespaceService.REPOSITORY_VIEW_1_0_URI, + "associations", + "view:associations"); + + // + writer.endElement( + ActionModel.TYPE_COMPOSITE_ACTION.getNamespaceURI(), + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName(), + ActionModel.ACTION_MODEL_PREFIX+ ":" + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName()); + + // + writer.endElement( + RuleModel.ASSOC_ACTION.getNamespaceURI(), + RuleModel.ASSOC_ACTION.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.ASSOC_ACTION.getLocalName()); + } + }, false); + } + else + { + // Output anything else that might be hanging araound + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + } + } + + // End the rules element + writer.endElement(namespace, name, prefix+":"+name); + } + } + + public static void main(String[] args) + { + if (args.length == 2) + { + ImportFileUpdater util = new ImportFileUpdater(); + util.updateImportFile(args[0], args[1]); + } + else if (args.length == 3) + { + ImportFileUpdater util = new ImportFileUpdater(); + util.setFileEncoding(args[2]); + util.updateImportFile(args[0], args[1]); + } + else + { + System.out.println(" ImportFileUpdater "); + System.out.println(" source - 1.3 import file name to be updated"); + System.out.println(" destination - name of the generated 1.4 import file"); + System.out.println(" file encoding (optional) - the file encoding, default is UTF-8"); + } + } + +} diff --git a/source/java/org/alfresco/repo/blog/BaseBlogIntegrationImplementation.java b/source/java/org/alfresco/repo/blog/BaseBlogIntegrationImplementation.java index d1a315e35d..124697ec9a 100644 --- a/source/java/org/alfresco/repo/blog/BaseBlogIntegrationImplementation.java +++ b/source/java/org/alfresco/repo/blog/BaseBlogIntegrationImplementation.java @@ -1,72 +1,72 @@ -package org.alfresco.repo.blog; - -/** - * Base blog implementation class. Extend this when writting a blog integration implementation. - * - * @author Roy Wetherall - */ -public abstract class BaseBlogIntegrationImplementation implements BlogIntegrationImplementation -{ - /** Blog integration service */ - private BlogIntegrationService blogIntegrationService; - - /** Integration name */ - private String name; - - /** Display name */ - private String displayName; - - /** - * Sets the blog integration service - * - * @param blogIntegrationService the blog integration service - */ - public void setBlogIntegrationService(BlogIntegrationService blogIntegrationService) - { - this.blogIntegrationService = blogIntegrationService; - } - - /** - * Registers the blog implementation with the blog integration service. - */ - public void register() - { - this.blogIntegrationService.register(this); - } - - /** - * Sets the name of the blog integration service - * - * @param name the name - */ - public void setName(String name) - { - this.name = name; - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationImplementation#getName() - */ - public String getName() - { - return this.name; - } - - /** - * Sets the display name - * - * @param displayName the display name - */ - public void setDisplayName(String displayName) - { - this.displayName = displayName; - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationImplementation#getDisplayName() - */ - public String getDisplayName() - { - return this.displayName; - } -} +package org.alfresco.repo.blog; + +/** + * Base blog implementation class. Extend this when writting a blog integration implementation. + * + * @author Roy Wetherall + */ +public abstract class BaseBlogIntegrationImplementation implements BlogIntegrationImplementation +{ + /** Blog integration service */ + private BlogIntegrationService blogIntegrationService; + + /** Integration name */ + private String name; + + /** Display name */ + private String displayName; + + /** + * Sets the blog integration service + * + * @param blogIntegrationService the blog integration service + */ + public void setBlogIntegrationService(BlogIntegrationService blogIntegrationService) + { + this.blogIntegrationService = blogIntegrationService; + } + + /** + * Registers the blog implementation with the blog integration service. + */ + public void register() + { + this.blogIntegrationService.register(this); + } + + /** + * Sets the name of the blog integration service + * + * @param name the name + */ + public void setName(String name) + { + this.name = name; + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationImplementation#getName() + */ + public String getName() + { + return this.name; + } + + /** + * Sets the display name + * + * @param displayName the display name + */ + public void setDisplayName(String displayName) + { + this.displayName = displayName; + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationImplementation#getDisplayName() + */ + public String getDisplayName() + { + return this.displayName; + } +} diff --git a/source/java/org/alfresco/repo/blog/BlogDetails.java b/source/java/org/alfresco/repo/blog/BlogDetails.java index 2f2d6e9123..f52cd91c78 100644 --- a/source/java/org/alfresco/repo/blog/BlogDetails.java +++ b/source/java/org/alfresco/repo/blog/BlogDetails.java @@ -1,189 +1,189 @@ -package org.alfresco.repo.blog; - -import java.io.Serializable; -import java.util.Map; - -import org.alfresco.model.BlogIntegrationModel; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -/** - * Blog details. Contains the detail of a blog. - * - * @author Roy Wetherall - */ -public class BlogDetails implements BlogIntegrationModel -{ - /** Node that has the blog details aspect applied */ - private NodeRef nodeRef; - - /** The blog implementation name (eg: wordpress, typepad, etc) */ - private String implementationName; - - /** The blog id */ - private String blogId; - - /** The blog URL */ - private String url; - - /** The user name */ - private String userName; - - /** The password */ - private String password; - - /** The display name of the blog */ - private String name; - - /** The description of the blog */ - private String description; - - /** - * Create a BlogDetails object from a node that has the blogDetails aspect applied. - * - * @param nodeService the node service - * @param nodeRef the node reference - * @return BlogDetails the blog details - */ - public static BlogDetails createBlogDetails(NodeService nodeService, NodeRef nodeRef) - { - // Check for the blog details aspect - if (nodeService.hasAspect(nodeRef, ASPECT_BLOG_DETAILS) == false) - { - throw new BlogIntegrationRuntimeException("Can not create blog details object since node does not have blogDetails aspect."); - } - - // Get the blog details - Map props = nodeService.getProperties(nodeRef); - return new BlogDetails( - (String)props.get(PROP_BLOG_IMPLEMENTATION), - (String)props.get(PROP_ID), - (String)props.get(PROP_URL), - (String)props.get(PROP_USER_NAME), - (String)props.get(PROP_PASSWORD), - (String)props.get(PROP_NAME), - (String)props.get(PROP_DESCRIPTION), - nodeRef); - } - - /** - * Constructor - * - * @param implementationName the implementation name - * @param blogId the blog id - * @param url the blog URL - * @param userName the user name - * @param password the password - * @param name the name - * @param description the description - */ - public BlogDetails(String implementationName, String blogId, String url, String userName, String password, String name, String description) - { - this(implementationName, blogId, url, userName, password, name, description, null); - } - - /** - * Constructor - * - * @param implementationName the implementation name - * @param blogId the blog id - * @param url the blog URL - * @param userName the user name - * @param password the password - * @param name the name - * @param description the description - * @param nodeRef the node reference - */ - public BlogDetails(String implementationName, String blogId, String url, String userName, String password, String name, String description, NodeRef nodeRef) - { - this.implementationName = implementationName; - this.blogId = blogId; - this.url = url; - this.userName = userName; - this.password = password; - this.name = name; - this.description = description; - this.nodeRef = nodeRef; - } - - /** - * Gets the node reference - * - * @return NodeRef the node reference - */ - public NodeRef getNodeRef() - { - return nodeRef; - } - - /** - * Get the implementation name - * - * @return String the implementation name - */ - public String getImplementationName() - { - return this.implementationName; - } - - /** - * Get the blog id - * - * @return String the blog id - */ - public String getBlogId() - { - return this.blogId; - } - - /** - * Get the blog URL - * - * @return String the blog URL - */ - public String getUrl() - { - return this.url; - } - - /** - * Get the user name - * - * @return String the user name - */ - public String getUserName() - { - return this.userName; - } - - /** - * Get the password - * - * @return String the password - */ - public String getPassword() - { - return this.password; - } - - /** - * Get the name - * - * @return String the name - */ - public String getName() - { - return name; - } - - /** - * Get the description - * - * @return String the description - */ - public String getDescription() - { - return description; - } -} +package org.alfresco.repo.blog; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.model.BlogIntegrationModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Blog details. Contains the detail of a blog. + * + * @author Roy Wetherall + */ +public class BlogDetails implements BlogIntegrationModel +{ + /** Node that has the blog details aspect applied */ + private NodeRef nodeRef; + + /** The blog implementation name (eg: wordpress, typepad, etc) */ + private String implementationName; + + /** The blog id */ + private String blogId; + + /** The blog URL */ + private String url; + + /** The user name */ + private String userName; + + /** The password */ + private String password; + + /** The display name of the blog */ + private String name; + + /** The description of the blog */ + private String description; + + /** + * Create a BlogDetails object from a node that has the blogDetails aspect applied. + * + * @param nodeService the node service + * @param nodeRef the node reference + * @return BlogDetails the blog details + */ + public static BlogDetails createBlogDetails(NodeService nodeService, NodeRef nodeRef) + { + // Check for the blog details aspect + if (nodeService.hasAspect(nodeRef, ASPECT_BLOG_DETAILS) == false) + { + throw new BlogIntegrationRuntimeException("Can not create blog details object since node does not have blogDetails aspect."); + } + + // Get the blog details + Map props = nodeService.getProperties(nodeRef); + return new BlogDetails( + (String)props.get(PROP_BLOG_IMPLEMENTATION), + (String)props.get(PROP_ID), + (String)props.get(PROP_URL), + (String)props.get(PROP_USER_NAME), + (String)props.get(PROP_PASSWORD), + (String)props.get(PROP_NAME), + (String)props.get(PROP_DESCRIPTION), + nodeRef); + } + + /** + * Constructor + * + * @param implementationName the implementation name + * @param blogId the blog id + * @param url the blog URL + * @param userName the user name + * @param password the password + * @param name the name + * @param description the description + */ + public BlogDetails(String implementationName, String blogId, String url, String userName, String password, String name, String description) + { + this(implementationName, blogId, url, userName, password, name, description, null); + } + + /** + * Constructor + * + * @param implementationName the implementation name + * @param blogId the blog id + * @param url the blog URL + * @param userName the user name + * @param password the password + * @param name the name + * @param description the description + * @param nodeRef the node reference + */ + public BlogDetails(String implementationName, String blogId, String url, String userName, String password, String name, String description, NodeRef nodeRef) + { + this.implementationName = implementationName; + this.blogId = blogId; + this.url = url; + this.userName = userName; + this.password = password; + this.name = name; + this.description = description; + this.nodeRef = nodeRef; + } + + /** + * Gets the node reference + * + * @return NodeRef the node reference + */ + public NodeRef getNodeRef() + { + return nodeRef; + } + + /** + * Get the implementation name + * + * @return String the implementation name + */ + public String getImplementationName() + { + return this.implementationName; + } + + /** + * Get the blog id + * + * @return String the blog id + */ + public String getBlogId() + { + return this.blogId; + } + + /** + * Get the blog URL + * + * @return String the blog URL + */ + public String getUrl() + { + return this.url; + } + + /** + * Get the user name + * + * @return String the user name + */ + public String getUserName() + { + return this.userName; + } + + /** + * Get the password + * + * @return String the password + */ + public String getPassword() + { + return this.password; + } + + /** + * Get the name + * + * @return String the name + */ + public String getName() + { + return name; + } + + /** + * Get the description + * + * @return String the description + */ + public String getDescription() + { + return description; + } +} diff --git a/source/java/org/alfresco/repo/blog/BlogIntegrationImplementation.java b/source/java/org/alfresco/repo/blog/BlogIntegrationImplementation.java index 2074de76e0..b818b9f98e 100644 --- a/source/java/org/alfresco/repo/blog/BlogIntegrationImplementation.java +++ b/source/java/org/alfresco/repo/blog/BlogIntegrationImplementation.java @@ -1,66 +1,66 @@ -package org.alfresco.repo.blog; - -import java.util.Map; - -/** - * Blog integration implementation interface - * - * @author Roy Wetherall - */ -public interface BlogIntegrationImplementation -{ - /** - * Gets the name of the blog integration - * - * @return String the name of the blog integration - */ - String getName(); - - /** - * Gets the display name of the blog integration - * - * @return String the display name of the blog integration - */ - String getDisplayName(); - - /** - * Create a new post on the blog. - * - * @param blogDetails the blog details - * @param title the title of the post - * @param body the body of the post - * @param publish indicates whether the post is published or not - * @return String the newly created post id - */ - String newPost(BlogDetails blogDetails, String title, String body, boolean publish); - - /** - * Update an exisiting blog post - * - * @param blogDetails BlogDetails - * @param postId String - * @param title String - * @param body String - * @param publish boolean - * @return boolean - */ - boolean updatePost(BlogDetails blogDetails, String postId, String title, String body, boolean publish); - - /** - * Get the details of an existing blog post - * - * @param blogDetails BlogDetails - * @param postId String - * @return Map - */ - Map getPost(BlogDetails blogDetails, String postId); - - /** - * Delete an existing blog post - * - * @param blogDetails BlogDetails - * @param postId String - * @return boolean - */ - boolean deletePost(BlogDetails blogDetails, String postId); -} +package org.alfresco.repo.blog; + +import java.util.Map; + +/** + * Blog integration implementation interface + * + * @author Roy Wetherall + */ +public interface BlogIntegrationImplementation +{ + /** + * Gets the name of the blog integration + * + * @return String the name of the blog integration + */ + String getName(); + + /** + * Gets the display name of the blog integration + * + * @return String the display name of the blog integration + */ + String getDisplayName(); + + /** + * Create a new post on the blog. + * + * @param blogDetails the blog details + * @param title the title of the post + * @param body the body of the post + * @param publish indicates whether the post is published or not + * @return String the newly created post id + */ + String newPost(BlogDetails blogDetails, String title, String body, boolean publish); + + /** + * Update an exisiting blog post + * + * @param blogDetails BlogDetails + * @param postId String + * @param title String + * @param body String + * @param publish boolean + * @return boolean + */ + boolean updatePost(BlogDetails blogDetails, String postId, String title, String body, boolean publish); + + /** + * Get the details of an existing blog post + * + * @param blogDetails BlogDetails + * @param postId String + * @return Map + */ + Map getPost(BlogDetails blogDetails, String postId); + + /** + * Delete an existing blog post + * + * @param blogDetails BlogDetails + * @param postId String + * @return boolean + */ + boolean deletePost(BlogDetails blogDetails, String postId); +} diff --git a/source/java/org/alfresco/repo/blog/BlogIntegrationRuntimeException.java b/source/java/org/alfresco/repo/blog/BlogIntegrationRuntimeException.java index 0630edbeec..dfd9910a38 100644 --- a/source/java/org/alfresco/repo/blog/BlogIntegrationRuntimeException.java +++ b/source/java/org/alfresco/repo/blog/BlogIntegrationRuntimeException.java @@ -1,58 +1,58 @@ -package org.alfresco.repo.blog; - -import org.alfresco.error.AlfrescoRuntimeException; - -/** - * Blog integration runtime exception - * - * @author Roy Wetherall - */ -public class BlogIntegrationRuntimeException extends AlfrescoRuntimeException -{ - private static final long serialVersionUID = -159901552962025003L; - - /** - * Constructor - * - * @param msgId String - */ - public BlogIntegrationRuntimeException(String msgId) - { - super(msgId); - } - - /** - * Constructor - * - * @param msgId String - * @param msgParams Object[] - */ - public BlogIntegrationRuntimeException(String msgId, Object[] msgParams) - { - super(msgId, msgParams); - } - - /** - * Constructor - * - * @param msgId String - * @param cause Throwable - */ - public BlogIntegrationRuntimeException(String msgId, Throwable cause) - { - super(msgId, cause); - } - - /** - * Constructor - * - * @param msgId String - * @param msgParams Object[] - * @param cause Throwable - */ - public BlogIntegrationRuntimeException(String msgId, Object[] msgParams, Throwable cause) - { - super(msgId, msgParams, cause); - } - -} +package org.alfresco.repo.blog; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Blog integration runtime exception + * + * @author Roy Wetherall + */ +public class BlogIntegrationRuntimeException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -159901552962025003L; + + /** + * Constructor + * + * @param msgId String + */ + public BlogIntegrationRuntimeException(String msgId) + { + super(msgId); + } + + /** + * Constructor + * + * @param msgId String + * @param msgParams Object[] + */ + public BlogIntegrationRuntimeException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + /** + * Constructor + * + * @param msgId String + * @param cause Throwable + */ + public BlogIntegrationRuntimeException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + /** + * Constructor + * + * @param msgId String + * @param msgParams Object[] + * @param cause Throwable + */ + public BlogIntegrationRuntimeException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + +} diff --git a/source/java/org/alfresco/repo/blog/BlogIntegrationService.java b/source/java/org/alfresco/repo/blog/BlogIntegrationService.java index eadda03971..78596f3f8e 100644 --- a/source/java/org/alfresco/repo/blog/BlogIntegrationService.java +++ b/source/java/org/alfresco/repo/blog/BlogIntegrationService.java @@ -1,72 +1,72 @@ -package org.alfresco.repo.blog; - -import java.util.List; - -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; - -/** - * Blog integration service. - * - * @author Roy Wetherall - * - */ -public interface BlogIntegrationService -{ - /** - * Register a new blog integration implementation with the service - * - * @param implementation the implementation - */ - void register(BlogIntegrationImplementation implementation); - - /** - * Get the named blog integration implementation, null if name not recognised - * - * @param implementationName the implementation name - * @return BlogIntegrationImplementation the blog integration implementation - */ - BlogIntegrationImplementation getBlogIntegrationImplementation(String implementationName); - - /** - * Get a list of the registered integration implementations. - * - * @return List list of registered blog integration implementations - */ - List getBlogIntegrationImplementations(); - - /** - * Given a node reference, gets a list of 'in scope' BlogDetails. - * - * The node itself and then the primary parent hierarchy is searched and any blog details found returned in - * a list, with the 'nearest' first. - * - * @param nodeRef the node reference - * @return List list of the blog details found 'in scope' for the node, empty if none found - */ - List getBlogDetails(NodeRef nodeRef); - - /** - * Posts the content of a node to the blog specified - * - * @param blogDetails BlogDetails - * @param nodeRef NodeRef - * @param contentProperty QName - * @param publish boolean - */ - void newPost(BlogDetails blogDetails, NodeRef nodeRef, QName contentProperty, boolean publish); - - /** - * - * @param nodeRef NodeRef - * @param contentProperty QName - * @param publish boolean - */ - void updatePost(NodeRef nodeRef, QName contentProperty, boolean publish); - - /** - * - * @param nodeRef NodeRef - */ - void deletePost(NodeRef nodeRef); -} +package org.alfresco.repo.blog; + +import java.util.List; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * Blog integration service. + * + * @author Roy Wetherall + * + */ +public interface BlogIntegrationService +{ + /** + * Register a new blog integration implementation with the service + * + * @param implementation the implementation + */ + void register(BlogIntegrationImplementation implementation); + + /** + * Get the named blog integration implementation, null if name not recognised + * + * @param implementationName the implementation name + * @return BlogIntegrationImplementation the blog integration implementation + */ + BlogIntegrationImplementation getBlogIntegrationImplementation(String implementationName); + + /** + * Get a list of the registered integration implementations. + * + * @return List list of registered blog integration implementations + */ + List getBlogIntegrationImplementations(); + + /** + * Given a node reference, gets a list of 'in scope' BlogDetails. + * + * The node itself and then the primary parent hierarchy is searched and any blog details found returned in + * a list, with the 'nearest' first. + * + * @param nodeRef the node reference + * @return List list of the blog details found 'in scope' for the node, empty if none found + */ + List getBlogDetails(NodeRef nodeRef); + + /** + * Posts the content of a node to the blog specified + * + * @param blogDetails BlogDetails + * @param nodeRef NodeRef + * @param contentProperty QName + * @param publish boolean + */ + void newPost(BlogDetails blogDetails, NodeRef nodeRef, QName contentProperty, boolean publish); + + /** + * + * @param nodeRef NodeRef + * @param contentProperty QName + * @param publish boolean + */ + void updatePost(NodeRef nodeRef, QName contentProperty, boolean publish); + + /** + * + * @param nodeRef NodeRef + */ + void deletePost(NodeRef nodeRef); +} diff --git a/source/java/org/alfresco/repo/blog/BlogIntegrationServiceImpl.java b/source/java/org/alfresco/repo/blog/BlogIntegrationServiceImpl.java index c0e4dcbed5..667587fe3f 100644 --- a/source/java/org/alfresco/repo/blog/BlogIntegrationServiceImpl.java +++ b/source/java/org/alfresco/repo/blog/BlogIntegrationServiceImpl.java @@ -1,351 +1,351 @@ -package org.alfresco.repo.blog; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.BlogIntegrationModel; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -/** - * Blog integration service implementation - * - * @author Roy Wetherall - */ -public class BlogIntegrationServiceImpl implements BlogIntegrationService, BlogIntegrationModel -{ - /** Node service */ - private NodeService nodeService; - - /** Content service */ - private ContentService contentService; - - /** Registered blog integration implemenatations */ - private Map implementations = new HashMap(5); - - /** Supported mimetypes */ - public static List supportedMimetypes = new ArrayList(5); - - /** Static initialisation of supported mimetypes */ - static - { - supportedMimetypes.add(MimetypeMap.MIMETYPE_TEXT_PLAIN); - supportedMimetypes.add(MimetypeMap.MIMETYPE_HTML); - } - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the content service - * - * @param contentService the content service - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationService#register(org.alfresco.repo.blog.BlogIntegrationImplementation) - */ - public void register(BlogIntegrationImplementation implementation) - { - if (this.implementations.containsKey(implementation.getName()) == true) - { - throw new BlogIntegrationRuntimeException("A blog implementation with name '" + implementation.getName() + "' has already been registered."); - } - this.implementations.put(implementation.getName(), implementation); - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationService#getBlogIntegrationImplementation(java.lang.String) - */ - public BlogIntegrationImplementation getBlogIntegrationImplementation(String implementationName) - { - return this.implementations.get(implementationName); - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationService#getBlogIntegrationImplementations() - */ - public List getBlogIntegrationImplementations() - { - return new ArrayList(this.implementations.values()); - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationService#getBlogDetails(org.alfresco.service.cmr.repository.NodeRef) - */ - public List getBlogDetails(NodeRef nodeRef) - { - List result = new ArrayList(5); - - // First check the node itself - if (this.nodeService.hasAspect(nodeRef, ASPECT_BLOG_DETAILS) == true) - { - result.add(BlogDetails.createBlogDetails(this.nodeService, nodeRef)); - } - - // Now walk up the parent hiearchy adding details as they are found - getBlogDetailsImpl(nodeRef, result); - return result; - } - - /** - * Helper method that recurses up the primary parent hierarchy checking for - * blog details - * - * @param nodeRef the node reference - * @param blogDetails list of blog details - */ - private void getBlogDetailsImpl(NodeRef nodeRef, List blogDetails) - { - // Check the parent assoc - ChildAssociationRef parentAssoc = this.nodeService.getPrimaryParent(nodeRef); - if (parentAssoc != null) - { - // Check for the blog details - NodeRef parent = parentAssoc.getParentRef(); - if (parent != null) - { - if (this.nodeService.hasAspect(parent, ASPECT_BLOG_DETAILS) == true) - { - blogDetails.add(BlogDetails.createBlogDetails(this.nodeService, parent)); - } - - // Recurse - getBlogDetailsImpl(parent, blogDetails); - } - } - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationService#newPost(BlogDetails, NodeRef, QName, boolean) - */ - public void newPost(BlogDetails blogDetails, NodeRef nodeRef, QName contentProperty, boolean publish) - { - // Get the blog implementation - BlogIntegrationImplementation implementation = getImplementation(blogDetails.getImplementationName()); - - // Check that this node has not already been posted to a blog - if (this.nodeService.hasAspect(nodeRef, ASPECT_BLOG_POST) == true) - { - throw new BlogIntegrationRuntimeException("Can not create new blog post since this conten has already been posted to a blog."); - } - - // Get the posts body - ContentReader contentReader = this.contentService.getReader(nodeRef, contentProperty); - if (contentReader == null) - { - throw new BlogIntegrationRuntimeException("No content found for new blog entry."); - } - - // Check the mimetype - String body = null; - if (supportedMimetypes.contains(contentReader.getMimetype()) == true) - { - // Get the content - body = contentReader.getContentString(); - } - else - { - throw new BlogIntegrationRuntimeException("The content mimetype '" + contentReader.getMimetype() + "' is not supported."); - } - - // Get the posts title - String title = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); - if (title == null || title.length() == 0) - { - if (body.length() > 23) - { - // Get the title from the first 22 character plus ' ...' - title = body.substring(0, 23) + " ..."; - } - else - { - title = body; - } - } - - // Post the new blog entry - String postId = implementation.newPost(blogDetails, title, body, true); - - // Get the blog details node if the is one - NodeRef blogDetailsNodeRef = blogDetails.getNodeRef(); - if (blogDetailsNodeRef != null) - { - // Now get the details of the newly created post - Map details = implementation.getPost(blogDetails, postId); - String link = (String)details.get("link"); - - // Add the details of the new post to the node - Map props = new HashMap(5); - props.put(PROP_POST_ID, postId); - if (link != null) - { - props.put(PROP_LINK, link); - } - Date now = new Date(); - props.put(PROP_POSTED, now); - props.put(PROP_LAST_UPDATE, now); - props.put(PROP_PUBLISHED, Boolean.valueOf(publish)); - this.nodeService.addAspect(nodeRef, ASPECT_BLOG_POST, props); - - // Associate to the blog details - this.nodeService.createAssociation(nodeRef, blogDetailsNodeRef, ASSOC_BLOG_DETAILS); - } - } - - /** - * Gets the blog implementation based on its name - * - * @param implementationName the implementation name - * @return BlogIntegrationImplementation the blog integration - */ - private BlogIntegrationImplementation getImplementation(String implementationName) - { - if (this.implementations.containsKey(implementationName) == false) - { - throw new BlogIntegrationRuntimeException("There is no blog implementation present for '" + implementationName + "'"); - } - return this.implementations.get(implementationName); - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationService#updatePost(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, boolean) - */ - public void updatePost(NodeRef nodeRef, QName contentProperty, boolean publish) - { - // Get the blog details and post id - BlogDetails blogDetails = null; - String postId = null; - if (this.nodeService.hasAspect(nodeRef, ASPECT_BLOG_POST) == true) - { - List assocs = this.nodeService.getTargetAssocs(nodeRef, ASSOC_BLOG_DETAILS); - if (assocs.size() == 0) - { - throw new BlogIntegrationRuntimeException("Can not resolve blog details for update because blogDetails association is not populated."); - } - else - { - blogDetails = BlogDetails.createBlogDetails(this.nodeService, assocs.get(0).getTargetRef()); - postId = (String)this.nodeService.getProperty(nodeRef, PROP_POST_ID); - } - } - else - { - throw new BlogIntegrationRuntimeException("Can not update blog post as this node has not been previously posted to a blog."); - } - - // Get the blog implementation - BlogIntegrationImplementation implementation = getImplementation(blogDetails.getImplementationName()); - - // Get the posts title - String title = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); - if (title == null || title.length() == 0) - { - throw new BlogIntegrationRuntimeException("No title available for update blog post. Set the title property and re-try."); - } - - // Get the posts body - ContentReader contentReader = this.contentService.getReader(nodeRef, contentProperty); - if (contentReader == null) - { - throw new BlogIntegrationRuntimeException("No content found for update blog entry."); - } - - // Check the mimetype - String body = null; - if (supportedMimetypes.contains(contentReader.getMimetype()) == true) - { - // Get the content - body = contentReader.getContentString(); - } - else - { - throw new BlogIntegrationRuntimeException("The content mimetype '" + contentReader.getMimetype() + "' is not supported."); - } - - // Update the blog post - boolean result = implementation.updatePost(blogDetails, postId, title, body, publish); - - // Check the return result - if (result == false) - { - throw new BlogIntegrationRuntimeException("The update of the post unexpectedly failed. Check your blog for more information."); - } - - // Now get the details of the newly created post - Map details = implementation.getPost(blogDetails, postId); - String link = (String)details.get("link"); - - // Update the post details accordingly - Map props = this.nodeService.getProperties(nodeRef); - Date now = new Date(); - props.put(PROP_LAST_UPDATE, now); - props.put(PROP_PUBLISHED, Boolean.valueOf(publish)); - props.put(PROP_LINK, link); - this.nodeService.setProperties(nodeRef, props); - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationService#deletePost(org.alfresco.service.cmr.repository.NodeRef) - */ - public void deletePost(NodeRef nodeRef) - { - // Get the blog details and post id - BlogDetails blogDetails = null; - String postId = null; - if (this.nodeService.hasAspect(nodeRef, ASPECT_BLOG_POST) == true) - { - List assocs = this.nodeService.getTargetAssocs(nodeRef, ASSOC_BLOG_DETAILS); - if (assocs.size() == 0) - { - throw new BlogIntegrationRuntimeException("Can not resolve blog details for delete because blogDetails association is not populated."); - } - else - { - blogDetails = BlogDetails.createBlogDetails(this.nodeService, assocs.get(0).getTargetRef()); - postId = (String)this.nodeService.getProperty(nodeRef, PROP_POST_ID); - } - } - else - { - throw new BlogIntegrationRuntimeException("Can not delete blog post as this node has not been previously posted to a blog."); - } - - // Get the blog implementation - BlogIntegrationImplementation implementation = getImplementation(blogDetails.getImplementationName()); - - // Delete the post - boolean result = implementation.deletePost(blogDetails, postId); - - // Check the return result - if (result == false) - { - throw new BlogIntegrationRuntimeException("Deleting the post unexpectedly failed. Check your blog for more information."); - } - - // Remove the postDetails aspect from the node - this.nodeService.removeAspect(nodeRef, ASPECT_BLOG_POST); - } -} +package org.alfresco.repo.blog; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.BlogIntegrationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Blog integration service implementation + * + * @author Roy Wetherall + */ +public class BlogIntegrationServiceImpl implements BlogIntegrationService, BlogIntegrationModel +{ + /** Node service */ + private NodeService nodeService; + + /** Content service */ + private ContentService contentService; + + /** Registered blog integration implemenatations */ + private Map implementations = new HashMap(5); + + /** Supported mimetypes */ + public static List supportedMimetypes = new ArrayList(5); + + /** Static initialisation of supported mimetypes */ + static + { + supportedMimetypes.add(MimetypeMap.MIMETYPE_TEXT_PLAIN); + supportedMimetypes.add(MimetypeMap.MIMETYPE_HTML); + } + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the content service + * + * @param contentService the content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationService#register(org.alfresco.repo.blog.BlogIntegrationImplementation) + */ + public void register(BlogIntegrationImplementation implementation) + { + if (this.implementations.containsKey(implementation.getName()) == true) + { + throw new BlogIntegrationRuntimeException("A blog implementation with name '" + implementation.getName() + "' has already been registered."); + } + this.implementations.put(implementation.getName(), implementation); + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationService#getBlogIntegrationImplementation(java.lang.String) + */ + public BlogIntegrationImplementation getBlogIntegrationImplementation(String implementationName) + { + return this.implementations.get(implementationName); + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationService#getBlogIntegrationImplementations() + */ + public List getBlogIntegrationImplementations() + { + return new ArrayList(this.implementations.values()); + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationService#getBlogDetails(org.alfresco.service.cmr.repository.NodeRef) + */ + public List getBlogDetails(NodeRef nodeRef) + { + List result = new ArrayList(5); + + // First check the node itself + if (this.nodeService.hasAspect(nodeRef, ASPECT_BLOG_DETAILS) == true) + { + result.add(BlogDetails.createBlogDetails(this.nodeService, nodeRef)); + } + + // Now walk up the parent hiearchy adding details as they are found + getBlogDetailsImpl(nodeRef, result); + return result; + } + + /** + * Helper method that recurses up the primary parent hierarchy checking for + * blog details + * + * @param nodeRef the node reference + * @param blogDetails list of blog details + */ + private void getBlogDetailsImpl(NodeRef nodeRef, List blogDetails) + { + // Check the parent assoc + ChildAssociationRef parentAssoc = this.nodeService.getPrimaryParent(nodeRef); + if (parentAssoc != null) + { + // Check for the blog details + NodeRef parent = parentAssoc.getParentRef(); + if (parent != null) + { + if (this.nodeService.hasAspect(parent, ASPECT_BLOG_DETAILS) == true) + { + blogDetails.add(BlogDetails.createBlogDetails(this.nodeService, parent)); + } + + // Recurse + getBlogDetailsImpl(parent, blogDetails); + } + } + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationService#newPost(BlogDetails, NodeRef, QName, boolean) + */ + public void newPost(BlogDetails blogDetails, NodeRef nodeRef, QName contentProperty, boolean publish) + { + // Get the blog implementation + BlogIntegrationImplementation implementation = getImplementation(blogDetails.getImplementationName()); + + // Check that this node has not already been posted to a blog + if (this.nodeService.hasAspect(nodeRef, ASPECT_BLOG_POST) == true) + { + throw new BlogIntegrationRuntimeException("Can not create new blog post since this conten has already been posted to a blog."); + } + + // Get the posts body + ContentReader contentReader = this.contentService.getReader(nodeRef, contentProperty); + if (contentReader == null) + { + throw new BlogIntegrationRuntimeException("No content found for new blog entry."); + } + + // Check the mimetype + String body = null; + if (supportedMimetypes.contains(contentReader.getMimetype()) == true) + { + // Get the content + body = contentReader.getContentString(); + } + else + { + throw new BlogIntegrationRuntimeException("The content mimetype '" + contentReader.getMimetype() + "' is not supported."); + } + + // Get the posts title + String title = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); + if (title == null || title.length() == 0) + { + if (body.length() > 23) + { + // Get the title from the first 22 character plus ' ...' + title = body.substring(0, 23) + " ..."; + } + else + { + title = body; + } + } + + // Post the new blog entry + String postId = implementation.newPost(blogDetails, title, body, true); + + // Get the blog details node if the is one + NodeRef blogDetailsNodeRef = blogDetails.getNodeRef(); + if (blogDetailsNodeRef != null) + { + // Now get the details of the newly created post + Map details = implementation.getPost(blogDetails, postId); + String link = (String)details.get("link"); + + // Add the details of the new post to the node + Map props = new HashMap(5); + props.put(PROP_POST_ID, postId); + if (link != null) + { + props.put(PROP_LINK, link); + } + Date now = new Date(); + props.put(PROP_POSTED, now); + props.put(PROP_LAST_UPDATE, now); + props.put(PROP_PUBLISHED, Boolean.valueOf(publish)); + this.nodeService.addAspect(nodeRef, ASPECT_BLOG_POST, props); + + // Associate to the blog details + this.nodeService.createAssociation(nodeRef, blogDetailsNodeRef, ASSOC_BLOG_DETAILS); + } + } + + /** + * Gets the blog implementation based on its name + * + * @param implementationName the implementation name + * @return BlogIntegrationImplementation the blog integration + */ + private BlogIntegrationImplementation getImplementation(String implementationName) + { + if (this.implementations.containsKey(implementationName) == false) + { + throw new BlogIntegrationRuntimeException("There is no blog implementation present for '" + implementationName + "'"); + } + return this.implementations.get(implementationName); + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationService#updatePost(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, boolean) + */ + public void updatePost(NodeRef nodeRef, QName contentProperty, boolean publish) + { + // Get the blog details and post id + BlogDetails blogDetails = null; + String postId = null; + if (this.nodeService.hasAspect(nodeRef, ASPECT_BLOG_POST) == true) + { + List assocs = this.nodeService.getTargetAssocs(nodeRef, ASSOC_BLOG_DETAILS); + if (assocs.size() == 0) + { + throw new BlogIntegrationRuntimeException("Can not resolve blog details for update because blogDetails association is not populated."); + } + else + { + blogDetails = BlogDetails.createBlogDetails(this.nodeService, assocs.get(0).getTargetRef()); + postId = (String)this.nodeService.getProperty(nodeRef, PROP_POST_ID); + } + } + else + { + throw new BlogIntegrationRuntimeException("Can not update blog post as this node has not been previously posted to a blog."); + } + + // Get the blog implementation + BlogIntegrationImplementation implementation = getImplementation(blogDetails.getImplementationName()); + + // Get the posts title + String title = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); + if (title == null || title.length() == 0) + { + throw new BlogIntegrationRuntimeException("No title available for update blog post. Set the title property and re-try."); + } + + // Get the posts body + ContentReader contentReader = this.contentService.getReader(nodeRef, contentProperty); + if (contentReader == null) + { + throw new BlogIntegrationRuntimeException("No content found for update blog entry."); + } + + // Check the mimetype + String body = null; + if (supportedMimetypes.contains(contentReader.getMimetype()) == true) + { + // Get the content + body = contentReader.getContentString(); + } + else + { + throw new BlogIntegrationRuntimeException("The content mimetype '" + contentReader.getMimetype() + "' is not supported."); + } + + // Update the blog post + boolean result = implementation.updatePost(blogDetails, postId, title, body, publish); + + // Check the return result + if (result == false) + { + throw new BlogIntegrationRuntimeException("The update of the post unexpectedly failed. Check your blog for more information."); + } + + // Now get the details of the newly created post + Map details = implementation.getPost(blogDetails, postId); + String link = (String)details.get("link"); + + // Update the post details accordingly + Map props = this.nodeService.getProperties(nodeRef); + Date now = new Date(); + props.put(PROP_LAST_UPDATE, now); + props.put(PROP_PUBLISHED, Boolean.valueOf(publish)); + props.put(PROP_LINK, link); + this.nodeService.setProperties(nodeRef, props); + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationService#deletePost(org.alfresco.service.cmr.repository.NodeRef) + */ + public void deletePost(NodeRef nodeRef) + { + // Get the blog details and post id + BlogDetails blogDetails = null; + String postId = null; + if (this.nodeService.hasAspect(nodeRef, ASPECT_BLOG_POST) == true) + { + List assocs = this.nodeService.getTargetAssocs(nodeRef, ASSOC_BLOG_DETAILS); + if (assocs.size() == 0) + { + throw new BlogIntegrationRuntimeException("Can not resolve blog details for delete because blogDetails association is not populated."); + } + else + { + blogDetails = BlogDetails.createBlogDetails(this.nodeService, assocs.get(0).getTargetRef()); + postId = (String)this.nodeService.getProperty(nodeRef, PROP_POST_ID); + } + } + else + { + throw new BlogIntegrationRuntimeException("Can not delete blog post as this node has not been previously posted to a blog."); + } + + // Get the blog implementation + BlogIntegrationImplementation implementation = getImplementation(blogDetails.getImplementationName()); + + // Delete the post + boolean result = implementation.deletePost(blogDetails, postId); + + // Check the return result + if (result == false) + { + throw new BlogIntegrationRuntimeException("Deleting the post unexpectedly failed. Check your blog for more information."); + } + + // Remove the postDetails aspect from the node + this.nodeService.removeAspect(nodeRef, ASPECT_BLOG_POST); + } +} diff --git a/source/java/org/alfresco/repo/blog/DefaultBlogIntegrationImplementation.java b/source/java/org/alfresco/repo/blog/DefaultBlogIntegrationImplementation.java index 2466a681b2..aaf7c2e722 100644 --- a/source/java/org/alfresco/repo/blog/DefaultBlogIntegrationImplementation.java +++ b/source/java/org/alfresco/repo/blog/DefaultBlogIntegrationImplementation.java @@ -1,208 +1,208 @@ -package org.alfresco.repo.blog; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; - -import org.apache.xmlrpc.XmlRpcException; -import org.apache.xmlrpc.client.XmlRpcClient; -import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; - -/** - * Default blog integration implementation. Uses various standard XML PRC blogging API to satisfy the - * blog integration implementation interface. - * - * Based on origional contribution by Sudhakar Selvaraj. - * - * @author Roy Wetherall - */ -public abstract class DefaultBlogIntegrationImplementation extends BaseBlogIntegrationImplementation -{ - /** Blog actions */ - protected static final String ACTION_NEW_POST = "metaWeblog.newPost"; - protected static final String ACTION_EDIT_POST = "metaWeblog.editPost"; - protected static final String ACTION_GET_POST = "metaWeblog.getPost"; - protected static final String ACTION_DELETE_POST = "blogger.deletePost"; - - /** - * Gets the XML RPC end point URL for the given blog details. - * - * @param blogDetails blog details - * @return String the end point URL - */ - protected abstract String getEndpointURL(BlogDetails blogDetails); - - /** - * @see org.alfresco.repo.blog.BlogIntegrationImplementation#newPost(org.alfresco.repo.blog.BlogDetails, java.lang.String, java.lang.String, boolean) - */ - public String newPost(BlogDetails blogDetails, String title, String body, boolean publish) - { - // Create the hash table containing details of the post's content - Hashtable content = new Hashtable(); - content.put("title", title); - content.put("description", body); - - // Create a list of parameters - List params = new ArrayList(5); - params.add(blogDetails.getBlogId()); - params.add(blogDetails.getUserName()); - params.add(blogDetails.getPassword()); - params.add(content); - params.add(publish); - - // Create the new post - return (String)execute(getEndpointURL(blogDetails), ACTION_NEW_POST, params); - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationImplementation#updatePost(org.alfresco.repo.blog.BlogDetails, java.lang.String, java.lang.String, java.lang.String, boolean) - */ - public boolean updatePost(BlogDetails blogDetails, String postId, String title, String body, boolean publish) - { - // Create the hash table containing details of the post's content - Hashtable content = new Hashtable(); - content.put("title", title); - content.put("description", body); - - // Create a list of parameters - List params = new ArrayList(5); - params.add(postId); - params.add(blogDetails.getUserName()); - params.add(blogDetails.getPassword()); - params.add(content); - params.add(publish); - - // Create the new post - Object result = execute(getEndpointURL(blogDetails), ACTION_EDIT_POST, params); - - if (result.getClass().equals(Boolean.class)) - { - return ((Boolean)result).booleanValue(); - } - return false; - - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationImplementation#getPost(org.alfresco.repo.blog.BlogDetails, java.lang.String) - */ - @SuppressWarnings("unchecked") - public Map getPost(BlogDetails blogDetails, String postId) - { - // Create a list of parameters - List params = new ArrayList(3); - params.add(postId); - params.add(blogDetails.getUserName()); - params.add(blogDetails.getPassword()); - - // Get the post details - return (Map)execute(getEndpointURL(blogDetails), ACTION_GET_POST, params); - } - - /** - * @see org.alfresco.repo.blog.BlogIntegrationImplementation#deletePost(org.alfresco.repo.blog.BlogDetails, java.lang.String) - */ - public boolean deletePost(BlogDetails blogDetails, String postId) - { - // Create a list of parameters - List params = new ArrayList(5); - // Use the blog id for the app key - params.add(blogDetails.getBlogId()); - params.add(postId); - params.add(blogDetails.getUserName()); - params.add(blogDetails.getPassword()); - params.add(true); - - // Delete post - Object result = execute(getEndpointURL(blogDetails), ACTION_DELETE_POST, params); - if (result.getClass().equals(Boolean.class)) - { - return ((Boolean)result).booleanValue(); - } - return false; - } - - /** - * Helper method to get the XML RPC client - * - * @param url String - * @return XmlRpcClient - */ - private XmlRpcClient getClient(String url) - { - XmlRpcClient client = null; - try - { - client = new XmlRpcClient(); - XmlRpcClientConfigImpl conf = new XmlRpcClientConfigImpl(); - conf.setServerURL(new URL(url)); - conf.setEncoding("UTF-8"); - client.setConfig(conf); - } - catch (MalformedURLException exception) - { - throw new BlogIntegrationRuntimeException("Blog url '" + url + "' is invalid.", exception); - } - - return client; - - } - - /** - * Executes an XML RPC method - * - * @param url String - * @param method String - * @param params List - * @return Object - */ - protected Object execute(String url, String method, List params) - { - Object result = null; - - try - { - XmlRpcClient client = getClient(url); - result = client.execute(method, params); - } - catch (XmlRpcException exception) - { - throw new BlogIntegrationRuntimeException("Failed to execute blog action '" + method + "' @ url '" + url + "'", exception); - } - - return result; - } - - /** - * Checks a url for a protocol and adds http if none present - * - * @param url the url - * @return String the checked url - */ - protected String checkForProtocol(String url) - { - if (url.indexOf("://") == -1) - { - url = "http://" + url; - } - return url; - } - - /** - * Checks the url for a trailing slash and adds one if none present - * - * @param url the url - * @return String the checked url - */ - protected String checkForTrainlingSlash(String url) - { - if (url.endsWith("/") == false) - { - url = url + "/"; - } - return url; - } -} +package org.alfresco.repo.blog; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.apache.xmlrpc.XmlRpcException; +import org.apache.xmlrpc.client.XmlRpcClient; +import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; + +/** + * Default blog integration implementation. Uses various standard XML PRC blogging API to satisfy the + * blog integration implementation interface. + * + * Based on origional contribution by Sudhakar Selvaraj. + * + * @author Roy Wetherall + */ +public abstract class DefaultBlogIntegrationImplementation extends BaseBlogIntegrationImplementation +{ + /** Blog actions */ + protected static final String ACTION_NEW_POST = "metaWeblog.newPost"; + protected static final String ACTION_EDIT_POST = "metaWeblog.editPost"; + protected static final String ACTION_GET_POST = "metaWeblog.getPost"; + protected static final String ACTION_DELETE_POST = "blogger.deletePost"; + + /** + * Gets the XML RPC end point URL for the given blog details. + * + * @param blogDetails blog details + * @return String the end point URL + */ + protected abstract String getEndpointURL(BlogDetails blogDetails); + + /** + * @see org.alfresco.repo.blog.BlogIntegrationImplementation#newPost(org.alfresco.repo.blog.BlogDetails, java.lang.String, java.lang.String, boolean) + */ + public String newPost(BlogDetails blogDetails, String title, String body, boolean publish) + { + // Create the hash table containing details of the post's content + Hashtable content = new Hashtable(); + content.put("title", title); + content.put("description", body); + + // Create a list of parameters + List params = new ArrayList(5); + params.add(blogDetails.getBlogId()); + params.add(blogDetails.getUserName()); + params.add(blogDetails.getPassword()); + params.add(content); + params.add(publish); + + // Create the new post + return (String)execute(getEndpointURL(blogDetails), ACTION_NEW_POST, params); + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationImplementation#updatePost(org.alfresco.repo.blog.BlogDetails, java.lang.String, java.lang.String, java.lang.String, boolean) + */ + public boolean updatePost(BlogDetails blogDetails, String postId, String title, String body, boolean publish) + { + // Create the hash table containing details of the post's content + Hashtable content = new Hashtable(); + content.put("title", title); + content.put("description", body); + + // Create a list of parameters + List params = new ArrayList(5); + params.add(postId); + params.add(blogDetails.getUserName()); + params.add(blogDetails.getPassword()); + params.add(content); + params.add(publish); + + // Create the new post + Object result = execute(getEndpointURL(blogDetails), ACTION_EDIT_POST, params); + + if (result.getClass().equals(Boolean.class)) + { + return ((Boolean)result).booleanValue(); + } + return false; + + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationImplementation#getPost(org.alfresco.repo.blog.BlogDetails, java.lang.String) + */ + @SuppressWarnings("unchecked") + public Map getPost(BlogDetails blogDetails, String postId) + { + // Create a list of parameters + List params = new ArrayList(3); + params.add(postId); + params.add(blogDetails.getUserName()); + params.add(blogDetails.getPassword()); + + // Get the post details + return (Map)execute(getEndpointURL(blogDetails), ACTION_GET_POST, params); + } + + /** + * @see org.alfresco.repo.blog.BlogIntegrationImplementation#deletePost(org.alfresco.repo.blog.BlogDetails, java.lang.String) + */ + public boolean deletePost(BlogDetails blogDetails, String postId) + { + // Create a list of parameters + List params = new ArrayList(5); + // Use the blog id for the app key + params.add(blogDetails.getBlogId()); + params.add(postId); + params.add(blogDetails.getUserName()); + params.add(blogDetails.getPassword()); + params.add(true); + + // Delete post + Object result = execute(getEndpointURL(blogDetails), ACTION_DELETE_POST, params); + if (result.getClass().equals(Boolean.class)) + { + return ((Boolean)result).booleanValue(); + } + return false; + } + + /** + * Helper method to get the XML RPC client + * + * @param url String + * @return XmlRpcClient + */ + private XmlRpcClient getClient(String url) + { + XmlRpcClient client = null; + try + { + client = new XmlRpcClient(); + XmlRpcClientConfigImpl conf = new XmlRpcClientConfigImpl(); + conf.setServerURL(new URL(url)); + conf.setEncoding("UTF-8"); + client.setConfig(conf); + } + catch (MalformedURLException exception) + { + throw new BlogIntegrationRuntimeException("Blog url '" + url + "' is invalid.", exception); + } + + return client; + + } + + /** + * Executes an XML RPC method + * + * @param url String + * @param method String + * @param params List + * @return Object + */ + protected Object execute(String url, String method, List params) + { + Object result = null; + + try + { + XmlRpcClient client = getClient(url); + result = client.execute(method, params); + } + catch (XmlRpcException exception) + { + throw new BlogIntegrationRuntimeException("Failed to execute blog action '" + method + "' @ url '" + url + "'", exception); + } + + return result; + } + + /** + * Checks a url for a protocol and adds http if none present + * + * @param url the url + * @return String the checked url + */ + protected String checkForProtocol(String url) + { + if (url.indexOf("://") == -1) + { + url = "http://" + url; + } + return url; + } + + /** + * Checks the url for a trailing slash and adds one if none present + * + * @param url the url + * @return String the checked url + */ + protected String checkForTrainlingSlash(String url) + { + if (url.endsWith("/") == false) + { + url = url + "/"; + } + return url; + } +} diff --git a/source/java/org/alfresco/repo/blog/typepad/TypepadIntegration.java b/source/java/org/alfresco/repo/blog/typepad/TypepadIntegration.java index fe8a47f3d7..276d79a80f 100644 --- a/source/java/org/alfresco/repo/blog/typepad/TypepadIntegration.java +++ b/source/java/org/alfresco/repo/blog/typepad/TypepadIntegration.java @@ -1,36 +1,36 @@ -package org.alfresco.repo.blog.typepad; - -import org.alfresco.repo.blog.BlogDetails; -import org.alfresco.repo.blog.DefaultBlogIntegrationImplementation; - -/** - * Typepad integration implementation - * - * @author Roy Wetherall - */ -public class TypepadIntegration extends DefaultBlogIntegrationImplementation -{ - /** - * @see org.alfresco.repo.blog.DefaultBlogIntegrationImplementation#getEndpointURL(org.alfresco.repo.blog.BlogDetails) - */ - @Override - protected String getEndpointURL(BlogDetails blogDetails) - { - return "http://www.typepad.com/t/api"; - } - - /** - * For some reason typepad returns a hash table rather than the expected boolean result. - * - * @see org.alfresco.repo.blog.BlogIntegrationImplementation#deletePost(org.alfresco.repo.blog.BlogDetails, java.lang.String) - */ - @Override - public boolean deletePost(BlogDetails blogDetails, String postId) - { - // NOTE: At the time of testing typepad.com failed when making this call, for now the implementation is - // being overriden to return success - - return true; - } - -} +package org.alfresco.repo.blog.typepad; + +import org.alfresco.repo.blog.BlogDetails; +import org.alfresco.repo.blog.DefaultBlogIntegrationImplementation; + +/** + * Typepad integration implementation + * + * @author Roy Wetherall + */ +public class TypepadIntegration extends DefaultBlogIntegrationImplementation +{ + /** + * @see org.alfresco.repo.blog.DefaultBlogIntegrationImplementation#getEndpointURL(org.alfresco.repo.blog.BlogDetails) + */ + @Override + protected String getEndpointURL(BlogDetails blogDetails) + { + return "http://www.typepad.com/t/api"; + } + + /** + * For some reason typepad returns a hash table rather than the expected boolean result. + * + * @see org.alfresco.repo.blog.BlogIntegrationImplementation#deletePost(org.alfresco.repo.blog.BlogDetails, java.lang.String) + */ + @Override + public boolean deletePost(BlogDetails blogDetails, String postId) + { + // NOTE: At the time of testing typepad.com failed when making this call, for now the implementation is + // being overriden to return success + + return true; + } + +} diff --git a/source/java/org/alfresco/repo/blog/wordpress/WordPressIntegration.java b/source/java/org/alfresco/repo/blog/wordpress/WordPressIntegration.java index 8eff1d7eb2..2239b9e17c 100644 --- a/source/java/org/alfresco/repo/blog/wordpress/WordPressIntegration.java +++ b/source/java/org/alfresco/repo/blog/wordpress/WordPressIntegration.java @@ -1,23 +1,23 @@ -package org.alfresco.repo.blog.wordpress; - -import org.alfresco.repo.blog.BlogDetails; -import org.alfresco.repo.blog.DefaultBlogIntegrationImplementation; - -/** - * @author Roy Wetherall - */ -public class WordPressIntegration extends DefaultBlogIntegrationImplementation -{ - private static String ENDPOINT = "xmlrpc.php"; - - /** - * @see DefaultBlogIntegrationImplementation#getEndpointURL(BlogDetails) - */ - @Override - protected String getEndpointURL(BlogDetails blogDetails) - { - String endpoint = checkForProtocol(blogDetails.getUrl()); - return checkForTrainlingSlash(endpoint) + ENDPOINT; - } - -} +package org.alfresco.repo.blog.wordpress; + +import org.alfresco.repo.blog.BlogDetails; +import org.alfresco.repo.blog.DefaultBlogIntegrationImplementation; + +/** + * @author Roy Wetherall + */ +public class WordPressIntegration extends DefaultBlogIntegrationImplementation +{ + private static String ENDPOINT = "xmlrpc.php"; + + /** + * @see DefaultBlogIntegrationImplementation#getEndpointURL(BlogDetails) + */ + @Override + protected String getEndpointURL(BlogDetails blogDetails) + { + String endpoint = checkForProtocol(blogDetails.getUrl()); + return checkForTrainlingSlash(endpoint) + ENDPOINT; + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/AnalysedDirectory.java b/source/java/org/alfresco/repo/bulkimport/AnalysedDirectory.java index 29d18fc369..aeb665ead5 100644 --- a/source/java/org/alfresco/repo/bulkimport/AnalysedDirectory.java +++ b/source/java/org/alfresco/repo/bulkimport/AnalysedDirectory.java @@ -1,71 +1,71 @@ - -package org.alfresco.repo.bulkimport; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * This class represents the analysed contents of a directory. - * - * @since 4.0 - */ -public class AnalysedDirectory -{ - private List originalListing = null; - private Map importableItems = null; - private Map importableDirectories = null; - - public AnalysedDirectory(File[] files) - { - originalListing = Arrays.asList(files); - // Sort the files/directories so that the *.metadata.properties.xml found later, see ALF-17965 for details. - Collections.sort(originalListing); - importableItems = new HashMap(); - importableDirectories = new HashMap(); - } - - public List getOriginalListing() - { - return originalListing; - } - - public Collection getImportableItems() - { - return importableItems.values(); - } - - public Collection getImportableDirectories() - { - return importableDirectories.values(); - } - - public void addImportableItem(ImportableItem importableItem) - { - if(importableItem.getHeadRevision().contentFileExists() && - ImportableItem.FileType.DIRECTORY.equals(importableItem.getHeadRevision().getContentFileType())) - { - importableDirectories.put(importableItem.getHeadRevision().getContentFile(), importableItem); - } - else - { - importableItems.put(importableItem.getHeadRevision().getContentFile(), importableItem); - } - } - - public ImportableItem findImportableItem(File contentFile) - { - ImportableItem result = null; - result = importableItems.get(contentFile); - if(result == null) - { - result = importableDirectories.get(contentFile); - } - return result; - } -} + +package org.alfresco.repo.bulkimport; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class represents the analysed contents of a directory. + * + * @since 4.0 + */ +public class AnalysedDirectory +{ + private List originalListing = null; + private Map importableItems = null; + private Map importableDirectories = null; + + public AnalysedDirectory(File[] files) + { + originalListing = Arrays.asList(files); + // Sort the files/directories so that the *.metadata.properties.xml found later, see ALF-17965 for details. + Collections.sort(originalListing); + importableItems = new HashMap(); + importableDirectories = new HashMap(); + } + + public List getOriginalListing() + { + return originalListing; + } + + public Collection getImportableItems() + { + return importableItems.values(); + } + + public Collection getImportableDirectories() + { + return importableDirectories.values(); + } + + public void addImportableItem(ImportableItem importableItem) + { + if(importableItem.getHeadRevision().contentFileExists() && + ImportableItem.FileType.DIRECTORY.equals(importableItem.getHeadRevision().getContentFileType())) + { + importableDirectories.put(importableItem.getHeadRevision().getContentFile(), importableItem); + } + else + { + importableItems.put(importableItem.getHeadRevision().getContentFile(), importableItem); + } + } + + public ImportableItem findImportableItem(File contentFile) + { + ImportableItem result = null; + result = importableItems.get(contentFile); + if(result == null) + { + result = importableDirectories.get(contentFile); + } + return result; + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/BulkFSImportEvent.java b/source/java/org/alfresco/repo/bulkimport/BulkFSImportEvent.java index a37afb3966..49ecabfb4d 100644 --- a/source/java/org/alfresco/repo/bulkimport/BulkFSImportEvent.java +++ b/source/java/org/alfresco/repo/bulkimport/BulkFSImportEvent.java @@ -1,30 +1,30 @@ -package org.alfresco.repo.bulkimport; - -import org.springframework.context.ApplicationEvent; - -/** - * A class of event that notifies the listener of a significant event relating to a bulk filesystem - * import. Useful for Monitoring purposes. - * - * @since 4.0 - */ -public class BulkFSImportEvent extends ApplicationEvent -{ - private static final long serialVersionUID = 6249867689460133967L; - - /** - * The Constructor. - * - * @param source - * the source index monitor - */ - public BulkFSImportEvent(BulkFilesystemImporter source) - { - super(source); - } - - public BulkFilesystemImporter getBulkFilesystemImporter() - { - return (BulkFilesystemImporter)source; - } -} +package org.alfresco.repo.bulkimport; + +import org.springframework.context.ApplicationEvent; + +/** + * A class of event that notifies the listener of a significant event relating to a bulk filesystem + * import. Useful for Monitoring purposes. + * + * @since 4.0 + */ +public class BulkFSImportEvent extends ApplicationEvent +{ + private static final long serialVersionUID = 6249867689460133967L; + + /** + * The Constructor. + * + * @param source + * the source index monitor + */ + public BulkFSImportEvent(BulkFilesystemImporter source) + { + super(source); + } + + public BulkFilesystemImporter getBulkFilesystemImporter() + { + return (BulkFilesystemImporter)source; + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/BulkFilesystemImporter.java b/source/java/org/alfresco/repo/bulkimport/BulkFilesystemImporter.java index 308e79c1c5..7c33c85471 100644 --- a/source/java/org/alfresco/repo/bulkimport/BulkFilesystemImporter.java +++ b/source/java/org/alfresco/repo/bulkimport/BulkFilesystemImporter.java @@ -1,34 +1,34 @@ - -package org.alfresco.repo.bulkimport; - - -/** - * Interface defining a bulk filesystem importer. - * - * @since 4.0 - */ -public interface BulkFilesystemImporter -{ - /** - * Initiates a bulk filesystem import. - * Note: getStatus().inProgress() must be false prior to calling this method or an Exception will be thrown. - * - * @param bulkImportParameters The target bulk import parameters. - * @param nodeImporter The node importer. - */ - void bulkImport(BulkImportParameters bulkImportParameters, NodeImporter nodeImporter); - - /** - * Initiates a bulk filesystem import asynchronously i.e. in a background thread. - * Note: getStatus().inProgress() must be false prior to calling this method or an Exception will be thrown. - * - * @param bulkImportParameters The target bulk import parameters. - * @param nodeImporter The node importer. - */ - void asyncBulkImport(BulkImportParameters bulkImportParameters, NodeImporter nodeImporter); - - /** - * @return A status object that describes the current state of the bulk filesystem importer. - */ - BulkImportStatus getStatus(); -} + +package org.alfresco.repo.bulkimport; + + +/** + * Interface defining a bulk filesystem importer. + * + * @since 4.0 + */ +public interface BulkFilesystemImporter +{ + /** + * Initiates a bulk filesystem import. + * Note: getStatus().inProgress() must be false prior to calling this method or an Exception will be thrown. + * + * @param bulkImportParameters The target bulk import parameters. + * @param nodeImporter The node importer. + */ + void bulkImport(BulkImportParameters bulkImportParameters, NodeImporter nodeImporter); + + /** + * Initiates a bulk filesystem import asynchronously i.e. in a background thread. + * Note: getStatus().inProgress() must be false prior to calling this method or an Exception will be thrown. + * + * @param bulkImportParameters The target bulk import parameters. + * @param nodeImporter The node importer. + */ + void asyncBulkImport(BulkImportParameters bulkImportParameters, NodeImporter nodeImporter); + + /** + * @return A status object that describes the current state of the bulk filesystem importer. + */ + BulkImportStatus getStatus(); +} diff --git a/source/java/org/alfresco/repo/bulkimport/BulkImportParameters.java b/source/java/org/alfresco/repo/bulkimport/BulkImportParameters.java index 1770ea0baf..655d8158ba 100644 --- a/source/java/org/alfresco/repo/bulkimport/BulkImportParameters.java +++ b/source/java/org/alfresco/repo/bulkimport/BulkImportParameters.java @@ -1,63 +1,63 @@ -package org.alfresco.repo.bulkimport; - -import org.alfresco.service.cmr.repository.NodeRef; - -public class BulkImportParameters -{ - private NodeRef target; - private boolean replaceExisting = false; - private Integer batchSize; - private Integer numThreads; - private Integer loggingInterval; - private boolean disableRulesService = false; - - public boolean isDisableRulesService() - { - return disableRulesService; - } - public void setDisableRulesService(boolean disableRulesService) - { - this.disableRulesService = disableRulesService; - } - public Integer getLoggingInterval() - { - return loggingInterval; - } - public void setLoggingInterval(Integer loggingInterval) - { - this.loggingInterval = loggingInterval; - } - public NodeRef getTarget() - { - return target; - } - public void setTarget(NodeRef target) - { - this.target = target; - } - public boolean isReplaceExisting() - { - return replaceExisting; - } - public void setReplaceExisting(boolean replaceExisting) - { - this.replaceExisting = replaceExisting; - } - public Integer getBatchSize() - { - return batchSize; - } - public void setBatchSize(Integer batchSize) - { - this.batchSize = batchSize; - } - public Integer getNumThreads() - { - return numThreads; - } - public void setNumThreads(Integer numThreads) - { - this.numThreads = numThreads; - } - -} +package org.alfresco.repo.bulkimport; + +import org.alfresco.service.cmr.repository.NodeRef; + +public class BulkImportParameters +{ + private NodeRef target; + private boolean replaceExisting = false; + private Integer batchSize; + private Integer numThreads; + private Integer loggingInterval; + private boolean disableRulesService = false; + + public boolean isDisableRulesService() + { + return disableRulesService; + } + public void setDisableRulesService(boolean disableRulesService) + { + this.disableRulesService = disableRulesService; + } + public Integer getLoggingInterval() + { + return loggingInterval; + } + public void setLoggingInterval(Integer loggingInterval) + { + this.loggingInterval = loggingInterval; + } + public NodeRef getTarget() + { + return target; + } + public void setTarget(NodeRef target) + { + this.target = target; + } + public boolean isReplaceExisting() + { + return replaceExisting; + } + public void setReplaceExisting(boolean replaceExisting) + { + this.replaceExisting = replaceExisting; + } + public Integer getBatchSize() + { + return batchSize; + } + public void setBatchSize(Integer batchSize) + { + this.batchSize = batchSize; + } + public Integer getNumThreads() + { + return numThreads; + } + public void setNumThreads(Integer numThreads) + { + this.numThreads = numThreads; + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/BulkImportStatus.java b/source/java/org/alfresco/repo/bulkimport/BulkImportStatus.java index 7cb04a3bcc..bfeeb533a0 100644 --- a/source/java/org/alfresco/repo/bulkimport/BulkImportStatus.java +++ b/source/java/org/alfresco/repo/bulkimport/BulkImportStatus.java @@ -1,71 +1,71 @@ - -package org.alfresco.repo.bulkimport; - -import java.util.Date; - -/** - * Interface defining which information can be obtained from the Bulk Filesystem Import engine. - * - * @since 4.0 - */ -public interface BulkImportStatus -{ - // General information - boolean inProgress(); - - int getNumThreads(); - int getBatchSize(); - - String getSourceDirectory(); - String getTargetSpace(); - - Date getStartDate(); - Date getEndDate(); - - long getNumberOfBatchesCompleted(); - - Long getDurationInNs(); // Note: java.lang.Long, _not_ primitive long - may be null - Throwable getLastException(); - String getLastExceptionAsString(); - - - // Read-side information - long getNumberOfFoldersScanned(); - long getNumberOfFilesScanned(); - long getNumberOfUnreadableEntries(); - - long getNumberOfContentFilesRead(); - long getNumberOfContentBytesRead(); - - long getNumberOfMetadataFilesRead(); - long getNumberOfMetadataBytesRead(); - - long getNumberOfContentVersionFilesRead(); - long getNumberOfContentVersionBytesRead(); - - long getNumberOfMetadataVersionFilesRead(); - long getNumberOfMetadataVersionBytesRead(); - - // Write-side information - long getNumberOfSpaceNodesCreated(); - long getNumberOfSpaceNodesReplaced(); - long getNumberOfSpaceNodesSkipped(); - long getNumberOfSpacePropertiesWritten(); - - long getNumberOfContentNodesCreated(); - long getNumberOfContentNodesReplaced(); - long getNumberOfContentNodesSkipped(); - long getNumberOfContentBytesWritten(); - long getNumberOfContentPropertiesWritten(); - - long getNumberOfContentVersionsCreated(); - long getNumberOfContentVersionBytesWritten(); - long getNumberOfContentVersionPropertiesWritten(); - - // Throughput - public Long getFilesReadPerSecond(); - public Long getBytesReadPerSecond(); - public Long getEntriesScannedPerSecond(); - public Long getBytesWrittenPerSecond(); - public Long getNodesCreatedPerSecond(); -} + +package org.alfresco.repo.bulkimport; + +import java.util.Date; + +/** + * Interface defining which information can be obtained from the Bulk Filesystem Import engine. + * + * @since 4.0 + */ +public interface BulkImportStatus +{ + // General information + boolean inProgress(); + + int getNumThreads(); + int getBatchSize(); + + String getSourceDirectory(); + String getTargetSpace(); + + Date getStartDate(); + Date getEndDate(); + + long getNumberOfBatchesCompleted(); + + Long getDurationInNs(); // Note: java.lang.Long, _not_ primitive long - may be null + Throwable getLastException(); + String getLastExceptionAsString(); + + + // Read-side information + long getNumberOfFoldersScanned(); + long getNumberOfFilesScanned(); + long getNumberOfUnreadableEntries(); + + long getNumberOfContentFilesRead(); + long getNumberOfContentBytesRead(); + + long getNumberOfMetadataFilesRead(); + long getNumberOfMetadataBytesRead(); + + long getNumberOfContentVersionFilesRead(); + long getNumberOfContentVersionBytesRead(); + + long getNumberOfMetadataVersionFilesRead(); + long getNumberOfMetadataVersionBytesRead(); + + // Write-side information + long getNumberOfSpaceNodesCreated(); + long getNumberOfSpaceNodesReplaced(); + long getNumberOfSpaceNodesSkipped(); + long getNumberOfSpacePropertiesWritten(); + + long getNumberOfContentNodesCreated(); + long getNumberOfContentNodesReplaced(); + long getNumberOfContentNodesSkipped(); + long getNumberOfContentBytesWritten(); + long getNumberOfContentPropertiesWritten(); + + long getNumberOfContentVersionsCreated(); + long getNumberOfContentVersionBytesWritten(); + long getNumberOfContentVersionPropertiesWritten(); + + // Throughput + public Long getFilesReadPerSecond(); + public Long getBytesReadPerSecond(); + public Long getEntriesScannedPerSecond(); + public Long getBytesWrittenPerSecond(); + public Long getNodesCreatedPerSecond(); +} diff --git a/source/java/org/alfresco/repo/bulkimport/ContentDataFactory.java b/source/java/org/alfresco/repo/bulkimport/ContentDataFactory.java index 7605ee8bf0..87992dcafc 100644 --- a/source/java/org/alfresco/repo/bulkimport/ContentDataFactory.java +++ b/source/java/org/alfresco/repo/bulkimport/ContentDataFactory.java @@ -1,29 +1,29 @@ - -package org.alfresco.repo.bulkimport; - -import java.io.File; - -import org.alfresco.repo.content.ContentStore; -import org.alfresco.service.cmr.repository.ContentData; - -/** - * Build a {@link ContentData} out of a given {@link ContentStore} and a given {@link File} within - * that store - * - * @since 4.0 - * - */ -public interface ContentDataFactory -{ - /** - * Create a {@link ContentData} by combining the given {@link ContentStore}'s root location and the {@link File}'s path within that store. - * The given file must therefore be accessible within the content store's configured root location. - * The encoding and mimetype will be guessed from the given file. - * - * @param store The {@link ContentStore} in which the file should be - * @param contentFile The {@link File} to check - * @return the constructed {@link ContentData} - */ - public ContentData createContentData(ContentStore store, File contentFile); - + +package org.alfresco.repo.bulkimport; + +import java.io.File; + +import org.alfresco.repo.content.ContentStore; +import org.alfresco.service.cmr.repository.ContentData; + +/** + * Build a {@link ContentData} out of a given {@link ContentStore} and a given {@link File} within + * that store + * + * @since 4.0 + * + */ +public interface ContentDataFactory +{ + /** + * Create a {@link ContentData} by combining the given {@link ContentStore}'s root location and the {@link File}'s path within that store. + * The given file must therefore be accessible within the content store's configured root location. + * The encoding and mimetype will be guessed from the given file. + * + * @param store The {@link ContentStore} in which the file should be + * @param contentFile The {@link File} to check + * @return the constructed {@link ContentData} + */ + public ContentData createContentData(ContentStore store, File contentFile); + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/bulkimport/ContentStoreMapProvider.java b/source/java/org/alfresco/repo/bulkimport/ContentStoreMapProvider.java index eaac16dc1e..5bf4379066 100644 --- a/source/java/org/alfresco/repo/bulkimport/ContentStoreMapProvider.java +++ b/source/java/org/alfresco/repo/bulkimport/ContentStoreMapProvider.java @@ -1,30 +1,30 @@ - -package org.alfresco.repo.bulkimport; - -import java.util.Map; - -import org.alfresco.repo.content.ContentStore; - -/** - * - * @since 4.0 - * - */ -public interface ContentStoreMapProvider -{ - /** - * Get a map of the currently registered {@link ContentStore}, keyed by store name - * @return a {@link Map} - */ - public Map getStoreMap(); - - /** - * Check that the given store name is part of the map. It it is not, an exception will - * be thrown. If it is, it will be returned. - * - * @param storeName the store - * @return the corresponding {@link ContentStore} - */ - public ContentStore checkAndGetStore(String storeName); - + +package org.alfresco.repo.bulkimport; + +import java.util.Map; + +import org.alfresco.repo.content.ContentStore; + +/** + * + * @since 4.0 + * + */ +public interface ContentStoreMapProvider +{ + /** + * Get a map of the currently registered {@link ContentStore}, keyed by store name + * @return a {@link Map} + */ + public Map getStoreMap(); + + /** + * Check that the given store name is part of the map. It it is not, an exception will + * be thrown. If it is, it will be returned. + * + * @param storeName the store + * @return the corresponding {@link ContentStore} + */ + public ContentStore checkAndGetStore(String storeName); + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/bulkimport/DirectoryAnalyser.java b/source/java/org/alfresco/repo/bulkimport/DirectoryAnalyser.java index c0c3232299..2e45da6e35 100644 --- a/source/java/org/alfresco/repo/bulkimport/DirectoryAnalyser.java +++ b/source/java/org/alfresco/repo/bulkimport/DirectoryAnalyser.java @@ -1,32 +1,32 @@ - -package org.alfresco.repo.bulkimport; - -import java.io.FileFilter; - - -/** - * This interface defines a directory analyser. This is the process by which - * the contents of a source directory are grouped together into a list of - * ImportableItems. - * - * Please note that this interface is not intended to have more than one implementation - * (DirectoryAnalyserImpl) - it exists solely for dependency injection purposes. - * - * @since 4.0 - */ -public interface DirectoryAnalyser -{ - /** - * Regex string for the version filename suffix - */ - public final static String VERSION_SUFFIX_REGEX = "\\.v([0-9]+)\\z"; - - /** - * Analyses the given directory. - * - * @param directory The directory to analyse (note: must be a directory) (must not be null). - * @return An AnalysedDirectory object (will not be null). - */ - public AnalysedDirectory analyseDirectory(ImportableItem directory, FileFilter filter); - -} + +package org.alfresco.repo.bulkimport; + +import java.io.FileFilter; + + +/** + * This interface defines a directory analyser. This is the process by which + * the contents of a source directory are grouped together into a list of + * ImportableItems. + * + * Please note that this interface is not intended to have more than one implementation + * (DirectoryAnalyserImpl) - it exists solely for dependency injection purposes. + * + * @since 4.0 + */ +public interface DirectoryAnalyser +{ + /** + * Regex string for the version filename suffix + */ + public final static String VERSION_SUFFIX_REGEX = "\\.v([0-9]+)\\z"; + + /** + * Analyses the given directory. + * + * @param directory The directory to analyse (note: must be a directory) (must not be null). + * @return An AnalysedDirectory object (will not be null). + */ + public AnalysedDirectory analyseDirectory(ImportableItem directory, FileFilter filter); + +} diff --git a/source/java/org/alfresco/repo/bulkimport/FilesystemTracker.java b/source/java/org/alfresco/repo/bulkimport/FilesystemTracker.java index bcba83d5f1..e9bcaed77a 100644 --- a/source/java/org/alfresco/repo/bulkimport/FilesystemTracker.java +++ b/source/java/org/alfresco/repo/bulkimport/FilesystemTracker.java @@ -1,37 +1,37 @@ -package org.alfresco.repo.bulkimport; - -import org.alfresco.repo.batch.BatchProcessWorkProvider; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * A filesystem walker walks a filesystem, returning directories and files as it goes. - * - * @since 4.0 - */ -public interface FilesystemTracker -{ - /** - * An estimate of the number of directories and files in the filesystem. - * - * @return int - */ - int count(); - - /** - * Returns a list of at most 'count' importable items - * - * @param count - * @return - */ -// List getImportableItems(int count); - - /** - * A callback to indicate that the item has been imported into the repository. - * - * @param nodeRef NodeRef - * @param importableItem ImportableItem - */ - void itemImported(NodeRef nodeRef, ImportableItem importableItem); - - public BatchProcessWorkProvider getWorkProvider(); -} +package org.alfresco.repo.bulkimport; + +import org.alfresco.repo.batch.BatchProcessWorkProvider; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * A filesystem walker walks a filesystem, returning directories and files as it goes. + * + * @since 4.0 + */ +public interface FilesystemTracker +{ + /** + * An estimate of the number of directories and files in the filesystem. + * + * @return int + */ + int count(); + + /** + * Returns a list of at most 'count' importable items + * + * @param count + * @return + */ +// List getImportableItems(int count); + + /** + * A callback to indicate that the item has been imported into the repository. + * + * @param nodeRef NodeRef + * @param importableItem ImportableItem + */ + void itemImported(NodeRef nodeRef, ImportableItem importableItem); + + public BatchProcessWorkProvider getWorkProvider(); +} diff --git a/source/java/org/alfresco/repo/bulkimport/ImportFilter.java b/source/java/org/alfresco/repo/bulkimport/ImportFilter.java index 6cdb20c903..4da83fd0a1 100644 --- a/source/java/org/alfresco/repo/bulkimport/ImportFilter.java +++ b/source/java/org/alfresco/repo/bulkimport/ImportFilter.java @@ -1,25 +1,25 @@ - -package org.alfresco.repo.bulkimport; - -/** - * Definition of a source filter - a class that filters out importable items idenfitied from the source - * directory from the import. - * - * Note that source filters can be "chained", in which case each source filter effectively has - * "veto" power - if any single filter requests that a given importable item be filtered, it - * will be filtered. - * - * @since 4.0 - */ -public interface ImportFilter -{ - - /** - * Method that checks whether the given file or folder should be filtered. - * - * @param importableItem The source importable item to check for filtering (will not be null). - * @return True if the given importable item should be filtered, false otherwise. - */ - boolean shouldFilter(final ImportableItem importableItem); - -} + +package org.alfresco.repo.bulkimport; + +/** + * Definition of a source filter - a class that filters out importable items idenfitied from the source + * directory from the import. + * + * Note that source filters can be "chained", in which case each source filter effectively has + * "veto" power - if any single filter requests that a given importable item be filtered, it + * will be filtered. + * + * @since 4.0 + */ +public interface ImportFilter +{ + + /** + * Method that checks whether the given file or folder should be filtered. + * + * @param importableItem The source importable item to check for filtering (will not be null). + * @return True if the given importable item should be filtered, false otherwise. + */ + boolean shouldFilter(final ImportableItem importableItem); + +} diff --git a/source/java/org/alfresco/repo/bulkimport/ImportableItem.java b/source/java/org/alfresco/repo/bulkimport/ImportableItem.java index b0f9515843..bf4093cdfd 100644 --- a/source/java/org/alfresco/repo/bulkimport/ImportableItem.java +++ b/source/java/org/alfresco/repo/bulkimport/ImportableItem.java @@ -1,317 +1,317 @@ -package org.alfresco.repo.bulkimport; - -import java.io.File; -import java.util.Collections; -import java.util.Date; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.alfresco.service.cmr.repository.NodeRef; -import org.apache.commons.lang.builder.ToStringBuilder; - -/** - * This class is a DTO that represents an "importable item" - a series of files - * that represent a single node (content OR space) in the repository. - * - * @since 4.0 - * - */ -public final class ImportableItem -{ - public enum FileType - { - FILE, - DIRECTORY, - OTHER - }; - - private ContentAndMetadata headRevision = new ContentAndMetadata(); - private SortedSet versionEntries = null; - private NodeRef nodeRef; - private ImportableItem parent; - private long numChildren = 0; - - public boolean isValid() - { - return(headRevision.contentFileExists() || headRevision.metadataFileExists() || hasVersionEntries()); - } - - public ContentAndMetadata getHeadRevision() - { - return(headRevision); - } - - public void setNodeRef(NodeRef nodeRef) - { - this.nodeRef = nodeRef; - } - - public NodeRef getNodeRef() - { - return nodeRef; - } - - public void clearParent() - { - numChildren--; - if(numChildren <= 0) - { - numChildren = 0; - parent = null; - } - } - - public void setParent(ImportableItem parent) - { - if(parent == null) - { - throw new IllegalArgumentException("Parent cannot be null"); - } - this.parent = parent; - } - - public ImportableItem getParent() - { - return parent; - } - - /** - * @return True if this ImportableItem has version entries. - */ - public boolean hasVersionEntries() - { - return(versionEntries != null && versionEntries.size() > 0); - } - - public Set getVersionEntries() - { - return(Collections.unmodifiableSet(versionEntries)); - } - - public void addVersionEntry(final VersionedContentAndMetadata versionEntry) - { - if (versionEntry != null) - { - if (versionEntries == null) - { - versionEntries = new TreeSet(); - } - - versionEntries.add(versionEntry); - } - } - - @Override - public String toString() - { - return(new ToStringBuilder(this) - .append("HeadRevision", headRevision) - .append("Versions", versionEntries) - .toString()); - } - - public class ContentAndMetadata - { - private File contentFile = null; - private boolean contentFileExists = false; - private boolean contentFileIsReadable = false; - private FileType contentFileType = null; - private long contentFileSize = -1; - private Date contentFileCreated = null; - private Date contentFileModified = null; - private File metadataFile = null; - private long metadataFileSize = -1; - - - public final File getContentFile() - { - return(contentFile); - } - - public final void setContentFile(final File contentFile) - { - this.contentFile = contentFile; - - if (contentFile != null) - { - // stat the file, to find out a few key details - contentFileExists = contentFile.exists(); - - if (contentFileExists) - { - contentFileIsReadable = contentFile.canRead(); - contentFileSize = contentFile.length(); - contentFileModified = new Date(contentFile.lastModified()); - contentFileCreated = contentFileModified; //TODO: determine proper file creation time (awaiting JDK 1.7 NIO2 library) - - if (contentFile.isFile()) - { - contentFileType = FileType.FILE; - } - else if (contentFile.isDirectory()) - { - contentFileType = FileType.DIRECTORY; - } - else - { - contentFileType = FileType.OTHER; - } - } - } - } - - public final boolean contentFileExists() - { - return(contentFileExists); - } - - public final boolean isContentFileReadable() - { - return(contentFileIsReadable); - } - - public final FileType getContentFileType() - { - if (!contentFileExists()) - { - throw new IllegalStateException("Cannot determine content file type if content file doesn't exist."); - } - - return(contentFileType); - } - - public final long getContentFileSize() - { - if (!contentFileExists()) - { - throw new IllegalStateException("Cannot determine content file size if content file doesn't exist."); - } - - return(contentFileSize); - } - - public final Date getContentFileCreatedDate() - { - if (!contentFileExists()) - { - throw new IllegalStateException("Cannot determine content file creation date if content file doesn't exist."); - } - - return(contentFileCreated); - } - - public final Date getContentFileModifiedDate() - { - if (!contentFileExists()) - { - throw new IllegalStateException("Cannot determine content file modification date if content file doesn't exist."); - } - - return(contentFileModified); - } - - public final boolean metadataFileExists() - { - return(metadataFile != null); - } - - public final File getMetadataFile() - { - return(metadataFile); - } - - public final void setMetadataFile(final File metadataFile) - { - if (metadataFile != null && metadataFile.exists()) - { - this.metadataFile = metadataFile; - this.metadataFileSize = metadataFile.length(); - } - } - - public final long getMetadataFileSize() - { - if (!metadataFileExists()) - { - throw new IllegalStateException("Cannot determine metadata file size if metadata file doesn't exist."); - } - - return(metadataFileSize); - } - - public final int weight() - { - return((contentFile == null || !contentFileExists ? 0 : 1) + - (metadataFile == null ? 0 : 1)); - } - - @Override - public String toString() - { - return(new ToStringBuilder(this) - .append("contentFile", (contentFileExists ? contentFile : null)) - .append("metadatafile", metadataFile) - .toString()); - } - } - - /** - * - * @since 4.0 - * - */ - public class VersionedContentAndMetadata extends ContentAndMetadata implements Comparable - { - private int version; - - public VersionedContentAndMetadata(final int version) - { - this.version = version; - } - - public final int getVersion() - { - return(version); - } - - @Override - public String toString() - { - return(new ToStringBuilder(this) - .append("version", version) - .appendSuper("") - .toString()); - } - - public int compareTo(final VersionedContentAndMetadata other) - { - return(this.version < other.version ? -1 : - this.version == other.version ? 0 : 1); - } - - @Override - public boolean equals(final Object other) - { - if (this == other) - { - return(true); - } - - if (!(other instanceof VersionedContentAndMetadata)) - { - return(false); - } - - VersionedContentAndMetadata otherVCAM = (VersionedContentAndMetadata)other; - - return(this.version == otherVCAM.version); - } - - @Override - public int hashCode() - { - return(version); - } - } -} +package org.alfresco.repo.bulkimport; + +import java.io.File; +import java.util.Collections; +import java.util.Date; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.lang.builder.ToStringBuilder; + +/** + * This class is a DTO that represents an "importable item" - a series of files + * that represent a single node (content OR space) in the repository. + * + * @since 4.0 + * + */ +public final class ImportableItem +{ + public enum FileType + { + FILE, + DIRECTORY, + OTHER + }; + + private ContentAndMetadata headRevision = new ContentAndMetadata(); + private SortedSet versionEntries = null; + private NodeRef nodeRef; + private ImportableItem parent; + private long numChildren = 0; + + public boolean isValid() + { + return(headRevision.contentFileExists() || headRevision.metadataFileExists() || hasVersionEntries()); + } + + public ContentAndMetadata getHeadRevision() + { + return(headRevision); + } + + public void setNodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + public NodeRef getNodeRef() + { + return nodeRef; + } + + public void clearParent() + { + numChildren--; + if(numChildren <= 0) + { + numChildren = 0; + parent = null; + } + } + + public void setParent(ImportableItem parent) + { + if(parent == null) + { + throw new IllegalArgumentException("Parent cannot be null"); + } + this.parent = parent; + } + + public ImportableItem getParent() + { + return parent; + } + + /** + * @return True if this ImportableItem has version entries. + */ + public boolean hasVersionEntries() + { + return(versionEntries != null && versionEntries.size() > 0); + } + + public Set getVersionEntries() + { + return(Collections.unmodifiableSet(versionEntries)); + } + + public void addVersionEntry(final VersionedContentAndMetadata versionEntry) + { + if (versionEntry != null) + { + if (versionEntries == null) + { + versionEntries = new TreeSet(); + } + + versionEntries.add(versionEntry); + } + } + + @Override + public String toString() + { + return(new ToStringBuilder(this) + .append("HeadRevision", headRevision) + .append("Versions", versionEntries) + .toString()); + } + + public class ContentAndMetadata + { + private File contentFile = null; + private boolean contentFileExists = false; + private boolean contentFileIsReadable = false; + private FileType contentFileType = null; + private long contentFileSize = -1; + private Date contentFileCreated = null; + private Date contentFileModified = null; + private File metadataFile = null; + private long metadataFileSize = -1; + + + public final File getContentFile() + { + return(contentFile); + } + + public final void setContentFile(final File contentFile) + { + this.contentFile = contentFile; + + if (contentFile != null) + { + // stat the file, to find out a few key details + contentFileExists = contentFile.exists(); + + if (contentFileExists) + { + contentFileIsReadable = contentFile.canRead(); + contentFileSize = contentFile.length(); + contentFileModified = new Date(contentFile.lastModified()); + contentFileCreated = contentFileModified; //TODO: determine proper file creation time (awaiting JDK 1.7 NIO2 library) + + if (contentFile.isFile()) + { + contentFileType = FileType.FILE; + } + else if (contentFile.isDirectory()) + { + contentFileType = FileType.DIRECTORY; + } + else + { + contentFileType = FileType.OTHER; + } + } + } + } + + public final boolean contentFileExists() + { + return(contentFileExists); + } + + public final boolean isContentFileReadable() + { + return(contentFileIsReadable); + } + + public final FileType getContentFileType() + { + if (!contentFileExists()) + { + throw new IllegalStateException("Cannot determine content file type if content file doesn't exist."); + } + + return(contentFileType); + } + + public final long getContentFileSize() + { + if (!contentFileExists()) + { + throw new IllegalStateException("Cannot determine content file size if content file doesn't exist."); + } + + return(contentFileSize); + } + + public final Date getContentFileCreatedDate() + { + if (!contentFileExists()) + { + throw new IllegalStateException("Cannot determine content file creation date if content file doesn't exist."); + } + + return(contentFileCreated); + } + + public final Date getContentFileModifiedDate() + { + if (!contentFileExists()) + { + throw new IllegalStateException("Cannot determine content file modification date if content file doesn't exist."); + } + + return(contentFileModified); + } + + public final boolean metadataFileExists() + { + return(metadataFile != null); + } + + public final File getMetadataFile() + { + return(metadataFile); + } + + public final void setMetadataFile(final File metadataFile) + { + if (metadataFile != null && metadataFile.exists()) + { + this.metadataFile = metadataFile; + this.metadataFileSize = metadataFile.length(); + } + } + + public final long getMetadataFileSize() + { + if (!metadataFileExists()) + { + throw new IllegalStateException("Cannot determine metadata file size if metadata file doesn't exist."); + } + + return(metadataFileSize); + } + + public final int weight() + { + return((contentFile == null || !contentFileExists ? 0 : 1) + + (metadataFile == null ? 0 : 1)); + } + + @Override + public String toString() + { + return(new ToStringBuilder(this) + .append("contentFile", (contentFileExists ? contentFile : null)) + .append("metadatafile", metadataFile) + .toString()); + } + } + + /** + * + * @since 4.0 + * + */ + public class VersionedContentAndMetadata extends ContentAndMetadata implements Comparable + { + private int version; + + public VersionedContentAndMetadata(final int version) + { + this.version = version; + } + + public final int getVersion() + { + return(version); + } + + @Override + public String toString() + { + return(new ToStringBuilder(this) + .append("version", version) + .appendSuper("") + .toString()); + } + + public int compareTo(final VersionedContentAndMetadata other) + { + return(this.version < other.version ? -1 : + this.version == other.version ? 0 : 1); + } + + @Override + public boolean equals(final Object other) + { + if (this == other) + { + return(true); + } + + if (!(other instanceof VersionedContentAndMetadata)) + { + return(false); + } + + VersionedContentAndMetadata otherVCAM = (VersionedContentAndMetadata)other; + + return(this.version == otherVCAM.version); + } + + @Override + public int hashCode() + { + return(version); + } + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/MetadataLoader.java b/source/java/org/alfresco/repo/bulkimport/MetadataLoader.java index a0a94a04ab..20d954fce6 100644 --- a/source/java/org/alfresco/repo/bulkimport/MetadataLoader.java +++ b/source/java/org/alfresco/repo/bulkimport/MetadataLoader.java @@ -1,134 +1,134 @@ -package org.alfresco.repo.bulkimport; - -import java.io.Serializable; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.alfresco.service.namespace.QName; - -/** - * Definition of a metadata loader - a class that can load metadata for a file from some other source. - * Note that metadata loaders can be "chained", so an implementation needs to be careful about how the - * Metadata object is populated in the populateMetadata method. - * - * Implementors also need to be careful when configuring the bulk import process, as the order in which - * metadata loaders are configured into a bulk importer is the order of precendence (from lowest to - * highest). - * - * @since 4.0 - */ -public interface MetadataLoader -{ - /** - * Metadata filename suffix (excluding file-type specific ending) - */ - public final static String METADATA_SUFFIX = ".metadata."; - - /** - * @return The extension for files used to store this metadata, minus the stop character (.) e.g. "properties", "xml", "json", etc. - */ - String getMetadataFileExtension(); - - - /** - * Method that populates the type, aspects and properties to attach to a given file or space. - * - * @param contentAndMetadata The contentAndMetadata from which to obtain the metadata (will not be null). - * @param metadata The metadata object to populate (will not be null, and may already be partially populated). - */ - void loadMetadata(final ImportableItem.ContentAndMetadata contentAndMetadata, MetadataLoader.Metadata metadata); - - - /** - * Class used to encapsulate the type, aspects and property values for a single file or folder. - */ - public final class Metadata - { - private QName type; - private Set aspects; - private Map properties; - - - public Metadata() - { - this.type = null; - aspects = new HashSet(); - properties = new HashMap(); - } - - - /** - * @return the type - */ - public QName getType() - { - return(type); - } - - - /** - * @param type The type to set in this metadata object (must not be null). - */ - public void setType(final QName type) - { - // PRECONDITIONS - assert type != null : "type must not be null."; - - // Body - this.type = type; - } - - - /** - * @return The set of aspects in this metadata object (will not be null, but may be empty). - */ - public Set getAspects() - { - return(Collections.unmodifiableSet(aspects)); - } - - - /** - * @param aspect An aspect to add to this metadata object (must not be null). - */ - public void addAspect(final QName aspect) - { - // PRECONDITIONS - assert aspect != null : "aspect must not be null."; - - // Body - aspects.add(aspect); - } - - - /** - * @return The properties in this metadata object (will not be null, but may be empty). - */ - public Map getProperties() - { - return(Collections.unmodifiableMap(properties)); - } - - - /** - * Adds a property and its value to this metadata object. - * - * @param property The property to populate (must not be null). - * @param value The value of the property (may be null). - */ - public void addProperty(final QName property, final Serializable value) - { - // PRECONDITIONS - assert property != null : "property must not be null"; - - // Body - properties.put(property, value); - } - - } - - -} +package org.alfresco.repo.bulkimport; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.service.namespace.QName; + +/** + * Definition of a metadata loader - a class that can load metadata for a file from some other source. + * Note that metadata loaders can be "chained", so an implementation needs to be careful about how the + * Metadata object is populated in the populateMetadata method. + * + * Implementors also need to be careful when configuring the bulk import process, as the order in which + * metadata loaders are configured into a bulk importer is the order of precendence (from lowest to + * highest). + * + * @since 4.0 + */ +public interface MetadataLoader +{ + /** + * Metadata filename suffix (excluding file-type specific ending) + */ + public final static String METADATA_SUFFIX = ".metadata."; + + /** + * @return The extension for files used to store this metadata, minus the stop character (.) e.g. "properties", "xml", "json", etc. + */ + String getMetadataFileExtension(); + + + /** + * Method that populates the type, aspects and properties to attach to a given file or space. + * + * @param contentAndMetadata The contentAndMetadata from which to obtain the metadata (will not be null). + * @param metadata The metadata object to populate (will not be null, and may already be partially populated). + */ + void loadMetadata(final ImportableItem.ContentAndMetadata contentAndMetadata, MetadataLoader.Metadata metadata); + + + /** + * Class used to encapsulate the type, aspects and property values for a single file or folder. + */ + public final class Metadata + { + private QName type; + private Set aspects; + private Map properties; + + + public Metadata() + { + this.type = null; + aspects = new HashSet(); + properties = new HashMap(); + } + + + /** + * @return the type + */ + public QName getType() + { + return(type); + } + + + /** + * @param type The type to set in this metadata object (must not be null). + */ + public void setType(final QName type) + { + // PRECONDITIONS + assert type != null : "type must not be null."; + + // Body + this.type = type; + } + + + /** + * @return The set of aspects in this metadata object (will not be null, but may be empty). + */ + public Set getAspects() + { + return(Collections.unmodifiableSet(aspects)); + } + + + /** + * @param aspect An aspect to add to this metadata object (must not be null). + */ + public void addAspect(final QName aspect) + { + // PRECONDITIONS + assert aspect != null : "aspect must not be null."; + + // Body + aspects.add(aspect); + } + + + /** + * @return The properties in this metadata object (will not be null, but may be empty). + */ + public Map getProperties() + { + return(Collections.unmodifiableMap(properties)); + } + + + /** + * Adds a property and its value to this metadata object. + * + * @param property The property to populate (must not be null). + * @param value The value of the property (may be null). + */ + public void addProperty(final QName property, final Serializable value) + { + // PRECONDITIONS + assert property != null : "property must not be null"; + + // Body + properties.put(property, value); + } + + } + + +} diff --git a/source/java/org/alfresco/repo/bulkimport/NodeImporter.java b/source/java/org/alfresco/repo/bulkimport/NodeImporter.java index ddcd7c07dc..37e4fd6fc0 100644 --- a/source/java/org/alfresco/repo/bulkimport/NodeImporter.java +++ b/source/java/org/alfresco/repo/bulkimport/NodeImporter.java @@ -1,17 +1,17 @@ -package org.alfresco.repo.bulkimport; - -import java.io.File; - -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Imports an importable item in the filesystem into the repository by creating a node to represent it. - * - * @since 4.0 - * - */ -public interface NodeImporter -{ - public NodeRef importImportableItem(ImportableItem importableItem, boolean replaceExisting); - public File getSourceFolder(); -} +package org.alfresco.repo.bulkimport; + +import java.io.File; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Imports an importable item in the filesystem into the repository by creating a node to represent it. + * + * @since 4.0 + * + */ +public interface NodeImporter +{ + public NodeRef importImportableItem(ImportableItem importableItem, boolean replaceExisting); + public File getSourceFolder(); +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/AbstractBulkFilesystemImporter.java b/source/java/org/alfresco/repo/bulkimport/impl/AbstractBulkFilesystemImporter.java index 0e78183e46..8f3a5043cb 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/AbstractBulkFilesystemImporter.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/AbstractBulkFilesystemImporter.java @@ -1,398 +1,398 @@ -package org.alfresco.repo.bulkimport.impl; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import org.alfresco.repo.bulkimport.BulkFSImportEvent; -import org.alfresco.repo.bulkimport.BulkFilesystemImporter; -import org.alfresco.repo.bulkimport.BulkImportParameters; -import org.alfresco.repo.bulkimport.BulkImportStatus; -import org.alfresco.repo.bulkimport.DirectoryAnalyser; -import org.alfresco.repo.bulkimport.NodeImporter; -import org.alfresco.repo.lock.JobLockService; -import org.alfresco.repo.lock.LockAcquisitionException; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.rule.RuleService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * - * @since 4.0 - * - */ -public abstract class AbstractBulkFilesystemImporter implements BulkFilesystemImporter, InitializingBean, ApplicationContextAware -{ - private static final QName LOCK = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "BatchFilesystemImport"); - protected static final Log logger = LogFactory.getLog(BulkFilesystemImporter.class); - - protected ApplicationContext applicationContext; - - protected FileFolderService fileFolderService; - protected TransactionService transactionService; - protected PermissionService permissionService; - protected RetryingTransactionHelper transactionHelper; - protected RuleService ruleService; - - protected BulkImportStatusImpl importStatus; - protected DirectoryAnalyser directoryAnalyser = null; - - protected JobLockService jobLockService; - - protected BehaviourFilter behaviourFilter; - - public void setRuleService(RuleService ruleService) - { - this.ruleService = ruleService; - } - - public void setBehaviourFilter(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } - - public void setJobLockService(JobLockService jobLockService) - { - this.jobLockService = jobLockService; - } - - public void setImportStatus(BulkImportStatusImpl importStatus) - { - this.importStatus = importStatus; - } - - public final void setDirectoryAnalyser(DirectoryAnalyser directoryAnalyser) - { - this.directoryAnalyser = directoryAnalyser; - } - - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - /** - * @see org.alfresco.repo.bulkimport.BulkFilesystemImporter#getStatus() - */ - public final BulkImportStatus getStatus() - { - return(importStatus); - } - - public void afterPropertiesSet() throws Exception - { - PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); - PropertyCheck.mandatory(this, "transactionService", transactionService); - PropertyCheck.mandatory(this, "permissionService", permissionService); - - PropertyCheck.mandatory(this, "importStatus", importStatus); - PropertyCheck.mandatory(this, "directoryAnalyser", directoryAnalyser); - - this.transactionHelper = transactionService.getRetryingTransactionHelper(); - } - - protected abstract void bulkImportImpl(BulkImportParameters bulkImportParameters, NodeImporter nodeImporter, String lockToken); - - /** - * Attempts to get the lock. If the lock couldn't be taken, then null is returned. - * - * @return Returns the lock token or null - */ - protected String getLock(long time) - { - try - { - return jobLockService.getLock(LOCK, time); - } - catch (LockAcquisitionException e) - { - return null; - } - } - - protected void refreshLock(String lockToken, long time) - { - if (lockToken == null) - { - throw new IllegalArgumentException("Must provide existing lockToken"); - } - jobLockService.refreshLock(lockToken, LOCK, time); - } - - protected void releaseLock(String lockToken) - { - if (lockToken == null) - { - throw new IllegalArgumentException("Must provide existing lockToken"); - } - jobLockService.releaseLock(lockToken, LOCK); - } - - /* - * Because commons-lang ToStringBuilder doesn't seem to like unmodifiable Maps - */ - protected final String mapToString(Map map) - { - StringBuffer result = new StringBuffer(); - - if (map != null) - { - result.append('['); - - if (map.size() > 0) - { - for (Object key : map.keySet()) - { - result.append(String.valueOf(key)); - result.append(" = "); - result.append(String.valueOf(map.get(key))); - result.append(",\n"); - } - - // Delete final dangling ", " value - result.delete(result.length() - 2, result.length()); - } - - result.append(']'); - } - else - { - result.append("(null)"); - } - - return(result.toString()); - } - - protected final String getRepositoryPath(NodeRef nodeRef) - { - String result = null; - - if (nodeRef != null) - { - List pathElements = null; - - try - { - pathElements = fileFolderService.getNamePath(null, nodeRef); - - if (pathElements != null && pathElements.size() > 0) - { - StringBuilder temp = new StringBuilder(); - - for (FileInfo pathElement : pathElements) - { - temp.append("/"); - temp.append(pathElement.getName()); - } - - result = temp.toString(); - } - } - catch (final FileNotFoundException fnfe) - { - // Do nothing - } - } - - return(result); - } - - protected final void validateNodeRefIsWritableSpace(NodeRef target) - { - if (target == null) - { - throw new IllegalArgumentException("target must not be null."); - } - - if (!fileFolderService.exists(target)) - { - throw new IllegalArgumentException("Target '" + target.toString() + "' doesn't exist."); - } - - if (AccessStatus.DENIED.equals(permissionService.hasPermission(target, PermissionService.ADD_CHILDREN))) - { - throw new IllegalArgumentException("Target '" + target.toString() + "' is not writeable."); - } - - if (!fileFolderService.getFileInfo(target).isFolder()) - { - throw new IllegalArgumentException("Target '" + target.toString() + "' is not a space."); - } - } - - protected String getFileName(File file) - { - return FileUtils.getFileName(file); - } - - protected String getLockToken() - { - // Take out a bulk filesystem import lock - RetryingTransactionCallback txnWork = new RetryingTransactionCallback() - { - public String execute() throws Exception - { - String lockToken = getLock(20000L); - return lockToken; - } - }; - - String lockToken = transactionService.getRetryingTransactionHelper().doInTransaction(txnWork, false, true); -// if(lockToken == null) -// { -// logger.warn("Can't get lock. Assume multiple bulk filesystem importers ..."); -// return; -// } - - return lockToken; - } - - public void validateSourceIsReadableDirectory(File source) - { - try - { - if (source == null) - { - throw new IllegalArgumentException("source must not be null."); - } - - if (!source.exists()) - { - throw new IllegalArgumentException("Source '" + source.getCanonicalPath() + "' doesn't exist."); - } - - if (!source.canRead()) - { - throw new IllegalArgumentException("Source '" + source.getCanonicalPath() + "' is not readable."); - } - - if (!source.isDirectory()) - { - throw new IllegalArgumentException("Source '" + source.getCanonicalPath() + "' is not a directory."); - } - } - catch (final IOException ioe) - { - throw new RuntimeException(ioe); - } - } - - public void asyncBulkImport(final BulkImportParameters bulkImportParameters, final NodeImporter nodeImporter) - { - final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - - Runnable backgroundLogic = new Runnable() - { - public void run() - { - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - bulkImport(bulkImportParameters, nodeImporter); - return null; - } - }, currentUser); - } - }; - - Thread backgroundThread = new Thread(backgroundLogic, "BulkFilesystemImport-BackgroundThread"); - backgroundThread.start(); - } - - /** - * @see org.alfresco.repo.bulkimport.BulkFilesystemImporter#bulkImport(org.alfresco.repo.bulkimport.BulkImportParameters, org.alfresco.repo.bulkimport.NodeImporter) - */ - public void bulkImport(final BulkImportParameters bulkImportParameters, final NodeImporter nodeImporter) - { - final File sourceFolder = nodeImporter.getSourceFolder(); - final BulkFilesystemImporter importer = this; - - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - final String sourceDirectory = getFileName(sourceFolder); - final String targetSpace = getRepositoryPath(bulkImportParameters.getTarget()); - final String lockToken = getLockToken(); - - try - { - importStatus.startImport(sourceDirectory, targetSpace); - - BulkFSImportEvent bulkImportEvent = new BulkFSImportEvent(importer); - applicationContext.publishEvent(bulkImportEvent); - - validateNodeRefIsWritableSpace(bulkImportParameters.getTarget()); - validateSourceIsReadableDirectory(sourceFolder); - - if(logger.isDebugEnabled()) - { - logger.debug("Bulk import started from '" + sourceFolder.getAbsolutePath() + "'..."); - } - - - bulkImportImpl(bulkImportParameters, nodeImporter, lockToken); - - importStatus.stopImport(); - - if(logger.isDebugEnabled()) - { - logger.debug("Bulk import from '" + getFileName(sourceFolder) + "' succeeded."); - } - - return null; - } - catch(Throwable e) - { - importStatus.stopImport(e); - throw e; - } - finally - { - BulkFSImportEvent bulkImportEvent = new BulkFSImportEvent(importer); - applicationContext.publishEvent(bulkImportEvent); - - releaseLock(lockToken); - } - } - }, false, true); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - this.applicationContext = applicationContext; - } -} +package org.alfresco.repo.bulkimport.impl; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.bulkimport.BulkFSImportEvent; +import org.alfresco.repo.bulkimport.BulkFilesystemImporter; +import org.alfresco.repo.bulkimport.BulkImportParameters; +import org.alfresco.repo.bulkimport.BulkImportStatus; +import org.alfresco.repo.bulkimport.DirectoryAnalyser; +import org.alfresco.repo.bulkimport.NodeImporter; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * + * @since 4.0 + * + */ +public abstract class AbstractBulkFilesystemImporter implements BulkFilesystemImporter, InitializingBean, ApplicationContextAware +{ + private static final QName LOCK = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "BatchFilesystemImport"); + protected static final Log logger = LogFactory.getLog(BulkFilesystemImporter.class); + + protected ApplicationContext applicationContext; + + protected FileFolderService fileFolderService; + protected TransactionService transactionService; + protected PermissionService permissionService; + protected RetryingTransactionHelper transactionHelper; + protected RuleService ruleService; + + protected BulkImportStatusImpl importStatus; + protected DirectoryAnalyser directoryAnalyser = null; + + protected JobLockService jobLockService; + + protected BehaviourFilter behaviourFilter; + + public void setRuleService(RuleService ruleService) + { + this.ruleService = ruleService; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + + public void setImportStatus(BulkImportStatusImpl importStatus) + { + this.importStatus = importStatus; + } + + public final void setDirectoryAnalyser(DirectoryAnalyser directoryAnalyser) + { + this.directoryAnalyser = directoryAnalyser; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * @see org.alfresco.repo.bulkimport.BulkFilesystemImporter#getStatus() + */ + public final BulkImportStatus getStatus() + { + return(importStatus); + } + + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "permissionService", permissionService); + + PropertyCheck.mandatory(this, "importStatus", importStatus); + PropertyCheck.mandatory(this, "directoryAnalyser", directoryAnalyser); + + this.transactionHelper = transactionService.getRetryingTransactionHelper(); + } + + protected abstract void bulkImportImpl(BulkImportParameters bulkImportParameters, NodeImporter nodeImporter, String lockToken); + + /** + * Attempts to get the lock. If the lock couldn't be taken, then null is returned. + * + * @return Returns the lock token or null + */ + protected String getLock(long time) + { + try + { + return jobLockService.getLock(LOCK, time); + } + catch (LockAcquisitionException e) + { + return null; + } + } + + protected void refreshLock(String lockToken, long time) + { + if (lockToken == null) + { + throw new IllegalArgumentException("Must provide existing lockToken"); + } + jobLockService.refreshLock(lockToken, LOCK, time); + } + + protected void releaseLock(String lockToken) + { + if (lockToken == null) + { + throw new IllegalArgumentException("Must provide existing lockToken"); + } + jobLockService.releaseLock(lockToken, LOCK); + } + + /* + * Because commons-lang ToStringBuilder doesn't seem to like unmodifiable Maps + */ + protected final String mapToString(Map map) + { + StringBuffer result = new StringBuffer(); + + if (map != null) + { + result.append('['); + + if (map.size() > 0) + { + for (Object key : map.keySet()) + { + result.append(String.valueOf(key)); + result.append(" = "); + result.append(String.valueOf(map.get(key))); + result.append(",\n"); + } + + // Delete final dangling ", " value + result.delete(result.length() - 2, result.length()); + } + + result.append(']'); + } + else + { + result.append("(null)"); + } + + return(result.toString()); + } + + protected final String getRepositoryPath(NodeRef nodeRef) + { + String result = null; + + if (nodeRef != null) + { + List pathElements = null; + + try + { + pathElements = fileFolderService.getNamePath(null, nodeRef); + + if (pathElements != null && pathElements.size() > 0) + { + StringBuilder temp = new StringBuilder(); + + for (FileInfo pathElement : pathElements) + { + temp.append("/"); + temp.append(pathElement.getName()); + } + + result = temp.toString(); + } + } + catch (final FileNotFoundException fnfe) + { + // Do nothing + } + } + + return(result); + } + + protected final void validateNodeRefIsWritableSpace(NodeRef target) + { + if (target == null) + { + throw new IllegalArgumentException("target must not be null."); + } + + if (!fileFolderService.exists(target)) + { + throw new IllegalArgumentException("Target '" + target.toString() + "' doesn't exist."); + } + + if (AccessStatus.DENIED.equals(permissionService.hasPermission(target, PermissionService.ADD_CHILDREN))) + { + throw new IllegalArgumentException("Target '" + target.toString() + "' is not writeable."); + } + + if (!fileFolderService.getFileInfo(target).isFolder()) + { + throw new IllegalArgumentException("Target '" + target.toString() + "' is not a space."); + } + } + + protected String getFileName(File file) + { + return FileUtils.getFileName(file); + } + + protected String getLockToken() + { + // Take out a bulk filesystem import lock + RetryingTransactionCallback txnWork = new RetryingTransactionCallback() + { + public String execute() throws Exception + { + String lockToken = getLock(20000L); + return lockToken; + } + }; + + String lockToken = transactionService.getRetryingTransactionHelper().doInTransaction(txnWork, false, true); +// if(lockToken == null) +// { +// logger.warn("Can't get lock. Assume multiple bulk filesystem importers ..."); +// return; +// } + + return lockToken; + } + + public void validateSourceIsReadableDirectory(File source) + { + try + { + if (source == null) + { + throw new IllegalArgumentException("source must not be null."); + } + + if (!source.exists()) + { + throw new IllegalArgumentException("Source '" + source.getCanonicalPath() + "' doesn't exist."); + } + + if (!source.canRead()) + { + throw new IllegalArgumentException("Source '" + source.getCanonicalPath() + "' is not readable."); + } + + if (!source.isDirectory()) + { + throw new IllegalArgumentException("Source '" + source.getCanonicalPath() + "' is not a directory."); + } + } + catch (final IOException ioe) + { + throw new RuntimeException(ioe); + } + } + + public void asyncBulkImport(final BulkImportParameters bulkImportParameters, final NodeImporter nodeImporter) + { + final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + + Runnable backgroundLogic = new Runnable() + { + public void run() + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + bulkImport(bulkImportParameters, nodeImporter); + return null; + } + }, currentUser); + } + }; + + Thread backgroundThread = new Thread(backgroundLogic, "BulkFilesystemImport-BackgroundThread"); + backgroundThread.start(); + } + + /** + * @see org.alfresco.repo.bulkimport.BulkFilesystemImporter#bulkImport(org.alfresco.repo.bulkimport.BulkImportParameters, org.alfresco.repo.bulkimport.NodeImporter) + */ + public void bulkImport(final BulkImportParameters bulkImportParameters, final NodeImporter nodeImporter) + { + final File sourceFolder = nodeImporter.getSourceFolder(); + final BulkFilesystemImporter importer = this; + + transactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + final String sourceDirectory = getFileName(sourceFolder); + final String targetSpace = getRepositoryPath(bulkImportParameters.getTarget()); + final String lockToken = getLockToken(); + + try + { + importStatus.startImport(sourceDirectory, targetSpace); + + BulkFSImportEvent bulkImportEvent = new BulkFSImportEvent(importer); + applicationContext.publishEvent(bulkImportEvent); + + validateNodeRefIsWritableSpace(bulkImportParameters.getTarget()); + validateSourceIsReadableDirectory(sourceFolder); + + if(logger.isDebugEnabled()) + { + logger.debug("Bulk import started from '" + sourceFolder.getAbsolutePath() + "'..."); + } + + + bulkImportImpl(bulkImportParameters, nodeImporter, lockToken); + + importStatus.stopImport(); + + if(logger.isDebugEnabled()) + { + logger.debug("Bulk import from '" + getFileName(sourceFolder) + "' succeeded."); + } + + return null; + } + catch(Throwable e) + { + importStatus.stopImport(e); + throw e; + } + finally + { + BulkFSImportEvent bulkImportEvent = new BulkFSImportEvent(importer); + applicationContext.publishEvent(bulkImportEvent); + + releaseLock(lockToken); + } + } + }, false, true); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/AbstractFilesystemTracker.java b/source/java/org/alfresco/repo/bulkimport/impl/AbstractFilesystemTracker.java index 781742b6b5..aa32b34b5c 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/AbstractFilesystemTracker.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/AbstractFilesystemTracker.java @@ -1,72 +1,72 @@ -package org.alfresco.repo.bulkimport.impl; - -import java.io.File; -import java.io.FileFilter; - -import org.alfresco.repo.bulkimport.AnalysedDirectory; -import org.alfresco.repo.bulkimport.DirectoryAnalyser; -import org.alfresco.repo.bulkimport.FilesystemTracker; -import org.alfresco.repo.bulkimport.ImportableItem; -import org.alfresco.util.PropertyCheck; -import org.apache.log4j.Logger; - -/** - * - * @since 4.0 - * - */ -public abstract class AbstractFilesystemTracker implements FilesystemTracker -{ - protected static Logger logger = Logger.getLogger(FilesystemTracker.class); - - protected DirectoryAnalyser directoryAnalyser = null; - - public final void setDirectoryAnalyser(DirectoryAnalyser directoryAnalyser) - { - this.directoryAnalyser = directoryAnalyser; - } - - public void afterPropertiesSet() throws Exception - { - PropertyCheck.mandatory(this, "directoryAnalyser", directoryAnalyser); - } - - protected final AnalysedDirectory getImportableItemsInDirectory(ImportableItem directory) - { - AnalysedDirectory analysedDirectory = directoryAnalyser.analyseDirectory(directory, null); - return analysedDirectory; - } - - protected final AnalysedDirectory getImportableDirectoriesInDirectory(ImportableItem directory, final int count) - { - FileFilter filter = null; - - if(count != -1) - { - filter = new FileFilter() - { - private int i = count; - - @Override - public boolean accept(File file) - { - return file.isDirectory() && i-- > 0; - } - }; - } - else - { - filter = new FileFilter() - { - @Override - public boolean accept(File file) - { - return file.isDirectory(); - } - }; - } - - AnalysedDirectory analysedDirectory = directoryAnalyser.analyseDirectory(directory, filter); - return analysedDirectory; - } -} +package org.alfresco.repo.bulkimport.impl; + +import java.io.File; +import java.io.FileFilter; + +import org.alfresco.repo.bulkimport.AnalysedDirectory; +import org.alfresco.repo.bulkimport.DirectoryAnalyser; +import org.alfresco.repo.bulkimport.FilesystemTracker; +import org.alfresco.repo.bulkimport.ImportableItem; +import org.alfresco.util.PropertyCheck; +import org.apache.log4j.Logger; + +/** + * + * @since 4.0 + * + */ +public abstract class AbstractFilesystemTracker implements FilesystemTracker +{ + protected static Logger logger = Logger.getLogger(FilesystemTracker.class); + + protected DirectoryAnalyser directoryAnalyser = null; + + public final void setDirectoryAnalyser(DirectoryAnalyser directoryAnalyser) + { + this.directoryAnalyser = directoryAnalyser; + } + + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "directoryAnalyser", directoryAnalyser); + } + + protected final AnalysedDirectory getImportableItemsInDirectory(ImportableItem directory) + { + AnalysedDirectory analysedDirectory = directoryAnalyser.analyseDirectory(directory, null); + return analysedDirectory; + } + + protected final AnalysedDirectory getImportableDirectoriesInDirectory(ImportableItem directory, final int count) + { + FileFilter filter = null; + + if(count != -1) + { + filter = new FileFilter() + { + private int i = count; + + @Override + public boolean accept(File file) + { + return file.isDirectory() && i-- > 0; + } + }; + } + else + { + filter = new FileFilter() + { + @Override + public boolean accept(File file) + { + return file.isDirectory(); + } + }; + } + + AnalysedDirectory analysedDirectory = directoryAnalyser.analyseDirectory(directory, filter); + return analysedDirectory; + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/AbstractNodeImporter.java b/source/java/org/alfresco/repo/bulkimport/impl/AbstractNodeImporter.java index 6e5be5e300..215064b872 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/AbstractNodeImporter.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/AbstractNodeImporter.java @@ -1,437 +1,437 @@ -package org.alfresco.repo.bulkimport.impl; - -import java.io.File; -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.bulkimport.BulkFilesystemImporter; -import org.alfresco.repo.bulkimport.DirectoryAnalyser; -import org.alfresco.repo.bulkimport.ImportableItem; -import org.alfresco.repo.bulkimport.MetadataLoader; -import org.alfresco.repo.bulkimport.NodeImporter; -import org.alfresco.repo.bulkimport.impl.BulkImportStatusImpl.NodeState; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.version.VersionModel; -import org.alfresco.service.cmr.model.FileExistsException; -import org.alfresco.service.cmr.model.FileFolderService; -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.version.VersionService; -import org.alfresco.service.cmr.version.VersionType; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.Triple; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Abstract base class for the node importer, containing helper methods for use by subclasses. - * - * @since 4.0 - * - */ -public abstract class AbstractNodeImporter implements NodeImporter -{ - protected final static Log logger = LogFactory.getLog(BulkFilesystemImporter.class); - - protected FileFolderService fileFolderService; - protected NodeService nodeService; - protected MetadataLoader metadataLoader = null; - protected BulkImportStatusImpl importStatus; - protected VersionService versionService; - protected BehaviourFilter behaviourFilter; - - public void setVersionService(VersionService versionService) - { - this.versionService = versionService; - } - - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setMetadataLoader(MetadataLoader metadataLoader) - { - this.metadataLoader = metadataLoader; - } - - public void setImportStatus(BulkImportStatusImpl importStatus) - { - this.importStatus = importStatus; - } - - public void setBehaviourFilter(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } - - protected abstract NodeRef importImportableItemImpl(ImportableItem importableItem, boolean replaceExisting); - protected abstract void importContentAndMetadata(NodeRef nodeRef, ImportableItem.ContentAndMetadata contentAndMetadata, MetadataLoader.Metadata metadata); - - /* - * Because commons-lang ToStringBuilder doesn't seem to like unmodifiable Maps - */ - protected final String mapToString(Map map) - { - StringBuffer result = new StringBuffer(); - - if (map != null) - { - result.append('['); - - if (map.size() > 0) - { - for (Object key : map.keySet()) - { - result.append(String.valueOf(key)); - result.append(" = "); - result.append(String.valueOf(map.get(key))); - result.append(",\n"); - } - - // Delete final dangling ", " value - result.delete(result.length() - 2, result.length()); - } - - result.append(']'); - } - else - { - result.append("(null)"); - } - - return(result.toString()); - } - - /** - * Returns the name of the given importable item. This is the final name of the item, as it would appear in the repository, - * after metadata renames are taken into account. - * - * @param importableItem The importableItem with which to - * @param metadata MetadataLoader.Metadata - * @return the name of the given importable item - */ - protected final String getImportableItemName(ImportableItem importableItem, MetadataLoader.Metadata metadata) - { - String result = null; - - // Step 1: attempt to get name from metadata - if (metadata != null) - { - result = (String)metadata.getProperties().get(ContentModel.PROP_NAME); - } - - // Step 2: attempt to get name from metadata file - if (result == null && - importableItem != null && - importableItem.getHeadRevision() != null) - { - File metadataFile = importableItem.getHeadRevision().getMetadataFile(); - - if (metadataFile != null) - { - final String metadataFileName = metadataFile.getName(); - - result = metadataFileName.substring(0, metadataFileName.length() - - (MetadataLoader.METADATA_SUFFIX.length() + metadataLoader.getMetadataFileExtension().length())); - } - } - - // Step 3: read the parent filename from the item itself - if (result == null && - importableItem != null) - { - result = importableItem.getHeadRevision().getContentFile().getName(); - } - - return(result); - } - - protected final int importImportableItemFile(NodeRef nodeRef, ImportableItem importableItem, MetadataLoader.Metadata metadata, NodeState nodeState) - { - int result = 0; - - if (importableItem.hasVersionEntries()) - { - result = importContentVersions(nodeRef, importableItem, nodeState); - } - else - { - if (nodeState == NodeState.REPLACED) - { - nodeService.removeAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE); - } - - importContentAndMetadata(nodeRef, importableItem.getHeadRevision(), metadata); - } - - return(result); - } - - protected final int importContentVersions(NodeRef nodeRef, ImportableItem importableItem, NodeState nodeState) - { - int result = 0; - Map versionProperties = new HashMap(); - // Note: PROP_VERSION_LABEL is a "reserved" property, and cannot be modified by custom code. - // In other words, we can't use the version label on disk as the version label in Alfresco. :-( - // See: http://code.google.com/p/alfresco-bulk-filesystem-import/issues/detail?id=85 - //versionProperties.put(ContentModel.PROP_VERSION_LABEL.toPrefixString(), String.valueOf(versionEntry.getVersion())); - versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); // Load every version as a major version for now - see http://code.google.com/p/alfresco-bulk-filesystem-import/issues/detail?id=84 - - // handle versions - for (final ImportableItem.VersionedContentAndMetadata versionEntry : importableItem.getVersionEntries()) - { - MetadataLoader.Metadata metadata = loadMetadata(versionEntry); - importContentAndMetadata(nodeRef, versionEntry, metadata); - - if (logger.isDebugEnabled()) logger.debug("Creating v" + String.valueOf(versionEntry.getVersion()) + " of node '" + nodeRef.toString() + "' (note: version label in Alfresco will not be the same - it is not currently possible to explicitly force a particular version label)."); - - versionService.createVersion(nodeRef, versionProperties); - result += metadata.getProperties().size() + 4; // Add 4 for "standard" metadata properties read from filesystem - } - - // handle head version, if exists - ImportableItem.ContentAndMetadata headRevContentAndMetadata = importableItem.getHeadRevision(); - if(headRevContentAndMetadata != null && (headRevContentAndMetadata.contentFileExists() - || headRevContentAndMetadata.metadataFileExists())) - { - if (logger.isDebugEnabled()) - { - logger.debug("Creating head revision of node " + nodeRef.toString()); - } - - MetadataLoader.Metadata metadata = loadMetadata(headRevContentAndMetadata); - importContentAndMetadata(nodeRef, headRevContentAndMetadata, metadata); - versionService.createVersion(nodeRef, versionProperties); - } - - return(result); - } - - protected final Triple createOrFindNode(NodeRef target, ImportableItem importableItem, - boolean replaceExisting, MetadataLoader.Metadata metadata) - { - Triple result = null; - boolean isDirectory = false; - NodeState nodeState = replaceExisting ? NodeState.REPLACED : NodeState.SKIPPED; - String nodeName = getImportableItemName(importableItem, metadata); - NodeRef nodeRef = null; - - //####TODO: handle this more elegantly - if (nodeName == null) - { - throw new IllegalStateException("Unable to determine node name for " + String.valueOf(importableItem)); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Searching for node with name '" + nodeName + "' within node '" + target.toString() + "'."); - } - - nodeRef = fileFolderService.searchSimple(target, nodeName); - - // If we didn't find an existing item, create a new node in the repo. - if (nodeRef == null) - { - // But only if the content file exists - we don't create new nodes based on metadata-only importableItems - if (importableItem.getHeadRevision().contentFileExists()) - { - isDirectory = ImportableItem.FileType.DIRECTORY.equals(importableItem.getHeadRevision().getContentFileType()); - - try - { - if (logger.isDebugEnabled()) logger.debug("Creating new node of type '" + metadata.getType().toString() + "' with name '" + nodeName + "' within node '" + target.toString() + "'."); - nodeRef = fileFolderService.create(target, nodeName, metadata.getType()).getNodeRef(); - nodeState = NodeState.CREATED; - } - catch (final FileExistsException fee) - { - if (logger.isWarnEnabled()) logger.warn("Node with name '" + nodeName + "' within node '" + target.toString() + "' was created concurrently to the bulk import. Skipping importing it.", fee); - nodeRef = null; - nodeState = NodeState.SKIPPED; - } - } - else - { - if (logger.isWarnEnabled()) logger.warn("Skipping creation of new node '" + nodeName + "' within node '" + target.toString() + "' since it doesn't have a content file."); - nodeRef = null; - nodeState = NodeState.SKIPPED; - } - } - // We found the node in the repository. Make sure we return the NodeRef, so that recursive loading works (we need the NodeRef of all sub-spaces, even if we didn't create them). - else - { - if (replaceExisting) - { - boolean targetNodeIsSpace = fileFolderService.getFileInfo(nodeRef).isFolder(); - - if (importableItem.getHeadRevision().contentFileExists()) - { - // If the source file exists, ensure that the target node is of the same type (i.e. file or folder) as it. - isDirectory = ImportableItem.FileType.DIRECTORY.equals(importableItem.getHeadRevision().getContentFileType()); - - if (isDirectory != targetNodeIsSpace) - { - if (logger.isWarnEnabled()) logger.warn("Skipping replacement of " + (isDirectory ? "Directory " : "File ") + - "'" + getFileName(importableItem.getHeadRevision().getContentFile()) + "'. " + - "The target node in the repository is a " + (targetNodeIsSpace ? "space node" : "content node") + "."); - nodeState = NodeState.SKIPPED; - } - } - else - { - isDirectory = targetNodeIsSpace; - } - - if (nodeRef != null) - { - if (metadata.getType() != null) - { - // Finally, specialise the type. - if (logger.isDebugEnabled()) logger.debug("Specialising type of node '" + nodeRef.toString() + "' to '" + String.valueOf(metadata.getType()) + "'."); - nodeService.setType(nodeRef, metadata.getType()); - } - - nodeState = NodeState.REPLACED; - } - } - else - { - if (logger.isDebugEnabled()) logger.debug("Found content node '" + nodeRef.toString() + "', but replaceExisting=false, so skipping it."); - nodeState = NodeState.SKIPPED; - } - } - - result = new Triple(nodeRef, isDirectory, nodeState); - - return(result); - } - - protected String getFileName(File file) - { - return FileUtils.getFileName(file); - } - - protected final void importImportableItemMetadata(NodeRef nodeRef, File parentFile, MetadataLoader.Metadata metadata) - { - // Attach aspects - if (metadata.getAspects() != null) - { - for (final QName aspect : metadata.getAspects()) - { - if (logger.isDebugEnabled()) logger.debug("Attaching aspect '" + aspect.toString() + "' to node '" + nodeRef.toString() + "'."); - - nodeService.addAspect(nodeRef, aspect, null); // Note: we set the aspect's properties separately, hence null for the third parameter - } - } - - // Set property values for both the type and any aspect(s) - if (metadata.getProperties() != null) - { - if (logger.isDebugEnabled()) logger.debug("Adding properties to node '" + nodeRef.toString() + "':\n" + mapToString(metadata.getProperties())); - - try - { - nodeService.addProperties(nodeRef, metadata.getProperties()); - } - catch (final InvalidNodeRefException inre) - { - if (!nodeRef.equals(inre.getNodeRef())) - { - // Caused by an invalid NodeRef in the metadata (e.g. in an association) - throw new IllegalStateException("Invalid nodeRef found in metadata for '" + getFileName(parentFile) + "'. " + - "Probable cause: an association is being populated via metadata, but the " + - "NodeRef for the target of that association ('" + inre.getNodeRef() + "') is invalid. " + - "Please double check your metadata file and try again.", inre); - } - else - { - // Logic bug in the BFSIT. :-( - throw inre; - } - } - } - } - - protected final void importImportableItemDirectory(NodeRef nodeRef, ImportableItem importableItem, MetadataLoader.Metadata metadata) - { - if (importableItem.hasVersionEntries()) - { - logger.warn("Skipping versions for directory '" + getFileName(importableItem.getHeadRevision().getContentFile()) + "' - Alfresco does not support versioned spaces."); - } - - // Attach aspects and set all properties - importImportableItemMetadata(nodeRef, importableItem.getHeadRevision().getContentFile(), metadata); - } - - protected final MetadataLoader.Metadata loadMetadata(ImportableItem.ContentAndMetadata contentAndMetadata) - { - MetadataLoader.Metadata result = new MetadataLoader.Metadata(); - - // Load "standard" metadata from the filesystem - if (contentAndMetadata != null && contentAndMetadata.contentFileExists()) - { - final String filename = contentAndMetadata.getContentFile().getName().trim().replaceFirst(DirectoryAnalyser.VERSION_SUFFIX_REGEX, ""); // Strip off the version suffix (if any) - final Date modified = new Date(contentAndMetadata.getContentFile().lastModified()); - final Date created = modified; //TODO: determine proper file creation time (awaiting JDK 1.7 NIO2 library) - - result.setType(ImportableItem.FileType.FILE.equals(contentAndMetadata.getContentFileType()) ? ContentModel.TYPE_CONTENT : ContentModel.TYPE_FOLDER); - result.addProperty(ContentModel.PROP_NAME, filename); - result.addProperty(ContentModel.PROP_TITLE, filename); - result.addProperty(ContentModel.PROP_CREATED, created); - result.addProperty(ContentModel.PROP_MODIFIED, modified); - } - - if (metadataLoader != null) - { - metadataLoader.loadMetadata(contentAndMetadata, result); - } - - return(result); - } - - public NodeRef importImportableItem(ImportableItem importableItem, boolean replaceExisting) - { - if(logger.isDebugEnabled()) - { - logger.debug("Importing " + String.valueOf(importableItem)); - } - - NodeRef nodeRef = importImportableItemImpl(importableItem, replaceExisting); - - // allow parent to be garbage collected - //importableItem.setParent(null); -// importableItem.clearParent(); - - importableItem.setNodeRef(nodeRef); - - return nodeRef; - } - - protected void skipImportableDirectory(ImportableItem importableItem) - { - if (logger.isInfoEnabled()) - { - logger.info("Skipping '" + getFileName(importableItem.getHeadRevision().getContentFile())); - } - importStatus.incrementImportableItemsSkipped(importableItem, true); - } - - protected void skipImportableFile(ImportableItem importableItem) - { - if (logger.isInfoEnabled()) - { - logger.info("Skipping '" + getFileName(importableItem.getHeadRevision().getContentFile())); - } - importStatus.incrementImportableItemsSkipped(importableItem, false); - } -} +package org.alfresco.repo.bulkimport.impl; + +import java.io.File; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.bulkimport.BulkFilesystemImporter; +import org.alfresco.repo.bulkimport.DirectoryAnalyser; +import org.alfresco.repo.bulkimport.ImportableItem; +import org.alfresco.repo.bulkimport.MetadataLoader; +import org.alfresco.repo.bulkimport.NodeImporter; +import org.alfresco.repo.bulkimport.impl.BulkImportStatusImpl.NodeState; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +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.version.VersionService; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Triple; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract base class for the node importer, containing helper methods for use by subclasses. + * + * @since 4.0 + * + */ +public abstract class AbstractNodeImporter implements NodeImporter +{ + protected final static Log logger = LogFactory.getLog(BulkFilesystemImporter.class); + + protected FileFolderService fileFolderService; + protected NodeService nodeService; + protected MetadataLoader metadataLoader = null; + protected BulkImportStatusImpl importStatus; + protected VersionService versionService; + protected BehaviourFilter behaviourFilter; + + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setMetadataLoader(MetadataLoader metadataLoader) + { + this.metadataLoader = metadataLoader; + } + + public void setImportStatus(BulkImportStatusImpl importStatus) + { + this.importStatus = importStatus; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + protected abstract NodeRef importImportableItemImpl(ImportableItem importableItem, boolean replaceExisting); + protected abstract void importContentAndMetadata(NodeRef nodeRef, ImportableItem.ContentAndMetadata contentAndMetadata, MetadataLoader.Metadata metadata); + + /* + * Because commons-lang ToStringBuilder doesn't seem to like unmodifiable Maps + */ + protected final String mapToString(Map map) + { + StringBuffer result = new StringBuffer(); + + if (map != null) + { + result.append('['); + + if (map.size() > 0) + { + for (Object key : map.keySet()) + { + result.append(String.valueOf(key)); + result.append(" = "); + result.append(String.valueOf(map.get(key))); + result.append(",\n"); + } + + // Delete final dangling ", " value + result.delete(result.length() - 2, result.length()); + } + + result.append(']'); + } + else + { + result.append("(null)"); + } + + return(result.toString()); + } + + /** + * Returns the name of the given importable item. This is the final name of the item, as it would appear in the repository, + * after metadata renames are taken into account. + * + * @param importableItem The importableItem with which to + * @param metadata MetadataLoader.Metadata + * @return the name of the given importable item + */ + protected final String getImportableItemName(ImportableItem importableItem, MetadataLoader.Metadata metadata) + { + String result = null; + + // Step 1: attempt to get name from metadata + if (metadata != null) + { + result = (String)metadata.getProperties().get(ContentModel.PROP_NAME); + } + + // Step 2: attempt to get name from metadata file + if (result == null && + importableItem != null && + importableItem.getHeadRevision() != null) + { + File metadataFile = importableItem.getHeadRevision().getMetadataFile(); + + if (metadataFile != null) + { + final String metadataFileName = metadataFile.getName(); + + result = metadataFileName.substring(0, metadataFileName.length() - + (MetadataLoader.METADATA_SUFFIX.length() + metadataLoader.getMetadataFileExtension().length())); + } + } + + // Step 3: read the parent filename from the item itself + if (result == null && + importableItem != null) + { + result = importableItem.getHeadRevision().getContentFile().getName(); + } + + return(result); + } + + protected final int importImportableItemFile(NodeRef nodeRef, ImportableItem importableItem, MetadataLoader.Metadata metadata, NodeState nodeState) + { + int result = 0; + + if (importableItem.hasVersionEntries()) + { + result = importContentVersions(nodeRef, importableItem, nodeState); + } + else + { + if (nodeState == NodeState.REPLACED) + { + nodeService.removeAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE); + } + + importContentAndMetadata(nodeRef, importableItem.getHeadRevision(), metadata); + } + + return(result); + } + + protected final int importContentVersions(NodeRef nodeRef, ImportableItem importableItem, NodeState nodeState) + { + int result = 0; + Map versionProperties = new HashMap(); + // Note: PROP_VERSION_LABEL is a "reserved" property, and cannot be modified by custom code. + // In other words, we can't use the version label on disk as the version label in Alfresco. :-( + // See: http://code.google.com/p/alfresco-bulk-filesystem-import/issues/detail?id=85 + //versionProperties.put(ContentModel.PROP_VERSION_LABEL.toPrefixString(), String.valueOf(versionEntry.getVersion())); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); // Load every version as a major version for now - see http://code.google.com/p/alfresco-bulk-filesystem-import/issues/detail?id=84 + + // handle versions + for (final ImportableItem.VersionedContentAndMetadata versionEntry : importableItem.getVersionEntries()) + { + MetadataLoader.Metadata metadata = loadMetadata(versionEntry); + importContentAndMetadata(nodeRef, versionEntry, metadata); + + if (logger.isDebugEnabled()) logger.debug("Creating v" + String.valueOf(versionEntry.getVersion()) + " of node '" + nodeRef.toString() + "' (note: version label in Alfresco will not be the same - it is not currently possible to explicitly force a particular version label)."); + + versionService.createVersion(nodeRef, versionProperties); + result += metadata.getProperties().size() + 4; // Add 4 for "standard" metadata properties read from filesystem + } + + // handle head version, if exists + ImportableItem.ContentAndMetadata headRevContentAndMetadata = importableItem.getHeadRevision(); + if(headRevContentAndMetadata != null && (headRevContentAndMetadata.contentFileExists() + || headRevContentAndMetadata.metadataFileExists())) + { + if (logger.isDebugEnabled()) + { + logger.debug("Creating head revision of node " + nodeRef.toString()); + } + + MetadataLoader.Metadata metadata = loadMetadata(headRevContentAndMetadata); + importContentAndMetadata(nodeRef, headRevContentAndMetadata, metadata); + versionService.createVersion(nodeRef, versionProperties); + } + + return(result); + } + + protected final Triple createOrFindNode(NodeRef target, ImportableItem importableItem, + boolean replaceExisting, MetadataLoader.Metadata metadata) + { + Triple result = null; + boolean isDirectory = false; + NodeState nodeState = replaceExisting ? NodeState.REPLACED : NodeState.SKIPPED; + String nodeName = getImportableItemName(importableItem, metadata); + NodeRef nodeRef = null; + + //####TODO: handle this more elegantly + if (nodeName == null) + { + throw new IllegalStateException("Unable to determine node name for " + String.valueOf(importableItem)); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Searching for node with name '" + nodeName + "' within node '" + target.toString() + "'."); + } + + nodeRef = fileFolderService.searchSimple(target, nodeName); + + // If we didn't find an existing item, create a new node in the repo. + if (nodeRef == null) + { + // But only if the content file exists - we don't create new nodes based on metadata-only importableItems + if (importableItem.getHeadRevision().contentFileExists()) + { + isDirectory = ImportableItem.FileType.DIRECTORY.equals(importableItem.getHeadRevision().getContentFileType()); + + try + { + if (logger.isDebugEnabled()) logger.debug("Creating new node of type '" + metadata.getType().toString() + "' with name '" + nodeName + "' within node '" + target.toString() + "'."); + nodeRef = fileFolderService.create(target, nodeName, metadata.getType()).getNodeRef(); + nodeState = NodeState.CREATED; + } + catch (final FileExistsException fee) + { + if (logger.isWarnEnabled()) logger.warn("Node with name '" + nodeName + "' within node '" + target.toString() + "' was created concurrently to the bulk import. Skipping importing it.", fee); + nodeRef = null; + nodeState = NodeState.SKIPPED; + } + } + else + { + if (logger.isWarnEnabled()) logger.warn("Skipping creation of new node '" + nodeName + "' within node '" + target.toString() + "' since it doesn't have a content file."); + nodeRef = null; + nodeState = NodeState.SKIPPED; + } + } + // We found the node in the repository. Make sure we return the NodeRef, so that recursive loading works (we need the NodeRef of all sub-spaces, even if we didn't create them). + else + { + if (replaceExisting) + { + boolean targetNodeIsSpace = fileFolderService.getFileInfo(nodeRef).isFolder(); + + if (importableItem.getHeadRevision().contentFileExists()) + { + // If the source file exists, ensure that the target node is of the same type (i.e. file or folder) as it. + isDirectory = ImportableItem.FileType.DIRECTORY.equals(importableItem.getHeadRevision().getContentFileType()); + + if (isDirectory != targetNodeIsSpace) + { + if (logger.isWarnEnabled()) logger.warn("Skipping replacement of " + (isDirectory ? "Directory " : "File ") + + "'" + getFileName(importableItem.getHeadRevision().getContentFile()) + "'. " + + "The target node in the repository is a " + (targetNodeIsSpace ? "space node" : "content node") + "."); + nodeState = NodeState.SKIPPED; + } + } + else + { + isDirectory = targetNodeIsSpace; + } + + if (nodeRef != null) + { + if (metadata.getType() != null) + { + // Finally, specialise the type. + if (logger.isDebugEnabled()) logger.debug("Specialising type of node '" + nodeRef.toString() + "' to '" + String.valueOf(metadata.getType()) + "'."); + nodeService.setType(nodeRef, metadata.getType()); + } + + nodeState = NodeState.REPLACED; + } + } + else + { + if (logger.isDebugEnabled()) logger.debug("Found content node '" + nodeRef.toString() + "', but replaceExisting=false, so skipping it."); + nodeState = NodeState.SKIPPED; + } + } + + result = new Triple(nodeRef, isDirectory, nodeState); + + return(result); + } + + protected String getFileName(File file) + { + return FileUtils.getFileName(file); + } + + protected final void importImportableItemMetadata(NodeRef nodeRef, File parentFile, MetadataLoader.Metadata metadata) + { + // Attach aspects + if (metadata.getAspects() != null) + { + for (final QName aspect : metadata.getAspects()) + { + if (logger.isDebugEnabled()) logger.debug("Attaching aspect '" + aspect.toString() + "' to node '" + nodeRef.toString() + "'."); + + nodeService.addAspect(nodeRef, aspect, null); // Note: we set the aspect's properties separately, hence null for the third parameter + } + } + + // Set property values for both the type and any aspect(s) + if (metadata.getProperties() != null) + { + if (logger.isDebugEnabled()) logger.debug("Adding properties to node '" + nodeRef.toString() + "':\n" + mapToString(metadata.getProperties())); + + try + { + nodeService.addProperties(nodeRef, metadata.getProperties()); + } + catch (final InvalidNodeRefException inre) + { + if (!nodeRef.equals(inre.getNodeRef())) + { + // Caused by an invalid NodeRef in the metadata (e.g. in an association) + throw new IllegalStateException("Invalid nodeRef found in metadata for '" + getFileName(parentFile) + "'. " + + "Probable cause: an association is being populated via metadata, but the " + + "NodeRef for the target of that association ('" + inre.getNodeRef() + "') is invalid. " + + "Please double check your metadata file and try again.", inre); + } + else + { + // Logic bug in the BFSIT. :-( + throw inre; + } + } + } + } + + protected final void importImportableItemDirectory(NodeRef nodeRef, ImportableItem importableItem, MetadataLoader.Metadata metadata) + { + if (importableItem.hasVersionEntries()) + { + logger.warn("Skipping versions for directory '" + getFileName(importableItem.getHeadRevision().getContentFile()) + "' - Alfresco does not support versioned spaces."); + } + + // Attach aspects and set all properties + importImportableItemMetadata(nodeRef, importableItem.getHeadRevision().getContentFile(), metadata); + } + + protected final MetadataLoader.Metadata loadMetadata(ImportableItem.ContentAndMetadata contentAndMetadata) + { + MetadataLoader.Metadata result = new MetadataLoader.Metadata(); + + // Load "standard" metadata from the filesystem + if (contentAndMetadata != null && contentAndMetadata.contentFileExists()) + { + final String filename = contentAndMetadata.getContentFile().getName().trim().replaceFirst(DirectoryAnalyser.VERSION_SUFFIX_REGEX, ""); // Strip off the version suffix (if any) + final Date modified = new Date(contentAndMetadata.getContentFile().lastModified()); + final Date created = modified; //TODO: determine proper file creation time (awaiting JDK 1.7 NIO2 library) + + result.setType(ImportableItem.FileType.FILE.equals(contentAndMetadata.getContentFileType()) ? ContentModel.TYPE_CONTENT : ContentModel.TYPE_FOLDER); + result.addProperty(ContentModel.PROP_NAME, filename); + result.addProperty(ContentModel.PROP_TITLE, filename); + result.addProperty(ContentModel.PROP_CREATED, created); + result.addProperty(ContentModel.PROP_MODIFIED, modified); + } + + if (metadataLoader != null) + { + metadataLoader.loadMetadata(contentAndMetadata, result); + } + + return(result); + } + + public NodeRef importImportableItem(ImportableItem importableItem, boolean replaceExisting) + { + if(logger.isDebugEnabled()) + { + logger.debug("Importing " + String.valueOf(importableItem)); + } + + NodeRef nodeRef = importImportableItemImpl(importableItem, replaceExisting); + + // allow parent to be garbage collected + //importableItem.setParent(null); +// importableItem.clearParent(); + + importableItem.setNodeRef(nodeRef); + + return nodeRef; + } + + protected void skipImportableDirectory(ImportableItem importableItem) + { + if (logger.isInfoEnabled()) + { + logger.info("Skipping '" + getFileName(importableItem.getHeadRevision().getContentFile())); + } + importStatus.incrementImportableItemsSkipped(importableItem, true); + } + + protected void skipImportableFile(ImportableItem importableItem) + { + if (logger.isInfoEnabled()) + { + logger.info("Skipping '" + getFileName(importableItem.getHeadRevision().getContentFile())); + } + importStatus.incrementImportableItemsSkipped(importableItem, false); + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/AbstractNodeImporterFactory.java b/source/java/org/alfresco/repo/bulkimport/impl/AbstractNodeImporterFactory.java index 16fb687fbe..f52d4c2938 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/AbstractNodeImporterFactory.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/AbstractNodeImporterFactory.java @@ -1,52 +1,52 @@ -package org.alfresco.repo.bulkimport.impl; - -import org.alfresco.repo.bulkimport.MetadataLoader; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.version.VersionService; - -/** - * - * @since 4.0 - * - */ -public class AbstractNodeImporterFactory -{ - protected FileFolderService fileFolderService; - protected NodeService nodeService; - protected MetadataLoader metadataLoader = null; - protected BulkImportStatusImpl importStatus; - protected VersionService versionService; - protected BehaviourFilter behaviourFilter; - - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setMetadataLoader(MetadataLoader metadataLoader) - { - this.metadataLoader = metadataLoader; - } - - public void setImportStatus(BulkImportStatusImpl importStatus) - { - this.importStatus = importStatus; - } - - public void setVersionService(VersionService versionService) - { - this.versionService = versionService; - } - - public void setBehaviourFilter(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } -} +package org.alfresco.repo.bulkimport.impl; + +import org.alfresco.repo.bulkimport.MetadataLoader; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.version.VersionService; + +/** + * + * @since 4.0 + * + */ +public class AbstractNodeImporterFactory +{ + protected FileFolderService fileFolderService; + protected NodeService nodeService; + protected MetadataLoader metadataLoader = null; + protected BulkImportStatusImpl importStatus; + protected VersionService versionService; + protected BehaviourFilter behaviourFilter; + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setMetadataLoader(MetadataLoader metadataLoader) + { + this.metadataLoader = metadataLoader; + } + + public void setImportStatus(BulkImportStatusImpl importStatus) + { + this.importStatus = importStatus; + } + + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/BulkImportStatusImpl.java b/source/java/org/alfresco/repo/bulkimport/impl/BulkImportStatusImpl.java index f2798a2e4e..83c40199d6 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/BulkImportStatusImpl.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/BulkImportStatusImpl.java @@ -1,655 +1,655 @@ -package org.alfresco.repo.bulkimport.impl; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Date; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.bulkimport.BulkImportStatus; -import org.alfresco.repo.bulkimport.ImportableItem; - -/** - * Thread-safe implementation of Bulk Import Status. - * - * @since 4.0 - * - * @see org.alfresco.repo.bulkimport.BulkImportStatus - */ -public class BulkImportStatusImpl implements BulkImportStatus -{ - public enum NodeState - { - SKIPPED, - CREATED, - REPLACED - }; - - // General information - private int numThreads; - private int batchSize; - private AtomicBoolean inProgress = new AtomicBoolean(); - private String sourceDirectory = null; - private String targetSpace = null; - private Date startDate = null; - private Date endDate = null; - private Long startNs = null; - private Long endNs = null; - private Throwable lastException = null; - private AtomicLong numberOfBatchesCompleted = new AtomicLong(); - - // Read-side information - private AtomicLong numberOfFoldersScanned = new AtomicLong(); - private AtomicLong numberOfFilesScanned = new AtomicLong(); - private AtomicLong numberOfUnreadableEntries = new AtomicLong(); - - private AtomicLong numberOfContentFilesRead = new AtomicLong(); - private AtomicLong numberOfContentBytesRead = new AtomicLong(); - - private AtomicLong numberOfMetadataFilesRead = new AtomicLong(); - private AtomicLong numberOfMetadataBytesRead = new AtomicLong(); - - private AtomicLong numberOfContentVersionFilesRead = new AtomicLong(); - private AtomicLong numberOfContentVersionBytesRead = new AtomicLong(); - - private AtomicLong numberOfMetadataVersionFilesRead = new AtomicLong(); - private AtomicLong numberOfMetadataVersionBytesRead = new AtomicLong(); - - // Write-side information - private AtomicLong numberOfSpaceNodesCreated = new AtomicLong(); - private AtomicLong numberOfSpaceNodesReplaced = new AtomicLong(); - private AtomicLong numberOfSpaceNodesSkipped = new AtomicLong(); - private AtomicLong numberOfSpacePropertiesWritten = new AtomicLong(); - - private AtomicLong numberOfContentNodesCreated = new AtomicLong(); - private AtomicLong numberOfContentNodesReplaced = new AtomicLong(); - private AtomicLong numberOfContentNodesSkipped = new AtomicLong(); - private AtomicLong numberOfContentBytesWritten = new AtomicLong(); - private AtomicLong numberOfContentPropertiesWritten = new AtomicLong(); - - private AtomicLong numberOfContentVersionsCreated = new AtomicLong(); - private AtomicLong numberOfContentVersionBytesWritten = new AtomicLong(); - private AtomicLong numberOfContentVersionPropertiesWritten = new AtomicLong(); - - private ReadLock readLock; - private WriteLock writeLock; - - public BulkImportStatusImpl() - { - inProgress.set(false); - - ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - this.readLock = lock.readLock(); - this.writeLock = lock.writeLock(); - } - - // General information - public String getSourceDirectory() { return(sourceDirectory); } - public String getTargetSpace() { return(targetSpace); } - public Date getStartDate() { return(copyDate(startDate)); } - public Date getEndDate() { return(copyDate(endDate)); } - - public Long getDurationInNs() - { - Long result = null; - - if (startNs != null) - { - if (endNs != null) - { - result = new Long(endNs - startNs); - } - else - { - result = new Long(System.nanoTime() - startNs); - } - } - - return(result); - } - - public Long getDuration() - { - long duration = 0; - Long durationNS = getDurationInNs(); - - if(durationNS != null) - { - duration = durationNS / (1000 * 1000 * 1000); - if(duration == 0) - { - return null; - } - } - - return new Long(duration); - } - - public Throwable getLastException() - { - readLock.lock(); - try - { - return(lastException); - } - finally - { - readLock.unlock(); - } - } - - public String getLastExceptionAsString() - { - String result = null; - - readLock.lock(); - try - { - if (lastException != null) - { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw, true); - - lastException.printStackTrace(pw); - - pw.flush(); - sw.flush(); - - result = sw.toString(); - } - - return(result); - } - finally - { - readLock.unlock(); - } - } - - public boolean inProgress() - { - return(inProgress.get()); - } - - public long getNumberOfBatchesCompleted() - { - return(numberOfBatchesCompleted.get()); - } - - public void incrementNumberOfBatchesCompleted() - { - numberOfBatchesCompleted.incrementAndGet(); - } - - public void startImport(final String sourceDirectory, final String targetSpace) - { - if (!inProgress.compareAndSet(false, true)) - { - throw new AlfrescoRuntimeException("Import already in progress."); - } - - // General information - this.sourceDirectory = sourceDirectory; - this.targetSpace = targetSpace; - this.startDate = new Date(); - this.endDate = null; - this.lastException = null; - this.numberOfBatchesCompleted.set(0); - - // Read-side information - this.numberOfFoldersScanned.set(1); // We set this to one to count the initial starting directory (which doesn't otherwise get counted) - this.numberOfFilesScanned.set(0); - this.numberOfUnreadableEntries.set(0); - - this.numberOfContentFilesRead.set(0); - this.numberOfContentBytesRead.set(0); - - this.numberOfMetadataFilesRead.set(0); - this.numberOfMetadataBytesRead.set(0); - - this.numberOfContentVersionFilesRead.set(0); - this.numberOfContentVersionBytesRead.set(0); - - this.numberOfMetadataVersionFilesRead.set(0); - this.numberOfMetadataVersionBytesRead.set(0); - - // Write-side information - this.numberOfSpaceNodesCreated.set(0); - this.numberOfSpaceNodesReplaced.set(0); - this.numberOfSpaceNodesSkipped.set(0); - this.numberOfSpacePropertiesWritten.set(0); - - this.numberOfContentNodesCreated.set(0); - this.numberOfContentNodesReplaced.set(0); - this.numberOfContentNodesSkipped.set(0); - this.numberOfContentBytesWritten.set(0); - this.numberOfContentPropertiesWritten.set(0); - - this.numberOfContentVersionsCreated.set(0); - this.numberOfContentVersionBytesWritten.set(0); - this.numberOfContentVersionPropertiesWritten.set(0); - - this.startNs = System.nanoTime(); - this.endNs = null; - } - - public void stopImport() - { - if (!inProgress.compareAndSet(true, false)) - { - throw new RuntimeException("Import not in progress."); - } - - endNs = System.nanoTime(); - endDate = new Date(); - } - - public void stopImport(final Throwable lastException) - { - stopImport(); - - writeLock.lock(); - try - { - this.lastException = lastException; - } - finally - { - writeLock.unlock(); - } - } - - public synchronized void reportException(final Throwable exception) - { - stopImport(); - this.lastException = exception; - } - - // Read-side information - public long getNumberOfFoldersScanned() { return(numberOfFoldersScanned.longValue()); } - public long getNumberOfFilesScanned() { return(numberOfFilesScanned.longValue()); } - public long getNumberOfUnreadableEntries() { return(numberOfUnreadableEntries.longValue()); } - - public long getNumberOfContentFilesRead() { return(numberOfContentFilesRead.longValue()); } - public long getNumberOfContentBytesRead() { return(numberOfContentBytesRead.longValue()); } - - public long getNumberOfMetadataFilesRead() { return(numberOfMetadataFilesRead.longValue()); } - public long getNumberOfMetadataBytesRead() { return(numberOfMetadataBytesRead.longValue()); } - - public long getNumberOfContentVersionFilesRead() { return(numberOfContentVersionFilesRead.longValue()); } - public long getNumberOfContentVersionBytesRead() { return(numberOfContentVersionBytesRead.longValue()); } - - public long getNumberOfMetadataVersionFilesRead() { return(numberOfMetadataVersionFilesRead.longValue()); } - public long getNumberOfMetadataVersionBytesRead() { return(numberOfMetadataVersionBytesRead.longValue()); } - - public void incrementImportableItemsRead(final ImportableItem importableItem, final boolean isDirectory) - { - if (importableItem.getHeadRevision().contentFileExists()) - { - if (!isDirectory) - { - numberOfContentFilesRead.incrementAndGet(); - numberOfContentBytesRead.addAndGet(importableItem.getHeadRevision().getContentFileSize()); - } - } - - if (importableItem.getHeadRevision().metadataFileExists()) - { - numberOfMetadataFilesRead.incrementAndGet(); - numberOfMetadataBytesRead.addAndGet(importableItem.getHeadRevision().getMetadataFileSize()); - } - - if (!isDirectory && importableItem.hasVersionEntries()) - { - for (final ImportableItem.ContentAndMetadata versionEntry : importableItem.getVersionEntries()) - { - if (versionEntry.contentFileExists()) - { - numberOfContentVersionFilesRead.incrementAndGet(); - numberOfContentVersionBytesRead.addAndGet(versionEntry.getContentFileSize()); - } - - if (versionEntry.metadataFileExists()) - { - numberOfMetadataVersionFilesRead.incrementAndGet(); - numberOfMetadataVersionBytesRead.addAndGet(versionEntry.getMetadataFileSize()); - } - } - } - } - - public void incrementNumberOfFilesScanned() - { - numberOfFilesScanned.incrementAndGet(); - } - - public void incrementNumberOfFoldersScanned() - { - numberOfFoldersScanned.incrementAndGet(); - } - - public void incrementNumberOfUnreadableEntries() - { - numberOfUnreadableEntries.incrementAndGet(); - } - - public void incrementImportableItemsSkipped(final ImportableItem importableItem, final boolean isDirectory) - { - if (importableItem.getHeadRevision().contentFileExists()) - { - long ignored = isDirectory ? numberOfSpaceNodesSkipped.incrementAndGet() : numberOfContentNodesSkipped.incrementAndGet(); - } - - // We don't track the number of properties or version entries skipped - } - - - - // Write-side information - public long getNumberOfSpaceNodesCreated() { return(numberOfSpaceNodesCreated.longValue()); } - public long getNumberOfSpaceNodesReplaced() { return(numberOfSpaceNodesReplaced.longValue()); } - public long getNumberOfSpaceNodesSkipped() { return(numberOfSpaceNodesSkipped.longValue()); } - public long getNumberOfSpacePropertiesWritten() { return(numberOfSpacePropertiesWritten.longValue()); } - - public long getNumberOfContentNodesCreated() { return(numberOfContentNodesCreated.longValue()); } - public long getNumberOfContentNodesReplaced() { return(numberOfContentNodesReplaced.longValue()); } - public long getNumberOfContentNodesSkipped() { return(numberOfContentNodesSkipped.longValue()); } - public long getNumberOfContentBytesWritten() { return(numberOfContentBytesWritten.longValue()); } - public long getNumberOfContentPropertiesWritten() { return(numberOfContentPropertiesWritten.longValue()); } - - public long getNumberOfContentVersionsCreated() { return(numberOfContentVersionsCreated.longValue()); } - public long getNumberOfContentVersionBytesWritten() { return(numberOfContentVersionBytesWritten.longValue()); } - public long getNumberOfContentVersionPropertiesWritten() { return(numberOfContentVersionPropertiesWritten.longValue()); } - - public void incrementContentBytesWritten(final ImportableItem importableItem, final boolean isSpace, - final NodeState nodeState) - { - if (importableItem.getHeadRevision().contentFileExists()) - { - switch (nodeState) - { - case CREATED: - numberOfContentBytesWritten.addAndGet(importableItem.getHeadRevision().getContentFileSize()); - break; - - case REPLACED: - numberOfContentBytesWritten.addAndGet(importableItem.getHeadRevision().getContentFileSize()); - break; - } - } - } - - public void incrementNodesWritten(final ImportableItem importableItem, - final boolean isSpace, - final NodeState nodeState, - final long numProperties, - final long numVersionProperties) - { - long ignored; - - if (importableItem.getHeadRevision().contentFileExists()) - { - switch (nodeState) - { - case SKIPPED: - ignored = isSpace ? numberOfSpaceNodesSkipped.incrementAndGet() : numberOfContentNodesSkipped.incrementAndGet(); - break; - - case CREATED: - ignored = isSpace ? numberOfSpaceNodesCreated.incrementAndGet() : numberOfContentNodesCreated.incrementAndGet(); - break; - - case REPLACED: - ignored = isSpace ? numberOfSpaceNodesReplaced.incrementAndGet() : numberOfContentNodesReplaced.incrementAndGet(); - break; - } - } - - switch (nodeState) - { - case SKIPPED: - // We don't track the number of properties skipped - break; - - case CREATED: - case REPLACED: - ignored = isSpace ? numberOfSpacePropertiesWritten.addAndGet(numProperties) : numberOfContentPropertiesWritten.addAndGet(numProperties); - break; - } - - if (!isSpace && importableItem.hasVersionEntries()) - { - numberOfContentVersionPropertiesWritten.addAndGet(numVersionProperties); - - for (final ImportableItem.ContentAndMetadata versionEntry : importableItem.getVersionEntries()) - { - if (versionEntry.contentFileExists()) - { - switch (nodeState) - { - case SKIPPED: - // We only track the number of items skipped on the read side - break; - - case CREATED: - case REPLACED: - numberOfContentVersionsCreated.incrementAndGet(); - numberOfContentVersionBytesWritten.addAndGet(versionEntry.getContentFileSize()); - break; - } - } - } - } - } - - public Long getFilesReadPerSecond() - { - Long duration = getDuration(); - if(duration != null) - { - long totalFilesRead = numberOfContentFilesRead.longValue() + numberOfMetadataFilesRead.longValue() + - numberOfContentVersionFilesRead.longValue() + numberOfMetadataVersionFilesRead.longValue(); - long filesReadPerSecond = totalFilesRead / duration; - return filesReadPerSecond; - } - else - { - return null; - } - } - - public Long getBytesReadPerSecond() - { - Long duration = getDuration(); - if(duration != null) - { - long totalDataRead = numberOfContentBytesRead.longValue() + numberOfMetadataBytesRead.longValue() + - numberOfContentVersionBytesRead.longValue() + numberOfMetadataVersionBytesRead.longValue(); - long bytesReadPerSecond = totalDataRead / duration; - return bytesReadPerSecond; - } - else - { - return null; - } - } - - public Long getEntriesScannedPerSecond() - { - Long duration = getDuration(); - if(duration != null) - { - long entriesScannedPerSecond = (numberOfFilesScanned.longValue() + numberOfFoldersScanned.longValue()) / duration; - return entriesScannedPerSecond; - } - else - { - return null; - } - } - - public Long getBytesWrittenPerSecond() - { - Long duration = getDuration(); - if(duration != null) - { - long totalDataWritten = numberOfContentBytesWritten.longValue() + numberOfContentVersionBytesWritten.longValue(); - long bytesWrittenPerSecond = totalDataWritten / duration; - return bytesWrittenPerSecond; - } - else - { - return null; - } - } - - public Long getNodesCreatedPerSecond() - { - Long duration = getDuration(); - if(duration != null) - { - // Note: we count versions as a node - long totalNodesWritten = numberOfSpaceNodesCreated.longValue() + numberOfSpaceNodesReplaced.longValue() + - numberOfContentNodesCreated.longValue() + numberOfContentNodesReplaced.longValue() + numberOfContentVersionsCreated.longValue(); - long nodesCreatedPerSecond = totalNodesWritten / duration; - return nodesCreatedPerSecond; - } - else - { - return null; - } - } - - // Private helper methods - private final Date copyDate(final Date date) - { - // Defensively copy the date. - Date result = null; - - if (date != null) - { - result = new Date(date.getTime()); - } - - return(result); - } - - public int getNumThreads() - { - return numThreads; - } - - public int getBatchSize() - { - return batchSize; - } - - public void setNumThreads(int numThreads) - { - this.numThreads = numThreads; - } - - public void setBatchSize(int batchSize) - { - this.batchSize = batchSize; - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append("Bulk Import Status\n"); - sb.append("Source directory: "); - sb.append(getSourceDirectory()); - sb.append("\nTarget space: "); - sb.append(getTargetSpace()); - sb.append("\nStart date : "); - sb.append(getStartDate()); - sb.append("\nEnd date : "); - sb.append(getEndDate()); - sb.append("\nNum threads : "); - sb.append(getNumThreads()); - sb.append("\nBatch size : "); - sb.append(getBatchSize()); - - if(inProgress()) - { - sb.append("\n\nIn progress"); - - } - else - { - sb.append("\n\nNot in progress"); - } - - sb.append("\nBytes written/sec : "); - sb.append(getBytesWrittenPerSecond()); - sb.append("\nBytes read/sec : "); - sb.append(getBytesReadPerSecond()); - sb.append("\nEntries scanned/sec : "); - sb.append(getEntriesScannedPerSecond()); - sb.append("\nFiles read/sec : "); - sb.append(getFilesReadPerSecond()); - sb.append("\nNodes created/sec : "); - sb.append(getNodesCreatedPerSecond()); - sb.append("\nNumber of files scanned : "); - sb.append(getNumberOfFilesScanned()); - sb.append("\nNumber of folders scanned : "); - sb.append(getNumberOfFoldersScanned()); - sb.append("\nNumber of content files read : "); - sb.append(getNumberOfContentFilesRead()); - sb.append("\nNumber of content version files read : "); - sb.append(getNumberOfContentVersionFilesRead()); - sb.append("\nNumber of metadata files read : "); - sb.append(getNumberOfMetadataFilesRead()); - sb.append("\nNumber of metadata version files read : "); - sb.append(getNumberOfMetadataVersionFilesRead()); - sb.append("\nNumber of unreadable entries : "); - sb.append(getNumberOfUnreadableEntries()); - - sb.append("\nNumber of content nodes created : "); - sb.append(getNumberOfContentNodesCreated()); - sb.append("\nNumber of content nodes replaced : "); - sb.append(getNumberOfContentNodesReplaced()); - sb.append("\nNumber of content nodes skipped : "); - sb.append(getNumberOfContentNodesSkipped()); - sb.append("\nNumber of content properties written : "); - sb.append(getNumberOfContentPropertiesWritten()); - sb.append("\nNumber of content version properties written : "); - sb.append(getNumberOfContentVersionPropertiesWritten()); - sb.append("\nNumber of content versions created : "); - sb.append(getNumberOfContentVersionsCreated()); - sb.append("\nNumber of space nodes created : "); - sb.append(getNumberOfSpaceNodesCreated()); - sb.append("\nNumber of space nodes replaced : "); - sb.append(getNumberOfSpaceNodesReplaced()); - sb.append("\nNumber of space nodes skipped : "); - sb.append(getNumberOfSpaceNodesSkipped()); - sb.append("\nNumber of space properties written : "); - sb.append(getNumberOfSpacePropertiesWritten()); - - sb.append("\nNumber of bytes read : "); - sb.append(getNumberOfContentBytesRead()); - sb.append("\nNumber of metadata bytes read: "); - sb.append(getNumberOfMetadataBytesRead()); - sb.append("\nNumber of content version bytes read : "); - sb.append(getNumberOfContentVersionBytesRead()); - sb.append("\nNumber of metadata bytes read : "); - sb.append(getNumberOfMetadataBytesRead()); - sb.append("\nNumber of metadata version bytes read : "); - sb.append(getNumberOfMetadataVersionBytesRead()); - - sb.append("\nNumber of batches completed : "); - sb.append(getNumberOfBatchesCompleted()); - - sb.append("\nNumber of bytes written : "); - sb.append(getNumberOfContentBytesWritten()); - sb.append("\nNumber of content version bytes written : "); - sb.append(getNumberOfContentVersionBytesWritten()); - - return sb.toString(); - } -} +package org.alfresco.repo.bulkimport.impl; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.bulkimport.BulkImportStatus; +import org.alfresco.repo.bulkimport.ImportableItem; + +/** + * Thread-safe implementation of Bulk Import Status. + * + * @since 4.0 + * + * @see org.alfresco.repo.bulkimport.BulkImportStatus + */ +public class BulkImportStatusImpl implements BulkImportStatus +{ + public enum NodeState + { + SKIPPED, + CREATED, + REPLACED + }; + + // General information + private int numThreads; + private int batchSize; + private AtomicBoolean inProgress = new AtomicBoolean(); + private String sourceDirectory = null; + private String targetSpace = null; + private Date startDate = null; + private Date endDate = null; + private Long startNs = null; + private Long endNs = null; + private Throwable lastException = null; + private AtomicLong numberOfBatchesCompleted = new AtomicLong(); + + // Read-side information + private AtomicLong numberOfFoldersScanned = new AtomicLong(); + private AtomicLong numberOfFilesScanned = new AtomicLong(); + private AtomicLong numberOfUnreadableEntries = new AtomicLong(); + + private AtomicLong numberOfContentFilesRead = new AtomicLong(); + private AtomicLong numberOfContentBytesRead = new AtomicLong(); + + private AtomicLong numberOfMetadataFilesRead = new AtomicLong(); + private AtomicLong numberOfMetadataBytesRead = new AtomicLong(); + + private AtomicLong numberOfContentVersionFilesRead = new AtomicLong(); + private AtomicLong numberOfContentVersionBytesRead = new AtomicLong(); + + private AtomicLong numberOfMetadataVersionFilesRead = new AtomicLong(); + private AtomicLong numberOfMetadataVersionBytesRead = new AtomicLong(); + + // Write-side information + private AtomicLong numberOfSpaceNodesCreated = new AtomicLong(); + private AtomicLong numberOfSpaceNodesReplaced = new AtomicLong(); + private AtomicLong numberOfSpaceNodesSkipped = new AtomicLong(); + private AtomicLong numberOfSpacePropertiesWritten = new AtomicLong(); + + private AtomicLong numberOfContentNodesCreated = new AtomicLong(); + private AtomicLong numberOfContentNodesReplaced = new AtomicLong(); + private AtomicLong numberOfContentNodesSkipped = new AtomicLong(); + private AtomicLong numberOfContentBytesWritten = new AtomicLong(); + private AtomicLong numberOfContentPropertiesWritten = new AtomicLong(); + + private AtomicLong numberOfContentVersionsCreated = new AtomicLong(); + private AtomicLong numberOfContentVersionBytesWritten = new AtomicLong(); + private AtomicLong numberOfContentVersionPropertiesWritten = new AtomicLong(); + + private ReadLock readLock; + private WriteLock writeLock; + + public BulkImportStatusImpl() + { + inProgress.set(false); + + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + this.readLock = lock.readLock(); + this.writeLock = lock.writeLock(); + } + + // General information + public String getSourceDirectory() { return(sourceDirectory); } + public String getTargetSpace() { return(targetSpace); } + public Date getStartDate() { return(copyDate(startDate)); } + public Date getEndDate() { return(copyDate(endDate)); } + + public Long getDurationInNs() + { + Long result = null; + + if (startNs != null) + { + if (endNs != null) + { + result = new Long(endNs - startNs); + } + else + { + result = new Long(System.nanoTime() - startNs); + } + } + + return(result); + } + + public Long getDuration() + { + long duration = 0; + Long durationNS = getDurationInNs(); + + if(durationNS != null) + { + duration = durationNS / (1000 * 1000 * 1000); + if(duration == 0) + { + return null; + } + } + + return new Long(duration); + } + + public Throwable getLastException() + { + readLock.lock(); + try + { + return(lastException); + } + finally + { + readLock.unlock(); + } + } + + public String getLastExceptionAsString() + { + String result = null; + + readLock.lock(); + try + { + if (lastException != null) + { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw, true); + + lastException.printStackTrace(pw); + + pw.flush(); + sw.flush(); + + result = sw.toString(); + } + + return(result); + } + finally + { + readLock.unlock(); + } + } + + public boolean inProgress() + { + return(inProgress.get()); + } + + public long getNumberOfBatchesCompleted() + { + return(numberOfBatchesCompleted.get()); + } + + public void incrementNumberOfBatchesCompleted() + { + numberOfBatchesCompleted.incrementAndGet(); + } + + public void startImport(final String sourceDirectory, final String targetSpace) + { + if (!inProgress.compareAndSet(false, true)) + { + throw new AlfrescoRuntimeException("Import already in progress."); + } + + // General information + this.sourceDirectory = sourceDirectory; + this.targetSpace = targetSpace; + this.startDate = new Date(); + this.endDate = null; + this.lastException = null; + this.numberOfBatchesCompleted.set(0); + + // Read-side information + this.numberOfFoldersScanned.set(1); // We set this to one to count the initial starting directory (which doesn't otherwise get counted) + this.numberOfFilesScanned.set(0); + this.numberOfUnreadableEntries.set(0); + + this.numberOfContentFilesRead.set(0); + this.numberOfContentBytesRead.set(0); + + this.numberOfMetadataFilesRead.set(0); + this.numberOfMetadataBytesRead.set(0); + + this.numberOfContentVersionFilesRead.set(0); + this.numberOfContentVersionBytesRead.set(0); + + this.numberOfMetadataVersionFilesRead.set(0); + this.numberOfMetadataVersionBytesRead.set(0); + + // Write-side information + this.numberOfSpaceNodesCreated.set(0); + this.numberOfSpaceNodesReplaced.set(0); + this.numberOfSpaceNodesSkipped.set(0); + this.numberOfSpacePropertiesWritten.set(0); + + this.numberOfContentNodesCreated.set(0); + this.numberOfContentNodesReplaced.set(0); + this.numberOfContentNodesSkipped.set(0); + this.numberOfContentBytesWritten.set(0); + this.numberOfContentPropertiesWritten.set(0); + + this.numberOfContentVersionsCreated.set(0); + this.numberOfContentVersionBytesWritten.set(0); + this.numberOfContentVersionPropertiesWritten.set(0); + + this.startNs = System.nanoTime(); + this.endNs = null; + } + + public void stopImport() + { + if (!inProgress.compareAndSet(true, false)) + { + throw new RuntimeException("Import not in progress."); + } + + endNs = System.nanoTime(); + endDate = new Date(); + } + + public void stopImport(final Throwable lastException) + { + stopImport(); + + writeLock.lock(); + try + { + this.lastException = lastException; + } + finally + { + writeLock.unlock(); + } + } + + public synchronized void reportException(final Throwable exception) + { + stopImport(); + this.lastException = exception; + } + + // Read-side information + public long getNumberOfFoldersScanned() { return(numberOfFoldersScanned.longValue()); } + public long getNumberOfFilesScanned() { return(numberOfFilesScanned.longValue()); } + public long getNumberOfUnreadableEntries() { return(numberOfUnreadableEntries.longValue()); } + + public long getNumberOfContentFilesRead() { return(numberOfContentFilesRead.longValue()); } + public long getNumberOfContentBytesRead() { return(numberOfContentBytesRead.longValue()); } + + public long getNumberOfMetadataFilesRead() { return(numberOfMetadataFilesRead.longValue()); } + public long getNumberOfMetadataBytesRead() { return(numberOfMetadataBytesRead.longValue()); } + + public long getNumberOfContentVersionFilesRead() { return(numberOfContentVersionFilesRead.longValue()); } + public long getNumberOfContentVersionBytesRead() { return(numberOfContentVersionBytesRead.longValue()); } + + public long getNumberOfMetadataVersionFilesRead() { return(numberOfMetadataVersionFilesRead.longValue()); } + public long getNumberOfMetadataVersionBytesRead() { return(numberOfMetadataVersionBytesRead.longValue()); } + + public void incrementImportableItemsRead(final ImportableItem importableItem, final boolean isDirectory) + { + if (importableItem.getHeadRevision().contentFileExists()) + { + if (!isDirectory) + { + numberOfContentFilesRead.incrementAndGet(); + numberOfContentBytesRead.addAndGet(importableItem.getHeadRevision().getContentFileSize()); + } + } + + if (importableItem.getHeadRevision().metadataFileExists()) + { + numberOfMetadataFilesRead.incrementAndGet(); + numberOfMetadataBytesRead.addAndGet(importableItem.getHeadRevision().getMetadataFileSize()); + } + + if (!isDirectory && importableItem.hasVersionEntries()) + { + for (final ImportableItem.ContentAndMetadata versionEntry : importableItem.getVersionEntries()) + { + if (versionEntry.contentFileExists()) + { + numberOfContentVersionFilesRead.incrementAndGet(); + numberOfContentVersionBytesRead.addAndGet(versionEntry.getContentFileSize()); + } + + if (versionEntry.metadataFileExists()) + { + numberOfMetadataVersionFilesRead.incrementAndGet(); + numberOfMetadataVersionBytesRead.addAndGet(versionEntry.getMetadataFileSize()); + } + } + } + } + + public void incrementNumberOfFilesScanned() + { + numberOfFilesScanned.incrementAndGet(); + } + + public void incrementNumberOfFoldersScanned() + { + numberOfFoldersScanned.incrementAndGet(); + } + + public void incrementNumberOfUnreadableEntries() + { + numberOfUnreadableEntries.incrementAndGet(); + } + + public void incrementImportableItemsSkipped(final ImportableItem importableItem, final boolean isDirectory) + { + if (importableItem.getHeadRevision().contentFileExists()) + { + long ignored = isDirectory ? numberOfSpaceNodesSkipped.incrementAndGet() : numberOfContentNodesSkipped.incrementAndGet(); + } + + // We don't track the number of properties or version entries skipped + } + + + + // Write-side information + public long getNumberOfSpaceNodesCreated() { return(numberOfSpaceNodesCreated.longValue()); } + public long getNumberOfSpaceNodesReplaced() { return(numberOfSpaceNodesReplaced.longValue()); } + public long getNumberOfSpaceNodesSkipped() { return(numberOfSpaceNodesSkipped.longValue()); } + public long getNumberOfSpacePropertiesWritten() { return(numberOfSpacePropertiesWritten.longValue()); } + + public long getNumberOfContentNodesCreated() { return(numberOfContentNodesCreated.longValue()); } + public long getNumberOfContentNodesReplaced() { return(numberOfContentNodesReplaced.longValue()); } + public long getNumberOfContentNodesSkipped() { return(numberOfContentNodesSkipped.longValue()); } + public long getNumberOfContentBytesWritten() { return(numberOfContentBytesWritten.longValue()); } + public long getNumberOfContentPropertiesWritten() { return(numberOfContentPropertiesWritten.longValue()); } + + public long getNumberOfContentVersionsCreated() { return(numberOfContentVersionsCreated.longValue()); } + public long getNumberOfContentVersionBytesWritten() { return(numberOfContentVersionBytesWritten.longValue()); } + public long getNumberOfContentVersionPropertiesWritten() { return(numberOfContentVersionPropertiesWritten.longValue()); } + + public void incrementContentBytesWritten(final ImportableItem importableItem, final boolean isSpace, + final NodeState nodeState) + { + if (importableItem.getHeadRevision().contentFileExists()) + { + switch (nodeState) + { + case CREATED: + numberOfContentBytesWritten.addAndGet(importableItem.getHeadRevision().getContentFileSize()); + break; + + case REPLACED: + numberOfContentBytesWritten.addAndGet(importableItem.getHeadRevision().getContentFileSize()); + break; + } + } + } + + public void incrementNodesWritten(final ImportableItem importableItem, + final boolean isSpace, + final NodeState nodeState, + final long numProperties, + final long numVersionProperties) + { + long ignored; + + if (importableItem.getHeadRevision().contentFileExists()) + { + switch (nodeState) + { + case SKIPPED: + ignored = isSpace ? numberOfSpaceNodesSkipped.incrementAndGet() : numberOfContentNodesSkipped.incrementAndGet(); + break; + + case CREATED: + ignored = isSpace ? numberOfSpaceNodesCreated.incrementAndGet() : numberOfContentNodesCreated.incrementAndGet(); + break; + + case REPLACED: + ignored = isSpace ? numberOfSpaceNodesReplaced.incrementAndGet() : numberOfContentNodesReplaced.incrementAndGet(); + break; + } + } + + switch (nodeState) + { + case SKIPPED: + // We don't track the number of properties skipped + break; + + case CREATED: + case REPLACED: + ignored = isSpace ? numberOfSpacePropertiesWritten.addAndGet(numProperties) : numberOfContentPropertiesWritten.addAndGet(numProperties); + break; + } + + if (!isSpace && importableItem.hasVersionEntries()) + { + numberOfContentVersionPropertiesWritten.addAndGet(numVersionProperties); + + for (final ImportableItem.ContentAndMetadata versionEntry : importableItem.getVersionEntries()) + { + if (versionEntry.contentFileExists()) + { + switch (nodeState) + { + case SKIPPED: + // We only track the number of items skipped on the read side + break; + + case CREATED: + case REPLACED: + numberOfContentVersionsCreated.incrementAndGet(); + numberOfContentVersionBytesWritten.addAndGet(versionEntry.getContentFileSize()); + break; + } + } + } + } + } + + public Long getFilesReadPerSecond() + { + Long duration = getDuration(); + if(duration != null) + { + long totalFilesRead = numberOfContentFilesRead.longValue() + numberOfMetadataFilesRead.longValue() + + numberOfContentVersionFilesRead.longValue() + numberOfMetadataVersionFilesRead.longValue(); + long filesReadPerSecond = totalFilesRead / duration; + return filesReadPerSecond; + } + else + { + return null; + } + } + + public Long getBytesReadPerSecond() + { + Long duration = getDuration(); + if(duration != null) + { + long totalDataRead = numberOfContentBytesRead.longValue() + numberOfMetadataBytesRead.longValue() + + numberOfContentVersionBytesRead.longValue() + numberOfMetadataVersionBytesRead.longValue(); + long bytesReadPerSecond = totalDataRead / duration; + return bytesReadPerSecond; + } + else + { + return null; + } + } + + public Long getEntriesScannedPerSecond() + { + Long duration = getDuration(); + if(duration != null) + { + long entriesScannedPerSecond = (numberOfFilesScanned.longValue() + numberOfFoldersScanned.longValue()) / duration; + return entriesScannedPerSecond; + } + else + { + return null; + } + } + + public Long getBytesWrittenPerSecond() + { + Long duration = getDuration(); + if(duration != null) + { + long totalDataWritten = numberOfContentBytesWritten.longValue() + numberOfContentVersionBytesWritten.longValue(); + long bytesWrittenPerSecond = totalDataWritten / duration; + return bytesWrittenPerSecond; + } + else + { + return null; + } + } + + public Long getNodesCreatedPerSecond() + { + Long duration = getDuration(); + if(duration != null) + { + // Note: we count versions as a node + long totalNodesWritten = numberOfSpaceNodesCreated.longValue() + numberOfSpaceNodesReplaced.longValue() + + numberOfContentNodesCreated.longValue() + numberOfContentNodesReplaced.longValue() + numberOfContentVersionsCreated.longValue(); + long nodesCreatedPerSecond = totalNodesWritten / duration; + return nodesCreatedPerSecond; + } + else + { + return null; + } + } + + // Private helper methods + private final Date copyDate(final Date date) + { + // Defensively copy the date. + Date result = null; + + if (date != null) + { + result = new Date(date.getTime()); + } + + return(result); + } + + public int getNumThreads() + { + return numThreads; + } + + public int getBatchSize() + { + return batchSize; + } + + public void setNumThreads(int numThreads) + { + this.numThreads = numThreads; + } + + public void setBatchSize(int batchSize) + { + this.batchSize = batchSize; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("Bulk Import Status\n"); + sb.append("Source directory: "); + sb.append(getSourceDirectory()); + sb.append("\nTarget space: "); + sb.append(getTargetSpace()); + sb.append("\nStart date : "); + sb.append(getStartDate()); + sb.append("\nEnd date : "); + sb.append(getEndDate()); + sb.append("\nNum threads : "); + sb.append(getNumThreads()); + sb.append("\nBatch size : "); + sb.append(getBatchSize()); + + if(inProgress()) + { + sb.append("\n\nIn progress"); + + } + else + { + sb.append("\n\nNot in progress"); + } + + sb.append("\nBytes written/sec : "); + sb.append(getBytesWrittenPerSecond()); + sb.append("\nBytes read/sec : "); + sb.append(getBytesReadPerSecond()); + sb.append("\nEntries scanned/sec : "); + sb.append(getEntriesScannedPerSecond()); + sb.append("\nFiles read/sec : "); + sb.append(getFilesReadPerSecond()); + sb.append("\nNodes created/sec : "); + sb.append(getNodesCreatedPerSecond()); + sb.append("\nNumber of files scanned : "); + sb.append(getNumberOfFilesScanned()); + sb.append("\nNumber of folders scanned : "); + sb.append(getNumberOfFoldersScanned()); + sb.append("\nNumber of content files read : "); + sb.append(getNumberOfContentFilesRead()); + sb.append("\nNumber of content version files read : "); + sb.append(getNumberOfContentVersionFilesRead()); + sb.append("\nNumber of metadata files read : "); + sb.append(getNumberOfMetadataFilesRead()); + sb.append("\nNumber of metadata version files read : "); + sb.append(getNumberOfMetadataVersionFilesRead()); + sb.append("\nNumber of unreadable entries : "); + sb.append(getNumberOfUnreadableEntries()); + + sb.append("\nNumber of content nodes created : "); + sb.append(getNumberOfContentNodesCreated()); + sb.append("\nNumber of content nodes replaced : "); + sb.append(getNumberOfContentNodesReplaced()); + sb.append("\nNumber of content nodes skipped : "); + sb.append(getNumberOfContentNodesSkipped()); + sb.append("\nNumber of content properties written : "); + sb.append(getNumberOfContentPropertiesWritten()); + sb.append("\nNumber of content version properties written : "); + sb.append(getNumberOfContentVersionPropertiesWritten()); + sb.append("\nNumber of content versions created : "); + sb.append(getNumberOfContentVersionsCreated()); + sb.append("\nNumber of space nodes created : "); + sb.append(getNumberOfSpaceNodesCreated()); + sb.append("\nNumber of space nodes replaced : "); + sb.append(getNumberOfSpaceNodesReplaced()); + sb.append("\nNumber of space nodes skipped : "); + sb.append(getNumberOfSpaceNodesSkipped()); + sb.append("\nNumber of space properties written : "); + sb.append(getNumberOfSpacePropertiesWritten()); + + sb.append("\nNumber of bytes read : "); + sb.append(getNumberOfContentBytesRead()); + sb.append("\nNumber of metadata bytes read: "); + sb.append(getNumberOfMetadataBytesRead()); + sb.append("\nNumber of content version bytes read : "); + sb.append(getNumberOfContentVersionBytesRead()); + sb.append("\nNumber of metadata bytes read : "); + sb.append(getNumberOfMetadataBytesRead()); + sb.append("\nNumber of metadata version bytes read : "); + sb.append(getNumberOfMetadataVersionBytesRead()); + + sb.append("\nNumber of batches completed : "); + sb.append(getNumberOfBatchesCompleted()); + + sb.append("\nNumber of bytes written : "); + sb.append(getNumberOfContentBytesWritten()); + sb.append("\nNumber of content version bytes written : "); + sb.append(getNumberOfContentVersionBytesWritten()); + + return sb.toString(); + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/DirectoryAnalyserImpl.java b/source/java/org/alfresco/repo/bulkimport/impl/DirectoryAnalyserImpl.java index 8c50a1796f..13fdbe5dfc 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/DirectoryAnalyserImpl.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/DirectoryAnalyserImpl.java @@ -1,493 +1,493 @@ - -package org.alfresco.repo.bulkimport.impl; - -import java.io.File; -import java.io.FileFilter; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.bulkimport.AnalysedDirectory; -import org.alfresco.repo.bulkimport.DirectoryAnalyser; -import org.alfresco.repo.bulkimport.ImportFilter; -import org.alfresco.repo.bulkimport.ImportableItem; -import org.alfresco.repo.bulkimport.ImportableItem.FileType; -import org.alfresco.repo.bulkimport.MetadataLoader; -import org.alfresco.repo.dictionary.constraint.NameChecker; -import org.alfresco.service.cmr.dictionary.Constraint; -import org.alfresco.service.cmr.dictionary.ConstraintDefinition; -import org.alfresco.service.cmr.dictionary.ConstraintException; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.ISO8601DateFormat; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.exception.PlatformRuntimeException; - -/** - * This class provides the implementation for directory analysis, the process by - * which a directory listing of files is broken up into ImportableItems. - * - * @since 4.0 - * - */ -public class DirectoryAnalyserImpl implements DirectoryAnalyser -{ - private final static Log log = LogFactory.getLog(DirectoryAnalyserImpl.class); - - private final static Pattern VERSION_SUFFIX_PATTERN = Pattern.compile(".+" + VERSION_SUFFIX_REGEX); - - private MetadataLoader metadataLoader; - private BulkImportStatusImpl importStatus; - private List importFilters; - private NameChecker nameChecker; - private DictionaryService dictionaryService; - - - public DirectoryAnalyserImpl(MetadataLoader metadataLoader, BulkImportStatusImpl importStatus, List importFilters, - NameChecker nameChecker) - { - this.metadataLoader = metadataLoader; - this.importStatus = importStatus; - this.importFilters = importFilters; - this.nameChecker = nameChecker; - } - - public DirectoryAnalyserImpl() - { - } - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public void setNameChecker(NameChecker nameChecker) - { - this.nameChecker = nameChecker; - } - - public void setMetadataLoader(MetadataLoader metadataLoader) - { - this.metadataLoader = metadataLoader; - } - - public void setImportStatus(BulkImportStatusImpl status) - { - importStatus = status; - } - - public final void setImportFilters(List importFilters) - { - if(importFilters != null) - { - this.importFilters = importFilters; - } - else - { - this.importFilters = new ArrayList(); - } - } - - protected boolean shouldFilter(ImportableItem importableItem) - { - boolean filterImportableItem = false; - - if(importFilters != null && importFilters.size() > 0) - { - for (ImportFilter filter : importFilters) - { - if (filter.shouldFilter(importableItem)) - { - filterImportableItem = true; - break; - } - } - } - - return filterImportableItem; - } - - /** - * @see org.alfresco.repo.bulkimport.DirectoryAnalyser#analyseDirectory(ImportableItem, FileFilter) - */ - public AnalysedDirectory analyseDirectory(ImportableItem directory, FileFilter filter) - { - File directoryFile = directory.getHeadRevision().getContentFile(); - AnalysedDirectory result = null; - - if(filter == null) - { - result = new AnalysedDirectory(directoryFile.listFiles()); - } - else - { - result = new AnalysedDirectory(directoryFile.listFiles(filter)); - } - - if (log.isDebugEnabled()) - { - log.debug("Analysing directory " + FileUtils.getFileName(directoryFile) + "..."); - } - - // Build up the list of ImportableItems from the directory listing - for (File file : result.getOriginalListing()) - { - // MNT-9763 bulkimport fails when there is a very large LastModified timestamp. - String isoDate = null; - try - { - isoDate = ISO8601DateFormat.format(new Date(file.lastModified())); - ISO8601DateFormat.parse(isoDate); - } - catch (PlatformRuntimeException e) - { - log.warn("Failed to convert date " + isoDate + " to string for " + file.getName(), e); - importStatus.incrementNumberOfUnreadableEntries(); - continue; - } - - if (log.isTraceEnabled()) - { - log.trace("Scanning file " + FileUtils.getFileName(file) + "..."); - } - - if (file.canRead()) - { - try - { - nameChecker.evaluate(file.getName()); - } - catch (ConstraintException e) - { - if (log.isWarnEnabled()) - { - log.warn("Skipping file with invalid name: '" + FileUtils.getFileName(file) + "'."); - } - // mark file with invalid name as unreadable - importStatus.incrementNumberOfUnreadableEntries(); - - continue; - } - - if (isVersionFile(file)) - { - addVersionFile(directory, result, file); - importStatus.incrementNumberOfFilesScanned(); - } - else if (isMetadataFile(file)) - { - addMetadataFile(directory, result, file); - importStatus.incrementNumberOfFilesScanned(); - } - else - { - boolean isDirectory = addParentFile(directory, result, file); - - if (isDirectory) - { - importStatus.incrementNumberOfFoldersScanned(); - } - else - { - importStatus.incrementNumberOfFilesScanned(); - } - } - } - else - { - if (log.isWarnEnabled()) - { - log.warn("Skipping unreadable file '" + FileUtils.getFileName(file) + "'."); - } - - importStatus.incrementNumberOfUnreadableEntries(); - } - } - - // Finally, remove any items from the list that aren't valid (don't have either a - // contentFile or a metadataFile) - Iterator iter = result.getImportableItems().iterator(); - - while (iter.hasNext()) - { - ImportableItem importableItem = iter.next(); - - if (!importableItem.isValid() || !isMetadataValid(importableItem)) - { - iter.remove(); - } - } - - iter = result.getImportableDirectories().iterator(); - while (iter.hasNext()) - { - ImportableItem importableItem = iter.next(); - - if (!importableItem.isValid()) - { - iter.remove(); - } - } - - if (log.isDebugEnabled()) - { - log.debug("Finished analysing directory " + FileUtils.getFileName(directoryFile) + "."); - } - - return result; - } - - private boolean isMetadataValid(ImportableItem importableItem) - { - if (!importableItem.getHeadRevision().metadataFileExists()) - { - return true; - } - - if (metadataLoader != null) - { - MetadataLoader.Metadata result = new MetadataLoader.Metadata(); - metadataLoader.loadMetadata(importableItem.getHeadRevision(), result); - - Map metadataProperties = result.getProperties(); - for (QName propertyName : metadataProperties.keySet()) - { - PropertyDefinition propDef = dictionaryService.getProperty(propertyName); - if (propDef != null) - { - for (ConstraintDefinition constraintDef : propDef.getConstraints()) - { - Constraint constraint = constraintDef.getConstraint(); - if (constraint != null) - { - try - { - constraint.evaluate(metadataProperties.get(propertyName)); - } - catch (ConstraintException e) - { - if (log.isWarnEnabled()) - { - log.warn("Skipping file '" + FileUtils.getFileName(importableItem.getHeadRevision().getContentFile()) - +"' with invalid metadata: '" + FileUtils.getFileName(importableItem.getHeadRevision().getMetadataFile()) + "'.", e); - } - return false; - } - } - } - } - } - } - - return true; - } - - private boolean isVersionFile(File file) - { - Matcher matcher = VERSION_SUFFIX_PATTERN.matcher(file.getName()); - - return matcher.matches(); - } - - - private boolean isMetadataFile(File file) - { - boolean result = false; - - if (metadataLoader != null) - { - result = file.getName().endsWith(MetadataLoader.METADATA_SUFFIX + metadataLoader.getMetadataFileExtension()); - } - - return(result); - } - - private void addVersionFile(ImportableItem parent, AnalysedDirectory analysedDirectory, File versionFile) - { - File parentContentFile = getParentOfVersionFile(versionFile); - boolean isContentVersion = false; - - if (isMetadataFile(parentContentFile)) - { - parentContentFile = getParentOfMetadatafile(parentContentFile); - isContentVersion = false; - } - else - { - isContentVersion = true; - } - - ImportableItem importableItem = findOrCreateImportableItem(parent, analysedDirectory, parentContentFile); - int version = getVersionNumber(versionFile); - ImportableItem.VersionedContentAndMetadata versionEntry = findOrCreateVersionEntry(importableItem, version); - - if (isContentVersion) - { - versionEntry.setContentFile(versionFile); - } - else - { - versionEntry.setMetadataFile(versionFile); - } - } - - - private void addMetadataFile(ImportableItem parent, AnalysedDirectory analysedDirectory, File metadataFile) - { - File parentContentfile = getParentOfMetadatafile(metadataFile); - - ImportableItem importableItem = findOrCreateImportableItem(parent, analysedDirectory, parentContentfile); - - importableItem.getHeadRevision().setMetadataFile(metadataFile); - } - - - private boolean addParentFile(ImportableItem parent, AnalysedDirectory analysedDirectory, File contentFile) - { - ImportableItem importableItem = findOrCreateImportableItem(parent, analysedDirectory, contentFile); - - importableItem.getHeadRevision().setContentFile(contentFile); - - return(importableItem.getHeadRevision().getContentFileType() == FileType.DIRECTORY); - } - - private ImportableItem findOrCreateImportableItem(ImportableItem parent, AnalysedDirectory analysedDirectory, File contentFile) - { - ImportableItem result = findImportableItem(analysedDirectory, contentFile); - - // We didn't find it, so create it - if (result == null) - { - result = new ImportableItem(); - result.setParent(parent); - result.getHeadRevision().setContentFile(contentFile); - if(!shouldFilter(result)) - { - analysedDirectory.addImportableItem(result); - } - } - - return(result); - } - - - private ImportableItem findImportableItem(AnalysedDirectory analysedDirectory, File contentFile) - { - ImportableItem result = null; - - if (contentFile == null) - { - throw new IllegalStateException("Cannot call findOrCreateImportableItem with null key"); - } - - result = analysedDirectory.findImportableItem(contentFile); - - return(result); - } - - - private ImportableItem.VersionedContentAndMetadata findOrCreateVersionEntry(ImportableItem importableItem, int version) - { - ImportableItem.VersionedContentAndMetadata result = findVersionEntry(importableItem, version); - - if (result == null) - { - result = importableItem.new VersionedContentAndMetadata(version); - - importableItem.addVersionEntry(result); - } - - return (result); - } - - - private ImportableItem.VersionedContentAndMetadata findVersionEntry(ImportableItem importableItem, int version) - { - ImportableItem.VersionedContentAndMetadata result = null; - - if (importableItem.hasVersionEntries()) - { - for (ImportableItem.VersionedContentAndMetadata versionEntry : importableItem.getVersionEntries()) - { - if (version == versionEntry.getVersion()) - { - result = versionEntry; - break; - } - } - } - - return(result); - } - - - private int getVersionNumber(File versionFile) - { - int result = -1; - - if (!isVersionFile(versionFile)) - { - throw new IllegalStateException(FileUtils.getFileName(versionFile) + " is not a version file."); - } - - Matcher matcher = VERSION_SUFFIX_PATTERN.matcher(versionFile.getName()); - String versionStr = null; - - if (matcher.matches()) - { - versionStr = matcher.group(1); - } - else - { - throw new IllegalStateException(""); // ####TODO!!!! - } - - result = Integer.parseInt(versionStr); - - return(result); - } - - - private File getParentOfVersionFile(File versionFile) - { - File result = null; - - if (!isVersionFile(versionFile)) - { - throw new IllegalStateException(FileUtils.getFileName(versionFile) + " is not a version file."); - } - - String parentFilename = versionFile.getName().replaceFirst(VERSION_SUFFIX_REGEX, ""); - - result = new File(versionFile.getParent(), parentFilename); - - return(result); - } - - - private File getParentOfMetadatafile(File metadataFile) - { - File result = null; - - if (!isMetadataFile(metadataFile)) - { - throw new IllegalStateException(FileUtils.getFileName(metadataFile) + " is not a metadata file."); - } - - String name = metadataFile.getName(); - String contentName = name.substring(0, name.length() - (MetadataLoader.METADATA_SUFFIX + metadataLoader.getMetadataFileExtension()).length()); - - result = new File(metadataFile.getParent(), contentName); - - return(result); - } - -} + +package org.alfresco.repo.bulkimport.impl; + +import java.io.File; +import java.io.FileFilter; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.bulkimport.AnalysedDirectory; +import org.alfresco.repo.bulkimport.DirectoryAnalyser; +import org.alfresco.repo.bulkimport.ImportFilter; +import org.alfresco.repo.bulkimport.ImportableItem; +import org.alfresco.repo.bulkimport.ImportableItem.FileType; +import org.alfresco.repo.bulkimport.MetadataLoader; +import org.alfresco.repo.dictionary.constraint.NameChecker; +import org.alfresco.service.cmr.dictionary.Constraint; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintException; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO8601DateFormat; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.exception.PlatformRuntimeException; + +/** + * This class provides the implementation for directory analysis, the process by + * which a directory listing of files is broken up into ImportableItems. + * + * @since 4.0 + * + */ +public class DirectoryAnalyserImpl implements DirectoryAnalyser +{ + private final static Log log = LogFactory.getLog(DirectoryAnalyserImpl.class); + + private final static Pattern VERSION_SUFFIX_PATTERN = Pattern.compile(".+" + VERSION_SUFFIX_REGEX); + + private MetadataLoader metadataLoader; + private BulkImportStatusImpl importStatus; + private List importFilters; + private NameChecker nameChecker; + private DictionaryService dictionaryService; + + + public DirectoryAnalyserImpl(MetadataLoader metadataLoader, BulkImportStatusImpl importStatus, List importFilters, + NameChecker nameChecker) + { + this.metadataLoader = metadataLoader; + this.importStatus = importStatus; + this.importFilters = importFilters; + this.nameChecker = nameChecker; + } + + public DirectoryAnalyserImpl() + { + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setNameChecker(NameChecker nameChecker) + { + this.nameChecker = nameChecker; + } + + public void setMetadataLoader(MetadataLoader metadataLoader) + { + this.metadataLoader = metadataLoader; + } + + public void setImportStatus(BulkImportStatusImpl status) + { + importStatus = status; + } + + public final void setImportFilters(List importFilters) + { + if(importFilters != null) + { + this.importFilters = importFilters; + } + else + { + this.importFilters = new ArrayList(); + } + } + + protected boolean shouldFilter(ImportableItem importableItem) + { + boolean filterImportableItem = false; + + if(importFilters != null && importFilters.size() > 0) + { + for (ImportFilter filter : importFilters) + { + if (filter.shouldFilter(importableItem)) + { + filterImportableItem = true; + break; + } + } + } + + return filterImportableItem; + } + + /** + * @see org.alfresco.repo.bulkimport.DirectoryAnalyser#analyseDirectory(ImportableItem, FileFilter) + */ + public AnalysedDirectory analyseDirectory(ImportableItem directory, FileFilter filter) + { + File directoryFile = directory.getHeadRevision().getContentFile(); + AnalysedDirectory result = null; + + if(filter == null) + { + result = new AnalysedDirectory(directoryFile.listFiles()); + } + else + { + result = new AnalysedDirectory(directoryFile.listFiles(filter)); + } + + if (log.isDebugEnabled()) + { + log.debug("Analysing directory " + FileUtils.getFileName(directoryFile) + "..."); + } + + // Build up the list of ImportableItems from the directory listing + for (File file : result.getOriginalListing()) + { + // MNT-9763 bulkimport fails when there is a very large LastModified timestamp. + String isoDate = null; + try + { + isoDate = ISO8601DateFormat.format(new Date(file.lastModified())); + ISO8601DateFormat.parse(isoDate); + } + catch (PlatformRuntimeException e) + { + log.warn("Failed to convert date " + isoDate + " to string for " + file.getName(), e); + importStatus.incrementNumberOfUnreadableEntries(); + continue; + } + + if (log.isTraceEnabled()) + { + log.trace("Scanning file " + FileUtils.getFileName(file) + "..."); + } + + if (file.canRead()) + { + try + { + nameChecker.evaluate(file.getName()); + } + catch (ConstraintException e) + { + if (log.isWarnEnabled()) + { + log.warn("Skipping file with invalid name: '" + FileUtils.getFileName(file) + "'."); + } + // mark file with invalid name as unreadable + importStatus.incrementNumberOfUnreadableEntries(); + + continue; + } + + if (isVersionFile(file)) + { + addVersionFile(directory, result, file); + importStatus.incrementNumberOfFilesScanned(); + } + else if (isMetadataFile(file)) + { + addMetadataFile(directory, result, file); + importStatus.incrementNumberOfFilesScanned(); + } + else + { + boolean isDirectory = addParentFile(directory, result, file); + + if (isDirectory) + { + importStatus.incrementNumberOfFoldersScanned(); + } + else + { + importStatus.incrementNumberOfFilesScanned(); + } + } + } + else + { + if (log.isWarnEnabled()) + { + log.warn("Skipping unreadable file '" + FileUtils.getFileName(file) + "'."); + } + + importStatus.incrementNumberOfUnreadableEntries(); + } + } + + // Finally, remove any items from the list that aren't valid (don't have either a + // contentFile or a metadataFile) + Iterator iter = result.getImportableItems().iterator(); + + while (iter.hasNext()) + { + ImportableItem importableItem = iter.next(); + + if (!importableItem.isValid() || !isMetadataValid(importableItem)) + { + iter.remove(); + } + } + + iter = result.getImportableDirectories().iterator(); + while (iter.hasNext()) + { + ImportableItem importableItem = iter.next(); + + if (!importableItem.isValid()) + { + iter.remove(); + } + } + + if (log.isDebugEnabled()) + { + log.debug("Finished analysing directory " + FileUtils.getFileName(directoryFile) + "."); + } + + return result; + } + + private boolean isMetadataValid(ImportableItem importableItem) + { + if (!importableItem.getHeadRevision().metadataFileExists()) + { + return true; + } + + if (metadataLoader != null) + { + MetadataLoader.Metadata result = new MetadataLoader.Metadata(); + metadataLoader.loadMetadata(importableItem.getHeadRevision(), result); + + Map metadataProperties = result.getProperties(); + for (QName propertyName : metadataProperties.keySet()) + { + PropertyDefinition propDef = dictionaryService.getProperty(propertyName); + if (propDef != null) + { + for (ConstraintDefinition constraintDef : propDef.getConstraints()) + { + Constraint constraint = constraintDef.getConstraint(); + if (constraint != null) + { + try + { + constraint.evaluate(metadataProperties.get(propertyName)); + } + catch (ConstraintException e) + { + if (log.isWarnEnabled()) + { + log.warn("Skipping file '" + FileUtils.getFileName(importableItem.getHeadRevision().getContentFile()) + +"' with invalid metadata: '" + FileUtils.getFileName(importableItem.getHeadRevision().getMetadataFile()) + "'.", e); + } + return false; + } + } + } + } + } + } + + return true; + } + + private boolean isVersionFile(File file) + { + Matcher matcher = VERSION_SUFFIX_PATTERN.matcher(file.getName()); + + return matcher.matches(); + } + + + private boolean isMetadataFile(File file) + { + boolean result = false; + + if (metadataLoader != null) + { + result = file.getName().endsWith(MetadataLoader.METADATA_SUFFIX + metadataLoader.getMetadataFileExtension()); + } + + return(result); + } + + private void addVersionFile(ImportableItem parent, AnalysedDirectory analysedDirectory, File versionFile) + { + File parentContentFile = getParentOfVersionFile(versionFile); + boolean isContentVersion = false; + + if (isMetadataFile(parentContentFile)) + { + parentContentFile = getParentOfMetadatafile(parentContentFile); + isContentVersion = false; + } + else + { + isContentVersion = true; + } + + ImportableItem importableItem = findOrCreateImportableItem(parent, analysedDirectory, parentContentFile); + int version = getVersionNumber(versionFile); + ImportableItem.VersionedContentAndMetadata versionEntry = findOrCreateVersionEntry(importableItem, version); + + if (isContentVersion) + { + versionEntry.setContentFile(versionFile); + } + else + { + versionEntry.setMetadataFile(versionFile); + } + } + + + private void addMetadataFile(ImportableItem parent, AnalysedDirectory analysedDirectory, File metadataFile) + { + File parentContentfile = getParentOfMetadatafile(metadataFile); + + ImportableItem importableItem = findOrCreateImportableItem(parent, analysedDirectory, parentContentfile); + + importableItem.getHeadRevision().setMetadataFile(metadataFile); + } + + + private boolean addParentFile(ImportableItem parent, AnalysedDirectory analysedDirectory, File contentFile) + { + ImportableItem importableItem = findOrCreateImportableItem(parent, analysedDirectory, contentFile); + + importableItem.getHeadRevision().setContentFile(contentFile); + + return(importableItem.getHeadRevision().getContentFileType() == FileType.DIRECTORY); + } + + private ImportableItem findOrCreateImportableItem(ImportableItem parent, AnalysedDirectory analysedDirectory, File contentFile) + { + ImportableItem result = findImportableItem(analysedDirectory, contentFile); + + // We didn't find it, so create it + if (result == null) + { + result = new ImportableItem(); + result.setParent(parent); + result.getHeadRevision().setContentFile(contentFile); + if(!shouldFilter(result)) + { + analysedDirectory.addImportableItem(result); + } + } + + return(result); + } + + + private ImportableItem findImportableItem(AnalysedDirectory analysedDirectory, File contentFile) + { + ImportableItem result = null; + + if (contentFile == null) + { + throw new IllegalStateException("Cannot call findOrCreateImportableItem with null key"); + } + + result = analysedDirectory.findImportableItem(contentFile); + + return(result); + } + + + private ImportableItem.VersionedContentAndMetadata findOrCreateVersionEntry(ImportableItem importableItem, int version) + { + ImportableItem.VersionedContentAndMetadata result = findVersionEntry(importableItem, version); + + if (result == null) + { + result = importableItem.new VersionedContentAndMetadata(version); + + importableItem.addVersionEntry(result); + } + + return (result); + } + + + private ImportableItem.VersionedContentAndMetadata findVersionEntry(ImportableItem importableItem, int version) + { + ImportableItem.VersionedContentAndMetadata result = null; + + if (importableItem.hasVersionEntries()) + { + for (ImportableItem.VersionedContentAndMetadata versionEntry : importableItem.getVersionEntries()) + { + if (version == versionEntry.getVersion()) + { + result = versionEntry; + break; + } + } + } + + return(result); + } + + + private int getVersionNumber(File versionFile) + { + int result = -1; + + if (!isVersionFile(versionFile)) + { + throw new IllegalStateException(FileUtils.getFileName(versionFile) + " is not a version file."); + } + + Matcher matcher = VERSION_SUFFIX_PATTERN.matcher(versionFile.getName()); + String versionStr = null; + + if (matcher.matches()) + { + versionStr = matcher.group(1); + } + else + { + throw new IllegalStateException(""); // ####TODO!!!! + } + + result = Integer.parseInt(versionStr); + + return(result); + } + + + private File getParentOfVersionFile(File versionFile) + { + File result = null; + + if (!isVersionFile(versionFile)) + { + throw new IllegalStateException(FileUtils.getFileName(versionFile) + " is not a version file."); + } + + String parentFilename = versionFile.getName().replaceFirst(VERSION_SUFFIX_REGEX, ""); + + result = new File(versionFile.getParent(), parentFilename); + + return(result); + } + + + private File getParentOfMetadatafile(File metadataFile) + { + File result = null; + + if (!isMetadataFile(metadataFile)) + { + throw new IllegalStateException(FileUtils.getFileName(metadataFile) + " is not a metadata file."); + } + + String name = metadataFile.getName(); + String contentName = name.substring(0, name.length() - (MetadataLoader.METADATA_SUFFIX + metadataLoader.getMetadataFileExtension()).length()); + + result = new File(metadataFile.getParent(), contentName); + + return(result); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/FileUtils.java b/source/java/org/alfresco/repo/bulkimport/impl/FileUtils.java index ee9392d933..778358eb02 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/FileUtils.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/FileUtils.java @@ -1,32 +1,32 @@ -package org.alfresco.repo.bulkimport.impl; - -import java.io.File; -import java.io.IOException; - -/** - * - * @since 4.0 - * - * TODO move to core project - */ -public class FileUtils -{ - public static String getFileName(final File file) - { - String result = null; - - if (file != null) - { - try - { - result = file.getCanonicalPath(); - } - catch (final IOException ioe) - { - result = file.toString(); - } - } - - return(result); - } -} +package org.alfresco.repo.bulkimport.impl; + +import java.io.File; +import java.io.IOException; + +/** + * + * @since 4.0 + * + * TODO move to core project + */ +public class FileUtils +{ + public static String getFileName(final File file) + { + String result = null; + + if (file != null) + { + try + { + result = file.getCanonicalPath(); + } + catch (final IOException ioe) + { + result = file.toString(); + } + } + + return(result); + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/FilesystemContentDataFactory.java b/source/java/org/alfresco/repo/bulkimport/impl/FilesystemContentDataFactory.java index baae8373da..f7e9e8d4aa 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/FilesystemContentDataFactory.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/FilesystemContentDataFactory.java @@ -1,149 +1,149 @@ -package org.alfresco.repo.bulkimport.impl; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.bulkimport.ContentDataFactory; -import org.alfresco.repo.content.ContentStore; -import org.alfresco.repo.content.encoding.ContentCharsetFinder; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.MimetypeService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -/** - * - * Factory that creates {@link ContentData} out of : - *
    - *
  • a {@link ContentStore} - *
  • a {@link File} located within that store's root - *

- * - * The mimetype will be guessed from the file extension, or fall back to binary. - * The encoding will be guessed from the file itself, or fall back to {@link #defaultEncoding}. - * - * @since 4.0 - */ -public class FilesystemContentDataFactory implements ContentDataFactory, InitializingBean -{ - private static final Log logger = LogFactory.getLog(FilesystemContentDataFactory.class); - - private static final String PROTOCOL_DELIMITER = ContentStore.PROTOCOL_DELIMITER; - - private MimetypeService mimetypeService; - private String defaultEncoding; - private String storeProtocol; - - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } - - public void setDefaultEncoding(String defaultEncoding) - { - this.defaultEncoding = defaultEncoding; - } - - public void setStoreProtocol(String storeProtocol) - { - this.storeProtocol = storeProtocol; - } - - public void afterPropertiesSet() throws Exception - { - PropertyCheck.mandatory(this, "mimetypeService", mimetypeService); - PropertyCheck.mandatory(this, "defaultEncoding", defaultEncoding); - PropertyCheck.mandatory(this, "storeProtocol", storeProtocol); - } - - /** - * Create a {@link ContentData} by combining the given {@link ContentStore}'s root location and the {@link File}'s path within that store. - * The given file must therefore be accessible within the content store's configured root location. - * The encoding and mimetype will be guessed from the given file. - * - * @param store The {@link ContentStore} in which the file should be - * @param contentFile The {@link File} to check - * @return the constructed {@link ContentData} - */ - public ContentData createContentData(ContentStore store, File contentFile) - { - try - { - String rootLocation = new File(store.getRootLocation()).getCanonicalPath(); - String contentLocation = contentFile.getCanonicalPath(); - if (!contentLocation.startsWith(rootLocation + File.separator)) - { - throw new IllegalArgumentException("Can't create content URL : file '" + contentLocation - + "' is not located within the store's tree ! The store's root is :'" + rootLocation); - } - String relativeFilePath = contentLocation.substring(rootLocation.length() + File.separator.length()); - String mimetype = mimetypeService.guessMimetype(contentFile.getName()); - String encoding = defaultEncoding; - if (!contentFile.isDirectory()) - { - encoding = guessEncoding(contentFile, mimetype); - } - - ContentData contentData = new ContentData(storeProtocol + PROTOCOL_DELIMITER + relativeFilePath, mimetype, - contentFile.length(), encoding); - - Map contentProps = new HashMap(); - contentProps.put(ContentModel.PROP_NAME, contentFile.getName()); - contentProps.put(ContentModel.PROP_CONTENT, contentData); - - return contentData; - } - catch (IOException e) - { - throw new AlfrescoRuntimeException(e.getMessage(), e); - } - } - - /** - * Attempt to guess file encoding. fall back to {@link #defaultEncoding} otherwise. - * - * @param file the {@link java.io.File} to test - * @param mimetype the file mimetype. used to first distinguish between binary and text files - * @return the encoding as a {@link String} - */ - private String guessEncoding(File file,String mimetype) - { - String encoding = defaultEncoding; // fallback default - if(file.isDirectory()) - return defaultEncoding; // not necessary to guess folder encoding - InputStream is = null; - ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder(); - - try - { - is = new BufferedInputStream(new FileInputStream(file)); - encoding = charsetFinder.getCharset(is, mimetype).name(); - } - catch (Throwable e) - { - if(logger.isWarnEnabled()) - logger.warn("Failed to guess character encoding of file: '" + file.getName() + "'. Falling back to configured default encoding (" + defaultEncoding + ")"); - } - finally - { - if (is != null) - { - try { is.close(); } catch (Throwable e) {} - } - } - - return encoding; - } - -} +package org.alfresco.repo.bulkimport.impl; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.bulkimport.ContentDataFactory; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.encoding.ContentCharsetFinder; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * + * Factory that creates {@link ContentData} out of : + *
    + *
  • a {@link ContentStore} + *
  • a {@link File} located within that store's root + *

+ * + * The mimetype will be guessed from the file extension, or fall back to binary. + * The encoding will be guessed from the file itself, or fall back to {@link #defaultEncoding}. + * + * @since 4.0 + */ +public class FilesystemContentDataFactory implements ContentDataFactory, InitializingBean +{ + private static final Log logger = LogFactory.getLog(FilesystemContentDataFactory.class); + + private static final String PROTOCOL_DELIMITER = ContentStore.PROTOCOL_DELIMITER; + + private MimetypeService mimetypeService; + private String defaultEncoding; + private String storeProtocol; + + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + public void setDefaultEncoding(String defaultEncoding) + { + this.defaultEncoding = defaultEncoding; + } + + public void setStoreProtocol(String storeProtocol) + { + this.storeProtocol = storeProtocol; + } + + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "mimetypeService", mimetypeService); + PropertyCheck.mandatory(this, "defaultEncoding", defaultEncoding); + PropertyCheck.mandatory(this, "storeProtocol", storeProtocol); + } + + /** + * Create a {@link ContentData} by combining the given {@link ContentStore}'s root location and the {@link File}'s path within that store. + * The given file must therefore be accessible within the content store's configured root location. + * The encoding and mimetype will be guessed from the given file. + * + * @param store The {@link ContentStore} in which the file should be + * @param contentFile The {@link File} to check + * @return the constructed {@link ContentData} + */ + public ContentData createContentData(ContentStore store, File contentFile) + { + try + { + String rootLocation = new File(store.getRootLocation()).getCanonicalPath(); + String contentLocation = contentFile.getCanonicalPath(); + if (!contentLocation.startsWith(rootLocation + File.separator)) + { + throw new IllegalArgumentException("Can't create content URL : file '" + contentLocation + + "' is not located within the store's tree ! The store's root is :'" + rootLocation); + } + String relativeFilePath = contentLocation.substring(rootLocation.length() + File.separator.length()); + String mimetype = mimetypeService.guessMimetype(contentFile.getName()); + String encoding = defaultEncoding; + if (!contentFile.isDirectory()) + { + encoding = guessEncoding(contentFile, mimetype); + } + + ContentData contentData = new ContentData(storeProtocol + PROTOCOL_DELIMITER + relativeFilePath, mimetype, + contentFile.length(), encoding); + + Map contentProps = new HashMap(); + contentProps.put(ContentModel.PROP_NAME, contentFile.getName()); + contentProps.put(ContentModel.PROP_CONTENT, contentData); + + return contentData; + } + catch (IOException e) + { + throw new AlfrescoRuntimeException(e.getMessage(), e); + } + } + + /** + * Attempt to guess file encoding. fall back to {@link #defaultEncoding} otherwise. + * + * @param file the {@link java.io.File} to test + * @param mimetype the file mimetype. used to first distinguish between binary and text files + * @return the encoding as a {@link String} + */ + private String guessEncoding(File file,String mimetype) + { + String encoding = defaultEncoding; // fallback default + if(file.isDirectory()) + return defaultEncoding; // not necessary to guess folder encoding + InputStream is = null; + ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder(); + + try + { + is = new BufferedInputStream(new FileInputStream(file)); + encoding = charsetFinder.getCharset(is, mimetype).name(); + } + catch (Throwable e) + { + if(logger.isWarnEnabled()) + logger.warn("Failed to guess character encoding of file: '" + file.getName() + "'. Falling back to configured default encoding (" + defaultEncoding + ")"); + } + finally + { + if (is != null) + { + try { is.close(); } catch (Throwable e) {} + } + } + + return encoding; + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/MultiThreadedBulkFilesystemImporter.java b/source/java/org/alfresco/repo/bulkimport/impl/MultiThreadedBulkFilesystemImporter.java index 0f4d4e6da1..148b493ebb 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/MultiThreadedBulkFilesystemImporter.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/MultiThreadedBulkFilesystemImporter.java @@ -1,178 +1,178 @@ -package org.alfresco.repo.bulkimport.impl; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.batch.BatchProcessWorkProvider; -import org.alfresco.repo.batch.BatchProcessor; -import org.alfresco.repo.bulkimport.BulkFilesystemImporter; -import org.alfresco.repo.bulkimport.BulkImportParameters; -import org.alfresco.repo.bulkimport.FilesystemTracker; -import org.alfresco.repo.bulkimport.ImportableItem; -import org.alfresco.repo.bulkimport.NodeImporter; -import org.alfresco.repo.node.integrity.IntegrityException; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.tenant.TenantUtil; -import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; -import org.alfresco.service.cmr.repository.NodeRef; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Performs a multi-threaded filesystem import into the repository using the {@link BatchProcessor}. - * - * @since 4.0 - * - */ -public abstract class MultiThreadedBulkFilesystemImporter extends AbstractBulkFilesystemImporter -{ - protected final static Log logger = LogFactory.getLog(BulkFilesystemImporter.class); - - protected int defaultBatchSize; - protected int defaultNumThreads; - protected int defaultLoggingInterval = 100; - - protected int getLoggingInterval(BulkImportParameters bulkImportParameters) - { - return bulkImportParameters.getLoggingInterval() != null ? bulkImportParameters.getLoggingInterval() : defaultLoggingInterval; - } - - protected int getBatchSize(BulkImportParameters bulkImportParameters) - { - return bulkImportParameters.getBatchSize() != null ? bulkImportParameters.getBatchSize() : defaultBatchSize; - } - - protected int getNumThreads(BulkImportParameters bulkImportParameters) - { - return bulkImportParameters.getNumThreads() != null ? bulkImportParameters.getNumThreads() : defaultNumThreads; - } - - protected BatchProcessor.BatchProcessWorker getWorker(final BulkImportParameters bulkImportParameters, final String lockToken, - final NodeImporter nodeImporter, final FilesystemTracker filesystemTracker) - { - final int batchSize = bulkImportParameters.getBatchSize() != null ? bulkImportParameters.getBatchSize() : defaultBatchSize; - final boolean rulesEnabled = ruleService.isEnabled(); - final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - final String currentDomain = TenantUtil.getCurrentDomain(); - - BatchProcessor.BatchProcessWorker worker = new BatchProcessor.BatchProcessWorker() - { - public String getIdentifier(ImportableItem importableItem) - { - return importableItem.toString(); - } - - public void beforeProcess() throws Throwable - { - // Run as the correct user - AuthenticationUtil.setRunAsUser(currentUser); - - refreshLock(lockToken, batchSize * 250L); - if(bulkImportParameters.isDisableRulesService() && rulesEnabled) - { - ruleService.disableRules(); - } - } - - public void afterProcess() throws Throwable - { - if(bulkImportParameters.isDisableRulesService() && rulesEnabled) - { - ruleService.enableRules(); - } - - importStatus.incrementNumberOfBatchesCompleted(); - - AuthenticationUtil.clearCurrentSecurityContext(); - } - - public void process(final ImportableItem importableItem) throws Throwable - { - if(importStatus.getLastException() != null) - { - // bail out early if an exception occurs - return; - } - - TenantUtil.runAsUserTenant(new TenantRunAsWork() - { - @Override - public Void doWork() throws Exception - { - try - { - behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE); - - NodeRef nodeRef = nodeImporter.importImportableItem(importableItem, bulkImportParameters.isReplaceExisting()); - filesystemTracker.itemImported(nodeRef, importableItem); - } - finally - { - behaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); - } - - return null; - } - }, currentUser, currentDomain); - } - }; - - return worker; - } - - protected BatchProcessor getBatchProcessor(final BulkImportParameters bulkImportParameters, - final BatchProcessWorkProvider workProvider, final int loggingInterval) - { - final int numThreads = getNumThreads(bulkImportParameters); - final int batchSize = getBatchSize(bulkImportParameters); - - importStatus.setNumThreads(numThreads); - importStatus.setBatchSize(batchSize); - - BatchProcessor batchProcessor = new BatchProcessor( - "Bulk Filesystem Import", - transactionHelper, - workProvider, - numThreads, batchSize, - applicationContext, - logger, loggingInterval); - - return batchProcessor; - } - - public void setDefaultNumThreads(int defaultNumThreads) - { - this.defaultNumThreads = defaultNumThreads; - } - - public void setDefaultBatchSize(int defaultBatchSize) - { - this.defaultBatchSize = defaultBatchSize; - } - - public int getDefaultNumThreads() - { - return defaultNumThreads; - } - - public int getDefaultBatchSize() - { - return defaultBatchSize; - } - - /** - * Method that does the work of importing a filesystem using the BatchProcessor. - * - * @param bulkImportParameters The bulk import parameters to apply to this bulk import. - * @param nodeImporter The node importer implementation that will import each node. - * @param lockToken The lock token to use during the bulk import. - */ - @Override - protected void bulkImportImpl(final BulkImportParameters bulkImportParameters, final NodeImporter nodeImporter, final String lockToken) - { - final int batchSize = getBatchSize(bulkImportParameters); - final int numThreads = getNumThreads(bulkImportParameters); - - importStatus.setNumThreads(numThreads); - importStatus.setBatchSize(batchSize); - } - -} +package org.alfresco.repo.bulkimport.impl; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.batch.BatchProcessWorkProvider; +import org.alfresco.repo.batch.BatchProcessor; +import org.alfresco.repo.bulkimport.BulkFilesystemImporter; +import org.alfresco.repo.bulkimport.BulkImportParameters; +import org.alfresco.repo.bulkimport.FilesystemTracker; +import org.alfresco.repo.bulkimport.ImportableItem; +import org.alfresco.repo.bulkimport.NodeImporter; +import org.alfresco.repo.node.integrity.IntegrityException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Performs a multi-threaded filesystem import into the repository using the {@link BatchProcessor}. + * + * @since 4.0 + * + */ +public abstract class MultiThreadedBulkFilesystemImporter extends AbstractBulkFilesystemImporter +{ + protected final static Log logger = LogFactory.getLog(BulkFilesystemImporter.class); + + protected int defaultBatchSize; + protected int defaultNumThreads; + protected int defaultLoggingInterval = 100; + + protected int getLoggingInterval(BulkImportParameters bulkImportParameters) + { + return bulkImportParameters.getLoggingInterval() != null ? bulkImportParameters.getLoggingInterval() : defaultLoggingInterval; + } + + protected int getBatchSize(BulkImportParameters bulkImportParameters) + { + return bulkImportParameters.getBatchSize() != null ? bulkImportParameters.getBatchSize() : defaultBatchSize; + } + + protected int getNumThreads(BulkImportParameters bulkImportParameters) + { + return bulkImportParameters.getNumThreads() != null ? bulkImportParameters.getNumThreads() : defaultNumThreads; + } + + protected BatchProcessor.BatchProcessWorker getWorker(final BulkImportParameters bulkImportParameters, final String lockToken, + final NodeImporter nodeImporter, final FilesystemTracker filesystemTracker) + { + final int batchSize = bulkImportParameters.getBatchSize() != null ? bulkImportParameters.getBatchSize() : defaultBatchSize; + final boolean rulesEnabled = ruleService.isEnabled(); + final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + final String currentDomain = TenantUtil.getCurrentDomain(); + + BatchProcessor.BatchProcessWorker worker = new BatchProcessor.BatchProcessWorker() + { + public String getIdentifier(ImportableItem importableItem) + { + return importableItem.toString(); + } + + public void beforeProcess() throws Throwable + { + // Run as the correct user + AuthenticationUtil.setRunAsUser(currentUser); + + refreshLock(lockToken, batchSize * 250L); + if(bulkImportParameters.isDisableRulesService() && rulesEnabled) + { + ruleService.disableRules(); + } + } + + public void afterProcess() throws Throwable + { + if(bulkImportParameters.isDisableRulesService() && rulesEnabled) + { + ruleService.enableRules(); + } + + importStatus.incrementNumberOfBatchesCompleted(); + + AuthenticationUtil.clearCurrentSecurityContext(); + } + + public void process(final ImportableItem importableItem) throws Throwable + { + if(importStatus.getLastException() != null) + { + // bail out early if an exception occurs + return; + } + + TenantUtil.runAsUserTenant(new TenantRunAsWork() + { + @Override + public Void doWork() throws Exception + { + try + { + behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE); + + NodeRef nodeRef = nodeImporter.importImportableItem(importableItem, bulkImportParameters.isReplaceExisting()); + filesystemTracker.itemImported(nodeRef, importableItem); + } + finally + { + behaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); + } + + return null; + } + }, currentUser, currentDomain); + } + }; + + return worker; + } + + protected BatchProcessor getBatchProcessor(final BulkImportParameters bulkImportParameters, + final BatchProcessWorkProvider workProvider, final int loggingInterval) + { + final int numThreads = getNumThreads(bulkImportParameters); + final int batchSize = getBatchSize(bulkImportParameters); + + importStatus.setNumThreads(numThreads); + importStatus.setBatchSize(batchSize); + + BatchProcessor batchProcessor = new BatchProcessor( + "Bulk Filesystem Import", + transactionHelper, + workProvider, + numThreads, batchSize, + applicationContext, + logger, loggingInterval); + + return batchProcessor; + } + + public void setDefaultNumThreads(int defaultNumThreads) + { + this.defaultNumThreads = defaultNumThreads; + } + + public void setDefaultBatchSize(int defaultBatchSize) + { + this.defaultBatchSize = defaultBatchSize; + } + + public int getDefaultNumThreads() + { + return defaultNumThreads; + } + + public int getDefaultBatchSize() + { + return defaultBatchSize; + } + + /** + * Method that does the work of importing a filesystem using the BatchProcessor. + * + * @param bulkImportParameters The bulk import parameters to apply to this bulk import. + * @param nodeImporter The node importer implementation that will import each node. + * @param lockToken The lock token to use during the bulk import. + */ + @Override + protected void bulkImportImpl(final BulkImportParameters bulkImportParameters, final NodeImporter nodeImporter, final String lockToken) + { + final int batchSize = getBatchSize(bulkImportParameters); + final int numThreads = getNumThreads(bulkImportParameters); + + importStatus.setNumThreads(numThreads); + importStatus.setBatchSize(batchSize); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/StreamingNodeImporterFactory.java b/source/java/org/alfresco/repo/bulkimport/impl/StreamingNodeImporterFactory.java index d8304674a6..ad566645f8 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/StreamingNodeImporterFactory.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/StreamingNodeImporterFactory.java @@ -1,131 +1,131 @@ -package org.alfresco.repo.bulkimport.impl; - -import java.io.File; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.bulkimport.ImportableItem; -import org.alfresco.repo.bulkimport.MetadataLoader; -import org.alfresco.repo.bulkimport.NodeImporter; -import org.alfresco.repo.bulkimport.impl.BulkImportStatusImpl.NodeState; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.util.Triple; - -/** - * - * @since 4.0 - * - */ -public class StreamingNodeImporterFactory extends AbstractNodeImporterFactory -{ - public NodeImporter getNodeImporter(File sourceFolder) - { - StreamingNodeImporter nodeImporter = new StreamingNodeImporter(); - nodeImporter.setNodeService(nodeService); - nodeImporter.setBehaviourFilter(behaviourFilter); - nodeImporter.setFileFolderService(fileFolderService); - nodeImporter.setMetadataLoader(metadataLoader); - nodeImporter.setVersionService(versionService); - nodeImporter.setImportStatus(importStatus); - - nodeImporter.setSourceFolder(sourceFolder); - - return nodeImporter; - } - - /** - * - * @since 4.0 - * - */ - private static class StreamingNodeImporter extends AbstractNodeImporter - { - private File sourceFolder; - - public void setSourceFolder(File sourceFolder) - { - this.sourceFolder = sourceFolder; - } - - protected final void importContentAndMetadata(NodeRef nodeRef, ImportableItem.ContentAndMetadata contentAndMetadata, MetadataLoader.Metadata metadata) - { - // Write the content of the file - if (contentAndMetadata.contentFileExists()) - { - String filename = getFileName(contentAndMetadata.getContentFile()); - - if (logger.isDebugEnabled()) - { - logger.debug("Streaming contents of file '" + filename + "' into node '" + nodeRef.toString() + "'."); - } - - ContentWriter writer = fileFolderService.getWriter(nodeRef); - writer.putContent(contentAndMetadata.getContentFile()); - } - else - { - if (logger.isDebugEnabled()) logger.debug("No content to stream into node '" + nodeRef.toString() + "' - importing metadata only."); - } - - // Attach aspects and set all properties - importImportableItemMetadata(nodeRef, contentAndMetadata.getContentFile(), metadata); - } - - protected NodeRef importImportableItemImpl(ImportableItem importableItem, boolean replaceExisting) - { - NodeRef target = importableItem.getParent().getNodeRef(); - if(target == null) - { - // the parent has not been created yet, retry - throw new AlfrescoRuntimeException("Bulk importer: target is not known for importable item: " + importableItem.getParent()); - } - NodeRef result = null; - MetadataLoader.Metadata metadata = loadMetadata(importableItem.getHeadRevision()); - - Triple node = createOrFindNode(target, importableItem, replaceExisting, metadata); - boolean isDirectory = node.getSecond() == null ? false : node.getSecond(); // Watch out for NPEs during unboxing! - NodeState nodeState = node.getThird(); - - result = node.getFirst(); - - if (result != null && nodeState != NodeState.SKIPPED) - { - int numVersionProperties = 0; - - importStatus.incrementImportableItemsRead(importableItem, isDirectory); - - // Load the item - if (isDirectory) - { - importImportableItemDirectory(result, importableItem, metadata); - } - else - { - numVersionProperties = importImportableItemFile(result, importableItem, metadata, nodeState); - } - - importStatus.incrementNodesWritten(importableItem, isDirectory, nodeState, metadata.getProperties().size() + 4, numVersionProperties); - importStatus.incrementContentBytesWritten(importableItem, isDirectory, nodeState); - } - else - { - if(isDirectory) - { - skipImportableDirectory(importableItem); - } - else - { - skipImportableFile(importableItem); - } - } - - return(result); - } - - @Override - public File getSourceFolder() - { - return sourceFolder; - } - } -} +package org.alfresco.repo.bulkimport.impl; + +import java.io.File; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.bulkimport.ImportableItem; +import org.alfresco.repo.bulkimport.MetadataLoader; +import org.alfresco.repo.bulkimport.NodeImporter; +import org.alfresco.repo.bulkimport.impl.BulkImportStatusImpl.NodeState; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.Triple; + +/** + * + * @since 4.0 + * + */ +public class StreamingNodeImporterFactory extends AbstractNodeImporterFactory +{ + public NodeImporter getNodeImporter(File sourceFolder) + { + StreamingNodeImporter nodeImporter = new StreamingNodeImporter(); + nodeImporter.setNodeService(nodeService); + nodeImporter.setBehaviourFilter(behaviourFilter); + nodeImporter.setFileFolderService(fileFolderService); + nodeImporter.setMetadataLoader(metadataLoader); + nodeImporter.setVersionService(versionService); + nodeImporter.setImportStatus(importStatus); + + nodeImporter.setSourceFolder(sourceFolder); + + return nodeImporter; + } + + /** + * + * @since 4.0 + * + */ + private static class StreamingNodeImporter extends AbstractNodeImporter + { + private File sourceFolder; + + public void setSourceFolder(File sourceFolder) + { + this.sourceFolder = sourceFolder; + } + + protected final void importContentAndMetadata(NodeRef nodeRef, ImportableItem.ContentAndMetadata contentAndMetadata, MetadataLoader.Metadata metadata) + { + // Write the content of the file + if (contentAndMetadata.contentFileExists()) + { + String filename = getFileName(contentAndMetadata.getContentFile()); + + if (logger.isDebugEnabled()) + { + logger.debug("Streaming contents of file '" + filename + "' into node '" + nodeRef.toString() + "'."); + } + + ContentWriter writer = fileFolderService.getWriter(nodeRef); + writer.putContent(contentAndMetadata.getContentFile()); + } + else + { + if (logger.isDebugEnabled()) logger.debug("No content to stream into node '" + nodeRef.toString() + "' - importing metadata only."); + } + + // Attach aspects and set all properties + importImportableItemMetadata(nodeRef, contentAndMetadata.getContentFile(), metadata); + } + + protected NodeRef importImportableItemImpl(ImportableItem importableItem, boolean replaceExisting) + { + NodeRef target = importableItem.getParent().getNodeRef(); + if(target == null) + { + // the parent has not been created yet, retry + throw new AlfrescoRuntimeException("Bulk importer: target is not known for importable item: " + importableItem.getParent()); + } + NodeRef result = null; + MetadataLoader.Metadata metadata = loadMetadata(importableItem.getHeadRevision()); + + Triple node = createOrFindNode(target, importableItem, replaceExisting, metadata); + boolean isDirectory = node.getSecond() == null ? false : node.getSecond(); // Watch out for NPEs during unboxing! + NodeState nodeState = node.getThird(); + + result = node.getFirst(); + + if (result != null && nodeState != NodeState.SKIPPED) + { + int numVersionProperties = 0; + + importStatus.incrementImportableItemsRead(importableItem, isDirectory); + + // Load the item + if (isDirectory) + { + importImportableItemDirectory(result, importableItem, metadata); + } + else + { + numVersionProperties = importImportableItemFile(result, importableItem, metadata, nodeState); + } + + importStatus.incrementNodesWritten(importableItem, isDirectory, nodeState, metadata.getProperties().size() + 4, numVersionProperties); + importStatus.incrementContentBytesWritten(importableItem, isDirectory, nodeState); + } + else + { + if(isDirectory) + { + skipImportableDirectory(importableItem); + } + else + { + skipImportableFile(importableItem); + } + } + + return(result); + } + + @Override + public File getSourceFolder() + { + return sourceFolder; + } + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/StripingBulkFilesystemImporter.java b/source/java/org/alfresco/repo/bulkimport/impl/StripingBulkFilesystemImporter.java index 6548052bd5..6c13271ece 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/StripingBulkFilesystemImporter.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/StripingBulkFilesystemImporter.java @@ -1,48 +1,48 @@ -package org.alfresco.repo.bulkimport.impl; - -import java.io.File; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.batch.BatchProcessor; -import org.alfresco.repo.bulkimport.BulkImportParameters; -import org.alfresco.repo.bulkimport.ImportableItem; -import org.alfresco.repo.bulkimport.NodeImporter; - -/** - * A multi threaded bulk importer that imports by striping across filesystem levels. - * - * @since 4.0 - * - */ -public class StripingBulkFilesystemImporter extends MultiThreadedBulkFilesystemImporter -{ - /** - * Method that does the work of importing a filesystem using the BatchProcessor. - * - * @param bulkImportParameters The bulk import parameters to apply to this bulk import. - * @param nodeImporter The node importer implementation that will import each node. - * @param lockToken The lock token to use during the bulk import. - */ - @Override - protected void bulkImportImpl(final BulkImportParameters bulkImportParameters, final NodeImporter nodeImporter, final String lockToken) - { - super.bulkImportImpl(bulkImportParameters, nodeImporter, lockToken); - - final File sourceFolder = nodeImporter.getSourceFolder(); - final int batchSize = getBatchSize(bulkImportParameters); - final int loggingInterval = getLoggingInterval(bulkImportParameters); - final StripingFilesystemTracker tracker = new StripingFilesystemTracker(directoryAnalyser, bulkImportParameters.getTarget(), sourceFolder, batchSize); - final BatchProcessor batchProcessor = getBatchProcessor(bulkImportParameters, tracker.getWorkProvider(), loggingInterval); - final BatchProcessor.BatchProcessWorker worker = getWorker(bulkImportParameters, lockToken, nodeImporter, tracker); - - do - { - batchProcessor.process(worker, true); - if(batchProcessor.getLastError() != null) - { - throw new AlfrescoRuntimeException(batchProcessor.getLastError()); - } - } - while(tracker.moreLevels()); - } -} +package org.alfresco.repo.bulkimport.impl; + +import java.io.File; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.batch.BatchProcessor; +import org.alfresco.repo.bulkimport.BulkImportParameters; +import org.alfresco.repo.bulkimport.ImportableItem; +import org.alfresco.repo.bulkimport.NodeImporter; + +/** + * A multi threaded bulk importer that imports by striping across filesystem levels. + * + * @since 4.0 + * + */ +public class StripingBulkFilesystemImporter extends MultiThreadedBulkFilesystemImporter +{ + /** + * Method that does the work of importing a filesystem using the BatchProcessor. + * + * @param bulkImportParameters The bulk import parameters to apply to this bulk import. + * @param nodeImporter The node importer implementation that will import each node. + * @param lockToken The lock token to use during the bulk import. + */ + @Override + protected void bulkImportImpl(final BulkImportParameters bulkImportParameters, final NodeImporter nodeImporter, final String lockToken) + { + super.bulkImportImpl(bulkImportParameters, nodeImporter, lockToken); + + final File sourceFolder = nodeImporter.getSourceFolder(); + final int batchSize = getBatchSize(bulkImportParameters); + final int loggingInterval = getLoggingInterval(bulkImportParameters); + final StripingFilesystemTracker tracker = new StripingFilesystemTracker(directoryAnalyser, bulkImportParameters.getTarget(), sourceFolder, batchSize); + final BatchProcessor batchProcessor = getBatchProcessor(bulkImportParameters, tracker.getWorkProvider(), loggingInterval); + final BatchProcessor.BatchProcessWorker worker = getWorker(bulkImportParameters, lockToken, nodeImporter, tracker); + + do + { + batchProcessor.process(worker, true); + if(batchProcessor.getLastError() != null) + { + throw new AlfrescoRuntimeException(batchProcessor.getLastError()); + } + } + while(tracker.moreLevels()); + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/StripingFilesystemTracker.java b/source/java/org/alfresco/repo/bulkimport/impl/StripingFilesystemTracker.java index 7850ca7348..b748770e51 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/StripingFilesystemTracker.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/StripingFilesystemTracker.java @@ -1,184 +1,184 @@ -package org.alfresco.repo.bulkimport.impl; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.repo.batch.BatchProcessWorkProvider; -import org.alfresco.repo.bulkimport.AnalysedDirectory; -import org.alfresco.repo.bulkimport.DirectoryAnalyser; -import org.alfresco.repo.bulkimport.ImportableItem; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * A filesystem walker that returns all files and directories in subsequent levels of a filesystem tree; it returns all directories - * and files in a given level, at which point it moves on to the next level and starts returning directories and files in that level. - * - * @since 4.0 - * - */ -public class StripingFilesystemTracker extends AbstractFilesystemTracker -{ - private ImportableItem rootFolder; - private int currentLevel = 0; - private int batchSize; - - // TODO choose most appropriate list type - private Map> directoriesToProcess = new HashMap>(10); - private List toProcess = new ArrayList(); - - public StripingFilesystemTracker(DirectoryAnalyser directoryAnalyser, NodeRef target, File sourceFolder, int batchSize) - { - this.directoryAnalyser = directoryAnalyser; - this.batchSize = batchSize; - - // not really an importable item but the interface requires it to be in this form - rootFolder = new ImportableItem(); - rootFolder.getHeadRevision().setContentFile(sourceFolder); - rootFolder.setNodeRef(target); - - addDirectoryToProcess(rootFolder, currentLevel); - } - - protected void addDirectoriesToProcess(Collection dirsToAdd, int level) - { - List dirs = getDirectoriesToProcess(level); - dirs.addAll(dirsToAdd); - } - - protected void addDirectoryToProcess(ImportableItem dir, int level) - { - List dirs = getDirectoriesToProcess(level); - dirs.add(dir); - } - - protected List getDirectoriesToProcess(int level) - { - List dirs = directoriesToProcess.get(new Integer(level)); - if(dirs == null) - { - dirs = new ArrayList(); - directoriesToProcess.put(new Integer(level), dirs); - } - - return dirs; - } - - public int count() - { - // Note: this is an estimate of the number of directories and files in the current level - // TODO guess - multiplier of number of directories to process in the current directory - return numDirectoriesToProcess() * 100; - } - - protected void incrementLevel() - { - currentLevel++; - } - - public void itemImported(NodeRef nodeRef, ImportableItem importableItem) - { - // nothing to do - } - - protected void addItemsToProcess(Collection items) - { - toProcess.addAll(items); - } - - protected ImportableItem getDirectoryToProcess() - { - List dirs = getDirectoriesToProcess(currentLevel); - if(dirs.size() > 0) - { - return dirs.remove(0); - } - else - { - return null; - } - } - - public boolean moreLevels() - { - return getDirectoriesToProcess(currentLevel).size() > 0; - } - - public int numDirectoriesToProcess() - { - return getDirectoriesToProcess(currentLevel).size(); - } - - protected List getImportableItems(int count) - { - while(toProcess.size() < count) - { - ImportableItem directory = getDirectoryToProcess(); - if(directory != null) - { - AnalysedDirectory analysedDirectory = getImportableItemsInDirectory(directory); - addItemsToProcess(analysedDirectory.getImportableDirectories()); - addItemsToProcess(analysedDirectory.getImportableItems()); - - // add new directories to process in next level - getDirectoriesToProcess(currentLevel+1).addAll(analysedDirectory.getImportableDirectories()); - } - else - { - break; - } - } - - int size = (toProcess.size() >= count ? count : toProcess.size()); - List result = new ArrayList(size); - int i = size; - while(i > 0) - { - // we can assume that there are items in toProcess to remove because the size has been pre-calculated above - ImportableItem importableItem = toProcess.remove(0); - if(importableItem != null) - { - result.add(importableItem); - i--; - } - else - { - logger.warn("Unexpected empty toProcess queue"); - } - } - - if(result.size() == 0) - { - // this level has been exhausted, increment level - incrementLevel(); - } - - return result; - } - - @Override - public BatchProcessWorkProvider getWorkProvider() - { - BatchProcessWorkProvider provider = new BatchProcessWorkProvider() - { - @Override - public int getTotalEstimatedWorkSize() - { - return count(); - } - - @Override - public Collection getNextWork() - { - // TODO perhaps some multiple of the batchSize to limit calls - // to getNextWork? Add this to repository.properties. - return getImportableItems(batchSize*1000); - } - }; - - return provider; - } +package org.alfresco.repo.bulkimport.impl; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.batch.BatchProcessWorkProvider; +import org.alfresco.repo.bulkimport.AnalysedDirectory; +import org.alfresco.repo.bulkimport.DirectoryAnalyser; +import org.alfresco.repo.bulkimport.ImportableItem; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * A filesystem walker that returns all files and directories in subsequent levels of a filesystem tree; it returns all directories + * and files in a given level, at which point it moves on to the next level and starts returning directories and files in that level. + * + * @since 4.0 + * + */ +public class StripingFilesystemTracker extends AbstractFilesystemTracker +{ + private ImportableItem rootFolder; + private int currentLevel = 0; + private int batchSize; + + // TODO choose most appropriate list type + private Map> directoriesToProcess = new HashMap>(10); + private List toProcess = new ArrayList(); + + public StripingFilesystemTracker(DirectoryAnalyser directoryAnalyser, NodeRef target, File sourceFolder, int batchSize) + { + this.directoryAnalyser = directoryAnalyser; + this.batchSize = batchSize; + + // not really an importable item but the interface requires it to be in this form + rootFolder = new ImportableItem(); + rootFolder.getHeadRevision().setContentFile(sourceFolder); + rootFolder.setNodeRef(target); + + addDirectoryToProcess(rootFolder, currentLevel); + } + + protected void addDirectoriesToProcess(Collection dirsToAdd, int level) + { + List dirs = getDirectoriesToProcess(level); + dirs.addAll(dirsToAdd); + } + + protected void addDirectoryToProcess(ImportableItem dir, int level) + { + List dirs = getDirectoriesToProcess(level); + dirs.add(dir); + } + + protected List getDirectoriesToProcess(int level) + { + List dirs = directoriesToProcess.get(new Integer(level)); + if(dirs == null) + { + dirs = new ArrayList(); + directoriesToProcess.put(new Integer(level), dirs); + } + + return dirs; + } + + public int count() + { + // Note: this is an estimate of the number of directories and files in the current level + // TODO guess - multiplier of number of directories to process in the current directory + return numDirectoriesToProcess() * 100; + } + + protected void incrementLevel() + { + currentLevel++; + } + + public void itemImported(NodeRef nodeRef, ImportableItem importableItem) + { + // nothing to do + } + + protected void addItemsToProcess(Collection items) + { + toProcess.addAll(items); + } + + protected ImportableItem getDirectoryToProcess() + { + List dirs = getDirectoriesToProcess(currentLevel); + if(dirs.size() > 0) + { + return dirs.remove(0); + } + else + { + return null; + } + } + + public boolean moreLevels() + { + return getDirectoriesToProcess(currentLevel).size() > 0; + } + + public int numDirectoriesToProcess() + { + return getDirectoriesToProcess(currentLevel).size(); + } + + protected List getImportableItems(int count) + { + while(toProcess.size() < count) + { + ImportableItem directory = getDirectoryToProcess(); + if(directory != null) + { + AnalysedDirectory analysedDirectory = getImportableItemsInDirectory(directory); + addItemsToProcess(analysedDirectory.getImportableDirectories()); + addItemsToProcess(analysedDirectory.getImportableItems()); + + // add new directories to process in next level + getDirectoriesToProcess(currentLevel+1).addAll(analysedDirectory.getImportableDirectories()); + } + else + { + break; + } + } + + int size = (toProcess.size() >= count ? count : toProcess.size()); + List result = new ArrayList(size); + int i = size; + while(i > 0) + { + // we can assume that there are items in toProcess to remove because the size has been pre-calculated above + ImportableItem importableItem = toProcess.remove(0); + if(importableItem != null) + { + result.add(importableItem); + i--; + } + else + { + logger.warn("Unexpected empty toProcess queue"); + } + } + + if(result.size() == 0) + { + // this level has been exhausted, increment level + incrementLevel(); + } + + return result; + } + + @Override + public BatchProcessWorkProvider getWorkProvider() + { + BatchProcessWorkProvider provider = new BatchProcessWorkProvider() + { + @Override + public int getTotalEstimatedWorkSize() + { + return count(); + } + + @Override + public Collection getNextWork() + { + // TODO perhaps some multiple of the batchSize to limit calls + // to getNextWork? Add this to repository.properties. + return getImportableItems(batchSize*1000); + } + }; + + return provider; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/bulkimport/impl/stores/AbstractContentStoreMapProvider.java b/source/java/org/alfresco/repo/bulkimport/impl/stores/AbstractContentStoreMapProvider.java index 8878cc4a1b..fd9f3fde29 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/stores/AbstractContentStoreMapProvider.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/stores/AbstractContentStoreMapProvider.java @@ -1,97 +1,97 @@ - -package org.alfresco.repo.bulkimport.impl.stores; - -import java.util.Iterator; -import java.util.Map; - -import org.alfresco.repo.bulkimport.ContentStoreMapProvider; -import org.alfresco.repo.content.ContentStore; -import org.alfresco.repo.content.filestore.FileContentStore; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; - -/** - * Common elements of the role of a {@link ContentStoreMapProvider}. - * Extending classes should implement {@link #setUpStoreMap()} to initialize the {@link Map}. - * - * @since 4.0 - * - */ -public abstract class AbstractContentStoreMapProvider extends AbstractLifecycleBean implements ContentStoreMapProvider -{ - private final static Log logger = LogFactory.getLog(AbstractContentStoreMapProvider.class); - - protected ContentStore contentStore; - protected Map storeMap; - - protected abstract void setUpStoreMap(); - - /** - * set up the map on startup. see {@link #setUpStoreMap()}. - */ - protected void onBootstrap(ApplicationEvent event) - { - setUpStoreMap(); - } - - - protected void onShutdown(ApplicationEvent event) - { - // nothing particular to do - } - - /** - * Check that the given store name is in the list. - * Also check it's an instance of {@link FileContentStore}. If it's not, output a warning - * as non-file-based implementations have not been tested and may be unsupported. - * - * @param storeName the store name to check - */ - public ContentStore checkAndGetStore(String storeName) - { - ContentStore store = storeMap.get(storeName); - if(store == null) - { - String validStores =""; - Iterator it = storeMap.keySet().iterator(); - while (it.hasNext()) - { - validStores += "'" + it.next() + "'" + (it.hasNext() ? " , " : ""); - } - throw new IllegalArgumentException("given store name : '" + storeName + "' is not part of the registered stores : " + validStores); - } - if(!(store instanceof FileContentStore)) - { - // letting you off with a warning :) - // some people may have a custom content store for which the import could work in this case too ... - if(logger.isWarnEnabled()) - { - logger.warn("selected store '" + storeName + "' is not a FileContentStore. Is the implementation based on local files ?"); - } - } - - return store; - } - - /** - * see {@link ContentStoreMapProvider#getStoreMap()} - */ - public Map getStoreMap() - { - return storeMap; - } - - public ContentStore getContentStore() - { - return contentStore; - } - - public void setContentStore(ContentStore contentStore) - { - this.contentStore = contentStore; - } - - -} + +package org.alfresco.repo.bulkimport.impl.stores; + +import java.util.Iterator; +import java.util.Map; + +import org.alfresco.repo.bulkimport.ContentStoreMapProvider; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.filestore.FileContentStore; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * Common elements of the role of a {@link ContentStoreMapProvider}. + * Extending classes should implement {@link #setUpStoreMap()} to initialize the {@link Map}. + * + * @since 4.0 + * + */ +public abstract class AbstractContentStoreMapProvider extends AbstractLifecycleBean implements ContentStoreMapProvider +{ + private final static Log logger = LogFactory.getLog(AbstractContentStoreMapProvider.class); + + protected ContentStore contentStore; + protected Map storeMap; + + protected abstract void setUpStoreMap(); + + /** + * set up the map on startup. see {@link #setUpStoreMap()}. + */ + protected void onBootstrap(ApplicationEvent event) + { + setUpStoreMap(); + } + + + protected void onShutdown(ApplicationEvent event) + { + // nothing particular to do + } + + /** + * Check that the given store name is in the list. + * Also check it's an instance of {@link FileContentStore}. If it's not, output a warning + * as non-file-based implementations have not been tested and may be unsupported. + * + * @param storeName the store name to check + */ + public ContentStore checkAndGetStore(String storeName) + { + ContentStore store = storeMap.get(storeName); + if(store == null) + { + String validStores =""; + Iterator it = storeMap.keySet().iterator(); + while (it.hasNext()) + { + validStores += "'" + it.next() + "'" + (it.hasNext() ? " , " : ""); + } + throw new IllegalArgumentException("given store name : '" + storeName + "' is not part of the registered stores : " + validStores); + } + if(!(store instanceof FileContentStore)) + { + // letting you off with a warning :) + // some people may have a custom content store for which the import could work in this case too ... + if(logger.isWarnEnabled()) + { + logger.warn("selected store '" + storeName + "' is not a FileContentStore. Is the implementation based on local files ?"); + } + } + + return store; + } + + /** + * see {@link ContentStoreMapProvider#getStoreMap()} + */ + public Map getStoreMap() + { + return storeMap; + } + + public ContentStore getContentStore() + { + return contentStore; + } + + public void setContentStore(ContentStore contentStore) + { + this.contentStore = contentStore; + } + + +} diff --git a/source/java/org/alfresco/repo/bulkimport/impl/stores/DefaultContentStoreMapProvider.java b/source/java/org/alfresco/repo/bulkimport/impl/stores/DefaultContentStoreMapProvider.java index 22ce2bcdde..e278f561e7 100644 --- a/source/java/org/alfresco/repo/bulkimport/impl/stores/DefaultContentStoreMapProvider.java +++ b/source/java/org/alfresco/repo/bulkimport/impl/stores/DefaultContentStoreMapProvider.java @@ -1,37 +1,37 @@ - -package org.alfresco.repo.bulkimport.impl.stores; - -import java.util.HashMap; - -import org.alfresco.repo.content.ContentStore; - -/** - * Provides a default {@link java.util.Map} of registered content stores. - * Use when the Content Store Selector is not available (e.g on community releases). - * - * @since 4.0 - * - */ -public class DefaultContentStoreMapProvider extends AbstractContentStoreMapProvider -{ - /** - * the default store name, should match the default store defined by the content store selector aspect. - */ - private String defaultStoreName; - - /** - * Default implementation, relies on the default {@link ContentStore}. - */ - protected void setUpStoreMap() - { - storeMap = new HashMap(); - storeMap.put(defaultStoreName, contentStore); - } - - // boilerplate setters - - public void setDefaultStoreName(String defaultStoreName) - { - this.defaultStoreName = defaultStoreName; - } -} + +package org.alfresco.repo.bulkimport.impl.stores; + +import java.util.HashMap; + +import org.alfresco.repo.content.ContentStore; + +/** + * Provides a default {@link java.util.Map} of registered content stores. + * Use when the Content Store Selector is not available (e.g on community releases). + * + * @since 4.0 + * + */ +public class DefaultContentStoreMapProvider extends AbstractContentStoreMapProvider +{ + /** + * the default store name, should match the default store defined by the content store selector aspect. + */ + private String defaultStoreName; + + /** + * Default implementation, relies on the default {@link ContentStore}. + */ + protected void setUpStoreMap() + { + storeMap = new HashMap(); + storeMap.put(defaultStoreName, contentStore); + } + + // boilerplate setters + + public void setDefaultStoreName(String defaultStoreName) + { + this.defaultStoreName = defaultStoreName; + } +} diff --git a/source/java/org/alfresco/repo/bulkimport/importfilters/AndImportFilter.java b/source/java/org/alfresco/repo/bulkimport/importfilters/AndImportFilter.java index 8c5c523a24..c84eadedec 100644 --- a/source/java/org/alfresco/repo/bulkimport/importfilters/AndImportFilter.java +++ b/source/java/org/alfresco/repo/bulkimport/importfilters/AndImportFilter.java @@ -1,63 +1,63 @@ - -package org.alfresco.repo.bulkimport.importfilters; - -import java.util.ArrayList; -import java.util.List; - -import org.alfresco.repo.bulkimport.ImportFilter; -import org.alfresco.repo.bulkimport.ImportableItem; - -/** - * This class provides an ImportFilter that only returns true if all of the configured ImportFilters return true. - * - * @since 4.0 - */ -public class AndImportFilter - implements ImportFilter -{ - private final List filters; - - public AndImportFilter(final ImportFilter left, final ImportFilter right) - { - // PRECONDITIONS - assert left != null : "left must not be null."; - assert right != null : "right must not be null."; - - // Body - this.filters = new ArrayList(2); - - filters.add(left); - filters.add(right); - } - - public AndImportFilter(final List filters) - { - // PRECONDITIONS - assert filters != null : "filters must not be null."; - assert filters.size() >= 2 : "filters must contain at least 2 items."; - - // Body - this.filters = filters; - } - - - /** - * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) - */ - public boolean shouldFilter(final ImportableItem importableItem) - { - boolean result = true; - - for (final ImportFilter sourceFilter : filters) - { - if (!sourceFilter.shouldFilter(importableItem)) - { - result = false; - break; - } - } - - return(result); - } - -} + +package org.alfresco.repo.bulkimport.importfilters; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.repo.bulkimport.ImportFilter; +import org.alfresco.repo.bulkimport.ImportableItem; + +/** + * This class provides an ImportFilter that only returns true if all of the configured ImportFilters return true. + * + * @since 4.0 + */ +public class AndImportFilter + implements ImportFilter +{ + private final List filters; + + public AndImportFilter(final ImportFilter left, final ImportFilter right) + { + // PRECONDITIONS + assert left != null : "left must not be null."; + assert right != null : "right must not be null."; + + // Body + this.filters = new ArrayList(2); + + filters.add(left); + filters.add(right); + } + + public AndImportFilter(final List filters) + { + // PRECONDITIONS + assert filters != null : "filters must not be null."; + assert filters.size() >= 2 : "filters must contain at least 2 items."; + + // Body + this.filters = filters; + } + + + /** + * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) + */ + public boolean shouldFilter(final ImportableItem importableItem) + { + boolean result = true; + + for (final ImportFilter sourceFilter : filters) + { + if (!sourceFilter.shouldFilter(importableItem)) + { + result = false; + break; + } + } + + return(result); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/importfilters/DirectoryImportFilter.java b/source/java/org/alfresco/repo/bulkimport/importfilters/DirectoryImportFilter.java index 645c334c23..2edb142ef0 100644 --- a/source/java/org/alfresco/repo/bulkimport/importfilters/DirectoryImportFilter.java +++ b/source/java/org/alfresco/repo/bulkimport/importfilters/DirectoryImportFilter.java @@ -1,32 +1,32 @@ - -package org.alfresco.repo.bulkimport.importfilters; - -import org.alfresco.repo.bulkimport.ImportFilter; -import org.alfresco.repo.bulkimport.ImportableItem; - - -/** - * This class is an ImportFilter that filters out directories. - * - * @since 4.0 - */ -public class DirectoryImportFilter - implements ImportFilter -{ - - /** - * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) - */ - public boolean shouldFilter(final ImportableItem importableItem) - { - boolean result = false; - - if (importableItem.getHeadRevision().contentFileExists()) - { - result = ImportableItem.FileType.DIRECTORY.equals(importableItem.getHeadRevision().getContentFileType()); - } - - return(result); - } - -} + +package org.alfresco.repo.bulkimport.importfilters; + +import org.alfresco.repo.bulkimport.ImportFilter; +import org.alfresco.repo.bulkimport.ImportableItem; + + +/** + * This class is an ImportFilter that filters out directories. + * + * @since 4.0 + */ +public class DirectoryImportFilter + implements ImportFilter +{ + + /** + * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) + */ + public boolean shouldFilter(final ImportableItem importableItem) + { + boolean result = false; + + if (importableItem.getHeadRevision().contentFileExists()) + { + result = ImportableItem.FileType.DIRECTORY.equals(importableItem.getHeadRevision().getContentFileType()); + } + + return(result); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/importfilters/FileImportFilter.java b/source/java/org/alfresco/repo/bulkimport/importfilters/FileImportFilter.java index 91a7a5587d..225641195b 100644 --- a/source/java/org/alfresco/repo/bulkimport/importfilters/FileImportFilter.java +++ b/source/java/org/alfresco/repo/bulkimport/importfilters/FileImportFilter.java @@ -1,31 +1,31 @@ - -package org.alfresco.repo.bulkimport.importfilters; - -import org.alfresco.repo.bulkimport.ImportFilter; -import org.alfresco.repo.bulkimport.ImportableItem; - - -/** - * This class is an ImportFilter that filters out files. - * - * @since 4.0 - */ -public class FileImportFilter - implements ImportFilter -{ - /** - * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) - */ - public boolean shouldFilter(final ImportableItem importableItem) - { - boolean result = false; - - if (importableItem.getHeadRevision().contentFileExists()) - { - result = ImportableItem.FileType.FILE.equals(importableItem.getHeadRevision().getContentFileType()); - } - - return(result); - } - -} + +package org.alfresco.repo.bulkimport.importfilters; + +import org.alfresco.repo.bulkimport.ImportFilter; +import org.alfresco.repo.bulkimport.ImportableItem; + + +/** + * This class is an ImportFilter that filters out files. + * + * @since 4.0 + */ +public class FileImportFilter + implements ImportFilter +{ + /** + * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) + */ + public boolean shouldFilter(final ImportableItem importableItem) + { + boolean result = false; + + if (importableItem.getHeadRevision().contentFileExists()) + { + result = ImportableItem.FileType.FILE.equals(importableItem.getHeadRevision().getContentFileType()); + } + + return(result); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/importfilters/FileNameRegexImportFilter.java b/source/java/org/alfresco/repo/bulkimport/importfilters/FileNameRegexImportFilter.java index ae441f11a3..c960dabed7 100644 --- a/source/java/org/alfresco/repo/bulkimport/importfilters/FileNameRegexImportFilter.java +++ b/source/java/org/alfresco/repo/bulkimport/importfilters/FileNameRegexImportFilter.java @@ -1,39 +1,39 @@ - -package org.alfresco.repo.bulkimport.importfilters; - - -import java.util.regex.Pattern; - -import org.alfresco.repo.bulkimport.ImportFilter; -import org.alfresco.repo.bulkimport.ImportableItem; - - -/** - * This class is an ImportFilter that filters out files and/or folders whose name, excluding - * path, matches the configured regular expression. - * - * @since 4.0 - */ -public class FileNameRegexImportFilter implements ImportFilter -{ - private final Pattern pattern; - - /** - * Simple constructor for a FileNameRegexSourceFilter - * - * @param filenameRegex The regex to use to match against file and folder names (must not be null). - */ - public FileNameRegexImportFilter(final String filenameRegex) - { - this.pattern = Pattern.compile(filenameRegex); - } - - /** - * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) - */ - public boolean shouldFilter(final ImportableItem importableItem) - { - return(pattern.matcher(importableItem.getHeadRevision().getContentFile().getName()).matches()); - } - -} + +package org.alfresco.repo.bulkimport.importfilters; + + +import java.util.regex.Pattern; + +import org.alfresco.repo.bulkimport.ImportFilter; +import org.alfresco.repo.bulkimport.ImportableItem; + + +/** + * This class is an ImportFilter that filters out files and/or folders whose name, excluding + * path, matches the configured regular expression. + * + * @since 4.0 + */ +public class FileNameRegexImportFilter implements ImportFilter +{ + private final Pattern pattern; + + /** + * Simple constructor for a FileNameRegexSourceFilter + * + * @param filenameRegex The regex to use to match against file and folder names (must not be null). + */ + public FileNameRegexImportFilter(final String filenameRegex) + { + this.pattern = Pattern.compile(filenameRegex); + } + + /** + * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) + */ + public boolean shouldFilter(final ImportableItem importableItem) + { + return(pattern.matcher(importableItem.getHeadRevision().getContentFile().getName()).matches()); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/importfilters/HiddenFileFilter.java b/source/java/org/alfresco/repo/bulkimport/importfilters/HiddenFileFilter.java index ed73507359..6cbe955fcb 100644 --- a/source/java/org/alfresco/repo/bulkimport/importfilters/HiddenFileFilter.java +++ b/source/java/org/alfresco/repo/bulkimport/importfilters/HiddenFileFilter.java @@ -1,33 +1,33 @@ - -package org.alfresco.repo.bulkimport.importfilters; - -import org.alfresco.repo.bulkimport.ImportFilter; -import org.alfresco.repo.bulkimport.ImportableItem; - - -/** - * This class is an ImportFilter that filters out hidden files. - * - * The exact definition of "hidden" is OS dependent - see http://download.oracle.com/javase/6/docs/api/java/io/File.html#isHidden() for details. - * - * @since 4.0 - * - */ -public class HiddenFileFilter implements ImportFilter -{ - /** - * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) - */ - public boolean shouldFilter(final ImportableItem importableItem) - { - boolean result = false; - - if (importableItem.getHeadRevision().contentFileExists()) - { - result = importableItem.getHeadRevision().getContentFile().isHidden(); - } - - return(result); - } - -} + +package org.alfresco.repo.bulkimport.importfilters; + +import org.alfresco.repo.bulkimport.ImportFilter; +import org.alfresco.repo.bulkimport.ImportableItem; + + +/** + * This class is an ImportFilter that filters out hidden files. + * + * The exact definition of "hidden" is OS dependent - see http://download.oracle.com/javase/6/docs/api/java/io/File.html#isHidden() for details. + * + * @since 4.0 + * + */ +public class HiddenFileFilter implements ImportFilter +{ + /** + * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) + */ + public boolean shouldFilter(final ImportableItem importableItem) + { + boolean result = false; + + if (importableItem.getHeadRevision().contentFileExists()) + { + result = importableItem.getHeadRevision().getContentFile().isHidden(); + } + + return(result); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/importfilters/NonExistentContentFileImportFilter.java b/source/java/org/alfresco/repo/bulkimport/importfilters/NonExistentContentFileImportFilter.java index 0fc41a7506..4370267458 100644 --- a/source/java/org/alfresco/repo/bulkimport/importfilters/NonExistentContentFileImportFilter.java +++ b/source/java/org/alfresco/repo/bulkimport/importfilters/NonExistentContentFileImportFilter.java @@ -1,24 +1,24 @@ - -package org.alfresco.repo.bulkimport.importfilters; - -import org.alfresco.repo.bulkimport.ImportFilter; -import org.alfresco.repo.bulkimport.ImportableItem; - - -/** - * This class is an ImportFilter that filters out importable items whose content file doesn't exist. - * - * @since 4.0 - */ -public class NonExistentContentFileImportFilter - implements ImportFilter -{ - /** - * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) - */ - public boolean shouldFilter(final ImportableItem importableItem) - { - return(!importableItem.getHeadRevision().contentFileExists()); - } - -} + +package org.alfresco.repo.bulkimport.importfilters; + +import org.alfresco.repo.bulkimport.ImportFilter; +import org.alfresco.repo.bulkimport.ImportableItem; + + +/** + * This class is an ImportFilter that filters out importable items whose content file doesn't exist. + * + * @since 4.0 + */ +public class NonExistentContentFileImportFilter + implements ImportFilter +{ + /** + * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) + */ + public boolean shouldFilter(final ImportableItem importableItem) + { + return(!importableItem.getHeadRevision().contentFileExists()); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/importfilters/NotImportFilter.java b/source/java/org/alfresco/repo/bulkimport/importfilters/NotImportFilter.java index 33b002cf1a..678c82e6c7 100644 --- a/source/java/org/alfresco/repo/bulkimport/importfilters/NotImportFilter.java +++ b/source/java/org/alfresco/repo/bulkimport/importfilters/NotImportFilter.java @@ -1,36 +1,36 @@ - -package org.alfresco.repo.bulkimport.importfilters; - -import org.alfresco.repo.bulkimport.ImportFilter; -import org.alfresco.repo.bulkimport.ImportableItem; - - -/** - * This class provides an ImportFilter that returns the opposite of the configured SourceFilter. - * - * @since 4.0 - */ -public class NotImportFilter - implements ImportFilter -{ - private ImportFilter original; - - public NotImportFilter(final ImportFilter original) - { - // PRECONDITIONS - assert original != null : "original must not be null."; - - // Body - this.original = original; - } - - - /** - * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) - */ - public boolean shouldFilter(final ImportableItem importableItem) - { - return(!original.shouldFilter(importableItem)); - } - -} + +package org.alfresco.repo.bulkimport.importfilters; + +import org.alfresco.repo.bulkimport.ImportFilter; +import org.alfresco.repo.bulkimport.ImportableItem; + + +/** + * This class provides an ImportFilter that returns the opposite of the configured SourceFilter. + * + * @since 4.0 + */ +public class NotImportFilter + implements ImportFilter +{ + private ImportFilter original; + + public NotImportFilter(final ImportFilter original) + { + // PRECONDITIONS + assert original != null : "original must not be null."; + + // Body + this.original = original; + } + + + /** + * @see org.alfresco.repo.bulkimport.ImportFilter#shouldFilter(org.alfresco.repo.bulkimport.ImportableItem) + */ + public boolean shouldFilter(final ImportableItem importableItem) + { + return(!original.shouldFilter(importableItem)); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/metadataloaders/AbstractMapBasedMetadataLoader.java b/source/java/org/alfresco/repo/bulkimport/metadataloaders/AbstractMapBasedMetadataLoader.java index 5762f50239..dd51fd13b5 100644 --- a/source/java/org/alfresco/repo/bulkimport/metadataloaders/AbstractMapBasedMetadataLoader.java +++ b/source/java/org/alfresco/repo/bulkimport/metadataloaders/AbstractMapBasedMetadataLoader.java @@ -1,143 +1,143 @@ -package org.alfresco.repo.bulkimport.metadataloaders; - -import java.io.File; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; - -import org.alfresco.repo.bulkimport.ImportableItem.ContentAndMetadata; -import org.alfresco.repo.bulkimport.MetadataLoader; -import org.alfresco.repo.bulkimport.impl.FileUtils; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Abstract MetadataLoader abstracts out the common features of loading metadata - * from a java.util.Map, regardless of where it came from. - * - * @since 4.0 - * - * @see MetadataLoader - */ -abstract class AbstractMapBasedMetadataLoader implements MetadataLoader -{ - private final static Log log = LogFactory.getLog(AbstractMapBasedMetadataLoader.class); - - private final static String PROPERTY_NAME_TYPE = "type"; - private final static String PROPERTY_NAME_ASPECTS = "aspects"; - - private final static String DEFAULT_MULTI_VALUED_SEPARATOR = ","; - - protected final NamespaceService namespaceService; - protected final DictionaryService dictionaryService; - protected final String multiValuedSeparator; - protected final String metadataFileExtension; - - protected AbstractMapBasedMetadataLoader(final ServiceRegistry serviceRegistry, final String fileExtension) - { - this(serviceRegistry, DEFAULT_MULTI_VALUED_SEPARATOR, fileExtension); - } - - protected AbstractMapBasedMetadataLoader(final ServiceRegistry serviceRegistry, final String multiValuedSeparator, final String fileExtension) - { - // PRECONDITIONS - assert serviceRegistry != null : "serviceRegistry must not be null"; - assert multiValuedSeparator != null : "multiValuedSeparator must not be null"; - - // Body - this.namespaceService = serviceRegistry.getNamespaceService(); - this.dictionaryService = serviceRegistry.getDictionaryService(); - this.multiValuedSeparator = multiValuedSeparator; - this.metadataFileExtension = fileExtension; - } - - - /** - * @see org.alfresco.repo.bulkimport.MetadataLoader#getMetadataFileExtension() - */ - @Override - public final String getMetadataFileExtension() - { - return(metadataFileExtension); - } - - - /** - * Method that actually loads the properties from the file. - * @param metadataFile The file to load the properties from (must not be null). - * @return A new Properties object loaded from that file. - */ - abstract protected Map loadMetadataFromFile(final File metadataFile); - - - @Override - public final void loadMetadata(final ContentAndMetadata contentAndMetadata, Metadata metadata) - { - if (contentAndMetadata.metadataFileExists()) - { - final File metadataFile = contentAndMetadata.getMetadataFile(); - - if (metadataFile.canRead()) - { - Map metadataProperties = loadMetadataFromFile(metadataFile); - - for (String key : metadataProperties.keySet()) - { - if (PROPERTY_NAME_TYPE.equals(key)) - { - String typeName = (String)metadataProperties.get(key); - QName type = QName.createQName(typeName, namespaceService); - - metadata.setType(type); - } - else if (PROPERTY_NAME_ASPECTS.equals(key)) - { - String[] aspectNames = ((String)metadataProperties.get(key)).split(","); - - for (final String aspectName : aspectNames) - { - QName aspect = QName.createQName(aspectName.trim(), namespaceService); - metadata.addAspect(aspect); - } - } - else // Any other key => property - { - //####TODO: figure out how to handle properties of type cm:content - they need to be streamed in via a Writer - QName name = QName.createQName(key, namespaceService); - PropertyDefinition propertyDefinition = dictionaryService.getProperty(name); // TODO: measure performance impact of this API call!! - - if (propertyDefinition != null) - { - if (propertyDefinition.isMultiValued()) - { - // Multi-valued property - ArrayList values = new ArrayList(Arrays.asList(((String)metadataProperties.get(key)).split(multiValuedSeparator))); - metadata.addProperty(name, values); - } - else - { - // Single value property - metadata.addProperty(name, metadataProperties.get(key)); - } - } - else - { - if (log.isWarnEnabled()) log.warn("Property " + String.valueOf(name) + " doesn't exist in the Data Dictionary. Ignoring it."); - } - } - } - } - else - { - if (log.isWarnEnabled()) log.warn("Metadata file '" + FileUtils.getFileName(metadataFile) + "' is not readable."); - } - } - } - -} +package org.alfresco.repo.bulkimport.metadataloaders; + +import java.io.File; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; + +import org.alfresco.repo.bulkimport.ImportableItem.ContentAndMetadata; +import org.alfresco.repo.bulkimport.MetadataLoader; +import org.alfresco.repo.bulkimport.impl.FileUtils; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract MetadataLoader abstracts out the common features of loading metadata + * from a java.util.Map, regardless of where it came from. + * + * @since 4.0 + * + * @see MetadataLoader + */ +abstract class AbstractMapBasedMetadataLoader implements MetadataLoader +{ + private final static Log log = LogFactory.getLog(AbstractMapBasedMetadataLoader.class); + + private final static String PROPERTY_NAME_TYPE = "type"; + private final static String PROPERTY_NAME_ASPECTS = "aspects"; + + private final static String DEFAULT_MULTI_VALUED_SEPARATOR = ","; + + protected final NamespaceService namespaceService; + protected final DictionaryService dictionaryService; + protected final String multiValuedSeparator; + protected final String metadataFileExtension; + + protected AbstractMapBasedMetadataLoader(final ServiceRegistry serviceRegistry, final String fileExtension) + { + this(serviceRegistry, DEFAULT_MULTI_VALUED_SEPARATOR, fileExtension); + } + + protected AbstractMapBasedMetadataLoader(final ServiceRegistry serviceRegistry, final String multiValuedSeparator, final String fileExtension) + { + // PRECONDITIONS + assert serviceRegistry != null : "serviceRegistry must not be null"; + assert multiValuedSeparator != null : "multiValuedSeparator must not be null"; + + // Body + this.namespaceService = serviceRegistry.getNamespaceService(); + this.dictionaryService = serviceRegistry.getDictionaryService(); + this.multiValuedSeparator = multiValuedSeparator; + this.metadataFileExtension = fileExtension; + } + + + /** + * @see org.alfresco.repo.bulkimport.MetadataLoader#getMetadataFileExtension() + */ + @Override + public final String getMetadataFileExtension() + { + return(metadataFileExtension); + } + + + /** + * Method that actually loads the properties from the file. + * @param metadataFile The file to load the properties from (must not be null). + * @return A new Properties object loaded from that file. + */ + abstract protected Map loadMetadataFromFile(final File metadataFile); + + + @Override + public final void loadMetadata(final ContentAndMetadata contentAndMetadata, Metadata metadata) + { + if (contentAndMetadata.metadataFileExists()) + { + final File metadataFile = contentAndMetadata.getMetadataFile(); + + if (metadataFile.canRead()) + { + Map metadataProperties = loadMetadataFromFile(metadataFile); + + for (String key : metadataProperties.keySet()) + { + if (PROPERTY_NAME_TYPE.equals(key)) + { + String typeName = (String)metadataProperties.get(key); + QName type = QName.createQName(typeName, namespaceService); + + metadata.setType(type); + } + else if (PROPERTY_NAME_ASPECTS.equals(key)) + { + String[] aspectNames = ((String)metadataProperties.get(key)).split(","); + + for (final String aspectName : aspectNames) + { + QName aspect = QName.createQName(aspectName.trim(), namespaceService); + metadata.addAspect(aspect); + } + } + else // Any other key => property + { + //####TODO: figure out how to handle properties of type cm:content - they need to be streamed in via a Writer + QName name = QName.createQName(key, namespaceService); + PropertyDefinition propertyDefinition = dictionaryService.getProperty(name); // TODO: measure performance impact of this API call!! + + if (propertyDefinition != null) + { + if (propertyDefinition.isMultiValued()) + { + // Multi-valued property + ArrayList values = new ArrayList(Arrays.asList(((String)metadataProperties.get(key)).split(multiValuedSeparator))); + metadata.addProperty(name, values); + } + else + { + // Single value property + metadata.addProperty(name, metadataProperties.get(key)); + } + } + else + { + if (log.isWarnEnabled()) log.warn("Property " + String.valueOf(name) + " doesn't exist in the Data Dictionary. Ignoring it."); + } + } + } + } + else + { + if (log.isWarnEnabled()) log.warn("Metadata file '" + FileUtils.getFileName(metadataFile) + "' is not readable."); + } + } + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/metadataloaders/PropertiesFileMetadataLoader.java b/source/java/org/alfresco/repo/bulkimport/metadataloaders/PropertiesFileMetadataLoader.java index 427550a104..aad00e5b1b 100644 --- a/source/java/org/alfresco/repo/bulkimport/metadataloaders/PropertiesFileMetadataLoader.java +++ b/source/java/org/alfresco/repo/bulkimport/metadataloaders/PropertiesFileMetadataLoader.java @@ -1,106 +1,106 @@ -package org.alfresco.repo.bulkimport.metadataloaders; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -import org.alfresco.repo.bulkimport.MetadataLoader; -import org.alfresco.repo.bulkimport.impl.FileUtils; -import org.alfresco.service.ServiceRegistry; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * MetadataLoader that loads metadata from an (optional) "shadow" properties - * file. This shadow properties file must have exactly the - * same name and extension as the file for whom it is storing metadata, but - * with the suffix ".metadata.properties". So for example, if there is a file - * called "IMG_1967.jpg", the "shadow" properties metadata file for it would - * be called "IMG_1967.jpg.metadata.properties". - * - * The metadata file itself follows the usual rules for Java properties files, - * with a property with the key "type" containing the qualified name of the - * content type to use for the file, a property with the key "aspects" - * containing a comma-delimited list of qualified names of the aspects to - * attach to the file, and then one Java property per metadata property, with - * the key being the Alfresco property QName and the value being the value of - * that property. - * - * For example (note escaping rules for namespace separator!): - * - * - * type=cm:content - * aspects=cm:versionable, custom:myAspect - * cm\:title=This is the value of the cm:title field. - * cm\:description=This is the value of the cm:description field. - * cm\:taggable=workspace://SpacesStore/3da6c395-3a4b-4a57-836d-8e5 - * custom\:myProperty=This is the value of the custom:myProperty field. - * custom\:aDateProperty=2001-01-01T12:00:00.000+01:00 - * - * - * Notes: - *
    - *
  • Java properties files do not support Unicode characters - all values - * are loaded assuming an ISO-8859-1 character set. For Unicode - * metadata, you should use XmlPropertiesFileMetadataLoader - * instead.
  • - *
  • the metadata must conform to the type and aspect definitions - * configured in Alfresco (including mandatory fields, constraints and data - * types). Any violations will terminate the bulk import process.
  • - *
  • associations are not yet supported
  • - *
  • dates, times and date times must be stored in ISO8601 format - * (although note that Alfresco ignores timezone modifiers)
  • - *
- * - * @since 4.0 - * - * @see MetadataLoader - */ -public final class PropertiesFileMetadataLoader -extends AbstractMapBasedMetadataLoader -{ - private final static Log log = LogFactory.getLog(PropertiesFileMetadataLoader.class); - - private final static String METADATA_FILE_EXTENSION = "properties.xml"; - - - public PropertiesFileMetadataLoader(final ServiceRegistry serviceRegistry) - { - super(serviceRegistry, METADATA_FILE_EXTENSION); - } - - - public PropertiesFileMetadataLoader(final ServiceRegistry serviceRegistry, final String multiValuedSeparator) - { - super(serviceRegistry, multiValuedSeparator, METADATA_FILE_EXTENSION); - } - - - /** - * @see org.alfresco.repo.bulkimport.metadataloaders.AbstractMapBasedMetadataLoader#loadMetadataFromFile(java.io.File) - */ - @Override - protected Map loadMetadataFromFile(File metadataFile) - { - Map result = null; - - try - { - Properties props = new Properties(); - props.load(new BufferedInputStream(new FileInputStream(metadataFile))); - result = new HashMap((Map)props); - } - catch (final IOException ioe) - { - if (log.isWarnEnabled()) log.warn("Metadata file '" + FileUtils.getFileName(metadataFile) + "' could not be read.", ioe); - } - - return(result); - } - -} +package org.alfresco.repo.bulkimport.metadataloaders; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.alfresco.repo.bulkimport.MetadataLoader; +import org.alfresco.repo.bulkimport.impl.FileUtils; +import org.alfresco.service.ServiceRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * MetadataLoader that loads metadata from an (optional) "shadow" properties + * file. This shadow properties file must have exactly the + * same name and extension as the file for whom it is storing metadata, but + * with the suffix ".metadata.properties". So for example, if there is a file + * called "IMG_1967.jpg", the "shadow" properties metadata file for it would + * be called "IMG_1967.jpg.metadata.properties". + * + * The metadata file itself follows the usual rules for Java properties files, + * with a property with the key "type" containing the qualified name of the + * content type to use for the file, a property with the key "aspects" + * containing a comma-delimited list of qualified names of the aspects to + * attach to the file, and then one Java property per metadata property, with + * the key being the Alfresco property QName and the value being the value of + * that property. + * + * For example (note escaping rules for namespace separator!): + * + * + * type=cm:content + * aspects=cm:versionable, custom:myAspect + * cm\:title=This is the value of the cm:title field. + * cm\:description=This is the value of the cm:description field. + * cm\:taggable=workspace://SpacesStore/3da6c395-3a4b-4a57-836d-8e5 + * custom\:myProperty=This is the value of the custom:myProperty field. + * custom\:aDateProperty=2001-01-01T12:00:00.000+01:00 + * + * + * Notes: + *
    + *
  • Java properties files do not support Unicode characters - all values + * are loaded assuming an ISO-8859-1 character set. For Unicode + * metadata, you should use XmlPropertiesFileMetadataLoader + * instead.
  • + *
  • the metadata must conform to the type and aspect definitions + * configured in Alfresco (including mandatory fields, constraints and data + * types). Any violations will terminate the bulk import process.
  • + *
  • associations are not yet supported
  • + *
  • dates, times and date times must be stored in ISO8601 format + * (although note that Alfresco ignores timezone modifiers)
  • + *
+ * + * @since 4.0 + * + * @see MetadataLoader + */ +public final class PropertiesFileMetadataLoader +extends AbstractMapBasedMetadataLoader +{ + private final static Log log = LogFactory.getLog(PropertiesFileMetadataLoader.class); + + private final static String METADATA_FILE_EXTENSION = "properties.xml"; + + + public PropertiesFileMetadataLoader(final ServiceRegistry serviceRegistry) + { + super(serviceRegistry, METADATA_FILE_EXTENSION); + } + + + public PropertiesFileMetadataLoader(final ServiceRegistry serviceRegistry, final String multiValuedSeparator) + { + super(serviceRegistry, multiValuedSeparator, METADATA_FILE_EXTENSION); + } + + + /** + * @see org.alfresco.repo.bulkimport.metadataloaders.AbstractMapBasedMetadataLoader#loadMetadataFromFile(java.io.File) + */ + @Override + protected Map loadMetadataFromFile(File metadataFile) + { + Map result = null; + + try + { + Properties props = new Properties(); + props.load(new BufferedInputStream(new FileInputStream(metadataFile))); + result = new HashMap((Map)props); + } + catch (final IOException ioe) + { + if (log.isWarnEnabled()) log.warn("Metadata file '" + FileUtils.getFileName(metadataFile) + "' could not be read.", ioe); + } + + return(result); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/metadataloaders/XmlPropertiesFileMetadataLoader.java b/source/java/org/alfresco/repo/bulkimport/metadataloaders/XmlPropertiesFileMetadataLoader.java index 67c4ce5e66..503a52f122 100644 --- a/source/java/org/alfresco/repo/bulkimport/metadataloaders/XmlPropertiesFileMetadataLoader.java +++ b/source/java/org/alfresco/repo/bulkimport/metadataloaders/XmlPropertiesFileMetadataLoader.java @@ -1,108 +1,108 @@ - -package org.alfresco.repo.bulkimport.metadataloaders; - - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -import org.alfresco.repo.bulkimport.MetadataLoader; -import org.alfresco.repo.bulkimport.impl.FileUtils; -import org.alfresco.service.ServiceRegistry; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - - -/** - * MetadataLoader that loads metadata from an (optional) "shadow" properties - * file in XML format. This shadow properties file must have exactly - * the same name and extension as the file for whom it is storing metadata, but - * with the suffix ".metadata.properties.xml". So for example, if there is a file - * called "IMG_1967.jpg", the "shadow" metadata file for it would be called - * "IMG_1967.jpg.metadata.properties.xml". - * - * The metadata file itself follows the usual rules for Java properties XML - * files, with a property with the key "type" containing the qualified name of - * the content type to use for the file, a property with the key "aspects" - * containing a comma-delimited list of qualified names of the aspects to - * attach to the file, and then one Java property per metadata property, with - * the key being the Alfresco property QName and the value being the value of - * that property. - * - * For example (note escaping rules for namespace separator!): - * - * - * - * - * - * Metadata for IMG_1967.jpg - * cm:content - * cm:versionable, custom:myAspect - * This is the value of the cm:title field. - * This is the value of the cm:description field. - * workspace://SpacesStore/3da6c395-3a4b-4a57-836d-8e5 - * This is the value of the custom:myProperty field. - * 2001-01-01T12:00:00.000+01:00 - * - * - * - * Notes: - *
    - *
  • Java XML properties files fully support Unicode characters (unlike the - * original properties file format), so use of this class is strongly - * recommended over and PropertiesFileMetadataLoader.
  • - *
  • the metadata must conform to the type and aspect definitions - * configured in Alfresco (including mandatory fields, constraints and data - * types). Any violations will terminate the bulk import process.
  • - *
  • associations are not yet supported
  • - *
  • dates, times and date times must be stored in ISO8601 format - * (although note that Alfresco ignores timezone modifiers)
  • - *
- * - * @since 4.0 - * - * @see MetadataLoader - */ -public final class XmlPropertiesFileMetadataLoader extends AbstractMapBasedMetadataLoader -{ - private final static Log log = LogFactory.getLog(XmlPropertiesFileMetadataLoader.class); - private final static String METADATA_FILE_EXTENSION = "properties.xml"; - - public XmlPropertiesFileMetadataLoader(final ServiceRegistry serviceRegistry) - { - super(serviceRegistry, METADATA_FILE_EXTENSION); - } - - public XmlPropertiesFileMetadataLoader(final ServiceRegistry serviceRegistry, final String multiValuedSeparator) - { - super(serviceRegistry, multiValuedSeparator, METADATA_FILE_EXTENSION); - } - - /** - * @see AbstractMapBasedMetadataLoader#loadMetadataFromFile(java.io.File) - */ - @Override - protected Map loadMetadataFromFile(File metadataFile) - { - Map result = null; - - try - { - Properties props = new Properties(); - props.loadFromXML(new BufferedInputStream(new FileInputStream(metadataFile))); - result = new HashMap((Map)props); - } - catch (final IOException ioe) - { - if (log.isWarnEnabled()) log.warn("Metadata file '" + FileUtils.getFileName(metadataFile) + "' could not be read.", ioe); - } - - return(result); - } - -} + +package org.alfresco.repo.bulkimport.metadataloaders; + + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.alfresco.repo.bulkimport.MetadataLoader; +import org.alfresco.repo.bulkimport.impl.FileUtils; +import org.alfresco.service.ServiceRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * MetadataLoader that loads metadata from an (optional) "shadow" properties + * file in XML format. This shadow properties file must have exactly + * the same name and extension as the file for whom it is storing metadata, but + * with the suffix ".metadata.properties.xml". So for example, if there is a file + * called "IMG_1967.jpg", the "shadow" metadata file for it would be called + * "IMG_1967.jpg.metadata.properties.xml". + * + * The metadata file itself follows the usual rules for Java properties XML + * files, with a property with the key "type" containing the qualified name of + * the content type to use for the file, a property with the key "aspects" + * containing a comma-delimited list of qualified names of the aspects to + * attach to the file, and then one Java property per metadata property, with + * the key being the Alfresco property QName and the value being the value of + * that property. + * + * For example (note escaping rules for namespace separator!): + * + * + * + * + * + * Metadata for IMG_1967.jpg + * cm:content + * cm:versionable, custom:myAspect + * This is the value of the cm:title field. + * This is the value of the cm:description field. + * workspace://SpacesStore/3da6c395-3a4b-4a57-836d-8e5 + * This is the value of the custom:myProperty field. + * 2001-01-01T12:00:00.000+01:00 + * + * + * + * Notes: + *
    + *
  • Java XML properties files fully support Unicode characters (unlike the + * original properties file format), so use of this class is strongly + * recommended over and PropertiesFileMetadataLoader.
  • + *
  • the metadata must conform to the type and aspect definitions + * configured in Alfresco (including mandatory fields, constraints and data + * types). Any violations will terminate the bulk import process.
  • + *
  • associations are not yet supported
  • + *
  • dates, times and date times must be stored in ISO8601 format + * (although note that Alfresco ignores timezone modifiers)
  • + *
+ * + * @since 4.0 + * + * @see MetadataLoader + */ +public final class XmlPropertiesFileMetadataLoader extends AbstractMapBasedMetadataLoader +{ + private final static Log log = LogFactory.getLog(XmlPropertiesFileMetadataLoader.class); + private final static String METADATA_FILE_EXTENSION = "properties.xml"; + + public XmlPropertiesFileMetadataLoader(final ServiceRegistry serviceRegistry) + { + super(serviceRegistry, METADATA_FILE_EXTENSION); + } + + public XmlPropertiesFileMetadataLoader(final ServiceRegistry serviceRegistry, final String multiValuedSeparator) + { + super(serviceRegistry, multiValuedSeparator, METADATA_FILE_EXTENSION); + } + + /** + * @see AbstractMapBasedMetadataLoader#loadMetadataFromFile(java.io.File) + */ + @Override + protected Map loadMetadataFromFile(File metadataFile) + { + Map result = null; + + try + { + Properties props = new Properties(); + props.loadFromXML(new BufferedInputStream(new FileInputStream(metadataFile))); + result = new HashMap((Map)props); + } + catch (final IOException ioe) + { + if (log.isWarnEnabled()) log.warn("Metadata file '" + FileUtils.getFileName(metadataFile) + "' could not be read.", ioe); + } + + return(result); + } + +} diff --git a/source/java/org/alfresco/repo/bulkimport/script/BulkImport.java b/source/java/org/alfresco/repo/bulkimport/script/BulkImport.java index 455261f0bc..6001061186 100644 --- a/source/java/org/alfresco/repo/bulkimport/script/BulkImport.java +++ b/source/java/org/alfresco/repo/bulkimport/script/BulkImport.java @@ -1,46 +1,46 @@ - -package org.alfresco.repo.bulkimport.script; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import org.alfresco.repo.bulkimport.ContentStoreMapProvider; -import org.alfresco.repo.processor.BaseProcessorExtension; - -/** - * Custom javascript root object to provide access to the {@link org.alfresco.repo.bulkimport.BulkFilesystemImporter} from scripts. - * - * @since 4.0 - * - */ -public class BulkImport extends BaseProcessorExtension -{ - private ContentStoreMapProvider storeMapProvider; - private volatile List storeNamesList; - - public void setStoreMapProvider(ContentStoreMapProvider storeMapProvider) - { - this.storeMapProvider = storeMapProvider; - } - - /** - * Get a list of the currently registered content stores, from the configured {@link ContentStoreMapProvider}. - * @return the {@link List} of store names - */ - public List getStoreNames() - { - if(storeNamesList == null) - { - synchronized(this) - { - Set storeNamesSet = storeMapProvider.getStoreMap().keySet(); - if(storeNamesList == null) - storeNamesList = Collections.unmodifiableList(new ArrayList(storeNamesSet)); - } - - } - return storeNamesList; - } -} + +package org.alfresco.repo.bulkimport.script; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.bulkimport.ContentStoreMapProvider; +import org.alfresco.repo.processor.BaseProcessorExtension; + +/** + * Custom javascript root object to provide access to the {@link org.alfresco.repo.bulkimport.BulkFilesystemImporter} from scripts. + * + * @since 4.0 + * + */ +public class BulkImport extends BaseProcessorExtension +{ + private ContentStoreMapProvider storeMapProvider; + private volatile List storeNamesList; + + public void setStoreMapProvider(ContentStoreMapProvider storeMapProvider) + { + this.storeMapProvider = storeMapProvider; + } + + /** + * Get a list of the currently registered content stores, from the configured {@link ContentStoreMapProvider}. + * @return the {@link List} of store names + */ + public List getStoreNames() + { + if(storeNamesList == null) + { + synchronized(this) + { + Set storeNamesSet = storeMapProvider.getStoreMap().keySet(); + if(storeNamesList == null) + storeNamesList = Collections.unmodifiableList(new ArrayList(storeNamesSet)); + } + + } + return storeNamesList; + } +} diff --git a/source/java/org/alfresco/repo/cache/AbstractMTAsynchronouslyRefreshedCache.java b/source/java/org/alfresco/repo/cache/AbstractMTAsynchronouslyRefreshedCache.java index 74bffa15bf..a1be414702 100644 --- a/source/java/org/alfresco/repo/cache/AbstractMTAsynchronouslyRefreshedCache.java +++ b/source/java/org/alfresco/repo/cache/AbstractMTAsynchronouslyRefreshedCache.java @@ -1,89 +1,89 @@ -package org.alfresco.repo.cache; - -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.TransactionListener; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.InitializingBean; - -/** - * The base implementation for Multi-tenant asynchronously refreshed cache. Currently supports one value per tenant. - * - * Implementors just need to provide buildCache(String tennantId) - * - * @author Andy - * @since 4.1.3 - */ -public abstract class AbstractMTAsynchronouslyRefreshedCache - extends org.alfresco.util.cache.AbstractAsynchronouslyRefreshedCache - implements AsynchronouslyRefreshedCache, InitializingBean -{ - - private static Log logger = LogFactory.getLog(AbstractMTAsynchronouslyRefreshedCache.class); - - private TenantService tenantService; - - /** - * @param tenantService - * the tenantService to set - */ - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - - @Override - public T get() - { - String tenantId = tenantService.getCurrentUserDomain(); - return get(tenantId); - } - - public void forceInChangesForThisUncommittedTransaction() - { - String tenantId = tenantService.getCurrentUserDomain(); - forceInChangesForThisUncommittedTransaction(tenantId); - } - - @Override - public void refresh() - { - String tenantId = tenantService.getCurrentUserDomain(); - refresh(tenantId); - } - - - @Override - public boolean isUpToDate() - { - String tenantId = tenantService.getCurrentUserDomain(); - return isUpToDate(tenantId); - } - - /** - * Build the cache entry for the specific tenant. - * This method is called in a thread-safe manner i.e. it is only ever called by a single - * thread. - */ - protected abstract T buildCache(String tenantId); - - - @Override - public void afterPropertiesSet() throws Exception - { - PropertyCheck.mandatory(this, "tenantService", tenantService); - super.afterPropertiesSet(); - } -} +package org.alfresco.repo.cache; + +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionListener; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.InitializingBean; + +/** + * The base implementation for Multi-tenant asynchronously refreshed cache. Currently supports one value per tenant. + * + * Implementors just need to provide buildCache(String tennantId) + * + * @author Andy + * @since 4.1.3 + */ +public abstract class AbstractMTAsynchronouslyRefreshedCache + extends org.alfresco.util.cache.AbstractAsynchronouslyRefreshedCache + implements AsynchronouslyRefreshedCache, InitializingBean +{ + + private static Log logger = LogFactory.getLog(AbstractMTAsynchronouslyRefreshedCache.class); + + private TenantService tenantService; + + /** + * @param tenantService + * the tenantService to set + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + + @Override + public T get() + { + String tenantId = tenantService.getCurrentUserDomain(); + return get(tenantId); + } + + public void forceInChangesForThisUncommittedTransaction() + { + String tenantId = tenantService.getCurrentUserDomain(); + forceInChangesForThisUncommittedTransaction(tenantId); + } + + @Override + public void refresh() + { + String tenantId = tenantService.getCurrentUserDomain(); + refresh(tenantId); + } + + + @Override + public boolean isUpToDate() + { + String tenantId = tenantService.getCurrentUserDomain(); + return isUpToDate(tenantId); + } + + /** + * Build the cache entry for the specific tenant. + * This method is called in a thread-safe manner i.e. it is only ever called by a single + * thread. + */ + protected abstract T buildCache(String tenantId); + + + @Override + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "tenantService", tenantService); + super.afterPropertiesSet(); + } +} diff --git a/source/java/org/alfresco/repo/cache/AbstractRefreshableCacheEvent.java b/source/java/org/alfresco/repo/cache/AbstractRefreshableCacheEvent.java index c3d9073d88..2583f399fd 100644 --- a/source/java/org/alfresco/repo/cache/AbstractRefreshableCacheEvent.java +++ b/source/java/org/alfresco/repo/cache/AbstractRefreshableCacheEvent.java @@ -1,68 +1,68 @@ -package org.alfresco.repo.cache; - -/** - * A generic event with the cache id and affected tenant - * - * @author Andy - */ -public abstract class AbstractRefreshableCacheEvent implements RefreshableCacheEvent -{ - private static final long serialVersionUID = 1324638640132648062L; - - private String cacheId; - private String tenantId; - - AbstractRefreshableCacheEvent(String cacheId, String tenantId) - { - this.cacheId = cacheId; - this.tenantId = tenantId; - } - - @Override - public String getCacheId() - { - return cacheId; - } - - @Override - public String getTenantId() - { - return tenantId; - } - - @Override - public String toString() - { - return "AbstractRefreshableCacheEvent [cacheId=" + cacheId + ", tenantId=" + tenantId + "]"; - } - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = prime * result + ((cacheId == null) ? 0 : cacheId.hashCode()); - result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - AbstractRefreshableCacheEvent other = (AbstractRefreshableCacheEvent) obj; - if (cacheId == null) - { - if (other.cacheId != null) return false; - } - else if (!cacheId.equals(other.cacheId)) return false; - if (tenantId == null) - { - if (other.tenantId != null) return false; - } - else if (!tenantId.equals(other.tenantId)) return false; - return true; - } -} +package org.alfresco.repo.cache; + +/** + * A generic event with the cache id and affected tenant + * + * @author Andy + */ +public abstract class AbstractRefreshableCacheEvent implements RefreshableCacheEvent +{ + private static final long serialVersionUID = 1324638640132648062L; + + private String cacheId; + private String tenantId; + + AbstractRefreshableCacheEvent(String cacheId, String tenantId) + { + this.cacheId = cacheId; + this.tenantId = tenantId; + } + + @Override + public String getCacheId() + { + return cacheId; + } + + @Override + public String getTenantId() + { + return tenantId; + } + + @Override + public String toString() + { + return "AbstractRefreshableCacheEvent [cacheId=" + cacheId + ", tenantId=" + tenantId + "]"; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((cacheId == null) ? 0 : cacheId.hashCode()); + result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + AbstractRefreshableCacheEvent other = (AbstractRefreshableCacheEvent) obj; + if (cacheId == null) + { + if (other.cacheId != null) return false; + } + else if (!cacheId.equals(other.cacheId)) return false; + if (tenantId == null) + { + if (other.tenantId != null) return false; + } + else if (!tenantId.equals(other.tenantId)) return false; + return true; + } +} diff --git a/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCache.java b/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCache.java index b960d11771..5a3b37ca1b 100644 --- a/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCache.java +++ b/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCache.java @@ -1,24 +1,24 @@ -package org.alfresco.repo.cache; - -/** - * Implementation details in addition to the exposed interface. - * - * @author Andy - * @since 4.1.3 - */ -public interface AsynchronouslyRefreshedCache extends RefreshableCache -{ - /** - * Get the cache id - * - * @return the cache ID - */ - String getCacheId(); - - /** - * Determine if the cache is up to date - * - * @return true if the cache is not currently refreshing itself - */ - boolean isUpToDate(); -} +package org.alfresco.repo.cache; + +/** + * Implementation details in addition to the exposed interface. + * + * @author Andy + * @since 4.1.3 + */ +public interface AsynchronouslyRefreshedCache extends RefreshableCache +{ + /** + * Get the cache id + * + * @return the cache ID + */ + String getCacheId(); + + /** + * Determine if the cache is up to date + * + * @return true if the cache is not currently refreshing itself + */ + boolean isUpToDate(); +} diff --git a/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCacheRegistry.java b/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCacheRegistry.java index 252b5bdeb0..69da4ce177 100644 --- a/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCacheRegistry.java +++ b/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCacheRegistry.java @@ -1,23 +1,23 @@ -package org.alfresco.repo.cache; - -/** - * A registry of all AsynchronouslyRefreshedCaches to be used for notification. - * - * @author Andy - * - */ -public interface AsynchronouslyRefreshedCacheRegistry -{ - /** - * Register a listener - * @param listener RefreshableCacheListener - */ - public void register(RefreshableCacheListener listener); - - /** - * Fire an even - * @param event RefreshableCacheEvent - * @param toAll - true goes to all listeners, false only to listeners that have a matching cacheId - */ - public void broadcastEvent(RefreshableCacheEvent event, boolean toAll); -} +package org.alfresco.repo.cache; + +/** + * A registry of all AsynchronouslyRefreshedCaches to be used for notification. + * + * @author Andy + * + */ +public interface AsynchronouslyRefreshedCacheRegistry +{ + /** + * Register a listener + * @param listener RefreshableCacheListener + */ + public void register(RefreshableCacheListener listener); + + /** + * Fire an even + * @param event RefreshableCacheEvent + * @param toAll - true goes to all listeners, false only to listeners that have a matching cacheId + */ + public void broadcastEvent(RefreshableCacheEvent event, boolean toAll); +} diff --git a/source/java/org/alfresco/repo/cache/RefreshableCache.java b/source/java/org/alfresco/repo/cache/RefreshableCache.java index 015a483333..5b1c7a936e 100644 --- a/source/java/org/alfresco/repo/cache/RefreshableCache.java +++ b/source/java/org/alfresco/repo/cache/RefreshableCache.java @@ -1,33 +1,33 @@ -package org.alfresco.repo.cache; - -/** - * Basic cache API - * - * @author Andy - * - */ -public interface RefreshableCache -{ - /** - * Get the cache. - * If there is no cache value this call will block. - * If the underlying cache is being refreshed, the old cache value will be returned until the refresh is complete. - * - * @return T - */ - public T get(); - - /** - * Refresh the cache asynchronously. - */ - public void refresh(); - -// /** -// * Register to be informed when the cache is updated in the background. -// * -// * Note: it is up to the implementation to provide any transactional wrapping. -// * Transactional wrapping is not required to invalidate a shared cache entry directly via a transactional cache -// * @param listener -// */ -// void register(RefreshableCacheListener listener); -} +package org.alfresco.repo.cache; + +/** + * Basic cache API + * + * @author Andy + * + */ +public interface RefreshableCache +{ + /** + * Get the cache. + * If there is no cache value this call will block. + * If the underlying cache is being refreshed, the old cache value will be returned until the refresh is complete. + * + * @return T + */ + public T get(); + + /** + * Refresh the cache asynchronously. + */ + public void refresh(); + +// /** +// * Register to be informed when the cache is updated in the background. +// * +// * Note: it is up to the implementation to provide any transactional wrapping. +// * Transactional wrapping is not required to invalidate a shared cache entry directly via a transactional cache +// * @param listener +// */ +// void register(RefreshableCacheListener listener); +} diff --git a/source/java/org/alfresco/repo/cache/RefreshableCacheEvent.java b/source/java/org/alfresco/repo/cache/RefreshableCacheEvent.java index 10ce8286e3..26a977ad5b 100644 --- a/source/java/org/alfresco/repo/cache/RefreshableCacheEvent.java +++ b/source/java/org/alfresco/repo/cache/RefreshableCacheEvent.java @@ -1,22 +1,22 @@ -package org.alfresco.repo.cache; - -import java.io.Serializable; - -/** - * A cache event - * - * @author Andy - * - */ -public interface RefreshableCacheEvent extends Serializable -{ - /** - * Get the cache id - */ - public String getCacheId(); - - /** - * Get the affected tenant id - */ - public String getTenantId(); -} +package org.alfresco.repo.cache; + +import java.io.Serializable; + +/** + * A cache event + * + * @author Andy + * + */ +public interface RefreshableCacheEvent extends Serializable +{ + /** + * Get the cache id + */ + public String getCacheId(); + + /** + * Get the affected tenant id + */ + public String getTenantId(); +} diff --git a/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java b/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java index a6935c9092..f54570184a 100644 --- a/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java +++ b/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java @@ -1,11 +1,11 @@ -package org.alfresco.repo.cache; - -/** - * API to listen to cache events - * - * @author Andy - */ -public interface RefreshableCacheListener extends org.alfresco.util.cache.RefreshableCacheListener -{ - -} +package org.alfresco.repo.cache; + +/** + * API to listen to cache events + * + * @author Andy + */ +public interface RefreshableCacheListener extends org.alfresco.util.cache.RefreshableCacheListener +{ + +} diff --git a/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshEvent.java b/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshEvent.java index b219c05e3e..64aa7d9eb6 100644 --- a/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshEvent.java +++ b/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshEvent.java @@ -1,26 +1,26 @@ -package org.alfresco.repo.cache; - -/** - * Describes an entry that is stale in the cache - * - * @author Andy - * - */ -public class RefreshableCacheRefreshEvent extends AbstractRefreshableCacheEvent -{ - /** - * - * @param cacheId String - * @param tenantId String - */ - RefreshableCacheRefreshEvent(String cacheId, String tenantId) - { - super(cacheId, tenantId); - } - - /** - * - */ - private static final long serialVersionUID = -8011932788039835334L; - -} +package org.alfresco.repo.cache; + +/** + * Describes an entry that is stale in the cache + * + * @author Andy + * + */ +public class RefreshableCacheRefreshEvent extends AbstractRefreshableCacheEvent +{ + /** + * + * @param cacheId String + * @param tenantId String + */ + RefreshableCacheRefreshEvent(String cacheId, String tenantId) + { + super(cacheId, tenantId); + } + + /** + * + */ + private static final long serialVersionUID = -8011932788039835334L; + +} diff --git a/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshedEvent.java b/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshedEvent.java index 4799c52f2f..412db3307f 100644 --- a/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshedEvent.java +++ b/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshedEvent.java @@ -1,26 +1,26 @@ -package org.alfresco.repo.cache; - -/** - * Describes a new entry has been inserted in the cache. - * - * @author Andy - * - */ -public class RefreshableCacheRefreshedEvent extends AbstractRefreshableCacheEvent -{ - - /** - * - */ - private static final long serialVersionUID = 2352511592269578075L; - - /** - * @param cacheId String - * @param tenantId String - */ - RefreshableCacheRefreshedEvent(String cacheId, String tenantId) - { - super(cacheId, tenantId); - } - -} +package org.alfresco.repo.cache; + +/** + * Describes a new entry has been inserted in the cache. + * + * @author Andy + * + */ +public class RefreshableCacheRefreshedEvent extends AbstractRefreshableCacheEvent +{ + + /** + * + */ + private static final long serialVersionUID = 2352511592269578075L; + + /** + * @param cacheId String + * @param tenantId String + */ + RefreshableCacheRefreshedEvent(String cacheId, String tenantId) + { + super(cacheId, tenantId); + } + +} diff --git a/source/java/org/alfresco/repo/coci/CheckedOutAspect.java b/source/java/org/alfresco/repo/coci/CheckedOutAspect.java index 95ad2bd3b7..f34d95dfcc 100644 --- a/source/java/org/alfresco/repo/coci/CheckedOutAspect.java +++ b/source/java/org/alfresco/repo/coci/CheckedOutAspect.java @@ -1,70 +1,70 @@ -package org.alfresco.repo.coci; - -import java.io.Serializable; -import java.util.Map; - -import org.alfresco.model.ContentModel; -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.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; -import org.alfresco.repo.version.VersionServicePolicies; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; - -/** - * Policies relating to the checkedOut aspect. - * - * @since 4.0 - * - */ -public class CheckedOutAspect -{ - /** - * Policy component - */ - protected PolicyComponent policyComponent; - - /** - * Sets the policy component - * - * @param policyComponent the policy component - */ - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public void init() - { - this.policyComponent.bindClassBehaviour( - CopyServicePolicies.OnCopyNodePolicy.QNAME, - ContentModel.ASPECT_CHECKED_OUT, - new JavaBehaviour(this, "getCallbackForCheckedOutAspect")); - - this.policyComponent.bindClassBehaviour(VersionServicePolicies.OnCreateVersionPolicy.QNAME, - ContentModel.ASPECT_CHECKED_OUT, - new JavaBehaviour(this, "onCreateVersion")); - } - - /** - * Callback behaviour retrieval for the 'onCreateVersion' aspect. - */ - public void onCreateVersion(QName classRef, NodeRef versionableNode, Map versionProperties, PolicyScope nodeDetails) - { - nodeDetails.getAspects().remove(ContentModel.ASPECT_CHECKED_OUT); - } - - /** - * Callback behaviour retrieval for the 'checkedOut' aspect. - * - * @return Returns {@link DoNothingCopyBehaviourCallback} always - */ - public CopyBehaviourCallback getCallbackForCheckedOutAspect(QName classRef, CopyDetails copyDetails) - { - return DoNothingCopyBehaviourCallback.getInstance(); - } -} +package org.alfresco.repo.coci; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.model.ContentModel; +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.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.policy.PolicyScope; +import org.alfresco.repo.version.VersionServicePolicies; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * Policies relating to the checkedOut aspect. + * + * @since 4.0 + * + */ +public class CheckedOutAspect +{ + /** + * Policy component + */ + protected PolicyComponent policyComponent; + + /** + * Sets the policy component + * + * @param policyComponent the policy component + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void init() + { + this.policyComponent.bindClassBehaviour( + CopyServicePolicies.OnCopyNodePolicy.QNAME, + ContentModel.ASPECT_CHECKED_OUT, + new JavaBehaviour(this, "getCallbackForCheckedOutAspect")); + + this.policyComponent.bindClassBehaviour(VersionServicePolicies.OnCreateVersionPolicy.QNAME, + ContentModel.ASPECT_CHECKED_OUT, + new JavaBehaviour(this, "onCreateVersion")); + } + + /** + * Callback behaviour retrieval for the 'onCreateVersion' aspect. + */ + public void onCreateVersion(QName classRef, NodeRef versionableNode, Map versionProperties, PolicyScope nodeDetails) + { + nodeDetails.getAspects().remove(ContentModel.ASPECT_CHECKED_OUT); + } + + /** + * Callback behaviour retrieval for the 'checkedOut' aspect. + * + * @return Returns {@link DoNothingCopyBehaviourCallback} always + */ + public CopyBehaviourCallback getCallbackForCheckedOutAspect(QName classRef, CopyDetails copyDetails) + { + return DoNothingCopyBehaviourCallback.getInstance(); + } +} diff --git a/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java b/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java index 6064bb10cd..0e945b7a02 100644 --- a/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java +++ b/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java @@ -1,264 +1,264 @@ - -package org.alfresco.repo.coci; - -import java.io.Serializable; -import java.util.Collections; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.copy.CopyBehaviourCallback; -import org.alfresco.repo.copy.CopyDetails; -import org.alfresco.repo.copy.CopyServicePolicies; -import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; -import org.alfresco.repo.node.NodeServicePolicies; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.GUID; - -public class WorkingCopyAspect implements CopyServicePolicies.OnCopyNodePolicy, NodeServicePolicies.OnRemoveAspectPolicy -{ - private PolicyComponent policyComponent; - private NodeService nodeService; - private LockService lockService; - private CheckOutCheckInService checkOutCheckInService; - private BehaviourFilter policyBehaviourFilter; - - - /** - * The working copy aspect copy behaviour callback. - */ - private WorkingCopyAspectCopyBehaviourCallback workingCopyAspectCopyBehaviourCallback = new WorkingCopyAspectCopyBehaviourCallback(); - - /** - * Sets the policy component - */ - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - /** - * Set the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the lock service - */ - public void setLockService(LockService lockService) - { - this.lockService = lockService; - } - - /** - * @param checkOutCheckInService the service dealing with working copies - */ - public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService) - { - this.checkOutCheckInService = checkOutCheckInService; - } - - /** - * @param policyBehaviourFilter BehaviourFilter - */ - public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter) - { - this.policyBehaviourFilter = policyBehaviourFilter; - } - - /** - * Initialise method - */ - public void init() - { - // Register copy behaviour for the working copy aspect - this.policyComponent.bindClassBehaviour( - CopyServicePolicies.OnCopyNodePolicy.QNAME, - ContentModel.TYPE_CMOBJECT, - new JavaBehaviour(this, "getCopyCallback")); - this.policyComponent.bindClassBehaviour( - CopyServicePolicies.OnCopyNodePolicy.QNAME, - ContentModel.ASPECT_WORKING_COPY, - new JavaBehaviour(this, "getCopyCallback")); - this.policyComponent.bindClassBehaviour( - CopyServicePolicies.OnCopyNodePolicy.QNAME, - ContentModel.ASPECT_CHECKED_OUT, - new JavaBehaviour(this, "getCopyCallback")); - - // register onBeforeDelete class behaviour for the working copy aspect - this.policyComponent.bindClassBehaviour( - NodeServicePolicies.BeforeDeleteNodePolicy.QNAME, - ContentModel.ASPECT_WORKING_COPY, - new JavaBehaviour(this, "beforeDeleteWorkingCopy")); - - // Watch for removal of the aspect and ensure that the cm:workingcopylink assoc is removed - this.policyComponent.bindClassBehaviour( - NodeServicePolicies.OnRemoveAspectPolicy.QNAME, - ContentModel.ASPECT_WORKING_COPY, - new JavaBehaviour(this, "onRemoveAspect")); - - this.policyComponent.bindAssociationBehaviour( - NodeServicePolicies.OnDeleteAssociationPolicy.QNAME, - ContentModel.ASPECT_CMIS_CREATED_CHECKEDOUT, - ContentModel.ASSOC_WORKING_COPY_LINK, - new JavaBehaviour(this, "onDeleteCmisCreatedCheckoutWorkingCopyAssociation")); - } - - /** - * beforeDeleteNode policy behaviour - * - * @param nodeRef the node reference about to be deleted - */ - public void beforeDeleteWorkingCopy(NodeRef nodeRef) - { - NodeRef checkedOutNodeRef = checkOutCheckInService.getCheckedOut(nodeRef); - if (checkedOutNodeRef != null) - { - policyBehaviourFilter.disableBehaviour(checkedOutNodeRef, ContentModel.ASPECT_AUDITABLE); - try - { - lockService.unlock(checkedOutNodeRef, false, true); - nodeService.removeAspect(checkedOutNodeRef, ContentModel.ASPECT_CHECKED_OUT); - - } - finally - { - policyBehaviourFilter.enableBehaviour(checkedOutNodeRef, ContentModel.ASPECT_AUDITABLE); - } - } - } - - /** - * onDeleteAssociation policy behaviour If the node has the aspect ASPECT_CMIS_CREATED_CHECKEDOUT and ASSOC_WORKING_COPY_LINK association is deleted, delete the node. Fix for MNT-14850. - * - * @param nodeAssocRef ASSOC_WORKING_COPY_LINK association where the source is the checkedOut node and the target is the workingCopy - */ - public void onDeleteCmisCreatedCheckoutWorkingCopyAssociation(AssociationRef nodeAssocRef) - { - NodeRef checkedOutNodeRef = nodeAssocRef.getSourceRef(); - policyBehaviourFilter.disableBehaviour(checkedOutNodeRef, ContentModel.ASPECT_AUDITABLE); - try - { - - nodeService.deleteNode(checkedOutNodeRef); - } - finally - { - policyBehaviourFilter.enableBehaviour(checkedOutNodeRef, ContentModel.ASPECT_AUDITABLE); - } - - } - - @Override - public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) - { - // This is simply not allowed. - throw new UnsupportedOperationException("Use CheckOutCheckInservice to manipulate working copies."); - } - - /** - * @return Returns CopyBehaviourCallback - */ - public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) - { - return this.workingCopyAspectCopyBehaviourCallback; - } - - /** - * Dual behaviour to ensure that cm:name is not copied if the source node has the - * cm:workingCopy aspect, and to prevent the cm:workingCopy aspect from - * being carried to the new node. - * - * @author Derek Hulley - * @since 3.2 - */ - private class WorkingCopyAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback - { - /** - * Disallows copying of the {@link ContentModel#ASPECT_WORKING_COPY cm:workingCopy} aspect. - */ - @Override - public boolean getMustCopy(QName classQName, CopyDetails copyDetails) - { - if (classQName.equals(ContentModel.ASPECT_WORKING_COPY)) - { - return false; - } - else - { - return true; - } - } - - /** - * Allows copy of workingCopy top-level node to be renamed - */ - @Override - public boolean isTopLevelCanBeRenamed(QName classQName, CopyDetails copyDetails) - { - return true; - } - - /** - * Prevents copying off the {@link ContentModel#PROP_NAME cm:name} property. - */ - @Override - public Map getCopyProperties( - QName classQName, - CopyDetails copyDetails, - Map properties) - { - if (classQName.equals(ContentModel.ASPECT_WORKING_COPY)) - { - return Collections.emptyMap(); - } - else if (copyDetails.getSourceNodeAspectQNames().contains(ContentModel.ASPECT_WORKING_COPY)) - { - // Generate a new name for a new copy of a working copy - String newName = null; - - if (copyDetails.isTargetNodeIsNew()) - { - // This is a copy of a working copy to a new node (not a check in). Try to derive a new name from the - // node it is checked out from - NodeRef checkedOutFrom = checkOutCheckInService.getCheckedOut(copyDetails.getSourceNodeRef()); - if (checkedOutFrom != null) - { - String oldName = (String) nodeService.getProperty(checkedOutFrom, ContentModel.PROP_NAME); - int extIndex = oldName.lastIndexOf('.'); - newName = extIndex == -1 ? oldName + "_" + GUID.generate() : oldName.substring(0, extIndex) - + "_" + GUID.generate() + oldName.substring(extIndex); - } - } - else - { - // This is a check-in i.e. a copy to an existing node, so keep a null cm:name - } - - if (newName == null) - { - properties.remove(ContentModel.PROP_NAME); - } - else - { - properties.put(ContentModel.PROP_NAME, newName); - } - return properties; - } - else - { - return properties; - } - } - } -} + +package org.alfresco.repo.coci; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; + +public class WorkingCopyAspect implements CopyServicePolicies.OnCopyNodePolicy, NodeServicePolicies.OnRemoveAspectPolicy +{ + private PolicyComponent policyComponent; + private NodeService nodeService; + private LockService lockService; + private CheckOutCheckInService checkOutCheckInService; + private BehaviourFilter policyBehaviourFilter; + + + /** + * The working copy aspect copy behaviour callback. + */ + private WorkingCopyAspectCopyBehaviourCallback workingCopyAspectCopyBehaviourCallback = new WorkingCopyAspectCopyBehaviourCallback(); + + /** + * Sets the policy component + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Set the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the lock service + */ + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + + /** + * @param checkOutCheckInService the service dealing with working copies + */ + public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService) + { + this.checkOutCheckInService = checkOutCheckInService; + } + + /** + * @param policyBehaviourFilter BehaviourFilter + */ + public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter) + { + this.policyBehaviourFilter = policyBehaviourFilter; + } + + /** + * Initialise method + */ + public void init() + { + // Register copy behaviour for the working copy aspect + this.policyComponent.bindClassBehaviour( + CopyServicePolicies.OnCopyNodePolicy.QNAME, + ContentModel.TYPE_CMOBJECT, + new JavaBehaviour(this, "getCopyCallback")); + this.policyComponent.bindClassBehaviour( + CopyServicePolicies.OnCopyNodePolicy.QNAME, + ContentModel.ASPECT_WORKING_COPY, + new JavaBehaviour(this, "getCopyCallback")); + this.policyComponent.bindClassBehaviour( + CopyServicePolicies.OnCopyNodePolicy.QNAME, + ContentModel.ASPECT_CHECKED_OUT, + new JavaBehaviour(this, "getCopyCallback")); + + // register onBeforeDelete class behaviour for the working copy aspect + this.policyComponent.bindClassBehaviour( + NodeServicePolicies.BeforeDeleteNodePolicy.QNAME, + ContentModel.ASPECT_WORKING_COPY, + new JavaBehaviour(this, "beforeDeleteWorkingCopy")); + + // Watch for removal of the aspect and ensure that the cm:workingcopylink assoc is removed + this.policyComponent.bindClassBehaviour( + NodeServicePolicies.OnRemoveAspectPolicy.QNAME, + ContentModel.ASPECT_WORKING_COPY, + new JavaBehaviour(this, "onRemoveAspect")); + + this.policyComponent.bindAssociationBehaviour( + NodeServicePolicies.OnDeleteAssociationPolicy.QNAME, + ContentModel.ASPECT_CMIS_CREATED_CHECKEDOUT, + ContentModel.ASSOC_WORKING_COPY_LINK, + new JavaBehaviour(this, "onDeleteCmisCreatedCheckoutWorkingCopyAssociation")); + } + + /** + * beforeDeleteNode policy behaviour + * + * @param nodeRef the node reference about to be deleted + */ + public void beforeDeleteWorkingCopy(NodeRef nodeRef) + { + NodeRef checkedOutNodeRef = checkOutCheckInService.getCheckedOut(nodeRef); + if (checkedOutNodeRef != null) + { + policyBehaviourFilter.disableBehaviour(checkedOutNodeRef, ContentModel.ASPECT_AUDITABLE); + try + { + lockService.unlock(checkedOutNodeRef, false, true); + nodeService.removeAspect(checkedOutNodeRef, ContentModel.ASPECT_CHECKED_OUT); + + } + finally + { + policyBehaviourFilter.enableBehaviour(checkedOutNodeRef, ContentModel.ASPECT_AUDITABLE); + } + } + } + + /** + * onDeleteAssociation policy behaviour If the node has the aspect ASPECT_CMIS_CREATED_CHECKEDOUT and ASSOC_WORKING_COPY_LINK association is deleted, delete the node. Fix for MNT-14850. + * + * @param nodeAssocRef ASSOC_WORKING_COPY_LINK association where the source is the checkedOut node and the target is the workingCopy + */ + public void onDeleteCmisCreatedCheckoutWorkingCopyAssociation(AssociationRef nodeAssocRef) + { + NodeRef checkedOutNodeRef = nodeAssocRef.getSourceRef(); + policyBehaviourFilter.disableBehaviour(checkedOutNodeRef, ContentModel.ASPECT_AUDITABLE); + try + { + + nodeService.deleteNode(checkedOutNodeRef); + } + finally + { + policyBehaviourFilter.enableBehaviour(checkedOutNodeRef, ContentModel.ASPECT_AUDITABLE); + } + + } + + @Override + public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) + { + // This is simply not allowed. + throw new UnsupportedOperationException("Use CheckOutCheckInservice to manipulate working copies."); + } + + /** + * @return Returns CopyBehaviourCallback + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return this.workingCopyAspectCopyBehaviourCallback; + } + + /** + * Dual behaviour to ensure that cm:name is not copied if the source node has the + * cm:workingCopy aspect, and to prevent the cm:workingCopy aspect from + * being carried to the new node. + * + * @author Derek Hulley + * @since 3.2 + */ + private class WorkingCopyAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + /** + * Disallows copying of the {@link ContentModel#ASPECT_WORKING_COPY cm:workingCopy} aspect. + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + if (classQName.equals(ContentModel.ASPECT_WORKING_COPY)) + { + return false; + } + else + { + return true; + } + } + + /** + * Allows copy of workingCopy top-level node to be renamed + */ + @Override + public boolean isTopLevelCanBeRenamed(QName classQName, CopyDetails copyDetails) + { + return true; + } + + /** + * Prevents copying off the {@link ContentModel#PROP_NAME cm:name} property. + */ + @Override + public Map getCopyProperties( + QName classQName, + CopyDetails copyDetails, + Map properties) + { + if (classQName.equals(ContentModel.ASPECT_WORKING_COPY)) + { + return Collections.emptyMap(); + } + else if (copyDetails.getSourceNodeAspectQNames().contains(ContentModel.ASPECT_WORKING_COPY)) + { + // Generate a new name for a new copy of a working copy + String newName = null; + + if (copyDetails.isTargetNodeIsNew()) + { + // This is a copy of a working copy to a new node (not a check in). Try to derive a new name from the + // node it is checked out from + NodeRef checkedOutFrom = checkOutCheckInService.getCheckedOut(copyDetails.getSourceNodeRef()); + if (checkedOutFrom != null) + { + String oldName = (String) nodeService.getProperty(checkedOutFrom, ContentModel.PROP_NAME); + int extIndex = oldName.lastIndexOf('.'); + newName = extIndex == -1 ? oldName + "_" + GUID.generate() : oldName.substring(0, extIndex) + + "_" + GUID.generate() + oldName.substring(extIndex); + } + } + else + { + // This is a check-in i.e. a copy to an existing node, so keep a null cm:name + } + + if (newName == null) + { + properties.remove(ContentModel.PROP_NAME); + } + else + { + properties.put(ContentModel.PROP_NAME, newName); + } + return properties; + } + else + { + return properties; + } + } + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/MP3MetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/MP3MetadataExtracter.java index 51b9044826..28b1fd3957 100644 --- a/source/java/org/alfresco/repo/content/metadata/MP3MetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/MP3MetadataExtracter.java @@ -1,85 +1,85 @@ -package org.alfresco.repo.content.metadata; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Map; - -import org.alfresco.repo.content.MimetypeMap; -import org.apache.tika.metadata.Metadata; -import org.apache.tika.metadata.XMPDM; -import org.apache.tika.parser.Parser; -import org.apache.tika.parser.mp3.Mp3Parser; - -/** - * Extracts the following values from MP3 files: - *
- *   songTitle:              --      cm:title
- *   albumTitle:             --      audio:album
- *   artist:                 --      audio:artist, cm:author
- *   description:            --      cm:description
- *   comment:                --      
- *   yearReleased:           --      audio:releaseDate
- *   trackNumber:            --      audio:trackNumber
- *   genre:                  --      audio:genre
- *   composer:               --      audio:composer
- *   lyrics:                 --      
- * 
- * - * Note - XMPDM metadata keys are also emitted, in common with - * the other Tika powered extracters - * - * Uses Apache Tika - * - * @author Nick Burch - */ -public class MP3MetadataExtracter extends TikaAudioMetadataExtracter -{ - private static final String KEY_SONG_TITLE = "songTitle"; - private static final String KEY_ALBUM_TITLE = "albumTitle"; - private static final String KEY_ARTIST = "artist"; - private static final String KEY_COMMENT = "comment"; - private static final String KEY_YEAR_RELEASED = "yearReleased"; - private static final String KEY_TRACK_NUMBER = "trackNumber"; - private static final String KEY_GENRE = "genre"; - private static final String KEY_COMPOSER = "composer"; - - public static ArrayList SUPPORTED_MIMETYPES = buildSupportedMimetypes( - new String[] { MimetypeMap.MIMETYPE_MP3 }, - new Mp3Parser() - ); - - public MP3MetadataExtracter() - { - super(SUPPORTED_MIMETYPES); - } - - @Override - protected Parser getParser() - { - return new Mp3Parser(); - } - - @SuppressWarnings("deprecation") - @Override - protected Map extractSpecific(Metadata metadata, - Map properties, Map headers) - { - // Do the normal Audio mappings - super.extractSpecific(metadata, properties, headers); - - // Now do the compatibility ones - // We only need these for people who had pre-existing mapping - // properties from before the proper audio model was added - putRawValue(KEY_ALBUM_TITLE, metadata.get(XMPDM.ALBUM), properties); - putRawValue(KEY_SONG_TITLE, metadata.get(Metadata.TITLE), properties); - putRawValue(KEY_ARTIST, metadata.get(XMPDM.ARTIST), properties); - putRawValue(KEY_COMMENT, metadata.get(XMPDM.LOG_COMMENT), properties); - putRawValue(KEY_TRACK_NUMBER, metadata.get(XMPDM.TRACK_NUMBER), properties); - putRawValue(KEY_GENRE, metadata.get(XMPDM.GENRE), properties); - putRawValue(KEY_YEAR_RELEASED, metadata.get(XMPDM.RELEASE_DATE), properties); - putRawValue(KEY_COMPOSER, metadata.get(XMPDM.COMPOSER), properties); - - // All done - return properties; - } -} +package org.alfresco.repo.content.metadata; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Map; + +import org.alfresco.repo.content.MimetypeMap; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.XMPDM; +import org.apache.tika.parser.Parser; +import org.apache.tika.parser.mp3.Mp3Parser; + +/** + * Extracts the following values from MP3 files: + *
+ *   songTitle:              --      cm:title
+ *   albumTitle:             --      audio:album
+ *   artist:                 --      audio:artist, cm:author
+ *   description:            --      cm:description
+ *   comment:                --      
+ *   yearReleased:           --      audio:releaseDate
+ *   trackNumber:            --      audio:trackNumber
+ *   genre:                  --      audio:genre
+ *   composer:               --      audio:composer
+ *   lyrics:                 --      
+ * 
+ * + * Note - XMPDM metadata keys are also emitted, in common with + * the other Tika powered extracters + * + * Uses Apache Tika + * + * @author Nick Burch + */ +public class MP3MetadataExtracter extends TikaAudioMetadataExtracter +{ + private static final String KEY_SONG_TITLE = "songTitle"; + private static final String KEY_ALBUM_TITLE = "albumTitle"; + private static final String KEY_ARTIST = "artist"; + private static final String KEY_COMMENT = "comment"; + private static final String KEY_YEAR_RELEASED = "yearReleased"; + private static final String KEY_TRACK_NUMBER = "trackNumber"; + private static final String KEY_GENRE = "genre"; + private static final String KEY_COMPOSER = "composer"; + + public static ArrayList SUPPORTED_MIMETYPES = buildSupportedMimetypes( + new String[] { MimetypeMap.MIMETYPE_MP3 }, + new Mp3Parser() + ); + + public MP3MetadataExtracter() + { + super(SUPPORTED_MIMETYPES); + } + + @Override + protected Parser getParser() + { + return new Mp3Parser(); + } + + @SuppressWarnings("deprecation") + @Override + protected Map extractSpecific(Metadata metadata, + Map properties, Map headers) + { + // Do the normal Audio mappings + super.extractSpecific(metadata, properties, headers); + + // Now do the compatibility ones + // We only need these for people who had pre-existing mapping + // properties from before the proper audio model was added + putRawValue(KEY_ALBUM_TITLE, metadata.get(XMPDM.ALBUM), properties); + putRawValue(KEY_SONG_TITLE, metadata.get(Metadata.TITLE), properties); + putRawValue(KEY_ARTIST, metadata.get(XMPDM.ARTIST), properties); + putRawValue(KEY_COMMENT, metadata.get(XMPDM.LOG_COMMENT), properties); + putRawValue(KEY_TRACK_NUMBER, metadata.get(XMPDM.TRACK_NUMBER), properties); + putRawValue(KEY_GENRE, metadata.get(XMPDM.GENRE), properties); + putRawValue(KEY_YEAR_RELEASED, metadata.get(XMPDM.RELEASE_DATE), properties); + putRawValue(KEY_COMPOSER, metadata.get(XMPDM.COMPOSER), properties); + + // All done + return properties; + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java index 18c03aeccc..b2f0fb8dc8 100644 --- a/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java @@ -1,82 +1,82 @@ -package org.alfresco.repo.content.metadata; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Map; - -import org.alfresco.repo.content.MimetypeMap; -import org.apache.tika.metadata.Metadata; -import org.apache.tika.parser.Parser; -import org.apache.tika.parser.microsoft.OfficeParser; - -/** - * Outlook MAPI format email meta-data extractor extracting the following values: - *
- *   sentDate:               --      cm:sentdate
- *   originator:             --      cm:originator,    cm:author
- *   addressee:              --      cm:addressee
- *   addressees:             --      cm:addressees
- *   subjectLine:            --      cm:subjectline,   cm:description
- *   toNames:                --
- *   ccNames:                --
- *   bccNames:               --
- * 
- * - * TIKA note - to/cc/bcc go into the html part, not the metadata. - * Also, email addresses not included as yet. - * - * @since 2.1 - * @author Kevin Roast - */ -public class MailMetadataExtracter extends TikaPoweredMetadataExtracter -{ - private static final String KEY_SENT_DATE = "sentDate"; - private static final String KEY_ORIGINATOR = "originator"; - private static final String KEY_ADDRESSEE = "addressee"; - private static final String KEY_ADDRESSEES = "addressees"; - private static final String KEY_SUBJECT = "subjectLine"; - private static final String KEY_TO_NAMES = "toNames"; - private static final String KEY_CC_NAMES = "ccNames"; - private static final String KEY_BCC_NAMES = "bccNames"; - - public static ArrayList SUPPORTED_MIMETYPES = buildSupportedMimetypes( - new String[] {MimetypeMap.MIMETYPE_OUTLOOK_MSG}, - (Parser[])null - ); - - public MailMetadataExtracter() - { - super(SUPPORTED_MIMETYPES); - } - - @Override - protected Parser getParser() - { - // The office parser does Outlook as well as Word, Excel etc - return new OfficeParser(); - } - - @SuppressWarnings("deprecation") - @Override - protected Map extractSpecific(Metadata metadata, - Map properties, Map headers) - { - putRawValue(KEY_ORIGINATOR, metadata.get(Metadata.AUTHOR), properties); - putRawValue(KEY_SUBJECT, metadata.get(Metadata.TITLE), properties); - putRawValue(KEY_DESCRIPTION, metadata.get(Metadata.SUBJECT), properties); - putRawValue(KEY_SENT_DATE, metadata.get(Metadata.LAST_SAVED), properties); - - // Store the TO, but not cc/bcc in the addressee field - putRawValue(KEY_ADDRESSEE, metadata.get(Metadata.MESSAGE_TO), properties); - - // Store each of To, CC and BCC in their own fields - putRawValue(KEY_TO_NAMES, metadata.getValues(Metadata.MESSAGE_TO), properties); - putRawValue(KEY_CC_NAMES, metadata.getValues(Metadata.MESSAGE_CC), properties); - putRawValue(KEY_BCC_NAMES, metadata.getValues(Metadata.MESSAGE_BCC), properties); - - // But store all email addresses (to/cc/bcc) in the addresses field - putRawValue(KEY_ADDRESSEES, metadata.getValues(Metadata.MESSAGE_RECIPIENT_ADDRESS), properties); - - return properties; - } -} +package org.alfresco.repo.content.metadata; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Map; + +import org.alfresco.repo.content.MimetypeMap; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.parser.Parser; +import org.apache.tika.parser.microsoft.OfficeParser; + +/** + * Outlook MAPI format email meta-data extractor extracting the following values: + *
+ *   sentDate:               --      cm:sentdate
+ *   originator:             --      cm:originator,    cm:author
+ *   addressee:              --      cm:addressee
+ *   addressees:             --      cm:addressees
+ *   subjectLine:            --      cm:subjectline,   cm:description
+ *   toNames:                --
+ *   ccNames:                --
+ *   bccNames:               --
+ * 
+ * + * TIKA note - to/cc/bcc go into the html part, not the metadata. + * Also, email addresses not included as yet. + * + * @since 2.1 + * @author Kevin Roast + */ +public class MailMetadataExtracter extends TikaPoweredMetadataExtracter +{ + private static final String KEY_SENT_DATE = "sentDate"; + private static final String KEY_ORIGINATOR = "originator"; + private static final String KEY_ADDRESSEE = "addressee"; + private static final String KEY_ADDRESSEES = "addressees"; + private static final String KEY_SUBJECT = "subjectLine"; + private static final String KEY_TO_NAMES = "toNames"; + private static final String KEY_CC_NAMES = "ccNames"; + private static final String KEY_BCC_NAMES = "bccNames"; + + public static ArrayList SUPPORTED_MIMETYPES = buildSupportedMimetypes( + new String[] {MimetypeMap.MIMETYPE_OUTLOOK_MSG}, + (Parser[])null + ); + + public MailMetadataExtracter() + { + super(SUPPORTED_MIMETYPES); + } + + @Override + protected Parser getParser() + { + // The office parser does Outlook as well as Word, Excel etc + return new OfficeParser(); + } + + @SuppressWarnings("deprecation") + @Override + protected Map extractSpecific(Metadata metadata, + Map properties, Map headers) + { + putRawValue(KEY_ORIGINATOR, metadata.get(Metadata.AUTHOR), properties); + putRawValue(KEY_SUBJECT, metadata.get(Metadata.TITLE), properties); + putRawValue(KEY_DESCRIPTION, metadata.get(Metadata.SUBJECT), properties); + putRawValue(KEY_SENT_DATE, metadata.get(Metadata.LAST_SAVED), properties); + + // Store the TO, but not cc/bcc in the addressee field + putRawValue(KEY_ADDRESSEE, metadata.get(Metadata.MESSAGE_TO), properties); + + // Store each of To, CC and BCC in their own fields + putRawValue(KEY_TO_NAMES, metadata.getValues(Metadata.MESSAGE_TO), properties); + putRawValue(KEY_CC_NAMES, metadata.getValues(Metadata.MESSAGE_CC), properties); + putRawValue(KEY_BCC_NAMES, metadata.getValues(Metadata.MESSAGE_BCC), properties); + + // But store all email addresses (to/cc/bcc) in the addresses field + putRawValue(KEY_ADDRESSEES, metadata.getValues(Metadata.MESSAGE_RECIPIENT_ADDRESS), properties); + + return properties; + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/MediaTypeDisablingDocumentSelector.java b/source/java/org/alfresco/repo/content/metadata/MediaTypeDisablingDocumentSelector.java index f3889060ec..0f0a3f781d 100644 --- a/source/java/org/alfresco/repo/content/metadata/MediaTypeDisablingDocumentSelector.java +++ b/source/java/org/alfresco/repo/content/metadata/MediaTypeDisablingDocumentSelector.java @@ -1,33 +1,33 @@ -package org.alfresco.repo.content.metadata; - -import java.util.List; - -import org.apache.tika.extractor.DocumentSelector; -import org.apache.tika.metadata.Metadata; - -/** - * Tika 1.6 has the ability to parse embedded artifacts, such as images in a PDF, - * but this can be very resource intensive so adding this selector - * to parsers and transformers that handle formats with embedded artifacts - * will disable parsing of the specified content types. - */ -public class MediaTypeDisablingDocumentSelector implements DocumentSelector -{ - private List disabledMediaTypes; - - public void setDisabledMediaTypes(List disabledMediaTypes) - { - this.disabledMediaTypes = disabledMediaTypes; - } - - @Override - public boolean select(Metadata metadata) - { - String contentType = metadata.get(Metadata.CONTENT_TYPE); - if (contentType == null || contentType.equals("") || disabledMediaTypes == null) - { - return true; - } - return !disabledMediaTypes.contains(contentType); - } -} +package org.alfresco.repo.content.metadata; + +import java.util.List; + +import org.apache.tika.extractor.DocumentSelector; +import org.apache.tika.metadata.Metadata; + +/** + * Tika 1.6 has the ability to parse embedded artifacts, such as images in a PDF, + * but this can be very resource intensive so adding this selector + * to parsers and transformers that handle formats with embedded artifacts + * will disable parsing of the specified content types. + */ +public class MediaTypeDisablingDocumentSelector implements DocumentSelector +{ + private List disabledMediaTypes; + + public void setDisabledMediaTypes(List disabledMediaTypes) + { + this.disabledMediaTypes = disabledMediaTypes; + } + + @Override + public boolean select(Metadata metadata) + { + String contentType = metadata.get(Metadata.CONTENT_TYPE); + if (contentType == null || contentType.equals("") || disabledMediaTypes == null) + { + return true; + } + return !disabledMediaTypes.contains(contentType); + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/MetadataEmbedder.java b/source/java/org/alfresco/repo/content/metadata/MetadataEmbedder.java index af9074415d..44d863b112 100644 --- a/source/java/org/alfresco/repo/content/metadata/MetadataEmbedder.java +++ b/source/java/org/alfresco/repo/content/metadata/MetadataEmbedder.java @@ -1,48 +1,48 @@ -package org.alfresco.repo.content.metadata; - -import java.io.Serializable; -import java.util.Map; - -import org.alfresco.api.AlfrescoPublicApi; -import org.alfresco.repo.content.ContentWorker; -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.namespace.QName; - -/** - * Interface for writing metadata properties back into the content file. - * - * @author Ray Gauss II - * - */ -@AlfrescoPublicApi -public interface MetadataEmbedder extends ContentWorker { - - /** - * Determines if the extracter works against the given mimetype. - * - * @param mimetype the document mimetype - * @return Returns true if the mimetype is supported, otherwise false. - */ - public boolean isEmbeddingSupported(String mimetype); - - /** - * Embeds the given properties into the file specified by the given content writer. - * *

- * The embedding viability can be determined by an up front call to - * {@link #isEmbeddingSupported(String)}. - *

- * The source mimetype must be available on the - * {@link org.alfresco.service.cmr.repository.ContentAccessor#getMimetype()} method - * of the writer. - * - * @param properties the model properties to embed - * @param reader the reader for the original source content file - * @param writer the writer for the content after metadata has been embedded - * @throws ContentIOException - */ - public void embed(Map properties, ContentReader reader, ContentWriter writer) throws ContentIOException; - - -} +package org.alfresco.repo.content.metadata; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.api.AlfrescoPublicApi; +import org.alfresco.repo.content.ContentWorker; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.namespace.QName; + +/** + * Interface for writing metadata properties back into the content file. + * + * @author Ray Gauss II + * + */ +@AlfrescoPublicApi +public interface MetadataEmbedder extends ContentWorker { + + /** + * Determines if the extracter works against the given mimetype. + * + * @param mimetype the document mimetype + * @return Returns true if the mimetype is supported, otherwise false. + */ + public boolean isEmbeddingSupported(String mimetype); + + /** + * Embeds the given properties into the file specified by the given content writer. + * *

+ * The embedding viability can be determined by an up front call to + * {@link #isEmbeddingSupported(String)}. + *

+ * The source mimetype must be available on the + * {@link org.alfresco.service.cmr.repository.ContentAccessor#getMimetype()} method + * of the writer. + * + * @param properties the model properties to embed + * @param reader the reader for the original source content file + * @param writer the writer for the content after metadata has been embedded + * @throws ContentIOException + */ + public void embed(Map properties, ContentReader reader, ContentWriter writer) throws ContentIOException; + + +} diff --git a/source/java/org/alfresco/repo/content/metadata/MetadataExtracterLimits.java b/source/java/org/alfresco/repo/content/metadata/MetadataExtracterLimits.java index 50b70de85a..7b5f056541 100644 --- a/source/java/org/alfresco/repo/content/metadata/MetadataExtracterLimits.java +++ b/source/java/org/alfresco/repo/content/metadata/MetadataExtracterLimits.java @@ -1,37 +1,37 @@ -package org.alfresco.repo.content.metadata; - -import org.alfresco.api.AlfrescoPublicApi; - -/** - * Represents maximum values (that result in exceptions if exceeded) or - * limits on values (that result in EOF (End Of File) being returned - * early). The only current option is for elapsed time. - * - * @author Ray Gauss II - */ -@AlfrescoPublicApi -public class MetadataExtracterLimits -{ - private long timeoutMs = -1; - - /** - * Gets the time in milliseconds after which the metadata extracter will be stopped. - * - * @return the timeout - */ - public long getTimeoutMs() - { - return timeoutMs; - } - - /** - * Sets the time in milliseconds after which the metadata extracter will be stopped. - * - * @param timeoutMs the timeout - */ - public void setTimeoutMs(long timeoutMs) - { - this.timeoutMs = timeoutMs; - } - -} +package org.alfresco.repo.content.metadata; + +import org.alfresco.api.AlfrescoPublicApi; + +/** + * Represents maximum values (that result in exceptions if exceeded) or + * limits on values (that result in EOF (End Of File) being returned + * early). The only current option is for elapsed time. + * + * @author Ray Gauss II + */ +@AlfrescoPublicApi +public class MetadataExtracterLimits +{ + private long timeoutMs = -1; + + /** + * Gets the time in milliseconds after which the metadata extracter will be stopped. + * + * @return the timeout + */ + public long getTimeoutMs() + { + return timeoutMs; + } + + /** + * Sets the time in milliseconds after which the metadata extracter will be stopped. + * + * @param timeoutMs the timeout + */ + public void setTimeoutMs(long timeoutMs) + { + this.timeoutMs = timeoutMs; + } + +} diff --git a/source/java/org/alfresco/repo/content/metadata/OpenDocumentMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/OpenDocumentMetadataExtracter.java index f7435ccd64..31608741ca 100644 --- a/source/java/org/alfresco/repo/content/metadata/OpenDocumentMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/OpenDocumentMetadataExtracter.java @@ -1,154 +1,154 @@ -/* - * Copyright (C) 2005 Antti Jokipii - * - * 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 . - */ -package org.alfresco.repo.content.metadata; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.Map; -import java.util.Set; - -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.service.namespace.QName; -import org.apache.tika.metadata.Metadata; -import org.apache.tika.parser.Parser; -import org.apache.tika.parser.odf.OpenDocumentParser; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; - - -/** - * Metadata extractor for the - * {@link org.alfresco.repo.content.MimetypeMap#MIMETYPE_OPENDOCUMENT_TEXT MIMETYPE_OPENDOCUMENT_XXX} - * mimetypes. - *

- *   creationDate:           --      cm:created
- *   creator:                --      cm:author
- *   date:
- *   description:            --      cm:description
- *   generator:
- *   initialCreator:
- *   keyword:
- *   language:
- *   printDate:
- *   printedBy:
- *   subject:
- *   title:                  --      cm:title
- *   All user properties
- * 
- * - * Uses Apache Tika - * - * TODO decide if we need the few print info bits that - * Tika currently doesn't handle - * - * @author Antti Jokipii - * @author Derek Hulley - */ -public class OpenDocumentMetadataExtracter extends TikaPoweredMetadataExtracter -{ - private static final String KEY_CREATION_DATE = "creationDate"; - private static final String KEY_CREATOR = "creator"; - private static final String KEY_DATE = "date"; - private static final String KEY_GENERATOR = "generator"; - private static final String KEY_INITIAL_CREATOR = "initialCreator"; - private static final String KEY_KEYWORD = "keyword"; - private static final String KEY_LANGUAGE = "language"; -// private static final String KEY_PRINT_DATE = "printDate"; -// private static final String KEY_PRINTED_BY = "printedBy"; - - private static final String CUSTOM_PREFIX = "custom:"; - - public static ArrayList SUPPORTED_MIMETYPES = buildSupportedMimetypes( - new String[] { - MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT, - MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE, - MimetypeMap.MIMETYPE_OPENDOCUMENT_GRAPHICS, - MimetypeMap.MIMETYPE_OPENDOCUMENT_GRAPHICS_TEMPLATE, - MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, - MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION_TEMPLATE, - MimetypeMap.MIMETYPE_OPENDOCUMENT_SPREADSHEET, - MimetypeMap.MIMETYPE_OPENDOCUMENT_SPREADSHEET_TEMPLATE, - MimetypeMap.MIMETYPE_OPENDOCUMENT_CHART, - MimetypeMap.MIMETYPE_OPENDOCUMENT_CHART_TEMPLATE, - MimetypeMap.MIMETYPE_OPENDOCUMENT_IMAGE, - MimetypeMap.MIMETYPE_OPENDOCUMENT_IMAGE_TEMPLATE, - MimetypeMap.MIMETYPE_OPENDOCUMENT_FORMULA, - MimetypeMap.MIMETYPE_OPENDOCUMENT_FORMULA_TEMPLATE, - MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT_MASTER, - MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT_WEB, - MimetypeMap.MIMETYPE_OPENDOCUMENT_DATABASE - }, new OpenDocumentParser() - ); - - private static final DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss"); - - public OpenDocumentMetadataExtracter() - { - super(SUPPORTED_MIMETYPES); - } - - @Override - protected Parser getParser() - { - return new OpenDocumentParser(); - } - - @SuppressWarnings("deprecation") - @Override - protected Map extractSpecific(Metadata metadata, - Map properties, Map headers) - { - putRawValue(KEY_CREATION_DATE, getDateOrNull(metadata.get(Metadata.CREATION_DATE)), properties); - putRawValue(KEY_CREATOR, metadata.get(Metadata.CREATOR), properties); - putRawValue(KEY_DATE, getDateOrNull(metadata.get(Metadata.DATE)), properties); - putRawValue(KEY_DESCRIPTION, metadata.get(Metadata.DESCRIPTION), properties); - putRawValue(KEY_GENERATOR, metadata.get("generator"), properties); - putRawValue(KEY_INITIAL_CREATOR, metadata.get("initial-creator"), properties); - putRawValue(KEY_KEYWORD, metadata.get(Metadata.KEYWORDS), properties); - putRawValue(KEY_LANGUAGE, metadata.get(Metadata.LANGUAGE), properties); -// putRawValue(KEY_PRINT_DATE, getDateOrNull(metadata.get(Metadata.)), rawProperties); -// putRawValue(KEY_PRINTED_BY, metadata.get(Metadata.), rawProperties); - - // Handle user-defined properties dynamically - Map> mapping = super.getMapping(); - for (String key : mapping.keySet()) - { - if (metadata.get(CUSTOM_PREFIX + key) != null) - { - putRawValue(key, metadata.get(CUSTOM_PREFIX + key), properties); - } - } - - return properties; - } - - private Date getDateOrNull(String dateString) - { - if (dateString != null && dateString.length() != 0) - { - try - { - return dateFormatter.parseDateTime(dateString).toDate(); - } - catch (IllegalArgumentException e) {} - } - return null; - } -} +/* + * Copyright (C) 2005 Antti Jokipii + * + * 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 . + */ +package org.alfresco.repo.content.metadata; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.namespace.QName; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.parser.Parser; +import org.apache.tika.parser.odf.OpenDocumentParser; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + + +/** + * Metadata extractor for the + * {@link org.alfresco.repo.content.MimetypeMap#MIMETYPE_OPENDOCUMENT_TEXT MIMETYPE_OPENDOCUMENT_XXX} + * mimetypes. + *
+ *   creationDate:           --      cm:created
+ *   creator:                --      cm:author
+ *   date:
+ *   description:            --      cm:description
+ *   generator:
+ *   initialCreator:
+ *   keyword:
+ *   language:
+ *   printDate:
+ *   printedBy:
+ *   subject:
+ *   title:                  --      cm:title
+ *   All user properties
+ * 
+ * + * Uses Apache Tika + * + * TODO decide if we need the few print info bits that + * Tika currently doesn't handle + * + * @author Antti Jokipii + * @author Derek Hulley + */ +public class OpenDocumentMetadataExtracter extends TikaPoweredMetadataExtracter +{ + private static final String KEY_CREATION_DATE = "creationDate"; + private static final String KEY_CREATOR = "creator"; + private static final String KEY_DATE = "date"; + private static final String KEY_GENERATOR = "generator"; + private static final String KEY_INITIAL_CREATOR = "initialCreator"; + private static final String KEY_KEYWORD = "keyword"; + private static final String KEY_LANGUAGE = "language"; +// private static final String KEY_PRINT_DATE = "printDate"; +// private static final String KEY_PRINTED_BY = "printedBy"; + + private static final String CUSTOM_PREFIX = "custom:"; + + public static ArrayList SUPPORTED_MIMETYPES = buildSupportedMimetypes( + new String[] { + MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT, + MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE, + MimetypeMap.MIMETYPE_OPENDOCUMENT_GRAPHICS, + MimetypeMap.MIMETYPE_OPENDOCUMENT_GRAPHICS_TEMPLATE, + MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, + MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION_TEMPLATE, + MimetypeMap.MIMETYPE_OPENDOCUMENT_SPREADSHEET, + MimetypeMap.MIMETYPE_OPENDOCUMENT_SPREADSHEET_TEMPLATE, + MimetypeMap.MIMETYPE_OPENDOCUMENT_CHART, + MimetypeMap.MIMETYPE_OPENDOCUMENT_CHART_TEMPLATE, + MimetypeMap.MIMETYPE_OPENDOCUMENT_IMAGE, + MimetypeMap.MIMETYPE_OPENDOCUMENT_IMAGE_TEMPLATE, + MimetypeMap.MIMETYPE_OPENDOCUMENT_FORMULA, + MimetypeMap.MIMETYPE_OPENDOCUMENT_FORMULA_TEMPLATE, + MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT_MASTER, + MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT_WEB, + MimetypeMap.MIMETYPE_OPENDOCUMENT_DATABASE + }, new OpenDocumentParser() + ); + + private static final DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss"); + + public OpenDocumentMetadataExtracter() + { + super(SUPPORTED_MIMETYPES); + } + + @Override + protected Parser getParser() + { + return new OpenDocumentParser(); + } + + @SuppressWarnings("deprecation") + @Override + protected Map extractSpecific(Metadata metadata, + Map properties, Map headers) + { + putRawValue(KEY_CREATION_DATE, getDateOrNull(metadata.get(Metadata.CREATION_DATE)), properties); + putRawValue(KEY_CREATOR, metadata.get(Metadata.CREATOR), properties); + putRawValue(KEY_DATE, getDateOrNull(metadata.get(Metadata.DATE)), properties); + putRawValue(KEY_DESCRIPTION, metadata.get(Metadata.DESCRIPTION), properties); + putRawValue(KEY_GENERATOR, metadata.get("generator"), properties); + putRawValue(KEY_INITIAL_CREATOR, metadata.get("initial-creator"), properties); + putRawValue(KEY_KEYWORD, metadata.get(Metadata.KEYWORDS), properties); + putRawValue(KEY_LANGUAGE, metadata.get(Metadata.LANGUAGE), properties); +// putRawValue(KEY_PRINT_DATE, getDateOrNull(metadata.get(Metadata.)), rawProperties); +// putRawValue(KEY_PRINTED_BY, metadata.get(Metadata.), rawProperties); + + // Handle user-defined properties dynamically + Map> mapping = super.getMapping(); + for (String key : mapping.keySet()) + { + if (metadata.get(CUSTOM_PREFIX + key) != null) + { + putRawValue(key, metadata.get(CUSTOM_PREFIX + key), properties); + } + } + + return properties; + } + + private Date getDateOrNull(String dateString) + { + if (dateString != null && dateString.length() != 0) + { + try + { + return dateFormatter.parseDateTime(dateString).toDate(); + } + catch (IllegalArgumentException e) {} + } + return null; + } +} diff --git a/source/java/org/alfresco/repo/content/replication/AggregatingContentStore.java b/source/java/org/alfresco/repo/content/replication/AggregatingContentStore.java index 1c05440eeb..a776c5f5a6 100644 --- a/source/java/org/alfresco/repo/content/replication/AggregatingContentStore.java +++ b/source/java/org/alfresco/repo/content/replication/AggregatingContentStore.java @@ -1,197 +1,197 @@ -package org.alfresco.repo.content.replication; - -import java.util.Date; -import java.util.List; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.content.AbstractContentStore; -import org.alfresco.repo.content.ContentContext; -import org.alfresco.repo.content.ContentStore; -import org.alfresco.repo.content.caching.CachingContentStore; -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Aggregating Content Store - *

- * A content store implementation that aggregates a set of stores. Content is not - * persisted by this store, but rather it relies on any number of - * child {@link org.alfresco.repo.content.ContentStore stores} to provide access - * to content readers and writers. - *

- * The order in which the stores appear in the list of stores participating is - * important. The first store in the list is known as the primary store. -

- * Content is written to the primary store only. The other stores are - * only used to retrieve content and the primary store is not updated with - * the content. - * - * @author Derek Hulley - * @author Mark Rogers - * @see CachingContentStore - */ -public class AggregatingContentStore extends AbstractContentStore -{ - private static Log logger = LogFactory.getLog(AggregatingContentStore.class); - - private ContentStore primaryStore; - private List secondaryStores; - - private Lock readLock; - private Lock writeLock; - - /** - * Default constructor - */ - public AggregatingContentStore() - { - ReadWriteLock storeLock = new ReentrantReadWriteLock(); - readLock = storeLock.readLock(); - writeLock = storeLock.writeLock(); - } - - /** - * Set the primary store that content will be replicated to or from - * - * @param primaryStore the primary content store - */ - public void setPrimaryStore(ContentStore primaryStore) - { - this.primaryStore = primaryStore; - } - - /** - * Set the secondary stores that this component will replicate to or from - * - * @param secondaryStores a list of stores to replicate to or from - */ - public void setSecondaryStores(List secondaryStores) - { - this.secondaryStores = secondaryStores; - } - - /** - * @return Returns true if the primary store supports writing - */ - public boolean isWriteSupported() - { - return primaryStore.isWriteSupported(); - } - - /** - * @return Returns true if the primary store supports the URL - */ - @Override - public boolean isContentUrlSupported(String contentUrl) - { - return primaryStore.isContentUrlSupported(contentUrl); - } - - /** - * @return Return the primary store root location - */ - @Override - public String getRootLocation() - { - return primaryStore.getRootLocation(); - } - - /** - * Forwards the call directly to the first store in the list of stores. - */ - public ContentReader getReader(String contentUrl) throws ContentIOException - { - if (primaryStore == null) - { - throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised"); - } - - // get a read lock so that we are sure that no replication is underway - readLock.lock(); - try - { - // get a reader from the primary store - ContentReader primaryReader = primaryStore.getReader(contentUrl); - - // give it straight back if the content is there - if (primaryReader.exists()) - { - return primaryReader; - } - - // the content is not in the primary reader so we have to go looking for it - ContentReader secondaryContentReader = null; - for (ContentStore store : secondaryStores) - { - ContentReader reader = store.getReader(contentUrl); - if (reader.exists()) - { - // found the content in a secondary store - return reader; - } - } - - return primaryReader; - } - finally - { - readLock.unlock(); - } - } - - public ContentWriter getWriter(ContentContext ctx) - { - // get the writer - ContentWriter writer = primaryStore.getWriter(ctx); - - return writer; - } - - /** - * Performs a delete on the local store and if outbound replication is on, propogates - * the delete to the other stores too. - * - * @return Returns the value returned by the delete on the primary store. - */ - public boolean delete(String contentUrl) throws ContentIOException - { - // delete on the primary store - boolean deleted = primaryStore.delete(contentUrl); - - if (logger.isDebugEnabled()) - { - logger.debug("Deleted content for URL: " + contentUrl); - } - return deleted; - } - - /** - * Iterates over results as given by the primary store and all secondary stores. It is up to the handler to eliminate - * duplicates that will occur between the primary and secondary stores. - */ - @SuppressWarnings("deprecation") - public void getUrls(Date createdAfter, Date createdBefore, ContentUrlHandler handler) throws ContentIOException - { - // add in URLs from primary store - primaryStore.getUrls(createdAfter, createdBefore, handler); - - // add in URLs from secondary stores (they are visible for reads) - for (ContentStore secondaryStore : secondaryStores) - { - secondaryStore.getUrls(createdAfter, createdBefore, handler); - } - // done - if (logger.isDebugEnabled()) - { - logger.debug("Iterated over content URLs: \n" + - " created after: " + createdAfter + "\n" + - " created before: " + createdBefore); - } - } -} +package org.alfresco.repo.content.replication; + +import java.util.Date; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.content.AbstractContentStore; +import org.alfresco.repo.content.ContentContext; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.caching.CachingContentStore; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Aggregating Content Store + *

+ * A content store implementation that aggregates a set of stores. Content is not + * persisted by this store, but rather it relies on any number of + * child {@link org.alfresco.repo.content.ContentStore stores} to provide access + * to content readers and writers. + *

+ * The order in which the stores appear in the list of stores participating is + * important. The first store in the list is known as the primary store. +

+ * Content is written to the primary store only. The other stores are + * only used to retrieve content and the primary store is not updated with + * the content. + * + * @author Derek Hulley + * @author Mark Rogers + * @see CachingContentStore + */ +public class AggregatingContentStore extends AbstractContentStore +{ + private static Log logger = LogFactory.getLog(AggregatingContentStore.class); + + private ContentStore primaryStore; + private List secondaryStores; + + private Lock readLock; + private Lock writeLock; + + /** + * Default constructor + */ + public AggregatingContentStore() + { + ReadWriteLock storeLock = new ReentrantReadWriteLock(); + readLock = storeLock.readLock(); + writeLock = storeLock.writeLock(); + } + + /** + * Set the primary store that content will be replicated to or from + * + * @param primaryStore the primary content store + */ + public void setPrimaryStore(ContentStore primaryStore) + { + this.primaryStore = primaryStore; + } + + /** + * Set the secondary stores that this component will replicate to or from + * + * @param secondaryStores a list of stores to replicate to or from + */ + public void setSecondaryStores(List secondaryStores) + { + this.secondaryStores = secondaryStores; + } + + /** + * @return Returns true if the primary store supports writing + */ + public boolean isWriteSupported() + { + return primaryStore.isWriteSupported(); + } + + /** + * @return Returns true if the primary store supports the URL + */ + @Override + public boolean isContentUrlSupported(String contentUrl) + { + return primaryStore.isContentUrlSupported(contentUrl); + } + + /** + * @return Return the primary store root location + */ + @Override + public String getRootLocation() + { + return primaryStore.getRootLocation(); + } + + /** + * Forwards the call directly to the first store in the list of stores. + */ + public ContentReader getReader(String contentUrl) throws ContentIOException + { + if (primaryStore == null) + { + throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised"); + } + + // get a read lock so that we are sure that no replication is underway + readLock.lock(); + try + { + // get a reader from the primary store + ContentReader primaryReader = primaryStore.getReader(contentUrl); + + // give it straight back if the content is there + if (primaryReader.exists()) + { + return primaryReader; + } + + // the content is not in the primary reader so we have to go looking for it + ContentReader secondaryContentReader = null; + for (ContentStore store : secondaryStores) + { + ContentReader reader = store.getReader(contentUrl); + if (reader.exists()) + { + // found the content in a secondary store + return reader; + } + } + + return primaryReader; + } + finally + { + readLock.unlock(); + } + } + + public ContentWriter getWriter(ContentContext ctx) + { + // get the writer + ContentWriter writer = primaryStore.getWriter(ctx); + + return writer; + } + + /** + * Performs a delete on the local store and if outbound replication is on, propogates + * the delete to the other stores too. + * + * @return Returns the value returned by the delete on the primary store. + */ + public boolean delete(String contentUrl) throws ContentIOException + { + // delete on the primary store + boolean deleted = primaryStore.delete(contentUrl); + + if (logger.isDebugEnabled()) + { + logger.debug("Deleted content for URL: " + contentUrl); + } + return deleted; + } + + /** + * Iterates over results as given by the primary store and all secondary stores. It is up to the handler to eliminate + * duplicates that will occur between the primary and secondary stores. + */ + @SuppressWarnings("deprecation") + public void getUrls(Date createdAfter, Date createdBefore, ContentUrlHandler handler) throws ContentIOException + { + // add in URLs from primary store + primaryStore.getUrls(createdAfter, createdBefore, handler); + + // add in URLs from secondary stores (they are visible for reads) + for (ContentStore secondaryStore : secondaryStores) + { + secondaryStore.getUrls(createdAfter, createdBefore, handler); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Iterated over content URLs: \n" + + " created after: " + createdAfter + "\n" + + " created before: " + createdBefore); + } + } +} diff --git a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java index 93b4fc2d41..7320e73b1d 100644 --- a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java @@ -1,532 +1,532 @@ -package org.alfresco.repo.content.transform; - -import java.beans.PropertyDescriptor; -import java.io.File; -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayDeque; -import java.util.Collections; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import javax.faces.el.MethodNotFoundException; - -import org.alfresco.api.AlfrescoPublicApi; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.content.filestore.FileContentWriter; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.TransformationOptionLimits; -import org.alfresco.service.cmr.repository.TransformationOptions; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.beanutils.PropertyUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -/** - * Transformer that passes a document through several nested transformations - * in order to accomplish its goal. - * - * @author Derek Hulley - */ -@AlfrescoPublicApi -public class ComplexContentTransformer extends AbstractContentTransformer2 implements InitializingBean -{ - /** - * The logger - */ - private static Log logger = LogFactory.getLog(ComplexContentTransformer.class); - - /** - * Complex transformers contain lower level transformers. In order to find dynamic - * (defined as null) child transformers to use, they recursively check available - * transformers. It makes no sense to have a transformer that is its own child. - */ - static final ThreadLocal> parentTransformers = new ThreadLocal>() { - @Override - protected Deque initialValue() { - return new ArrayDeque(); - } - }; - - private List transformers; - private List intermediateMimetypes; - private Map transformationOptionOverrides; - private ContentService contentService; - - public ComplexContentTransformer() - { - } - - /** - * The list of transformers to use. If any element is null - * all possible transformers will be considered. If any element - * is null, the contentService property must be set. - *

- * If a single transformer is supplied, then it will still be used. - * - * @param transformers list of at least one transformer - * - * @see #setContentService(ContentService) - */ - public void setTransformers(List transformers) - { - this.transformers = transformers; - } - - /** - * Set the intermediate mimetypes that the transformer must take the content - * through. If the transformation A..B..C is performed in order to - * simulate A..C, then B is the intermediate mimetype. There - * must always be n-1 intermediate mimetypes, where n is the - * number of {@link #setTransformers(List) transformers} taking part in the - * transformation. - * - * @param intermediateMimetypes intermediate mimetypes to transition the content - * through. - */ - public void setIntermediateMimetypes(List intermediateMimetypes) - { - this.intermediateMimetypes = intermediateMimetypes; - } - - /** - * Sets any properties to be set on the TransformationOption as passed in. - * This allows you to force certain properties to always be set on it, - * to control the transformers in a different way to their default. - * Note that only properties that are supported by the passed-in - * {@link TransformationOptions} are changed, others are ignored. - * @param transformationOptionOverrides Map - */ - public void setTransformationOptionOverrides( - Map transformationOptionOverrides) - { - this.transformationOptionOverrides = transformationOptionOverrides; - } - - /** - * Sets the ContentService. Only required if {@code null} transformers - * are provided to {@link #setTransformers(List)}. - * @param contentService - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * Ensures that required properties have been set - */ - public void afterPropertiesSet() throws Exception - { - if (transformers == null || transformers.size() == 0) - { - throw new AlfrescoRuntimeException("At least one inner transformer must be supplied: " + this); - } - if (intermediateMimetypes == null || intermediateMimetypes.size() != transformers.size() - 1) - { - throw new AlfrescoRuntimeException( - "There must be n-1 intermediate mimetypes, where n is the number of transformers"); - } - if (getMimetypeService() == null) - { - throw new AlfrescoRuntimeException("'mimetypeService' is a required property"); - } - for (ContentTransformer transformer: transformers) - { - if (transformer == null) - { - if (contentService == null) - { - throw new AlfrescoRuntimeException("'contentService' is a required property if " + - "there are any null (dynamic) transformers"); - } - break; - } - } - } - - @Override - public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, - TransformationOptions options) - { - if (!isSupportedTransformation(sourceMimetype, targetMimetype, options)) - { - return false; - } - - // Don't allow transformer to be its own child. - if (parentTransformers.get().contains(this)) - { - return false; - } - - overrideTransformationOptions(options); - - // Can use super isTransformableSize as it indirectly calls getLimits in this class - // which combines the limits from the first transformer. Other transformer in the chain - // are no checked as sizes are unknown. - return - isTransformableMimetype(sourceMimetype, targetMimetype, options) && - isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); - } - - /** - * Sets any transformation option overrides it can. - */ - private void overrideTransformationOptions(TransformationOptions options) - { - // Set any transformation options overrides if we can - if(options != null && transformationOptionOverrides != null) - { - for(String key : transformationOptionOverrides.keySet()) - { - if(PropertyUtils.isWriteable(options, key)) - { - try - { - PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(options, key); - Class propertyClass = pd.getPropertyType(); - - Object value = transformationOptionOverrides.get(key); - if(value != null) - { - if(propertyClass.isInstance(value)) - { - // Nothing to do - } - else if(value instanceof String && propertyClass.isInstance(Boolean.TRUE)) - { - // Use relaxed converter - value = TransformationOptions.relaxedBooleanTypeConverter.convert((String)value); - } - else - { - value = DefaultTypeConverter.INSTANCE.convert(propertyClass, value); - } - } - PropertyUtils.setProperty(options, key, value); - } - catch(MethodNotFoundException mnfe) {} - catch(NoSuchMethodException nsme) {} - catch(InvocationTargetException ite) {} - catch(IllegalAccessException iae) {} - } - else - { - logger.warn("Unable to set override Transformation Option " + key + " on " + options); - } - } - } - } - - @Override - public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) - { - boolean result = true; - String currentSourceMimetype = sourceMimetype; - Iterator transformerIterator = transformers.iterator(); - - // When using a wild card (null) intermediate transformer, don't - // say we support a transformation that one of the none null intermediate - // transformers can do on its own, to avoid double transformations. - // Not done when there are no wild card transformers, as there are cases - // where it makes sense to go via an intermediate format (quality/speed). - while (transformerIterator.hasNext()) - { - ContentTransformer transformer = transformerIterator.next(); - if (transformer == null) - { - transformerIterator = transformers.iterator(); - while (transformerIterator.hasNext()) - { - transformer = transformerIterator.next(); - if (transformer != null) - { - if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options)) - { - return false; - } - } - } - break; - } - } - - transformerIterator = transformers.iterator(); - Iterator intermediateMimetypeIterator = intermediateMimetypes.iterator(); - while (transformerIterator.hasNext()) - { - ContentTransformer transformer = transformerIterator.next(); - - // determine the target mimetype. This is the final target if we are on the last transformation - String currentTargetMimetype = transformerIterator.hasNext() ? intermediateMimetypeIterator.next() : targetMimetype; - if (transformer == null) - { - try - { - parentTransformers.get().push(this); - @SuppressWarnings("deprecation") - List firstTansformers = contentService.getActiveTransformers( - currentSourceMimetype, -1, currentTargetMimetype, options); - if (firstTansformers.isEmpty()) - { - result = false; - break; - } - } - finally - { - parentTransformers.get().pop(); - } - } - else - { - if (transformer.isTransformable(currentSourceMimetype, -1, currentTargetMimetype, options) == false) - { - result = false; - break; - } - } - - // move on - currentSourceMimetype = currentTargetMimetype; - } - - return result; - } - - /** - * Indicates if 'page' limits are supported by the first transformer in the chain. - * If the first transformer is dynamic, all possible first transformers must support it. - * @return true if the first transformer supports them. - */ - @Override - protected boolean isPageLimitSupported(String sourceMimetype, String targetMimetype, - TransformationOptions options) - { - boolean pageLimitSupported; - ContentTransformer firstTransformer = transformers.get(0); - String firstTargetMimetype = intermediateMimetypes.get(0); - if (firstTransformer == null) - { - try - { - parentTransformers.get().push(this); - @SuppressWarnings("deprecation") - List firstTansformers = contentService.getActiveTransformers( - sourceMimetype, -1, firstTargetMimetype, options); - pageLimitSupported = !firstTansformers.isEmpty(); - if (pageLimitSupported) - { - for (ContentTransformer transformer: firstTansformers) - { - if (!isPageLimitSupported(transformer, sourceMimetype, targetMimetype, options)) - { - pageLimitSupported = false; - break; - } - } - } - } - finally - { - parentTransformers.get().pop(); - } - } - else - { - pageLimitSupported = isPageLimitSupported(firstTransformer, sourceMimetype, targetMimetype, options); - } - return pageLimitSupported; - } - - private boolean isPageLimitSupported(ContentTransformer transformer, String sourceMimetype, - String targetMimetype, TransformationOptions options) - { - return (transformer instanceof AbstractContentTransformerLimits) - ? ((AbstractContentTransformerLimits)transformer).isPageLimitSupported(sourceMimetype, targetMimetype, options) - : false; - } - - /** - * Returns the limits from this transformer combined with those of the first transformer in the chain. - * If the first transformer is dynamic, the lowest common denominator between all possible first transformers - * are combined. - */ - protected TransformationOptionLimits getLimits(String sourceMimetype, String targetMimetype, - TransformationOptions options) - { - TransformationOptionLimits firstTransformerLimits = null; - TransformationOptionLimits limits = super.getLimits(sourceMimetype, targetMimetype, options); - ContentTransformer firstTransformer = transformers.get(0); - String firstTargetMimetype = intermediateMimetypes.get(0); - if (firstTransformer == null) - { - try - { - parentTransformers.get().push(this); - @SuppressWarnings("deprecation") - List firstTansformers = contentService.getActiveTransformers( - sourceMimetype, -1, firstTargetMimetype, options); - if (!firstTansformers.isEmpty()) - { - for (ContentTransformer transformer: firstTansformers) - { - if (transformer instanceof AbstractContentTransformerLimits) - { - TransformationOptionLimits transformerLimits = ((AbstractContentTransformerLimits)transformer). - getLimits(sourceMimetype, firstTargetMimetype, options); - firstTransformerLimits = (firstTransformerLimits == null) - ? transformerLimits - : firstTransformerLimits.combineUpper(transformerLimits); - } - } - } - } - finally - { - parentTransformers.get().pop(); - } - } - else - { - if (firstTransformer instanceof AbstractContentTransformerLimits) - { - firstTransformerLimits = ((AbstractContentTransformerLimits)firstTransformer). - getLimits(sourceMimetype, firstTargetMimetype, options); - } - } - - if (firstTransformerLimits != null) - { - limits = limits.combine(firstTransformerLimits); - } - - return limits; - } - - /** - * @see org.alfresco.repo.content.transform.AbstractContentTransformer2#transformInternal(org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter, org.alfresco.service.cmr.repository.TransformationOptions) - */ - @Override - public void transformInternal( - ContentReader reader, - ContentWriter writer, - TransformationOptions options) throws Exception - { - NodeRef origSourceNodeRef = options.getSourceNodeRef(); - try - { - ContentReader currentReader = reader; - - Iterator transformerIterator = transformers.iterator(); - Iterator intermediateMimetypeIterator = intermediateMimetypes.iterator(); - while (transformerIterator.hasNext()) - { - ContentTransformer transformer = transformerIterator.next(); - // determine the target mimetype. This is the final target if we are on the last transformation - ContentWriter currentWriter = null; - if (!transformerIterator.hasNext()) - { - currentWriter = writer; - } - else - { - String nextMimetype = intermediateMimetypeIterator.next(); - // make a temp file writer with the correct extension - String sourceExt = getMimetypeService().getExtension(currentReader.getMimetype()); - String targetExt = getMimetypeService().getExtension(nextMimetype); - File tempFile = TempFileProvider.createTempFile( - "ComplextTransformer_intermediate_" + sourceExt + "_", - "." + targetExt); - currentWriter = new FileContentWriter(tempFile); - currentWriter.setMimetype(nextMimetype); - } - - // transform - if (transformer == null) - { - try - { - parentTransformers.get().push(this); - contentService.transform(currentReader, currentWriter, options); - } - finally - { - parentTransformers.get().pop(); - } - } - else - { - transformer.transform(currentReader, currentWriter, options); - } - - // Must clear the sourceNodeRef after the first transformation to avoid later - // transformers thinking the intermediate file is the original node. However as - // we put the original sourceNodeRef back at the end of this try block (so that we are - // not changing any data), we must setting the value to null just after the - // transformation. Really only needs to be done after the first transformation - // but doing it every time is simpler and faster. - options.setSourceNodeRef(null); - - // move the source on - currentReader = currentWriter.getReader(); - } - // done - } - finally - { - options.setSourceNodeRef(origSourceNodeRef); - } - } - - public List getIntermediateMimetypes() - { - return Collections.unmodifiableList(intermediateMimetypes); - } - - public List getIntermediateTransformers() - { - return Collections.unmodifiableList(transformers); - } - - /** - * Returns the transformer properties predefined (hard coded or implied) by this transformer. - */ - @Override - public String getComments(boolean available) - { - StringBuilder sb = new StringBuilder(); - sb.append(super.getComments(available)); - sb.append("# "); - sb.append(TransformerConfig.CONTENT); - sb.append(getName()); - sb.append(TransformerConfig.PIPELINE); - sb.append('='); - Iterator iterator = intermediateMimetypes.iterator(); - for (ContentTransformer transformer: transformers) - { - sb.append(transformer != null ? getSimpleName(transformer) : TransformerConfig.ANY); - if (iterator.hasNext()) - { - sb.append(TransformerConfig.PIPE); - String mimetype = iterator.next(); - if (mimetype != null && mimetype.length() != 0) - { - String extension = getMimetypeService().getExtension(mimetype); - sb.append(extension); - } - sb.append(TransformerConfig.PIPE); - } - } - sb.append('\n'); - return sb.toString(); - } -} +package org.alfresco.repo.content.transform; + +import java.beans.PropertyDescriptor; +import java.io.File; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.faces.el.MethodNotFoundException; + +import org.alfresco.api.AlfrescoPublicApi; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.content.filestore.FileContentWriter; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * Transformer that passes a document through several nested transformations + * in order to accomplish its goal. + * + * @author Derek Hulley + */ +@AlfrescoPublicApi +public class ComplexContentTransformer extends AbstractContentTransformer2 implements InitializingBean +{ + /** + * The logger + */ + private static Log logger = LogFactory.getLog(ComplexContentTransformer.class); + + /** + * Complex transformers contain lower level transformers. In order to find dynamic + * (defined as null) child transformers to use, they recursively check available + * transformers. It makes no sense to have a transformer that is its own child. + */ + static final ThreadLocal> parentTransformers = new ThreadLocal>() { + @Override + protected Deque initialValue() { + return new ArrayDeque(); + } + }; + + private List transformers; + private List intermediateMimetypes; + private Map transformationOptionOverrides; + private ContentService contentService; + + public ComplexContentTransformer() + { + } + + /** + * The list of transformers to use. If any element is null + * all possible transformers will be considered. If any element + * is null, the contentService property must be set. + *

+ * If a single transformer is supplied, then it will still be used. + * + * @param transformers list of at least one transformer + * + * @see #setContentService(ContentService) + */ + public void setTransformers(List transformers) + { + this.transformers = transformers; + } + + /** + * Set the intermediate mimetypes that the transformer must take the content + * through. If the transformation A..B..C is performed in order to + * simulate A..C, then B is the intermediate mimetype. There + * must always be n-1 intermediate mimetypes, where n is the + * number of {@link #setTransformers(List) transformers} taking part in the + * transformation. + * + * @param intermediateMimetypes intermediate mimetypes to transition the content + * through. + */ + public void setIntermediateMimetypes(List intermediateMimetypes) + { + this.intermediateMimetypes = intermediateMimetypes; + } + + /** + * Sets any properties to be set on the TransformationOption as passed in. + * This allows you to force certain properties to always be set on it, + * to control the transformers in a different way to their default. + * Note that only properties that are supported by the passed-in + * {@link TransformationOptions} are changed, others are ignored. + * @param transformationOptionOverrides Map + */ + public void setTransformationOptionOverrides( + Map transformationOptionOverrides) + { + this.transformationOptionOverrides = transformationOptionOverrides; + } + + /** + * Sets the ContentService. Only required if {@code null} transformers + * are provided to {@link #setTransformers(List)}. + * @param contentService + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Ensures that required properties have been set + */ + public void afterPropertiesSet() throws Exception + { + if (transformers == null || transformers.size() == 0) + { + throw new AlfrescoRuntimeException("At least one inner transformer must be supplied: " + this); + } + if (intermediateMimetypes == null || intermediateMimetypes.size() != transformers.size() - 1) + { + throw new AlfrescoRuntimeException( + "There must be n-1 intermediate mimetypes, where n is the number of transformers"); + } + if (getMimetypeService() == null) + { + throw new AlfrescoRuntimeException("'mimetypeService' is a required property"); + } + for (ContentTransformer transformer: transformers) + { + if (transformer == null) + { + if (contentService == null) + { + throw new AlfrescoRuntimeException("'contentService' is a required property if " + + "there are any null (dynamic) transformers"); + } + break; + } + } + } + + @Override + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, + TransformationOptions options) + { + if (!isSupportedTransformation(sourceMimetype, targetMimetype, options)) + { + return false; + } + + // Don't allow transformer to be its own child. + if (parentTransformers.get().contains(this)) + { + return false; + } + + overrideTransformationOptions(options); + + // Can use super isTransformableSize as it indirectly calls getLimits in this class + // which combines the limits from the first transformer. Other transformer in the chain + // are no checked as sizes are unknown. + return + isTransformableMimetype(sourceMimetype, targetMimetype, options) && + isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); + } + + /** + * Sets any transformation option overrides it can. + */ + private void overrideTransformationOptions(TransformationOptions options) + { + // Set any transformation options overrides if we can + if(options != null && transformationOptionOverrides != null) + { + for(String key : transformationOptionOverrides.keySet()) + { + if(PropertyUtils.isWriteable(options, key)) + { + try + { + PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(options, key); + Class propertyClass = pd.getPropertyType(); + + Object value = transformationOptionOverrides.get(key); + if(value != null) + { + if(propertyClass.isInstance(value)) + { + // Nothing to do + } + else if(value instanceof String && propertyClass.isInstance(Boolean.TRUE)) + { + // Use relaxed converter + value = TransformationOptions.relaxedBooleanTypeConverter.convert((String)value); + } + else + { + value = DefaultTypeConverter.INSTANCE.convert(propertyClass, value); + } + } + PropertyUtils.setProperty(options, key, value); + } + catch(MethodNotFoundException mnfe) {} + catch(NoSuchMethodException nsme) {} + catch(InvocationTargetException ite) {} + catch(IllegalAccessException iae) {} + } + else + { + logger.warn("Unable to set override Transformation Option " + key + " on " + options); + } + } + } + } + + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + boolean result = true; + String currentSourceMimetype = sourceMimetype; + Iterator transformerIterator = transformers.iterator(); + + // When using a wild card (null) intermediate transformer, don't + // say we support a transformation that one of the none null intermediate + // transformers can do on its own, to avoid double transformations. + // Not done when there are no wild card transformers, as there are cases + // where it makes sense to go via an intermediate format (quality/speed). + while (transformerIterator.hasNext()) + { + ContentTransformer transformer = transformerIterator.next(); + if (transformer == null) + { + transformerIterator = transformers.iterator(); + while (transformerIterator.hasNext()) + { + transformer = transformerIterator.next(); + if (transformer != null) + { + if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options)) + { + return false; + } + } + } + break; + } + } + + transformerIterator = transformers.iterator(); + Iterator intermediateMimetypeIterator = intermediateMimetypes.iterator(); + while (transformerIterator.hasNext()) + { + ContentTransformer transformer = transformerIterator.next(); + + // determine the target mimetype. This is the final target if we are on the last transformation + String currentTargetMimetype = transformerIterator.hasNext() ? intermediateMimetypeIterator.next() : targetMimetype; + if (transformer == null) + { + try + { + parentTransformers.get().push(this); + @SuppressWarnings("deprecation") + List firstTansformers = contentService.getActiveTransformers( + currentSourceMimetype, -1, currentTargetMimetype, options); + if (firstTansformers.isEmpty()) + { + result = false; + break; + } + } + finally + { + parentTransformers.get().pop(); + } + } + else + { + if (transformer.isTransformable(currentSourceMimetype, -1, currentTargetMimetype, options) == false) + { + result = false; + break; + } + } + + // move on + currentSourceMimetype = currentTargetMimetype; + } + + return result; + } + + /** + * Indicates if 'page' limits are supported by the first transformer in the chain. + * If the first transformer is dynamic, all possible first transformers must support it. + * @return true if the first transformer supports them. + */ + @Override + protected boolean isPageLimitSupported(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + boolean pageLimitSupported; + ContentTransformer firstTransformer = transformers.get(0); + String firstTargetMimetype = intermediateMimetypes.get(0); + if (firstTransformer == null) + { + try + { + parentTransformers.get().push(this); + @SuppressWarnings("deprecation") + List firstTansformers = contentService.getActiveTransformers( + sourceMimetype, -1, firstTargetMimetype, options); + pageLimitSupported = !firstTansformers.isEmpty(); + if (pageLimitSupported) + { + for (ContentTransformer transformer: firstTansformers) + { + if (!isPageLimitSupported(transformer, sourceMimetype, targetMimetype, options)) + { + pageLimitSupported = false; + break; + } + } + } + } + finally + { + parentTransformers.get().pop(); + } + } + else + { + pageLimitSupported = isPageLimitSupported(firstTransformer, sourceMimetype, targetMimetype, options); + } + return pageLimitSupported; + } + + private boolean isPageLimitSupported(ContentTransformer transformer, String sourceMimetype, + String targetMimetype, TransformationOptions options) + { + return (transformer instanceof AbstractContentTransformerLimits) + ? ((AbstractContentTransformerLimits)transformer).isPageLimitSupported(sourceMimetype, targetMimetype, options) + : false; + } + + /** + * Returns the limits from this transformer combined with those of the first transformer in the chain. + * If the first transformer is dynamic, the lowest common denominator between all possible first transformers + * are combined. + */ + protected TransformationOptionLimits getLimits(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + TransformationOptionLimits firstTransformerLimits = null; + TransformationOptionLimits limits = super.getLimits(sourceMimetype, targetMimetype, options); + ContentTransformer firstTransformer = transformers.get(0); + String firstTargetMimetype = intermediateMimetypes.get(0); + if (firstTransformer == null) + { + try + { + parentTransformers.get().push(this); + @SuppressWarnings("deprecation") + List firstTansformers = contentService.getActiveTransformers( + sourceMimetype, -1, firstTargetMimetype, options); + if (!firstTansformers.isEmpty()) + { + for (ContentTransformer transformer: firstTansformers) + { + if (transformer instanceof AbstractContentTransformerLimits) + { + TransformationOptionLimits transformerLimits = ((AbstractContentTransformerLimits)transformer). + getLimits(sourceMimetype, firstTargetMimetype, options); + firstTransformerLimits = (firstTransformerLimits == null) + ? transformerLimits + : firstTransformerLimits.combineUpper(transformerLimits); + } + } + } + } + finally + { + parentTransformers.get().pop(); + } + } + else + { + if (firstTransformer instanceof AbstractContentTransformerLimits) + { + firstTransformerLimits = ((AbstractContentTransformerLimits)firstTransformer). + getLimits(sourceMimetype, firstTargetMimetype, options); + } + } + + if (firstTransformerLimits != null) + { + limits = limits.combine(firstTransformerLimits); + } + + return limits; + } + + /** + * @see org.alfresco.repo.content.transform.AbstractContentTransformer2#transformInternal(org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter, org.alfresco.service.cmr.repository.TransformationOptions) + */ + @Override + public void transformInternal( + ContentReader reader, + ContentWriter writer, + TransformationOptions options) throws Exception + { + NodeRef origSourceNodeRef = options.getSourceNodeRef(); + try + { + ContentReader currentReader = reader; + + Iterator transformerIterator = transformers.iterator(); + Iterator intermediateMimetypeIterator = intermediateMimetypes.iterator(); + while (transformerIterator.hasNext()) + { + ContentTransformer transformer = transformerIterator.next(); + // determine the target mimetype. This is the final target if we are on the last transformation + ContentWriter currentWriter = null; + if (!transformerIterator.hasNext()) + { + currentWriter = writer; + } + else + { + String nextMimetype = intermediateMimetypeIterator.next(); + // make a temp file writer with the correct extension + String sourceExt = getMimetypeService().getExtension(currentReader.getMimetype()); + String targetExt = getMimetypeService().getExtension(nextMimetype); + File tempFile = TempFileProvider.createTempFile( + "ComplextTransformer_intermediate_" + sourceExt + "_", + "." + targetExt); + currentWriter = new FileContentWriter(tempFile); + currentWriter.setMimetype(nextMimetype); + } + + // transform + if (transformer == null) + { + try + { + parentTransformers.get().push(this); + contentService.transform(currentReader, currentWriter, options); + } + finally + { + parentTransformers.get().pop(); + } + } + else + { + transformer.transform(currentReader, currentWriter, options); + } + + // Must clear the sourceNodeRef after the first transformation to avoid later + // transformers thinking the intermediate file is the original node. However as + // we put the original sourceNodeRef back at the end of this try block (so that we are + // not changing any data), we must setting the value to null just after the + // transformation. Really only needs to be done after the first transformation + // but doing it every time is simpler and faster. + options.setSourceNodeRef(null); + + // move the source on + currentReader = currentWriter.getReader(); + } + // done + } + finally + { + options.setSourceNodeRef(origSourceNodeRef); + } + } + + public List getIntermediateMimetypes() + { + return Collections.unmodifiableList(intermediateMimetypes); + } + + public List getIntermediateTransformers() + { + return Collections.unmodifiableList(transformers); + } + + /** + * Returns the transformer properties predefined (hard coded or implied) by this transformer. + */ + @Override + public String getComments(boolean available) + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getComments(available)); + sb.append("# "); + sb.append(TransformerConfig.CONTENT); + sb.append(getName()); + sb.append(TransformerConfig.PIPELINE); + sb.append('='); + Iterator iterator = intermediateMimetypes.iterator(); + for (ContentTransformer transformer: transformers) + { + sb.append(transformer != null ? getSimpleName(transformer) : TransformerConfig.ANY); + if (iterator.hasNext()) + { + sb.append(TransformerConfig.PIPE); + String mimetype = iterator.next(); + if (mimetype != null && mimetype.length() != 0) + { + String extension = getMimetypeService().getExtension(mimetype); + sb.append(extension); + } + sb.append(TransformerConfig.PIPE); + } + } + sb.append('\n'); + return sb.toString(); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java b/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java index 43d4f8fdc4..50eac2fe72 100644 --- a/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java @@ -1,26 +1,26 @@ -package org.alfresco.repo.content.transform; - -import org.alfresco.repo.content.MimetypeMap; -import org.apache.tika.parser.Parser; -import org.apache.tika.parser.microsoft.OfficeParser; - -/** - * Uses Apache Tika and - * Apache POI to transform - * Outlook email msg files. - * - * @author Nick Burch - */ -public class MailContentTransformer extends TikaPoweredContentTransformer -{ - public MailContentTransformer() { - super(new String[] { - MimetypeMap.MIMETYPE_OUTLOOK_MSG - }); - } - - @Override - protected Parser getParser() { - return new OfficeParser(); - } -} +package org.alfresco.repo.content.transform; + +import org.alfresco.repo.content.MimetypeMap; +import org.apache.tika.parser.Parser; +import org.apache.tika.parser.microsoft.OfficeParser; + +/** + * Uses Apache Tika and + * Apache POI to transform + * Outlook email msg files. + * + * @author Nick Burch + */ +public class MailContentTransformer extends TikaPoweredContentTransformer +{ + public MailContentTransformer() { + super(new String[] { + MimetypeMap.MIMETYPE_OUTLOOK_MSG + }); + } + + @Override + protected Parser getParser() { + return new OfficeParser(); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java index 0d0a8459fa..3978980f6c 100644 --- a/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java @@ -1,273 +1,273 @@ -package org.alfresco.repo.content.transform; - -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.api.AlfrescoPublicApi; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.TransformationOptions; -import org.alfresco.util.TempFileProvider; -import org.alfresco.util.exec.RuntimeExec; -import org.alfresco.util.exec.RuntimeExec.ExecutionResult; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -/** - * This configurable wrapper is able to execute any command line transformation that - * accepts an input and an output file on the command line. - *

- * The following parameters are use: - *

    - *
  • {@link #VAR_SOURCE target} - full path to the source file
  • - *
  • {@link #VAR_TARGET source} - full path to the target file
  • - *
- * Provided that the command executed ultimately transforms the source file - * and leaves the result in the target file, the transformation should be - * successful. - *

- * NOTE: It is only the contents of the files that can be transformed. - * Any attempt to modify the source or target file metadata will, at best, have - * no effect, but may ultimately lead to the transformation failing. This is - * because the files provided are both temporary files that reside in a location - * outside the system's content store. - *

- * This transformer requires the setting of the explicitTransformations - * property. - * - * @see org.alfresco.util.exec.RuntimeExec - * - * @since 1.1 - * @author Derek Hulley - */ -@AlfrescoPublicApi -public class RuntimeExecutableContentTransformerWorker extends ContentTransformerHelper implements ContentTransformerWorker, InitializingBean -{ - public static final String VAR_SOURCE = "source"; - public static final String VAR_TARGET = "target"; - public static final String VAR_PAGE_RANGE = "pageRange"; - - private static Log logger = LogFactory.getLog(RuntimeExecutableContentTransformerWorker.class); - - private boolean available; - private RuntimeExec checkCommand; - private RuntimeExec transformCommand; - - /** Stores the output from the check command */ - private String versionString; - - public RuntimeExecutableContentTransformerWorker() - { - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(this.getClass().getSimpleName()) - .append("[ transform=").append(transformCommand).append("\n") - .append("]"); - return sb.toString(); - } - - /** - * Set the runtime executer that will be called as part of the initialisation - * to determine if the transformer is able to function. This is optional, but allows - * the transformer registry to detect and avoid using this instance if it is not working. - *

- * The command will be considered to have failed if the - * - * @param checkCommand the initialisation check command - */ - public void setCheckCommand(RuntimeExec checkCommand) - { - this.checkCommand = checkCommand; - } - - /** - * Set the runtime executer that will called to perform the actual transformation. - * - * @param transformCommand the runtime transform command - */ - public void setTransformCommand(RuntimeExec transformCommand) - { - this.transformCommand = transformCommand; - } - - /** - * A comma or space separated list of values that, if returned by the executed command, - * indicate an error value. This defaults to "1, 2". - * - * @param errCodesStr String - */ - public void setErrorCodes(String errCodesStr) - { - throw new AlfrescoRuntimeException("content.runtime_exec.property_moved"); - } - - - /** - * Executes the check command, if present. Any errors will result in this component - * being rendered unusable within the transformer registry, but may still be called - * directly. - */ - public void afterPropertiesSet() - { - if (transformCommand == null) - { - throw new AlfrescoRuntimeException("Mandatory property 'transformCommand' not set"); - } - - // execute the command - if (checkCommand != null) - { - ExecutionResult result = checkCommand.execute(); - // check the return code - if (this.available = result.getSuccess()) - { - this.versionString = result.getStdOut().trim(); - } - else - { - logger.error("Failed to start a runtime executable content transformer: \n" + result); - } - } - else - { - // no check - just assume it is available - available = true; - } - } - - /** - * If the initialization failed, then it returns 0.0. - * Otherwise the explicit transformations are checked for the reliability. - * - * @return Returns 1.0 if initialization succeeded, otherwise 0.0. - * - * @see AbstractContentTransformer#setExplicitTransformations(List) - */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) - { - return available; - } - - @Override - public String getComments(boolean available) - { - return ""; - } - - /** - * Signals whether this transformer is available. - * - * @return true, if is available - */ - public boolean isAvailable() - { - return this.available; - } - - /** - * Gets the version string captured from the check command. - * - * @return the version string - */ - public String getVersionString() - { - return this.versionString; - } - - /** - * Converts the source and target content to temporary files with the - * correct extensions for the mimetype that they map to. - * - */ - public final void transform( - ContentReader reader, - ContentWriter writer, - TransformationOptions options) throws Exception - { - // get mimetypes - String sourceMimetype = getMimetype(reader); - String targetMimetype = getMimetype(writer); - - // get the extensions to use - String sourceExtension = getMimetypeService().getExtension(sourceMimetype); - String targetExtension = getMimetypeService().getExtension(targetMimetype); - if (sourceExtension == null || targetExtension == null) - { - throw new AlfrescoRuntimeException("Unknown extensions for mimetypes: \n" + - " source mimetype: " + sourceMimetype + "\n" + - " source extension: " + sourceExtension + "\n" + - " target mimetype: " + targetMimetype + "\n" + - " target extension: " + targetExtension); - } - - // create required temp files - File sourceFile = TempFileProvider.createTempFile( - getClass().getSimpleName() + "_source_", - "." + sourceExtension); - File targetFile = TempFileProvider.createTempFile( - getClass().getSimpleName() + "_target_", - "." + targetExtension); - - Map properties = new HashMap(5); - // copy options over - Map optionsMap = options.toMap(); - for (Map.Entry entry : optionsMap.entrySet()) - { - String key = entry.getKey(); - Object value = entry.getValue(); - properties.put(key, (value == null ? null : value.toString())); - } - // add the source and target properties - properties.put(VAR_SOURCE, sourceFile.getAbsolutePath()); - properties.put(VAR_TARGET, targetFile.getAbsolutePath()); - properties.put(VAR_PAGE_RANGE, "0-"+(options.getPageLimit() >=0 ? options.getPageLimit() : "")); - - // pull reader file into source temp file - reader.getContent(sourceFile); - - // execute the transformation command - long timeoutMs = options.getTimeoutMs(); - ExecutionResult result = null; - try - { - result = transformCommand.execute(properties, timeoutMs); - } - catch (Throwable e) - { - throw new ContentIOException("Transformation failed during command execution: \n" + transformCommand, e); - } - - // check - if (!result.getSuccess()) - { - throw new ContentIOException("Transformation failed - status indicates an error: \n" + result); - } - - // check that the file was created - if (!targetFile.exists()) - { - throw new ContentIOException("Transformation failed - target file doesn't exist: \n" + result); - } - // copy the target file back into the repo - writer.putContent(targetFile); - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Transformation completed: \n" + - " source: " + reader + "\n" + - " target: " + writer + "\n" + - " options: " + options + "\n" + - " result: \n" + result); - } - } -} +package org.alfresco.repo.content.transform; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.api.AlfrescoPublicApi; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.alfresco.util.TempFileProvider; +import org.alfresco.util.exec.RuntimeExec; +import org.alfresco.util.exec.RuntimeExec.ExecutionResult; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * This configurable wrapper is able to execute any command line transformation that + * accepts an input and an output file on the command line. + *

+ * The following parameters are use: + *

    + *
  • {@link #VAR_SOURCE target} - full path to the source file
  • + *
  • {@link #VAR_TARGET source} - full path to the target file
  • + *
+ * Provided that the command executed ultimately transforms the source file + * and leaves the result in the target file, the transformation should be + * successful. + *

+ * NOTE: It is only the contents of the files that can be transformed. + * Any attempt to modify the source or target file metadata will, at best, have + * no effect, but may ultimately lead to the transformation failing. This is + * because the files provided are both temporary files that reside in a location + * outside the system's content store. + *

+ * This transformer requires the setting of the explicitTransformations + * property. + * + * @see org.alfresco.util.exec.RuntimeExec + * + * @since 1.1 + * @author Derek Hulley + */ +@AlfrescoPublicApi +public class RuntimeExecutableContentTransformerWorker extends ContentTransformerHelper implements ContentTransformerWorker, InitializingBean +{ + public static final String VAR_SOURCE = "source"; + public static final String VAR_TARGET = "target"; + public static final String VAR_PAGE_RANGE = "pageRange"; + + private static Log logger = LogFactory.getLog(RuntimeExecutableContentTransformerWorker.class); + + private boolean available; + private RuntimeExec checkCommand; + private RuntimeExec transformCommand; + + /** Stores the output from the check command */ + private String versionString; + + public RuntimeExecutableContentTransformerWorker() + { + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getSimpleName()) + .append("[ transform=").append(transformCommand).append("\n") + .append("]"); + return sb.toString(); + } + + /** + * Set the runtime executer that will be called as part of the initialisation + * to determine if the transformer is able to function. This is optional, but allows + * the transformer registry to detect and avoid using this instance if it is not working. + *

+ * The command will be considered to have failed if the + * + * @param checkCommand the initialisation check command + */ + public void setCheckCommand(RuntimeExec checkCommand) + { + this.checkCommand = checkCommand; + } + + /** + * Set the runtime executer that will called to perform the actual transformation. + * + * @param transformCommand the runtime transform command + */ + public void setTransformCommand(RuntimeExec transformCommand) + { + this.transformCommand = transformCommand; + } + + /** + * A comma or space separated list of values that, if returned by the executed command, + * indicate an error value. This defaults to "1, 2". + * + * @param errCodesStr String + */ + public void setErrorCodes(String errCodesStr) + { + throw new AlfrescoRuntimeException("content.runtime_exec.property_moved"); + } + + + /** + * Executes the check command, if present. Any errors will result in this component + * being rendered unusable within the transformer registry, but may still be called + * directly. + */ + public void afterPropertiesSet() + { + if (transformCommand == null) + { + throw new AlfrescoRuntimeException("Mandatory property 'transformCommand' not set"); + } + + // execute the command + if (checkCommand != null) + { + ExecutionResult result = checkCommand.execute(); + // check the return code + if (this.available = result.getSuccess()) + { + this.versionString = result.getStdOut().trim(); + } + else + { + logger.error("Failed to start a runtime executable content transformer: \n" + result); + } + } + else + { + // no check - just assume it is available + available = true; + } + } + + /** + * If the initialization failed, then it returns 0.0. + * Otherwise the explicit transformations are checked for the reliability. + * + * @return Returns 1.0 if initialization succeeded, otherwise 0.0. + * + * @see AbstractContentTransformer#setExplicitTransformations(List) + */ + public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + return available; + } + + @Override + public String getComments(boolean available) + { + return ""; + } + + /** + * Signals whether this transformer is available. + * + * @return true, if is available + */ + public boolean isAvailable() + { + return this.available; + } + + /** + * Gets the version string captured from the check command. + * + * @return the version string + */ + public String getVersionString() + { + return this.versionString; + } + + /** + * Converts the source and target content to temporary files with the + * correct extensions for the mimetype that they map to. + * + */ + public final void transform( + ContentReader reader, + ContentWriter writer, + TransformationOptions options) throws Exception + { + // get mimetypes + String sourceMimetype = getMimetype(reader); + String targetMimetype = getMimetype(writer); + + // get the extensions to use + String sourceExtension = getMimetypeService().getExtension(sourceMimetype); + String targetExtension = getMimetypeService().getExtension(targetMimetype); + if (sourceExtension == null || targetExtension == null) + { + throw new AlfrescoRuntimeException("Unknown extensions for mimetypes: \n" + + " source mimetype: " + sourceMimetype + "\n" + + " source extension: " + sourceExtension + "\n" + + " target mimetype: " + targetMimetype + "\n" + + " target extension: " + targetExtension); + } + + // create required temp files + File sourceFile = TempFileProvider.createTempFile( + getClass().getSimpleName() + "_source_", + "." + sourceExtension); + File targetFile = TempFileProvider.createTempFile( + getClass().getSimpleName() + "_target_", + "." + targetExtension); + + Map properties = new HashMap(5); + // copy options over + Map optionsMap = options.toMap(); + for (Map.Entry entry : optionsMap.entrySet()) + { + String key = entry.getKey(); + Object value = entry.getValue(); + properties.put(key, (value == null ? null : value.toString())); + } + // add the source and target properties + properties.put(VAR_SOURCE, sourceFile.getAbsolutePath()); + properties.put(VAR_TARGET, targetFile.getAbsolutePath()); + properties.put(VAR_PAGE_RANGE, "0-"+(options.getPageLimit() >=0 ? options.getPageLimit() : "")); + + // pull reader file into source temp file + reader.getContent(sourceFile); + + // execute the transformation command + long timeoutMs = options.getTimeoutMs(); + ExecutionResult result = null; + try + { + result = transformCommand.execute(properties, timeoutMs); + } + catch (Throwable e) + { + throw new ContentIOException("Transformation failed during command execution: \n" + transformCommand, e); + } + + // check + if (!result.getSuccess()) + { + throw new ContentIOException("Transformation failed - status indicates an error: \n" + result); + } + + // check that the file was created + if (!targetFile.exists()) + { + throw new ContentIOException("Transformation failed - target file doesn't exist: \n" + result); + } + // copy the target file back into the repo + writer.putContent(targetFile); + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Transformation completed: \n" + + " source: " + reader + "\n" + + " target: " + writer + "\n" + + " options: " + options + "\n" + + " result: \n" + result); + } + } +} diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java b/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java index 14dcd6102f..9267b280ba 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java @@ -1,181 +1,181 @@ -package org.alfresco.repo.descriptor; - -import java.security.Principal; -import java.util.Date; -import java.util.Properties; - -import org.alfresco.repo.mode.ServerModeProvider; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; -import org.alfresco.service.descriptor.Descriptor; -import org.alfresco.service.descriptor.DescriptorService; -import org.alfresco.service.license.LicenseDescriptor; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * Provide a Repository Startup Log - * - * @author davidc - */ -public class DescriptorStartupLog extends AbstractLifecycleBean -{ - // Logger - private static final Log logger = LogFactory.getLog(DescriptorService.class); - - // Dependencies - private DescriptorService descriptorService; - private TransactionService transactionService; - private ServerModeProvider serverModeProvider; - - private final String SYSTEM_INFO_STARTUP = "system.info.startup"; - private final String SYSTEM_WARN_READONLY = "system.warn.readonly"; - - /** - * @param descriptorService Descriptor Service - */ - public void setDescriptorService(DescriptorService descriptorService) - { - this.descriptorService = descriptorService; - } - - /** - * @param transactionService service to tell about read-write mode - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setServerModeProvider(ServerModeProvider serverModeProvider) - { - this.serverModeProvider = serverModeProvider; - } - - @Override - protected void onBootstrap(ApplicationEvent event) - { - // - // log output of VM stats - // - Properties properties = System.getProperties(); - String version = (properties.get("java.runtime.version") == null) ? "unknown" : (String)properties.get("java.runtime.version"); - long maxHeap = Runtime.getRuntime().maxMemory(); - float maxHeapMB = maxHeap / 1024l; - maxHeapMB = maxHeapMB / 1024l; - if (logger.isInfoEnabled()) - { - logger.info(String.format("Alfresco JVM - v%s; maximum heap size %.3fMB", version, maxHeapMB)); - } - if (logger.isWarnEnabled()) - { - if (version.startsWith("1.2") || version.startsWith("1.3") || version.startsWith("1.4")) - { - logger.warn(String.format("Alfresco JVM - WARNING - v1.5 is required; currently using v%s", version)); - } - if (maxHeapMB < 500) - { - logger.warn(String.format("Alfresco JVM - WARNING - maximum heap size %.3fMB is less than recommended 512MB", maxHeapMB)); - } - } - - // Log License Descriptors (if applicable) - LicenseDescriptor license = descriptorService.getLicenseDescriptor(); - if (license != null && logger.isInfoEnabled()) - { - LicenseMode licenseMode = license.getLicenseMode(); - - String msg = "Alfresco license: Mode " + licenseMode; - - if(license.isClusterEnabled()) - { - msg += ", cluster:enabled"; - } - else - { - msg += ", NO CLUSTER"; - } - - String holder = license.getHolderOrganisation(); - if (holder != null) - { - msg += " granted to " + holder; - } - - Date validUntil = license.getValidUntil(); - - if (validUntil != null) - { - Integer days = license.getDays(); - Integer remainingDays = license.getRemainingDays(); - - msg += " limited to " + days + " days expiring " + validUntil + " (" + remainingDays + " days remaining)."; - } - else - { - msg += " (does not expire)."; - } - - Long maxUsers = license.getMaxUsers(); - if (maxUsers != null) - { - msg += " User limit is " + maxUsers + "."; - } - Long maxDocs = license.getMaxDocs(); - if (maxDocs != null) - { - msg += " Content Object limit is " + maxDocs + "."; - } - - /* - * This is an important information logging since it logs the license - */ - logger.info(msg); - } - - // Log Repository Descriptors - if (logger.isInfoEnabled()) - { - logger.info("Server Mode :" + serverModeProvider.getServerMode()); - Descriptor serverDescriptor = descriptorService.getServerDescriptor(); - Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor(); - Descriptor installedRepoDescriptor = descriptorService.getInstalledRepositoryDescriptor(); - - String serverEdition = serverDescriptor.getEdition(); - - String currentVersion = currentDescriptor.getVersion(); - int currentSchemaVersion = currentDescriptor.getSchema(); - LicenseMode currentMode = currentDescriptor.getLicenseMode(); - - String installedRepoVersion = installedRepoDescriptor.getVersion(); - int installedSchemaVersion = installedRepoDescriptor.getSchema(); - - - /* - * Alfresco started - */ - Object[] params = new Object[] { - serverEdition, - currentMode != LicenseMode.TEAM ? "" : (" " + currentMode), // only append TEAM - (!AuthenticationUtil.isMtEnabled() ? "" : (" Multi-Tenant")), - currentVersion, currentSchemaVersion, installedRepoVersion, installedSchemaVersion}; - logger.info(I18NUtil.getMessage(SYSTEM_INFO_STARTUP, params)); - } - - // Issue a warning if the system is in read-only mode - if (logger.isWarnEnabled() && !transactionService.getAllowWrite()) - { - logger.warn(I18NUtil.getMessage(SYSTEM_WARN_READONLY)); - } - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - // NOOP - } +package org.alfresco.repo.descriptor; + +import java.security.Principal; +import java.util.Date; +import java.util.Properties; + +import org.alfresco.repo.mode.ServerModeProvider; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; +import org.alfresco.service.descriptor.Descriptor; +import org.alfresco.service.descriptor.DescriptorService; +import org.alfresco.service.license.LicenseDescriptor; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Provide a Repository Startup Log + * + * @author davidc + */ +public class DescriptorStartupLog extends AbstractLifecycleBean +{ + // Logger + private static final Log logger = LogFactory.getLog(DescriptorService.class); + + // Dependencies + private DescriptorService descriptorService; + private TransactionService transactionService; + private ServerModeProvider serverModeProvider; + + private final String SYSTEM_INFO_STARTUP = "system.info.startup"; + private final String SYSTEM_WARN_READONLY = "system.warn.readonly"; + + /** + * @param descriptorService Descriptor Service + */ + public void setDescriptorService(DescriptorService descriptorService) + { + this.descriptorService = descriptorService; + } + + /** + * @param transactionService service to tell about read-write mode + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setServerModeProvider(ServerModeProvider serverModeProvider) + { + this.serverModeProvider = serverModeProvider; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + // + // log output of VM stats + // + Properties properties = System.getProperties(); + String version = (properties.get("java.runtime.version") == null) ? "unknown" : (String)properties.get("java.runtime.version"); + long maxHeap = Runtime.getRuntime().maxMemory(); + float maxHeapMB = maxHeap / 1024l; + maxHeapMB = maxHeapMB / 1024l; + if (logger.isInfoEnabled()) + { + logger.info(String.format("Alfresco JVM - v%s; maximum heap size %.3fMB", version, maxHeapMB)); + } + if (logger.isWarnEnabled()) + { + if (version.startsWith("1.2") || version.startsWith("1.3") || version.startsWith("1.4")) + { + logger.warn(String.format("Alfresco JVM - WARNING - v1.5 is required; currently using v%s", version)); + } + if (maxHeapMB < 500) + { + logger.warn(String.format("Alfresco JVM - WARNING - maximum heap size %.3fMB is less than recommended 512MB", maxHeapMB)); + } + } + + // Log License Descriptors (if applicable) + LicenseDescriptor license = descriptorService.getLicenseDescriptor(); + if (license != null && logger.isInfoEnabled()) + { + LicenseMode licenseMode = license.getLicenseMode(); + + String msg = "Alfresco license: Mode " + licenseMode; + + if(license.isClusterEnabled()) + { + msg += ", cluster:enabled"; + } + else + { + msg += ", NO CLUSTER"; + } + + String holder = license.getHolderOrganisation(); + if (holder != null) + { + msg += " granted to " + holder; + } + + Date validUntil = license.getValidUntil(); + + if (validUntil != null) + { + Integer days = license.getDays(); + Integer remainingDays = license.getRemainingDays(); + + msg += " limited to " + days + " days expiring " + validUntil + " (" + remainingDays + " days remaining)."; + } + else + { + msg += " (does not expire)."; + } + + Long maxUsers = license.getMaxUsers(); + if (maxUsers != null) + { + msg += " User limit is " + maxUsers + "."; + } + Long maxDocs = license.getMaxDocs(); + if (maxDocs != null) + { + msg += " Content Object limit is " + maxDocs + "."; + } + + /* + * This is an important information logging since it logs the license + */ + logger.info(msg); + } + + // Log Repository Descriptors + if (logger.isInfoEnabled()) + { + logger.info("Server Mode :" + serverModeProvider.getServerMode()); + Descriptor serverDescriptor = descriptorService.getServerDescriptor(); + Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor(); + Descriptor installedRepoDescriptor = descriptorService.getInstalledRepositoryDescriptor(); + + String serverEdition = serverDescriptor.getEdition(); + + String currentVersion = currentDescriptor.getVersion(); + int currentSchemaVersion = currentDescriptor.getSchema(); + LicenseMode currentMode = currentDescriptor.getLicenseMode(); + + String installedRepoVersion = installedRepoDescriptor.getVersion(); + int installedSchemaVersion = installedRepoDescriptor.getSchema(); + + + /* + * Alfresco started + */ + Object[] params = new Object[] { + serverEdition, + currentMode != LicenseMode.TEAM ? "" : (" " + currentMode), // only append TEAM + (!AuthenticationUtil.isMtEnabled() ? "" : (" Multi-Tenant")), + currentVersion, currentSchemaVersion, installedRepoVersion, installedSchemaVersion}; + logger.info(I18NUtil.getMessage(SYSTEM_INFO_STARTUP, params)); + } + + // Issue a warning if the system is in read-only mode + if (logger.isWarnEnabled() && !transactionService.getAllowWrite()) + { + logger.warn(I18NUtil.getMessage(SYSTEM_WARN_READONLY)); + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/descriptor/LicenseResourceComponent.java b/source/java/org/alfresco/repo/descriptor/LicenseResourceComponent.java index 8a63df82cd..54c80c75cf 100644 --- a/source/java/org/alfresco/repo/descriptor/LicenseResourceComponent.java +++ b/source/java/org/alfresco/repo/descriptor/LicenseResourceComponent.java @@ -1,45 +1,45 @@ - -package org.alfresco.repo.descriptor; - -/** - * The licence resource component knows the locations where license files - * may be found. - * - * Locations are suitable to be loaded by spring's getResource method. - * - */ -public class LicenseResourceComponent -{ - public LicenseResourceComponent() - { - } - - private String externalLicenseLocation = "*.lic"; - private String embeddedLicenseLocation = "/WEB-INF/alfresco/license/*.lic"; - private String sharedLicenseLocation = "classpath*:/alfresco/extension/license/*.lic"; - - public void setExternalLicenseLocation(String externalLicenseLocation) - { - this.externalLicenseLocation = externalLicenseLocation; - } - public String getExternalLicenseLocation() - { - return externalLicenseLocation; - } - public void setEmbeddedLicenseLocation(String embeddedLicenseLocation) - { - this.embeddedLicenseLocation = embeddedLicenseLocation; - } - public String getEmbeddedLicenseLocation() - { - return embeddedLicenseLocation; - } - public void setSharedLicenseLocation(String sharedLicenseLocation) - { - this.sharedLicenseLocation = sharedLicenseLocation; - } - public String getSharedLicenseLocation() - { - return sharedLicenseLocation; - } -} + +package org.alfresco.repo.descriptor; + +/** + * The licence resource component knows the locations where license files + * may be found. + * + * Locations are suitable to be loaded by spring's getResource method. + * + */ +public class LicenseResourceComponent +{ + public LicenseResourceComponent() + { + } + + private String externalLicenseLocation = "*.lic"; + private String embeddedLicenseLocation = "/WEB-INF/alfresco/license/*.lic"; + private String sharedLicenseLocation = "classpath*:/alfresco/extension/license/*.lic"; + + public void setExternalLicenseLocation(String externalLicenseLocation) + { + this.externalLicenseLocation = externalLicenseLocation; + } + public String getExternalLicenseLocation() + { + return externalLicenseLocation; + } + public void setEmbeddedLicenseLocation(String embeddedLicenseLocation) + { + this.embeddedLicenseLocation = embeddedLicenseLocation; + } + public String getEmbeddedLicenseLocation() + { + return embeddedLicenseLocation; + } + public void setSharedLicenseLocation(String sharedLicenseLocation) + { + this.sharedLicenseLocation = sharedLicenseLocation; + } + public String getSharedLicenseLocation() + { + return sharedLicenseLocation; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java index 13da57040a..878fe73373 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java @@ -1,756 +1,756 @@ -package org.alfresco.repo.dictionary; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.EmptyContentReader; -import org.alfresco.repo.dictionary.DynamicModelPolicies.OnLoadDynamicModel; -import org.alfresco.repo.i18n.MessageDeployer; -import org.alfresco.repo.i18n.MessageService; -import org.alfresco.repo.policy.ClassPolicyDelegate; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.tenant.TenantDeployer; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.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.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; - -/** - * Bootstrap the dictionary from specified locations within the repository - * - * @author Roy Wetherall, JanV, sglover - */ -public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean -implements TenantDeployer, DictionaryListener, /*TenantDictionaryListener, */MessageDeployer -{ - // Logging support - private static Log logger = LogFactory.getLog(DictionaryRepositoryBootstrap.class); - - /** Locations in the repository from which models should be loaded */ - private List repositoryModelsLocations = new ArrayList(); - - /** Locations in the repository from which messages should be loaded */ - private List repositoryMessagesLocations = new ArrayList(); - - /** Dictionary DAO */ - private DictionaryDAO dictionaryDAO = null; - - /** The content service */ - private ContentService contentService; - - /** The node service */ - private NodeService nodeService; - - /** The tenant admin service */ - private TenantAdminService tenantAdminService; - - /** The namespace service */ - private NamespaceService namespaceService; - - /** The message service */ - private MessageService messageService; - - /** The transaction service */ - private TransactionService transactionService; - - /** The policy component */ - private PolicyComponent policyComponent; - - - /** - * Sets the Dictionary DAO - * - * @param dictionaryDAO DictionaryDAO - */ - public void setDictionaryDAO(DictionaryDAO dictionaryDAO) - { - this.dictionaryDAO = dictionaryDAO; - } - - /** - * Set the content service - * - * @param contentService the content service - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public PolicyComponent getPolicyComponent() - { - return policyComponent; - } - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - /** - * Set the tenant admin service - * - * @param tenantAdminService the tenant admin service - */ - public void setTenantAdminService(TenantAdminService tenantAdminService) - { - this.tenantAdminService = tenantAdminService; - } - - /** - * Set the namespace service - * - * @param namespaceService the namespace service - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * Set the message service - * - * @param messageService the message service - */ - public void setMessageService(MessageService messageService) - { - this.messageService = messageService; - } - - /** - * Set the transaction service - * - * @param transactionService the transaction service - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - /** - * Set the repository models locations - * - * @param repositoryLocations list of the repository models locations - */ public void setRepositoryModelsLocations( - List repositoryLocations) - { - this.repositoryModelsLocations = repositoryLocations; - } - - /** - * Set the repository messages (resource bundle) locations - * - * @param repositoryLocations - * list of the repository messages locations - */ - public void setRepositoryMessagesLocations( - List repositoryLocations) - { - this.repositoryMessagesLocations = repositoryLocations; - } - - private ClassPolicyDelegate onLoadDynamicModelDelegate; - - /** - * Initialise - after bootstrap of schema and tenant admin service - */ - public void init() - { - PropertyCheck.mandatory(this, "dictionaryDAO", dictionaryDAO); - PropertyCheck.mandatory(this, "contentService", contentService); - PropertyCheck.mandatory(this, "nodeService", nodeService); - PropertyCheck.mandatory(this, "tenantAdminService", tenantAdminService); - PropertyCheck.mandatory(this, "namespaceService", namespaceService); - PropertyCheck.mandatory(this, "messageService", messageService); - PropertyCheck.mandatory(this, "transactionService", transactionService); - PropertyCheck.mandatory(this, "policyComponent", policyComponent); - - if(onLoadDynamicModelDelegate == null) - { - onLoadDynamicModelDelegate = policyComponent.registerClassPolicy(DynamicModelPolicies.OnLoadDynamicModel.class); - } - - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - onDictionaryInit(); - initMessages(); - - return (Object)null; - } - }, transactionService.isReadOnly(), false); - } - - public void destroy() - { - // NOOP - will be destroyed directly via DictionaryComponent - } - - /** - * Perform the actual repository access, checking for the existence of a valid transaction - */ - private void onDictionaryInitInTxn() - { - if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_NONE) - { - throw new IllegalStateException("The Repository-based dictionary initialization has to be done in the context of a transaction."); - } - - long startTime = System.currentTimeMillis(); - - if (logger.isTraceEnabled()) - { - String tenantDomain = tenantAdminService.getCurrentUserDomain(); - logger.trace("onDictionaryInit: ["+Thread.currentThread()+"]"+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); - } - - Collection modelsBefore = dictionaryDAO.getModels(true); // note: re-entrant - int modelsBeforeCnt = (modelsBefore != null ? modelsBefore.size() : 0); - - List loadedModels = new ArrayList(); - - if (this.repositoryModelsLocations != null) - { - // URI to model map - Map modelMap = new HashMap(); - - if (logger.isTraceEnabled()) - { - logger.trace("onDictionaryInit: locations="+this.repositoryModelsLocations); - } - - // Register the models found in the repository - - for (RepositoryLocation repositoryLocation : this.repositoryModelsLocations) - { - StoreRef storeRef = repositoryLocation.getStoreRef(); - - if (! nodeService.exists(storeRef)) - { - logger.info("StoreRef '"+ storeRef+"' does not exist"); - continue; // skip this location - } - - List nodeRefs = null; - - if (repositoryLocation.getQueryLanguage().equals(RepositoryLocation.LANGUAGE_PATH)) - { - nodeRefs = getNodes(storeRef, repositoryLocation, ContentModel.TYPE_DICTIONARY_MODEL); - - if (nodeRefs.size() > 0) - { - for (NodeRef dictionaryModel : nodeRefs) - { - try - { - // Ignore if the node is a working copy or archived, or if its inactive - if (! (nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_WORKING_COPY) || - nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_ARCHIVED))) - { - Boolean isActive = (Boolean)nodeService.getProperty(dictionaryModel, ContentModel.PROP_MODEL_ACTIVE); - - if ((isActive != null) && (isActive.booleanValue() == true)) - { - M2Model model = createM2Model(dictionaryModel); - if (model != null) - { - if (logger.isTraceEnabled()) - { - logger.trace("onDictionaryInit: "+model.getName()+" ("+dictionaryModel+")"); - } - - for (M2Namespace namespace : model.getNamespaces()) - { - modelMap.put(namespace.getUri(), new DynamicModelInfo(repositoryLocation, model, dictionaryModel)); - } - } - } - } - } - catch (InvalidNodeRefException inre) - { - // ignore - model no longer exists - if (logger.isDebugEnabled()) - { - logger.debug("onDictionaryInit: "+inre+" (assume concurrently deleted)"); - } - - continue; - } - } - } - } - else - { - logger.error("Unsupported query language for models location: " + repositoryLocation.getQueryLanguage()); - } - } - - // Load the models ensuring that they are loaded in the correct order - for (Map.Entry entry : modelMap.entrySet()) - { - RepositoryLocation importedLocation = entry.getValue().location; - M2Model importedModel = entry.getValue().model; - loadModel(modelMap, loadedModels, importedModel, importedLocation); - notifyDynamicModelLoaded(entry.getValue()); - } - } - - Collection modelsAfter = dictionaryDAO.getModels(true); - int modelsAfterCnt = (modelsAfter != null ? modelsAfter.size() : 0); - - if (logger.isDebugEnabled()) - { - String tenantDomain = tenantAdminService.getCurrentUserDomain(); - logger.debug("Model count: before="+modelsBeforeCnt+", load/update="+loadedModels.size()+", after="+modelsAfterCnt+" in "+(System.currentTimeMillis()-startTime)+" msecs ["+Thread.currentThread()+"] "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); - } - } - - public void notifyDynamicModelLoaded(DynamicModelInfo entry) - { - if(onLoadDynamicModelDelegate == null) - { - onLoadDynamicModelDelegate = policyComponent.registerClassPolicy(DynamicModelPolicies.OnLoadDynamicModel.class); - } - - DynamicModelPolicies.OnLoadDynamicModel policy = onLoadDynamicModelDelegate.get(ContentModel.TYPE_CONTENT); - policy.onLoadDynamicModel(entry.model, entry.nodeRef); - } - - public void initMessages() - { - if (this.repositoryMessagesLocations != null) - { - // Register the messages found in the repository - for (RepositoryLocation repositoryLocation : this.repositoryMessagesLocations) - { - StoreRef storeRef = repositoryLocation.getStoreRef(); - - if (! nodeService.exists(storeRef)) - { - logger.info("StoreRef '"+ storeRef+"' does not exist"); - continue; // skip this location - } - - if (repositoryLocation.getQueryLanguage().equals(RepositoryLocation.LANGUAGE_PATH)) - { - List nodeRefs = getNodes(storeRef, repositoryLocation, ContentModel.TYPE_CONTENT); - - if (nodeRefs.size() > 0) - { - List resourceBundleBaseNames = new ArrayList(); - - for (NodeRef messageResource : nodeRefs) - { - String resourceName = (String) nodeService.getProperty(messageResource, ContentModel.PROP_NAME); - - String bundleBaseName = messageService.getBaseBundleName(resourceName); - - if (!resourceBundleBaseNames.contains(bundleBaseName)) - { - resourceBundleBaseNames.add(bundleBaseName); - } - } - } - } - else - { - logger.error("Unsupported query language for messages location: " + repositoryLocation.getQueryLanguage()); - } - } - } - } - - // note: active or inactive - public List getModelRefs() - { - List modelRefs = new ArrayList(); - - for (RepositoryLocation repositoryLocation : this.repositoryModelsLocations) - { - StoreRef storeRef = repositoryLocation.getStoreRef(); - - if (! nodeService.exists(storeRef)) - { - logger.info("StoreRef '"+ storeRef+"' does not exist"); - continue; // skip this location - } - - if (repositoryLocation.getQueryLanguage().equals(RepositoryLocation.LANGUAGE_PATH)) - { - List nodeRefs = getNodes(storeRef, repositoryLocation, ContentModel.TYPE_DICTIONARY_MODEL); - - if (nodeRefs.size() > 0) - { - for (NodeRef dictionaryModel : nodeRefs) - { - try - { - // Ignore if the node is a working copy or archived - if (! (nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_WORKING_COPY) || nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_ARCHIVED))) - { - modelRefs.add(dictionaryModel); - } - } - catch (InvalidNodeRefException inre) - { - // ignore - model no longer exists - if (logger.isDebugEnabled()) - { - logger.debug("getModelRefs: "+inre+" (assume concurrently deleted)"); - } - - continue; - } - } - } - } - else - { - logger.error("Unsupported query language for models location: " + repositoryLocation.getQueryLanguage()); - } - } - - return modelRefs; - } - - protected List getNodes(StoreRef storeRef, RepositoryLocation repositoryLocation, QName nodeType) - { - List nodeRefs = new ArrayList(); - - NodeRef rootNodeRef = nodeService.getRootNode(storeRef); - if(nodeService.exists(rootNodeRef) == false) - { - //Tenant is deleted. But cache refresh was called to inform another cluster nodes - //Should be reworked when MNT-11638 will be implemented - return nodeRefs; - } - - if(repositoryLocation instanceof DynamicCreateRepositoryLocation) - { - ((DynamicCreateRepositoryLocation)repositoryLocation).checkAndCreate(rootNodeRef); - } - - String[] pathElements = repositoryLocation.getPathElements(); - - NodeRef folderNodeRef = rootNodeRef; - if (pathElements.length > 0) - { - folderNodeRef = resolveQNamePath(rootNodeRef, pathElements); - } - - if (folderNodeRef != null) - { - Set types = new HashSet(1); - types.add(nodeType); - List childAssocRefs = nodeService.getChildAssocs(folderNodeRef, types); - - if (childAssocRefs.size() > 0) - { - nodeRefs = new ArrayList(childAssocRefs.size()); - for (ChildAssociationRef childAssocRef : childAssocRefs) - { - nodeRefs.add(childAssocRef.getChildRef()); - } - } - } - - return nodeRefs; - } - - private class DynamicModelInfo - { - RepositoryLocation location; - M2Model model; - NodeRef nodeRef; - - - DynamicModelInfo(RepositoryLocation location, M2Model model, NodeRef nodeRef) - { - this.location = location; - this.model = model; - this.nodeRef = nodeRef; - } - } - - /** - * Loads a model (and its dependents) if it does not exist in the list of loaded models. - * - * @param modelMap a map of the models to be loaded - * @param loadedModels the list of models already loaded - * @param model the model to try and load - */ - private void loadModel(Map modelMap, List loadedModels, M2Model model, RepositoryLocation modelLocation) - { - String modelName = model.getName(); - if (loadedModels.contains(modelName) == false) - { - for (M2Namespace importNamespace : model.getImports()) - { - DynamicModelInfo entry = modelMap.get(importNamespace.getUri()); - if (entry != null) - { - RepositoryLocation importedLocation = entry.location; - M2Model importedModel = entry.model; - - // Ensure that the imported model is loaded first - loadModel(modelMap, loadedModels, importedModel, importedLocation); - } - // else we can assume that the imported model is already loaded, if this not the case then - // an error will be raised during compilation - } - - try - { - if (logger.isDebugEnabled()) - { - logger.debug("Loading model: " + modelName - + " (from ["+ modelLocation.getStoreRef() + "]"+ modelLocation.getPath() + ")"); - } - - dictionaryDAO.putModel(model); - - loadedModels.add(modelName); - } - catch (AlfrescoRuntimeException e) - { - // note: skip with warning - to allow server to start, and hence allow the possibility of fixing the broken model(s) - logger.warn("Failed to load model '" + modelName + "' : " + e); - } - } - } - - /** - * Create a M2Model from a dictionary model node - * - * @param nodeRef the dictionary model node reference - * @return the M2Model - */ - public M2Model createM2Model(NodeRef nodeRef) - { - M2Model model = null; - ContentReader contentReader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); - if (contentReader != null) - { - if (contentReader instanceof EmptyContentReader) - { - // belts-and-braces - logger.error("Failed to create model (due to EmptyContentReader): "+nodeRef); - } - else - { - InputStream is = null; - try - { - is = contentReader.getContentInputStream(); - model = M2Model.createModel(is); - } - finally - { - if (is != null) - { - try - { - is.close(); - } - catch (IOException e) - { - logger.error("Failed to close input stream for " + nodeRef); - } - } - } - } - } - // TODO should we inactivate the model node and put the error somewhere?? - return model; - } - - @Override - protected void onBootstrap(ApplicationEvent event) - { - // Reset the dictionary (destroy and reload) in order to ensure that we have a basic version of - // the dictionary (static models) loaded at least - dictionaryDAO.reset(); - - // Register listeners, which will be called when the dictionary is next reloaded - register(); - - // Trigger a reload. The callbacks will occur immediately on the current thread, however, - // the model created in reset() will still be available for the basic necessities - dictionaryDAO.init(); - - // The listeners can now know about this - // However, the listeners will be needing to access the dictionary themselves, hence the earlier 'reset' - // to ensure that there is no deadlock waiting for a new dictionary - ((ApplicationContext) event.getSource()).publishEvent(new DictionaryRepositoryBootstrappedEvent(this)); - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - // NOOP - } - - public void onEnableTenant() - { - init(); // will be called in context of tenant - } - - public void onDisableTenant() - { - destroy(); // will be called in context of tenant - } - - /** - * Register listeners - */ - public void register() - { - // register with Dictionary Service to allow (re-)init - dictionaryDAO.registerListener(this); - - // register with Message Service to allow (re-)init - messageService.register(this); - - if (tenantAdminService.isEnabled()) - { - // register dictionary repository bootstrap - tenantAdminService.register(this); - - // register repository message (I18N) service - tenantAdminService.register(messageService); - } - } - - /** - * Unregister - */ - protected void unregister() - { - if (tenantAdminService.isEnabled()) - { - // register dictionary repository bootstrap - tenantAdminService.unregister(this); - - // register repository message (I18N) service - tenantAdminService.unregister(messageService); - } - } - - // TODO refactor (see also MessageServiceImpl) - protected NodeRef resolveQNamePath(NodeRef rootNodeRef, String[] pathPrefixQNameStrings) - { - if (pathPrefixQNameStrings.length == 0) - { - throw new IllegalArgumentException("Path array is empty"); - } - // walk the path - NodeRef parentNodeRef = rootNodeRef; - for (int i = 0; i < pathPrefixQNameStrings.length; i++) - { - String pathPrefixQNameString = pathPrefixQNameStrings[i]; - - QName pathQName = null; - if (tenantAdminService.isEnabled()) - { - String[] parts = QName.splitPrefixedQName(pathPrefixQNameString); - if ((parts.length == 2) && (parts[0].equals(NamespaceService.APP_MODEL_PREFIX))) - { - String pathUriQNameString = new StringBuilder(64). - append(QName.NAMESPACE_BEGIN). - append(NamespaceService.APP_MODEL_1_0_URI). - append(QName.NAMESPACE_END). - append(parts[1]).toString(); - - pathQName = QName.createQName(pathUriQNameString); - } - else - { - pathQName = QName.createQName(pathPrefixQNameString, namespaceService); - } - } - else - { - pathQName = QName.createQName(pathPrefixQNameString, namespaceService); - } - - List childAssocRefs = nodeService.getChildAssocs(parentNodeRef, RegexQNamePattern.MATCH_ALL, pathQName); - if (childAssocRefs.size() != 1) - { - return null; - } - parentNodeRef = childAssocRefs.get(0).getChildRef(); - } - return parentNodeRef; - } - - /** - * Initialise the dictionary, ensuring that a transaction is available - */ - @Override - public void onDictionaryInit() - { - if(onLoadDynamicModelDelegate == null) - { - onLoadDynamicModelDelegate = policyComponent.registerClassPolicy(DynamicModelPolicies.OnLoadDynamicModel.class); - } - RetryingTransactionCallback initCallback = new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - onDictionaryInitInTxn(); - return null; - } - }; - transactionService.getRetryingTransactionHelper().doInTransaction(initCallback, true, false); - } - - @Override - public void afterDictionaryDestroy() - { - } - - @Override - public void afterDictionaryInit() - { - } -} +package org.alfresco.repo.dictionary; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.EmptyContentReader; +import org.alfresco.repo.dictionary.DynamicModelPolicies.OnLoadDynamicModel; +import org.alfresco.repo.i18n.MessageDeployer; +import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.policy.ClassPolicyDelegate; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantDeployer; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.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.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * Bootstrap the dictionary from specified locations within the repository + * + * @author Roy Wetherall, JanV, sglover + */ +public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean +implements TenantDeployer, DictionaryListener, /*TenantDictionaryListener, */MessageDeployer +{ + // Logging support + private static Log logger = LogFactory.getLog(DictionaryRepositoryBootstrap.class); + + /** Locations in the repository from which models should be loaded */ + private List repositoryModelsLocations = new ArrayList(); + + /** Locations in the repository from which messages should be loaded */ + private List repositoryMessagesLocations = new ArrayList(); + + /** Dictionary DAO */ + private DictionaryDAO dictionaryDAO = null; + + /** The content service */ + private ContentService contentService; + + /** The node service */ + private NodeService nodeService; + + /** The tenant admin service */ + private TenantAdminService tenantAdminService; + + /** The namespace service */ + private NamespaceService namespaceService; + + /** The message service */ + private MessageService messageService; + + /** The transaction service */ + private TransactionService transactionService; + + /** The policy component */ + private PolicyComponent policyComponent; + + + /** + * Sets the Dictionary DAO + * + * @param dictionaryDAO DictionaryDAO + */ + public void setDictionaryDAO(DictionaryDAO dictionaryDAO) + { + this.dictionaryDAO = dictionaryDAO; + } + + /** + * Set the content service + * + * @param contentService the content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public PolicyComponent getPolicyComponent() + { + return policyComponent; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Set the tenant admin service + * + * @param tenantAdminService the tenant admin service + */ + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + /** + * Set the namespace service + * + * @param namespaceService the namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Set the message service + * + * @param messageService the message service + */ + public void setMessageService(MessageService messageService) + { + this.messageService = messageService; + } + + /** + * Set the transaction service + * + * @param transactionService the transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Set the repository models locations + * + * @param repositoryLocations list of the repository models locations + */ public void setRepositoryModelsLocations( + List repositoryLocations) + { + this.repositoryModelsLocations = repositoryLocations; + } + + /** + * Set the repository messages (resource bundle) locations + * + * @param repositoryLocations + * list of the repository messages locations + */ + public void setRepositoryMessagesLocations( + List repositoryLocations) + { + this.repositoryMessagesLocations = repositoryLocations; + } + + private ClassPolicyDelegate onLoadDynamicModelDelegate; + + /** + * Initialise - after bootstrap of schema and tenant admin service + */ + public void init() + { + PropertyCheck.mandatory(this, "dictionaryDAO", dictionaryDAO); + PropertyCheck.mandatory(this, "contentService", contentService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "tenantAdminService", tenantAdminService); + PropertyCheck.mandatory(this, "namespaceService", namespaceService); + PropertyCheck.mandatory(this, "messageService", messageService); + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "policyComponent", policyComponent); + + if(onLoadDynamicModelDelegate == null) + { + onLoadDynamicModelDelegate = policyComponent.registerClassPolicy(DynamicModelPolicies.OnLoadDynamicModel.class); + } + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + onDictionaryInit(); + initMessages(); + + return (Object)null; + } + }, transactionService.isReadOnly(), false); + } + + public void destroy() + { + // NOOP - will be destroyed directly via DictionaryComponent + } + + /** + * Perform the actual repository access, checking for the existence of a valid transaction + */ + private void onDictionaryInitInTxn() + { + if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_NONE) + { + throw new IllegalStateException("The Repository-based dictionary initialization has to be done in the context of a transaction."); + } + + long startTime = System.currentTimeMillis(); + + if (logger.isTraceEnabled()) + { + String tenantDomain = tenantAdminService.getCurrentUserDomain(); + logger.trace("onDictionaryInit: ["+Thread.currentThread()+"]"+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); + } + + Collection modelsBefore = dictionaryDAO.getModels(true); // note: re-entrant + int modelsBeforeCnt = (modelsBefore != null ? modelsBefore.size() : 0); + + List loadedModels = new ArrayList(); + + if (this.repositoryModelsLocations != null) + { + // URI to model map + Map modelMap = new HashMap(); + + if (logger.isTraceEnabled()) + { + logger.trace("onDictionaryInit: locations="+this.repositoryModelsLocations); + } + + // Register the models found in the repository + + for (RepositoryLocation repositoryLocation : this.repositoryModelsLocations) + { + StoreRef storeRef = repositoryLocation.getStoreRef(); + + if (! nodeService.exists(storeRef)) + { + logger.info("StoreRef '"+ storeRef+"' does not exist"); + continue; // skip this location + } + + List nodeRefs = null; + + if (repositoryLocation.getQueryLanguage().equals(RepositoryLocation.LANGUAGE_PATH)) + { + nodeRefs = getNodes(storeRef, repositoryLocation, ContentModel.TYPE_DICTIONARY_MODEL); + + if (nodeRefs.size() > 0) + { + for (NodeRef dictionaryModel : nodeRefs) + { + try + { + // Ignore if the node is a working copy or archived, or if its inactive + if (! (nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_WORKING_COPY) || + nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_ARCHIVED))) + { + Boolean isActive = (Boolean)nodeService.getProperty(dictionaryModel, ContentModel.PROP_MODEL_ACTIVE); + + if ((isActive != null) && (isActive.booleanValue() == true)) + { + M2Model model = createM2Model(dictionaryModel); + if (model != null) + { + if (logger.isTraceEnabled()) + { + logger.trace("onDictionaryInit: "+model.getName()+" ("+dictionaryModel+")"); + } + + for (M2Namespace namespace : model.getNamespaces()) + { + modelMap.put(namespace.getUri(), new DynamicModelInfo(repositoryLocation, model, dictionaryModel)); + } + } + } + } + } + catch (InvalidNodeRefException inre) + { + // ignore - model no longer exists + if (logger.isDebugEnabled()) + { + logger.debug("onDictionaryInit: "+inre+" (assume concurrently deleted)"); + } + + continue; + } + } + } + } + else + { + logger.error("Unsupported query language for models location: " + repositoryLocation.getQueryLanguage()); + } + } + + // Load the models ensuring that they are loaded in the correct order + for (Map.Entry entry : modelMap.entrySet()) + { + RepositoryLocation importedLocation = entry.getValue().location; + M2Model importedModel = entry.getValue().model; + loadModel(modelMap, loadedModels, importedModel, importedLocation); + notifyDynamicModelLoaded(entry.getValue()); + } + } + + Collection modelsAfter = dictionaryDAO.getModels(true); + int modelsAfterCnt = (modelsAfter != null ? modelsAfter.size() : 0); + + if (logger.isDebugEnabled()) + { + String tenantDomain = tenantAdminService.getCurrentUserDomain(); + logger.debug("Model count: before="+modelsBeforeCnt+", load/update="+loadedModels.size()+", after="+modelsAfterCnt+" in "+(System.currentTimeMillis()-startTime)+" msecs ["+Thread.currentThread()+"] "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); + } + } + + public void notifyDynamicModelLoaded(DynamicModelInfo entry) + { + if(onLoadDynamicModelDelegate == null) + { + onLoadDynamicModelDelegate = policyComponent.registerClassPolicy(DynamicModelPolicies.OnLoadDynamicModel.class); + } + + DynamicModelPolicies.OnLoadDynamicModel policy = onLoadDynamicModelDelegate.get(ContentModel.TYPE_CONTENT); + policy.onLoadDynamicModel(entry.model, entry.nodeRef); + } + + public void initMessages() + { + if (this.repositoryMessagesLocations != null) + { + // Register the messages found in the repository + for (RepositoryLocation repositoryLocation : this.repositoryMessagesLocations) + { + StoreRef storeRef = repositoryLocation.getStoreRef(); + + if (! nodeService.exists(storeRef)) + { + logger.info("StoreRef '"+ storeRef+"' does not exist"); + continue; // skip this location + } + + if (repositoryLocation.getQueryLanguage().equals(RepositoryLocation.LANGUAGE_PATH)) + { + List nodeRefs = getNodes(storeRef, repositoryLocation, ContentModel.TYPE_CONTENT); + + if (nodeRefs.size() > 0) + { + List resourceBundleBaseNames = new ArrayList(); + + for (NodeRef messageResource : nodeRefs) + { + String resourceName = (String) nodeService.getProperty(messageResource, ContentModel.PROP_NAME); + + String bundleBaseName = messageService.getBaseBundleName(resourceName); + + if (!resourceBundleBaseNames.contains(bundleBaseName)) + { + resourceBundleBaseNames.add(bundleBaseName); + } + } + } + } + else + { + logger.error("Unsupported query language for messages location: " + repositoryLocation.getQueryLanguage()); + } + } + } + } + + // note: active or inactive + public List getModelRefs() + { + List modelRefs = new ArrayList(); + + for (RepositoryLocation repositoryLocation : this.repositoryModelsLocations) + { + StoreRef storeRef = repositoryLocation.getStoreRef(); + + if (! nodeService.exists(storeRef)) + { + logger.info("StoreRef '"+ storeRef+"' does not exist"); + continue; // skip this location + } + + if (repositoryLocation.getQueryLanguage().equals(RepositoryLocation.LANGUAGE_PATH)) + { + List nodeRefs = getNodes(storeRef, repositoryLocation, ContentModel.TYPE_DICTIONARY_MODEL); + + if (nodeRefs.size() > 0) + { + for (NodeRef dictionaryModel : nodeRefs) + { + try + { + // Ignore if the node is a working copy or archived + if (! (nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_WORKING_COPY) || nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_ARCHIVED))) + { + modelRefs.add(dictionaryModel); + } + } + catch (InvalidNodeRefException inre) + { + // ignore - model no longer exists + if (logger.isDebugEnabled()) + { + logger.debug("getModelRefs: "+inre+" (assume concurrently deleted)"); + } + + continue; + } + } + } + } + else + { + logger.error("Unsupported query language for models location: " + repositoryLocation.getQueryLanguage()); + } + } + + return modelRefs; + } + + protected List getNodes(StoreRef storeRef, RepositoryLocation repositoryLocation, QName nodeType) + { + List nodeRefs = new ArrayList(); + + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + if(nodeService.exists(rootNodeRef) == false) + { + //Tenant is deleted. But cache refresh was called to inform another cluster nodes + //Should be reworked when MNT-11638 will be implemented + return nodeRefs; + } + + if(repositoryLocation instanceof DynamicCreateRepositoryLocation) + { + ((DynamicCreateRepositoryLocation)repositoryLocation).checkAndCreate(rootNodeRef); + } + + String[] pathElements = repositoryLocation.getPathElements(); + + NodeRef folderNodeRef = rootNodeRef; + if (pathElements.length > 0) + { + folderNodeRef = resolveQNamePath(rootNodeRef, pathElements); + } + + if (folderNodeRef != null) + { + Set types = new HashSet(1); + types.add(nodeType); + List childAssocRefs = nodeService.getChildAssocs(folderNodeRef, types); + + if (childAssocRefs.size() > 0) + { + nodeRefs = new ArrayList(childAssocRefs.size()); + for (ChildAssociationRef childAssocRef : childAssocRefs) + { + nodeRefs.add(childAssocRef.getChildRef()); + } + } + } + + return nodeRefs; + } + + private class DynamicModelInfo + { + RepositoryLocation location; + M2Model model; + NodeRef nodeRef; + + + DynamicModelInfo(RepositoryLocation location, M2Model model, NodeRef nodeRef) + { + this.location = location; + this.model = model; + this.nodeRef = nodeRef; + } + } + + /** + * Loads a model (and its dependents) if it does not exist in the list of loaded models. + * + * @param modelMap a map of the models to be loaded + * @param loadedModels the list of models already loaded + * @param model the model to try and load + */ + private void loadModel(Map modelMap, List loadedModels, M2Model model, RepositoryLocation modelLocation) + { + String modelName = model.getName(); + if (loadedModels.contains(modelName) == false) + { + for (M2Namespace importNamespace : model.getImports()) + { + DynamicModelInfo entry = modelMap.get(importNamespace.getUri()); + if (entry != null) + { + RepositoryLocation importedLocation = entry.location; + M2Model importedModel = entry.model; + + // Ensure that the imported model is loaded first + loadModel(modelMap, loadedModels, importedModel, importedLocation); + } + // else we can assume that the imported model is already loaded, if this not the case then + // an error will be raised during compilation + } + + try + { + if (logger.isDebugEnabled()) + { + logger.debug("Loading model: " + modelName + + " (from ["+ modelLocation.getStoreRef() + "]"+ modelLocation.getPath() + ")"); + } + + dictionaryDAO.putModel(model); + + loadedModels.add(modelName); + } + catch (AlfrescoRuntimeException e) + { + // note: skip with warning - to allow server to start, and hence allow the possibility of fixing the broken model(s) + logger.warn("Failed to load model '" + modelName + "' : " + e); + } + } + } + + /** + * Create a M2Model from a dictionary model node + * + * @param nodeRef the dictionary model node reference + * @return the M2Model + */ + public M2Model createM2Model(NodeRef nodeRef) + { + M2Model model = null; + ContentReader contentReader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + if (contentReader != null) + { + if (contentReader instanceof EmptyContentReader) + { + // belts-and-braces + logger.error("Failed to create model (due to EmptyContentReader): "+nodeRef); + } + else + { + InputStream is = null; + try + { + is = contentReader.getContentInputStream(); + model = M2Model.createModel(is); + } + finally + { + if (is != null) + { + try + { + is.close(); + } + catch (IOException e) + { + logger.error("Failed to close input stream for " + nodeRef); + } + } + } + } + } + // TODO should we inactivate the model node and put the error somewhere?? + return model; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + // Reset the dictionary (destroy and reload) in order to ensure that we have a basic version of + // the dictionary (static models) loaded at least + dictionaryDAO.reset(); + + // Register listeners, which will be called when the dictionary is next reloaded + register(); + + // Trigger a reload. The callbacks will occur immediately on the current thread, however, + // the model created in reset() will still be available for the basic necessities + dictionaryDAO.init(); + + // The listeners can now know about this + // However, the listeners will be needing to access the dictionary themselves, hence the earlier 'reset' + // to ensure that there is no deadlock waiting for a new dictionary + ((ApplicationContext) event.getSource()).publishEvent(new DictionaryRepositoryBootstrappedEvent(this)); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } + + public void onEnableTenant() + { + init(); // will be called in context of tenant + } + + public void onDisableTenant() + { + destroy(); // will be called in context of tenant + } + + /** + * Register listeners + */ + public void register() + { + // register with Dictionary Service to allow (re-)init + dictionaryDAO.registerListener(this); + + // register with Message Service to allow (re-)init + messageService.register(this); + + if (tenantAdminService.isEnabled()) + { + // register dictionary repository bootstrap + tenantAdminService.register(this); + + // register repository message (I18N) service + tenantAdminService.register(messageService); + } + } + + /** + * Unregister + */ + protected void unregister() + { + if (tenantAdminService.isEnabled()) + { + // register dictionary repository bootstrap + tenantAdminService.unregister(this); + + // register repository message (I18N) service + tenantAdminService.unregister(messageService); + } + } + + // TODO refactor (see also MessageServiceImpl) + protected NodeRef resolveQNamePath(NodeRef rootNodeRef, String[] pathPrefixQNameStrings) + { + if (pathPrefixQNameStrings.length == 0) + { + throw new IllegalArgumentException("Path array is empty"); + } + // walk the path + NodeRef parentNodeRef = rootNodeRef; + for (int i = 0; i < pathPrefixQNameStrings.length; i++) + { + String pathPrefixQNameString = pathPrefixQNameStrings[i]; + + QName pathQName = null; + if (tenantAdminService.isEnabled()) + { + String[] parts = QName.splitPrefixedQName(pathPrefixQNameString); + if ((parts.length == 2) && (parts[0].equals(NamespaceService.APP_MODEL_PREFIX))) + { + String pathUriQNameString = new StringBuilder(64). + append(QName.NAMESPACE_BEGIN). + append(NamespaceService.APP_MODEL_1_0_URI). + append(QName.NAMESPACE_END). + append(parts[1]).toString(); + + pathQName = QName.createQName(pathUriQNameString); + } + else + { + pathQName = QName.createQName(pathPrefixQNameString, namespaceService); + } + } + else + { + pathQName = QName.createQName(pathPrefixQNameString, namespaceService); + } + + List childAssocRefs = nodeService.getChildAssocs(parentNodeRef, RegexQNamePattern.MATCH_ALL, pathQName); + if (childAssocRefs.size() != 1) + { + return null; + } + parentNodeRef = childAssocRefs.get(0).getChildRef(); + } + return parentNodeRef; + } + + /** + * Initialise the dictionary, ensuring that a transaction is available + */ + @Override + public void onDictionaryInit() + { + if(onLoadDynamicModelDelegate == null) + { + onLoadDynamicModelDelegate = policyComponent.registerClassPolicy(DynamicModelPolicies.OnLoadDynamicModel.class); + } + RetryingTransactionCallback initCallback = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + onDictionaryInitInTxn(); + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(initCallback, true, false); + } + + @Override + public void afterDictionaryDestroy() + { + } + + @Override + public void afterDictionaryInit() + { + } +} diff --git a/source/java/org/alfresco/repo/dictionary/DynamicCreateRepositoryLocation.java b/source/java/org/alfresco/repo/dictionary/DynamicCreateRepositoryLocation.java index 63453ca4a9..68206ecaae 100644 --- a/source/java/org/alfresco/repo/dictionary/DynamicCreateRepositoryLocation.java +++ b/source/java/org/alfresco/repo/dictionary/DynamicCreateRepositoryLocation.java @@ -1,165 +1,165 @@ -package org.alfresco.repo.dictionary; - -import java.io.File; -import java.util.List; -import java.util.Locale; -import java.util.ResourceBundle; - -import org.alfresco.repo.importer.ACPImportPackageHandler; -import org.alfresco.repo.importer.ImporterBootstrap; -import org.alfresco.repo.tenant.TenantUtil; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.view.ImportPackageHandler; -import org.alfresco.service.cmr.view.ImporterBinding; -import org.alfresco.service.cmr.view.ImporterContentCache; -import org.alfresco.service.cmr.view.ImporterProgress; -import org.alfresco.service.cmr.view.ImporterService; -import org.alfresco.service.cmr.view.Location; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * - * @author sglover - * - */ -public class DynamicCreateRepositoryLocation extends RepositoryLocation -{ - private static final Log logger = LogFactory.getLog(DynamicCreateRepositoryLocation.class); - - private ImporterService importerService; - private String contentViewLocation; - private ResourceBundle bundle; - private NamespaceService namespaceService; - private SearchService searchService; - private TransactionService transactionService; - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setContentViewLocation(String contentViewLocation) - { - this.contentViewLocation = contentViewLocation; - } - - public void setImporterService(ImporterService importerService) - { - this.importerService = importerService; - } - - public void setBundleName(String bundleName) - { - Locale bindingLocale = I18NUtil.getLocale(); - this.bundle = ResourceBundle.getBundle(bundleName, bindingLocale); - } - - public void checkAndCreate(NodeRef rootNodeRef) - { - List nodes = searchService.selectNodes(rootNodeRef, getPath(), null, namespaceService, false); - if(nodes.size() == 0) - { - logger.info("Repository location " + getPath() + " does not exist for tenant " - + TenantUtil.getCurrentDomain() + ", creating"); - create(); - } - } - - protected String getParentPath() - { - String parentPath = null; - - String path = getPath(); - int idx = path.lastIndexOf("/"); - if(idx != -1) - { - parentPath = path.substring(0, idx); - } - else - { - parentPath = "/"; - } - - return parentPath; - } - - protected void create() - { - RetryingTransactionCallback initCallback = new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - onCreateInTxn(); - return null; - } - }; - getTransactionService().getRetryingTransactionHelper().doInTransaction(initCallback, false, true); - - } - private void onCreateInTxn() - { - final File viewFile = ImporterBootstrap.getFile(contentViewLocation); - ImportPackageHandler acpHandler = new ACPImportPackageHandler(viewFile, null); - Location location = new Location(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); - location.setPath(getParentPath()); - - final ImporterBinding binding = new ImporterBinding() - { - @Override - public String getValue(String key) - { - return bundle.getString(key); - } - - @Override - public UUID_BINDING getUUIDBinding() - { - return UUID_BINDING.CREATE_NEW; - } - - @Override - public QName[] getExcludedClasses() - { - return null; - } - - @Override - public boolean allowReferenceWithinTransaction() - { - return false; - } - - @Override - public ImporterContentCache getImportConentCache() - { - return null; - } - }; - - importerService.importView(acpHandler, location, binding, (ImporterProgress) null); - } - - public TransactionService getTransactionService() - { - return transactionService; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } -} +package org.alfresco.repo.dictionary; + +import java.io.File; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +import org.alfresco.repo.importer.ACPImportPackageHandler; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.view.ImportPackageHandler; +import org.alfresco.service.cmr.view.ImporterBinding; +import org.alfresco.service.cmr.view.ImporterContentCache; +import org.alfresco.service.cmr.view.ImporterProgress; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.view.Location; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * + * @author sglover + * + */ +public class DynamicCreateRepositoryLocation extends RepositoryLocation +{ + private static final Log logger = LogFactory.getLog(DynamicCreateRepositoryLocation.class); + + private ImporterService importerService; + private String contentViewLocation; + private ResourceBundle bundle; + private NamespaceService namespaceService; + private SearchService searchService; + private TransactionService transactionService; + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setContentViewLocation(String contentViewLocation) + { + this.contentViewLocation = contentViewLocation; + } + + public void setImporterService(ImporterService importerService) + { + this.importerService = importerService; + } + + public void setBundleName(String bundleName) + { + Locale bindingLocale = I18NUtil.getLocale(); + this.bundle = ResourceBundle.getBundle(bundleName, bindingLocale); + } + + public void checkAndCreate(NodeRef rootNodeRef) + { + List nodes = searchService.selectNodes(rootNodeRef, getPath(), null, namespaceService, false); + if(nodes.size() == 0) + { + logger.info("Repository location " + getPath() + " does not exist for tenant " + + TenantUtil.getCurrentDomain() + ", creating"); + create(); + } + } + + protected String getParentPath() + { + String parentPath = null; + + String path = getPath(); + int idx = path.lastIndexOf("/"); + if(idx != -1) + { + parentPath = path.substring(0, idx); + } + else + { + parentPath = "/"; + } + + return parentPath; + } + + protected void create() + { + RetryingTransactionCallback initCallback = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + onCreateInTxn(); + return null; + } + }; + getTransactionService().getRetryingTransactionHelper().doInTransaction(initCallback, false, true); + + } + private void onCreateInTxn() + { + final File viewFile = ImporterBootstrap.getFile(contentViewLocation); + ImportPackageHandler acpHandler = new ACPImportPackageHandler(viewFile, null); + Location location = new Location(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + location.setPath(getParentPath()); + + final ImporterBinding binding = new ImporterBinding() + { + @Override + public String getValue(String key) + { + return bundle.getString(key); + } + + @Override + public UUID_BINDING getUUIDBinding() + { + return UUID_BINDING.CREATE_NEW; + } + + @Override + public QName[] getExcludedClasses() + { + return null; + } + + @Override + public boolean allowReferenceWithinTransaction() + { + return false; + } + + @Override + public ImporterContentCache getImportConentCache() + { + return null; + } + }; + + importerService.importView(acpHandler, location, binding, (ImporterProgress) null); + } + + public TransactionService getTransactionService() + { + return transactionService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/DynamicModelPolicies.java b/source/java/org/alfresco/repo/dictionary/DynamicModelPolicies.java index 445c3bb220..9f0e1ce7e5 100644 --- a/source/java/org/alfresco/repo/dictionary/DynamicModelPolicies.java +++ b/source/java/org/alfresco/repo/dictionary/DynamicModelPolicies.java @@ -1,25 +1,25 @@ -package org.alfresco.repo.dictionary; - -import org.alfresco.repo.policy.ClassPolicy; -import org.alfresco.service.cmr.repository.NodeRef; - -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; - -public class DynamicModelPolicies -{ - - public interface OnLoadDynamicModel extends ClassPolicy - { - public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onLoadDynamicModel"); - - /** - * Called after a new dynamic model has been loaded. - * - * @param model the model loaded - * @param nodeRef the node ref of the model - */ - public void onLoadDynamicModel(M2Model model, NodeRef nodeRef); - } - -} +package org.alfresco.repo.dictionary; + +import org.alfresco.repo.policy.ClassPolicy; +import org.alfresco.service.cmr.repository.NodeRef; + +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +public class DynamicModelPolicies +{ + + public interface OnLoadDynamicModel extends ClassPolicy + { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onLoadDynamicModel"); + + /** + * Called after a new dynamic model has been loaded. + * + * @param model the model loaded + * @param nodeRef the node ref of the model + */ + public void onLoadDynamicModel(M2Model model, NodeRef nodeRef); + } + +} diff --git a/source/java/org/alfresco/repo/dictionary/ModelInUseException.java b/source/java/org/alfresco/repo/dictionary/ModelInUseException.java index 92cc26d40f..c55683ee63 100644 --- a/source/java/org/alfresco/repo/dictionary/ModelInUseException.java +++ b/source/java/org/alfresco/repo/dictionary/ModelInUseException.java @@ -1,21 +1,21 @@ -package org.alfresco.repo.dictionary; - -import org.alfresco.error.AlfrescoRuntimeException; - -/** - * An exception thrown when an attempt is made to remove a model (or part - * thereof) when the model is in use. That is, when nodes and node properties - * reference types, aspects, namespaces, etc in the model. - * - * @author sglover - * - */ -public class ModelInUseException extends AlfrescoRuntimeException -{ - private static final long serialVersionUID = 1447075542326143577L; - - public ModelInUseException(String message) - { - super(message); - } -} +package org.alfresco.repo.dictionary; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * An exception thrown when an attempt is made to remove a model (or part + * thereof) when the model is in use. That is, when nodes and node properties + * reference types, aspects, namespaces, etc in the model. + * + * @author sglover + * + */ +public class ModelInUseException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = 1447075542326143577L; + + public ModelInUseException(String message) + { + super(message); + } +} diff --git a/source/java/org/alfresco/repo/dictionary/ModelNotInUseException.java b/source/java/org/alfresco/repo/dictionary/ModelNotInUseException.java index 7c4b26b462..c9c5b1236e 100644 --- a/source/java/org/alfresco/repo/dictionary/ModelNotInUseException.java +++ b/source/java/org/alfresco/repo/dictionary/ModelNotInUseException.java @@ -1,19 +1,19 @@ -package org.alfresco.repo.dictionary; - -import org.alfresco.error.AlfrescoRuntimeException; - -/** - * An exception thrown to indicate that a model is not in use. - * - * @author sglover - * - */ -public class ModelNotInUseException extends AlfrescoRuntimeException -{ - private static final long serialVersionUID = 1447075542326143577L; - - public ModelNotInUseException(String message) - { - super(message); - } -} +package org.alfresco.repo.dictionary; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * An exception thrown to indicate that a model is not in use. + * + * @author sglover + * + */ +public class ModelNotInUseException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = 1447075542326143577L; + + public ModelNotInUseException(String message) + { + super(message); + } +} diff --git a/source/java/org/alfresco/repo/dictionary/ModelValidator.java b/source/java/org/alfresco/repo/dictionary/ModelValidator.java index 6bb6a558b5..a1b5a09509 100644 --- a/source/java/org/alfresco/repo/dictionary/ModelValidator.java +++ b/source/java/org/alfresco/repo/dictionary/ModelValidator.java @@ -1,36 +1,36 @@ -package org.alfresco.repo.dictionary; - -import org.alfresco.service.namespace.QName; - -/** - * Validates model changes and deletes against the repository. - * - * @author sglover - * - */ -public interface ModelValidator -{ - /** - * validate against dictionary - * - * if new model - * then nothing to validate - * - * else if an existing model - * then could be updated (or unchanged) so validate to currently only allow incremental updates - * - addition of new types, aspects (except default aspects), properties, associations - * - no deletion of types, aspects or properties or associations - * - no addition, update or deletion of default/mandatory aspects - * - * @throws ModelInUseException if the model is being used by nodes or properties - */ - void validateModel(CompiledModel compiledModel); - - /** - * Can the model be deleted (validate against repository contents / workflows)? - * - * @return true only if the model is not being used or if the model does not - * exist - */ - boolean canDeleteModel(QName modelName); -} +package org.alfresco.repo.dictionary; + +import org.alfresco.service.namespace.QName; + +/** + * Validates model changes and deletes against the repository. + * + * @author sglover + * + */ +public interface ModelValidator +{ + /** + * validate against dictionary + * + * if new model + * then nothing to validate + * + * else if an existing model + * then could be updated (or unchanged) so validate to currently only allow incremental updates + * - addition of new types, aspects (except default aspects), properties, associations + * - no deletion of types, aspects or properties or associations + * - no addition, update or deletion of default/mandatory aspects + * + * @throws ModelInUseException if the model is being used by nodes or properties + */ + void validateModel(CompiledModel compiledModel); + + /** + * Can the model be deleted (validate against repository contents / workflows)? + * + * @return true only if the model is not being used or if the model does not + * exist + */ + boolean canDeleteModel(QName modelName); +} diff --git a/source/java/org/alfresco/repo/dictionary/ModelValidatorImpl.java b/source/java/org/alfresco/repo/dictionary/ModelValidatorImpl.java index 9840017299..0da038db2e 100644 --- a/source/java/org/alfresco/repo/dictionary/ModelValidatorImpl.java +++ b/source/java/org/alfresco/repo/dictionary/ModelValidatorImpl.java @@ -1,459 +1,459 @@ -package org.alfresco.repo.dictionary; - -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.tenant.Tenant; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.tenant.TenantUtil; -import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.repo.workflow.BPMEngineRegistry; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.ConstraintDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryException; -import org.alfresco.service.cmr.dictionary.ModelDefinition; -import org.alfresco.service.cmr.dictionary.NamespaceDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; -import org.alfresco.service.cmr.workflow.WorkflowService; -import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; -import org.alfresco.service.namespace.NamespaceException; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.dao.DataIntegrityViolationException; - -/** - * Model change validation covering model deletes, model constituent changes e.g. property deletes, - * additions, etc. - * - * @author sglover - */ -public class ModelValidatorImpl implements ModelValidator -{ - private static final Log logger = LogFactory.getLog(ModelValidatorImpl.class); - - private DictionaryDAO dictionaryDAO; - private QNameDAO qnameDAO; - private NamespaceService namespaceService; - private TransactionService transactionService; - private WorkflowService workflowService; - private TenantService tenantService; - private TenantAdminService tenantAdminService; - private boolean enforceTenantInNamespace = false; - - public void setEnforceTenantInNamespace(boolean enforceTenantInNamespace) - { - this.enforceTenantInNamespace = enforceTenantInNamespace; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - - public void setDictionaryDAO(DictionaryDAO dictionaryDAO) - { - this.dictionaryDAO = dictionaryDAO; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setWorkflowService(WorkflowService workflowService) - { - this.workflowService = workflowService; - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - public void setTenantAdminService(TenantAdminService tenantAdminService) - { - this.tenantAdminService = tenantAdminService; - } - - private void checkCustomModelNamespace(M2Model model, String tenantDomain) - { - if(tenantDomain != null && !tenantDomain.equals("") && enforceTenantInNamespace) - { - // check only for "real" tenants - for(M2Namespace namespace : model.getNamespaces()) - { - String namespaceURI = namespace.getUri(); - if(namespaceURI.indexOf(tenantDomain) == -1) - { - throw new DictionaryException("Namespace " + namespaceURI + " does not contain the tenant " + tenantDomain); - } - } - } - } - - private boolean canDeleteModel(Collection namespaceDefs, Collection typeDefs, - Collection aspectDefs, Tenant tenant) - { - boolean canDelete = true; - - String tenantDomain = "for tenant [" - + (tenant == null ? TenantService.DEFAULT_DOMAIN : tenant.getTenantDomain()) + "]"; - - List workflowDefs = workflowService.getDefinitions(); - - if (workflowDefs.size() > 0) - { - if (namespaceDefs.size() > 0) - { - // check workflow namespace usage - for (WorkflowDefinition workflowDef : workflowDefs) - { - String workflowDefName = workflowDef.getName(); - - String workflowNamespaceURI = null; - try - { - workflowNamespaceURI = QName.createQName(BPMEngineRegistry.getLocalId(workflowDefName), namespaceService).getNamespaceURI(); - } - catch (NamespaceException ne) - { - logger.warn("Skipped workflow when validating model delete - unknown namespace: "+ne); - continue; - } - - for (NamespaceDefinition namespaceDef : namespaceDefs) - { - if (workflowNamespaceURI.equals(namespaceDef.getUri())) - { - logger.warn("Failed to validate model delete" + tenantDomain + " - found workflow process definition " - + workflowDefName + " using model namespace '" + namespaceDef.getUri() + "'"); - canDelete = false; - } - } - } - } - } - - // check for type usages - outer: - for (TypeDefinition type : typeDefs) - { - try - { - validateDeleteClass(tenant, type); - } - catch(ModelInUseException e) - { - canDelete = false; - break outer; - } - catch(ModelNotInUseException e) - { - // ok, continue - } - } - - // check for aspect usages - outer: - for (AspectDefinition aspect : aspectDefs) - { - try - { - validateDeleteClass(tenant, aspect); - } catch(ModelInUseException e) - { - canDelete = false; - break outer; - } - catch(ModelNotInUseException e) - { - // ok, continue - } - } - - return canDelete; - } - - private void validateDeleteClass(final Tenant tenant, final ClassDefinition classDef) - { - final String classType = "TYPE"; - final QName className = classDef.getName(); - - String tenantDomain = "for tenant [" - + (tenant == null ? TenantService.DEFAULT_DOMAIN : tenant.getTenantDomain()) + "]"; - - // We need a separate transaction to do the qname delete "check" - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - try - { - // The class QName may not have been created in the database if no - // properties have been created that use it, so check first and then - // try to delete it. - if(qnameDAO.getQName(className) != null) - { - qnameDAO.deleteQName(className); - } - throw new ModelNotInUseException("Class " + className + " not in use"); - } - catch(DataIntegrityViolationException e) - { - // catch data integrity violation e.g. foreign key constraint exception - logger.debug(e); - throw new ModelInUseException("Cannot delete model, class " - + className + " is in use"); - } - } - }, false, true); - - - // check against workflow task usage - for (WorkflowDefinition workflowDef : workflowService.getDefinitions()) - { - for (WorkflowTaskDefinition workflowTaskDef : workflowService.getTaskDefinitions(workflowDef.getId())) - { - TypeDefinition workflowTypeDef = workflowTaskDef.metadata; - if (workflowTypeDef.getName().equals(className)) - { - throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found task definition in workflow " - + workflowDef.getName() + " with " + classType + " '" + className + "'"); - } - } - } - } - - private void validateDeleteProperty(QName modelName, QName propertyQName, boolean sharedModel) - { - String tenantDomain = TenantService.DEFAULT_DOMAIN; - if (sharedModel) - { - tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]"; - } - - PropertyDefinition prop = dictionaryDAO.getProperty(propertyQName); - if(prop != null && prop.getName().equals(propertyQName) && prop.getModel().getName().equals(modelName)) - { - validateDeleteProperty(tenantDomain, prop); - } - else - { - throw new AlfrescoRuntimeException("Cannot delete model " + modelName + " in tenant " + tenantDomain - + " - property definition '" + propertyQName + "' not defined in model '" + modelName + "'"); - } - } - - private void validateDeleteProperty(final String tenantDomain, final PropertyDefinition propDef) - { - final QName propName = propDef.getName(); - - // We need a separate transaction to do the qname delete "check" - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - return TenantUtil.runAsTenant(new TenantRunAsWork() - { - @Override - public Void doWork() throws Exception - { - try - { - // The property QName may not have been created in the database if no - // properties have been created that use it, so check first and then - // try to delete it. - if(qnameDAO.getQName(propName) != null) - { - qnameDAO.deleteQName(propName); - } - } - catch(DataIntegrityViolationException e) - { - // catch data integrity violation e.g. foreign key constraint exception - logger.debug(e); - throw new ModelInUseException("Failed to validate property delete, property " + propName + " is in use"); - } - - return null; - } - }, tenantDomain); - } - }, false, true); - } - - // validate delete of a referencable constraint def - private void validateDeleteConstraint(CompiledModel compiledModel, QName constraintName, boolean sharedModel) - { - String tenantDomain = TenantService.DEFAULT_DOMAIN; - if (sharedModel) - { - tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]"; - } - - Set referencedBy = new HashSet(0); - - // check for references to constraint definition - // note: could be anon prop constraint (if no referenceable constraint) - Collection allModels = dictionaryDAO.getModels(); - for (QName model : allModels) - { - Collection propDefs = null; - if (compiledModel.getModelDefinition().getName().equals(model)) - { - // TODO deal with multiple pending model updates - propDefs = compiledModel.getProperties(); - } - else - { - propDefs = dictionaryDAO.getProperties(model); - } - - for (PropertyDefinition propDef : propDefs) - { - for (ConstraintDefinition conDef : propDef.getConstraints()) - { - if (constraintName.equals(conDef.getRef())) - { - referencedBy.add(conDef.getName()); - } - } - } - } - - if (referencedBy.size() == 1) - { - throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by '" + referencedBy.toArray()[0] + "' property constraint"); - } - else if (referencedBy.size() > 1) - { - throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by " + referencedBy.size() + " property constraints"); - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean canDeleteModel(final QName modelName) - { - boolean canDeleteModel = true; - - // TODO add model locking during delete (would need to be tenant-aware & cluster-aware) to avoid potential - // for concurrent addition of new content/workflow as model is being deleted - - final Collection namespaceDefs; - final Collection typeDefs; - final Collection aspectDefs; - - try - { - namespaceDefs = dictionaryDAO.getNamespaces(modelName); - typeDefs = dictionaryDAO.getTypes(modelName); - aspectDefs = dictionaryDAO.getAspects(modelName); - - // TODO - in case of MT we do not currently allow deletion of an overridden model (with usages) ... but could allow if (re-)inherited model is equivalent to an incremental update only ? - canDeleteModel &= canDeleteModel(namespaceDefs, typeDefs, aspectDefs, null); - if(canDeleteModel) - { - if (tenantService.isEnabled() && tenantService.isTenantUser() == false) - { - // TODO should fix this up - won't scale - // shared model - need to check all tenants (whether enabled or disabled) unless they have overridden - List tenants = tenantAdminService.getAllTenants(); - for (final Tenant tenant : tenants) - { - // validate model delete within context of tenant domain - canDeleteModel &= AuthenticationUtil.runAs(new RunAsWork() - { - public Boolean doWork() - { - boolean canDelete = canDeleteModel(namespaceDefs, typeDefs, aspectDefs, tenant); - return canDelete; - } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain())); - - if(!canDeleteModel) - { - break; - } - } - } - } - } - catch (DictionaryException e) - { - if (logger.isDebugEnabled()) - { - logger.debug("Dictionary model '" + modelName + "' does not exist ... skip delete validation : " + e); - } - // we must return true here - there is no model - canDeleteModel = true; - } - - return canDeleteModel; - } - - /** - * {@inheritDoc} - */ - @Override - public void validateModel(CompiledModel compiledModel) - { - ModelDefinition modelDef = compiledModel.getModelDefinition(); - QName modelName = modelDef.getName(); - M2Model model = compiledModel.getM2Model(); - - checkCustomModelNamespace(model, TenantUtil.getCurrentDomain()); - - List modelDiffs = dictionaryDAO.diffModel(model); - - for (M2ModelDiff modelDiff : modelDiffs) - { - if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_DELETED)) - { - // TODO - check tenants if model is shared / inherited - if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_PROPERTY)) - { - validateDeleteProperty(modelName, modelDiff.getElementName(), false); - } - else if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_CONSTRAINT)) - { - validateDeleteConstraint(compiledModel, modelDiff.getElementName(), false); - } - else - { - throw new AlfrescoRuntimeException("Failed to validate model update - found deleted " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'"); - } - } - - if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED)) - { - throw new AlfrescoRuntimeException("Failed to validate model update - found non-incrementally updated " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'"); - } - } - - // TODO validate that any deleted constraints are not being referenced - else currently will become anon - or push down into model compilation (check backwards compatibility ...) - } -} +package org.alfresco.repo.dictionary; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.Tenant; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.alfresco.service.cmr.dictionary.ModelDefinition; +import org.alfresco.service.cmr.dictionary.NamespaceDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.dao.DataIntegrityViolationException; + +/** + * Model change validation covering model deletes, model constituent changes e.g. property deletes, + * additions, etc. + * + * @author sglover + */ +public class ModelValidatorImpl implements ModelValidator +{ + private static final Log logger = LogFactory.getLog(ModelValidatorImpl.class); + + private DictionaryDAO dictionaryDAO; + private QNameDAO qnameDAO; + private NamespaceService namespaceService; + private TransactionService transactionService; + private WorkflowService workflowService; + private TenantService tenantService; + private TenantAdminService tenantAdminService; + private boolean enforceTenantInNamespace = false; + + public void setEnforceTenantInNamespace(boolean enforceTenantInNamespace) + { + this.enforceTenantInNamespace = enforceTenantInNamespace; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + public void setDictionaryDAO(DictionaryDAO dictionaryDAO) + { + this.dictionaryDAO = dictionaryDAO; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + private void checkCustomModelNamespace(M2Model model, String tenantDomain) + { + if(tenantDomain != null && !tenantDomain.equals("") && enforceTenantInNamespace) + { + // check only for "real" tenants + for(M2Namespace namespace : model.getNamespaces()) + { + String namespaceURI = namespace.getUri(); + if(namespaceURI.indexOf(tenantDomain) == -1) + { + throw new DictionaryException("Namespace " + namespaceURI + " does not contain the tenant " + tenantDomain); + } + } + } + } + + private boolean canDeleteModel(Collection namespaceDefs, Collection typeDefs, + Collection aspectDefs, Tenant tenant) + { + boolean canDelete = true; + + String tenantDomain = "for tenant [" + + (tenant == null ? TenantService.DEFAULT_DOMAIN : tenant.getTenantDomain()) + "]"; + + List workflowDefs = workflowService.getDefinitions(); + + if (workflowDefs.size() > 0) + { + if (namespaceDefs.size() > 0) + { + // check workflow namespace usage + for (WorkflowDefinition workflowDef : workflowDefs) + { + String workflowDefName = workflowDef.getName(); + + String workflowNamespaceURI = null; + try + { + workflowNamespaceURI = QName.createQName(BPMEngineRegistry.getLocalId(workflowDefName), namespaceService).getNamespaceURI(); + } + catch (NamespaceException ne) + { + logger.warn("Skipped workflow when validating model delete - unknown namespace: "+ne); + continue; + } + + for (NamespaceDefinition namespaceDef : namespaceDefs) + { + if (workflowNamespaceURI.equals(namespaceDef.getUri())) + { + logger.warn("Failed to validate model delete" + tenantDomain + " - found workflow process definition " + + workflowDefName + " using model namespace '" + namespaceDef.getUri() + "'"); + canDelete = false; + } + } + } + } + } + + // check for type usages + outer: + for (TypeDefinition type : typeDefs) + { + try + { + validateDeleteClass(tenant, type); + } + catch(ModelInUseException e) + { + canDelete = false; + break outer; + } + catch(ModelNotInUseException e) + { + // ok, continue + } + } + + // check for aspect usages + outer: + for (AspectDefinition aspect : aspectDefs) + { + try + { + validateDeleteClass(tenant, aspect); + } catch(ModelInUseException e) + { + canDelete = false; + break outer; + } + catch(ModelNotInUseException e) + { + // ok, continue + } + } + + return canDelete; + } + + private void validateDeleteClass(final Tenant tenant, final ClassDefinition classDef) + { + final String classType = "TYPE"; + final QName className = classDef.getName(); + + String tenantDomain = "for tenant [" + + (tenant == null ? TenantService.DEFAULT_DOMAIN : tenant.getTenantDomain()) + "]"; + + // We need a separate transaction to do the qname delete "check" + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + try + { + // The class QName may not have been created in the database if no + // properties have been created that use it, so check first and then + // try to delete it. + if(qnameDAO.getQName(className) != null) + { + qnameDAO.deleteQName(className); + } + throw new ModelNotInUseException("Class " + className + " not in use"); + } + catch(DataIntegrityViolationException e) + { + // catch data integrity violation e.g. foreign key constraint exception + logger.debug(e); + throw new ModelInUseException("Cannot delete model, class " + + className + " is in use"); + } + } + }, false, true); + + + // check against workflow task usage + for (WorkflowDefinition workflowDef : workflowService.getDefinitions()) + { + for (WorkflowTaskDefinition workflowTaskDef : workflowService.getTaskDefinitions(workflowDef.getId())) + { + TypeDefinition workflowTypeDef = workflowTaskDef.metadata; + if (workflowTypeDef.getName().equals(className)) + { + throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found task definition in workflow " + + workflowDef.getName() + " with " + classType + " '" + className + "'"); + } + } + } + } + + private void validateDeleteProperty(QName modelName, QName propertyQName, boolean sharedModel) + { + String tenantDomain = TenantService.DEFAULT_DOMAIN; + if (sharedModel) + { + tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]"; + } + + PropertyDefinition prop = dictionaryDAO.getProperty(propertyQName); + if(prop != null && prop.getName().equals(propertyQName) && prop.getModel().getName().equals(modelName)) + { + validateDeleteProperty(tenantDomain, prop); + } + else + { + throw new AlfrescoRuntimeException("Cannot delete model " + modelName + " in tenant " + tenantDomain + + " - property definition '" + propertyQName + "' not defined in model '" + modelName + "'"); + } + } + + private void validateDeleteProperty(final String tenantDomain, final PropertyDefinition propDef) + { + final QName propName = propDef.getName(); + + // We need a separate transaction to do the qname delete "check" + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + return TenantUtil.runAsTenant(new TenantRunAsWork() + { + @Override + public Void doWork() throws Exception + { + try + { + // The property QName may not have been created in the database if no + // properties have been created that use it, so check first and then + // try to delete it. + if(qnameDAO.getQName(propName) != null) + { + qnameDAO.deleteQName(propName); + } + } + catch(DataIntegrityViolationException e) + { + // catch data integrity violation e.g. foreign key constraint exception + logger.debug(e); + throw new ModelInUseException("Failed to validate property delete, property " + propName + " is in use"); + } + + return null; + } + }, tenantDomain); + } + }, false, true); + } + + // validate delete of a referencable constraint def + private void validateDeleteConstraint(CompiledModel compiledModel, QName constraintName, boolean sharedModel) + { + String tenantDomain = TenantService.DEFAULT_DOMAIN; + if (sharedModel) + { + tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]"; + } + + Set referencedBy = new HashSet(0); + + // check for references to constraint definition + // note: could be anon prop constraint (if no referenceable constraint) + Collection allModels = dictionaryDAO.getModels(); + for (QName model : allModels) + { + Collection propDefs = null; + if (compiledModel.getModelDefinition().getName().equals(model)) + { + // TODO deal with multiple pending model updates + propDefs = compiledModel.getProperties(); + } + else + { + propDefs = dictionaryDAO.getProperties(model); + } + + for (PropertyDefinition propDef : propDefs) + { + for (ConstraintDefinition conDef : propDef.getConstraints()) + { + if (constraintName.equals(conDef.getRef())) + { + referencedBy.add(conDef.getName()); + } + } + } + } + + if (referencedBy.size() == 1) + { + throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by '" + referencedBy.toArray()[0] + "' property constraint"); + } + else if (referencedBy.size() > 1) + { + throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by " + referencedBy.size() + " property constraints"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canDeleteModel(final QName modelName) + { + boolean canDeleteModel = true; + + // TODO add model locking during delete (would need to be tenant-aware & cluster-aware) to avoid potential + // for concurrent addition of new content/workflow as model is being deleted + + final Collection namespaceDefs; + final Collection typeDefs; + final Collection aspectDefs; + + try + { + namespaceDefs = dictionaryDAO.getNamespaces(modelName); + typeDefs = dictionaryDAO.getTypes(modelName); + aspectDefs = dictionaryDAO.getAspects(modelName); + + // TODO - in case of MT we do not currently allow deletion of an overridden model (with usages) ... but could allow if (re-)inherited model is equivalent to an incremental update only ? + canDeleteModel &= canDeleteModel(namespaceDefs, typeDefs, aspectDefs, null); + if(canDeleteModel) + { + if (tenantService.isEnabled() && tenantService.isTenantUser() == false) + { + // TODO should fix this up - won't scale + // shared model - need to check all tenants (whether enabled or disabled) unless they have overridden + List tenants = tenantAdminService.getAllTenants(); + for (final Tenant tenant : tenants) + { + // validate model delete within context of tenant domain + canDeleteModel &= AuthenticationUtil.runAs(new RunAsWork() + { + public Boolean doWork() + { + boolean canDelete = canDeleteModel(namespaceDefs, typeDefs, aspectDefs, tenant); + return canDelete; + } + }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain())); + + if(!canDeleteModel) + { + break; + } + } + } + } + } + catch (DictionaryException e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Dictionary model '" + modelName + "' does not exist ... skip delete validation : " + e); + } + // we must return true here - there is no model + canDeleteModel = true; + } + + return canDeleteModel; + } + + /** + * {@inheritDoc} + */ + @Override + public void validateModel(CompiledModel compiledModel) + { + ModelDefinition modelDef = compiledModel.getModelDefinition(); + QName modelName = modelDef.getName(); + M2Model model = compiledModel.getM2Model(); + + checkCustomModelNamespace(model, TenantUtil.getCurrentDomain()); + + List modelDiffs = dictionaryDAO.diffModel(model); + + for (M2ModelDiff modelDiff : modelDiffs) + { + if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_DELETED)) + { + // TODO - check tenants if model is shared / inherited + if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_PROPERTY)) + { + validateDeleteProperty(modelName, modelDiff.getElementName(), false); + } + else if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_CONSTRAINT)) + { + validateDeleteConstraint(compiledModel, modelDiff.getElementName(), false); + } + else + { + throw new AlfrescoRuntimeException("Failed to validate model update - found deleted " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'"); + } + } + + if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED)) + { + throw new AlfrescoRuntimeException("Failed to validate model update - found non-incrementally updated " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'"); + } + } + + // TODO validate that any deleted constraints are not being referenced - else currently will become anon - or push down into model compilation (check backwards compatibility ...) + } +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/AbstractCalendarPeriodProvider.java b/source/java/org/alfresco/repo/dictionary/types/period/AbstractCalendarPeriodProvider.java index a8648a9531..003bf56821 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/AbstractCalendarPeriodProvider.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/AbstractCalendarPeriodProvider.java @@ -1,66 +1,66 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; -import java.util.Date; - -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Support for calendar based periods - * @author andyh - * - */ -public abstract class AbstractCalendarPeriodProvider extends AbstractPeriodProvider -{ - /** Logger */ - private static Log logger = LogFactory.getLog(AbstractCalendarPeriodProvider.class); - - public String getDefaultExpression() - { - return "1"; - } - - public ExpressionMutiplicity getExpressionMutiplicity() - { - return ExpressionMutiplicity.OPTIONAL; - } - - public Date getNextDate(Date date, String expression) - { - int value = 1; - try - { - value = Integer.parseInt(expression); - } - catch (NumberFormatException nfe) - { - // default to 1 and log warning - value = 1; - - if (logger.isWarnEnabled()) - logger.warn("\"" + expression + "\" is not a valid period expression!"); - } - - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - - add(calendar, value); - Date next = calendar.getTime(); - return next; - } - - /** - * Implementation add - * @param calendar Calendar - * @param value int - */ - public abstract void add(Calendar calendar, int value); - - public QName getExpressionDataType() - { - return DataTypeDefinition.INT; - } -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; +import java.util.Date; + +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Support for calendar based periods + * @author andyh + * + */ +public abstract class AbstractCalendarPeriodProvider extends AbstractPeriodProvider +{ + /** Logger */ + private static Log logger = LogFactory.getLog(AbstractCalendarPeriodProvider.class); + + public String getDefaultExpression() + { + return "1"; + } + + public ExpressionMutiplicity getExpressionMutiplicity() + { + return ExpressionMutiplicity.OPTIONAL; + } + + public Date getNextDate(Date date, String expression) + { + int value = 1; + try + { + value = Integer.parseInt(expression); + } + catch (NumberFormatException nfe) + { + // default to 1 and log warning + value = 1; + + if (logger.isWarnEnabled()) + logger.warn("\"" + expression + "\" is not a valid period expression!"); + } + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + + add(calendar, value); + Date next = calendar.getTime(); + return next; + } + + /** + * Implementation add + * @param calendar Calendar + * @param value int + */ + public abstract void add(Calendar calendar, int value); + + public QName getExpressionDataType() + { + return DataTypeDefinition.INT; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/AbstractEndOfCalendarPeriodProvider.java b/source/java/org/alfresco/repo/dictionary/types/period/AbstractEndOfCalendarPeriodProvider.java index 7e767dbe6c..d40329220f 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/AbstractEndOfCalendarPeriodProvider.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/AbstractEndOfCalendarPeriodProvider.java @@ -1,51 +1,51 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; - -/** - * Support for calendar based "end of" periods with month and day offsets for fiscal year support - * @author andyh - * - */ -public abstract class AbstractEndOfCalendarPeriodProvider extends AbstractCalendarPeriodProvider -{ - private int startDayOfMonth = 1; - - private int startMonth = Calendar.JANUARY; - - /** - * Get the start day of the month (as defined by Calendar) - * @return - the start day of the month - */ - public int getStartDayOfMonth() - { - return startDayOfMonth; - } - - /** - * Set the start day of the month (as defined by Calendar) - * @param startDayOfMonth int - */ - public void setStartDayOfMonth(int startDayOfMonth) - { - this.startDayOfMonth = startDayOfMonth; - } - - /** - * Get the start month (as defined by Calendar) - * @return - the start month - */ - public int getStartMonth() - { - return startMonth; - } - - /** - * Set the start month (as defined by Calendar) - * @param startMonth int - */ - public void setStartMonth(int startMonth) - { - this.startMonth = startMonth; - } -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; + +/** + * Support for calendar based "end of" periods with month and day offsets for fiscal year support + * @author andyh + * + */ +public abstract class AbstractEndOfCalendarPeriodProvider extends AbstractCalendarPeriodProvider +{ + private int startDayOfMonth = 1; + + private int startMonth = Calendar.JANUARY; + + /** + * Get the start day of the month (as defined by Calendar) + * @return - the start day of the month + */ + public int getStartDayOfMonth() + { + return startDayOfMonth; + } + + /** + * Set the start day of the month (as defined by Calendar) + * @param startDayOfMonth int + */ + public void setStartDayOfMonth(int startDayOfMonth) + { + this.startDayOfMonth = startDayOfMonth; + } + + /** + * Get the start month (as defined by Calendar) + * @return - the start month + */ + public int getStartMonth() + { + return startMonth; + } + + /** + * Set the start month (as defined by Calendar) + * @param startMonth int + */ + public void setStartMonth(int startMonth) + { + this.startMonth = startMonth; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/AbstractPeriodProvider.java b/source/java/org/alfresco/repo/dictionary/types/period/AbstractPeriodProvider.java index 8e61aef7e2..90b5f4a363 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/AbstractPeriodProvider.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/AbstractPeriodProvider.java @@ -1,47 +1,47 @@ -package org.alfresco.repo.dictionary.types.period; - -import org.springframework.extensions.surf.util.I18NUtil; -import org.alfresco.service.cmr.repository.Period; -import org.alfresco.service.cmr.repository.PeriodProvider; -import org.springframework.beans.factory.InitializingBean; - -/** - * Common support for period implementations. - * - * They are Spring beans that register in the bootstrap context. - * - * @author andyh - * - */ -public abstract class AbstractPeriodProvider implements PeriodProvider, InitializingBean -{ - protected static final String MSG_PREFIX = "period_provider."; - - /** - * Default constructor - */ - public AbstractPeriodProvider() - { - super(); - } - - public void afterPropertiesSet() throws Exception - { - Period.registerProvider(this); - } - - /* - * @see org.alfresco.service.cmr.repository.PeriodProvider#getDisplayLabel() - */ - public String getDisplayLabel() - { - String label = I18NUtil.getMessage(MSG_PREFIX + getPeriodType()); - - if (label == null) - { - label = getPeriodType(); - } - - return label; - } -} +package org.alfresco.repo.dictionary.types.period; + +import org.springframework.extensions.surf.util.I18NUtil; +import org.alfresco.service.cmr.repository.Period; +import org.alfresco.service.cmr.repository.PeriodProvider; +import org.springframework.beans.factory.InitializingBean; + +/** + * Common support for period implementations. + * + * They are Spring beans that register in the bootstrap context. + * + * @author andyh + * + */ +public abstract class AbstractPeriodProvider implements PeriodProvider, InitializingBean +{ + protected static final String MSG_PREFIX = "period_provider."; + + /** + * Default constructor + */ + public AbstractPeriodProvider() + { + super(); + } + + public void afterPropertiesSet() throws Exception + { + Period.registerProvider(this); + } + + /* + * @see org.alfresco.service.cmr.repository.PeriodProvider#getDisplayLabel() + */ + public String getDisplayLabel() + { + String label = I18NUtil.getMessage(MSG_PREFIX + getPeriodType()); + + if (label == null) + { + label = getPeriodType(); + } + + return label; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/Cron.java b/source/java/org/alfresco/repo/dictionary/types/period/Cron.java index bfa6b48e55..64049a179a 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/Cron.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/Cron.java @@ -1,57 +1,57 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.text.ParseException; -import java.util.Date; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.namespace.QName; -import org.quartz.CronExpression; - -/** - * Cron based periods - * - * @author andyh - */ -public class Cron extends AbstractPeriodProvider -{ - /** - * Period type - */ - public static final String PERIOD_TYPE = "cron"; - - public String getDefaultExpression() - { - return "59 59 23 * * ?"; - } - - public ExpressionMutiplicity getExpressionMutiplicity() - { - return ExpressionMutiplicity.MANDATORY; - } - - public Date getNextDate(Date date, String expression) - { - CronExpression ce; - try - { - ce = new CronExpression(expression); - } - catch (ParseException e) - { - throw new AlfrescoRuntimeException("Invalid cron expression: " + expression); - } - return ce.getNextValidTimeAfter(date); - } - - public String getPeriodType() - { - return PERIOD_TYPE; - } - - public QName getExpressionDataType() - { - return DataTypeDefinition.TEXT; - } - -} +package org.alfresco.repo.dictionary.types.period; + +import java.text.ParseException; +import java.util.Date; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.namespace.QName; +import org.quartz.CronExpression; + +/** + * Cron based periods + * + * @author andyh + */ +public class Cron extends AbstractPeriodProvider +{ + /** + * Period type + */ + public static final String PERIOD_TYPE = "cron"; + + public String getDefaultExpression() + { + return "59 59 23 * * ?"; + } + + public ExpressionMutiplicity getExpressionMutiplicity() + { + return ExpressionMutiplicity.MANDATORY; + } + + public Date getNextDate(Date date, String expression) + { + CronExpression ce; + try + { + ce = new CronExpression(expression); + } + catch (ParseException e) + { + throw new AlfrescoRuntimeException("Invalid cron expression: " + expression); + } + return ce.getNextValidTimeAfter(date); + } + + public String getPeriodType() + { + return PERIOD_TYPE; + } + + public QName getExpressionDataType() + { + return DataTypeDefinition.TEXT; + } + +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/Days.java b/source/java/org/alfresco/repo/dictionary/types/period/Days.java index 2461a29da9..d42ae05061 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/Days.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/Days.java @@ -1,29 +1,29 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; - - -/** - * Day based periods - * @author andyh - * - */ -public class Days extends AbstractCalendarPeriodProvider -{ - /** - * - */ - public static final String PERIOD_TYPE = "day"; - - public String getPeriodType() - { - return PERIOD_TYPE; - } - - @Override - public void add(Calendar calendar, int value) - { - calendar.add(Calendar.DAY_OF_YEAR, value); - } - -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; + + +/** + * Day based periods + * @author andyh + * + */ +public class Days extends AbstractCalendarPeriodProvider +{ + /** + * + */ + public static final String PERIOD_TYPE = "day"; + + public String getPeriodType() + { + return PERIOD_TYPE; + } + + @Override + public void add(Calendar calendar, int value) + { + calendar.add(Calendar.DAY_OF_YEAR, value); + } + +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialMonth.java b/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialMonth.java index e8a0c8e027..2a34d88513 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialMonth.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialMonth.java @@ -1,21 +1,21 @@ -package org.alfresco.repo.dictionary.types.period; - -/** - * End of financial month - * @author andyh - * - */ -public class EndOfFinancialMonth extends EndOfMonth -{ - /** - * - */ - public static final String PERIOD_TYPE = "fmend"; - - - public String getPeriodType() - { - return PERIOD_TYPE; - } - -} +package org.alfresco.repo.dictionary.types.period; + +/** + * End of financial month + * @author andyh + * + */ +public class EndOfFinancialMonth extends EndOfMonth +{ + /** + * + */ + public static final String PERIOD_TYPE = "fmend"; + + + public String getPeriodType() + { + return PERIOD_TYPE; + } + +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialQuarter.java b/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialQuarter.java index 7de2808601..b325b7c29b 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialQuarter.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialQuarter.java @@ -1,20 +1,20 @@ -package org.alfresco.repo.dictionary.types.period; - -/** - * End of financial quarter - * @author andyh - * - */ -public class EndOfFinancialQuarter extends EndOfQuarter -{ - /** - * - */ - public static final String PERIOD_TYPE = "fqend"; - - - public String getPeriodType() - { - return PERIOD_TYPE; - } -} +package org.alfresco.repo.dictionary.types.period; + +/** + * End of financial quarter + * @author andyh + * + */ +public class EndOfFinancialQuarter extends EndOfQuarter +{ + /** + * + */ + public static final String PERIOD_TYPE = "fqend"; + + + public String getPeriodType() + { + return PERIOD_TYPE; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialYear.java b/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialYear.java index aa4067ea8c..89c30526f5 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialYear.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/EndOfFinancialYear.java @@ -1,20 +1,20 @@ -package org.alfresco.repo.dictionary.types.period; - -/** - * End of financial year - * @author andyh - * - */ -public class EndOfFinancialYear extends EndOfYear -{ - /** - * - */ - public static final String PERIOD_TYPE = "fyend"; - - - public String getPeriodType() - { - return PERIOD_TYPE; - } -} +package org.alfresco.repo.dictionary.types.period; + +/** + * End of financial year + * @author andyh + * + */ +public class EndOfFinancialYear extends EndOfYear +{ + /** + * + */ + public static final String PERIOD_TYPE = "fyend"; + + + public String getPeriodType() + { + return PERIOD_TYPE; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/EndOfMonth.java b/source/java/org/alfresco/repo/dictionary/types/period/EndOfMonth.java index 67905022c9..d4932f8168 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/EndOfMonth.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/EndOfMonth.java @@ -1,51 +1,51 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; - -/** - * End of month - * @author andyh - * - */ -public class EndOfMonth extends AbstractEndOfCalendarPeriodProvider -{ - /** - * - */ - public static final String PERIOD_TYPE = "monthend"; - - @Override - public void add(Calendar calendar, int value) - { - // Add a milli to nudge roll over given a month end date - if (value > 0) - { - calendar.add(Calendar.MILLISECOND, 1); - } - - int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); - if(dayOfMonth < getStartDayOfMonth()) - { - calendar.add(Calendar.MONTH, value-1); - } - else - { - calendar.add(Calendar.MONTH, value); - } - - calendar.set(Calendar.DAY_OF_MONTH, getStartDayOfMonth()); - calendar.add(Calendar.DAY_OF_YEAR, -1); - - // Set the time one minute to midnight - calendar.set(Calendar.HOUR_OF_DAY, 23); - calendar.set(Calendar.MINUTE, 59); - calendar.set(Calendar.SECOND, 59); - calendar.set(Calendar.MILLISECOND, 999); - } - - public String getPeriodType() - { - return PERIOD_TYPE; - } - -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; + +/** + * End of month + * @author andyh + * + */ +public class EndOfMonth extends AbstractEndOfCalendarPeriodProvider +{ + /** + * + */ + public static final String PERIOD_TYPE = "monthend"; + + @Override + public void add(Calendar calendar, int value) + { + // Add a milli to nudge roll over given a month end date + if (value > 0) + { + calendar.add(Calendar.MILLISECOND, 1); + } + + int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); + if(dayOfMonth < getStartDayOfMonth()) + { + calendar.add(Calendar.MONTH, value-1); + } + else + { + calendar.add(Calendar.MONTH, value); + } + + calendar.set(Calendar.DAY_OF_MONTH, getStartDayOfMonth()); + calendar.add(Calendar.DAY_OF_YEAR, -1); + + // Set the time one minute to midnight + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + } + + public String getPeriodType() + { + return PERIOD_TYPE; + } + +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/EndOfQuarter.java b/source/java/org/alfresco/repo/dictionary/types/period/EndOfQuarter.java index 8a595329d9..6e07c44f70 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/EndOfQuarter.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/EndOfQuarter.java @@ -1,60 +1,60 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; - -/** - * End of quarter - * @author andyh - * - */ -public class EndOfQuarter extends AbstractEndOfCalendarPeriodProvider -{ - /** - * - */ - public static final String PERIOD_TYPE = "quarterend"; - - @Override - public void add(Calendar calendar, int value) - { - // Add a milli to nudge roll over given a quarter end date - if (value > 0) - { - calendar.add(Calendar.MILLISECOND, 1); - } - - int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); - int month = calendar.get(Calendar.MONTH); - int monthInYear = month - getStartMonth(); - if(monthInYear < 0) - { - monthInYear += 12; - } - int residualMonths = monthInYear % 3; - if(dayOfMonth < getStartDayOfMonth() && (residualMonths == 0)) - { - calendar.add(Calendar.MONTH, (value-1)*3); - - } - else - { - calendar.add(Calendar.MONTH, value*3); - } - - calendar.add(Calendar.MONTH, -residualMonths); - calendar.set(Calendar.DAY_OF_MONTH, getStartDayOfMonth()); - calendar.add(Calendar.DAY_OF_YEAR, -1); - - // Set the time one minute to midnight - calendar.set(Calendar.HOUR_OF_DAY, 23); - calendar.set(Calendar.MINUTE, 59); - calendar.set(Calendar.SECOND, 59); - calendar.set(Calendar.MILLISECOND, 999); - } - - public String getPeriodType() - { - return PERIOD_TYPE; - } - -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; + +/** + * End of quarter + * @author andyh + * + */ +public class EndOfQuarter extends AbstractEndOfCalendarPeriodProvider +{ + /** + * + */ + public static final String PERIOD_TYPE = "quarterend"; + + @Override + public void add(Calendar calendar, int value) + { + // Add a milli to nudge roll over given a quarter end date + if (value > 0) + { + calendar.add(Calendar.MILLISECOND, 1); + } + + int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); + int month = calendar.get(Calendar.MONTH); + int monthInYear = month - getStartMonth(); + if(monthInYear < 0) + { + monthInYear += 12; + } + int residualMonths = monthInYear % 3; + if(dayOfMonth < getStartDayOfMonth() && (residualMonths == 0)) + { + calendar.add(Calendar.MONTH, (value-1)*3); + + } + else + { + calendar.add(Calendar.MONTH, value*3); + } + + calendar.add(Calendar.MONTH, -residualMonths); + calendar.set(Calendar.DAY_OF_MONTH, getStartDayOfMonth()); + calendar.add(Calendar.DAY_OF_YEAR, -1); + + // Set the time one minute to midnight + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + } + + public String getPeriodType() + { + return PERIOD_TYPE; + } + +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/EndOfYear.java b/source/java/org/alfresco/repo/dictionary/types/period/EndOfYear.java index 8ab07e21e0..ba7578bf8c 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/EndOfYear.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/EndOfYear.java @@ -1,54 +1,54 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; - -/** - * End of year - * @author andyh - * - */ -public class EndOfYear extends AbstractEndOfCalendarPeriodProvider -{ - /** - * - */ - public static final String PERIOD_TYPE = "yearend"; - - @Override - public void add(Calendar calendar, int value) - { - // Add a milli to nudge roll over given a year end date - if (value > 0) - { - calendar.add(Calendar.MILLISECOND, 1); - } - - int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); - int month = calendar.get(Calendar.MONTH); - if ((month < getStartMonth()) || ((dayOfMonth < getStartDayOfMonth()) && (month == getStartMonth()))) - { - calendar.add(Calendar.YEAR, value - 1); - } - else - { - calendar.add(Calendar.YEAR, value); - } - - calendar.set(Calendar.MONTH, getStartMonth()); - calendar.set(Calendar.DAY_OF_MONTH, getStartDayOfMonth()); - - calendar.add(Calendar.DAY_OF_YEAR, -1); - - // Set the time one minute to midnight - calendar.set(Calendar.HOUR_OF_DAY, 23); - calendar.set(Calendar.MINUTE, 59); - calendar.set(Calendar.SECOND, 59); - calendar.set(Calendar.MILLISECOND, 999); - } - - public String getPeriodType() - { - return PERIOD_TYPE; - } - -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; + +/** + * End of year + * @author andyh + * + */ +public class EndOfYear extends AbstractEndOfCalendarPeriodProvider +{ + /** + * + */ + public static final String PERIOD_TYPE = "yearend"; + + @Override + public void add(Calendar calendar, int value) + { + // Add a milli to nudge roll over given a year end date + if (value > 0) + { + calendar.add(Calendar.MILLISECOND, 1); + } + + int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); + int month = calendar.get(Calendar.MONTH); + if ((month < getStartMonth()) || ((dayOfMonth < getStartDayOfMonth()) && (month == getStartMonth()))) + { + calendar.add(Calendar.YEAR, value - 1); + } + else + { + calendar.add(Calendar.YEAR, value); + } + + calendar.set(Calendar.MONTH, getStartMonth()); + calendar.set(Calendar.DAY_OF_MONTH, getStartDayOfMonth()); + + calendar.add(Calendar.DAY_OF_YEAR, -1); + + // Set the time one minute to midnight + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + } + + public String getPeriodType() + { + return PERIOD_TYPE; + } + +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/Months.java b/source/java/org/alfresco/repo/dictionary/types/period/Months.java index 473c8229ef..ab5bfa8e95 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/Months.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/Months.java @@ -1,28 +1,28 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; - -/** - * Months - * @author andyh - * - */ -public class Months extends AbstractCalendarPeriodProvider -{ - /** - * - */ - public static final String PERIOD_TYPE = "month"; - - public String getPeriodType() - { - return PERIOD_TYPE; - } - - @Override - public void add(Calendar calendar, int value) - { - calendar.add(Calendar.MONTH, value); - } - -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; + +/** + * Months + * @author andyh + * + */ +public class Months extends AbstractCalendarPeriodProvider +{ + /** + * + */ + public static final String PERIOD_TYPE = "month"; + + public String getPeriodType() + { + return PERIOD_TYPE; + } + + @Override + public void add(Calendar calendar, int value) + { + calendar.add(Calendar.MONTH, value); + } + +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/NoPeriod.java b/source/java/org/alfresco/repo/dictionary/types/period/NoPeriod.java index 53aa92c0c6..7edada2190 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/NoPeriod.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/NoPeriod.java @@ -1,52 +1,52 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Date; - -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.namespace.QName; - -/** - * No period Period type "none" - * - * @author andyh - */ -public class NoPeriod extends AbstractPeriodProvider -{ - /** - * - */ - public static final String PERIOD_TYPE = "none"; - - /** - * Default constructor - */ - public NoPeriod() - { - - } - - public String getDefaultExpression() - { - return null; - } - - public ExpressionMutiplicity getExpressionMutiplicity() - { - return ExpressionMutiplicity.NONE; - } - - public Date getNextDate(Date date, String expression) - { - return null; - } - - public String getPeriodType() - { - return PERIOD_TYPE; - } - - public QName getExpressionDataType() - { - return null; - } -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Date; + +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.namespace.QName; + +/** + * No period Period type "none" + * + * @author andyh + */ +public class NoPeriod extends AbstractPeriodProvider +{ + /** + * + */ + public static final String PERIOD_TYPE = "none"; + + /** + * Default constructor + */ + public NoPeriod() + { + + } + + public String getDefaultExpression() + { + return null; + } + + public ExpressionMutiplicity getExpressionMutiplicity() + { + return ExpressionMutiplicity.NONE; + } + + public Date getNextDate(Date date, String expression) + { + return null; + } + + public String getPeriodType() + { + return PERIOD_TYPE; + } + + public QName getExpressionDataType() + { + return null; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/Quarters.java b/source/java/org/alfresco/repo/dictionary/types/period/Quarters.java index e7b123c366..64753020fa 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/Quarters.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/Quarters.java @@ -1,27 +1,27 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; - -/** - * Quarters - * @author andyh - * - */ -public class Quarters extends AbstractCalendarPeriodProvider -{ - /** - * - */ - public static final String PERIOD_TYPE = "quarter"; - - public String getPeriodType() - { - return PERIOD_TYPE; - } - - @Override - public void add(Calendar calendar, int value) - { - calendar.add(Calendar.MONTH, value*3); - } -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; + +/** + * Quarters + * @author andyh + * + */ +public class Quarters extends AbstractCalendarPeriodProvider +{ + /** + * + */ + public static final String PERIOD_TYPE = "quarter"; + + public String getPeriodType() + { + return PERIOD_TYPE; + } + + @Override + public void add(Calendar calendar, int value) + { + calendar.add(Calendar.MONTH, value*3); + } +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/Weeks.java b/source/java/org/alfresco/repo/dictionary/types/period/Weeks.java index 2d5708f001..80fd70190d 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/Weeks.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/Weeks.java @@ -1,26 +1,26 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; -/** - * Weeks - * @author andyh - * - */ -public class Weeks extends AbstractCalendarPeriodProvider -{ - /** - * - */ - public static final String PERIOD_TYPE = "week"; - - public String getPeriodType() - { - return PERIOD_TYPE; - } - - @Override - public void add(Calendar calendar, int value) - { - calendar.add(Calendar.WEEK_OF_YEAR, value); - } -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; +/** + * Weeks + * @author andyh + * + */ +public class Weeks extends AbstractCalendarPeriodProvider +{ + /** + * + */ + public static final String PERIOD_TYPE = "week"; + + public String getPeriodType() + { + return PERIOD_TYPE; + } + + @Override + public void add(Calendar calendar, int value) + { + calendar.add(Calendar.WEEK_OF_YEAR, value); + } +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/XMLDuration.java b/source/java/org/alfresco/repo/dictionary/types/period/XMLDuration.java index 9165e75581..74d0a24f5e 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/XMLDuration.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/XMLDuration.java @@ -1,47 +1,47 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Date; - -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.datatype.Duration; -import org.alfresco.service.namespace.QName; - -/** - * XMLDuration - * @author andyh - * - */ -public class XMLDuration extends AbstractPeriodProvider -{ - /** - * Period type - */ - public static final String PERIOD_TYPE = "duration"; - - public String getDefaultExpression() - { - return "P1D"; - } - - public ExpressionMutiplicity getExpressionMutiplicity() - { - return ExpressionMutiplicity.MANDATORY; - } - - public Date getNextDate(Date date, String expression) - { - Duration d = new Duration(expression); - return Duration.add(date, d); - } - - public String getPeriodType() - { - return PERIOD_TYPE; - } - - public QName getExpressionDataType() - { - return DataTypeDefinition.TEXT; - } - -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Date; + +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.datatype.Duration; +import org.alfresco.service.namespace.QName; + +/** + * XMLDuration + * @author andyh + * + */ +public class XMLDuration extends AbstractPeriodProvider +{ + /** + * Period type + */ + public static final String PERIOD_TYPE = "duration"; + + public String getDefaultExpression() + { + return "P1D"; + } + + public ExpressionMutiplicity getExpressionMutiplicity() + { + return ExpressionMutiplicity.MANDATORY; + } + + public Date getNextDate(Date date, String expression) + { + Duration d = new Duration(expression); + return Duration.add(date, d); + } + + public String getPeriodType() + { + return PERIOD_TYPE; + } + + public QName getExpressionDataType() + { + return DataTypeDefinition.TEXT; + } + +} diff --git a/source/java/org/alfresco/repo/dictionary/types/period/Years.java b/source/java/org/alfresco/repo/dictionary/types/period/Years.java index 7549b0506b..47f336c7fa 100644 --- a/source/java/org/alfresco/repo/dictionary/types/period/Years.java +++ b/source/java/org/alfresco/repo/dictionary/types/period/Years.java @@ -1,27 +1,27 @@ -package org.alfresco.repo.dictionary.types.period; - -import java.util.Calendar; - -/** - * Years - * @author andyh - * - */ -public class Years extends AbstractCalendarPeriodProvider -{ - /** - * - */ - public static final String PERIOD_TYPE = "year"; - - public String getPeriodType() - { - return PERIOD_TYPE; - } - - @Override - public void add(Calendar calendar, int value) - { - calendar.add(Calendar.YEAR, value); - } -} +package org.alfresco.repo.dictionary.types.period; + +import java.util.Calendar; + +/** + * Years + * @author andyh + * + */ +public class Years extends AbstractCalendarPeriodProvider +{ + /** + * + */ + public static final String PERIOD_TYPE = "year"; + + public String getPeriodType() + { + return PERIOD_TYPE; + } + + @Override + public void add(Calendar calendar, int value) + { + calendar.add(Calendar.YEAR, value); + } +} diff --git a/source/java/org/alfresco/repo/domain/contentdata/ContentUrlKeyEntity.java b/source/java/org/alfresco/repo/domain/contentdata/ContentUrlKeyEntity.java index d1a82e6c09..a51999f57f 100644 --- a/source/java/org/alfresco/repo/domain/contentdata/ContentUrlKeyEntity.java +++ b/source/java/org/alfresco/repo/domain/contentdata/ContentUrlKeyEntity.java @@ -1,219 +1,219 @@ - -package org.alfresco.repo.domain.contentdata; - -import java.io.Serializable; -import java.nio.ByteBuffer; - -import org.alfresco.service.cmr.repository.ContentUrlKey; -import org.apache.commons.codec.DecoderException; - -/** - * - * @author sglover - * - */ -public class ContentUrlKeyEntity implements Serializable -{ - private static final long serialVersionUID = -6594309522849585169L; - - private Long id; - private Long contentUrlId; - private byte[] encryptedKeyAsBytes; - private Integer keySize; - private String algorithm; - private String masterKeystoreId; - private String masterKeyAlias; - private Long unencryptedFileSize; - - public ContentUrlKeyEntity() - { - } - - public ContentUrlKey getContentUrlKey() throws DecoderException - { - ContentUrlKey contentUrlKey = new ContentUrlKey(); - contentUrlKey.setAlgorithm(algorithm); - contentUrlKey.setKeySize(keySize); - contentUrlKey.setEncryptedKeyBytes(ByteBuffer.wrap(encryptedKeyAsBytes)); - contentUrlKey.setMasterKeyAlias(masterKeyAlias); - contentUrlKey.setMasterKeystoreId(masterKeystoreId); - contentUrlKey.setUnencryptedFileSize(unencryptedFileSize); - return contentUrlKey; - } - - public Long getContentUrlId() - { - return contentUrlId; - } - - public void setContentUrlId(Long contentUrlId) - { - this.contentUrlId = contentUrlId; - } - - public void setEncryptedKeyAsBytes(byte[] encryptedKeyAsBytes) - { - this.encryptedKeyAsBytes = encryptedKeyAsBytes; - } - - public byte[] getEncryptedKeyAsBytes() - { - return encryptedKeyAsBytes; - } - - public void setEncryptedKey(EncryptedKey encryptedKey) - { - byte[] encryptedKeyAsBytes = new byte[encryptedKey.getByteBuffer().remaining()]; - encryptedKey.getByteBuffer().get(encryptedKeyAsBytes); - - this.encryptedKeyAsBytes = encryptedKeyAsBytes; - this.keySize = encryptedKeyAsBytes.length*8; - this.algorithm = encryptedKey.getAlgorithm(); - this.masterKeyAlias = encryptedKey.getMasterKeyAlias(); - this.masterKeystoreId = encryptedKey.getMasterKeystoreId(); - } - - public static ContentUrlKeyEntity setEncryptedKey(ContentUrlKeyEntity existing, EncryptedKey encryptedKey) - { - ContentUrlKeyEntity newContentUrlKeyEntity = new ContentUrlKeyEntity(); - - byte[] encryptedKeyAsBytes = new byte[encryptedKey.getByteBuffer().remaining()]; - encryptedKey.getByteBuffer().get(encryptedKeyAsBytes); - newContentUrlKeyEntity.setEncryptedKeyAsBytes(encryptedKeyAsBytes); - newContentUrlKeyEntity.setKeySize(encryptedKeyAsBytes.length*8); - newContentUrlKeyEntity.setAlgorithm(encryptedKey.getAlgorithm()); - newContentUrlKeyEntity.setMasterKeyAlias(encryptedKey.getMasterKeyAlias()); - newContentUrlKeyEntity.setMasterKeystoreId(encryptedKey.getMasterKeystoreId()); - newContentUrlKeyEntity.setContentUrlId(existing.getContentUrlId()); - newContentUrlKeyEntity.setUnencryptedFileSize(existing.getUnencryptedFileSize()); - newContentUrlKeyEntity.setId(existing.getId()); - - return newContentUrlKeyEntity; - } - - public Long getId() - { - return id; - } - - public void setId(Long id) - { - this.id = id; - } - - public EncryptedKey getEncryptedKey() throws DecoderException - { - EncryptedKey encryptedKey = new EncryptedKey(getMasterKeystoreId(), getMasterKeyAlias(), - getAlgorithm(), ByteBuffer.wrap(this.encryptedKeyAsBytes)); - return encryptedKey; - } - - public Long getUnencryptedFileSize() - { - return unencryptedFileSize; - } - - public void setUnencryptedFileSize(Long unencryptedFileSize) - { - this.unencryptedFileSize = unencryptedFileSize; - } - - public void setKeySize(Integer keySize) - { - this.keySize = keySize; - } - - public Integer getKeySize() - { - return keySize; - } - - public String getAlgorithm() - { - return algorithm; - } - - public void setAlgorithm(String algorithm) - { - this.algorithm = algorithm; - } - - public String getMasterKeystoreId() - { - return masterKeystoreId; - } - - public void setMasterKeystoreId(String masterKeystoreId) - { - this.masterKeystoreId = masterKeystoreId; - } - - public String getMasterKeyAlias() - { - return masterKeyAlias; - } - - public void setMasterKeyAlias(String masterKeyAlias) - { - this.masterKeyAlias = masterKeyAlias; - } - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = prime * result - + ((algorithm == null) ? 0 : algorithm.hashCode()); - result = prime * result + ((id == null) ? 0 : id.hashCode()); - result = prime * result - + ((masterKeyAlias == null) ? 0 : masterKeyAlias.hashCode()); - result = prime - * result - + ((masterKeystoreId == null) ? 0 : masterKeystoreId.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ContentUrlKeyEntity other = (ContentUrlKeyEntity) obj; - if (algorithm == null) { - if (other.algorithm != null) - return false; - } else if (!algorithm.equals(other.algorithm)) - return false; - if (id == null) { - if (other.id != null) - return false; - } else if (!id.equals(other.id)) - return false; - if (masterKeyAlias == null) { - if (other.masterKeyAlias != null) - return false; - } else if (!masterKeyAlias.equals(other.masterKeyAlias)) - return false; - if (masterKeystoreId == null) { - if (other.masterKeystoreId != null) - return false; - } else if (!masterKeystoreId.equals(other.masterKeystoreId)) - return false; - return true; - } - - @Override - public String toString() - { - return "ContentUrlKeyEntity [id=" + id + ", encryptedKeyAsBytes=" - + encryptedKeyAsBytes+ ", keySize=" + keySize + ", algorithm=" - + algorithm + ", masterKeystoreId=" + masterKeystoreId - + ", masterKeyAlias=" + masterKeyAlias - + ", unencryptedFileSize=" + unencryptedFileSize + "]"; - } -} + +package org.alfresco.repo.domain.contentdata; + +import java.io.Serializable; +import java.nio.ByteBuffer; + +import org.alfresco.service.cmr.repository.ContentUrlKey; +import org.apache.commons.codec.DecoderException; + +/** + * + * @author sglover + * + */ +public class ContentUrlKeyEntity implements Serializable +{ + private static final long serialVersionUID = -6594309522849585169L; + + private Long id; + private Long contentUrlId; + private byte[] encryptedKeyAsBytes; + private Integer keySize; + private String algorithm; + private String masterKeystoreId; + private String masterKeyAlias; + private Long unencryptedFileSize; + + public ContentUrlKeyEntity() + { + } + + public ContentUrlKey getContentUrlKey() throws DecoderException + { + ContentUrlKey contentUrlKey = new ContentUrlKey(); + contentUrlKey.setAlgorithm(algorithm); + contentUrlKey.setKeySize(keySize); + contentUrlKey.setEncryptedKeyBytes(ByteBuffer.wrap(encryptedKeyAsBytes)); + contentUrlKey.setMasterKeyAlias(masterKeyAlias); + contentUrlKey.setMasterKeystoreId(masterKeystoreId); + contentUrlKey.setUnencryptedFileSize(unencryptedFileSize); + return contentUrlKey; + } + + public Long getContentUrlId() + { + return contentUrlId; + } + + public void setContentUrlId(Long contentUrlId) + { + this.contentUrlId = contentUrlId; + } + + public void setEncryptedKeyAsBytes(byte[] encryptedKeyAsBytes) + { + this.encryptedKeyAsBytes = encryptedKeyAsBytes; + } + + public byte[] getEncryptedKeyAsBytes() + { + return encryptedKeyAsBytes; + } + + public void setEncryptedKey(EncryptedKey encryptedKey) + { + byte[] encryptedKeyAsBytes = new byte[encryptedKey.getByteBuffer().remaining()]; + encryptedKey.getByteBuffer().get(encryptedKeyAsBytes); + + this.encryptedKeyAsBytes = encryptedKeyAsBytes; + this.keySize = encryptedKeyAsBytes.length*8; + this.algorithm = encryptedKey.getAlgorithm(); + this.masterKeyAlias = encryptedKey.getMasterKeyAlias(); + this.masterKeystoreId = encryptedKey.getMasterKeystoreId(); + } + + public static ContentUrlKeyEntity setEncryptedKey(ContentUrlKeyEntity existing, EncryptedKey encryptedKey) + { + ContentUrlKeyEntity newContentUrlKeyEntity = new ContentUrlKeyEntity(); + + byte[] encryptedKeyAsBytes = new byte[encryptedKey.getByteBuffer().remaining()]; + encryptedKey.getByteBuffer().get(encryptedKeyAsBytes); + newContentUrlKeyEntity.setEncryptedKeyAsBytes(encryptedKeyAsBytes); + newContentUrlKeyEntity.setKeySize(encryptedKeyAsBytes.length*8); + newContentUrlKeyEntity.setAlgorithm(encryptedKey.getAlgorithm()); + newContentUrlKeyEntity.setMasterKeyAlias(encryptedKey.getMasterKeyAlias()); + newContentUrlKeyEntity.setMasterKeystoreId(encryptedKey.getMasterKeystoreId()); + newContentUrlKeyEntity.setContentUrlId(existing.getContentUrlId()); + newContentUrlKeyEntity.setUnencryptedFileSize(existing.getUnencryptedFileSize()); + newContentUrlKeyEntity.setId(existing.getId()); + + return newContentUrlKeyEntity; + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public EncryptedKey getEncryptedKey() throws DecoderException + { + EncryptedKey encryptedKey = new EncryptedKey(getMasterKeystoreId(), getMasterKeyAlias(), + getAlgorithm(), ByteBuffer.wrap(this.encryptedKeyAsBytes)); + return encryptedKey; + } + + public Long getUnencryptedFileSize() + { + return unencryptedFileSize; + } + + public void setUnencryptedFileSize(Long unencryptedFileSize) + { + this.unencryptedFileSize = unencryptedFileSize; + } + + public void setKeySize(Integer keySize) + { + this.keySize = keySize; + } + + public Integer getKeySize() + { + return keySize; + } + + public String getAlgorithm() + { + return algorithm; + } + + public void setAlgorithm(String algorithm) + { + this.algorithm = algorithm; + } + + public String getMasterKeystoreId() + { + return masterKeystoreId; + } + + public void setMasterKeystoreId(String masterKeystoreId) + { + this.masterKeystoreId = masterKeystoreId; + } + + public String getMasterKeyAlias() + { + return masterKeyAlias; + } + + public void setMasterKeyAlias(String masterKeyAlias) + { + this.masterKeyAlias = masterKeyAlias; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + + ((algorithm == null) ? 0 : algorithm.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + + ((masterKeyAlias == null) ? 0 : masterKeyAlias.hashCode()); + result = prime + * result + + ((masterKeystoreId == null) ? 0 : masterKeystoreId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ContentUrlKeyEntity other = (ContentUrlKeyEntity) obj; + if (algorithm == null) { + if (other.algorithm != null) + return false; + } else if (!algorithm.equals(other.algorithm)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (masterKeyAlias == null) { + if (other.masterKeyAlias != null) + return false; + } else if (!masterKeyAlias.equals(other.masterKeyAlias)) + return false; + if (masterKeystoreId == null) { + if (other.masterKeystoreId != null) + return false; + } else if (!masterKeystoreId.equals(other.masterKeystoreId)) + return false; + return true; + } + + @Override + public String toString() + { + return "ContentUrlKeyEntity [id=" + id + ", encryptedKeyAsBytes=" + + encryptedKeyAsBytes+ ", keySize=" + keySize + ", algorithm=" + + algorithm + ", masterKeystoreId=" + masterKeystoreId + + ", masterKeyAlias=" + masterKeyAlias + + ", unencryptedFileSize=" + unencryptedFileSize + "]"; + } +} diff --git a/source/java/org/alfresco/repo/domain/contentdata/ContentUrlOrphanQuery.java b/source/java/org/alfresco/repo/domain/contentdata/ContentUrlOrphanQuery.java index f345829dae..33ac3d0ba0 100644 --- a/source/java/org/alfresco/repo/domain/contentdata/ContentUrlOrphanQuery.java +++ b/source/java/org/alfresco/repo/domain/contentdata/ContentUrlOrphanQuery.java @@ -1,32 +1,32 @@ -package org.alfresco.repo.domain.contentdata; - -/** - * Entity bean for alf_content_url queries table. - * - * @author Derek Hulley - * @since 3.3.5 - */ -public class ContentUrlOrphanQuery -{ - private Long maxOrphanTimeExclusive; - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(512); - sb.append("ContentUrlOrphanQuery") - .append("[ maxOrphanTimeExclusive=").append(maxOrphanTimeExclusive) - .append("]"); - return sb.toString(); - } - - public Long getMaxOrphanTimeExclusive() - { - return maxOrphanTimeExclusive; - } - - public void setMaxOrphanTimeExclusive(Long maxOrphanTimeExclusive) - { - this.maxOrphanTimeExclusive = maxOrphanTimeExclusive; - } +package org.alfresco.repo.domain.contentdata; + +/** + * Entity bean for alf_content_url queries table. + * + * @author Derek Hulley + * @since 3.3.5 + */ +public class ContentUrlOrphanQuery +{ + private Long maxOrphanTimeExclusive; + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(512); + sb.append("ContentUrlOrphanQuery") + .append("[ maxOrphanTimeExclusive=").append(maxOrphanTimeExclusive) + .append("]"); + return sb.toString(); + } + + public Long getMaxOrphanTimeExclusive() + { + return maxOrphanTimeExclusive; + } + + public void setMaxOrphanTimeExclusive(Long maxOrphanTimeExclusive) + { + this.maxOrphanTimeExclusive = maxOrphanTimeExclusive; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/contentdata/EncryptedKey.java b/source/java/org/alfresco/repo/domain/contentdata/EncryptedKey.java index 4565c102cb..2caa360fd2 100644 --- a/source/java/org/alfresco/repo/domain/contentdata/EncryptedKey.java +++ b/source/java/org/alfresco/repo/domain/contentdata/EncryptedKey.java @@ -1,55 +1,55 @@ -package org.alfresco.repo.domain.contentdata; - -import java.io.Serializable; -import java.nio.ByteBuffer; - -public class EncryptedKey implements Serializable -{ - private static final long serialVersionUID = 1L; - - private String masterKeystoreId; - private String masterKeyAlias; - private final String algorithm; - private final ByteBuffer encryptedKeyBytes; - - public EncryptedKey(String masterKeystoreId, String masterKeyAlias, String algorithm, ByteBuffer encryptedKeyBytes) - { - this.masterKeyAlias = masterKeyAlias; - this.masterKeystoreId = masterKeystoreId; - this.algorithm = algorithm; - this.encryptedKeyBytes = encryptedKeyBytes.asReadOnlyBuffer(); - } - - public String getMasterKeystoreId() - { - return masterKeystoreId; - } - - public String getMasterKeyAlias() - { - return masterKeyAlias; - } - - public ByteBuffer getEncryptedKeyBytes() - { - return encryptedKeyBytes; - } - - public String getAlgorithm() - { - return this.algorithm; - } - - public ByteBuffer getByteBuffer() - { - return this.encryptedKeyBytes.asReadOnlyBuffer(); - } - - public int keySize() - { - byte[] eKey = new byte[getByteBuffer().remaining()]; - getByteBuffer().get(eKey); - return eKey.length * 8; - } - -} +package org.alfresco.repo.domain.contentdata; + +import java.io.Serializable; +import java.nio.ByteBuffer; + +public class EncryptedKey implements Serializable +{ + private static final long serialVersionUID = 1L; + + private String masterKeystoreId; + private String masterKeyAlias; + private final String algorithm; + private final ByteBuffer encryptedKeyBytes; + + public EncryptedKey(String masterKeystoreId, String masterKeyAlias, String algorithm, ByteBuffer encryptedKeyBytes) + { + this.masterKeyAlias = masterKeyAlias; + this.masterKeystoreId = masterKeystoreId; + this.algorithm = algorithm; + this.encryptedKeyBytes = encryptedKeyBytes.asReadOnlyBuffer(); + } + + public String getMasterKeystoreId() + { + return masterKeystoreId; + } + + public String getMasterKeyAlias() + { + return masterKeyAlias; + } + + public ByteBuffer getEncryptedKeyBytes() + { + return encryptedKeyBytes; + } + + public String getAlgorithm() + { + return this.algorithm; + } + + public ByteBuffer getByteBuffer() + { + return this.encryptedKeyBytes.asReadOnlyBuffer(); + } + + public int keySize() + { + byte[] eKey = new byte[getByteBuffer().remaining()]; + getByteBuffer().get(eKey); + return eKey.length * 8; + } + +} diff --git a/source/java/org/alfresco/repo/domain/node/Node.java b/source/java/org/alfresco/repo/domain/node/Node.java index 406c77b80e..c63d6ffa44 100644 --- a/source/java/org/alfresco/repo/domain/node/Node.java +++ b/source/java/org/alfresco/repo/domain/node/Node.java @@ -1,52 +1,52 @@ -package org.alfresco.repo.domain.node; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.util.Pair; - -/** - * Interface for beans carrying general information for alf_node data. - * - * @author andyh - */ -public interface Node extends NodeIdAndAclId -{ - /** - * Helper method to get a key that includes the node and its current version number - */ - public NodeVersionKey getNodeVersionKey(); - - /** - * Helper method to force the instance to be read-only - */ - public void lock(); - - public abstract NodeRef getNodeRef(); - - public NodeRef.Status getNodeStatus(QNameDAO qnameDAO); - - public abstract Pair getNodePair(); - - /** - * Checks the {@link #getTypeQNameId() type} of the node to determine if the node is deleted - * @param qnameDAO DAO to work out type IDs - * @return true if the node is {@link ContentModel#TYPE_DELETED} - */ - public abstract boolean getDeleted(QNameDAO qnameDAO); - - public abstract Long getVersion(); - - public abstract StoreEntity getStore(); - - public abstract String getUuid(); - - public abstract Long getTypeQNameId(); - - public abstract Long getLocaleId(); - - public abstract TransactionEntity getTransaction(); - - public abstract AuditablePropertiesEntity getAuditableProperties(); - +package org.alfresco.repo.domain.node; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.Pair; + +/** + * Interface for beans carrying general information for alf_node data. + * + * @author andyh + */ +public interface Node extends NodeIdAndAclId +{ + /** + * Helper method to get a key that includes the node and its current version number + */ + public NodeVersionKey getNodeVersionKey(); + + /** + * Helper method to force the instance to be read-only + */ + public void lock(); + + public abstract NodeRef getNodeRef(); + + public NodeRef.Status getNodeStatus(QNameDAO qnameDAO); + + public abstract Pair getNodePair(); + + /** + * Checks the {@link #getTypeQNameId() type} of the node to determine if the node is deleted + * @param qnameDAO DAO to work out type IDs + * @return true if the node is {@link ContentModel#TYPE_DELETED} + */ + public abstract boolean getDeleted(QNameDAO qnameDAO); + + public abstract Long getVersion(); + + public abstract StoreEntity getStore(); + + public abstract String getUuid(); + + public abstract Long getTypeQNameId(); + + public abstract Long getLocaleId(); + + public abstract TransactionEntity getTransaction(); + + public abstract AuditablePropertiesEntity getAuditableProperties(); + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/node/NodeIdAndAclId.java b/source/java/org/alfresco/repo/domain/node/NodeIdAndAclId.java index ebe93e0f08..995623f727 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeIdAndAclId.java +++ b/source/java/org/alfresco/repo/domain/node/NodeIdAndAclId.java @@ -1,13 +1,13 @@ -package org.alfresco.repo.domain.node; - -/** - * Simple interface for beans carrying basic Node ID and related ACL ID data - * - * @author andyh - */ -public interface NodeIdAndAclId -{ - public abstract Long getId(); - - public abstract Long getAclId(); -} +package org.alfresco.repo.domain.node; + +/** + * Simple interface for beans carrying basic Node ID and related ACL ID data + * + * @author andyh + */ +public interface NodeIdAndAclId +{ + public abstract Long getId(); + + public abstract Long getAclId(); +} diff --git a/source/java/org/alfresco/repo/domain/node/PrimaryChildrenAclUpdateEntity.java b/source/java/org/alfresco/repo/domain/node/PrimaryChildrenAclUpdateEntity.java index 1611a7315d..f4926dd812 100644 --- a/source/java/org/alfresco/repo/domain/node/PrimaryChildrenAclUpdateEntity.java +++ b/source/java/org/alfresco/repo/domain/node/PrimaryChildrenAclUpdateEntity.java @@ -1,66 +1,66 @@ -package org.alfresco.repo.domain.node; - -/** - * Carry bulk acl update info. - * - * @author andyh - * @since 3.4 - * - */ -public class PrimaryChildrenAclUpdateEntity -{ - Long txnId; - Long primaryParentNodeId; - Long optionalOldSharedAclIdInAdditionToNull; - Long newSharedAclId; - - public PrimaryChildrenAclUpdateEntity() - { - } - - public Long getTxnId() - { - return txnId; - } - - public void setTxnId(Long txnId) - { - this.txnId = txnId; - } - - public Long getPrimaryParentNodeId() - { - return primaryParentNodeId; - } - - public void setPrimaryParentNodeId(Long primaryParentNodeId) - { - this.primaryParentNodeId = primaryParentNodeId; - } - - public Long getOptionalOldSharedAclIdInAdditionToNull() - { - return optionalOldSharedAclIdInAdditionToNull; - } - - public void setOptionalOldSharedAclIdInAdditionToNull(Long optionalOldSharedAclIdInAdditionToNull) - { - this.optionalOldSharedAclIdInAdditionToNull = optionalOldSharedAclIdInAdditionToNull; - } - - public Long getNewSharedAclId() - { - return newSharedAclId; - } - - public void setNewSharedAclId(Long newSharedAclId) - { - this.newSharedAclId = newSharedAclId; - } - - public boolean getIsPrimary() - { - return true; - } - -} +package org.alfresco.repo.domain.node; + +/** + * Carry bulk acl update info. + * + * @author andyh + * @since 3.4 + * + */ +public class PrimaryChildrenAclUpdateEntity +{ + Long txnId; + Long primaryParentNodeId; + Long optionalOldSharedAclIdInAdditionToNull; + Long newSharedAclId; + + public PrimaryChildrenAclUpdateEntity() + { + } + + public Long getTxnId() + { + return txnId; + } + + public void setTxnId(Long txnId) + { + this.txnId = txnId; + } + + public Long getPrimaryParentNodeId() + { + return primaryParentNodeId; + } + + public void setPrimaryParentNodeId(Long primaryParentNodeId) + { + this.primaryParentNodeId = primaryParentNodeId; + } + + public Long getOptionalOldSharedAclIdInAdditionToNull() + { + return optionalOldSharedAclIdInAdditionToNull; + } + + public void setOptionalOldSharedAclIdInAdditionToNull(Long optionalOldSharedAclIdInAdditionToNull) + { + this.optionalOldSharedAclIdInAdditionToNull = optionalOldSharedAclIdInAdditionToNull; + } + + public Long getNewSharedAclId() + { + return newSharedAclId; + } + + public void setNewSharedAclId(Long newSharedAclId) + { + this.newSharedAclId = newSharedAclId; + } + + public boolean getIsPrimary() + { + return true; + } + +} diff --git a/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java b/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java index 135287071d..902ae79fc7 100644 --- a/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java @@ -1,19 +1,19 @@ -package org.alfresco.repo.domain.patch; - -import org.alfresco.ibatis.BatchingDAO; - -/** - * Abstract implementation for Patch DAO. - *

- * This provides additional queries used by patches. - * - * @author Derek Hulley - * @author janv - * @since 3.2 - */ -public abstract class AbstractPatchDAOImpl implements PatchDAO, BatchingDAO -{ - protected AbstractPatchDAOImpl() - { - } -} +package org.alfresco.repo.domain.patch; + +import org.alfresco.ibatis.BatchingDAO; + +/** + * Abstract implementation for Patch DAO. + *

+ * This provides additional queries used by patches. + * + * @author Derek Hulley + * @author janv + * @since 3.2 + */ +public abstract class AbstractPatchDAOImpl implements PatchDAO, BatchingDAO +{ + protected AbstractPatchDAOImpl() + { + } +} diff --git a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java index 6da69e7487..859e4e3c8a 100644 --- a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java +++ b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java @@ -1,128 +1,128 @@ -package org.alfresco.repo.domain.patch; - -import java.util.List; -import java.util.Set; - - -import org.alfresco.repo.domain.node.NodeDAO; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.Pair; - -/** - * Additional DAO services for patches - * - * @author janv - * @author Derek Hulley - * @since 3.2 - */ -public interface PatchDAO -{ - // DM-related - - /** - * @deprecated in 4.1: use {@link NodeDAO#getMaxNodeId()} - */ - @Deprecated - public long getMaxAdmNodeID(); - - /** - * Update all alf_content_data mimetype references. - * - * @param oldMimetypeId the ID to search for - * @param newMimetypeId the ID to change to - * @return the number of rows affected - */ - public int updateContentMimetypeIds(Long oldMimetypeId, Long newMimetypeId); - - /** - * Update all alf_node_properties of 'sizeCurrent' name to have correct persisted type of Long. - * - * @return the number of rows affected - */ - public int updatePersonSizeCurrentType(); - - /** - * Query for a list of nodes that have a given type and share the same name pattern (SQL LIKE syntax) - * - * @param typeQName the node type - * @param namePattern the SQL LIKE pattern - * @return Returns the node ID and node name - */ - public List> getNodesOfTypeWithNamePattern(QName typeQName, String namePattern); - - /** - * @param qnames the qnames to search for - * @return Returns a count of the number of nodes that have either of the aspects - */ - public long getCountNodesWithAspects(Set qnames); - - /** - * Find all the nodes ids with the given type - * @param typeQNameId - the id of the type qname - * @param minNodeId - min node id in the result set - inclusive - * @param maxNodeId - max node id in the result set - exclusive - * @return IDs of the nodes - */ - public List getNodesByTypeQNameId(Long typeQNameId, Long minNodeId, Long maxNodeId); - - /** - * Find all the nodes ids with the given type uri - * @param uriId - the id of the type qname uri - * @param minNodeId - min node id in the result set - inclusive - * @param maxNodeId - max node id in the result set - exclusive - * @return IDs of the nodes - */ - public List getNodesByTypeUriId(Long uriId, Long minNodeId, Long maxNodeId); - - /** - * Find all the nodes ids with the given aspect - * @param aspectQNameId - the id of the aspect qname - * @param minNodeId - min node id in the result set - inclusive - * @param maxNodeId - max node id in the result set - exclusive - * @return IDs of the nodes - */ - public List getNodesByAspectQNameId(Long aspectQNameId, Long minNodeId, Long maxNodeId); - - /** - * Find all the nodes ids with the given content property set with the given mimetype - * @param mimetypeId - the id of the content data mimetype - * @param minNodeId - min node id in the result set - inclusive - * @param maxNodeId - max node id in the result set - exclusive - * @return IDs of the nodes - */ - public List getNodesByContentPropertyMimetypeId(Long mimetypeId, Long minNodeId, Long maxNodeId); - - /** - * Find all the nodes ids with the given aspect and type - * @param typeQNameId - the id of the type qname - * @param aspectQNameId - the id of the aspect qname - * @param minNodeId - min node id in the result set - inclusive - * @param maxNodeId - max node id in the result set - exclusive - * @return List - */ - public List getNodesByTypeQNameAndAspectQNameId(long typeQNameId, long aspectQNameId, long minNodeId, long maxNodeId); - - /** - * Gets the total number of nodes which match the given Type QName. - * - * @param typeQName the qname to search for - * @return count of nodes that match the typeQName - */ - public long getCountNodesWithTypId(QName typeQName); - - /** - * Finds folders of the shared surf-config (for all tenants): - *

    - *
  • company_home/sites/surf-config/components
  • - *
  • company_home/sites/surf-config/pages
  • - *
  • company_home/sites/surf-config/pages/user
  • - *
  • company_home/sites/surf-config/pages/user{userId}
  • - *
- * @param minNodeId - min node id in the result set - inclusive - * @param maxNodeId - max node id in the result set - exclusive - * @return list of children nodeRefs - */ - public List getChildrenOfTheSharedSurfConfigFolder(Long minNodeId, Long maxNodeId); - -} +package org.alfresco.repo.domain.patch; + +import java.util.List; +import java.util.Set; + + +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; + +/** + * Additional DAO services for patches + * + * @author janv + * @author Derek Hulley + * @since 3.2 + */ +public interface PatchDAO +{ + // DM-related + + /** + * @deprecated in 4.1: use {@link NodeDAO#getMaxNodeId()} + */ + @Deprecated + public long getMaxAdmNodeID(); + + /** + * Update all alf_content_data mimetype references. + * + * @param oldMimetypeId the ID to search for + * @param newMimetypeId the ID to change to + * @return the number of rows affected + */ + public int updateContentMimetypeIds(Long oldMimetypeId, Long newMimetypeId); + + /** + * Update all alf_node_properties of 'sizeCurrent' name to have correct persisted type of Long. + * + * @return the number of rows affected + */ + public int updatePersonSizeCurrentType(); + + /** + * Query for a list of nodes that have a given type and share the same name pattern (SQL LIKE syntax) + * + * @param typeQName the node type + * @param namePattern the SQL LIKE pattern + * @return Returns the node ID and node name + */ + public List> getNodesOfTypeWithNamePattern(QName typeQName, String namePattern); + + /** + * @param qnames the qnames to search for + * @return Returns a count of the number of nodes that have either of the aspects + */ + public long getCountNodesWithAspects(Set qnames); + + /** + * Find all the nodes ids with the given type + * @param typeQNameId - the id of the type qname + * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return IDs of the nodes + */ + public List getNodesByTypeQNameId(Long typeQNameId, Long minNodeId, Long maxNodeId); + + /** + * Find all the nodes ids with the given type uri + * @param uriId - the id of the type qname uri + * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return IDs of the nodes + */ + public List getNodesByTypeUriId(Long uriId, Long minNodeId, Long maxNodeId); + + /** + * Find all the nodes ids with the given aspect + * @param aspectQNameId - the id of the aspect qname + * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return IDs of the nodes + */ + public List getNodesByAspectQNameId(Long aspectQNameId, Long minNodeId, Long maxNodeId); + + /** + * Find all the nodes ids with the given content property set with the given mimetype + * @param mimetypeId - the id of the content data mimetype + * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return IDs of the nodes + */ + public List getNodesByContentPropertyMimetypeId(Long mimetypeId, Long minNodeId, Long maxNodeId); + + /** + * Find all the nodes ids with the given aspect and type + * @param typeQNameId - the id of the type qname + * @param aspectQNameId - the id of the aspect qname + * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return List + */ + public List getNodesByTypeQNameAndAspectQNameId(long typeQNameId, long aspectQNameId, long minNodeId, long maxNodeId); + + /** + * Gets the total number of nodes which match the given Type QName. + * + * @param typeQName the qname to search for + * @return count of nodes that match the typeQName + */ + public long getCountNodesWithTypId(QName typeQName); + + /** + * Finds folders of the shared surf-config (for all tenants): + *
    + *
  • company_home/sites/surf-config/components
  • + *
  • company_home/sites/surf-config/pages
  • + *
  • company_home/sites/surf-config/pages/user
  • + *
  • company_home/sites/surf-config/pages/user{userId}
  • + *
+ * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return list of children nodeRefs + */ + public List getChildrenOfTheSharedSurfConfigFolder(Long minNodeId, Long maxNodeId); + +} diff --git a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java index 6e42969f14..ea863c949a 100644 --- a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java @@ -1,341 +1,341 @@ -package org.alfresco.repo.domain.patch.ibatis; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.ibatis.IdsEntity; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.contentdata.ContentDataDAO; -import org.alfresco.repo.domain.locale.LocaleDAO; -import org.alfresco.repo.domain.node.ChildAssocEntity; -import org.alfresco.repo.domain.patch.AbstractPatchDAOImpl; -import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.repo.site.SiteModel; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.Pair; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.ibatis.session.ResultContext; -import org.apache.ibatis.session.ResultHandler; -import org.mybatis.spring.SqlSessionTemplate; - -/** - * iBatis-specific implementation of the PatchDAO. - * - * @author janv - * @since 3.2 - */ -public class PatchDAOImpl extends AbstractPatchDAOImpl -{ - @SuppressWarnings("unused") - private static Log logger = LogFactory.getLog(PatchDAOImpl.class); - - private static final String SELECT_ADM_MAX_NODE_ID = "alfresco.patch.select_admMaxNodeId"; - private static final String SELECT_NODES_BY_TYPE_AND_NAME_PATTERN = "alfresco.patch.select_nodesByTypeAndNamePattern"; - - private static final String UPDATE_CONTENT_MIMETYPE_ID = "alfresco.patch.update_contentMimetypeId"; - private static final String UPDATE_PERSON_SIZECURRENT_TYPE = "alfresco.patch.update_fixSizeCurrentType"; - - private static final String SELECT_COUNT_NODES_WITH_ASPECTS = "alfresco.patch.select_CountNodesWithAspectIds"; - - private static final String SELECT_NODES_BY_TYPE_QNAME = "alfresco.patch.select_NodesByTypeQName"; - private static final String SELECT_NODES_BY_TYPE_URI = "alfresco.patch.select_NodesByTypeUriId"; - private static final String SELECT_NODES_BY_ASPECT_QNAME = "alfresco.patch.select_NodesByAspectQName"; - private static final String SELECT_NODES_BY_TYPE_AND_ASPECT_QNAME = "alfresco.patch.select_NodesByTypeAndAspectQNameQName"; - private static final String SELECT_NODES_BY_CONTENT_MIMETYPE = "alfresco.patch.select_NodesByContentMimetype"; - - private static final String SELECT_COUNT_NODES_WITH_TYPE_ID = "alfresco.patch.select_CountNodesWithTypeId"; - private static final String SELECT_CHILDREN_OF_THE_SHARED_SURFCONFIG_FOLDER = "alfresco.patch.select_ChildrenOfTheSharedSurfConfigFolder"; - - private QNameDAO qnameDAO; - @SuppressWarnings("unused") - private LocaleDAO localeDAO; - @SuppressWarnings("unused") - private ContentDataDAO contentDataDAO; - - protected SqlSessionTemplate template; - - public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) - { - this.template = sqlSessionTemplate; - } - - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - public void setLocaleDAO(LocaleDAO localeDAO) - { - this.localeDAO = localeDAO; - } - public void setContentDataDAO(ContentDataDAO contentDataDAO) - { - this.contentDataDAO = contentDataDAO; - } - - @Override - public void startBatch() - { - // TODO - /* - try - { - template.getSqlMapClient().startBatch(); - } - catch (SQLException e) - { - throw new RuntimeException("Failed to start batch", e); - } - */ - } - - @Override - public void executeBatch() - { - // TODO - /* - try - { - template.getSqlMapClient().executeBatch(); - } - catch (SQLException e) - { - throw new RuntimeException("Failed to start batch", e); - } - */ - } - - @Override - public long getMaxAdmNodeID() - { - Long count = template.selectOne(SELECT_ADM_MAX_NODE_ID); - return count == null ? 0L : count; - } - - @Override - public int updateContentMimetypeIds(Long oldMimetypeId, Long newMimetypeId) - { - Map params = new HashMap(11); - params.put("newMimetypeId", newMimetypeId); - params.put("oldMimetypeId", oldMimetypeId); - return template.update(UPDATE_CONTENT_MIMETYPE_ID, params); - } - - @Override - public int updatePersonSizeCurrentType() - { - Map params = new HashMap(2); - Long sizeCurrentPropQNameId = qnameDAO.getOrCreateQName(ContentModel.PROP_SIZE_CURRENT).getFirst(); - params.put("sizeCurrentQNameId", sizeCurrentPropQNameId); - return template.update(UPDATE_PERSON_SIZECURRENT_TYPE, params); - } - - @Override - public List> getNodesOfTypeWithNamePattern(QName typeQName, String namePattern) - { - Pair typeQNamePair = qnameDAO.getQName(typeQName); - if (typeQNamePair == null) - { - // No point querying - return Collections.emptyList(); - } - Long typeQNameId = typeQNamePair.getFirst(); - - Pair propQNamePair = qnameDAO.getQName(ContentModel.PROP_NAME); - if (propQNamePair == null) - { - return Collections.emptyList(); - } - Long propQNameId = propQNamePair.getFirst(); - - Map params = new HashMap(); - params.put("typeQNameId", typeQNameId); - params.put("propQNameId", propQNameId); - params.put("namePattern", namePattern); - - final List> results = new ArrayList>(500); - ResultHandler resultHandler = new ResultHandler() - { - @SuppressWarnings("unchecked") - public void handleResult(ResultContext context) - { - Map row = (Map) context.getResultObject(); - String protocol = (String) row.get("protocol"); - String identifier = (String) row.get("identifier"); - String uuid = (String) row.get("uuid"); - NodeRef nodeRef = new NodeRef(new StoreRef(protocol, identifier), uuid); - String name = (String) row.get("name"); - Pair pair = new Pair(nodeRef, name); - results.add(pair); - } - }; - template.select(SELECT_NODES_BY_TYPE_AND_NAME_PATTERN, params, resultHandler); - return results; - } - - @Override - public long getCountNodesWithAspects(Set qnames) - { - // Resolve QNames - Set qnameIds = qnameDAO.convertQNamesToIds(qnames, false); - if (qnameIds.size() == 0) - { - return 0L; - } - IdsEntity params = new IdsEntity(); - params.setIds(new ArrayList(qnameIds)); - Long count = template.selectOne(SELECT_COUNT_NODES_WITH_ASPECTS, params); - if (count == null) - { - return 0L; - } - else - { - return count; - } - } - - @Override - public List getNodesByTypeQNameId(Long typeQNameId, Long minNodeId, Long maxNodeId) - { - Map params = new HashMap(); - params.put("qnameId", typeQNameId); - params.put("minNodeId", minNodeId); - params.put("maxNodeId", maxNodeId); - return template.selectList(SELECT_NODES_BY_TYPE_QNAME, params); - } - - @Override - public List getNodesByTypeUriId(Long nsId, Long minNodeId, Long maxNodeId) - { - Map params = new HashMap(); - params.put("nsId", nsId); - params.put("minNodeId", minNodeId); - params.put("maxNodeId", maxNodeId); - return template.selectList(SELECT_NODES_BY_TYPE_URI, params); - } - - @Override - public List getNodesByAspectQNameId(Long aspectQNameId, Long minNodeId, Long maxNodeId) - { - Map params = new HashMap(); - params.put("qnameId", aspectQNameId); - params.put("minNodeId", minNodeId); - params.put("maxNodeId", maxNodeId); - return template.selectList(SELECT_NODES_BY_ASPECT_QNAME, params); - } - - @Override - public List getNodesByContentPropertyMimetypeId(Long mimetypeId, Long minNodeId, Long maxNodeId) - { - Map params = new HashMap(); - params.put("mimetypeId", mimetypeId); - params.put("minNodeId", minNodeId); - params.put("maxNodeId", maxNodeId); - return template.selectList(SELECT_NODES_BY_CONTENT_MIMETYPE, params); - } - - @SuppressWarnings("unchecked") - @Override - public List getNodesByTypeQNameAndAspectQNameId(long typeQNameId, long aspectQNameId, long minNodeId, long maxNodeId) - { - Map params = new HashMap(); - params.put("qnameId1", typeQNameId); - params.put("qnameId2", aspectQNameId); - params.put("minNodeId", minNodeId); - params.put("maxNodeId", maxNodeId); - return template.selectList(SELECT_NODES_BY_TYPE_AND_ASPECT_QNAME, params); - } - - @Override - public long getCountNodesWithTypId(QName typeQName) - { - // Resolve the QName - Pair qnameId = qnameDAO.getQName(typeQName); - if (qnameId == null) - { - return 0L; - } - IdsEntity params = new IdsEntity(); - params.setIdOne(qnameId.getFirst()); - Long count = (Long) template.selectOne(SELECT_COUNT_NODES_WITH_TYPE_ID, params); - if (count == null) - { - return 0L; - } - else - { - return count; - } - } - - @Override - public List getChildrenOfTheSharedSurfConfigFolder(Long minNodeId, Long maxNodeId) - { - Pair containsAssocQNamePair = qnameDAO.getQName(ContentModel.ASSOC_CONTAINS); - if (containsAssocQNamePair == null) - { - return Collections.emptyList(); - } - - Map params = new HashMap(7); - - // Get qname CRC - Long qnameCrcSites = ChildAssocEntity.getQNameCrc(QName.createQName(SiteModel.SITE_MODEL_URL, "sites")); - Long qnameCrcSurfConfig = ChildAssocEntity.getQNameCrc(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "surf-config")); - Long qnameCrcPages = ChildAssocEntity.getQNameCrc(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "pages")); - Long qnameCrcUser = ChildAssocEntity.getQNameCrc(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "user")); - - params.put("qnameCrcSites", qnameCrcSites); - params.put("qnameCrcSurfConfig", qnameCrcSurfConfig); - params.put("qnameCrcPages", qnameCrcPages); - params.put("qnameCrcUser", qnameCrcUser); - params.put("qnameTypeIdContains", containsAssocQNamePair.getFirst()); - params.put("minNodeId", minNodeId); - params.put("maxNodeId", maxNodeId); - - final List results = new ArrayList(1000); - ResultHandler resultHandler = new ResultHandler() - { - @SuppressWarnings("unchecked") - public void handleResult(ResultContext context) - { - Map row = (Map) context.getResultObject(); - String protocol = (String) row.get("protocol"); - String identifier = (String) row.get("identifier"); - String uuid = (String) row.get("uuid"); - NodeRef nodeRef = new NodeRef(new StoreRef(protocol, identifier), uuid); - results.add(nodeRef); - } - }; - template.select(SELECT_CHILDREN_OF_THE_SHARED_SURFCONFIG_FOLDER, params, resultHandler); - return results; - } - - /** - * PostgreSQL-specific DAO - * - * @author Derek Hulley - * @since 4.0 - */ - public static class PostgreSQL extends PatchDAOImpl - { - } - - /** - * Oracle-specific DAO - * - * @author Derek Hulley - * @since 4.0 - */ - public static class Oracle extends PatchDAOImpl - { - } -} +package org.alfresco.repo.domain.patch.ibatis; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.ibatis.IdsEntity; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.contentdata.ContentDataDAO; +import org.alfresco.repo.domain.locale.LocaleDAO; +import org.alfresco.repo.domain.node.ChildAssocEntity; +import org.alfresco.repo.domain.patch.AbstractPatchDAOImpl; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ibatis.session.ResultContext; +import org.apache.ibatis.session.ResultHandler; +import org.mybatis.spring.SqlSessionTemplate; + +/** + * iBatis-specific implementation of the PatchDAO. + * + * @author janv + * @since 3.2 + */ +public class PatchDAOImpl extends AbstractPatchDAOImpl +{ + @SuppressWarnings("unused") + private static Log logger = LogFactory.getLog(PatchDAOImpl.class); + + private static final String SELECT_ADM_MAX_NODE_ID = "alfresco.patch.select_admMaxNodeId"; + private static final String SELECT_NODES_BY_TYPE_AND_NAME_PATTERN = "alfresco.patch.select_nodesByTypeAndNamePattern"; + + private static final String UPDATE_CONTENT_MIMETYPE_ID = "alfresco.patch.update_contentMimetypeId"; + private static final String UPDATE_PERSON_SIZECURRENT_TYPE = "alfresco.patch.update_fixSizeCurrentType"; + + private static final String SELECT_COUNT_NODES_WITH_ASPECTS = "alfresco.patch.select_CountNodesWithAspectIds"; + + private static final String SELECT_NODES_BY_TYPE_QNAME = "alfresco.patch.select_NodesByTypeQName"; + private static final String SELECT_NODES_BY_TYPE_URI = "alfresco.patch.select_NodesByTypeUriId"; + private static final String SELECT_NODES_BY_ASPECT_QNAME = "alfresco.patch.select_NodesByAspectQName"; + private static final String SELECT_NODES_BY_TYPE_AND_ASPECT_QNAME = "alfresco.patch.select_NodesByTypeAndAspectQNameQName"; + private static final String SELECT_NODES_BY_CONTENT_MIMETYPE = "alfresco.patch.select_NodesByContentMimetype"; + + private static final String SELECT_COUNT_NODES_WITH_TYPE_ID = "alfresco.patch.select_CountNodesWithTypeId"; + private static final String SELECT_CHILDREN_OF_THE_SHARED_SURFCONFIG_FOLDER = "alfresco.patch.select_ChildrenOfTheSharedSurfConfigFolder"; + + private QNameDAO qnameDAO; + @SuppressWarnings("unused") + private LocaleDAO localeDAO; + @SuppressWarnings("unused") + private ContentDataDAO contentDataDAO; + + protected SqlSessionTemplate template; + + public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) + { + this.template = sqlSessionTemplate; + } + + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + public void setLocaleDAO(LocaleDAO localeDAO) + { + this.localeDAO = localeDAO; + } + public void setContentDataDAO(ContentDataDAO contentDataDAO) + { + this.contentDataDAO = contentDataDAO; + } + + @Override + public void startBatch() + { + // TODO + /* + try + { + template.getSqlMapClient().startBatch(); + } + catch (SQLException e) + { + throw new RuntimeException("Failed to start batch", e); + } + */ + } + + @Override + public void executeBatch() + { + // TODO + /* + try + { + template.getSqlMapClient().executeBatch(); + } + catch (SQLException e) + { + throw new RuntimeException("Failed to start batch", e); + } + */ + } + + @Override + public long getMaxAdmNodeID() + { + Long count = template.selectOne(SELECT_ADM_MAX_NODE_ID); + return count == null ? 0L : count; + } + + @Override + public int updateContentMimetypeIds(Long oldMimetypeId, Long newMimetypeId) + { + Map params = new HashMap(11); + params.put("newMimetypeId", newMimetypeId); + params.put("oldMimetypeId", oldMimetypeId); + return template.update(UPDATE_CONTENT_MIMETYPE_ID, params); + } + + @Override + public int updatePersonSizeCurrentType() + { + Map params = new HashMap(2); + Long sizeCurrentPropQNameId = qnameDAO.getOrCreateQName(ContentModel.PROP_SIZE_CURRENT).getFirst(); + params.put("sizeCurrentQNameId", sizeCurrentPropQNameId); + return template.update(UPDATE_PERSON_SIZECURRENT_TYPE, params); + } + + @Override + public List> getNodesOfTypeWithNamePattern(QName typeQName, String namePattern) + { + Pair typeQNamePair = qnameDAO.getQName(typeQName); + if (typeQNamePair == null) + { + // No point querying + return Collections.emptyList(); + } + Long typeQNameId = typeQNamePair.getFirst(); + + Pair propQNamePair = qnameDAO.getQName(ContentModel.PROP_NAME); + if (propQNamePair == null) + { + return Collections.emptyList(); + } + Long propQNameId = propQNamePair.getFirst(); + + Map params = new HashMap(); + params.put("typeQNameId", typeQNameId); + params.put("propQNameId", propQNameId); + params.put("namePattern", namePattern); + + final List> results = new ArrayList>(500); + ResultHandler resultHandler = new ResultHandler() + { + @SuppressWarnings("unchecked") + public void handleResult(ResultContext context) + { + Map row = (Map) context.getResultObject(); + String protocol = (String) row.get("protocol"); + String identifier = (String) row.get("identifier"); + String uuid = (String) row.get("uuid"); + NodeRef nodeRef = new NodeRef(new StoreRef(protocol, identifier), uuid); + String name = (String) row.get("name"); + Pair pair = new Pair(nodeRef, name); + results.add(pair); + } + }; + template.select(SELECT_NODES_BY_TYPE_AND_NAME_PATTERN, params, resultHandler); + return results; + } + + @Override + public long getCountNodesWithAspects(Set qnames) + { + // Resolve QNames + Set qnameIds = qnameDAO.convertQNamesToIds(qnames, false); + if (qnameIds.size() == 0) + { + return 0L; + } + IdsEntity params = new IdsEntity(); + params.setIds(new ArrayList(qnameIds)); + Long count = template.selectOne(SELECT_COUNT_NODES_WITH_ASPECTS, params); + if (count == null) + { + return 0L; + } + else + { + return count; + } + } + + @Override + public List getNodesByTypeQNameId(Long typeQNameId, Long minNodeId, Long maxNodeId) + { + Map params = new HashMap(); + params.put("qnameId", typeQNameId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return template.selectList(SELECT_NODES_BY_TYPE_QNAME, params); + } + + @Override + public List getNodesByTypeUriId(Long nsId, Long minNodeId, Long maxNodeId) + { + Map params = new HashMap(); + params.put("nsId", nsId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return template.selectList(SELECT_NODES_BY_TYPE_URI, params); + } + + @Override + public List getNodesByAspectQNameId(Long aspectQNameId, Long minNodeId, Long maxNodeId) + { + Map params = new HashMap(); + params.put("qnameId", aspectQNameId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return template.selectList(SELECT_NODES_BY_ASPECT_QNAME, params); + } + + @Override + public List getNodesByContentPropertyMimetypeId(Long mimetypeId, Long minNodeId, Long maxNodeId) + { + Map params = new HashMap(); + params.put("mimetypeId", mimetypeId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return template.selectList(SELECT_NODES_BY_CONTENT_MIMETYPE, params); + } + + @SuppressWarnings("unchecked") + @Override + public List getNodesByTypeQNameAndAspectQNameId(long typeQNameId, long aspectQNameId, long minNodeId, long maxNodeId) + { + Map params = new HashMap(); + params.put("qnameId1", typeQNameId); + params.put("qnameId2", aspectQNameId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return template.selectList(SELECT_NODES_BY_TYPE_AND_ASPECT_QNAME, params); + } + + @Override + public long getCountNodesWithTypId(QName typeQName) + { + // Resolve the QName + Pair qnameId = qnameDAO.getQName(typeQName); + if (qnameId == null) + { + return 0L; + } + IdsEntity params = new IdsEntity(); + params.setIdOne(qnameId.getFirst()); + Long count = (Long) template.selectOne(SELECT_COUNT_NODES_WITH_TYPE_ID, params); + if (count == null) + { + return 0L; + } + else + { + return count; + } + } + + @Override + public List getChildrenOfTheSharedSurfConfigFolder(Long minNodeId, Long maxNodeId) + { + Pair containsAssocQNamePair = qnameDAO.getQName(ContentModel.ASSOC_CONTAINS); + if (containsAssocQNamePair == null) + { + return Collections.emptyList(); + } + + Map params = new HashMap(7); + + // Get qname CRC + Long qnameCrcSites = ChildAssocEntity.getQNameCrc(QName.createQName(SiteModel.SITE_MODEL_URL, "sites")); + Long qnameCrcSurfConfig = ChildAssocEntity.getQNameCrc(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "surf-config")); + Long qnameCrcPages = ChildAssocEntity.getQNameCrc(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "pages")); + Long qnameCrcUser = ChildAssocEntity.getQNameCrc(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "user")); + + params.put("qnameCrcSites", qnameCrcSites); + params.put("qnameCrcSurfConfig", qnameCrcSurfConfig); + params.put("qnameCrcPages", qnameCrcPages); + params.put("qnameCrcUser", qnameCrcUser); + params.put("qnameTypeIdContains", containsAssocQNamePair.getFirst()); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + + final List results = new ArrayList(1000); + ResultHandler resultHandler = new ResultHandler() + { + @SuppressWarnings("unchecked") + public void handleResult(ResultContext context) + { + Map row = (Map) context.getResultObject(); + String protocol = (String) row.get("protocol"); + String identifier = (String) row.get("identifier"); + String uuid = (String) row.get("uuid"); + NodeRef nodeRef = new NodeRef(new StoreRef(protocol, identifier), uuid); + results.add(nodeRef); + } + }; + template.select(SELECT_CHILDREN_OF_THE_SHARED_SURFCONFIG_FOLDER, params, resultHandler); + return results; + } + + /** + * PostgreSQL-specific DAO + * + * @author Derek Hulley + * @since 4.0 + */ + public static class PostgreSQL extends PatchDAOImpl + { + } + + /** + * Oracle-specific DAO + * + * @author Derek Hulley + * @since 4.0 + */ + public static class Oracle extends PatchDAOImpl + { + } +} diff --git a/source/java/org/alfresco/repo/domain/propval/PropertyUniqueContextEntity.java b/source/java/org/alfresco/repo/domain/propval/PropertyUniqueContextEntity.java index 1dfccd1cdf..31175fdb9e 100644 --- a/source/java/org/alfresco/repo/domain/propval/PropertyUniqueContextEntity.java +++ b/source/java/org/alfresco/repo/domain/propval/PropertyUniqueContextEntity.java @@ -1,111 +1,111 @@ -package org.alfresco.repo.domain.propval; - -import java.io.Serializable; - -/** - * Entity bean for alf_prop_unique_ctx table. - * - * @author Derek Hulley - * @since 3.2 - */ -public class PropertyUniqueContextEntity implements Serializable -{ - private static final long serialVersionUID = 1L; - private Long id; - private short version; - private Long value1PropId; - private Long value2PropId; - private Long value3PropId; - private Long propertyId; - - public PropertyUniqueContextEntity() - { - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(512); - sb.append("PropertyRootEntity") - .append("[ ID=").append(id) - .append(", version=").append(version) - .append(", value1PropId=").append(value1PropId) - .append(", value2PropId=").append(value2PropId) - .append(", value3PropId=").append(value3PropId) - .append(", propertyId=").append(propertyId) - .append("]"); - return sb.toString(); - } - - public void incrementVersion() - { - if (version >= Short.MAX_VALUE) - { - this.version = 0; - } - else - { - this.version++; - } - } - - public Long getId() - { - return id; - } - - public void setId(Long id) - { - this.id = id; - } - - public short getVersion() - { - return version; - } - - public void setVersion(short version) - { - this.version = version; - } - - public Long getValue1PropId() - { - return value1PropId; - } - - public void setValue1PropId(Long value1PropId) - { - this.value1PropId = value1PropId; - } - - public Long getValue2PropId() - { - return value2PropId; - } - - public void setValue2PropId(Long value2PropId) - { - this.value2PropId = value2PropId; - } - - public Long getValue3PropId() - { - return value3PropId; - } - - public void setValue3PropId(Long value3PropId) - { - this.value3PropId = value3PropId; - } - - public Long getPropertyId() - { - return propertyId; - } - - public void setPropertyId(Long propId) - { - this.propertyId = propId; - } -} +package org.alfresco.repo.domain.propval; + +import java.io.Serializable; + +/** + * Entity bean for alf_prop_unique_ctx table. + * + * @author Derek Hulley + * @since 3.2 + */ +public class PropertyUniqueContextEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + private Long id; + private short version; + private Long value1PropId; + private Long value2PropId; + private Long value3PropId; + private Long propertyId; + + public PropertyUniqueContextEntity() + { + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(512); + sb.append("PropertyRootEntity") + .append("[ ID=").append(id) + .append(", version=").append(version) + .append(", value1PropId=").append(value1PropId) + .append(", value2PropId=").append(value2PropId) + .append(", value3PropId=").append(value3PropId) + .append(", propertyId=").append(propertyId) + .append("]"); + return sb.toString(); + } + + public void incrementVersion() + { + if (version >= Short.MAX_VALUE) + { + this.version = 0; + } + else + { + this.version++; + } + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public short getVersion() + { + return version; + } + + public void setVersion(short version) + { + this.version = version; + } + + public Long getValue1PropId() + { + return value1PropId; + } + + public void setValue1PropId(Long value1PropId) + { + this.value1PropId = value1PropId; + } + + public Long getValue2PropId() + { + return value2PropId; + } + + public void setValue2PropId(Long value2PropId) + { + this.value2PropId = value2PropId; + } + + public Long getValue3PropId() + { + return value3PropId; + } + + public void setValue3PropId(Long value3PropId) + { + this.value3PropId = value3PropId; + } + + public Long getPropertyId() + { + return propertyId; + } + + public void setPropertyId(Long propId) + { + this.propertyId = propId; + } +} diff --git a/source/java/org/alfresco/repo/domain/schema/script/ScriptBundleExecutor.java b/source/java/org/alfresco/repo/domain/schema/script/ScriptBundleExecutor.java index 54c3c9fc54..95df2da0a1 100644 --- a/source/java/org/alfresco/repo/domain/schema/script/ScriptBundleExecutor.java +++ b/source/java/org/alfresco/repo/domain/schema/script/ScriptBundleExecutor.java @@ -1,39 +1,39 @@ -package org.alfresco.repo.domain.schema.script; - -import org.alfresco.error.AlfrescoRuntimeException; - -/** - * Executes a set of zero or more SQL scripts. - * - * @author Matt Ward - */ -public interface ScriptBundleExecutor -{ - /** - * Runs a bundle of scripts. If any script within the bundle fails, then the rest of the files are not run. - * - * @param logOnly true to catch and log any exceptions or false to rethrow - * @param dir Directory where the script bundle may be found. - * @param scripts Names of the SQL scripts to run, relative to the specified directory. - * @throws AlfrescoRuntimeException if a script fails and the logOnly flag is false - */ - void exec(boolean logOnly, String dir, String... scripts); - - /** - * Runs a bundle of scripts. If any script within the bundle fails, then the rest of the files are not run. - * - * @param dir Directory where the script bundle may be found. - * @param scripts Names of the SQL scripts to run, relative to the specified directory. - */ - void exec(String dir, String... scripts); - - /** - * Runs a bundle of scripts. If any script within the bundle fails, then the rest of the files - * are not run, with the exception of postScript - which is always run (a clean-up script for example). - * - * @param dir Directory where the script bundle may be found. - * @param postScript A script that is always run after the other scripts. - * @param scripts Names of the SQL scripts to run, relative to the specified directory. - */ - void execWithPostScript(String dir, String postScript, String... scripts); -} +package org.alfresco.repo.domain.schema.script; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Executes a set of zero or more SQL scripts. + * + * @author Matt Ward + */ +public interface ScriptBundleExecutor +{ + /** + * Runs a bundle of scripts. If any script within the bundle fails, then the rest of the files are not run. + * + * @param logOnly true to catch and log any exceptions or false to rethrow + * @param dir Directory where the script bundle may be found. + * @param scripts Names of the SQL scripts to run, relative to the specified directory. + * @throws AlfrescoRuntimeException if a script fails and the logOnly flag is false + */ + void exec(boolean logOnly, String dir, String... scripts); + + /** + * Runs a bundle of scripts. If any script within the bundle fails, then the rest of the files are not run. + * + * @param dir Directory where the script bundle may be found. + * @param scripts Names of the SQL scripts to run, relative to the specified directory. + */ + void exec(String dir, String... scripts); + + /** + * Runs a bundle of scripts. If any script within the bundle fails, then the rest of the files + * are not run, with the exception of postScript - which is always run (a clean-up script for example). + * + * @param dir Directory where the script bundle may be found. + * @param postScript A script that is always run after the other scripts. + * @param scripts Names of the SQL scripts to run, relative to the specified directory. + */ + void execWithPostScript(String dir, String postScript, String... scripts); +} diff --git a/source/java/org/alfresco/repo/domain/schema/script/ScriptBundleExecutorImpl.java b/source/java/org/alfresco/repo/domain/schema/script/ScriptBundleExecutorImpl.java index 8a433b4212..988d6b60f7 100644 --- a/source/java/org/alfresco/repo/domain/schema/script/ScriptBundleExecutorImpl.java +++ b/source/java/org/alfresco/repo/domain/schema/script/ScriptBundleExecutorImpl.java @@ -1,73 +1,73 @@ -package org.alfresco.repo.domain.schema.script; - -import java.io.File; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * {@link ScriptBundleExecutor} implementation. Uses the supplied {@link ScriptExecutor} - * to invoke multiple SQL scripts in a particular directory. - * - * @author Matt Ward - * @author Derek Hulley - */ -public class ScriptBundleExecutorImpl implements ScriptBundleExecutor -{ - private ScriptExecutor scriptExecutor; - protected Log log = LogFactory.getLog(ScriptBundleExecutorImpl.class); - - public ScriptBundleExecutorImpl(ScriptExecutor scriptExecutor) - { - this.scriptExecutor = scriptExecutor; - } - - @Override - public void exec(boolean logOnly, String dir, String... scripts) - { - for (String name : scripts) - { - File file = new File(dir, name); - try - { - scriptExecutor.executeScriptUrl(file.getPath()); - } - catch (Exception e) - { - String msg = "Unable to run SQL script: dir=" + dir + ", name=" + name; - if (logOnly) - { - log.error(msg, e); - // Do not run any more scripts. - break; - } - else - { - // Client opted to rethrow - throw new AlfrescoRuntimeException(msg, e); - } - } - } - } - - @Override - public void exec(String dir, String... scripts) - { - this.exec(true, dir, scripts); - } - - @Override - public void execWithPostScript(String dir, String postScript, String... scripts) - { - try - { - exec(true, dir, scripts); - } - finally - { - // Always run the post-script. - exec(true, dir, postScript); - } - } -} +package org.alfresco.repo.domain.schema.script; + +import java.io.File; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * {@link ScriptBundleExecutor} implementation. Uses the supplied {@link ScriptExecutor} + * to invoke multiple SQL scripts in a particular directory. + * + * @author Matt Ward + * @author Derek Hulley + */ +public class ScriptBundleExecutorImpl implements ScriptBundleExecutor +{ + private ScriptExecutor scriptExecutor; + protected Log log = LogFactory.getLog(ScriptBundleExecutorImpl.class); + + public ScriptBundleExecutorImpl(ScriptExecutor scriptExecutor) + { + this.scriptExecutor = scriptExecutor; + } + + @Override + public void exec(boolean logOnly, String dir, String... scripts) + { + for (String name : scripts) + { + File file = new File(dir, name); + try + { + scriptExecutor.executeScriptUrl(file.getPath()); + } + catch (Exception e) + { + String msg = "Unable to run SQL script: dir=" + dir + ", name=" + name; + if (logOnly) + { + log.error(msg, e); + // Do not run any more scripts. + break; + } + else + { + // Client opted to rethrow + throw new AlfrescoRuntimeException(msg, e); + } + } + } + } + + @Override + public void exec(String dir, String... scripts) + { + this.exec(true, dir, scripts); + } + + @Override + public void execWithPostScript(String dir, String postScript, String... scripts) + { + try + { + exec(true, dir, scripts); + } + finally + { + // Always run the post-script. + exec(true, dir, postScript); + } + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/NodeParametersEntity.java b/source/java/org/alfresco/repo/domain/solr/NodeParametersEntity.java index 710cd530a1..f8fb2ba2db 100644 --- a/source/java/org/alfresco/repo/domain/solr/NodeParametersEntity.java +++ b/source/java/org/alfresco/repo/domain/solr/NodeParametersEntity.java @@ -1,139 +1,139 @@ -package org.alfresco.repo.domain.solr; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.repo.solr.NodeParameters; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.Pair; - -/** - * Stores node query parameters for use in SOLR DAO queries - * - * @since 4.0 - */ -public class NodeParametersEntity extends NodeParameters -{ - private List includeTypeIds; - private List excludeTypeIds; - - private List includeAspectIds; - private List excludeAspectIds; - - private Long originalIdPropQNameId; - - /** - * Public constructor, but not generally useful - */ - public NodeParametersEntity(QNameDAO qnameDAO) - { - Pair qnamePair = qnameDAO.getQName(ContentModel.PROP_ORIGINAL_ID); - this.setOriginalIdPropQNameId(qnamePair == null ? -1 : qnamePair.getFirst()); - } - - /** - * Construct from higher-level parameters - */ - public NodeParametersEntity(NodeParameters params, QNameDAO qnameDAO) - { - this(qnameDAO); - - this.setFromNodeId(params.getFromNodeId()); - this.setToNodeId(params.getToNodeId()); - - this.setFromTxnId(params.getFromTxnId()); - this.setToTxnId(params.getToTxnId()); - this.setTransactionIds(params.getTransactionIds()); - - this.setStoreIdentifier(params.getStoreIdentifier()); - this.setStoreProtocol(params.getStoreProtocol()); - - // Translate the QNames, if provided - if (params.getIncludeNodeTypes() != null) - { - Set qnamesIds = qnameDAO.convertQNamesToIds(params.getIncludeNodeTypes(), false); - this.setIncludeTypeIds(new ArrayList(qnamesIds)); - } - - if (params.getExcludeNodeTypes() != null) - { - Set qnamesIds = qnameDAO.convertQNamesToIds(params.getExcludeNodeTypes(), false); - this.setExcludeTypeIds(new ArrayList(qnamesIds)); - } - - if (params.getExcludeAspects() != null) - { - Set qnamesIds = qnameDAO.convertQNamesToIds(params.getExcludeAspects(), false); - this.setExcludeAspectIds(new ArrayList(qnamesIds)); - } - - if (params.getIncludeAspects() != null) - { - Set qnamesIds = qnameDAO.convertQNamesToIds(params.getIncludeAspects(), false); - this.setIncludeAspectIds(new ArrayList(qnamesIds)); - } - } - - public void setOriginalIdPropQNameId(Long originalIdPropQNameId) - { - this.originalIdPropQNameId = originalIdPropQNameId; - } - - public Long getOriginalIdPropQNameId() - { - return originalIdPropQNameId; - } - - public boolean getStoreFilter() - { - return (getStoreProtocol() != null || getStoreIdentifier() != null); - } - - public List getIncludeAspectIds() - { - return includeAspectIds; - } - - public void setIncludeAspectIds(List includeAspectIds) - { - this.includeAspectIds = includeAspectIds; - } - - public List getExcludeAspectIds() - { - return excludeAspectIds; - } - - public void setExcludeAspectIds(List excludeAspectIds) - { - this.excludeAspectIds = excludeAspectIds; - } - - public List getIncludeTypeIds() - { - return includeTypeIds; - } - - public void setIncludeTypeIds(List includeTypeIds) - { - this.includeTypeIds = includeTypeIds; - } - - public List getExcludeTypeIds() - { - return excludeTypeIds; - } - - public void setExcludeTypeIds(List excludeTypeIds) - { - this.excludeTypeIds = excludeTypeIds; - } - - public boolean isIncludeNodesTable() - { - return (getFromNodeId() != null || getToNodeId() != null || getIncludeTypeIds() != null || getExcludeTypeIds() != null || getIncludeAspectIds() != null || getExcludeAspectIds() != null); - } -} +package org.alfresco.repo.domain.solr; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.solr.NodeParameters; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; + +/** + * Stores node query parameters for use in SOLR DAO queries + * + * @since 4.0 + */ +public class NodeParametersEntity extends NodeParameters +{ + private List includeTypeIds; + private List excludeTypeIds; + + private List includeAspectIds; + private List excludeAspectIds; + + private Long originalIdPropQNameId; + + /** + * Public constructor, but not generally useful + */ + public NodeParametersEntity(QNameDAO qnameDAO) + { + Pair qnamePair = qnameDAO.getQName(ContentModel.PROP_ORIGINAL_ID); + this.setOriginalIdPropQNameId(qnamePair == null ? -1 : qnamePair.getFirst()); + } + + /** + * Construct from higher-level parameters + */ + public NodeParametersEntity(NodeParameters params, QNameDAO qnameDAO) + { + this(qnameDAO); + + this.setFromNodeId(params.getFromNodeId()); + this.setToNodeId(params.getToNodeId()); + + this.setFromTxnId(params.getFromTxnId()); + this.setToTxnId(params.getToTxnId()); + this.setTransactionIds(params.getTransactionIds()); + + this.setStoreIdentifier(params.getStoreIdentifier()); + this.setStoreProtocol(params.getStoreProtocol()); + + // Translate the QNames, if provided + if (params.getIncludeNodeTypes() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(params.getIncludeNodeTypes(), false); + this.setIncludeTypeIds(new ArrayList(qnamesIds)); + } + + if (params.getExcludeNodeTypes() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(params.getExcludeNodeTypes(), false); + this.setExcludeTypeIds(new ArrayList(qnamesIds)); + } + + if (params.getExcludeAspects() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(params.getExcludeAspects(), false); + this.setExcludeAspectIds(new ArrayList(qnamesIds)); + } + + if (params.getIncludeAspects() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(params.getIncludeAspects(), false); + this.setIncludeAspectIds(new ArrayList(qnamesIds)); + } + } + + public void setOriginalIdPropQNameId(Long originalIdPropQNameId) + { + this.originalIdPropQNameId = originalIdPropQNameId; + } + + public Long getOriginalIdPropQNameId() + { + return originalIdPropQNameId; + } + + public boolean getStoreFilter() + { + return (getStoreProtocol() != null || getStoreIdentifier() != null); + } + + public List getIncludeAspectIds() + { + return includeAspectIds; + } + + public void setIncludeAspectIds(List includeAspectIds) + { + this.includeAspectIds = includeAspectIds; + } + + public List getExcludeAspectIds() + { + return excludeAspectIds; + } + + public void setExcludeAspectIds(List excludeAspectIds) + { + this.excludeAspectIds = excludeAspectIds; + } + + public List getIncludeTypeIds() + { + return includeTypeIds; + } + + public void setIncludeTypeIds(List includeTypeIds) + { + this.includeTypeIds = includeTypeIds; + } + + public List getExcludeTypeIds() + { + return excludeTypeIds; + } + + public void setExcludeTypeIds(List excludeTypeIds) + { + this.excludeTypeIds = excludeTypeIds; + } + + public boolean isIncludeNodesTable() + { + return (getFromNodeId() != null || getToNodeId() != null || getIncludeTypeIds() != null || getExcludeTypeIds() != null || getIncludeAspectIds() != null || getExcludeAspectIds() != null); + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRTrackingParameters.java b/source/java/org/alfresco/repo/domain/solr/SOLRTrackingParameters.java index d3070c7cd4..93c5a9de43 100644 --- a/source/java/org/alfresco/repo/domain/solr/SOLRTrackingParameters.java +++ b/source/java/org/alfresco/repo/domain/solr/SOLRTrackingParameters.java @@ -1,147 +1,147 @@ -package org.alfresco.repo.domain.solr; - -import java.util.List; - -import org.alfresco.util.EqualsHelper; - -/** - * Holds parameters for SOLR DAO calls against alf_transaction and alf_change_set. - * - * @since 4.0 - */ -public class SOLRTrackingParameters -{ - private Long fromIdInclusive; - private Long fromCommitTimeInclusive; - private List ids; - private Long toIdExclusive; - private Long toCommitTimeExclusive; - private final Long deletedTypeQNameId; - - /** - * Construct the parameters - * - * @param deletedTypeQNameId the QName ID representing deleted nodes - */ - public SOLRTrackingParameters(Long deletedTypeQNameId) - { - this.deletedTypeQNameId = deletedTypeQNameId; - } - - public Long getFromIdInclusive() - { - return fromIdInclusive; - } - - public void setFromIdInclusive(Long fromIdInclusive) - { - this.fromIdInclusive = fromIdInclusive; - } - - public Long getFromCommitTimeInclusive() - { - return fromCommitTimeInclusive; - } - - public void setFromCommitTimeInclusive(Long fromCommitTimeInclusive) - { - this.fromCommitTimeInclusive = fromCommitTimeInclusive; - } - - public List getIds() - { - return ids; - } - - public void setIds(List ids) - { - this.ids = ids; - } - - /** - * Helper for cross-DB boolean support - * - * @return true always - */ - public boolean getTrue() - { - return true; - } - - /** - * Helper for cross-DB boolean support - * - * @return false always - */ - public boolean getFalse() - { - return false; - } - - public Long getDeletedTypeQNameId() - { - return deletedTypeQNameId; - } - - public Long getToIdExclusive() - { - return toIdExclusive; - } - - public void setToIdExclusive(Long toIdExclusive) - { - this.toIdExclusive = toIdExclusive; - } - - public Long getToCommitTimeExclusive() - { - return toCommitTimeExclusive; - } - - public void setToCommitTimeExclusive(Long toCommitTimeExclusive) - { - this.toCommitTimeExclusive = toCommitTimeExclusive; - } - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = prime * result + ((fromCommitTimeInclusive == null) ? 0 : fromCommitTimeInclusive.hashCode()); - result = prime * result + ((fromIdInclusive == null) ? 0 : fromIdInclusive.hashCode()); - result = prime * result + ((ids == null) ? 0 : ids.hashCode()); - result = prime * result + ((toCommitTimeExclusive == null) ? 0 : toCommitTimeExclusive.hashCode()); - result = prime * result + ((toIdExclusive == null) ? 0 : toIdExclusive.hashCode()); - result = prime * result + ((deletedTypeQNameId == null) ? 0 : deletedTypeQNameId.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SOLRTrackingParameters other = (SOLRTrackingParameters) obj; - return - EqualsHelper.nullSafeEquals(this.fromCommitTimeInclusive, other.fromCommitTimeInclusive) && - EqualsHelper.nullSafeEquals(this.fromIdInclusive, other.fromIdInclusive) && - EqualsHelper.nullSafeEquals(this.ids, other.ids) && - EqualsHelper.nullSafeEquals(this.toIdExclusive, other.toIdExclusive) && - EqualsHelper.nullSafeEquals(this.toCommitTimeExclusive, other.toCommitTimeExclusive) && - EqualsHelper.nullSafeEquals(this.deletedTypeQNameId, other.deletedTypeQNameId); - } - - @Override - public String toString() - { - return "SOLRTrackingParameters [fromIdInclusive=" + fromIdInclusive - + ", fromCommitTimeInclusive=" + fromCommitTimeInclusive + ", ids=" + ids - + ", toIdExclusive=" + toIdExclusive + ", toCommitTimeExclusive=" - + toCommitTimeExclusive + ", typeQNameId=" + deletedTypeQNameId + "]"; - } -} +package org.alfresco.repo.domain.solr; + +import java.util.List; + +import org.alfresco.util.EqualsHelper; + +/** + * Holds parameters for SOLR DAO calls against alf_transaction and alf_change_set. + * + * @since 4.0 + */ +public class SOLRTrackingParameters +{ + private Long fromIdInclusive; + private Long fromCommitTimeInclusive; + private List ids; + private Long toIdExclusive; + private Long toCommitTimeExclusive; + private final Long deletedTypeQNameId; + + /** + * Construct the parameters + * + * @param deletedTypeQNameId the QName ID representing deleted nodes + */ + public SOLRTrackingParameters(Long deletedTypeQNameId) + { + this.deletedTypeQNameId = deletedTypeQNameId; + } + + public Long getFromIdInclusive() + { + return fromIdInclusive; + } + + public void setFromIdInclusive(Long fromIdInclusive) + { + this.fromIdInclusive = fromIdInclusive; + } + + public Long getFromCommitTimeInclusive() + { + return fromCommitTimeInclusive; + } + + public void setFromCommitTimeInclusive(Long fromCommitTimeInclusive) + { + this.fromCommitTimeInclusive = fromCommitTimeInclusive; + } + + public List getIds() + { + return ids; + } + + public void setIds(List ids) + { + this.ids = ids; + } + + /** + * Helper for cross-DB boolean support + * + * @return true always + */ + public boolean getTrue() + { + return true; + } + + /** + * Helper for cross-DB boolean support + * + * @return false always + */ + public boolean getFalse() + { + return false; + } + + public Long getDeletedTypeQNameId() + { + return deletedTypeQNameId; + } + + public Long getToIdExclusive() + { + return toIdExclusive; + } + + public void setToIdExclusive(Long toIdExclusive) + { + this.toIdExclusive = toIdExclusive; + } + + public Long getToCommitTimeExclusive() + { + return toCommitTimeExclusive; + } + + public void setToCommitTimeExclusive(Long toCommitTimeExclusive) + { + this.toCommitTimeExclusive = toCommitTimeExclusive; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((fromCommitTimeInclusive == null) ? 0 : fromCommitTimeInclusive.hashCode()); + result = prime * result + ((fromIdInclusive == null) ? 0 : fromIdInclusive.hashCode()); + result = prime * result + ((ids == null) ? 0 : ids.hashCode()); + result = prime * result + ((toCommitTimeExclusive == null) ? 0 : toCommitTimeExclusive.hashCode()); + result = prime * result + ((toIdExclusive == null) ? 0 : toIdExclusive.hashCode()); + result = prime * result + ((deletedTypeQNameId == null) ? 0 : deletedTypeQNameId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SOLRTrackingParameters other = (SOLRTrackingParameters) obj; + return + EqualsHelper.nullSafeEquals(this.fromCommitTimeInclusive, other.fromCommitTimeInclusive) && + EqualsHelper.nullSafeEquals(this.fromIdInclusive, other.fromIdInclusive) && + EqualsHelper.nullSafeEquals(this.ids, other.ids) && + EqualsHelper.nullSafeEquals(this.toIdExclusive, other.toIdExclusive) && + EqualsHelper.nullSafeEquals(this.toCommitTimeExclusive, other.toCommitTimeExclusive) && + EqualsHelper.nullSafeEquals(this.deletedTypeQNameId, other.deletedTypeQNameId); + } + + @Override + public String toString() + { + return "SOLRTrackingParameters [fromIdInclusive=" + fromIdInclusive + + ", fromCommitTimeInclusive=" + fromCommitTimeInclusive + ", ids=" + ids + + ", toIdExclusive=" + toIdExclusive + ", toCommitTimeExclusive=" + + toCommitTimeExclusive + ", typeQNameId=" + deletedTypeQNameId + "]"; + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRTransaction.java b/source/java/org/alfresco/repo/domain/solr/SOLRTransaction.java index ac6f5f76a5..a6cb03b8a2 100644 --- a/source/java/org/alfresco/repo/domain/solr/SOLRTransaction.java +++ b/source/java/org/alfresco/repo/domain/solr/SOLRTransaction.java @@ -1,14 +1,14 @@ -package org.alfresco.repo.domain.solr; - -/** - * Interface for SOLR transaction objects. - * - * @since 4.0 - */ -public interface SOLRTransaction -{ - public Long getId(); - public Long getCommitTimeMs(); - public int getUpdates(); - public int getDeletes(); -} +package org.alfresco.repo.domain.solr; + +/** + * Interface for SOLR transaction objects. + * + * @since 4.0 + */ +public interface SOLRTransaction +{ + public Long getId(); + public Long getCommitTimeMs(); + public int getUpdates(); + public int getDeletes(); +} diff --git a/source/java/org/alfresco/repo/domain/subscriptions/AbstractSubscriptionsDAO.java b/source/java/org/alfresco/repo/domain/subscriptions/AbstractSubscriptionsDAO.java index 900dc2795f..d01b6af43e 100644 --- a/source/java/org/alfresco/repo/domain/subscriptions/AbstractSubscriptionsDAO.java +++ b/source/java/org/alfresco/repo/domain/subscriptions/AbstractSubscriptionsDAO.java @@ -1,59 +1,59 @@ -package org.alfresco.repo.domain.subscriptions; - -import org.alfresco.query.PagingRequest; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.NoSuchPersonException; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.subscriptions.PagingFollowingResults; -import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResults; -import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum; - -public abstract class AbstractSubscriptionsDAO implements SubscriptionsDAO -{ - protected NodeService nodeService; - protected PersonService personService; - - public final void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public final void setPersonService(PersonService personService) - { - this.personService = personService; - } - - @Override - public abstract PagingSubscriptionResults selectSubscriptions(String userId, SubscriptionItemTypeEnum type, - PagingRequest pagingRequest); - - @Override - public abstract int countSubscriptions(String userId, SubscriptionItemTypeEnum type); - - @Override - public abstract void insertSubscription(String userId, NodeRef node); - - @Override - public abstract void deleteSubscription(String userId, NodeRef node); - - @Override - public abstract boolean hasSubscribed(String userId, NodeRef node); - - @Override - public abstract PagingFollowingResults selectFollowers(String userId, PagingRequest pagingRequest); - - @Override - public abstract int countFollowers(String userId); - - protected NodeRef getUserNodeRef(String userId) - { - try - { - return personService.getPerson(userId, false); - } catch (NoSuchPersonException nspe) - { - return null; - } - } -} +package org.alfresco.repo.domain.subscriptions; + +import org.alfresco.query.PagingRequest; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.NoSuchPersonException; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.subscriptions.PagingFollowingResults; +import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResults; +import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum; + +public abstract class AbstractSubscriptionsDAO implements SubscriptionsDAO +{ + protected NodeService nodeService; + protected PersonService personService; + + public final void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public final void setPersonService(PersonService personService) + { + this.personService = personService; + } + + @Override + public abstract PagingSubscriptionResults selectSubscriptions(String userId, SubscriptionItemTypeEnum type, + PagingRequest pagingRequest); + + @Override + public abstract int countSubscriptions(String userId, SubscriptionItemTypeEnum type); + + @Override + public abstract void insertSubscription(String userId, NodeRef node); + + @Override + public abstract void deleteSubscription(String userId, NodeRef node); + + @Override + public abstract boolean hasSubscribed(String userId, NodeRef node); + + @Override + public abstract PagingFollowingResults selectFollowers(String userId, PagingRequest pagingRequest); + + @Override + public abstract int countFollowers(String userId); + + protected NodeRef getUserNodeRef(String userId) + { + try + { + return personService.getPerson(userId, false); + } catch (NoSuchPersonException nspe) + { + return null; + } + } +} diff --git a/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionEntity.java b/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionEntity.java index 03838dc659..7da1da4dd8 100644 --- a/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionEntity.java +++ b/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionEntity.java @@ -1,38 +1,38 @@ -package org.alfresco.repo.domain.subscriptions; - -public class SubscriptionEntity -{ - private Long userNodeId; - private Long nodeId; - private Long deletedTypeQNameId; - - public Long getUserNodeId() - { - return userNodeId; - } - - public void setUserNodeId(Long userNodeId) - { - this.userNodeId = userNodeId; - } - - public Long getNodeId() - { - return nodeId; - } - - public void setNodeId(Long nodeId) - { - this.nodeId = nodeId; - } - - public Long getDeletedTypeQNameId() - { - return deletedTypeQNameId; - } - - public void setDeletedTypeQNameId(Long deletedTypeQNameId) - { - this.deletedTypeQNameId = deletedTypeQNameId; - } -} +package org.alfresco.repo.domain.subscriptions; + +public class SubscriptionEntity +{ + private Long userNodeId; + private Long nodeId; + private Long deletedTypeQNameId; + + public Long getUserNodeId() + { + return userNodeId; + } + + public void setUserNodeId(Long userNodeId) + { + this.userNodeId = userNodeId; + } + + public Long getNodeId() + { + return nodeId; + } + + public void setNodeId(Long nodeId) + { + this.nodeId = nodeId; + } + + public Long getDeletedTypeQNameId() + { + return deletedTypeQNameId; + } + + public void setDeletedTypeQNameId(Long deletedTypeQNameId) + { + this.deletedTypeQNameId = deletedTypeQNameId; + } +} diff --git a/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionNodeEntity.java b/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionNodeEntity.java index 0236b5f4a6..eb618f7731 100644 --- a/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionNodeEntity.java +++ b/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionNodeEntity.java @@ -1,45 +1,45 @@ -package org.alfresco.repo.domain.subscriptions; - -import org.alfresco.service.cmr.repository.NodeRef; - -public class SubscriptionNodeEntity -{ - private String protocol; - private String identifier; - private String id; - - public String getProtocol() - { - return protocol; - } - - public void setProtocol(String protocol) - { - this.protocol = protocol; - } - - public String getIdentifier() - { - return identifier; - } - - public void setIdentifier(String identifier) - { - this.identifier = identifier; - } - - public String getId() - { - return id; - } - - public void setId(String id) - { - this.id = id; - } - - public NodeRef getNodeRef() - { - return new NodeRef(protocol, identifier, id); - } -} +package org.alfresco.repo.domain.subscriptions; + +import org.alfresco.service.cmr.repository.NodeRef; + +public class SubscriptionNodeEntity +{ + private String protocol; + private String identifier; + private String id; + + public String getProtocol() + { + return protocol; + } + + public void setProtocol(String protocol) + { + this.protocol = protocol; + } + + public String getIdentifier() + { + return identifier; + } + + public void setIdentifier(String identifier) + { + this.identifier = identifier; + } + + public String getId() + { + return id; + } + + public void setId(String id) + { + this.id = id; + } + + public NodeRef getNodeRef() + { + return new NodeRef(protocol, identifier, id); + } +} diff --git a/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionsDAO.java b/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionsDAO.java index aa47118b39..8c74b5874e 100644 --- a/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionsDAO.java +++ b/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionsDAO.java @@ -1,27 +1,27 @@ -package org.alfresco.repo.domain.subscriptions; - -import org.alfresco.query.PagingRequest; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.subscriptions.PagingFollowingResults; -import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResults; -import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum; - -public interface SubscriptionsDAO -{ - PagingSubscriptionResults selectSubscriptions(String userId, SubscriptionItemTypeEnum type, - PagingRequest pagingRequest); - - int countSubscriptions(String userId, SubscriptionItemTypeEnum type); - - void insertSubscription(String userId, NodeRef node); - - void deleteSubscription(String userId, NodeRef node); - - boolean hasSubscribed(String userId, NodeRef node); - - PagingFollowingResults selectFollowing(String userId, PagingRequest pagingRequest); - - PagingFollowingResults selectFollowers(String userId, PagingRequest pagingRequest); - - int countFollowers(String userId); -} +package org.alfresco.repo.domain.subscriptions; + +import org.alfresco.query.PagingRequest; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.subscriptions.PagingFollowingResults; +import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResults; +import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum; + +public interface SubscriptionsDAO +{ + PagingSubscriptionResults selectSubscriptions(String userId, SubscriptionItemTypeEnum type, + PagingRequest pagingRequest); + + int countSubscriptions(String userId, SubscriptionItemTypeEnum type); + + void insertSubscription(String userId, NodeRef node); + + void deleteSubscription(String userId, NodeRef node); + + boolean hasSubscribed(String userId, NodeRef node); + + PagingFollowingResults selectFollowing(String userId, PagingRequest pagingRequest); + + PagingFollowingResults selectFollowers(String userId, PagingRequest pagingRequest); + + int countFollowers(String userId); +} diff --git a/source/java/org/alfresco/repo/domain/subscriptions/ibatis/SubscriptionsDAOImpl.java b/source/java/org/alfresco/repo/domain/subscriptions/ibatis/SubscriptionsDAOImpl.java index 84834bb7e1..a11ff302a5 100644 --- a/source/java/org/alfresco/repo/domain/subscriptions/ibatis/SubscriptionsDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/subscriptions/ibatis/SubscriptionsDAOImpl.java @@ -1,335 +1,335 @@ -package org.alfresco.repo.domain.subscriptions.ibatis; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.query.PagingRequest; -import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.repo.domain.subscriptions.AbstractSubscriptionsDAO; -import org.alfresco.repo.domain.subscriptions.SubscriptionEntity; -import org.alfresco.repo.domain.subscriptions.SubscriptionNodeEntity; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.subscriptions.PagingFollowingResults; -import org.alfresco.service.cmr.subscriptions.PagingFollowingResultsImpl; -import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResults; -import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResultsImpl; -import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.Pair; -import org.apache.ibatis.session.RowBounds; -import org.mybatis.spring.SqlSessionTemplate; - -public class SubscriptionsDAOImpl extends AbstractSubscriptionsDAO -{ - private static final QName PROP_SYS_NODE_DBID = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "node-dbid"); - - private SqlSessionTemplate template; - private QNameDAO qnameDAO; - - public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) - { - this.template = sqlSessionTemplate; - } - - public final void setQNameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - - @Override - public PagingSubscriptionResults selectSubscriptions(String userId, SubscriptionItemTypeEnum type, - PagingRequest pagingRequest) - { - if (userId == null) - { - throw new IllegalArgumentException("User Id may not be null!"); - } - - if (type == null) - { - throw new IllegalArgumentException("Type may not be null!"); - } - - NodeRef userNodeRef = getUserNodeRef(userId); - if (userNodeRef == null) - { - throw new IllegalArgumentException("User does not exist!"); - } - - Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); - - Map map = new HashMap(); - map.put("userNodeId", dbid); - - int maxItems = (pagingRequest.getMaxItems() < 0 || pagingRequest.getMaxItems() > Integer.MAX_VALUE - 1 ? Integer.MAX_VALUE - 1 - : pagingRequest.getMaxItems() + 1); - - @SuppressWarnings("unchecked") - List nodeList = template.selectList( - "alfresco.subscriptions.select_Subscriptions", map, new RowBounds(pagingRequest.getSkipCount(), - maxItems + 1)); - - boolean hasMore = nodeList.size() > maxItems; - - List result = new ArrayList(nodeList.size()); - for (SubscriptionNodeEntity sne : nodeList) - { - result.add(sne.getNodeRef()); - if (result.size() == pagingRequest.getMaxItems()) - { - break; - } - } - - Integer totalCount = null; - if (pagingRequest.getRequestTotalCountMax() > 0) - { - totalCount = countSubscriptions(userId, type); - } - - return new PagingSubscriptionResultsImpl(result, hasMore, totalCount); - } - - @Override - public int countSubscriptions(String userId, SubscriptionItemTypeEnum type) - { - if (userId == null) - { - throw new IllegalArgumentException("User Id may not be null!"); - } - - if (type == null) - { - throw new IllegalArgumentException("Type may not be null!"); - } - - NodeRef userNodeRef = getUserNodeRef(userId); - if (userNodeRef == null) - { - return 0; - } - - Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); - - Map map = new HashMap(); - map.put("userNodeId", dbid); - - Number count = template.selectOne("alfresco.subscriptions.select_countSubscriptions", map); - return count == null ? 0 : count.intValue(); - } - - @Override - public void insertSubscription(String userId, NodeRef node) - { - if (userId == null) - { - throw new IllegalArgumentException("User Id may not be null!"); - } - - if (node == null) - { - throw new IllegalArgumentException("Node may not be null!"); - } - - NodeRef userNodeRef = getUserNodeRef(userId); - if (userNodeRef == null) - { - throw new IllegalArgumentException("User does not exist!"); - } - - Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); - Long nodedbid = (Long) nodeService.getProperty(node, PROP_SYS_NODE_DBID); - - SubscriptionEntity se = new SubscriptionEntity(); - se.setUserNodeId(dbid); - se.setNodeId(nodedbid); - - Number count = template.selectOne("alfresco.subscriptions.select_hasSubscribed", se); - if (count == null || count.intValue() == 0) - { - template.insert("alfresco.subscriptions.insert_Subscription", se); - } - } - - @Override - public void deleteSubscription(String userId, NodeRef node) - { - if (userId == null) - { - throw new IllegalArgumentException("User Id may not be null!"); - } - - if (node == null) - { - throw new IllegalArgumentException("Node may not be null!"); - } - - NodeRef userNodeRef = getUserNodeRef(userId); - if (userNodeRef == null) - { - throw new IllegalArgumentException("User does not exist!"); - } - - Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); - Long nodedbid = (Long) nodeService.getProperty(node, PROP_SYS_NODE_DBID); - - SubscriptionEntity se = new SubscriptionEntity(); - se.setUserNodeId(dbid); - se.setNodeId(nodedbid); - - template.delete("alfresco.subscriptions.delete_Subscription", se); - } - - @Override - public boolean hasSubscribed(String userId, NodeRef node) - { - if (userId == null) - { - throw new IllegalArgumentException("User Id may not be null!"); - } - - if (node == null) - { - throw new IllegalArgumentException("Node may not be null!"); - } - - NodeRef userNodeRef = getUserNodeRef(userId); - if (userNodeRef == null) - { - throw new IllegalArgumentException("User does not exist!"); - } - - Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); - Long nodedbid = (Long) nodeService.getProperty(node, PROP_SYS_NODE_DBID); - - SubscriptionEntity se = new SubscriptionEntity(); - se.setUserNodeId(dbid); - se.setNodeId(nodedbid); - - Number count = template.selectOne("alfresco.subscriptions.select_hasSubscribed", se); - return count == null ? false : count.intValue() > 0; - } - - @Override - public PagingFollowingResults selectFollowing(String userId, PagingRequest pagingRequest) - { - if (userId == null) - { - throw new IllegalArgumentException("User Id may not be null!"); - } - - NodeRef userNodeRef = getUserNodeRef(userId); - if (userNodeRef == null) - { - throw new IllegalArgumentException("User does not exist!"); - } - - Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); - - Map map = new HashMap(); - - Pair qNamePair = qnameDAO.getQName(ContentModel.PROP_USERNAME); - if (null != qNamePair) - { - map.put("userIdQname", qNamePair.getFirst()); - } - - map.put("userNodeId", dbid); - - int maxItems = (pagingRequest.getMaxItems() < 0 || pagingRequest.getMaxItems() > Integer.MAX_VALUE - 1 ? Integer.MAX_VALUE - 1 - : pagingRequest.getMaxItems() + 1); - - @SuppressWarnings("unchecked") - List userList = template.selectList("alfresco.subscriptions.select_Following", map, - new RowBounds(pagingRequest.getSkipCount(), maxItems + 1)); - - boolean hasMore = userList.size() > maxItems; - if (hasMore && userList.size() > 0) - { - userList.remove(userList.size() - 1); - } - - Integer totalCount = null; - if (pagingRequest.getRequestTotalCountMax() > 0) - { - totalCount = countSubscriptions(userId, SubscriptionItemTypeEnum.USER); - } - - return new PagingFollowingResultsImpl(userList, hasMore, totalCount); - } - - @Override - public PagingFollowingResults selectFollowers(String userId, PagingRequest pagingRequest) - { - if (userId == null) - { - throw new IllegalArgumentException("User Id may not be null!"); - } - - NodeRef userNodeRef = getUserNodeRef(userId); - if (userNodeRef == null) - { - throw new IllegalArgumentException("User does not exist!"); - } - - Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); - - Map map = new HashMap(); - - Pair qNamePair = qnameDAO.getQName(ContentModel.PROP_USERNAME); - if (null != qNamePair) - { - map.put("userIdQname", qNamePair.getFirst()); - } - - map.put("userNodeId", dbid); - - int maxItems = (pagingRequest.getMaxItems() < 0 || pagingRequest.getMaxItems() > Integer.MAX_VALUE - 1 ? Integer.MAX_VALUE - 1 - : pagingRequest.getMaxItems() + 1); - - @SuppressWarnings("unchecked") - List userList = template.selectList("alfresco.subscriptions.select_Followers", map, - new RowBounds(pagingRequest.getSkipCount(), maxItems + 1)); - - boolean hasMore = userList.size() > maxItems; - if (hasMore && userList.size() > 0) - { - userList.remove(userList.size() - 1); - } - - Integer totalCount = null; - if (pagingRequest.getRequestTotalCountMax() > 0) - { - totalCount = countFollowers(userId); - } - - return new PagingFollowingResultsImpl(userList, hasMore, totalCount); - } - - @Override - public int countFollowers(String userId) - { - if (userId == null) - { - throw new IllegalArgumentException("User Id may not be null!"); - } - - NodeRef userNodeRef = getUserNodeRef(userId); - if (userNodeRef == null) - { - return 0; - } - - Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); - - Map map = new HashMap(); - map.put("userNodeId", dbid); - - Number count = template.selectOne("alfresco.subscriptions.select_countFollowers", map); - return count == null ? 0 : count.intValue(); - } -} +package org.alfresco.repo.domain.subscriptions.ibatis; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.query.PagingRequest; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.domain.subscriptions.AbstractSubscriptionsDAO; +import org.alfresco.repo.domain.subscriptions.SubscriptionEntity; +import org.alfresco.repo.domain.subscriptions.SubscriptionNodeEntity; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.subscriptions.PagingFollowingResults; +import org.alfresco.service.cmr.subscriptions.PagingFollowingResultsImpl; +import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResults; +import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResultsImpl; +import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.apache.ibatis.session.RowBounds; +import org.mybatis.spring.SqlSessionTemplate; + +public class SubscriptionsDAOImpl extends AbstractSubscriptionsDAO +{ + private static final QName PROP_SYS_NODE_DBID = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "node-dbid"); + + private SqlSessionTemplate template; + private QNameDAO qnameDAO; + + public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) + { + this.template = sqlSessionTemplate; + } + + public final void setQNameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + @Override + public PagingSubscriptionResults selectSubscriptions(String userId, SubscriptionItemTypeEnum type, + PagingRequest pagingRequest) + { + if (userId == null) + { + throw new IllegalArgumentException("User Id may not be null!"); + } + + if (type == null) + { + throw new IllegalArgumentException("Type may not be null!"); + } + + NodeRef userNodeRef = getUserNodeRef(userId); + if (userNodeRef == null) + { + throw new IllegalArgumentException("User does not exist!"); + } + + Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); + + Map map = new HashMap(); + map.put("userNodeId", dbid); + + int maxItems = (pagingRequest.getMaxItems() < 0 || pagingRequest.getMaxItems() > Integer.MAX_VALUE - 1 ? Integer.MAX_VALUE - 1 + : pagingRequest.getMaxItems() + 1); + + @SuppressWarnings("unchecked") + List nodeList = template.selectList( + "alfresco.subscriptions.select_Subscriptions", map, new RowBounds(pagingRequest.getSkipCount(), + maxItems + 1)); + + boolean hasMore = nodeList.size() > maxItems; + + List result = new ArrayList(nodeList.size()); + for (SubscriptionNodeEntity sne : nodeList) + { + result.add(sne.getNodeRef()); + if (result.size() == pagingRequest.getMaxItems()) + { + break; + } + } + + Integer totalCount = null; + if (pagingRequest.getRequestTotalCountMax() > 0) + { + totalCount = countSubscriptions(userId, type); + } + + return new PagingSubscriptionResultsImpl(result, hasMore, totalCount); + } + + @Override + public int countSubscriptions(String userId, SubscriptionItemTypeEnum type) + { + if (userId == null) + { + throw new IllegalArgumentException("User Id may not be null!"); + } + + if (type == null) + { + throw new IllegalArgumentException("Type may not be null!"); + } + + NodeRef userNodeRef = getUserNodeRef(userId); + if (userNodeRef == null) + { + return 0; + } + + Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); + + Map map = new HashMap(); + map.put("userNodeId", dbid); + + Number count = template.selectOne("alfresco.subscriptions.select_countSubscriptions", map); + return count == null ? 0 : count.intValue(); + } + + @Override + public void insertSubscription(String userId, NodeRef node) + { + if (userId == null) + { + throw new IllegalArgumentException("User Id may not be null!"); + } + + if (node == null) + { + throw new IllegalArgumentException("Node may not be null!"); + } + + NodeRef userNodeRef = getUserNodeRef(userId); + if (userNodeRef == null) + { + throw new IllegalArgumentException("User does not exist!"); + } + + Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); + Long nodedbid = (Long) nodeService.getProperty(node, PROP_SYS_NODE_DBID); + + SubscriptionEntity se = new SubscriptionEntity(); + se.setUserNodeId(dbid); + se.setNodeId(nodedbid); + + Number count = template.selectOne("alfresco.subscriptions.select_hasSubscribed", se); + if (count == null || count.intValue() == 0) + { + template.insert("alfresco.subscriptions.insert_Subscription", se); + } + } + + @Override + public void deleteSubscription(String userId, NodeRef node) + { + if (userId == null) + { + throw new IllegalArgumentException("User Id may not be null!"); + } + + if (node == null) + { + throw new IllegalArgumentException("Node may not be null!"); + } + + NodeRef userNodeRef = getUserNodeRef(userId); + if (userNodeRef == null) + { + throw new IllegalArgumentException("User does not exist!"); + } + + Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); + Long nodedbid = (Long) nodeService.getProperty(node, PROP_SYS_NODE_DBID); + + SubscriptionEntity se = new SubscriptionEntity(); + se.setUserNodeId(dbid); + se.setNodeId(nodedbid); + + template.delete("alfresco.subscriptions.delete_Subscription", se); + } + + @Override + public boolean hasSubscribed(String userId, NodeRef node) + { + if (userId == null) + { + throw new IllegalArgumentException("User Id may not be null!"); + } + + if (node == null) + { + throw new IllegalArgumentException("Node may not be null!"); + } + + NodeRef userNodeRef = getUserNodeRef(userId); + if (userNodeRef == null) + { + throw new IllegalArgumentException("User does not exist!"); + } + + Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); + Long nodedbid = (Long) nodeService.getProperty(node, PROP_SYS_NODE_DBID); + + SubscriptionEntity se = new SubscriptionEntity(); + se.setUserNodeId(dbid); + se.setNodeId(nodedbid); + + Number count = template.selectOne("alfresco.subscriptions.select_hasSubscribed", se); + return count == null ? false : count.intValue() > 0; + } + + @Override + public PagingFollowingResults selectFollowing(String userId, PagingRequest pagingRequest) + { + if (userId == null) + { + throw new IllegalArgumentException("User Id may not be null!"); + } + + NodeRef userNodeRef = getUserNodeRef(userId); + if (userNodeRef == null) + { + throw new IllegalArgumentException("User does not exist!"); + } + + Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); + + Map map = new HashMap(); + + Pair qNamePair = qnameDAO.getQName(ContentModel.PROP_USERNAME); + if (null != qNamePair) + { + map.put("userIdQname", qNamePair.getFirst()); + } + + map.put("userNodeId", dbid); + + int maxItems = (pagingRequest.getMaxItems() < 0 || pagingRequest.getMaxItems() > Integer.MAX_VALUE - 1 ? Integer.MAX_VALUE - 1 + : pagingRequest.getMaxItems() + 1); + + @SuppressWarnings("unchecked") + List userList = template.selectList("alfresco.subscriptions.select_Following", map, + new RowBounds(pagingRequest.getSkipCount(), maxItems + 1)); + + boolean hasMore = userList.size() > maxItems; + if (hasMore && userList.size() > 0) + { + userList.remove(userList.size() - 1); + } + + Integer totalCount = null; + if (pagingRequest.getRequestTotalCountMax() > 0) + { + totalCount = countSubscriptions(userId, SubscriptionItemTypeEnum.USER); + } + + return new PagingFollowingResultsImpl(userList, hasMore, totalCount); + } + + @Override + public PagingFollowingResults selectFollowers(String userId, PagingRequest pagingRequest) + { + if (userId == null) + { + throw new IllegalArgumentException("User Id may not be null!"); + } + + NodeRef userNodeRef = getUserNodeRef(userId); + if (userNodeRef == null) + { + throw new IllegalArgumentException("User does not exist!"); + } + + Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); + + Map map = new HashMap(); + + Pair qNamePair = qnameDAO.getQName(ContentModel.PROP_USERNAME); + if (null != qNamePair) + { + map.put("userIdQname", qNamePair.getFirst()); + } + + map.put("userNodeId", dbid); + + int maxItems = (pagingRequest.getMaxItems() < 0 || pagingRequest.getMaxItems() > Integer.MAX_VALUE - 1 ? Integer.MAX_VALUE - 1 + : pagingRequest.getMaxItems() + 1); + + @SuppressWarnings("unchecked") + List userList = template.selectList("alfresco.subscriptions.select_Followers", map, + new RowBounds(pagingRequest.getSkipCount(), maxItems + 1)); + + boolean hasMore = userList.size() > maxItems; + if (hasMore && userList.size() > 0) + { + userList.remove(userList.size() - 1); + } + + Integer totalCount = null; + if (pagingRequest.getRequestTotalCountMax() > 0) + { + totalCount = countFollowers(userId); + } + + return new PagingFollowingResultsImpl(userList, hasMore, totalCount); + } + + @Override + public int countFollowers(String userId) + { + if (userId == null) + { + throw new IllegalArgumentException("User Id may not be null!"); + } + + NodeRef userNodeRef = getUserNodeRef(userId); + if (userNodeRef == null) + { + return 0; + } + + Long dbid = (Long) nodeService.getProperty(userNodeRef, PROP_SYS_NODE_DBID); + + Map map = new HashMap(); + map.put("userNodeId", dbid); + + Number count = template.selectOne("alfresco.subscriptions.select_countFollowers", map); + return count == null ? 0 : count.intValue(); + } +} diff --git a/source/java/org/alfresco/repo/exporter/NodeContentData.java b/source/java/org/alfresco/repo/exporter/NodeContentData.java index 3f5d7f4a02..22a0719343 100644 --- a/source/java/org/alfresco/repo/exporter/NodeContentData.java +++ b/source/java/org/alfresco/repo/exporter/NodeContentData.java @@ -1,32 +1,32 @@ -package org.alfresco.repo.exporter; - -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.NodeRef; - - -/** - * ContentData with associated NodeRef - */ -public class NodeContentData extends ContentData -{ - private static final long serialVersionUID = -455291250695108108L; - private NodeRef nodeRef; - - /** - * Construct - */ - public NodeContentData(NodeRef nodeRef, ContentData contentData) - { - super(contentData.getContentUrl(), contentData.getMimetype(), contentData.getSize(), - contentData.getEncoding(), contentData.getLocale()); - this.nodeRef = nodeRef; - } - - /** - * @return noderef - */ - public NodeRef getNodeRef() - { - return nodeRef; - } -} +package org.alfresco.repo.exporter; + +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; + + +/** + * ContentData with associated NodeRef + */ +public class NodeContentData extends ContentData +{ + private static final long serialVersionUID = -455291250695108108L; + private NodeRef nodeRef; + + /** + * Construct + */ + public NodeContentData(NodeRef nodeRef, ContentData contentData) + { + super(contentData.getContentUrl(), contentData.getMimetype(), contentData.getSize(), + contentData.getEncoding(), contentData.getLocale()); + this.nodeRef = nodeRef; + } + + /** + * @return noderef + */ + public NodeRef getNodeRef() + { + return nodeRef; + } +} diff --git a/source/java/org/alfresco/repo/exporter/RepositoryExporterComponent.java b/source/java/org/alfresco/repo/exporter/RepositoryExporterComponent.java index 49b5b8bd03..20fed81f21 100644 --- a/source/java/org/alfresco/repo/exporter/RepositoryExporterComponent.java +++ b/source/java/org/alfresco/repo/exporter/RepositoryExporterComponent.java @@ -1,471 +1,471 @@ -package org.alfresco.repo.exporter; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import org.springframework.extensions.surf.util.I18NUtil; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.importer.system.SystemExporterImporter; -import org.alfresco.service.cmr.model.FileExistsException; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.MimetypeService; -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.view.Exporter; -import org.alfresco.service.cmr.view.ExporterCrawlerParameters; -import org.alfresco.service.cmr.view.ExporterException; -import org.alfresco.service.cmr.view.ExporterService; -import org.alfresco.service.cmr.view.Location; -import org.alfresco.service.cmr.view.ReferenceType; -import org.alfresco.service.cmr.view.RepositoryExporterService; -import org.alfresco.service.namespace.QName; -import org.springframework.extensions.surf.util.ParameterCheck; -import org.alfresco.util.TempFileProvider; - - -/** - * Full Repository Export Service - * - * @author davidc - */ -public class RepositoryExporterComponent implements RepositoryExporterService -{ - private static final String STOREREF_KEY = "storeRef"; - private static final String PACKAGENAME_KEY = "packageName"; - private static final String INCLUDED_PATHS = "includedPaths"; - - // component dependencies - private ExporterService exporterService; - private MimetypeService mimetypeService; - private FileFolderService fileFolderService; - private SystemExporterImporter systemExporterImporter; - private NodeService nodeService; - private List exportStores; - - - public void setExporterService(ExporterService exporterService) - { - this.exporterService = exporterService; - } - - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } - - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - public void setSystemExporter(SystemExporterImporter systemExporterImporter) - { - this.systemExporterImporter = systemExporterImporter; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setStores(List exportStores) - { - this.exportStores = exportStores; - } - - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.RepositoryExporterService#export() - */ - public FileExportHandle[] export(String packageName) - { - List exportHandles = exportStores(exportStores, packageName, new TempFileExporter()); - return exportHandles.toArray(new FileExportHandle[exportHandles.size()]); - } - - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.RepositoryExporterService#export(java.io.File) - */ - public FileExportHandle[] export(File directoryDestination, String packageName) - { - ParameterCheck.mandatory("directoryDestination", directoryDestination); - if (!directoryDestination.isDirectory()) - { - throw new ExporterException("Export location " + directoryDestination.getAbsolutePath() + " is not a directory"); - } - - List exportHandles = exportStores(exportStores, packageName, new FileExporter(directoryDestination)); - return exportHandles.toArray(new FileExportHandle[exportHandles.size()]); - } - - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.RepositoryExporterService#export(org.alfresco.service.cmr.repository.NodeRef) - */ - public RepositoryExportHandle[] export(NodeRef repositoryDestination, String packageName) - { - ParameterCheck.mandatory("repositoryDestination", repositoryDestination); - FileInfo destInfo = fileFolderService.getFileInfo(repositoryDestination); - if (destInfo == null || !destInfo.isFolder()) - { - throw new ExporterException("Repository destination " + repositoryDestination + " is not a folder."); - } - - List exportHandles = exportStores(exportStores, packageName, new TempFileExporter()); - Map mimetypeExtensions = mimetypeService.getExtensionsByMimetype(); - List repoExportHandles = new ArrayList(exportHandles.size()); - for (FileExportHandle exportHandle : exportHandles) - { - String name = exportHandle.packageName + "." + mimetypeExtensions.get(exportHandle.mimeType); - String title = exportHandle.packageName; - String description; - if (exportHandle.storeRef != null) - { - description = I18NUtil.getMessage("export.store.package.description", new Object[] { exportHandle.storeRef.toString() }); - } - else - { - description = I18NUtil.getMessage("export.generic.package.description"); - } - - NodeRef repoExportFile = addExportFile(repositoryDestination, name, title, description, exportHandle.mimeType, exportHandle.exportFile); - RepositoryExportHandle handle = new RepositoryExportHandle(); - handle.storeRef = exportHandle.storeRef; - handle.packageName = exportHandle.packageName; - handle.mimeType = exportHandle.mimeType; - handle.exportFile = repoExportFile; - repoExportHandles.add(handle); - - // delete temporary export file - exportHandle.exportFile.delete(); - } - - return repoExportHandles.toArray(new RepositoryExportHandle[repoExportHandles.size()]); - } - - - /** - * Add a file system based .acp file into the repository - * - * @param repoDestination location within repository to place .acp file - * @param name name - * @param title title - * @param description description - * @param mimeType mime type - * @param exportFile the .acp file - * @return node reference to import .acp file - */ - private NodeRef addExportFile(NodeRef repoDestination, String name, String title, String description, String mimeType, File exportFile) - { - // - // import temp file into repository - // - - // determine if file already exists - List paths = new ArrayList(); - paths.add(name); - try - { - FileInfo fileInfo = fileFolderService.resolveNamePath(repoDestination, paths); - // Note: file already exists - delete - fileFolderService.delete(fileInfo.getNodeRef()); - } - catch (org.alfresco.service.cmr.model.FileNotFoundException e) - { - // Note: file does not exist - no need to delete - } - - // create acp file in repository - NodeRef exportFileNodeRef = null; - try - { - FileInfo fileInfo = fileFolderService.create(repoDestination, name, ContentModel.TYPE_CONTENT); - ContentWriter writer = fileFolderService.getWriter(fileInfo.getNodeRef()); - writer.setMimetype(mimeType); - writer.putContent(exportFile); - exportFileNodeRef = fileInfo.getNodeRef(); - - // add a title for Web Client viewing - Map titledProps = new HashMap(3, 1.0f); - titledProps.put(ContentModel.PROP_TITLE, title); - titledProps.put(ContentModel.PROP_DESCRIPTION, description); - nodeService.addAspect(exportFileNodeRef, ContentModel.ASPECT_TITLED, titledProps); - - } - catch (FileExistsException e) - { - // Note: shouldn't get here - } - - return exportFileNodeRef; - } - - - /** - * Contract for exporting a repository - * - * @author davidc - * - * @param - */ - private interface ExportStore - { - public ExportHandleType exportStore(ExporterCrawlerParameters exportParameters, String packageName, Exporter progress); - - public ExportHandleType exportSystem(String packageName); - } - - - /** - * Enumerate list of pre-configured Stores and export one by one - * - * @param stores the list of stores to export - * @param packageName package name - * @param exportStore the exporter call-back for handling the actual export - * @return the list export file handles - */ - private List exportStores(List stores, String packageName, ExportStore exportStore) - { - List exportHandles = new ArrayList(stores.size() +1); - - // export repository system info - { - String completePackageName = (packageName == null) ? "systeminfo" : packageName + "_systeminfo"; - ExportHandleType systemInfoHandle = exportStore.exportSystem(completePackageName); - exportHandles.add(systemInfoHandle); - } - - // export each store - for (Properties store : stores) - { - // retrieve store reference to export - String storeRefStr = (String)store.get(STOREREF_KEY); - if (storeRefStr == null || storeRefStr.length() == 0) - { - throw new ExporterException("Store Reference has not been provided."); - } - StoreRef storeRef = new StoreRef(storeRefStr); - - // retrieve package name to export into - String storePackageName = (String)store.get(PACKAGENAME_KEY); - if (storePackageName == null || storePackageName.length() == 0) - { - storePackageName = storeRef.getIdentifier(); - } - String completePackageName = (packageName == null) ? storePackageName : packageName + "_" + storePackageName; - - // retrieve included paths (optional) - // note: the default exporter will currently include parents and children, relative to the path (to support bootstrap import of Dynamic Models) - String includedPathsStr = (String)store.get(INCLUDED_PATHS); - String[] includedPaths = (includedPathsStr != null ? includedPathsStr.split(",\\s*") : null); - - // now export - // NOTE: For now, do not provide exporter progress - ExporterCrawlerParameters exportParameters = getExportParameters(storeRef, includedPaths); - ExportHandleType exportHandle = exportStore.exportStore(exportParameters, completePackageName, null); - exportHandles.add(exportHandle); - } - - return exportHandles; - } - - - /** - * Get export parameters for exporting a complete store - * - * @param storeRef store reference to export - * @return the parameters for exporting the complete store - */ - private ExporterCrawlerParameters getExportParameters(StoreRef storeRef, String[] includedPaths) - { - ExporterCrawlerParameters parameters = new ExporterCrawlerParameters(); - parameters.setExportFrom(new Location(storeRef)); - parameters.setCrawlSelf(true); - parameters.setCrawlChildNodes(true); - parameters.setCrawlContent(true); - parameters.setCrawlAssociations(true); - parameters.setCrawlNullProperties(true); - parameters.setExcludeNamespaceURIs(new String[] {}); - parameters.setIncludedPaths(includedPaths); - parameters.setReferenceType(ReferenceType.NODEREF); - return parameters; - } - - - /** - * Export a Store to a temporary file - * - * @author davidc - */ - private class TempFileExporter implements ExportStore - { - /* - * (non-Javadoc) - * @see org.alfresco.repo.exporter.RepositoryExporterComponent.ExportStore#exportStore(org.alfresco.service.cmr.view.ExporterCrawlerParameters, java.lang.String, org.alfresco.service.cmr.view.Exporter) - */ - public FileExportHandle exportStore(ExporterCrawlerParameters exportParameters, String packageName, Exporter progress) - { - // create a temporary file to hold the acp export - File systemTempDir = TempFileProvider.getSystemTempDir(); - File tempFile = TempFileProvider.createTempFile("repoExp" + packageName, "." + ACPExportPackageHandler.ACP_EXTENSION, systemTempDir); - - // create acp export handler around the temp file - File dataFile = new File(packageName); - File contentDir = new File(packageName); - try - { - OutputStream outputStream = new FileOutputStream(tempFile); - ACPExportPackageHandler acpHandler = new ACPExportPackageHandler(outputStream, dataFile, contentDir, mimetypeService); - - // export the store - exporterService.exportView(acpHandler, exportParameters, progress); - } - catch(FileNotFoundException e) - { - tempFile.delete(); - throw new ExporterException("Failed to create temporary file for holding export of store " + exportParameters.getExportFrom().getStoreRef()); - } - - // return handle onto temp file - FileExportHandle handle = new FileExportHandle(); - handle.storeRef = exportParameters.getExportFrom().getStoreRef(); - handle.packageName = packageName; - handle.mimeType = MimetypeMap.MIMETYPE_ACP; - handle.exportFile = tempFile; - return handle; - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.exporter.RepositoryExporterComponent.ExportStore#exportSystem() - */ - public FileExportHandle exportSystem(String packageName) - { - // create a temporary file to hold the system info export - File systemTempDir = TempFileProvider.getSystemTempDir(); - File tempFile = TempFileProvider.createTempFile("repoExpSystemInfo", ".xml", systemTempDir); - - try - { - OutputStream outputStream = new FileOutputStream(tempFile); - systemExporterImporter.exportSystem(outputStream); - } - catch(FileNotFoundException e) - { - tempFile.delete(); - throw new ExporterException("Failed to create temporary file for holding export of system info"); - } - - // return handle onto temp file - FileExportHandle handle = new FileExportHandle(); - handle.storeRef = null; - handle.packageName = packageName; - handle.mimeType = MimetypeMap.MIMETYPE_XML; - handle.exportFile = tempFile; - return handle; - } - }; - - - /** - * Export a Store to a file in a specified folder - * - * @author davidc - */ - private class FileExporter implements ExportStore - { - private File directoryDestination; - - /** - * Construct - * - * @param directoryDestination destination file system folder to create export file - */ - public FileExporter(File directoryDestination) - { - this.directoryDestination = directoryDestination; - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.exporter.RepositoryExporterComponent.ExportStore#exportStore(org.alfresco.service.cmr.view.ExporterCrawlerParameters, java.lang.String, org.alfresco.service.cmr.view.Exporter) - */ - public FileExportHandle exportStore(ExporterCrawlerParameters exportParameters, String packageName, Exporter progress) - { - // create a file to hold the acp export - File file = new File(directoryDestination, packageName + "." + ACPExportPackageHandler.ACP_EXTENSION); - - // create acp export handler around the temp file - File dataFile = new File(packageName); - File contentDir = new File(packageName); - try - { - OutputStream outputStream = new FileOutputStream(file); - ACPExportPackageHandler acpHandler = new ACPExportPackageHandler(outputStream, dataFile, contentDir, mimetypeService); - - // export the store - exporterService.exportView(acpHandler, exportParameters, progress); - } - catch(FileNotFoundException e) - { - file.delete(); - throw new ExporterException("Failed to create file " + file.getAbsolutePath() + " for holding the export of store " + exportParameters.getExportFrom().getStoreRef()); - } - - // return handle onto temp file - FileExportHandle handle = new FileExportHandle(); - handle.storeRef = exportParameters.getExportFrom().getStoreRef(); - handle.packageName = packageName; - handle.mimeType = MimetypeMap.MIMETYPE_ACP; - handle.exportFile = file; - return handle; - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.exporter.RepositoryExporterComponent.ExportStore#exportSystem() - */ - public FileExportHandle exportSystem(String packageName) - { - // create a temporary file to hold the system info export - File tempFile = TempFileProvider.createTempFile("repoExpSystemInfo", ".xml"); - - try - { - OutputStream outputStream = new FileOutputStream(tempFile); - systemExporterImporter.exportSystem(outputStream); - } - catch(FileNotFoundException e) - { - tempFile.delete(); - throw new ExporterException("Failed to create temporary file for holding export of system info"); - } - - // return handle onto temp file - FileExportHandle handle = new FileExportHandle(); - handle.storeRef = null; - handle.packageName = packageName; - handle.mimeType = MimetypeMap.MIMETYPE_XML; - handle.exportFile = tempFile; - return handle; - } - }; - - -} +package org.alfresco.repo.exporter; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.springframework.extensions.surf.util.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.importer.system.SystemExporterImporter; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MimetypeService; +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.view.Exporter; +import org.alfresco.service.cmr.view.ExporterCrawlerParameters; +import org.alfresco.service.cmr.view.ExporterException; +import org.alfresco.service.cmr.view.ExporterService; +import org.alfresco.service.cmr.view.Location; +import org.alfresco.service.cmr.view.ReferenceType; +import org.alfresco.service.cmr.view.RepositoryExporterService; +import org.alfresco.service.namespace.QName; +import org.springframework.extensions.surf.util.ParameterCheck; +import org.alfresco.util.TempFileProvider; + + +/** + * Full Repository Export Service + * + * @author davidc + */ +public class RepositoryExporterComponent implements RepositoryExporterService +{ + private static final String STOREREF_KEY = "storeRef"; + private static final String PACKAGENAME_KEY = "packageName"; + private static final String INCLUDED_PATHS = "includedPaths"; + + // component dependencies + private ExporterService exporterService; + private MimetypeService mimetypeService; + private FileFolderService fileFolderService; + private SystemExporterImporter systemExporterImporter; + private NodeService nodeService; + private List exportStores; + + + public void setExporterService(ExporterService exporterService) + { + this.exporterService = exporterService; + } + + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setSystemExporter(SystemExporterImporter systemExporterImporter) + { + this.systemExporterImporter = systemExporterImporter; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setStores(List exportStores) + { + this.exportStores = exportStores; + } + + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.RepositoryExporterService#export() + */ + public FileExportHandle[] export(String packageName) + { + List exportHandles = exportStores(exportStores, packageName, new TempFileExporter()); + return exportHandles.toArray(new FileExportHandle[exportHandles.size()]); + } + + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.RepositoryExporterService#export(java.io.File) + */ + public FileExportHandle[] export(File directoryDestination, String packageName) + { + ParameterCheck.mandatory("directoryDestination", directoryDestination); + if (!directoryDestination.isDirectory()) + { + throw new ExporterException("Export location " + directoryDestination.getAbsolutePath() + " is not a directory"); + } + + List exportHandles = exportStores(exportStores, packageName, new FileExporter(directoryDestination)); + return exportHandles.toArray(new FileExportHandle[exportHandles.size()]); + } + + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.RepositoryExporterService#export(org.alfresco.service.cmr.repository.NodeRef) + */ + public RepositoryExportHandle[] export(NodeRef repositoryDestination, String packageName) + { + ParameterCheck.mandatory("repositoryDestination", repositoryDestination); + FileInfo destInfo = fileFolderService.getFileInfo(repositoryDestination); + if (destInfo == null || !destInfo.isFolder()) + { + throw new ExporterException("Repository destination " + repositoryDestination + " is not a folder."); + } + + List exportHandles = exportStores(exportStores, packageName, new TempFileExporter()); + Map mimetypeExtensions = mimetypeService.getExtensionsByMimetype(); + List repoExportHandles = new ArrayList(exportHandles.size()); + for (FileExportHandle exportHandle : exportHandles) + { + String name = exportHandle.packageName + "." + mimetypeExtensions.get(exportHandle.mimeType); + String title = exportHandle.packageName; + String description; + if (exportHandle.storeRef != null) + { + description = I18NUtil.getMessage("export.store.package.description", new Object[] { exportHandle.storeRef.toString() }); + } + else + { + description = I18NUtil.getMessage("export.generic.package.description"); + } + + NodeRef repoExportFile = addExportFile(repositoryDestination, name, title, description, exportHandle.mimeType, exportHandle.exportFile); + RepositoryExportHandle handle = new RepositoryExportHandle(); + handle.storeRef = exportHandle.storeRef; + handle.packageName = exportHandle.packageName; + handle.mimeType = exportHandle.mimeType; + handle.exportFile = repoExportFile; + repoExportHandles.add(handle); + + // delete temporary export file + exportHandle.exportFile.delete(); + } + + return repoExportHandles.toArray(new RepositoryExportHandle[repoExportHandles.size()]); + } + + + /** + * Add a file system based .acp file into the repository + * + * @param repoDestination location within repository to place .acp file + * @param name name + * @param title title + * @param description description + * @param mimeType mime type + * @param exportFile the .acp file + * @return node reference to import .acp file + */ + private NodeRef addExportFile(NodeRef repoDestination, String name, String title, String description, String mimeType, File exportFile) + { + // + // import temp file into repository + // + + // determine if file already exists + List paths = new ArrayList(); + paths.add(name); + try + { + FileInfo fileInfo = fileFolderService.resolveNamePath(repoDestination, paths); + // Note: file already exists - delete + fileFolderService.delete(fileInfo.getNodeRef()); + } + catch (org.alfresco.service.cmr.model.FileNotFoundException e) + { + // Note: file does not exist - no need to delete + } + + // create acp file in repository + NodeRef exportFileNodeRef = null; + try + { + FileInfo fileInfo = fileFolderService.create(repoDestination, name, ContentModel.TYPE_CONTENT); + ContentWriter writer = fileFolderService.getWriter(fileInfo.getNodeRef()); + writer.setMimetype(mimeType); + writer.putContent(exportFile); + exportFileNodeRef = fileInfo.getNodeRef(); + + // add a title for Web Client viewing + Map titledProps = new HashMap(3, 1.0f); + titledProps.put(ContentModel.PROP_TITLE, title); + titledProps.put(ContentModel.PROP_DESCRIPTION, description); + nodeService.addAspect(exportFileNodeRef, ContentModel.ASPECT_TITLED, titledProps); + + } + catch (FileExistsException e) + { + // Note: shouldn't get here + } + + return exportFileNodeRef; + } + + + /** + * Contract for exporting a repository + * + * @author davidc + * + * @param + */ + private interface ExportStore + { + public ExportHandleType exportStore(ExporterCrawlerParameters exportParameters, String packageName, Exporter progress); + + public ExportHandleType exportSystem(String packageName); + } + + + /** + * Enumerate list of pre-configured Stores and export one by one + * + * @param stores the list of stores to export + * @param packageName package name + * @param exportStore the exporter call-back for handling the actual export + * @return the list export file handles + */ + private List exportStores(List stores, String packageName, ExportStore exportStore) + { + List exportHandles = new ArrayList(stores.size() +1); + + // export repository system info + { + String completePackageName = (packageName == null) ? "systeminfo" : packageName + "_systeminfo"; + ExportHandleType systemInfoHandle = exportStore.exportSystem(completePackageName); + exportHandles.add(systemInfoHandle); + } + + // export each store + for (Properties store : stores) + { + // retrieve store reference to export + String storeRefStr = (String)store.get(STOREREF_KEY); + if (storeRefStr == null || storeRefStr.length() == 0) + { + throw new ExporterException("Store Reference has not been provided."); + } + StoreRef storeRef = new StoreRef(storeRefStr); + + // retrieve package name to export into + String storePackageName = (String)store.get(PACKAGENAME_KEY); + if (storePackageName == null || storePackageName.length() == 0) + { + storePackageName = storeRef.getIdentifier(); + } + String completePackageName = (packageName == null) ? storePackageName : packageName + "_" + storePackageName; + + // retrieve included paths (optional) + // note: the default exporter will currently include parents and children, relative to the path (to support bootstrap import of Dynamic Models) + String includedPathsStr = (String)store.get(INCLUDED_PATHS); + String[] includedPaths = (includedPathsStr != null ? includedPathsStr.split(",\\s*") : null); + + // now export + // NOTE: For now, do not provide exporter progress + ExporterCrawlerParameters exportParameters = getExportParameters(storeRef, includedPaths); + ExportHandleType exportHandle = exportStore.exportStore(exportParameters, completePackageName, null); + exportHandles.add(exportHandle); + } + + return exportHandles; + } + + + /** + * Get export parameters for exporting a complete store + * + * @param storeRef store reference to export + * @return the parameters for exporting the complete store + */ + private ExporterCrawlerParameters getExportParameters(StoreRef storeRef, String[] includedPaths) + { + ExporterCrawlerParameters parameters = new ExporterCrawlerParameters(); + parameters.setExportFrom(new Location(storeRef)); + parameters.setCrawlSelf(true); + parameters.setCrawlChildNodes(true); + parameters.setCrawlContent(true); + parameters.setCrawlAssociations(true); + parameters.setCrawlNullProperties(true); + parameters.setExcludeNamespaceURIs(new String[] {}); + parameters.setIncludedPaths(includedPaths); + parameters.setReferenceType(ReferenceType.NODEREF); + return parameters; + } + + + /** + * Export a Store to a temporary file + * + * @author davidc + */ + private class TempFileExporter implements ExportStore + { + /* + * (non-Javadoc) + * @see org.alfresco.repo.exporter.RepositoryExporterComponent.ExportStore#exportStore(org.alfresco.service.cmr.view.ExporterCrawlerParameters, java.lang.String, org.alfresco.service.cmr.view.Exporter) + */ + public FileExportHandle exportStore(ExporterCrawlerParameters exportParameters, String packageName, Exporter progress) + { + // create a temporary file to hold the acp export + File systemTempDir = TempFileProvider.getSystemTempDir(); + File tempFile = TempFileProvider.createTempFile("repoExp" + packageName, "." + ACPExportPackageHandler.ACP_EXTENSION, systemTempDir); + + // create acp export handler around the temp file + File dataFile = new File(packageName); + File contentDir = new File(packageName); + try + { + OutputStream outputStream = new FileOutputStream(tempFile); + ACPExportPackageHandler acpHandler = new ACPExportPackageHandler(outputStream, dataFile, contentDir, mimetypeService); + + // export the store + exporterService.exportView(acpHandler, exportParameters, progress); + } + catch(FileNotFoundException e) + { + tempFile.delete(); + throw new ExporterException("Failed to create temporary file for holding export of store " + exportParameters.getExportFrom().getStoreRef()); + } + + // return handle onto temp file + FileExportHandle handle = new FileExportHandle(); + handle.storeRef = exportParameters.getExportFrom().getStoreRef(); + handle.packageName = packageName; + handle.mimeType = MimetypeMap.MIMETYPE_ACP; + handle.exportFile = tempFile; + return handle; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.exporter.RepositoryExporterComponent.ExportStore#exportSystem() + */ + public FileExportHandle exportSystem(String packageName) + { + // create a temporary file to hold the system info export + File systemTempDir = TempFileProvider.getSystemTempDir(); + File tempFile = TempFileProvider.createTempFile("repoExpSystemInfo", ".xml", systemTempDir); + + try + { + OutputStream outputStream = new FileOutputStream(tempFile); + systemExporterImporter.exportSystem(outputStream); + } + catch(FileNotFoundException e) + { + tempFile.delete(); + throw new ExporterException("Failed to create temporary file for holding export of system info"); + } + + // return handle onto temp file + FileExportHandle handle = new FileExportHandle(); + handle.storeRef = null; + handle.packageName = packageName; + handle.mimeType = MimetypeMap.MIMETYPE_XML; + handle.exportFile = tempFile; + return handle; + } + }; + + + /** + * Export a Store to a file in a specified folder + * + * @author davidc + */ + private class FileExporter implements ExportStore + { + private File directoryDestination; + + /** + * Construct + * + * @param directoryDestination destination file system folder to create export file + */ + public FileExporter(File directoryDestination) + { + this.directoryDestination = directoryDestination; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.exporter.RepositoryExporterComponent.ExportStore#exportStore(org.alfresco.service.cmr.view.ExporterCrawlerParameters, java.lang.String, org.alfresco.service.cmr.view.Exporter) + */ + public FileExportHandle exportStore(ExporterCrawlerParameters exportParameters, String packageName, Exporter progress) + { + // create a file to hold the acp export + File file = new File(directoryDestination, packageName + "." + ACPExportPackageHandler.ACP_EXTENSION); + + // create acp export handler around the temp file + File dataFile = new File(packageName); + File contentDir = new File(packageName); + try + { + OutputStream outputStream = new FileOutputStream(file); + ACPExportPackageHandler acpHandler = new ACPExportPackageHandler(outputStream, dataFile, contentDir, mimetypeService); + + // export the store + exporterService.exportView(acpHandler, exportParameters, progress); + } + catch(FileNotFoundException e) + { + file.delete(); + throw new ExporterException("Failed to create file " + file.getAbsolutePath() + " for holding the export of store " + exportParameters.getExportFrom().getStoreRef()); + } + + // return handle onto temp file + FileExportHandle handle = new FileExportHandle(); + handle.storeRef = exportParameters.getExportFrom().getStoreRef(); + handle.packageName = packageName; + handle.mimeType = MimetypeMap.MIMETYPE_ACP; + handle.exportFile = file; + return handle; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.exporter.RepositoryExporterComponent.ExportStore#exportSystem() + */ + public FileExportHandle exportSystem(String packageName) + { + // create a temporary file to hold the system info export + File tempFile = TempFileProvider.createTempFile("repoExpSystemInfo", ".xml"); + + try + { + OutputStream outputStream = new FileOutputStream(tempFile); + systemExporterImporter.exportSystem(outputStream); + } + catch(FileNotFoundException e) + { + tempFile.delete(); + throw new ExporterException("Failed to create temporary file for holding export of system info"); + } + + // return handle onto temp file + FileExportHandle handle = new FileExportHandle(); + handle.storeRef = null; + handle.packageName = packageName; + handle.mimeType = MimetypeMap.MIMETYPE_XML; + handle.exportFile = tempFile; + return handle; + } + }; + + +} diff --git a/source/java/org/alfresco/repo/favourites/FavouritesServiceImpl.java b/source/java/org/alfresco/repo/favourites/FavouritesServiceImpl.java index 1234ff50fe..8a4ec4c6ba 100644 --- a/source/java/org/alfresco/repo/favourites/FavouritesServiceImpl.java +++ b/source/java/org/alfresco/repo/favourites/FavouritesServiceImpl.java @@ -1,962 +1,962 @@ -package org.alfresco.repo.favourites; - -import java.io.Serializable; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.TreeMap; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.events.types.ActivityEvent; -import org.alfresco.events.types.Event; -import org.alfresco.model.ContentModel; -import org.alfresco.query.PageDetails; -import org.alfresco.query.PagingRequest; -import org.alfresco.query.PagingResults; -import org.alfresco.repo.Client; -import org.alfresco.repo.Client.ClientType; -import org.alfresco.repo.events.EventPreparator; -import org.alfresco.repo.events.EventPublisher; -import org.alfresco.repo.favourites.PersonFavourite.PersonFavouriteKey; -import org.alfresco.repo.policy.ClassPolicy; -import org.alfresco.repo.policy.ClassPolicyDelegate; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.repo.site.SiteModel; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.favourites.FavouritesService; -import org.alfresco.service.cmr.preference.PreferenceService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.ISO8601DateFormat; -import org.alfresco.util.Pair; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -/** - * Favourites service implementation that uses the PreferencesService for persisting favourites. - * - * Unfortunately, we are tied to the PreferencesService and to the preference names and data structure because Share uses the PreferenceService directly. - * - * @author steveglover - */ -public class FavouritesServiceImpl implements FavouritesService, InitializingBean -{ - private static final Log logger = LogFactory.getLog(FavouritesServiceImpl.class); - - private Map prefKeys; - - private PreferenceService preferenceService; - private NodeService nodeService; - private DictionaryService dictionaryService; - private SiteService siteService; - private PolicyComponent policyComponent; - private PermissionService permissionService; - private PersonService personService; - private EventPublisher eventPublisher; - - /** Authentication Service */ - private AuthenticationContext authenticationContext; - - private ClassPolicyDelegate onAddFavouriteDelegate; - private ClassPolicyDelegate onRemoveFavouriteDelegate; - - private Collator collator = Collator.getInstance(); - - public interface OnAddFavouritePolicy extends ClassPolicy - { - public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onAddfavourite"); - - /** - * Called after a node has been favourited - * - * @param userName the username of the person who favourited the node - * @param nodeRef the node which was favourited - */ - public void onAddFavourite(String userName, NodeRef nodeRef); - } - - public interface OnRemoveFavouritePolicy extends ClassPolicy - { - public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onRemovefavourite"); - - /** - * Called after a favourite has been removed - * - * @param userName the username of the person who favourited the node - * @param nodeRef the node that was un-favourited - */ - public void onRemoveFavourite(String userName, NodeRef nodeRef); - } - - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - public void setAuthenticationContext(AuthenticationContext authenticationContext) - { - this.authenticationContext = authenticationContext; - } - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public void setPreferenceService(PreferenceService preferenceService) - { - this.preferenceService = preferenceService; - } - - private static class PrefKeys - { - private String sharePrefKey; - private String alfrescoPrefKey; - - public PrefKeys(String sharePrefKey, String alfrescoPrefKey) - { - super(); - this.sharePrefKey = sharePrefKey; - this.alfrescoPrefKey = alfrescoPrefKey; - } - - public String getSharePrefKey() - { - return sharePrefKey; - } - - public String getAlfrescoPrefKey() - { - return alfrescoPrefKey; - } - } - - @Override - public void afterPropertiesSet() throws Exception - { - this.prefKeys = new HashMap(); - this.prefKeys.put(Type.SITE, new PrefKeys("org.alfresco.share.sites.favourites.", "org.alfresco.ext.sites.favourites.")); - this.prefKeys.put(Type.FILE, new PrefKeys("org.alfresco.share.documents.favourites", "org.alfresco.ext.documents.favourites.")); - this.prefKeys.put(Type.FOLDER, new PrefKeys("org.alfresco.share.folders.favourites", "org.alfresco.ext.folders.favourites.")); - } - - public void init() - { - this.onAddFavouriteDelegate = policyComponent.registerClassPolicy(OnAddFavouritePolicy.class); - this.onRemoveFavouriteDelegate = policyComponent.registerClassPolicy(OnRemoveFavouritePolicy.class); - } - - private PrefKeys getPrefKeys(Type type) - { - PrefKeys prefKey = prefKeys.get(type); - return prefKey; - } - - private boolean removeFavouriteSite(String userName, NodeRef nodeRef) - { - PrefKeys prefKeys = getPrefKeys(Type.SITE); - boolean exists = false; - - SiteInfo siteInfo = siteService.getSite(nodeRef); - if(siteInfo != null) - { - StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getSharePrefKey()); - sitePrefKeyBuilder.append(siteInfo.getShortName()); - String sitePrefKey = sitePrefKeyBuilder.toString(); - - String siteFavouritedKey = siteFavouritedKey(siteInfo); - - exists = preferenceService.getPreference(userName, siteFavouritedKey) != null; - preferenceService.clearPreferences(userName, sitePrefKey); - } - else - { - throw new IllegalArgumentException("NodeRef " + nodeRef + " is not a site"); - } - - return exists; - } - - private String siteFavouritedKey(SiteInfo siteInfo) - { - PrefKeys prefKeys = getPrefKeys(Type.SITE); - - StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getSharePrefKey()); - sitePrefKeyBuilder.append(siteInfo.getShortName()); - String sitePrefKey = sitePrefKeyBuilder.toString(); - - String favouritedKey = sitePrefKey; - return favouritedKey; - } - - private String siteCreatedAtKey(SiteInfo siteInfo) - { - PrefKeys prefKeys = getPrefKeys(Type.SITE); - - StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getAlfrescoPrefKey()); - sitePrefKeyBuilder.append(siteInfo.getShortName()); - String sitePrefKey = sitePrefKeyBuilder.toString(); - - StringBuilder createdAtKeyBuilder = new StringBuilder(sitePrefKey); - createdAtKeyBuilder.append(".createdAt"); - String createdAtKey = createdAtKeyBuilder.toString(); - return createdAtKey; - } - - private Comparator getComparator(final List> sortProps) - { - Comparator comparator = new Comparator() - { - @Override - public int compare(PersonFavouriteKey o1, PersonFavouriteKey o2) - { - int ret = 0; - for(Pair sort : sortProps) - { - FavouritesService.SortFields field = sort.getFirst(); - Boolean ascending = sort.getSecond(); - if(field.equals(FavouritesService.SortFields.username)) - { - if(ascending) - { - ret = collator.compare(o1.getUserName(), o2.getUserName()); - } - else - { - ret = o2.getUserName().compareTo(o1.getUserName()); - } - - if(ret != 0) - { - break; - } - } - else if(field.equals(FavouritesService.SortFields.type)) - { - if(ascending) - { - ret = o1.getType().compareTo(o2.getType()); - } - else - { - ret = o2.getType().compareTo(o1.getType()); - } - - if(ret != 0) - { - break; - } - } - else if(field.equals(FavouritesService.SortFields.createdAt)) - { - if(ascending) - { - if(o1.getCreatedAt() != null && o2.getCreatedAt() != null) - { - ret = o1.getCreatedAt().compareTo(o2.getCreatedAt()); - } - } - else - { - if(o1.getCreatedAt() != null && o2.getCreatedAt() != null) - { - ret = o2.getCreatedAt().compareTo(o1.getCreatedAt()); - } - } - - if(ret != 0) - { - break; - } - } - else if(field.equals(FavouritesService.SortFields.title)) - { - if(ascending) - { - if(o1.getTitle() != null && o2.getTitle() != null) - { - ret = collator.compare(o1.getTitle(), o2.getTitle()); - } - } - else - { - if(o1.getTitle() != null && o2.getTitle() != null) - { - ret = collator.compare(o2.getTitle(), o1.getTitle()); - } - } - - if(ret != 0) - { - break; - } - } - } - - if(ret == 0) - { - // some favourites may not have a createdAt value, rendering this comparator less selective. - // If the favourites are still regarded as the same, differentiate on nodeRef. - ret = o1.getNodeRef().toString().compareTo(o2.getNodeRef().toString()); - } - - return ret; - } - }; - return comparator; - } - - private PersonFavourite addFavouriteSite(String userName, NodeRef nodeRef) - { - PersonFavourite favourite = null; - - SiteInfo siteInfo = siteService.getSite(nodeRef); - if(siteInfo != null) - { - favourite = getFavouriteSite(userName, siteInfo); - if(favourite == null) - { - Map preferences = new HashMap(1); - - String siteFavouritedKey = siteFavouritedKey(siteInfo); - preferences.put(siteFavouritedKey, Boolean.TRUE); - - // ISO8601 string format: PreferenceService works with strings only for dates it seems - String siteCreatedAtKey = siteCreatedAtKey(siteInfo); - Date createdAt = new Date(); - String createdAtStr = ISO8601DateFormat.format(createdAt); - preferences.put(siteCreatedAtKey, createdAtStr); - - preferenceService.setPreferences(userName, preferences); - - favourite = new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteInfo.getTitle(), createdAt); - - QName nodeClass = nodeService.getType(nodeRef); - OnAddFavouritePolicy policy = onAddFavouriteDelegate.get(nodeRef, nodeClass); - policy.onAddFavourite(userName, nodeRef); - } - } - else - { - // shouldn't happen, getType recognizes it as a site or subtype - logger.warn("Unable to get site for " + nodeRef); - } - - return favourite; - } - - private PersonFavourite getFavouriteSite(String userName, SiteInfo siteInfo) - { - PersonFavourite favourite = null; - - String siteFavouritedKey = siteFavouritedKey(siteInfo); - String siteCreatedAtKey = siteCreatedAtKey(siteInfo); - - Boolean isFavourited = false; - Serializable s = preferenceService.getPreference(userName, siteFavouritedKey); - if(s != null) - { - if(s instanceof String) - { - isFavourited = Boolean.valueOf((String)s); - } - else if(s instanceof Boolean) - { - isFavourited = (Boolean)s; - } - else - { - throw new AlfrescoRuntimeException("Unexpected favourites preference value"); - } - } - - if(isFavourited) - { - String createdAtStr = (String)preferenceService.getPreference(userName, siteCreatedAtKey); - Date createdAt = (createdAtStr == null ? null : ISO8601DateFormat.parse(createdAtStr)); - - favourite = new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteInfo.getTitle(), createdAt); - } - - return favourite; - } - - private boolean isFavouriteSite(String userName, NodeRef nodeRef) - { - Boolean isFavourited = Boolean.FALSE; - SiteInfo siteInfo = siteService.getSite(nodeRef); - if(siteInfo != null) - { - String favouritedPrefKey = siteFavouritedKey(siteInfo); - Serializable value = preferenceService.getPreference(userName, favouritedPrefKey); - - if(value != null) - { - if(value instanceof String) - { - isFavourited = Boolean.valueOf((String)value); - } - else if(value instanceof Boolean) - { - isFavourited = (Boolean)value; - } - else - { - throw new AlfrescoRuntimeException("Unexpected favourites preference value"); - } - } - } - else - { - throw new IllegalArgumentException("NodeRef " + nodeRef + " is not a site"); - } - - return isFavourited.booleanValue(); - } - - private void updateFavouriteNodes(String userName, Type type, Map favouriteNodes) - { - PrefKeys prefKeys = getPrefKeys(type); - - Map preferences = new HashMap(1); - - StringBuilder values = new StringBuilder(); - for(PersonFavourite node : favouriteNodes.values()) - { - NodeRef nodeRef = node.getNodeRef(); - - values.append(nodeRef.toString()); - values.append(","); - - // ISO8601 string format: PreferenceService works with strings only for dates it seems - StringBuilder sb = new StringBuilder(prefKeys.getAlfrescoPrefKey()); - sb.append(nodeRef.toString()); - sb.append(".createdAt"); - String createdAtKey = sb.toString(); - Date createdAt = node.getCreatedAt(); - if(createdAt != null) - { - String createdAtStr = ISO8601DateFormat.format(createdAt); - preferences.put(createdAtKey, createdAtStr); - } - } - - if(values.length() > 1) - { - values.delete(values.length() - 1, values.length()); - } - - preferences.put(prefKeys.getSharePrefKey(), values.toString()); - preferenceService.setPreferences(userName, preferences); - } - - private Map getFavouriteNodes(String userName, Type type) - { - PrefKeys prefKeys = getPrefKeys(type); - Map favouriteNodes = Collections.emptyMap(); - Map prefs = preferenceService.getPreferences(userName, prefKeys.getSharePrefKey()); - String nodes = (String)prefs.get(prefKeys.getSharePrefKey()); - if(nodes != null) - { - favouriteNodes = extractFavouriteNodes(userName, type, nodes); - } - else - { - favouriteNodes = new HashMap(); - } - - return favouriteNodes; - } - - /* - * Extract favourite nodes of the given type from the comma-separated list in "nodes". - */ - private Map extractFavouriteNodes(String userName, Type type, String nodes) - { - PrefKeys prefKeys = getPrefKeys(type); - Map favouriteNodes = new HashMap(); - - StringTokenizer st = new StringTokenizer(nodes, ","); - while(st.hasMoreTokens()) - { - String nodeRefStr = st.nextToken(); - nodeRefStr = nodeRefStr.trim(); - if(!NodeRef.isNodeRef((String)nodeRefStr)) - { - continue; - } - - NodeRef nodeRef = new NodeRef((String)nodeRefStr); - - if(!nodeService.exists(nodeRef)) - { - continue; - } - - if(permissionService.hasPermission(nodeRef, PermissionService.READ_PROPERTIES) == AccessStatus.DENIED) - { - continue; - } - - // get createdAt for this favourited node - // use ISO8601 - StringBuilder builder = new StringBuilder(prefKeys.getAlfrescoPrefKey()); - builder.append(nodeRef.toString()); - builder.append(".createdAt"); - String prefKey = builder.toString(); - String createdAtStr = (String)preferenceService.getPreference(userName, prefKey); - Date createdAt = (createdAtStr != null ? ISO8601DateFormat.parse(createdAtStr): null); - - String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - - PersonFavourite personFavourite = new PersonFavourite(userName, nodeRef, type, name, createdAt); - PersonFavouriteKey key = personFavourite.getKey(); - favouriteNodes.put(key, personFavourite); - } - - return favouriteNodes; - } - - private void extractFavouriteSite(String userName, Type type, Map sortedFavouriteNodes, Map preferences, String key) - { - // preference value indicates whether the site has been favourited - Serializable pref = preferences.get(key); - Boolean isFavourite = (Boolean)pref; - if(isFavourite) - { - PrefKeys sitePrefKeys = getPrefKeys(Type.SITE); - int length = sitePrefKeys.getSharePrefKey().length(); - String siteId = key.substring(length); - - try - { - SiteInfo siteInfo = siteService.getSite(siteId); - if(siteInfo != null) - { - StringBuilder builder = new StringBuilder(sitePrefKeys.getAlfrescoPrefKey()); - builder.append(siteId); - builder.append(".createdAt"); - String createdAtPrefKey = builder.toString(); - String createdAtStr = (String)preferences.get(createdAtPrefKey); - Date createdAt = null; - if(createdAtStr != null) - { - createdAt = (createdAtStr != null ? ISO8601DateFormat.parse(createdAtStr): null); - } - PersonFavourite personFavourite = new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteId, createdAt); - sortedFavouriteNodes.put(personFavourite.getKey(), personFavourite); - } - } - catch(AccessDeniedException ex) - { - // the user no longer has access to this site, skip over the favourite - // TODO remove the favourite preference - return; - } - } - } - - private PersonFavourite getFavouriteDocumentOrFolder(String userName, Type type, NodeRef nodeRef) - { - PersonFavourite favourite = null; - - PrefKeys prefKeys = getPrefKeys(type); - Map preferences = preferenceService.getPreferences(userName, prefKeys.getSharePrefKey()); - String nodes = (String)preferences.get(prefKeys.getSharePrefKey()); - if(nodes != null) - { - Map favouriteNodes = extractFavouriteNodes(userName, type, nodes); - String title = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); - PersonFavouriteKey key = new PersonFavouriteKey(userName, title, type, nodeRef); - favourite = favouriteNodes.get(key); - } - return favourite; - } - - private PersonFavourite getPersonFavourite(String userName, Type type, NodeRef nodeRef) - { - PersonFavourite ret = null; - if(type.equals(Type.SITE)) - { - SiteInfo siteInfo = siteService.getSite(nodeRef); - if(siteInfo != null) - { - ret = getFavouriteSite(userName, siteInfo); - } - else - { - // shouldn't happen, getType recognizes it as a site or subtype - logger.warn("Unable to get site for " + nodeRef); - } - } - else if(type.equals(Type.FILE)) - { - ret = getFavouriteDocumentOrFolder(userName, type, nodeRef); - } - else if(type.equals(Type.FOLDER)) - { - ret = getFavouriteDocumentOrFolder(userName, type, nodeRef); - } - else - { - // shouldn't happen - throw new AlfrescoRuntimeException("Unexpected favourite type"); - } - - return ret; - } - - private PersonFavourite addFavouriteDocumentOrFolder(String userName, Type type, NodeRef nodeRef) - { - Map personFavourites = getFavouriteNodes(userName, type); - PersonFavourite personFavourite = getPersonFavourite(userName, type, nodeRef); - if(personFavourite == null) - { - Date createdAt = new Date(); - final String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - personFavourite = new PersonFavourite(userName, nodeRef, type, name, createdAt); - personFavourites.put(personFavourite.getKey(), personFavourite); - updateFavouriteNodes(userName, type, personFavourites); - - QName nodeClass = nodeService.getType(nodeRef); - final String finalRef = nodeRef.getId(); - final QName nodeType = nodeClass; - - eventPublisher.publishEvent(new EventPreparator(){ - @Override - public Event prepareEvent(String user, String networkId, String transactionId) - { - return new ActivityEvent("favorite.added", transactionId, networkId, user, finalRef, - null, nodeType==null?null:nodeType.toString(), Client.asType(ClientType.script), null, - name, null, 0l, null); - } - }); - - OnAddFavouritePolicy policy = onAddFavouriteDelegate.get(nodeRef, nodeClass); - policy.onAddFavourite(userName, nodeRef); - } - - return personFavourite; - } - - private boolean isFavouriteNode(String userName, Type type, NodeRef nodeRef) - { - Map personFavourites = getFavouriteNodes(userName, type); - PersonFavouriteKey personFavouriteKey = new PersonFavouriteKey(userName, null, type, nodeRef); - boolean isFavourite = personFavourites.containsKey(personFavouriteKey); - return isFavourite; - } - - public Type getType(NodeRef nodeRef) - { - Type favouriteType = null; - - QName type = nodeService.getType(nodeRef); - boolean isSite = dictionaryService.isSubClass(type, SiteModel.TYPE_SITE); - if(isSite) - { - favouriteType = Type.SITE; - } - else - { - boolean isContainer = (dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) && - !dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER)); - if(isContainer) - { - favouriteType = Type.FOLDER; - } - else - { - boolean isFile = dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT); - if(isFile) - { - favouriteType = Type.FILE; - } - } - } - - return favouriteType; - } - - private boolean removeFavouriteNode(String userName, Type type, NodeRef nodeRef) - { - boolean exists = false; - - Map personFavourites = getFavouriteNodes(userName, type); - - PersonFavouriteKey personFavouriteKey = new PersonFavouriteKey(userName, null, type, nodeRef); - PersonFavourite personFavourite = personFavourites.remove(personFavouriteKey); - exists = personFavourite != null; - updateFavouriteNodes(userName, type, personFavourites); - - QName nodeClass = nodeService.getType(nodeRef); - final String finalRef = nodeRef.getId(); - final QName nodeType = nodeClass; - - eventPublisher.publishEvent(new EventPreparator(){ - @Override - public Event prepareEvent(String user, String networkId, String transactionId) - { - return new ActivityEvent("favorite.removed", transactionId, networkId, user, finalRef, - null, nodeType==null?null:nodeType.toString(), Client.asType(ClientType.script), null, - null, null, 0l, null); - } - }); - - OnRemoveFavouritePolicy policy = onRemoveFavouriteDelegate.get(nodeRef, nodeClass); - policy.onRemoveFavourite(userName, nodeRef); - - return exists; - } - - @Override - public PersonFavourite addFavourite(String userName, NodeRef nodeRef) - { - PersonFavourite personFavourite = null; - - Type type = getType(nodeRef); - if(type == null) - { - throw new IllegalArgumentException("Cannot favourite this node"); - } - else if(type.equals(Type.FILE)) - { - personFavourite = addFavouriteDocumentOrFolder(userName, Type.FILE, nodeRef); - } - else if(type.equals(Type.FOLDER)) - { - personFavourite = addFavouriteDocumentOrFolder(userName, Type.FOLDER, nodeRef); - } - else if(type.equals(Type.SITE)) - { - personFavourite = addFavouriteSite(userName, nodeRef); - } - else - { - throw new IllegalArgumentException("Cannot favourite this node"); - } - - return personFavourite; - } - - @Override - public boolean removeFavourite(String userName, NodeRef nodeRef) - { - boolean exists = false; - - Type type = getType(nodeRef); - if(type == null) - { - throw new IllegalArgumentException("Cannot un-favourite this node"); - } - else if(type.equals(Type.FILE)) - { - exists = removeFavouriteNode(userName, type, nodeRef); - } - else if(type.equals(Type.FOLDER)) - { - exists = removeFavouriteNode(userName, type, nodeRef); - } - else if(type.equals(Type.SITE)) - { - exists = removeFavouriteSite(userName, nodeRef); - } - else - { - throw new IllegalArgumentException("Cannot un-favourite this node"); - } - - return exists; - } - - @Override - public boolean isFavourite(String userName, NodeRef nodeRef) - { - boolean isFavourite = false; - - Type type = getType(nodeRef); - if(type == null) - { - throw new IllegalArgumentException("Unsupported node type"); - } - else if(type.equals(Type.FILE)) - { - isFavourite = isFavouriteNode(userName, type, nodeRef); - } - else if(type.equals(Type.FOLDER)) - { - isFavourite = isFavouriteNode(userName, type, nodeRef); - } - else if(type.equals(Type.SITE)) - { - isFavourite = isFavouriteSite(userName, nodeRef); - } - else - { - throw new IllegalArgumentException("Unsupported node type"); - } - - return isFavourite; - } - - @Override - public PagingResults getPagedFavourites(String userName, Set types, - List> sortProps, PagingRequest pagingRequest) - { - // Get the user node reference - final NodeRef personNodeRef = personService.getPerson(userName); - if(personNodeRef == null) - { - throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because he/she does not exist."); - } - - boolean includeFiles = types.contains(Type.FILE); - boolean includeFolders = types.contains(Type.FOLDER); - boolean includeSites = types.contains(Type.SITE); - - String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); - if (authenticationContext.isSystemUserName(currentUserName) == true || - permissionService.hasPermission(personNodeRef, PermissionService.WRITE) == AccessStatus.ALLOWED || - userName.equals(currentUserName) == true) - { - // we may have more than one favourite that is considered the same w.r.t. the PersonFavourite comparator - final Map sortedFavouriteNodes = new TreeMap(getComparator(sortProps)); - - PrefKeys sitePrefKeys = getPrefKeys(Type.SITE); - PrefKeys documentsPrefKeys = getPrefKeys(Type.FILE); - PrefKeys foldersPrefKeys = getPrefKeys(Type.FOLDER); - - Map preferences = preferenceService.getPreferences(userName); - for(String key : preferences.keySet()) - { - if(includeFiles && key.startsWith(documentsPrefKeys.sharePrefKey)) - { - String nodes = (String)preferences.get(key); - if(nodes != null) - { - sortedFavouriteNodes.putAll(extractFavouriteNodes(userName, Type.FILE, nodes)); - } - } - else if(includeFolders && key.startsWith(foldersPrefKeys.sharePrefKey)) - { - String nodes = (String)preferences.get(key); - if(nodes != null) - { - sortedFavouriteNodes.putAll(extractFavouriteNodes(userName, Type.FOLDER, nodes)); - } - } - else if(includeSites && key.startsWith(sitePrefKeys.getSharePrefKey()) && !key.endsWith(".createdAt")) - { - // key is either of the form org.alfresco.share.sites.favourites..favourited or - // org.alfresco.share.sites.favourites. - extractFavouriteSite(userName, Type.SITE, sortedFavouriteNodes, preferences, key); - } - } - - int totalSize = sortedFavouriteNodes.size(); - final PageDetails pageDetails = PageDetails.getPageDetails(pagingRequest, totalSize); - - final List page = new ArrayList(pageDetails.getPageSize()); - Iterator it = sortedFavouriteNodes.values().iterator(); - for(int counter = 0; counter < pageDetails.getEnd() && it.hasNext(); counter++) - { - PersonFavourite favouriteNode = it.next(); - - if(counter < pageDetails.getSkipCount()) - { - continue; - } - - if(counter > pageDetails.getEnd() - 1) - { - break; - } - - page.add(favouriteNode); - } - - return new PagingResults() - { - @Override - public List getPage() - { - return page; - } - - @Override - public boolean hasMoreItems() - { - return pageDetails.hasMoreItems(); - } - - @Override - public Pair getTotalResultCount() - { - Integer total = Integer.valueOf(sortedFavouriteNodes.size()); - return new Pair(total, total); - } - - @Override - public String getQueryExecutionId() - { - return null; - } - }; - } - else - { - // The current user does not have sufficient permissions to update the preferences for this user - throw new AccessDeniedException("The current user " + currentUserName + " does not have sufficient permissions to get the favourites of the user " + userName); - } - } - - public PersonFavourite getFavourite(String userName, NodeRef nodeRef) - { - Type type = getType(nodeRef); - return getPersonFavourite(userName, type, nodeRef); - } - - public void setEventPublisher(EventPublisher eventPublisher) - { - this.eventPublisher = eventPublisher; - } -} +package org.alfresco.repo.favourites; + +import java.io.Serializable; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeMap; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.events.types.ActivityEvent; +import org.alfresco.events.types.Event; +import org.alfresco.model.ContentModel; +import org.alfresco.query.PageDetails; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.Client; +import org.alfresco.repo.Client.ClientType; +import org.alfresco.repo.events.EventPreparator; +import org.alfresco.repo.events.EventPublisher; +import org.alfresco.repo.favourites.PersonFavourite.PersonFavouriteKey; +import org.alfresco.repo.policy.ClassPolicy; +import org.alfresco.repo.policy.ClassPolicyDelegate; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.favourites.FavouritesService; +import org.alfresco.service.cmr.preference.PreferenceService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO8601DateFormat; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * Favourites service implementation that uses the PreferencesService for persisting favourites. + * + * Unfortunately, we are tied to the PreferencesService and to the preference names and data structure because Share uses the PreferenceService directly. + * + * @author steveglover + */ +public class FavouritesServiceImpl implements FavouritesService, InitializingBean +{ + private static final Log logger = LogFactory.getLog(FavouritesServiceImpl.class); + + private Map prefKeys; + + private PreferenceService preferenceService; + private NodeService nodeService; + private DictionaryService dictionaryService; + private SiteService siteService; + private PolicyComponent policyComponent; + private PermissionService permissionService; + private PersonService personService; + private EventPublisher eventPublisher; + + /** Authentication Service */ + private AuthenticationContext authenticationContext; + + private ClassPolicyDelegate onAddFavouriteDelegate; + private ClassPolicyDelegate onRemoveFavouriteDelegate; + + private Collator collator = Collator.getInstance(); + + public interface OnAddFavouritePolicy extends ClassPolicy + { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onAddfavourite"); + + /** + * Called after a node has been favourited + * + * @param userName the username of the person who favourited the node + * @param nodeRef the node which was favourited + */ + public void onAddFavourite(String userName, NodeRef nodeRef); + } + + public interface OnRemoveFavouritePolicy extends ClassPolicy + { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onRemovefavourite"); + + /** + * Called after a favourite has been removed + * + * @param userName the username of the person who favourited the node + * @param nodeRef the node that was un-favourited + */ + public void onRemoveFavourite(String userName, NodeRef nodeRef); + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setPreferenceService(PreferenceService preferenceService) + { + this.preferenceService = preferenceService; + } + + private static class PrefKeys + { + private String sharePrefKey; + private String alfrescoPrefKey; + + public PrefKeys(String sharePrefKey, String alfrescoPrefKey) + { + super(); + this.sharePrefKey = sharePrefKey; + this.alfrescoPrefKey = alfrescoPrefKey; + } + + public String getSharePrefKey() + { + return sharePrefKey; + } + + public String getAlfrescoPrefKey() + { + return alfrescoPrefKey; + } + } + + @Override + public void afterPropertiesSet() throws Exception + { + this.prefKeys = new HashMap(); + this.prefKeys.put(Type.SITE, new PrefKeys("org.alfresco.share.sites.favourites.", "org.alfresco.ext.sites.favourites.")); + this.prefKeys.put(Type.FILE, new PrefKeys("org.alfresco.share.documents.favourites", "org.alfresco.ext.documents.favourites.")); + this.prefKeys.put(Type.FOLDER, new PrefKeys("org.alfresco.share.folders.favourites", "org.alfresco.ext.folders.favourites.")); + } + + public void init() + { + this.onAddFavouriteDelegate = policyComponent.registerClassPolicy(OnAddFavouritePolicy.class); + this.onRemoveFavouriteDelegate = policyComponent.registerClassPolicy(OnRemoveFavouritePolicy.class); + } + + private PrefKeys getPrefKeys(Type type) + { + PrefKeys prefKey = prefKeys.get(type); + return prefKey; + } + + private boolean removeFavouriteSite(String userName, NodeRef nodeRef) + { + PrefKeys prefKeys = getPrefKeys(Type.SITE); + boolean exists = false; + + SiteInfo siteInfo = siteService.getSite(nodeRef); + if(siteInfo != null) + { + StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getSharePrefKey()); + sitePrefKeyBuilder.append(siteInfo.getShortName()); + String sitePrefKey = sitePrefKeyBuilder.toString(); + + String siteFavouritedKey = siteFavouritedKey(siteInfo); + + exists = preferenceService.getPreference(userName, siteFavouritedKey) != null; + preferenceService.clearPreferences(userName, sitePrefKey); + } + else + { + throw new IllegalArgumentException("NodeRef " + nodeRef + " is not a site"); + } + + return exists; + } + + private String siteFavouritedKey(SiteInfo siteInfo) + { + PrefKeys prefKeys = getPrefKeys(Type.SITE); + + StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getSharePrefKey()); + sitePrefKeyBuilder.append(siteInfo.getShortName()); + String sitePrefKey = sitePrefKeyBuilder.toString(); + + String favouritedKey = sitePrefKey; + return favouritedKey; + } + + private String siteCreatedAtKey(SiteInfo siteInfo) + { + PrefKeys prefKeys = getPrefKeys(Type.SITE); + + StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getAlfrescoPrefKey()); + sitePrefKeyBuilder.append(siteInfo.getShortName()); + String sitePrefKey = sitePrefKeyBuilder.toString(); + + StringBuilder createdAtKeyBuilder = new StringBuilder(sitePrefKey); + createdAtKeyBuilder.append(".createdAt"); + String createdAtKey = createdAtKeyBuilder.toString(); + return createdAtKey; + } + + private Comparator getComparator(final List> sortProps) + { + Comparator comparator = new Comparator() + { + @Override + public int compare(PersonFavouriteKey o1, PersonFavouriteKey o2) + { + int ret = 0; + for(Pair sort : sortProps) + { + FavouritesService.SortFields field = sort.getFirst(); + Boolean ascending = sort.getSecond(); + if(field.equals(FavouritesService.SortFields.username)) + { + if(ascending) + { + ret = collator.compare(o1.getUserName(), o2.getUserName()); + } + else + { + ret = o2.getUserName().compareTo(o1.getUserName()); + } + + if(ret != 0) + { + break; + } + } + else if(field.equals(FavouritesService.SortFields.type)) + { + if(ascending) + { + ret = o1.getType().compareTo(o2.getType()); + } + else + { + ret = o2.getType().compareTo(o1.getType()); + } + + if(ret != 0) + { + break; + } + } + else if(field.equals(FavouritesService.SortFields.createdAt)) + { + if(ascending) + { + if(o1.getCreatedAt() != null && o2.getCreatedAt() != null) + { + ret = o1.getCreatedAt().compareTo(o2.getCreatedAt()); + } + } + else + { + if(o1.getCreatedAt() != null && o2.getCreatedAt() != null) + { + ret = o2.getCreatedAt().compareTo(o1.getCreatedAt()); + } + } + + if(ret != 0) + { + break; + } + } + else if(field.equals(FavouritesService.SortFields.title)) + { + if(ascending) + { + if(o1.getTitle() != null && o2.getTitle() != null) + { + ret = collator.compare(o1.getTitle(), o2.getTitle()); + } + } + else + { + if(o1.getTitle() != null && o2.getTitle() != null) + { + ret = collator.compare(o2.getTitle(), o1.getTitle()); + } + } + + if(ret != 0) + { + break; + } + } + } + + if(ret == 0) + { + // some favourites may not have a createdAt value, rendering this comparator less selective. + // If the favourites are still regarded as the same, differentiate on nodeRef. + ret = o1.getNodeRef().toString().compareTo(o2.getNodeRef().toString()); + } + + return ret; + } + }; + return comparator; + } + + private PersonFavourite addFavouriteSite(String userName, NodeRef nodeRef) + { + PersonFavourite favourite = null; + + SiteInfo siteInfo = siteService.getSite(nodeRef); + if(siteInfo != null) + { + favourite = getFavouriteSite(userName, siteInfo); + if(favourite == null) + { + Map preferences = new HashMap(1); + + String siteFavouritedKey = siteFavouritedKey(siteInfo); + preferences.put(siteFavouritedKey, Boolean.TRUE); + + // ISO8601 string format: PreferenceService works with strings only for dates it seems + String siteCreatedAtKey = siteCreatedAtKey(siteInfo); + Date createdAt = new Date(); + String createdAtStr = ISO8601DateFormat.format(createdAt); + preferences.put(siteCreatedAtKey, createdAtStr); + + preferenceService.setPreferences(userName, preferences); + + favourite = new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteInfo.getTitle(), createdAt); + + QName nodeClass = nodeService.getType(nodeRef); + OnAddFavouritePolicy policy = onAddFavouriteDelegate.get(nodeRef, nodeClass); + policy.onAddFavourite(userName, nodeRef); + } + } + else + { + // shouldn't happen, getType recognizes it as a site or subtype + logger.warn("Unable to get site for " + nodeRef); + } + + return favourite; + } + + private PersonFavourite getFavouriteSite(String userName, SiteInfo siteInfo) + { + PersonFavourite favourite = null; + + String siteFavouritedKey = siteFavouritedKey(siteInfo); + String siteCreatedAtKey = siteCreatedAtKey(siteInfo); + + Boolean isFavourited = false; + Serializable s = preferenceService.getPreference(userName, siteFavouritedKey); + if(s != null) + { + if(s instanceof String) + { + isFavourited = Boolean.valueOf((String)s); + } + else if(s instanceof Boolean) + { + isFavourited = (Boolean)s; + } + else + { + throw new AlfrescoRuntimeException("Unexpected favourites preference value"); + } + } + + if(isFavourited) + { + String createdAtStr = (String)preferenceService.getPreference(userName, siteCreatedAtKey); + Date createdAt = (createdAtStr == null ? null : ISO8601DateFormat.parse(createdAtStr)); + + favourite = new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteInfo.getTitle(), createdAt); + } + + return favourite; + } + + private boolean isFavouriteSite(String userName, NodeRef nodeRef) + { + Boolean isFavourited = Boolean.FALSE; + SiteInfo siteInfo = siteService.getSite(nodeRef); + if(siteInfo != null) + { + String favouritedPrefKey = siteFavouritedKey(siteInfo); + Serializable value = preferenceService.getPreference(userName, favouritedPrefKey); + + if(value != null) + { + if(value instanceof String) + { + isFavourited = Boolean.valueOf((String)value); + } + else if(value instanceof Boolean) + { + isFavourited = (Boolean)value; + } + else + { + throw new AlfrescoRuntimeException("Unexpected favourites preference value"); + } + } + } + else + { + throw new IllegalArgumentException("NodeRef " + nodeRef + " is not a site"); + } + + return isFavourited.booleanValue(); + } + + private void updateFavouriteNodes(String userName, Type type, Map favouriteNodes) + { + PrefKeys prefKeys = getPrefKeys(type); + + Map preferences = new HashMap(1); + + StringBuilder values = new StringBuilder(); + for(PersonFavourite node : favouriteNodes.values()) + { + NodeRef nodeRef = node.getNodeRef(); + + values.append(nodeRef.toString()); + values.append(","); + + // ISO8601 string format: PreferenceService works with strings only for dates it seems + StringBuilder sb = new StringBuilder(prefKeys.getAlfrescoPrefKey()); + sb.append(nodeRef.toString()); + sb.append(".createdAt"); + String createdAtKey = sb.toString(); + Date createdAt = node.getCreatedAt(); + if(createdAt != null) + { + String createdAtStr = ISO8601DateFormat.format(createdAt); + preferences.put(createdAtKey, createdAtStr); + } + } + + if(values.length() > 1) + { + values.delete(values.length() - 1, values.length()); + } + + preferences.put(prefKeys.getSharePrefKey(), values.toString()); + preferenceService.setPreferences(userName, preferences); + } + + private Map getFavouriteNodes(String userName, Type type) + { + PrefKeys prefKeys = getPrefKeys(type); + Map favouriteNodes = Collections.emptyMap(); + Map prefs = preferenceService.getPreferences(userName, prefKeys.getSharePrefKey()); + String nodes = (String)prefs.get(prefKeys.getSharePrefKey()); + if(nodes != null) + { + favouriteNodes = extractFavouriteNodes(userName, type, nodes); + } + else + { + favouriteNodes = new HashMap(); + } + + return favouriteNodes; + } + + /* + * Extract favourite nodes of the given type from the comma-separated list in "nodes". + */ + private Map extractFavouriteNodes(String userName, Type type, String nodes) + { + PrefKeys prefKeys = getPrefKeys(type); + Map favouriteNodes = new HashMap(); + + StringTokenizer st = new StringTokenizer(nodes, ","); + while(st.hasMoreTokens()) + { + String nodeRefStr = st.nextToken(); + nodeRefStr = nodeRefStr.trim(); + if(!NodeRef.isNodeRef((String)nodeRefStr)) + { + continue; + } + + NodeRef nodeRef = new NodeRef((String)nodeRefStr); + + if(!nodeService.exists(nodeRef)) + { + continue; + } + + if(permissionService.hasPermission(nodeRef, PermissionService.READ_PROPERTIES) == AccessStatus.DENIED) + { + continue; + } + + // get createdAt for this favourited node + // use ISO8601 + StringBuilder builder = new StringBuilder(prefKeys.getAlfrescoPrefKey()); + builder.append(nodeRef.toString()); + builder.append(".createdAt"); + String prefKey = builder.toString(); + String createdAtStr = (String)preferenceService.getPreference(userName, prefKey); + Date createdAt = (createdAtStr != null ? ISO8601DateFormat.parse(createdAtStr): null); + + String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + + PersonFavourite personFavourite = new PersonFavourite(userName, nodeRef, type, name, createdAt); + PersonFavouriteKey key = personFavourite.getKey(); + favouriteNodes.put(key, personFavourite); + } + + return favouriteNodes; + } + + private void extractFavouriteSite(String userName, Type type, Map sortedFavouriteNodes, Map preferences, String key) + { + // preference value indicates whether the site has been favourited + Serializable pref = preferences.get(key); + Boolean isFavourite = (Boolean)pref; + if(isFavourite) + { + PrefKeys sitePrefKeys = getPrefKeys(Type.SITE); + int length = sitePrefKeys.getSharePrefKey().length(); + String siteId = key.substring(length); + + try + { + SiteInfo siteInfo = siteService.getSite(siteId); + if(siteInfo != null) + { + StringBuilder builder = new StringBuilder(sitePrefKeys.getAlfrescoPrefKey()); + builder.append(siteId); + builder.append(".createdAt"); + String createdAtPrefKey = builder.toString(); + String createdAtStr = (String)preferences.get(createdAtPrefKey); + Date createdAt = null; + if(createdAtStr != null) + { + createdAt = (createdAtStr != null ? ISO8601DateFormat.parse(createdAtStr): null); + } + PersonFavourite personFavourite = new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteId, createdAt); + sortedFavouriteNodes.put(personFavourite.getKey(), personFavourite); + } + } + catch(AccessDeniedException ex) + { + // the user no longer has access to this site, skip over the favourite + // TODO remove the favourite preference + return; + } + } + } + + private PersonFavourite getFavouriteDocumentOrFolder(String userName, Type type, NodeRef nodeRef) + { + PersonFavourite favourite = null; + + PrefKeys prefKeys = getPrefKeys(type); + Map preferences = preferenceService.getPreferences(userName, prefKeys.getSharePrefKey()); + String nodes = (String)preferences.get(prefKeys.getSharePrefKey()); + if(nodes != null) + { + Map favouriteNodes = extractFavouriteNodes(userName, type, nodes); + String title = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); + PersonFavouriteKey key = new PersonFavouriteKey(userName, title, type, nodeRef); + favourite = favouriteNodes.get(key); + } + return favourite; + } + + private PersonFavourite getPersonFavourite(String userName, Type type, NodeRef nodeRef) + { + PersonFavourite ret = null; + if(type.equals(Type.SITE)) + { + SiteInfo siteInfo = siteService.getSite(nodeRef); + if(siteInfo != null) + { + ret = getFavouriteSite(userName, siteInfo); + } + else + { + // shouldn't happen, getType recognizes it as a site or subtype + logger.warn("Unable to get site for " + nodeRef); + } + } + else if(type.equals(Type.FILE)) + { + ret = getFavouriteDocumentOrFolder(userName, type, nodeRef); + } + else if(type.equals(Type.FOLDER)) + { + ret = getFavouriteDocumentOrFolder(userName, type, nodeRef); + } + else + { + // shouldn't happen + throw new AlfrescoRuntimeException("Unexpected favourite type"); + } + + return ret; + } + + private PersonFavourite addFavouriteDocumentOrFolder(String userName, Type type, NodeRef nodeRef) + { + Map personFavourites = getFavouriteNodes(userName, type); + PersonFavourite personFavourite = getPersonFavourite(userName, type, nodeRef); + if(personFavourite == null) + { + Date createdAt = new Date(); + final String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + personFavourite = new PersonFavourite(userName, nodeRef, type, name, createdAt); + personFavourites.put(personFavourite.getKey(), personFavourite); + updateFavouriteNodes(userName, type, personFavourites); + + QName nodeClass = nodeService.getType(nodeRef); + final String finalRef = nodeRef.getId(); + final QName nodeType = nodeClass; + + eventPublisher.publishEvent(new EventPreparator(){ + @Override + public Event prepareEvent(String user, String networkId, String transactionId) + { + return new ActivityEvent("favorite.added", transactionId, networkId, user, finalRef, + null, nodeType==null?null:nodeType.toString(), Client.asType(ClientType.script), null, + name, null, 0l, null); + } + }); + + OnAddFavouritePolicy policy = onAddFavouriteDelegate.get(nodeRef, nodeClass); + policy.onAddFavourite(userName, nodeRef); + } + + return personFavourite; + } + + private boolean isFavouriteNode(String userName, Type type, NodeRef nodeRef) + { + Map personFavourites = getFavouriteNodes(userName, type); + PersonFavouriteKey personFavouriteKey = new PersonFavouriteKey(userName, null, type, nodeRef); + boolean isFavourite = personFavourites.containsKey(personFavouriteKey); + return isFavourite; + } + + public Type getType(NodeRef nodeRef) + { + Type favouriteType = null; + + QName type = nodeService.getType(nodeRef); + boolean isSite = dictionaryService.isSubClass(type, SiteModel.TYPE_SITE); + if(isSite) + { + favouriteType = Type.SITE; + } + else + { + boolean isContainer = (dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) && + !dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER)); + if(isContainer) + { + favouriteType = Type.FOLDER; + } + else + { + boolean isFile = dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT); + if(isFile) + { + favouriteType = Type.FILE; + } + } + } + + return favouriteType; + } + + private boolean removeFavouriteNode(String userName, Type type, NodeRef nodeRef) + { + boolean exists = false; + + Map personFavourites = getFavouriteNodes(userName, type); + + PersonFavouriteKey personFavouriteKey = new PersonFavouriteKey(userName, null, type, nodeRef); + PersonFavourite personFavourite = personFavourites.remove(personFavouriteKey); + exists = personFavourite != null; + updateFavouriteNodes(userName, type, personFavourites); + + QName nodeClass = nodeService.getType(nodeRef); + final String finalRef = nodeRef.getId(); + final QName nodeType = nodeClass; + + eventPublisher.publishEvent(new EventPreparator(){ + @Override + public Event prepareEvent(String user, String networkId, String transactionId) + { + return new ActivityEvent("favorite.removed", transactionId, networkId, user, finalRef, + null, nodeType==null?null:nodeType.toString(), Client.asType(ClientType.script), null, + null, null, 0l, null); + } + }); + + OnRemoveFavouritePolicy policy = onRemoveFavouriteDelegate.get(nodeRef, nodeClass); + policy.onRemoveFavourite(userName, nodeRef); + + return exists; + } + + @Override + public PersonFavourite addFavourite(String userName, NodeRef nodeRef) + { + PersonFavourite personFavourite = null; + + Type type = getType(nodeRef); + if(type == null) + { + throw new IllegalArgumentException("Cannot favourite this node"); + } + else if(type.equals(Type.FILE)) + { + personFavourite = addFavouriteDocumentOrFolder(userName, Type.FILE, nodeRef); + } + else if(type.equals(Type.FOLDER)) + { + personFavourite = addFavouriteDocumentOrFolder(userName, Type.FOLDER, nodeRef); + } + else if(type.equals(Type.SITE)) + { + personFavourite = addFavouriteSite(userName, nodeRef); + } + else + { + throw new IllegalArgumentException("Cannot favourite this node"); + } + + return personFavourite; + } + + @Override + public boolean removeFavourite(String userName, NodeRef nodeRef) + { + boolean exists = false; + + Type type = getType(nodeRef); + if(type == null) + { + throw new IllegalArgumentException("Cannot un-favourite this node"); + } + else if(type.equals(Type.FILE)) + { + exists = removeFavouriteNode(userName, type, nodeRef); + } + else if(type.equals(Type.FOLDER)) + { + exists = removeFavouriteNode(userName, type, nodeRef); + } + else if(type.equals(Type.SITE)) + { + exists = removeFavouriteSite(userName, nodeRef); + } + else + { + throw new IllegalArgumentException("Cannot un-favourite this node"); + } + + return exists; + } + + @Override + public boolean isFavourite(String userName, NodeRef nodeRef) + { + boolean isFavourite = false; + + Type type = getType(nodeRef); + if(type == null) + { + throw new IllegalArgumentException("Unsupported node type"); + } + else if(type.equals(Type.FILE)) + { + isFavourite = isFavouriteNode(userName, type, nodeRef); + } + else if(type.equals(Type.FOLDER)) + { + isFavourite = isFavouriteNode(userName, type, nodeRef); + } + else if(type.equals(Type.SITE)) + { + isFavourite = isFavouriteSite(userName, nodeRef); + } + else + { + throw new IllegalArgumentException("Unsupported node type"); + } + + return isFavourite; + } + + @Override + public PagingResults getPagedFavourites(String userName, Set types, + List> sortProps, PagingRequest pagingRequest) + { + // Get the user node reference + final NodeRef personNodeRef = personService.getPerson(userName); + if(personNodeRef == null) + { + throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because he/she does not exist."); + } + + boolean includeFiles = types.contains(Type.FILE); + boolean includeFolders = types.contains(Type.FOLDER); + boolean includeSites = types.contains(Type.SITE); + + String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); + if (authenticationContext.isSystemUserName(currentUserName) == true || + permissionService.hasPermission(personNodeRef, PermissionService.WRITE) == AccessStatus.ALLOWED || + userName.equals(currentUserName) == true) + { + // we may have more than one favourite that is considered the same w.r.t. the PersonFavourite comparator + final Map sortedFavouriteNodes = new TreeMap(getComparator(sortProps)); + + PrefKeys sitePrefKeys = getPrefKeys(Type.SITE); + PrefKeys documentsPrefKeys = getPrefKeys(Type.FILE); + PrefKeys foldersPrefKeys = getPrefKeys(Type.FOLDER); + + Map preferences = preferenceService.getPreferences(userName); + for(String key : preferences.keySet()) + { + if(includeFiles && key.startsWith(documentsPrefKeys.sharePrefKey)) + { + String nodes = (String)preferences.get(key); + if(nodes != null) + { + sortedFavouriteNodes.putAll(extractFavouriteNodes(userName, Type.FILE, nodes)); + } + } + else if(includeFolders && key.startsWith(foldersPrefKeys.sharePrefKey)) + { + String nodes = (String)preferences.get(key); + if(nodes != null) + { + sortedFavouriteNodes.putAll(extractFavouriteNodes(userName, Type.FOLDER, nodes)); + } + } + else if(includeSites && key.startsWith(sitePrefKeys.getSharePrefKey()) && !key.endsWith(".createdAt")) + { + // key is either of the form org.alfresco.share.sites.favourites..favourited or + // org.alfresco.share.sites.favourites. + extractFavouriteSite(userName, Type.SITE, sortedFavouriteNodes, preferences, key); + } + } + + int totalSize = sortedFavouriteNodes.size(); + final PageDetails pageDetails = PageDetails.getPageDetails(pagingRequest, totalSize); + + final List page = new ArrayList(pageDetails.getPageSize()); + Iterator it = sortedFavouriteNodes.values().iterator(); + for(int counter = 0; counter < pageDetails.getEnd() && it.hasNext(); counter++) + { + PersonFavourite favouriteNode = it.next(); + + if(counter < pageDetails.getSkipCount()) + { + continue; + } + + if(counter > pageDetails.getEnd() - 1) + { + break; + } + + page.add(favouriteNode); + } + + return new PagingResults() + { + @Override + public List getPage() + { + return page; + } + + @Override + public boolean hasMoreItems() + { + return pageDetails.hasMoreItems(); + } + + @Override + public Pair getTotalResultCount() + { + Integer total = Integer.valueOf(sortedFavouriteNodes.size()); + return new Pair(total, total); + } + + @Override + public String getQueryExecutionId() + { + return null; + } + }; + } + else + { + // The current user does not have sufficient permissions to update the preferences for this user + throw new AccessDeniedException("The current user " + currentUserName + " does not have sufficient permissions to get the favourites of the user " + userName); + } + } + + public PersonFavourite getFavourite(String userName, NodeRef nodeRef) + { + Type type = getType(nodeRef); + return getPersonFavourite(userName, type, nodeRef); + } + + public void setEventPublisher(EventPublisher eventPublisher) + { + this.eventPublisher = eventPublisher; + } +} diff --git a/source/java/org/alfresco/repo/favourites/PersonFavourite.java b/source/java/org/alfresco/repo/favourites/PersonFavourite.java index 545829f81e..fd7ed80486 100644 --- a/source/java/org/alfresco/repo/favourites/PersonFavourite.java +++ b/source/java/org/alfresco/repo/favourites/PersonFavourite.java @@ -1,251 +1,251 @@ -package org.alfresco.repo.favourites; - -import java.util.Date; - -import org.alfresco.service.cmr.favourites.FavouritesService.Type; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Representation of a user's favourite site, document, folder. - * - * @author steveglover - * - */ -public class PersonFavourite -{ - private String userName; - private String title; - private Type type; // using a type rather then subclassing to make sorting of PersonFavourites easier TODO - private Date createdAt; - private NodeRef nodeRef; - - public static class PersonFavouriteKey - { - private String userName; - private Type type; - private String title; - private NodeRef nodeRef; - private Date createdAt; - - public PersonFavouriteKey(String userName, String title, Type type, NodeRef nodeRef) - { - super(); - this.userName = userName; - this.type = type; - this.nodeRef = nodeRef; - } - - public PersonFavouriteKey(String userName, String title, Type type, NodeRef nodeRef, Date createdAt) - { - super(); - this.userName = userName; - this.type = type; - this.nodeRef = nodeRef; - this.createdAt = createdAt; - } - - public String getTitle() - { - return title; - } - - public String getUserName() - { - return userName; - } - - public Type getType() - { - return type; - } - - public Date getCreatedAt() - { - return createdAt; - } - - public NodeRef getNodeRef() - { - return nodeRef; - } - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = prime * result - + ((nodeRef == null) ? 0 : nodeRef.hashCode()); - result = prime * result - + ((userName == null) ? 0 : userName.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - PersonFavouriteKey other = (PersonFavouriteKey) obj; - if (nodeRef == null) - { - if (other.nodeRef != null) - return false; - } else if (!nodeRef.equals(other.nodeRef)) - return false; - if (userName == null) - { - if (other.userName != null) - return false; - } else if (!userName.equals(other.userName)) - return false; - return true; - } - - @Override - public String toString() - { - return "PersonFavouriteKey [userName=" + userName + ", nodeRef=" - + nodeRef + "]"; - } - } - - /* - * Used for comparisons - */ - PersonFavourite(String userName, NodeRef nodeRef, Type type) - { - super(); - if(userName == null) - { - throw new IllegalArgumentException("Must provide a userName"); - } - if(nodeRef == null) - { - throw new IllegalArgumentException("Must provide a nodeRef"); - } - if(type == null) - { - throw new IllegalArgumentException("Must provide a type"); - } - this.userName = userName; - this.nodeRef = nodeRef; - this.type = type; - } - - public PersonFavourite(String userName, NodeRef nodeRef, Type type, String title, Date createdAt) - { - super(); - if(userName == null) - { - throw new IllegalArgumentException("Must provide a userName"); - } - if(nodeRef == null) - { - throw new IllegalArgumentException("Must provide a nodeRef"); - } - if(type == null) - { - throw new IllegalArgumentException("Must provide a type"); - } - if(title == null) - { - throw new IllegalArgumentException("Must provide a title"); - } - // re-instate if Share can persist createdAt for favourites -// if(createdAt == null) -// { -// throw new IllegalArgumentException("Must provide a createdAt"); -// } - this.userName = userName; - this.nodeRef = nodeRef; - this.type = type; - this.title = title; - this.createdAt = createdAt; - } - -// PersonFavourite(String userName, NodeRef nodeRef, Type type, String title, Date createdAt, boolean exists) -// { -// this(userName, nodeRef, type, title, createdAt); -// this.exists = exists; -// } - - public PersonFavouriteKey getKey() - { - PersonFavouriteKey key = new PersonFavouriteKey(getUserName(), getTitle(), getType(), getNodeRef(), getCreatedAt()); - return key; - } - - public String getTitle() - { - return title; - } - - public String getUserName() - { - return userName; - } - - public Type getType() - { - return type; - } - - public NodeRef getNodeRef() - { - return nodeRef; - } - - public Date getCreatedAt() - { - return createdAt; - } - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = prime * result + ((nodeRef == null) ? 0 : nodeRef.hashCode()); - result = prime * result - + ((userName == null) ? 0 : userName.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - PersonFavourite other = (PersonFavourite) obj; - if (nodeRef == null) - { - if (other.nodeRef != null) - return false; - } else if (!nodeRef.equals(other.nodeRef)) - return false; - if (userName == null) - { - if (other.userName != null) - return false; - } else if (!userName.equals(other.userName)) - return false; - return true; - } - - @Override - public String toString() - { - return "PersonFavourite [userName=" + userName + ", name=" + title - + ", type=" + type + ", createdAt=" + createdAt + ", nodeRef=" - + nodeRef + "]"; - } -} +package org.alfresco.repo.favourites; + +import java.util.Date; + +import org.alfresco.service.cmr.favourites.FavouritesService.Type; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Representation of a user's favourite site, document, folder. + * + * @author steveglover + * + */ +public class PersonFavourite +{ + private String userName; + private String title; + private Type type; // using a type rather then subclassing to make sorting of PersonFavourites easier TODO + private Date createdAt; + private NodeRef nodeRef; + + public static class PersonFavouriteKey + { + private String userName; + private Type type; + private String title; + private NodeRef nodeRef; + private Date createdAt; + + public PersonFavouriteKey(String userName, String title, Type type, NodeRef nodeRef) + { + super(); + this.userName = userName; + this.type = type; + this.nodeRef = nodeRef; + } + + public PersonFavouriteKey(String userName, String title, Type type, NodeRef nodeRef, Date createdAt) + { + super(); + this.userName = userName; + this.type = type; + this.nodeRef = nodeRef; + this.createdAt = createdAt; + } + + public String getTitle() + { + return title; + } + + public String getUserName() + { + return userName; + } + + public Type getType() + { + return type; + } + + public Date getCreatedAt() + { + return createdAt; + } + + public NodeRef getNodeRef() + { + return nodeRef; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + + ((nodeRef == null) ? 0 : nodeRef.hashCode()); + result = prime * result + + ((userName == null) ? 0 : userName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PersonFavouriteKey other = (PersonFavouriteKey) obj; + if (nodeRef == null) + { + if (other.nodeRef != null) + return false; + } else if (!nodeRef.equals(other.nodeRef)) + return false; + if (userName == null) + { + if (other.userName != null) + return false; + } else if (!userName.equals(other.userName)) + return false; + return true; + } + + @Override + public String toString() + { + return "PersonFavouriteKey [userName=" + userName + ", nodeRef=" + + nodeRef + "]"; + } + } + + /* + * Used for comparisons + */ + PersonFavourite(String userName, NodeRef nodeRef, Type type) + { + super(); + if(userName == null) + { + throw new IllegalArgumentException("Must provide a userName"); + } + if(nodeRef == null) + { + throw new IllegalArgumentException("Must provide a nodeRef"); + } + if(type == null) + { + throw new IllegalArgumentException("Must provide a type"); + } + this.userName = userName; + this.nodeRef = nodeRef; + this.type = type; + } + + public PersonFavourite(String userName, NodeRef nodeRef, Type type, String title, Date createdAt) + { + super(); + if(userName == null) + { + throw new IllegalArgumentException("Must provide a userName"); + } + if(nodeRef == null) + { + throw new IllegalArgumentException("Must provide a nodeRef"); + } + if(type == null) + { + throw new IllegalArgumentException("Must provide a type"); + } + if(title == null) + { + throw new IllegalArgumentException("Must provide a title"); + } + // re-instate if Share can persist createdAt for favourites +// if(createdAt == null) +// { +// throw new IllegalArgumentException("Must provide a createdAt"); +// } + this.userName = userName; + this.nodeRef = nodeRef; + this.type = type; + this.title = title; + this.createdAt = createdAt; + } + +// PersonFavourite(String userName, NodeRef nodeRef, Type type, String title, Date createdAt, boolean exists) +// { +// this(userName, nodeRef, type, title, createdAt); +// this.exists = exists; +// } + + public PersonFavouriteKey getKey() + { + PersonFavouriteKey key = new PersonFavouriteKey(getUserName(), getTitle(), getType(), getNodeRef(), getCreatedAt()); + return key; + } + + public String getTitle() + { + return title; + } + + public String getUserName() + { + return userName; + } + + public Type getType() + { + return type; + } + + public NodeRef getNodeRef() + { + return nodeRef; + } + + public Date getCreatedAt() + { + return createdAt; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((nodeRef == null) ? 0 : nodeRef.hashCode()); + result = prime * result + + ((userName == null) ? 0 : userName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PersonFavourite other = (PersonFavourite) obj; + if (nodeRef == null) + { + if (other.nodeRef != null) + return false; + } else if (!nodeRef.equals(other.nodeRef)) + return false; + if (userName == null) + { + if (other.userName != null) + return false; + } else if (!userName.equals(other.userName)) + return false; + return true; + } + + @Override + public String toString() + { + return "PersonFavourite [userName=" + userName + ", name=" + title + + ", type=" + type + ", createdAt=" + createdAt + ", nodeRef=" + + nodeRef + "]"; + } +} diff --git a/source/java/org/alfresco/repo/forms/AssociationFieldDefinition.java b/source/java/org/alfresco/repo/forms/AssociationFieldDefinition.java index 457757b4a8..f90723d4a7 100644 --- a/source/java/org/alfresco/repo/forms/AssociationFieldDefinition.java +++ b/source/java/org/alfresco/repo/forms/AssociationFieldDefinition.java @@ -1,122 +1,122 @@ -package org.alfresco.repo.forms; - -/** - * An association field definition that can represent a source->target association - * or a target->source association. - * - * @author Gavin Cornwell - */ -public class AssociationFieldDefinition extends FieldDefinition -{ - public enum Direction { SOURCE, TARGET } - - protected String endpointType; - protected Direction endpointDirection; - protected boolean endpointMandatory = false; - protected boolean endpointMany = false; - - /** - * Default constructor - * - * @param name The name of the association - * @param endpointType The type of the item at the end of the association - * @param endpointDirection The direction the association is going - */ - public AssociationFieldDefinition(String name, String endpointType, Direction endpointDirection) - { - super(name); - - this.endpointType = endpointType; - this.endpointDirection = endpointDirection; - } - - /** - * Returns the type of the target of the association - * - * @return The type of the target - */ - public String getEndpointType() - { - return this.endpointType; - } - - /** - * Returns the direction the association is going. - *

- * Direction.TARGET means the endpoint is the target - * and the field is the source. - *

- * Direction.SOURCE means the endpoint is the source - * and the field is the target. - * - * @return Direction.TARGET or Direction.SOURCE - */ - public Direction getEndpointDirection() - { - return this.endpointDirection; - } - - /** - * Determines whether the target is mandatory - * - * @return true if a target has to be selected - */ - public boolean isEndpointMandatory() - { - return this.endpointMandatory; - } - - /** - * Sets whether the target is mandatory - * - * @param endpointMandatory true if a target has to be selected - */ - public void setEndpointMandatory(boolean endpointMandatory) - { - this.endpointMandatory = endpointMandatory; - } - - /** - * Determines if multiple targets can be selected - * - * @return true if multiple targets can be selected - */ - public boolean isEndpointMany() - { - return this.endpointMany; - } - - /** - * Sets whether multiple targets can be selected - * - * @param endpointMany true if multiple targets can be selected - */ - public void setEndpointMany(boolean endpointMany) - { - this.endpointMany = endpointMany; - } - - /* - * @see java.lang.Object#toString() - */ - @Override - public String toString() - { - StringBuilder buffer = new StringBuilder(super.toString()); - buffer.append(" ("); - buffer.append("name=").append(this.name); - buffer.append(", endpointType=").append(this.endpointType); - buffer.append(", endpointDirection=").append(this.endpointDirection); - buffer.append(", endpointMandatory=").append(this.endpointMandatory); - buffer.append(", endpointMany=").append(this.endpointMany); - buffer.append(", label=").append(this.label); - buffer.append(", description=").append(this.description); - buffer.append(", binding=").append(this.binding); - buffer.append(", defaultValue=").append(this.defaultValue); - buffer.append(", dataKeyName=").append(this.dataKeyName); - buffer.append(", group=").append(this.group); - buffer.append(", protectedField=").append(this.protectedField); - buffer.append(")"); - return buffer.toString(); - } -} +package org.alfresco.repo.forms; + +/** + * An association field definition that can represent a source->target association + * or a target->source association. + * + * @author Gavin Cornwell + */ +public class AssociationFieldDefinition extends FieldDefinition +{ + public enum Direction { SOURCE, TARGET } + + protected String endpointType; + protected Direction endpointDirection; + protected boolean endpointMandatory = false; + protected boolean endpointMany = false; + + /** + * Default constructor + * + * @param name The name of the association + * @param endpointType The type of the item at the end of the association + * @param endpointDirection The direction the association is going + */ + public AssociationFieldDefinition(String name, String endpointType, Direction endpointDirection) + { + super(name); + + this.endpointType = endpointType; + this.endpointDirection = endpointDirection; + } + + /** + * Returns the type of the target of the association + * + * @return The type of the target + */ + public String getEndpointType() + { + return this.endpointType; + } + + /** + * Returns the direction the association is going. + *

+ * Direction.TARGET means the endpoint is the target + * and the field is the source. + *

+ * Direction.SOURCE means the endpoint is the source + * and the field is the target. + * + * @return Direction.TARGET or Direction.SOURCE + */ + public Direction getEndpointDirection() + { + return this.endpointDirection; + } + + /** + * Determines whether the target is mandatory + * + * @return true if a target has to be selected + */ + public boolean isEndpointMandatory() + { + return this.endpointMandatory; + } + + /** + * Sets whether the target is mandatory + * + * @param endpointMandatory true if a target has to be selected + */ + public void setEndpointMandatory(boolean endpointMandatory) + { + this.endpointMandatory = endpointMandatory; + } + + /** + * Determines if multiple targets can be selected + * + * @return true if multiple targets can be selected + */ + public boolean isEndpointMany() + { + return this.endpointMany; + } + + /** + * Sets whether multiple targets can be selected + * + * @param endpointMany true if multiple targets can be selected + */ + public void setEndpointMany(boolean endpointMany) + { + this.endpointMany = endpointMany; + } + + /* + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" ("); + buffer.append("name=").append(this.name); + buffer.append(", endpointType=").append(this.endpointType); + buffer.append(", endpointDirection=").append(this.endpointDirection); + buffer.append(", endpointMandatory=").append(this.endpointMandatory); + buffer.append(", endpointMany=").append(this.endpointMany); + buffer.append(", label=").append(this.label); + buffer.append(", description=").append(this.description); + buffer.append(", binding=").append(this.binding); + buffer.append(", defaultValue=").append(this.defaultValue); + buffer.append(", dataKeyName=").append(this.dataKeyName); + buffer.append(", group=").append(this.group); + buffer.append(", protectedField=").append(this.protectedField); + buffer.append(")"); + return buffer.toString(); + } +} diff --git a/source/java/org/alfresco/repo/forms/DataTypeParameters.java b/source/java/org/alfresco/repo/forms/DataTypeParameters.java index 2879882e75..e9b2f12f21 100644 --- a/source/java/org/alfresco/repo/forms/DataTypeParameters.java +++ b/source/java/org/alfresco/repo/forms/DataTypeParameters.java @@ -1,32 +1,32 @@ -package org.alfresco.repo.forms; - -/** - * Interface definition for an object used to represent any restrictions - * a data type may enforce. - * - * @author Gavin Cornwell - */ -public interface DataTypeParameters -{ - /** - * Returns the parameters in a Java friendly manner i.e. as an Object. - * The Object can be as complex as a multiple nested Map of Maps or as - * simple as a String. - * - * @return An Object representing the data type parameters - */ - public Object getAsObject(); - - /** - * Returns the parameters represented as JSON. - *

- * Implementations can use whatever JSON libraries they - * desire, the only rule is that the object returned must - * toString() to either a JSON array or JSON object i.e. - * [...] or {...} - *

- * - * @return JSON Object representing the parameters - */ - public Object getAsJSON(); -} +package org.alfresco.repo.forms; + +/** + * Interface definition for an object used to represent any restrictions + * a data type may enforce. + * + * @author Gavin Cornwell + */ +public interface DataTypeParameters +{ + /** + * Returns the parameters in a Java friendly manner i.e. as an Object. + * The Object can be as complex as a multiple nested Map of Maps or as + * simple as a String. + * + * @return An Object representing the data type parameters + */ + public Object getAsObject(); + + /** + * Returns the parameters represented as JSON. + *

+ * Implementations can use whatever JSON libraries they + * desire, the only rule is that the object returned must + * toString() to either a JSON array or JSON object i.e. + * [...] or {...} + *

+ * + * @return JSON Object representing the parameters + */ + public Object getAsJSON(); +} diff --git a/source/java/org/alfresco/repo/forms/FieldDefinition.java b/source/java/org/alfresco/repo/forms/FieldDefinition.java index e7b3b11e00..833c251d1d 100644 --- a/source/java/org/alfresco/repo/forms/FieldDefinition.java +++ b/source/java/org/alfresco/repo/forms/FieldDefinition.java @@ -1,182 +1,182 @@ -package org.alfresco.repo.forms; - -/** - * Abstract representation of a field defintion. - * - * @author Gavin Cornwell - */ -public abstract class FieldDefinition -{ - protected String name; - protected String label; - protected String description; - protected String binding; - protected String defaultValue; - protected String dataKeyName; - protected FieldGroup group; - protected boolean protectedField = false; - - /** - * Default constructor - */ - public FieldDefinition(String name) - { - this.name = name; - } - - /** - * Returns the name of the field - * - * @return The field's name - */ - public String getName() - { - return this.name; - } - - /** - * Returns the display label for the field - * - * @return The field's display label - */ - public String getLabel() - { - return this.label; - } - - /** - * Sets the display label for the field - * - * @param label The field's display label - */ - public void setLabel(String label) - { - this.label = label; - } - - /** - * Returns the description of the field - * - * @return The field's description - */ - public String getDescription() - { - return this.description; - } - - /** - * Sets the description of the field - * - * @param description The field's description - */ - public void setDescription(String description) - { - this.description = description; - } - - /** - * Returns the binding for the field, this is used by some - * FormModelProcessor implementations to generate an - * alternative representation of the data - * - * @return The field's binding - */ - public String getBinding() - { - return this.binding; - } - - /** - * Sets the binding to use for the field, this is used by some - * FormModelProcessor implementations to generate an - * alternative representation of the data - * - * @param binding The field's binding - */ - public void setBinding(String binding) - { - this.binding = binding; - } - - /** - * Returns any default value the field may have - * - * @return The field's default value or null if there isn't one - */ - public String getDefaultValue() - { - return this.defaultValue; - } - - /** - * Sets the default value for the field - * - * @param defaultValue The field's default value - */ - public void setDefaultValue(String defaultValue) - { - this.defaultValue = defaultValue; - } - - /** - * Returns the name of the key being used to hold the data for the field - * - * @return Name of the key being used to hold the data for the field - */ - public String getDataKeyName() - { - return this.dataKeyName; - } - - /** - * Sets the name of the key to be used to hold the data for the field - * - * @param dataKeyName The name of the key to be used to hold the data for the field - */ - public void setDataKeyName(String dataKeyName) - { - this.dataKeyName = dataKeyName; - } - - /** - * Returns the group the field may be a part of - * - * @return The field's group or null if it does not belong to a group - */ - public FieldGroup getGroup() - { - return this.group; - } - - /** - * Sets the group the field is part of - * - * @param group The group the field belongs to - */ - public void setGroup(FieldGroup group) - { - this.group = group; - } - - /** - * Determines whether the field is protected i.e. it should be rendered - * as read-only in any client displaying the field - * - * @return true if the field is protected - */ - public boolean isProtectedField() - { - return this.protectedField; - } - - /** - * Sets whether the field is protected i.e. it should be rendered - * as read-only in any client displaying the field - * - * @param protectedField true if the field is protected - */ - public void setProtectedField(boolean protectedField) - { - this.protectedField = protectedField; - } -} +package org.alfresco.repo.forms; + +/** + * Abstract representation of a field defintion. + * + * @author Gavin Cornwell + */ +public abstract class FieldDefinition +{ + protected String name; + protected String label; + protected String description; + protected String binding; + protected String defaultValue; + protected String dataKeyName; + protected FieldGroup group; + protected boolean protectedField = false; + + /** + * Default constructor + */ + public FieldDefinition(String name) + { + this.name = name; + } + + /** + * Returns the name of the field + * + * @return The field's name + */ + public String getName() + { + return this.name; + } + + /** + * Returns the display label for the field + * + * @return The field's display label + */ + public String getLabel() + { + return this.label; + } + + /** + * Sets the display label for the field + * + * @param label The field's display label + */ + public void setLabel(String label) + { + this.label = label; + } + + /** + * Returns the description of the field + * + * @return The field's description + */ + public String getDescription() + { + return this.description; + } + + /** + * Sets the description of the field + * + * @param description The field's description + */ + public void setDescription(String description) + { + this.description = description; + } + + /** + * Returns the binding for the field, this is used by some + * FormModelProcessor implementations to generate an + * alternative representation of the data + * + * @return The field's binding + */ + public String getBinding() + { + return this.binding; + } + + /** + * Sets the binding to use for the field, this is used by some + * FormModelProcessor implementations to generate an + * alternative representation of the data + * + * @param binding The field's binding + */ + public void setBinding(String binding) + { + this.binding = binding; + } + + /** + * Returns any default value the field may have + * + * @return The field's default value or null if there isn't one + */ + public String getDefaultValue() + { + return this.defaultValue; + } + + /** + * Sets the default value for the field + * + * @param defaultValue The field's default value + */ + public void setDefaultValue(String defaultValue) + { + this.defaultValue = defaultValue; + } + + /** + * Returns the name of the key being used to hold the data for the field + * + * @return Name of the key being used to hold the data for the field + */ + public String getDataKeyName() + { + return this.dataKeyName; + } + + /** + * Sets the name of the key to be used to hold the data for the field + * + * @param dataKeyName The name of the key to be used to hold the data for the field + */ + public void setDataKeyName(String dataKeyName) + { + this.dataKeyName = dataKeyName; + } + + /** + * Returns the group the field may be a part of + * + * @return The field's group or null if it does not belong to a group + */ + public FieldGroup getGroup() + { + return this.group; + } + + /** + * Sets the group the field is part of + * + * @param group The group the field belongs to + */ + public void setGroup(FieldGroup group) + { + this.group = group; + } + + /** + * Determines whether the field is protected i.e. it should be rendered + * as read-only in any client displaying the field + * + * @return true if the field is protected + */ + public boolean isProtectedField() + { + return this.protectedField; + } + + /** + * Sets whether the field is protected i.e. it should be rendered + * as read-only in any client displaying the field + * + * @param protectedField true if the field is protected + */ + public void setProtectedField(boolean protectedField) + { + this.protectedField = protectedField; + } +} diff --git a/source/java/org/alfresco/repo/forms/FieldGroup.java b/source/java/org/alfresco/repo/forms/FieldGroup.java index 046ab19a40..25a5721c8d 100644 --- a/source/java/org/alfresco/repo/forms/FieldGroup.java +++ b/source/java/org/alfresco/repo/forms/FieldGroup.java @@ -1,86 +1,86 @@ -package org.alfresco.repo.forms; - -/** - * Represents a field group - * - * @author Gavin Cornwell - */ -public class FieldGroup -{ - protected String id; - protected String label; - protected FieldGroup parent; - protected boolean repeats; - protected boolean mandatory; - - /** - * Constructs a FieldGroup - * - * @param id The id of the group - * @param label The display label of the group - * @param mandatory Whether the group is mandatory - * @param repeats Whether the group of fields can repeat - * @param parent The group's parent group or null if it - * doesn't have a parent - */ - public FieldGroup(String id, String label, boolean mandatory, - boolean repeats, FieldGroup parent) - { - this.id = id; - this.label = label; - this.mandatory = mandatory; - this.parent = parent; - this.repeats = repeats; - } - - /** - * Returns the id of the group - * - * @return The id of the group - */ - public String getId() - { - return this.id; - } - - /** - * Returns the display label of the group - * - * @return The display label of the group - */ - public String getLabel() - { - return this.label; - } - - /** - * Returns the parent group - * - * @return The parent group or null if there isn't a parent - */ - public FieldGroup getParent() - { - return this.parent; - } - - /** - * Determines whether the fields inside this group can - * repeat multiple times - * - * @return true if the group repeats - */ - public boolean isRepeating() - { - return this.repeats; - } - - /** - * Determines if the group is mandatory - * - * @return true if the group is mandatory - */ - public boolean isMandatory() - { - return this.mandatory; - } -} +package org.alfresco.repo.forms; + +/** + * Represents a field group + * + * @author Gavin Cornwell + */ +public class FieldGroup +{ + protected String id; + protected String label; + protected FieldGroup parent; + protected boolean repeats; + protected boolean mandatory; + + /** + * Constructs a FieldGroup + * + * @param id The id of the group + * @param label The display label of the group + * @param mandatory Whether the group is mandatory + * @param repeats Whether the group of fields can repeat + * @param parent The group's parent group or null if it + * doesn't have a parent + */ + public FieldGroup(String id, String label, boolean mandatory, + boolean repeats, FieldGroup parent) + { + this.id = id; + this.label = label; + this.mandatory = mandatory; + this.parent = parent; + this.repeats = repeats; + } + + /** + * Returns the id of the group + * + * @return The id of the group + */ + public String getId() + { + return this.id; + } + + /** + * Returns the display label of the group + * + * @return The display label of the group + */ + public String getLabel() + { + return this.label; + } + + /** + * Returns the parent group + * + * @return The parent group or null if there isn't a parent + */ + public FieldGroup getParent() + { + return this.parent; + } + + /** + * Determines whether the fields inside this group can + * repeat multiple times + * + * @return true if the group repeats + */ + public boolean isRepeating() + { + return this.repeats; + } + + /** + * Determines if the group is mandatory + * + * @return true if the group is mandatory + */ + public boolean isMandatory() + { + return this.mandatory; + } +} diff --git a/source/java/org/alfresco/repo/forms/Form.java b/source/java/org/alfresco/repo/forms/Form.java index 707323d893..de54d74deb 100644 --- a/source/java/org/alfresco/repo/forms/Form.java +++ b/source/java/org/alfresco/repo/forms/Form.java @@ -1,237 +1,237 @@ -package org.alfresco.repo.forms; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.alfresco.repo.forms.FormData.FieldData; - -/** - * Data representation of a form to be displayed in the UI. - * - * @author Gavin Cornwell - */ -public class Form -{ - protected Item item; - protected String submissionUrl; - protected List fieldDefinitions; - protected Collection fieldGroups; - protected FormData data; - - /** - * Constructs a Form - * - * @param item The item the form is for - */ - public Form(Item item) - { - this.item = item; - } - - /** - * Returns the item the form is for - * - * @return The item - */ - public Item getItem() - { - return this.item; - } - - /** - * Returns the submission URL to use for the form - * - * @return URL to submit to - */ - public String getSubmissionUrl() - { - return this.submissionUrl; - } - - /** - * Sets the submission URL the form should use - * - * @param url URL to submit to - */ - public void setSubmissionUrl(String url) - { - this.submissionUrl = url; - } - - /** - * Returns the list of field definitions for the form - * - * @return List of FieldDefinition objects or null if there are no fields - */ - public List getFieldDefinitions() - { - return this.fieldDefinitions; - } - - public List getFieldDefinitionNames() - { - List result = new ArrayList(fieldDefinitions.size()); - for (FieldDefinition fieldDefn : fieldDefinitions) - { - result.add(fieldDefn.getName()); - } - return result; - } - - /** - * Sets the list of FieldDefinition objects representing the fields the - * form is able to display - * - * @param fieldDefinitions List of FieldDefinition objects - */ - public void setFieldDefinitions(List fieldDefinitions) - { - this.fieldDefinitions = fieldDefinitions; - } - - /** - * Adds the given FieldDefinition to the form. - *

- * NOTE: Multiple fields with the same name can be added to the list, - * it is therefore the form processor and the client of the - * FormService responsibility to differentiate the fields in - * some way i.e. by type, property vs. association. - * - * @param definition The FieldDefinition to add - */ - public void addFieldDefinition(FieldDefinition definition) - { - if (this.fieldDefinitions == null) - { - this.fieldDefinitions = new ArrayList(8); - } - - this.fieldDefinitions.add(definition); - } - - /** - * Returns the collection of field groups for the form - * - * @return Collection of FieldGroup objects or null if there are no groups - */ - public Collection getFieldGroups() - { - return this.fieldGroups; - } - - /** - * Sets the collection of FieldGroup objects representing the groups of - * fields the form should display and maintain - * - * @param fieldGroups Collection of FieldGroup objects - */ - public void setFieldGroups(Collection fieldGroups) - { - this.fieldGroups = fieldGroups; - } - - /** - * Returns the data to display in the form - * - * @return FormData object holding the data of the form or null - * if there is no data i.e. for a create form - */ - public FormData getFormData() - { - return this.data; - } - - /** - * Sets the data this form should display. This will overwrite - * any existing form data being held - * - * @param data FormData instance containing the data - */ - public void setFormData(FormData data) - { - this.data = data; - } - - /** - * Returns true if the Form contains {@link FieldData} for the - * specified dataKey. - * - * @param dataKey The dataKey for the field. - * @return boolean - */ - public boolean dataExists(String dataKey) - { - if(data == null) - return false; - return data.getFieldNames().contains(dataKey); - } - - /** - * Adds some data to be displayed by the form - * - * @param fieldName Name of the field the data is for - * @param fieldData The value - */ - public void addData(String fieldName, Object fieldData) - { - if (this.data == null) - { - this.data = new FormData(); - } - - this.data.addFieldData(fieldName, fieldData); - } - - /** - * Adds a {@link Field} to the form by adding the {@link FieldDefinition} - * and the value if any. - * - * @param field Field - */ - public void addField(Field field) - { - if (field == null) - { - return; - } - - FieldDefinition fieldDefinition = field.getFieldDefinition(); - addFieldDefinition(fieldDefinition); - Object value = field.getValue(); - - if (value != null) - { - addData(fieldDefinition.getDataKeyName(), value); - } - } - - /** - * Adds a {@link Collection} of {@link Field Fields} to the form by adding the {@link FieldDefinition FieldDefinitions} - * and the values if any. - */ - public void addFields(Collection fields) - { - for (Field field : fields) - { - addField(field); - } - } - - /* - * @see java.lang.Object#toString() - */ - @Override - public String toString() - { - StringBuilder buffer = new StringBuilder(super.toString()); - buffer.append(" ("); - buffer.append("item=").append(this.item); - buffer.append(", submissionUrl=").append(this.submissionUrl); - buffer.append(", fieldGroups=").append(this.fieldGroups); - buffer.append("\nfieldDefinitions=").append(this.fieldDefinitions); - buffer.append("\nformData=").append(this.data); - buffer.append(")"); - return buffer.toString(); - } -} +package org.alfresco.repo.forms; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.alfresco.repo.forms.FormData.FieldData; + +/** + * Data representation of a form to be displayed in the UI. + * + * @author Gavin Cornwell + */ +public class Form +{ + protected Item item; + protected String submissionUrl; + protected List fieldDefinitions; + protected Collection fieldGroups; + protected FormData data; + + /** + * Constructs a Form + * + * @param item The item the form is for + */ + public Form(Item item) + { + this.item = item; + } + + /** + * Returns the item the form is for + * + * @return The item + */ + public Item getItem() + { + return this.item; + } + + /** + * Returns the submission URL to use for the form + * + * @return URL to submit to + */ + public String getSubmissionUrl() + { + return this.submissionUrl; + } + + /** + * Sets the submission URL the form should use + * + * @param url URL to submit to + */ + public void setSubmissionUrl(String url) + { + this.submissionUrl = url; + } + + /** + * Returns the list of field definitions for the form + * + * @return List of FieldDefinition objects or null if there are no fields + */ + public List getFieldDefinitions() + { + return this.fieldDefinitions; + } + + public List getFieldDefinitionNames() + { + List result = new ArrayList(fieldDefinitions.size()); + for (FieldDefinition fieldDefn : fieldDefinitions) + { + result.add(fieldDefn.getName()); + } + return result; + } + + /** + * Sets the list of FieldDefinition objects representing the fields the + * form is able to display + * + * @param fieldDefinitions List of FieldDefinition objects + */ + public void setFieldDefinitions(List fieldDefinitions) + { + this.fieldDefinitions = fieldDefinitions; + } + + /** + * Adds the given FieldDefinition to the form. + *

+ * NOTE: Multiple fields with the same name can be added to the list, + * it is therefore the form processor and the client of the + * FormService responsibility to differentiate the fields in + * some way i.e. by type, property vs. association. + * + * @param definition The FieldDefinition to add + */ + public void addFieldDefinition(FieldDefinition definition) + { + if (this.fieldDefinitions == null) + { + this.fieldDefinitions = new ArrayList(8); + } + + this.fieldDefinitions.add(definition); + } + + /** + * Returns the collection of field groups for the form + * + * @return Collection of FieldGroup objects or null if there are no groups + */ + public Collection getFieldGroups() + { + return this.fieldGroups; + } + + /** + * Sets the collection of FieldGroup objects representing the groups of + * fields the form should display and maintain + * + * @param fieldGroups Collection of FieldGroup objects + */ + public void setFieldGroups(Collection fieldGroups) + { + this.fieldGroups = fieldGroups; + } + + /** + * Returns the data to display in the form + * + * @return FormData object holding the data of the form or null + * if there is no data i.e. for a create form + */ + public FormData getFormData() + { + return this.data; + } + + /** + * Sets the data this form should display. This will overwrite + * any existing form data being held + * + * @param data FormData instance containing the data + */ + public void setFormData(FormData data) + { + this.data = data; + } + + /** + * Returns true if the Form contains {@link FieldData} for the + * specified dataKey. + * + * @param dataKey The dataKey for the field. + * @return boolean + */ + public boolean dataExists(String dataKey) + { + if(data == null) + return false; + return data.getFieldNames().contains(dataKey); + } + + /** + * Adds some data to be displayed by the form + * + * @param fieldName Name of the field the data is for + * @param fieldData The value + */ + public void addData(String fieldName, Object fieldData) + { + if (this.data == null) + { + this.data = new FormData(); + } + + this.data.addFieldData(fieldName, fieldData); + } + + /** + * Adds a {@link Field} to the form by adding the {@link FieldDefinition} + * and the value if any. + * + * @param field Field + */ + public void addField(Field field) + { + if (field == null) + { + return; + } + + FieldDefinition fieldDefinition = field.getFieldDefinition(); + addFieldDefinition(fieldDefinition); + Object value = field.getValue(); + + if (value != null) + { + addData(fieldDefinition.getDataKeyName(), value); + } + } + + /** + * Adds a {@link Collection} of {@link Field Fields} to the form by adding the {@link FieldDefinition FieldDefinitions} + * and the values if any. + */ + public void addFields(Collection fields) + { + for (Field field : fields) + { + addField(field); + } + } + + /* + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" ("); + buffer.append("item=").append(this.item); + buffer.append(", submissionUrl=").append(this.submissionUrl); + buffer.append(", fieldGroups=").append(this.fieldGroups); + buffer.append("\nfieldDefinitions=").append(this.fieldDefinitions); + buffer.append("\nformData=").append(this.data); + buffer.append(")"); + return buffer.toString(); + } +} diff --git a/source/java/org/alfresco/repo/forms/FormData.java b/source/java/org/alfresco/repo/forms/FormData.java index 162bba2c37..066ad58dfc 100644 --- a/source/java/org/alfresco/repo/forms/FormData.java +++ b/source/java/org/alfresco/repo/forms/FormData.java @@ -1,287 +1,287 @@ -package org.alfresco.repo.forms; - -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.extensions.webscripts.servlet.FormData.FormField; - -/** - * Represents the data going to or coming from a Form. - * - * @author Gavin Cornwell - */ -public class FormData implements Iterable -{ - protected Map data; - - /** - * Default constructor - */ - public FormData() - { - this.data = new LinkedHashMap(8); - } - - /** - * Determines whether field data for the given item exists. - * - * @param fieldName Name of field to look for - * @return true if the field exists, false otherwise - */ - public boolean hasFieldData(String fieldName) - { - return this.data.containsKey(fieldName); - } - - /** - * Returns the data for the given field. - * - * @param fieldName Name of field to look for - * @return FieldData object representing the data for - * the field or null if it doesn't exist - */ - public FieldData getFieldData(String fieldName) - { - return this.data.get(fieldName); - } - - /** - * Adds the given data to the form. - *

- * NOTE: Adding the same named data will append the value and - * thereafter return a List containing all added values. - *

- * - * @param fieldName The name of the field - * @param fieldValue The value of the data - */ - public void addFieldData(String fieldName, Object fieldValue) - { - this.addFieldData(fieldName, fieldValue, false); - } - - /** - * Adds the given webscript FormField object to the form. - * - * @param field A WebScript FormField object - */ - public void addFieldData(FormField field) - { - FieldData fieldData = new FieldData(field); - this.data.put(fieldData.getName(), fieldData); - } - - /** - * Adds the given data to the form. If data for the field is already - * present the behaviour is controlled by the overwrite property. - *

- * If overwrite is true the provided value replaces the existing value - * whereas false will force the creation of a List (if necessary) and the - * provided value will be added to the List. - *

- * - * @param fieldName The name of the field - * @param fieldValue The value of the data - * @param overwrite boolean - */ - @SuppressWarnings("unchecked") - public void addFieldData(String fieldName, Object fieldValue, boolean overwrite) - { - // check whether some data already exists - if (this.data.containsKey(fieldName)) - { - // if we are overwriting just replace with provided data - if (overwrite) - { - this.data.put(fieldName, new FieldData(fieldName, fieldValue, false)); - } - else - { - // pull out the existing value and create a List if necessary - List currentValues = null; - Object currentValue = this.data.get(fieldName).getValue(); - if (currentValue instanceof List) - { - currentValues = (List)currentValue; - } - else - { - // a non List value is present, create the new list - // and add the current value to it - currentValues = new ArrayList(4); - currentValues.add(currentValue); - this.data.put(fieldName, new FieldData(fieldName, currentValues, false)); - } - - // add the provided value to the list - currentValues.add(fieldValue); - } - } - else - { - this.data.put(fieldName, new FieldData(fieldName, fieldValue, false)); - } - } - - /** - * Removes the data associated with the given field - * if it exists. - * - * @param fieldName Name of the field to remove - */ - public void removeFieldData(String fieldName) - { - this.data.remove(fieldName); - } - - /** - * Returns a list of the names of the fields held by this - * object. - * - * @return List of String objects - */ - public Set getFieldNames() - { - return this.data.keySet(); - } - - /** - * Returns the number of fields data is being held for. - * - * @return Number of fields - */ - public int getNumberOfFields() - { - return this.data.size(); - } - - /** - * Returns an Iterator over the FieldData objects - * held by this object. - * - * @return Iterator of FieldData - */ - public Iterator iterator() - { - return this.data.values().iterator(); - } - - /* - * @see java.lang.Object#toString() - */ - @Override - public String toString() - { - StringBuilder buffer = new StringBuilder(super.toString()); - buffer.append(" ("); - buffer.append("data=").append(this.data); - buffer.append(")"); - return buffer.toString(); - } - - /** - * Inner class to represent the value of a field on a form - * - * @author Gavin Cornwell - */ - public class FieldData - { - protected String name; - protected Object value; - protected boolean isFile = false; - protected InputStream is; - - /** - * Default Constructor - * - * @param name The name of the form field - * @param value The value of the form field - * @param isFile Whether the field data represents an uploaded file - */ - public FieldData(String name, Object value, boolean isFile) - { - this.name = name; - this.value = value; - this.isFile = isFile; - } - - /** - * Constructs a FieldData object from a WebScript FormField object - * - * @param field The WebScript FormData object to create the field from - */ - public FieldData(FormField field) - { - this.name = field.getName(); - this.value = field.getValue(); - this.isFile = field.getIsFile(); - - if (isFile) - { - is = field.getInputStream(); - } - } - - /** - * Returns the name of the form field that data represents - * - * @return The name - */ - public String getName() - { - return this.name; - } - - /** - * Returns the value of the form field that data represents - * - * @return The value - */ - public Object getValue() - { - return this.value; - } - - /** - * Determines whether the data represents a file - * - * @return true if the data is a file - */ - public boolean isFile() - { - return this.isFile; - } - - /** - * Returns an InputStream onto the content of the file, - * throws IllegalStateException if this is called for - * non file field data - * - * @return An InputStream onto the file - */ - public InputStream getInputStream() - { - return this.is; - } - - /* - * @see java.lang.Object#toString() - */ - @Override - public String toString() - { - StringBuilder buffer = new StringBuilder(super.toString()); - buffer.append(" ("); - buffer.append("name=").append(this.name); - buffer.append(", value=").append(this.value); - buffer.append(", isFile=").append(this.isFile); - buffer.append(")"); - return buffer.toString(); - } - } -} +package org.alfresco.repo.forms; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.extensions.webscripts.servlet.FormData.FormField; + +/** + * Represents the data going to or coming from a Form. + * + * @author Gavin Cornwell + */ +public class FormData implements Iterable +{ + protected Map data; + + /** + * Default constructor + */ + public FormData() + { + this.data = new LinkedHashMap(8); + } + + /** + * Determines whether field data for the given item exists. + * + * @param fieldName Name of field to look for + * @return true if the field exists, false otherwise + */ + public boolean hasFieldData(String fieldName) + { + return this.data.containsKey(fieldName); + } + + /** + * Returns the data for the given field. + * + * @param fieldName Name of field to look for + * @return FieldData object representing the data for + * the field or null if it doesn't exist + */ + public FieldData getFieldData(String fieldName) + { + return this.data.get(fieldName); + } + + /** + * Adds the given data to the form. + *

+ * NOTE: Adding the same named data will append the value and + * thereafter return a List containing all added values. + *

+ * + * @param fieldName The name of the field + * @param fieldValue The value of the data + */ + public void addFieldData(String fieldName, Object fieldValue) + { + this.addFieldData(fieldName, fieldValue, false); + } + + /** + * Adds the given webscript FormField object to the form. + * + * @param field A WebScript FormField object + */ + public void addFieldData(FormField field) + { + FieldData fieldData = new FieldData(field); + this.data.put(fieldData.getName(), fieldData); + } + + /** + * Adds the given data to the form. If data for the field is already + * present the behaviour is controlled by the overwrite property. + *

+ * If overwrite is true the provided value replaces the existing value + * whereas false will force the creation of a List (if necessary) and the + * provided value will be added to the List. + *

+ * + * @param fieldName The name of the field + * @param fieldValue The value of the data + * @param overwrite boolean + */ + @SuppressWarnings("unchecked") + public void addFieldData(String fieldName, Object fieldValue, boolean overwrite) + { + // check whether some data already exists + if (this.data.containsKey(fieldName)) + { + // if we are overwriting just replace with provided data + if (overwrite) + { + this.data.put(fieldName, new FieldData(fieldName, fieldValue, false)); + } + else + { + // pull out the existing value and create a List if necessary + List currentValues = null; + Object currentValue = this.data.get(fieldName).getValue(); + if (currentValue instanceof List) + { + currentValues = (List)currentValue; + } + else + { + // a non List value is present, create the new list + // and add the current value to it + currentValues = new ArrayList(4); + currentValues.add(currentValue); + this.data.put(fieldName, new FieldData(fieldName, currentValues, false)); + } + + // add the provided value to the list + currentValues.add(fieldValue); + } + } + else + { + this.data.put(fieldName, new FieldData(fieldName, fieldValue, false)); + } + } + + /** + * Removes the data associated with the given field + * if it exists. + * + * @param fieldName Name of the field to remove + */ + public void removeFieldData(String fieldName) + { + this.data.remove(fieldName); + } + + /** + * Returns a list of the names of the fields held by this + * object. + * + * @return List of String objects + */ + public Set getFieldNames() + { + return this.data.keySet(); + } + + /** + * Returns the number of fields data is being held for. + * + * @return Number of fields + */ + public int getNumberOfFields() + { + return this.data.size(); + } + + /** + * Returns an Iterator over the FieldData objects + * held by this object. + * + * @return Iterator of FieldData + */ + public Iterator iterator() + { + return this.data.values().iterator(); + } + + /* + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" ("); + buffer.append("data=").append(this.data); + buffer.append(")"); + return buffer.toString(); + } + + /** + * Inner class to represent the value of a field on a form + * + * @author Gavin Cornwell + */ + public class FieldData + { + protected String name; + protected Object value; + protected boolean isFile = false; + protected InputStream is; + + /** + * Default Constructor + * + * @param name The name of the form field + * @param value The value of the form field + * @param isFile Whether the field data represents an uploaded file + */ + public FieldData(String name, Object value, boolean isFile) + { + this.name = name; + this.value = value; + this.isFile = isFile; + } + + /** + * Constructs a FieldData object from a WebScript FormField object + * + * @param field The WebScript FormData object to create the field from + */ + public FieldData(FormField field) + { + this.name = field.getName(); + this.value = field.getValue(); + this.isFile = field.getIsFile(); + + if (isFile) + { + is = field.getInputStream(); + } + } + + /** + * Returns the name of the form field that data represents + * + * @return The name + */ + public String getName() + { + return this.name; + } + + /** + * Returns the value of the form field that data represents + * + * @return The value + */ + public Object getValue() + { + return this.value; + } + + /** + * Determines whether the data represents a file + * + * @return true if the data is a file + */ + public boolean isFile() + { + return this.isFile; + } + + /** + * Returns an InputStream onto the content of the file, + * throws IllegalStateException if this is called for + * non file field data + * + * @return An InputStream onto the file + */ + public InputStream getInputStream() + { + return this.is; + } + + /* + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" ("); + buffer.append("name=").append(this.name); + buffer.append(", value=").append(this.value); + buffer.append(", isFile=").append(this.isFile); + buffer.append(")"); + return buffer.toString(); + } + } +} diff --git a/source/java/org/alfresco/repo/forms/FormException.java b/source/java/org/alfresco/repo/forms/FormException.java index 0c17a0f567..f189ee504d 100644 --- a/source/java/org/alfresco/repo/forms/FormException.java +++ b/source/java/org/alfresco/repo/forms/FormException.java @@ -1,28 +1,28 @@ -package org.alfresco.repo.forms; - -import org.alfresco.error.AlfrescoRuntimeException; - -/** - * Exception used by the Form services - * - * @author Gavin Cornwell - */ -public class FormException extends AlfrescoRuntimeException -{ - private static final long serialVersionUID = 688834574410335422L; - - public FormException(String msgId) - { - super(msgId); - } - - public FormException(String msgId, Throwable cause) - { - super(msgId, cause); - } - - public FormException(String msgId, Object[] msgParams) - { - super(msgId, msgParams); - } -} +package org.alfresco.repo.forms; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Exception used by the Form services + * + * @author Gavin Cornwell + */ +public class FormException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = 688834574410335422L; + + public FormException(String msgId) + { + super(msgId); + } + + public FormException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + public FormException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } +} diff --git a/source/java/org/alfresco/repo/forms/FormService.java b/source/java/org/alfresco/repo/forms/FormService.java index 53b5e18e04..9ba286ecb8 100644 --- a/source/java/org/alfresco/repo/forms/FormService.java +++ b/source/java/org/alfresco/repo/forms/FormService.java @@ -1,104 +1,104 @@ -package org.alfresco.repo.forms; - -import java.util.List; -import java.util.Map; - - -/** - * Form service API. - *

- * This service API is designed to support the public facing Form APIs - * - * @author Gavin Cornwell - */ -public interface FormService -{ - /** - * Returns a form representation of the given item, - * all known fields for the item are included. - * - * @param item The item to get a form for - * @return The Form representation - */ - public Form getForm(Item item); - - /** - * Returns a form representation of the given item, - * all known fields for the item are included. - * - * @param item The item to get a form for - * @param context Map representing optional context that - * can be used during retrieval of the form - * @return The Form representation - */ - public Form getForm(Item item, Map context); - - /** - * Returns a form representation of the given item consisting - * only of the given fields. - * - * @param item The item to get a form for - * @param fields Restricted list of fields to include, null - * indicates all possible fields for the item - * should be included - * @return The Form representation - */ - public Form getForm(Item item, List fields); - - /** - * Returns a form representation of the given item consisting - * only of the given fields. - * - * @param item The item to get a form for - * @param fields Restricted list of fields to include, null - * indicates all possible fields for the item - * should be included - * @param context Map representing optional context that - * can be used during retrieval of the form - * @return The Form representation - */ - public Form getForm(Item item, List fields, Map context); - - /** - * Returns a form representation of the given item consisting - * only of the given fields. - * - * @param item The item to get a form for - * @param fields Restricted list of fields to include, null - * indicates all possible fields for the item - * should be included - * @param forcedFields List of field names from 'fields' list - * that should be forcibly included, it is - * up to the form processor implementation - * to determine how to enforce this - * @return The Form representation - */ - public Form getForm(Item item, List fields, List forcedFields); - - /** - * Returns a form representation of the given item consisting - * only of the given fields. - * - * @param item The item to get a form for - * @param fields Restricted list of fields to include, null - * indicates all possible fields for the item - * should be included - * @param forcedFields List of field names from 'fields' list - * that should be forcibly included, it is - * up to the form processor implementation - * to determine how to enforce this - * @param context Map representing optional context that - * can be used during retrieval of the form - * @return The Form representation - */ - public Form getForm(Item item, List fields, List forcedFields, Map context); - - /** - * Persists the given form representation for the given item. - * - * @param item The item to persist the form for - * @param data An object representing the form data to persist - * @return The object persisted - */ - public Object saveForm(Item item, FormData data); -} +package org.alfresco.repo.forms; + +import java.util.List; +import java.util.Map; + + +/** + * Form service API. + *

+ * This service API is designed to support the public facing Form APIs + * + * @author Gavin Cornwell + */ +public interface FormService +{ + /** + * Returns a form representation of the given item, + * all known fields for the item are included. + * + * @param item The item to get a form for + * @return The Form representation + */ + public Form getForm(Item item); + + /** + * Returns a form representation of the given item, + * all known fields for the item are included. + * + * @param item The item to get a form for + * @param context Map representing optional context that + * can be used during retrieval of the form + * @return The Form representation + */ + public Form getForm(Item item, Map context); + + /** + * Returns a form representation of the given item consisting + * only of the given fields. + * + * @param item The item to get a form for + * @param fields Restricted list of fields to include, null + * indicates all possible fields for the item + * should be included + * @return The Form representation + */ + public Form getForm(Item item, List fields); + + /** + * Returns a form representation of the given item consisting + * only of the given fields. + * + * @param item The item to get a form for + * @param fields Restricted list of fields to include, null + * indicates all possible fields for the item + * should be included + * @param context Map representing optional context that + * can be used during retrieval of the form + * @return The Form representation + */ + public Form getForm(Item item, List fields, Map context); + + /** + * Returns a form representation of the given item consisting + * only of the given fields. + * + * @param item The item to get a form for + * @param fields Restricted list of fields to include, null + * indicates all possible fields for the item + * should be included + * @param forcedFields List of field names from 'fields' list + * that should be forcibly included, it is + * up to the form processor implementation + * to determine how to enforce this + * @return The Form representation + */ + public Form getForm(Item item, List fields, List forcedFields); + + /** + * Returns a form representation of the given item consisting + * only of the given fields. + * + * @param item The item to get a form for + * @param fields Restricted list of fields to include, null + * indicates all possible fields for the item + * should be included + * @param forcedFields List of field names from 'fields' list + * that should be forcibly included, it is + * up to the form processor implementation + * to determine how to enforce this + * @param context Map representing optional context that + * can be used during retrieval of the form + * @return The Form representation + */ + public Form getForm(Item item, List fields, List forcedFields, Map context); + + /** + * Persists the given form representation for the given item. + * + * @param item The item to persist the form for + * @param data An object representing the form data to persist + * @return The object persisted + */ + public Object saveForm(Item item, FormData data); +} diff --git a/source/java/org/alfresco/repo/forms/FormServiceImpl.java b/source/java/org/alfresco/repo/forms/FormServiceImpl.java index 2c8cb8cfb2..2c4e45b276 100644 --- a/source/java/org/alfresco/repo/forms/FormServiceImpl.java +++ b/source/java/org/alfresco/repo/forms/FormServiceImpl.java @@ -1,123 +1,123 @@ -package org.alfresco.repo.forms; - -import java.util.List; -import java.util.Map; - -import org.alfresco.repo.forms.processor.FormProcessor; -import org.alfresco.repo.forms.processor.FormProcessorRegistry; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Form Service Implementation. - * - * @author Gavin Cornwell - */ -public class FormServiceImpl implements FormService -{ - /** Logger */ - private static Log logger = LogFactory.getLog(FormServiceImpl.class); - - /** Services */ - private FormProcessorRegistry processorRegistry; - - /** - * Sets the FormProcessorRegistry - * - * @param registry The FormProcessorRegistry instance to use - */ - public void setProcessorRegistry(FormProcessorRegistry registry) - { - this.processorRegistry = registry; - } - - /* - * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item) - */ - public Form getForm(Item item) - { - return getForm(item, null, null, null); - } - - /* - * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.Map) - */ - public Form getForm(Item item, Map context) - { - return getForm(item, null, null, context); - } - - /* - * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List) - */ - public Form getForm(Item item, List fields) - { - return getForm(item, fields, null, null); - } - - /* - * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List, java.util.Map) - */ - public Form getForm(Item item, List fields, Map context) - { - return getForm(item, fields, null, context); - } - - /* - * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List, java.util.List) - */ - public Form getForm(Item item, List fields, List forcedFields) - { - return getForm(item, fields, forcedFields, null); - } - - /* - * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List, java.util.List, java.util.Map) - */ - public Form getForm(Item item, List fields, List forcedFields, Map context) - { - if (this.processorRegistry == null) - { - throw new FormException("Property 'processorRegistry' has not been set."); - } - - if (logger.isDebugEnabled()) - logger.debug("Retrieving form for item: " + item); - - FormProcessor processor = this.processorRegistry.getApplicableFormProcessor(item); - - if (processor == null) - { - throw new FormException("Failed to find appropriate FormProcessor to generate Form for item: " + item); - } - else - { - return processor.generate(item, fields, forcedFields, context); - } - } - - /* - * @see org.alfresco.repo.forms.FormService#saveForm(org.alfresco.repo.forms.Item, org.alfresco.repo.forms.FormData) - */ - public Object saveForm(Item item, FormData data) - { - if (this.processorRegistry == null) - { - throw new FormException("FormProcessorRegistry has not been setup"); - } - - if (logger.isDebugEnabled()) - logger.debug("Saving form for item '" + item + "': " + data); - - FormProcessor processor = this.processorRegistry.getApplicableFormProcessor(item); - - if (processor == null) - { - throw new FormException("Failed to find appropriate FormProcessor to persist Form for item: " + item); - } - else - { - return processor.persist(item, data); - } - } -} +package org.alfresco.repo.forms; + +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.forms.processor.FormProcessor; +import org.alfresco.repo.forms.processor.FormProcessorRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Form Service Implementation. + * + * @author Gavin Cornwell + */ +public class FormServiceImpl implements FormService +{ + /** Logger */ + private static Log logger = LogFactory.getLog(FormServiceImpl.class); + + /** Services */ + private FormProcessorRegistry processorRegistry; + + /** + * Sets the FormProcessorRegistry + * + * @param registry The FormProcessorRegistry instance to use + */ + public void setProcessorRegistry(FormProcessorRegistry registry) + { + this.processorRegistry = registry; + } + + /* + * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item) + */ + public Form getForm(Item item) + { + return getForm(item, null, null, null); + } + + /* + * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.Map) + */ + public Form getForm(Item item, Map context) + { + return getForm(item, null, null, context); + } + + /* + * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List) + */ + public Form getForm(Item item, List fields) + { + return getForm(item, fields, null, null); + } + + /* + * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List, java.util.Map) + */ + public Form getForm(Item item, List fields, Map context) + { + return getForm(item, fields, null, context); + } + + /* + * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List, java.util.List) + */ + public Form getForm(Item item, List fields, List forcedFields) + { + return getForm(item, fields, forcedFields, null); + } + + /* + * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List, java.util.List, java.util.Map) + */ + public Form getForm(Item item, List fields, List forcedFields, Map context) + { + if (this.processorRegistry == null) + { + throw new FormException("Property 'processorRegistry' has not been set."); + } + + if (logger.isDebugEnabled()) + logger.debug("Retrieving form for item: " + item); + + FormProcessor processor = this.processorRegistry.getApplicableFormProcessor(item); + + if (processor == null) + { + throw new FormException("Failed to find appropriate FormProcessor to generate Form for item: " + item); + } + else + { + return processor.generate(item, fields, forcedFields, context); + } + } + + /* + * @see org.alfresco.repo.forms.FormService#saveForm(org.alfresco.repo.forms.Item, org.alfresco.repo.forms.FormData) + */ + public Object saveForm(Item item, FormData data) + { + if (this.processorRegistry == null) + { + throw new FormException("FormProcessorRegistry has not been setup"); + } + + if (logger.isDebugEnabled()) + logger.debug("Saving form for item '" + item + "': " + data); + + FormProcessor processor = this.processorRegistry.getApplicableFormProcessor(item); + + if (processor == null) + { + throw new FormException("Failed to find appropriate FormProcessor to persist Form for item: " + item); + } + else + { + return processor.persist(item, data); + } + } +} diff --git a/source/java/org/alfresco/repo/forms/PropertyFieldDefinition.java b/source/java/org/alfresco/repo/forms/PropertyFieldDefinition.java index 6a23d1626a..a060503d29 100644 --- a/source/java/org/alfresco/repo/forms/PropertyFieldDefinition.java +++ b/source/java/org/alfresco/repo/forms/PropertyFieldDefinition.java @@ -1,264 +1,264 @@ -package org.alfresco.repo.forms; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import org.alfresco.repo.dictionary.IndexTokenisationMode; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * A property field definition. - * - * @author Gavin Cornwell - */ -public class PropertyFieldDefinition extends FieldDefinition -{ - /** Logger */ - private static Log logger = LogFactory.getLog(PropertyFieldDefinition.class); - - protected String dataType; - protected DataTypeParameters dataTypeParams; - protected boolean mandatory = false; - protected boolean repeats = false; - protected IndexTokenisationMode indexTokenisationMode = IndexTokenisationMode.TRUE; - protected List constraints; - - /** - * Default constructor - * - * @param name The name of the property - * @param dataType The data type of the property - */ - public PropertyFieldDefinition(String name, String dataType) - { - super(name); - - this.dataType = dataType; - } - - /** - * Returns the dataType for the property, this is a value from the - * Alfresco data dictionary i.e. d:text, d:int etc. - * - * @return The field's data type - */ - public String getDataType() - { - return this.dataType; - } - - /** - * Returns the data type parameters for the field - * - * @return DataTypeParameters object or null - */ - public DataTypeParameters getDataTypeParameters() - { - return this.dataTypeParams; - } - - /** - * Sets the data type parameters for the field - * - * @param dataTypeParams The DataTypeParameters for the field - */ - public void setDataTypeParameters(DataTypeParameters dataTypeParams) - { - this.dataTypeParams = dataTypeParams; - } - - /** - * Determines if the property is mandatory - * - * @return true if the field is mandatory - */ - public boolean isMandatory() - { - return this.mandatory; - } - - /** - * Sets whether the property is mandatory - * - * @param mandatory true if it is mandatory - */ - public void setMandatory(boolean mandatory) - { - this.mandatory = mandatory; - } - - /** - * Determines if the property can contain multiple values - * - * @return true if the field can contain multiple values - */ - public boolean isRepeating() - { - return this.repeats; - } - - /** - * Sets whether the property can contain multiple values - * - * @param repeats true if the field can contain multiple values - */ - public void setRepeating(boolean repeats) - { - this.repeats = repeats; - } - - /** - * Returns a list of constraints the property may have - * - * @return List of FieldContstraint objects or null if there are - * no constraints for the field - */ - public List getConstraints() - { - return this.constraints; - } - - /** - * Sets the list of FieldConstraint objects for the property - * - * @param constraints List of FieldConstraint objects - */ - public void setConstraints(List constraints) - { - this.constraints = constraints; - } - - /** - * Returns a IndexTokenisationMode the property - * - * @return IndexTokenisationMode objects or null - */ - public IndexTokenisationMode getIndexTokenisationMode() - { - return indexTokenisationMode; - } - - /** - * Sets the IndexTokenisationMode objects for the property - * - * @param indexTokenisationMode objects - */ - public void setIndexTokenisationMode(IndexTokenisationMode indexTokenisationMode) - { - this.indexTokenisationMode = indexTokenisationMode; - } - - /* - * @see java.lang.Object#toString() - */ - public String toString() - { - StringBuilder buffer = new StringBuilder(super.toString()); - buffer.append(" ("); - buffer.append("name=").append(this.name); - buffer.append(", dataType=").append(this.dataType); - buffer.append(", dataTypeParams=").append(this.dataTypeParams); - buffer.append(", label=").append(this.label); - buffer.append(", description=").append(this.description); - buffer.append(", binding=").append(this.binding); - buffer.append(", defaultValue=").append(this.defaultValue); - buffer.append(", dataKeyName=").append(this.dataKeyName); - buffer.append(", group=").append(this.group); - buffer.append(", protectedField=").append(this.protectedField); - buffer.append(", mandatory=").append(this.mandatory); - buffer.append(", repeats=").append(this.repeats); - buffer.append(", constraints=").append(this.constraints); - buffer.append(")"); - return buffer.toString(); - } - - /** - * Represents a constraint on a property field - */ - public static class FieldConstraint - { - protected String type; - protected Map params; - - /** - * Constructs a FieldConstraint - * - * @param type The type of the constraint - * @param params Map of parameters for the constraint - */ - public FieldConstraint(String type, Map params) - { - super(); - this.type = type; - this.params = params; - } - - /** - * Returns the type of the constraint - * - * @return The constraint type - */ - public String getType() - { - return this.type; - } - - /** - * Returns the parameters for the constraint - * - * @return Map of parameters for the constraint or null if - * there are no parameters - */ - public Map getParameters() - { - return this.params; - } - - /** - * Returns the paramters for the constraint as a JSONObject - * - * @return JSONObject representation of the parameters - */ - @SuppressWarnings("unchecked") - public JSONObject getParametersAsJSON() - { - JSONObject result = null; - - if (this.params != null) - { - result = new JSONObject(); - - for (String name : this.params.keySet()) - { - try - { - Object value = this.params.get(name); - if (value instanceof Collection) - { - // if the value is a Collection add to JSONObject as a JSONArray - result.put(name, new JSONArray((Collection)value)); - } - else - { - result.put(name, value); - } - } - catch (JSONException je) - { - // just log a warning - if (logger.isWarnEnabled()) - logger.warn("Failed to add constraint parameter '" + name + - "' to JSON object.", je); - } - } - } - - return result; - } - } -} +package org.alfresco.repo.forms; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.dictionary.IndexTokenisationMode; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * A property field definition. + * + * @author Gavin Cornwell + */ +public class PropertyFieldDefinition extends FieldDefinition +{ + /** Logger */ + private static Log logger = LogFactory.getLog(PropertyFieldDefinition.class); + + protected String dataType; + protected DataTypeParameters dataTypeParams; + protected boolean mandatory = false; + protected boolean repeats = false; + protected IndexTokenisationMode indexTokenisationMode = IndexTokenisationMode.TRUE; + protected List constraints; + + /** + * Default constructor + * + * @param name The name of the property + * @param dataType The data type of the property + */ + public PropertyFieldDefinition(String name, String dataType) + { + super(name); + + this.dataType = dataType; + } + + /** + * Returns the dataType for the property, this is a value from the + * Alfresco data dictionary i.e. d:text, d:int etc. + * + * @return The field's data type + */ + public String getDataType() + { + return this.dataType; + } + + /** + * Returns the data type parameters for the field + * + * @return DataTypeParameters object or null + */ + public DataTypeParameters getDataTypeParameters() + { + return this.dataTypeParams; + } + + /** + * Sets the data type parameters for the field + * + * @param dataTypeParams The DataTypeParameters for the field + */ + public void setDataTypeParameters(DataTypeParameters dataTypeParams) + { + this.dataTypeParams = dataTypeParams; + } + + /** + * Determines if the property is mandatory + * + * @return true if the field is mandatory + */ + public boolean isMandatory() + { + return this.mandatory; + } + + /** + * Sets whether the property is mandatory + * + * @param mandatory true if it is mandatory + */ + public void setMandatory(boolean mandatory) + { + this.mandatory = mandatory; + } + + /** + * Determines if the property can contain multiple values + * + * @return true if the field can contain multiple values + */ + public boolean isRepeating() + { + return this.repeats; + } + + /** + * Sets whether the property can contain multiple values + * + * @param repeats true if the field can contain multiple values + */ + public void setRepeating(boolean repeats) + { + this.repeats = repeats; + } + + /** + * Returns a list of constraints the property may have + * + * @return List of FieldContstraint objects or null if there are + * no constraints for the field + */ + public List getConstraints() + { + return this.constraints; + } + + /** + * Sets the list of FieldConstraint objects for the property + * + * @param constraints List of FieldConstraint objects + */ + public void setConstraints(List constraints) + { + this.constraints = constraints; + } + + /** + * Returns a IndexTokenisationMode the property + * + * @return IndexTokenisationMode objects or null + */ + public IndexTokenisationMode getIndexTokenisationMode() + { + return indexTokenisationMode; + } + + /** + * Sets the IndexTokenisationMode objects for the property + * + * @param indexTokenisationMode objects + */ + public void setIndexTokenisationMode(IndexTokenisationMode indexTokenisationMode) + { + this.indexTokenisationMode = indexTokenisationMode; + } + + /* + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" ("); + buffer.append("name=").append(this.name); + buffer.append(", dataType=").append(this.dataType); + buffer.append(", dataTypeParams=").append(this.dataTypeParams); + buffer.append(", label=").append(this.label); + buffer.append(", description=").append(this.description); + buffer.append(", binding=").append(this.binding); + buffer.append(", defaultValue=").append(this.defaultValue); + buffer.append(", dataKeyName=").append(this.dataKeyName); + buffer.append(", group=").append(this.group); + buffer.append(", protectedField=").append(this.protectedField); + buffer.append(", mandatory=").append(this.mandatory); + buffer.append(", repeats=").append(this.repeats); + buffer.append(", constraints=").append(this.constraints); + buffer.append(")"); + return buffer.toString(); + } + + /** + * Represents a constraint on a property field + */ + public static class FieldConstraint + { + protected String type; + protected Map params; + + /** + * Constructs a FieldConstraint + * + * @param type The type of the constraint + * @param params Map of parameters for the constraint + */ + public FieldConstraint(String type, Map params) + { + super(); + this.type = type; + this.params = params; + } + + /** + * Returns the type of the constraint + * + * @return The constraint type + */ + public String getType() + { + return this.type; + } + + /** + * Returns the parameters for the constraint + * + * @return Map of parameters for the constraint or null if + * there are no parameters + */ + public Map getParameters() + { + return this.params; + } + + /** + * Returns the paramters for the constraint as a JSONObject + * + * @return JSONObject representation of the parameters + */ + @SuppressWarnings("unchecked") + public JSONObject getParametersAsJSON() + { + JSONObject result = null; + + if (this.params != null) + { + result = new JSONObject(); + + for (String name : this.params.keySet()) + { + try + { + Object value = this.params.get(name); + if (value instanceof Collection) + { + // if the value is a Collection add to JSONObject as a JSONArray + result.put(name, new JSONArray((Collection)value)); + } + else + { + result.put(name, value); + } + } + catch (JSONException je) + { + // just log a warning + if (logger.isWarnEnabled()) + logger.warn("Failed to add constraint parameter '" + name + + "' to JSON object.", je); + } + } + } + + return result; + } + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/AbstractFilter.java b/source/java/org/alfresco/repo/forms/processor/AbstractFilter.java index 84c222c7f4..c61956fa1c 100644 --- a/source/java/org/alfresco/repo/forms/processor/AbstractFilter.java +++ b/source/java/org/alfresco/repo/forms/processor/AbstractFilter.java @@ -1,74 +1,74 @@ -package org.alfresco.repo.forms.processor; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Abstract base class for all Filter implementations. - * - * @author Gavin Cornwell - */ -public abstract class AbstractFilter implements Filter -{ - private static final Log logger = LogFactory.getLog(AbstractFilter.class); - - protected FilterRegistry filterRegistry; - protected boolean active = true; - - /** - * Sets the filter registry - * - * @param filterRegistry The FilterRegistry instance - */ - public void setFilterRegistry(FilterRegistry filterRegistry) - { - this.filterRegistry = filterRegistry; - } - - /** - * Sets whether this filter is active - * - * @param active true if the filter should be active - */ - public void setActive(boolean active) - { - this.active = active; - } - - /** - * Registers this filter with the filter registry - */ - public void register() - { - if (filterRegistry == null) - { - if (logger.isWarnEnabled()) - logger.warn("Property 'filterRegistry' has not been set. Ignoring auto-registration of filter: " + this); - - return; - } - - // register this instance - filterRegistry.addFilter(this); - } - - /* - * @see org.alfresco.repo.forms.processor.Filter#isActive() - */ - public boolean isActive() - { - return this.active; - } - - /* - * @see java.lang.Object#toString() - */ - public String toString() - { - StringBuilder buffer = new StringBuilder(super.toString()); - buffer.append(" ("); - buffer.append("active=").append(this.isActive()); - buffer.append(")"); - return buffer.toString(); - } -} +package org.alfresco.repo.forms.processor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract base class for all Filter implementations. + * + * @author Gavin Cornwell + */ +public abstract class AbstractFilter implements Filter +{ + private static final Log logger = LogFactory.getLog(AbstractFilter.class); + + protected FilterRegistry filterRegistry; + protected boolean active = true; + + /** + * Sets the filter registry + * + * @param filterRegistry The FilterRegistry instance + */ + public void setFilterRegistry(FilterRegistry filterRegistry) + { + this.filterRegistry = filterRegistry; + } + + /** + * Sets whether this filter is active + * + * @param active true if the filter should be active + */ + public void setActive(boolean active) + { + this.active = active; + } + + /** + * Registers this filter with the filter registry + */ + public void register() + { + if (filterRegistry == null) + { + if (logger.isWarnEnabled()) + logger.warn("Property 'filterRegistry' has not been set. Ignoring auto-registration of filter: " + this); + + return; + } + + // register this instance + filterRegistry.addFilter(this); + } + + /* + * @see org.alfresco.repo.forms.processor.Filter#isActive() + */ + public boolean isActive() + { + return this.active; + } + + /* + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" ("); + buffer.append("active=").append(this.isActive()); + buffer.append(")"); + return buffer.toString(); + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessor.java index e0c8bed44b..8a39b59cb6 100644 --- a/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessor.java @@ -1,160 +1,160 @@ - -package org.alfresco.repo.forms.processor; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.repo.forms.Form; -import org.alfresco.repo.forms.Item; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Abstract base class for all FormProcessor implementations provides a regex - * pattern match to test for processor applicability - * - * @author Gavin Cornwell - */ -public abstract class AbstractFormProcessor implements FormProcessor -{ - public static final String DESTINATION = "alf_destination"; - - private static final Log logger = LogFactory.getLog(AbstractFormProcessor.class); - - protected FormProcessorRegistry processorRegistry; - - protected String matchPattern; - - protected boolean active = true; - - protected Pattern patternMatcher; - - /** - * Sets the form process registry - * - * @param processorRegistry The FormProcessorRegistry instance - */ - public void setProcessorRegistry(FormProcessorRegistry processorRegistry) - { - this.processorRegistry = processorRegistry; - } - - /** - * Sets the match pattern - * - * @param pattern The regex pattern to use to determine if this processor is - * applicable - */ - public void setMatchPattern(String pattern) - { - this.matchPattern = pattern; - } - - /** - * Sets whether this processor is active - * - * @param active true if the processor should be active - */ - public void setActive(boolean active) - { - this.active = active; - } - - /** - * Registers this processor with the processor registry - */ - public void register() - { - if (this.processorRegistry == null) - { - if (logger.isWarnEnabled()) - logger.warn("Property 'processorRegistry' has not been set. Ignoring auto-registration of processor: " - + this); - - return; - } - - if (this.matchPattern == null) - { - if (logger.isWarnEnabled()) - logger.warn("Property 'matchPattern' has not been set. Ignoring auto-registration of processor: " - + this); - - return; - } - else - { - // setup pattern matcher - this.patternMatcher = Pattern.compile(this.matchPattern); - } - - // register this instance - this.processorRegistry.addProcessor(this); - } - - /* - * @see org.alfresco.repo.forms.processor.FormProcessor#isActive() - */ - public boolean isActive() - { - return this.active; - } - - /* - * @see - * org.alfresco.repo.forms.processor.FormProcessor#isApplicable(org.alfresco - * .repo.forms.Item) - */ - public boolean isApplicable(Item item) - { - // this form processor matches if the match pattern provided matches - // the kind of the item provided - - Matcher matcher = patternMatcher.matcher(item.getKind()); - boolean matches = matcher.matches(); - - if (logger.isDebugEnabled()) - logger.debug("Checking processor " + this + " for applicability for item '" + item + "', result = " - + matches); - - return matches; - } - - /* - * @see java.lang.Object#toString() - */ - public String toString() - { - StringBuilder buffer = new StringBuilder(super.toString()); - buffer.append(" ("); - buffer.append("active=").append(this.active); - buffer.append(", matchPattern=").append(this.matchPattern); - buffer.append(")"); - return buffer.toString(); - } - - /** - * Gets the Item from the form parameter and sets its type - * field to type. - * - * @param form Form - * @param type String - */ - protected void setFormItemType(Form form, String type) - { - form.getItem().setType(type); - } - - /** - * Gets the Item from the form parameter and sets its URL field - * to url. - * - * @param form Form - * @param url String - */ - protected void setFormItemUrl(Form form, String url) - { - form.getItem().setUrl(url); - } - -} + +package org.alfresco.repo.forms.processor; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.repo.forms.Form; +import org.alfresco.repo.forms.Item; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract base class for all FormProcessor implementations provides a regex + * pattern match to test for processor applicability + * + * @author Gavin Cornwell + */ +public abstract class AbstractFormProcessor implements FormProcessor +{ + public static final String DESTINATION = "alf_destination"; + + private static final Log logger = LogFactory.getLog(AbstractFormProcessor.class); + + protected FormProcessorRegistry processorRegistry; + + protected String matchPattern; + + protected boolean active = true; + + protected Pattern patternMatcher; + + /** + * Sets the form process registry + * + * @param processorRegistry The FormProcessorRegistry instance + */ + public void setProcessorRegistry(FormProcessorRegistry processorRegistry) + { + this.processorRegistry = processorRegistry; + } + + /** + * Sets the match pattern + * + * @param pattern The regex pattern to use to determine if this processor is + * applicable + */ + public void setMatchPattern(String pattern) + { + this.matchPattern = pattern; + } + + /** + * Sets whether this processor is active + * + * @param active true if the processor should be active + */ + public void setActive(boolean active) + { + this.active = active; + } + + /** + * Registers this processor with the processor registry + */ + public void register() + { + if (this.processorRegistry == null) + { + if (logger.isWarnEnabled()) + logger.warn("Property 'processorRegistry' has not been set. Ignoring auto-registration of processor: " + + this); + + return; + } + + if (this.matchPattern == null) + { + if (logger.isWarnEnabled()) + logger.warn("Property 'matchPattern' has not been set. Ignoring auto-registration of processor: " + + this); + + return; + } + else + { + // setup pattern matcher + this.patternMatcher = Pattern.compile(this.matchPattern); + } + + // register this instance + this.processorRegistry.addProcessor(this); + } + + /* + * @see org.alfresco.repo.forms.processor.FormProcessor#isActive() + */ + public boolean isActive() + { + return this.active; + } + + /* + * @see + * org.alfresco.repo.forms.processor.FormProcessor#isApplicable(org.alfresco + * .repo.forms.Item) + */ + public boolean isApplicable(Item item) + { + // this form processor matches if the match pattern provided matches + // the kind of the item provided + + Matcher matcher = patternMatcher.matcher(item.getKind()); + boolean matches = matcher.matches(); + + if (logger.isDebugEnabled()) + logger.debug("Checking processor " + this + " for applicability for item '" + item + "', result = " + + matches); + + return matches; + } + + /* + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" ("); + buffer.append("active=").append(this.active); + buffer.append(", matchPattern=").append(this.matchPattern); + buffer.append(")"); + return buffer.toString(); + } + + /** + * Gets the Item from the form parameter and sets its type + * field to type. + * + * @param form Form + * @param type String + */ + protected void setFormItemType(Form form, String type) + { + form.getItem().setType(type); + } + + /** + * Gets the Item from the form parameter and sets its URL field + * to url. + * + * @param form Form + * @param url String + */ + protected void setFormItemUrl(Form form, String url) + { + form.getItem().setUrl(url); + } + +} diff --git a/source/java/org/alfresco/repo/forms/processor/Filter.java b/source/java/org/alfresco/repo/forms/processor/Filter.java index 103aacdd22..b5bdcf19b6 100644 --- a/source/java/org/alfresco/repo/forms/processor/Filter.java +++ b/source/java/org/alfresco/repo/forms/processor/Filter.java @@ -1,94 +1,94 @@ -package org.alfresco.repo.forms.processor; - -import java.util.List; -import java.util.Map; - -import org.alfresco.api.AlfrescoPublicApi; -import org.alfresco.repo.forms.Form; -import org.alfresco.repo.forms.FormData; - -/** - * Interface definition for a filter which is called before and after - * a form is generated and persisted. - * - * @author Gavin Cornwell - */ -@AlfrescoPublicApi -public interface Filter -{ - /** - * Determines whether the filter is active - * - * @return true if the filter is active - */ - public boolean isActive(); - - /** - * Callback used to indicate that a form is about to be generated for - * the given items and fields. - * - *

- * NOTE: Filters all relating to the same type of form can cast the Object - * to a more appropriate object, for example all the Node based handlers - * can expect a NodeRef object and therefore cast to that. - * - * @param item The item to generate a Form for - * @param fields Restricted list of fields to include - * @param forcedFields List of fields to forcibly include - * @param form The Form object - * @param context Map representing optional context that - * can be used during retrieval of the form - */ - public void beforeGenerate(ItemType item, List fields, List forcedFields, - Form form, Map context); - - /** - * Callback used to indicate that a form has just been generated for - * the given items and fields. - * - *

- * NOTE: Filters all relating to the same type of form can cast the Object - * to a more appropriate object, for example all the Node based handlers - * can expect a NodeRef object and therefore cast to that. - * - * @param item The item to generate a Form for - * @param fields Restricted list of fields to include - * @param forcedFields List of fields to forcibly include - * @param form The Form object - * @param context Map representing optional context that - * can be used during retrieval of the form - */ - public void afterGenerate(ItemType item, List fields, List forcedFields, - Form form, Map context); - - /** - * Callback used to indicate that the given form data is about to be - * persisted for the given item. - * - *

- * NOTE: Filters all relating to the same type of form can cast the item Object - * to a more appropriate object, for example all the Node based handlers - * can expect a NodeRef object and therefore cast to that. - * - * @param item The item to persist the form data for - * @param data The form data - */ - public void beforePersist(ItemType item, FormData data); - - /** - * Callback used to indicate that the given form data was just persisted - * for the item and the given persistedObject was created or modified. - * - *

- * NOTE: Filters all relating to the same type of form can cast the item - * and persistedObject Objects to a more appropriate object, for example - * all the Node based handlers can expect a NodeRef object and therefore - * cast to that. - * - * @param item The item to persist the form data for - * @param data The form data - * @param persistedObject The object created or modified as a result of - * the form persistence - */ - public void afterPersist(ItemType item, FormData data, PersistType persistedObject); -} +package org.alfresco.repo.forms.processor; + +import java.util.List; +import java.util.Map; + +import org.alfresco.api.AlfrescoPublicApi; +import org.alfresco.repo.forms.Form; +import org.alfresco.repo.forms.FormData; + +/** + * Interface definition for a filter which is called before and after + * a form is generated and persisted. + * + * @author Gavin Cornwell + */ +@AlfrescoPublicApi +public interface Filter +{ + /** + * Determines whether the filter is active + * + * @return true if the filter is active + */ + public boolean isActive(); + + /** + * Callback used to indicate that a form is about to be generated for + * the given items and fields. + * + *

+ * NOTE: Filters all relating to the same type of form can cast the Object + * to a more appropriate object, for example all the Node based handlers + * can expect a NodeRef object and therefore cast to that. + * + * @param item The item to generate a Form for + * @param fields Restricted list of fields to include + * @param forcedFields List of fields to forcibly include + * @param form The Form object + * @param context Map representing optional context that + * can be used during retrieval of the form + */ + public void beforeGenerate(ItemType item, List fields, List forcedFields, + Form form, Map context); + + /** + * Callback used to indicate that a form has just been generated for + * the given items and fields. + * + *

+ * NOTE: Filters all relating to the same type of form can cast the Object + * to a more appropriate object, for example all the Node based handlers + * can expect a NodeRef object and therefore cast to that. + * + * @param item The item to generate a Form for + * @param fields Restricted list of fields to include + * @param forcedFields List of fields to forcibly include + * @param form The Form object + * @param context Map representing optional context that + * can be used during retrieval of the form + */ + public void afterGenerate(ItemType item, List fields, List forcedFields, + Form form, Map context); + + /** + * Callback used to indicate that the given form data is about to be + * persisted for the given item. + * + *

+ * NOTE: Filters all relating to the same type of form can cast the item Object + * to a more appropriate object, for example all the Node based handlers + * can expect a NodeRef object and therefore cast to that. + * + * @param item The item to persist the form data for + * @param data The form data + */ + public void beforePersist(ItemType item, FormData data); + + /** + * Callback used to indicate that the given form data was just persisted + * for the item and the given persistedObject was created or modified. + * + *

+ * NOTE: Filters all relating to the same type of form can cast the item + * and persistedObject Objects to a more appropriate object, for example + * all the Node based handlers can expect a NodeRef object and therefore + * cast to that. + * + * @param item The item to persist the form data for + * @param data The form data + * @param persistedObject The object created or modified as a result of + * the form persistence + */ + public void afterPersist(ItemType item, FormData data, PersistType persistedObject); +} diff --git a/source/java/org/alfresco/repo/forms/processor/FilterRegistry.java b/source/java/org/alfresco/repo/forms/processor/FilterRegistry.java index 2ae9d37580..4403d45c7a 100644 --- a/source/java/org/alfresco/repo/forms/processor/FilterRegistry.java +++ b/source/java/org/alfresco/repo/forms/processor/FilterRegistry.java @@ -1,80 +1,80 @@ - -package org.alfresco.repo.forms.processor; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Holds a list of filters for a type of form processor. - *

- * Each filter is called before and after the processor generates and persists - * the form, thus allowing the form and the effected objects to be manipulated - * prior to generation or persistence or after the fact. - *

- *

- * Each filter is responsible for determing whether it applies to the item being - * processed. - *

- * - * @see org.alfresco.repo.forms.processor.Filter - * @author Gavin Cornwell - */ -public class FilterRegistry -{ - private static final Log logger = LogFactory.getLog(FilterRegistry.class); - - protected List> filters; - - /** - * Constructs the registry - */ - public FilterRegistry() - { - this.filters = new ArrayList>(4); - } - - /** - * Registers a filter - * - * @param filter The Filter to regsiter - */ - public void addFilter(Filter filter) - { - if (filter.isActive()) - { - this.filters.add(filter); - - if (logger.isDebugEnabled()) logger.debug("Registered filter: " + filter + " in registry: " + this); - } - else if (logger.isWarnEnabled()) - { - logger.warn("Ignored registration of filter " + filter + " as it was marked as inactive"); - } - } - - /** - * Returns a list of active filters - * - * @return List of active Filter objects - */ - public List> getFilters() - { - List> activeFilters = new ArrayList>(4); - - // iterate round the filters and add each active filter to the list - for (Filter filter : this.filters) - { - if (filter.isActive()) - { - activeFilters.add(filter); - } - } - - if (logger.isDebugEnabled()) logger.debug("Returning active filters: " + activeFilters); - - return activeFilters; - } -} + +package org.alfresco.repo.forms.processor; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Holds a list of filters for a type of form processor. + *

+ * Each filter is called before and after the processor generates and persists + * the form, thus allowing the form and the effected objects to be manipulated + * prior to generation or persistence or after the fact. + *

+ *

+ * Each filter is responsible for determing whether it applies to the item being + * processed. + *

+ * + * @see org.alfresco.repo.forms.processor.Filter + * @author Gavin Cornwell + */ +public class FilterRegistry +{ + private static final Log logger = LogFactory.getLog(FilterRegistry.class); + + protected List> filters; + + /** + * Constructs the registry + */ + public FilterRegistry() + { + this.filters = new ArrayList>(4); + } + + /** + * Registers a filter + * + * @param filter The Filter to regsiter + */ + public void addFilter(Filter filter) + { + if (filter.isActive()) + { + this.filters.add(filter); + + if (logger.isDebugEnabled()) logger.debug("Registered filter: " + filter + " in registry: " + this); + } + else if (logger.isWarnEnabled()) + { + logger.warn("Ignored registration of filter " + filter + " as it was marked as inactive"); + } + } + + /** + * Returns a list of active filters + * + * @return List of active Filter objects + */ + public List> getFilters() + { + List> activeFilters = new ArrayList>(4); + + // iterate round the filters and add each active filter to the list + for (Filter filter : this.filters) + { + if (filter.isActive()) + { + activeFilters.add(filter); + } + } + + if (logger.isDebugEnabled()) logger.debug("Returning active filters: " + activeFilters); + + return activeFilters; + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/FilteredFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/FilteredFormProcessor.java index 7608bcdae8..6dffd45687 100644 --- a/source/java/org/alfresco/repo/forms/processor/FilteredFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/FilteredFormProcessor.java @@ -1,286 +1,286 @@ - -package org.alfresco.repo.forms.processor; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.alfresco.repo.forms.Field; -import org.alfresco.repo.forms.Form; -import org.alfresco.repo.forms.FormData; -import org.alfresco.repo.forms.Item; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Abstract base class for all FormProcessor implementations that wish to use - * the filter mechanism. - * - * @author Gavin Cornwell - * @author Nick Smith - */ -public abstract class FilteredFormProcessor extends AbstractFormProcessor -{ - private static final Log logger = LogFactory.getLog(FilteredFormProcessor.class); - - private List ignoredFields = null; - - protected FilterRegistry filterRegistry; - - protected FieldProcessorRegistry fieldProcessorRegistry; - - /** - * Sets the filter registry - * - * @param filterRegistry The FilterRegistry instance - */ - public void setFilterRegistry(FilterRegistry filterRegistry) - { - this.filterRegistry = filterRegistry; - - if (logger.isDebugEnabled()) - logger.debug("Set filter registry: " + this.filterRegistry + " for processor: " + this); - } - - /** - * @param ignoredFields the ignoredFields to set - */ - public void setIgnoredFields(List ignoredFields) - { - this.ignoredFields = ignoredFields; - } - - /** - * {@inheritDoc} - */ - public Form generate(Item item, List fields, List forcedFields, Map context) - { - // get the typed object representing the item - ItemType typedItem = getTypedItem(item); - - // create an empty Form - Form form = new Form(item); - - // inform all regsitered filters the form is about to be generated - if (this.filterRegistry != null) - { - for (Filter filter : this.filterRegistry.getFilters()) - { - filter.beforeGenerate(typedItem, fields, forcedFields, form, context); - } - } - - // perform the actual generation of the form - internalGenerate(typedItem, fields, forcedFields, form, context); - - // inform all regsitered filters the form has been generated - if (this.filterRegistry != null) - { - for (Filter filter : this.filterRegistry.getFilters()) - { - filter.afterGenerate(typedItem, fields, forcedFields, form, context); - } - } - return form; - } - - /** - * Persists the given form data for the given item, completed by calling - * each applicable registered handler - * - * @see org.alfresco.repo.forms.processor.FormProcessor#persist(org.alfresco.repo.forms.Item, - * org.alfresco.repo.forms.FormData) - * @param item The item to save the form for - * @param data The object representing the form data - * @return The object persisted - */ - public Object persist(Item item, FormData data) - { - // get the typed object representing the item - ItemType typedItem = getTypedItem(item); - - // inform all regsitered filters the form is about to be persisted - if (this.filterRegistry != null) - { - for (Filter filter : this.filterRegistry.getFilters()) - { - filter.beforePersist(typedItem, data); - } - } - - // perform the actual persistence of the form - PersistType persistedObject = internalPersist(typedItem, data); - - // inform all regsitered filters the form has been persisted - if (this.filterRegistry != null) - { - for (Filter filter : this.filterRegistry.getFilters()) - { - filter.afterPersist(typedItem, data, persistedObject); - } - } - - return persistedObject; - } - - /** - * Generates the form. - * - * @param item The object to generate a form for - * @param fields Restricted list of fields to include - * @param forcedFields List of fields to forcibly include - * @param form The form object being generated - * @param context Map representing optional context that can be used during - * retrieval of the form - */ - protected void internalGenerate(ItemType item, List fields, List forcedFields, Form form, Map context) - { - Log log = getLogger(); - if (log.isDebugEnabled()) log.debug("Generating form for: " + item); - - // generate the form type and URI for the item. - Item formItem = form.getItem(); - formItem.setType(getItemType(item)); - formItem.setUrl(getItemURI(item)); - - Object itemData = makeItemData(item); - FormCreationData data = new FormCreationDataImpl(itemData, forcedFields, context); - populateForm(form, fields, data); - if (log.isDebugEnabled()) // - log.debug("Generated form: " + form); - } - - /** - * This method generates all the fields to be added and adds them to the Form, together with the associated field data. - * @param form The {@link Form} to which the fields are added. - * @param fields The names of all the fields to be added. - * @param data {@link FormCreationData} used to generate all the fields. - */ - protected void populateForm(Form form, List fields, FormCreationData data) - { - List fieldsToAdd; - if (fields != null && fields.size() > 0) - { - fieldsToAdd = generateSelectedFields(fields, data); - } - else - { - fieldsToAdd = generateDefaultFields(data, getIgnoredFields()); - } - form.addFields(fieldsToAdd); - } - - private List getIgnoredFields() - { - if (ignoredFields != null) - { - return ignoredFields; - } - - return getDefaultIgnoredFields(); - } - - /** - * Generates a list of default fields to add if no field names are specified. - * @param data Used for field creation. - * @param fieldsToIgnore TODO - * @return a {@link List} of {@link Field Fields} which may be empty. - */ - protected List generateDefaultFields(FormCreationData data, List fieldsToIgnore) - { - return Collections.emptyList(); - } - - protected List generateSelectedFields(List fields, FormCreationData data) - { - List fieldData = new ArrayList(fields.size()); - for (String fieldName : fields) - { - Field field = fieldProcessorRegistry.buildField(fieldName, data); - if (field == null) - { - if (getLogger().isDebugEnabled()) - { - String msg = "Ignoring unrecognised field \"" + fieldName + "\""; - getLogger().debug(msg); - } - } - else - { - fieldData.add(field); - } - } - return fieldData; - } - - - /** - * Sets the field processor registry. - * - * @param fieldProcessorRegistry - * The {@link FieldProcessorRegistry} to use. - */ - public void setFieldProcessorRegistry(FieldProcessorRegistry fieldProcessorRegistry) - { - this.fieldProcessorRegistry = fieldProcessorRegistry; - } - - /** - * Creates a data object used by the {@link FormProcessor} and {@link FieldProcessor FieldProcessors} to create {@link Field Fields} - * @return Object - */ - protected abstract Object makeItemData(ItemType item); - - /** - * Returns a typed Object representing the given item. - *

- * Subclasses that represent a form type will return a typed object that is - * then passed to each of it's handlers, the handlers can therefore safely - * cast the Object to the type they expect. - * - * @param item The item to get a typed object for - * @return The typed object - */ - protected abstract ItemType getTypedItem(Item item); - - /** - * Retrieves a logger instance to log to. - * - * @return Log instance to log to. - */ - protected abstract Log getLogger(); - - /** - * Returns a {@link String} describing the type fo the specified item. - * @param item ItemType - * @return String - */ - protected abstract String getItemType(ItemType item); - - /** - * Returns the URI location of the specified item. - * @param item ItemType - * @return String - */ - protected abstract String getItemURI(ItemType item); - - /** - * Persists the form data. - * - * @param item The object to persist the form for - * @param data The data to persist - * @return The object that got created or modified - */ - protected abstract PersistType internalPersist(ItemType item, FormData data); - - /** - * When a {@link Form} is generated with no field names specifically set then a default {@link Form} is created. - * The default {@link Form} contains all the properties and associations related to the {@link Item}, excluding a - * blacklist of ignored fields which defaults to the return value of this method. - * The default ignored values can be overridden by setting the property ignoredFields. - * - * @return the names of all the fields to be excluded from the default {@link Form} if no defaultFields property is explicitly set. - */ - protected abstract List getDefaultIgnoredFields(); -} + +package org.alfresco.repo.forms.processor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.forms.Field; +import org.alfresco.repo.forms.Form; +import org.alfresco.repo.forms.FormData; +import org.alfresco.repo.forms.Item; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract base class for all FormProcessor implementations that wish to use + * the filter mechanism. + * + * @author Gavin Cornwell + * @author Nick Smith + */ +public abstract class FilteredFormProcessor extends AbstractFormProcessor +{ + private static final Log logger = LogFactory.getLog(FilteredFormProcessor.class); + + private List ignoredFields = null; + + protected FilterRegistry filterRegistry; + + protected FieldProcessorRegistry fieldProcessorRegistry; + + /** + * Sets the filter registry + * + * @param filterRegistry The FilterRegistry instance + */ + public void setFilterRegistry(FilterRegistry filterRegistry) + { + this.filterRegistry = filterRegistry; + + if (logger.isDebugEnabled()) + logger.debug("Set filter registry: " + this.filterRegistry + " for processor: " + this); + } + + /** + * @param ignoredFields the ignoredFields to set + */ + public void setIgnoredFields(List ignoredFields) + { + this.ignoredFields = ignoredFields; + } + + /** + * {@inheritDoc} + */ + public Form generate(Item item, List fields, List forcedFields, Map context) + { + // get the typed object representing the item + ItemType typedItem = getTypedItem(item); + + // create an empty Form + Form form = new Form(item); + + // inform all regsitered filters the form is about to be generated + if (this.filterRegistry != null) + { + for (Filter filter : this.filterRegistry.getFilters()) + { + filter.beforeGenerate(typedItem, fields, forcedFields, form, context); + } + } + + // perform the actual generation of the form + internalGenerate(typedItem, fields, forcedFields, form, context); + + // inform all regsitered filters the form has been generated + if (this.filterRegistry != null) + { + for (Filter filter : this.filterRegistry.getFilters()) + { + filter.afterGenerate(typedItem, fields, forcedFields, form, context); + } + } + return form; + } + + /** + * Persists the given form data for the given item, completed by calling + * each applicable registered handler + * + * @see org.alfresco.repo.forms.processor.FormProcessor#persist(org.alfresco.repo.forms.Item, + * org.alfresco.repo.forms.FormData) + * @param item The item to save the form for + * @param data The object representing the form data + * @return The object persisted + */ + public Object persist(Item item, FormData data) + { + // get the typed object representing the item + ItemType typedItem = getTypedItem(item); + + // inform all regsitered filters the form is about to be persisted + if (this.filterRegistry != null) + { + for (Filter filter : this.filterRegistry.getFilters()) + { + filter.beforePersist(typedItem, data); + } + } + + // perform the actual persistence of the form + PersistType persistedObject = internalPersist(typedItem, data); + + // inform all regsitered filters the form has been persisted + if (this.filterRegistry != null) + { + for (Filter filter : this.filterRegistry.getFilters()) + { + filter.afterPersist(typedItem, data, persistedObject); + } + } + + return persistedObject; + } + + /** + * Generates the form. + * + * @param item The object to generate a form for + * @param fields Restricted list of fields to include + * @param forcedFields List of fields to forcibly include + * @param form The form object being generated + * @param context Map representing optional context that can be used during + * retrieval of the form + */ + protected void internalGenerate(ItemType item, List fields, List forcedFields, Form form, Map context) + { + Log log = getLogger(); + if (log.isDebugEnabled()) log.debug("Generating form for: " + item); + + // generate the form type and URI for the item. + Item formItem = form.getItem(); + formItem.setType(getItemType(item)); + formItem.setUrl(getItemURI(item)); + + Object itemData = makeItemData(item); + FormCreationData data = new FormCreationDataImpl(itemData, forcedFields, context); + populateForm(form, fields, data); + if (log.isDebugEnabled()) // + log.debug("Generated form: " + form); + } + + /** + * This method generates all the fields to be added and adds them to the Form, together with the associated field data. + * @param form The {@link Form} to which the fields are added. + * @param fields The names of all the fields to be added. + * @param data {@link FormCreationData} used to generate all the fields. + */ + protected void populateForm(Form form, List fields, FormCreationData data) + { + List fieldsToAdd; + if (fields != null && fields.size() > 0) + { + fieldsToAdd = generateSelectedFields(fields, data); + } + else + { + fieldsToAdd = generateDefaultFields(data, getIgnoredFields()); + } + form.addFields(fieldsToAdd); + } + + private List getIgnoredFields() + { + if (ignoredFields != null) + { + return ignoredFields; + } + + return getDefaultIgnoredFields(); + } + + /** + * Generates a list of default fields to add if no field names are specified. + * @param data Used for field creation. + * @param fieldsToIgnore TODO + * @return a {@link List} of {@link Field Fields} which may be empty. + */ + protected List generateDefaultFields(FormCreationData data, List fieldsToIgnore) + { + return Collections.emptyList(); + } + + protected List generateSelectedFields(List fields, FormCreationData data) + { + List fieldData = new ArrayList(fields.size()); + for (String fieldName : fields) + { + Field field = fieldProcessorRegistry.buildField(fieldName, data); + if (field == null) + { + if (getLogger().isDebugEnabled()) + { + String msg = "Ignoring unrecognised field \"" + fieldName + "\""; + getLogger().debug(msg); + } + } + else + { + fieldData.add(field); + } + } + return fieldData; + } + + + /** + * Sets the field processor registry. + * + * @param fieldProcessorRegistry + * The {@link FieldProcessorRegistry} to use. + */ + public void setFieldProcessorRegistry(FieldProcessorRegistry fieldProcessorRegistry) + { + this.fieldProcessorRegistry = fieldProcessorRegistry; + } + + /** + * Creates a data object used by the {@link FormProcessor} and {@link FieldProcessor FieldProcessors} to create {@link Field Fields} + * @return Object + */ + protected abstract Object makeItemData(ItemType item); + + /** + * Returns a typed Object representing the given item. + *

+ * Subclasses that represent a form type will return a typed object that is + * then passed to each of it's handlers, the handlers can therefore safely + * cast the Object to the type they expect. + * + * @param item The item to get a typed object for + * @return The typed object + */ + protected abstract ItemType getTypedItem(Item item); + + /** + * Retrieves a logger instance to log to. + * + * @return Log instance to log to. + */ + protected abstract Log getLogger(); + + /** + * Returns a {@link String} describing the type fo the specified item. + * @param item ItemType + * @return String + */ + protected abstract String getItemType(ItemType item); + + /** + * Returns the URI location of the specified item. + * @param item ItemType + * @return String + */ + protected abstract String getItemURI(ItemType item); + + /** + * Persists the form data. + * + * @param item The object to persist the form for + * @param data The data to persist + * @return The object that got created or modified + */ + protected abstract PersistType internalPersist(ItemType item, FormData data); + + /** + * When a {@link Form} is generated with no field names specifically set then a default {@link Form} is created. + * The default {@link Form} contains all the properties and associations related to the {@link Item}, excluding a + * blacklist of ignored fields which defaults to the return value of this method. + * The default ignored values can be overridden by setting the property ignoredFields. + * + * @return the names of all the fields to be excluded from the default {@link Form} if no defaultFields property is explicitly set. + */ + protected abstract List getDefaultIgnoredFields(); +} diff --git a/source/java/org/alfresco/repo/forms/processor/FormProcessor.java b/source/java/org/alfresco/repo/forms/processor/FormProcessor.java index 729334890e..a24bfeea7b 100644 --- a/source/java/org/alfresco/repo/forms/processor/FormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/FormProcessor.java @@ -1,65 +1,65 @@ -package org.alfresco.repo.forms.processor; - -import java.util.List; -import java.util.Map; - -import org.alfresco.api.AlfrescoPublicApi; -import org.alfresco.repo.forms.Form; -import org.alfresco.repo.forms.FormData; -import org.alfresco.repo.forms.Item; - -/** - * Interface definition of a form processor which is responsible - * for generating a Form representation of a data source, for example a - * repository node, a task or an XML schema and for persisting the - * form data back to the data source. - * - * @author Gavin Cornwell - */ -@AlfrescoPublicApi -public interface FormProcessor -{ - /** - * Determines whether this form processor is applicable for - * the supplied item - * - * @param item The item the form is being generated for - * @return true if the processor is applicable - */ - public boolean isApplicable(Item item); - - /** - * Determines whether this form processor is active - * - * @return true if the processor is active - */ - public boolean isActive(); - - /** - * Returns a Form representation for an item - * - * @param item The item to generate a Form object for - * @param fields Restricted list of fields to include, null - * indicates all possible fields for the item - * should be included - * @param forcedFields List of field names from 'fields' list - * that should be forcibly included, it is - * up to the form processor implementation - * to determine how to enforce this - * @param context Map representing optional context that - * can be used during retrieval of the form - * @return The Form representation - */ - public Form generate(Item item, List fields, List forcedFields, - Map context); - - /** - * Persists the given object representing the form data - * for an item - * - * @param item The item to generate a Form object for - * @param data An object representing the data of the form - * @return The object persisted - */ - public Object persist(Item item, FormData data); -} +package org.alfresco.repo.forms.processor; + +import java.util.List; +import java.util.Map; + +import org.alfresco.api.AlfrescoPublicApi; +import org.alfresco.repo.forms.Form; +import org.alfresco.repo.forms.FormData; +import org.alfresco.repo.forms.Item; + +/** + * Interface definition of a form processor which is responsible + * for generating a Form representation of a data source, for example a + * repository node, a task or an XML schema and for persisting the + * form data back to the data source. + * + * @author Gavin Cornwell + */ +@AlfrescoPublicApi +public interface FormProcessor +{ + /** + * Determines whether this form processor is applicable for + * the supplied item + * + * @param item The item the form is being generated for + * @return true if the processor is applicable + */ + public boolean isApplicable(Item item); + + /** + * Determines whether this form processor is active + * + * @return true if the processor is active + */ + public boolean isActive(); + + /** + * Returns a Form representation for an item + * + * @param item The item to generate a Form object for + * @param fields Restricted list of fields to include, null + * indicates all possible fields for the item + * should be included + * @param forcedFields List of field names from 'fields' list + * that should be forcibly included, it is + * up to the form processor implementation + * to determine how to enforce this + * @param context Map representing optional context that + * can be used during retrieval of the form + * @return The Form representation + */ + public Form generate(Item item, List fields, List forcedFields, + Map context); + + /** + * Persists the given object representing the form data + * for an item + * + * @param item The item to generate a Form object for + * @param data An object representing the data of the form + * @return The object persisted + */ + public Object persist(Item item, FormData data); +} diff --git a/source/java/org/alfresco/repo/forms/processor/FormProcessorRegistry.java b/source/java/org/alfresco/repo/forms/processor/FormProcessorRegistry.java index 15ad1ef32d..5b3c953ee8 100644 --- a/source/java/org/alfresco/repo/forms/processor/FormProcessorRegistry.java +++ b/source/java/org/alfresco/repo/forms/processor/FormProcessorRegistry.java @@ -1,85 +1,85 @@ -package org.alfresco.repo.forms.processor; - -import java.util.ArrayList; -import java.util.List; - -import org.alfresco.repo.forms.Item; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Holds a FormProcessor implementation for each of the types of form that - * can be processed. By default a node, type, task, and workflow form processor - * are available. - *

- * Given an item the registry selects the relevant form processor, the match - * is performed via pattern matching on the supplied string. - * - * @author Gavin Cornwell - */ -public class FormProcessorRegistry -{ - /** Logger */ - private static Log logger = LogFactory.getLog(FormProcessorRegistry.class); - - protected List processors; - - /** - * Constructs the registry - */ - public FormProcessorRegistry() - { - this.processors = new ArrayList(4); - } - - /** - * Registers a form processor - * - * @param processor The FormProcessor to regsiter - */ - public void addProcessor(FormProcessor processor) - { - if (processor.isActive()) - { - this.processors.add(processor); - - if (logger.isDebugEnabled()) - logger.debug("Registered processor: " + processor); - } - else if (logger.isWarnEnabled()) - { - logger.warn("Ignored registration of processor " + processor + "as it was marked as inactive"); - } - } - - /** - * Returns a FormProcessor for the provided item. - *

- * Each registered processors is asked if it is applicable for - * the given item, the first processor to positively respond - * that is also active is selected and returned. - * - * @param item The item to find a form processor for - * @return An applicable FormProcessor - */ - public FormProcessor getApplicableFormProcessor(Item item) - { - FormProcessor selectedProcessor = null; - - // iterate round the processors and fall out once the first - // active applicable processor is found - for (FormProcessor processor : this.processors) - { - if (processor.isActive() && processor.isApplicable(item)) - { - selectedProcessor = processor; - break; - } - } - - if (logger.isDebugEnabled()) - logger.debug("Returning applicable processor: " + selectedProcessor); - - return selectedProcessor; - } -} +package org.alfresco.repo.forms.processor; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.repo.forms.Item; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Holds a FormProcessor implementation for each of the types of form that + * can be processed. By default a node, type, task, and workflow form processor + * are available. + *

+ * Given an item the registry selects the relevant form processor, the match + * is performed via pattern matching on the supplied string. + * + * @author Gavin Cornwell + */ +public class FormProcessorRegistry +{ + /** Logger */ + private static Log logger = LogFactory.getLog(FormProcessorRegistry.class); + + protected List processors; + + /** + * Constructs the registry + */ + public FormProcessorRegistry() + { + this.processors = new ArrayList(4); + } + + /** + * Registers a form processor + * + * @param processor The FormProcessor to regsiter + */ + public void addProcessor(FormProcessor processor) + { + if (processor.isActive()) + { + this.processors.add(processor); + + if (logger.isDebugEnabled()) + logger.debug("Registered processor: " + processor); + } + else if (logger.isWarnEnabled()) + { + logger.warn("Ignored registration of processor " + processor + "as it was marked as inactive"); + } + } + + /** + * Returns a FormProcessor for the provided item. + *

+ * Each registered processors is asked if it is applicable for + * the given item, the first processor to positively respond + * that is also active is selected and returned. + * + * @param item The item to find a form processor for + * @return An applicable FormProcessor + */ + public FormProcessor getApplicableFormProcessor(Item item) + { + FormProcessor selectedProcessor = null; + + // iterate round the processors and fall out once the first + // active applicable processor is found + for (FormProcessor processor : this.processors) + { + if (processor.isActive() && processor.isApplicable(item)) + { + selectedProcessor = processor; + break; + } + } + + if (logger.isDebugEnabled()) + logger.debug("Returning applicable processor: " + selectedProcessor); + + return selectedProcessor; + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/action/ActionFormResult.java b/source/java/org/alfresco/repo/forms/processor/action/ActionFormResult.java index df60f5fa71..028fc2c0c3 100644 --- a/source/java/org/alfresco/repo/forms/processor/action/ActionFormResult.java +++ b/source/java/org/alfresco/repo/forms/processor/action/ActionFormResult.java @@ -1,61 +1,61 @@ -package org.alfresco.repo.forms.processor.action; - -import org.alfresco.service.cmr.action.Action; -import org.alfresco.util.ParameterCheck; - -/** - * Class used purely to represent the result of an action being - * executed via the {@link ActionFormProcessor}. - * - * This class holds the {@link Action} executed and any optional - * results stored by the action. - * - * @author Gavin Cornwell - */ -public class ActionFormResult -{ - private Action action; - private Object result; - - /** - * Default constructor. - * - * @param action The action that was executed, can not be null - * @param result The result from the action, can be null - */ - public ActionFormResult(Action action, Object result) - { - ParameterCheck.mandatory("action", action); - - this.action = action; - this.result = result; - } - - /** - * Returns the action that was executed - * - * @return The executed Action - */ - public Action getAction() - { - return this.action; - } - - /** - * Returns the result from the executed action - * - * @return The result or null if there were no results - */ - public Object getResult() - { - return this.result; - } - - @Override - public String toString() - { - StringBuilder builder = new StringBuilder(this.action.toString()); - builder.append(" result=").append(this.result); - return builder.toString(); - } -} +package org.alfresco.repo.forms.processor.action; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.util.ParameterCheck; + +/** + * Class used purely to represent the result of an action being + * executed via the {@link ActionFormProcessor}. + * + * This class holds the {@link Action} executed and any optional + * results stored by the action. + * + * @author Gavin Cornwell + */ +public class ActionFormResult +{ + private Action action; + private Object result; + + /** + * Default constructor. + * + * @param action The action that was executed, can not be null + * @param result The result from the action, can be null + */ + public ActionFormResult(Action action, Object result) + { + ParameterCheck.mandatory("action", action); + + this.action = action; + this.result = result; + } + + /** + * Returns the action that was executed + * + * @return The executed Action + */ + public Action getAction() + { + return this.action; + } + + /** + * Returns the result from the executed action + * + * @return The result or null if there were no results + */ + public Object getResult() + { + return this.result; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(this.action.toString()); + builder.append(" result=").append(this.result); + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java index 335e45d3af..ac3523d46c 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java @@ -1,972 +1,972 @@ -package org.alfresco.repo.forms.processor.node; - -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ASSOC_DATA_ADDED_SUFFIX; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ASSOC_DATA_PREFIX; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ASSOC_DATA_REMOVED_SUFFIX; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.DEFAULT_CONTENT_MIMETYPE; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.DOT_CHARACTER; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.DOT_CHARACTER_REPLACEMENT; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ON; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP_DATA_PREFIX; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.model.ContentModel; -import org.alfresco.model.RenditionModel; -import org.alfresco.repo.forms.Field; -import org.alfresco.repo.forms.Form; -import org.alfresco.repo.forms.FormData; -import org.alfresco.repo.forms.FormData.FieldData; -import org.alfresco.repo.forms.FormException; -import org.alfresco.repo.forms.processor.FilteredFormProcessor; -import org.alfresco.repo.forms.processor.FormCreationData; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; -import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; -import org.alfresco.service.cmr.dictionary.ConstraintDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.model.FileExistsException; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONArray; -import org.json.JSONException; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * Abstract FormProcessor implementation that provides common functionality for - * form processors that deal with Alfresco content models i.e. types and nodes. - * - * @author Gavin Cornwell - * @author Nick Smith - */ -public abstract class ContentModelFormProcessor extends - FilteredFormProcessor -{ - /** Services */ - protected NodeService nodeService; - - protected FileFolderService fileFolderService; - - protected DictionaryService dictionaryService; - - protected NamespaceService namespaceService; - - protected ContentService contentService; - - protected PermissionService permissionService; - - /** - * A regular expression which can be used to match property names. These - * names will look like "prop_cm_name". The pattern can also be - * used to extract the "cm" and the "name" parts. - */ - protected Pattern propertyNamePattern = Pattern.compile(PROP_DATA_PREFIX + "([a-zA-Z0-9-]+)_(.*)"); - - /** - * A regular expression which can be used to match tranisent property names. - * These names will look like "prop_name". The pattern can also - * be used to extract the "name" part. - */ - protected Pattern transientPropertyPattern = Pattern.compile(PROP_DATA_PREFIX + "(.*){1}?"); - - /** - * A regular expression which can be used to match association names. These - * names will look like "assoc_cm_references_added". The - * pattern can also be used to extract the "cm", the "name" and the suffix - * parts. - */ - protected Pattern associationNamePattern = Pattern.compile(ASSOC_DATA_PREFIX + "([a-zA-Z0-9-]+)_(.*)(_[a-zA-Z]+)"); - - /** - * Sets the node service - * - * @param nodeService The NodeService instance - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Sets the file folder service - * - * @param fileFolderService The FileFolderService instance - */ - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - /** - * Sets the data dictionary service - * - * @param dictionaryService The DictionaryService instance - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Sets the namespace service - * - * @param namespaceService The NamespaceService instance - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * Sets the content service - * - * @param contentService The ContentService instance - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * Sets the content service - * - * @param permissionService The PermissionService instance - */ - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - protected void addPropertyDataIfRequired(QName propName, Form form, ContentModelItemData itemData) - { - String dataKey = makePropDataKey(propName); - if (form.dataExists(dataKey) == false) - { - PropertyFieldProcessor processor = new PropertyFieldProcessor(namespaceService, dictionaryService); - Object value = processor.getValue(propName, itemData); - form.addData(dataKey, value); - } - } - - private String makePropDataKey(QName propName) - { - String propPrefixName = propName.toPrefixString(namespaceService); - String dataKey = FormFieldConstants.PROP_DATA_PREFIX + propPrefixName.replace(':', '_'); - return dataKey; - } - - @Override - protected List generateDefaultFields(FormCreationData data, List fieldsToIgnore) - { - DefaultFieldBuilder defaultFieldBuilder = - new DefaultFieldBuilder(data, fieldProcessorRegistry, namespaceService, fieldsToIgnore); - return defaultFieldBuilder.buildDefaultFields(); - } - - @Override - protected ContentModelItemData makeItemData(ItemType item) - { - TypeDefinition baseType = getBaseType(item); - Set aspects = getAspectNames(item); - TypeDefinition anonType = dictionaryService.getAnonymousType(baseType.getName(), aspects); - Map propDefs = anonType.getProperties(); - Map assocDefs = anonType.getAssociations(); - Map propValues = getPropertyValues(item); - Map assocValues = getAssociationValues(item); - Map transientValues = getTransientValues(item); - return new ContentModelItemData(item, propDefs, assocDefs, propValues, assocValues, transientValues); - } - - protected List getDefaultIgnoredFields() - { - ArrayList fields = new ArrayList(8); - - // ignore system properties by default - fields.add(ContentModel.PROP_NODE_DBID.toPrefixString(this.namespaceService)); - fields.add(ContentModel.PROP_NODE_UUID.toPrefixString(this.namespaceService)); - fields.add(ContentModel.PROP_STORE_IDENTIFIER.toPrefixString(this.namespaceService)); - fields.add(ContentModel.PROP_STORE_PROTOCOL.toPrefixString(this.namespaceService)); - - // ignore associations that are system maintained - fields.add(RenditionModel.ASSOC_RENDITION.toPrefixString(this.namespaceService)); - - return fields; - } - - protected Set getAspectNames(ItemType item) - { - return getBaseType(item).getDefaultAspectNames(); - } - - protected abstract Map getAssociationValues(ItemType item); - - protected abstract Map getPropertyValues(ItemType item); - - protected abstract Map getTransientValues(ItemType item); - - protected abstract TypeDefinition getBaseType(ItemType item); - - /** - * Persists the given FormData on the given NodeRef - * - * @param nodeRef The NodeRef to persist the form data on - * @param data The FormData to persist - */ - protected void persistNode(NodeRef nodeRef, FormData data) - { - // get the property definitions for the type of node being persisted - QName type = this.nodeService.getType(nodeRef); - TypeDefinition typeDef = this.dictionaryService.getAnonymousType(type, this.nodeService.getAspects(nodeRef)); - Map assocDefs = typeDef.getAssociations(); - Map childAssocDefs = typeDef.getChildAssociations(); - Map propDefs = typeDef.getProperties(); - - Map propsToPersist = new HashMap(data.getNumberOfFields()); - List assocsToPersist = new ArrayList(); - - for (FieldData fieldData : data) - { - // NOTE: ignore file fields for now, not supported yet! - if (fieldData.isFile() == false) - { - String fieldName = fieldData.getName(); - - if (fieldName.startsWith(PROP_DATA_PREFIX)) - { - processPropertyPersist(nodeRef, propDefs, fieldData, propsToPersist, data); - } - else if (fieldName.startsWith(ASSOC_DATA_PREFIX)) - { - processAssociationPersist(nodeRef, assocDefs, childAssocDefs, fieldData, assocsToPersist); - } - else if (getLogger().isWarnEnabled()) - { - getLogger().warn("Ignoring unrecognised field '" + fieldName + "'"); - } - } - } - - // persist the properties using addProperties as this changes the repo - // values of - // those properties included in the Map, but leaves any other property - // values unchanged, - // whereas setProperties causes the deletion of properties that are not - // included in the Map. - this.nodeService.addProperties(nodeRef, propsToPersist); - - for (AbstractAssocCommand cmd : assocsToPersist) - { - // TODO If there is an attempt to add and remove the same assoc in - // one request, - // we could drop each request and do nothing. - cmd.updateAssociations(nodeService); - } - } - - /** - * Processes the given field data for persistence as a property. - * - * @param nodeRef The NodeRef to persist the properties on - * @param propDefs Map of PropertyDefinition's for the node being persisted - * @param fieldData Data to persist for the property - * @param propsToPersist Map of properties to be persisted - * @param data The FormData to persist - */ - protected void processPropertyPersist(NodeRef nodeRef, Map propDefs, - FieldData fieldData, Map propsToPersist, FormData data) - { - if (getLogger().isDebugEnabled()) - getLogger().debug("Processing field " + fieldData + " for property persistence"); - - // match and extract the prefix and name parts - Matcher m = this.propertyNamePattern.matcher(fieldData.getName().replaceAll(DOT_CHARACTER_REPLACEMENT, DOT_CHARACTER)); - if (m.matches()) - { - String qNamePrefix = m.group(1); - String localName = m.group(2); - QName fullQName = QName.createQName(qNamePrefix, localName, namespaceService); - - // ensure that the property being persisted is defined in the model - PropertyDefinition propDef = propDefs.get(fullQName); - - // if the property is not defined on the node, check for the - // property in all models - if (propDef == null) - { - propDef = this.dictionaryService.getProperty(fullQName); - } - - // if we have a property definition attempt the persist - if (propDef != null) - { - // look for properties that have well known handling - // requirements - if (fullQName.equals(ContentModel.PROP_NAME)) - { - processNamePropertyPersist(nodeRef, fieldData, propsToPersist); - } - else if (propDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) - { - processContentPropertyPersist(nodeRef, fieldData, propsToPersist, data); - } - else - { - Object value = fieldData.getValue(); - - // before persisting check data type of property - if (propDef.isMultiValued()) - { - // depending on client the value could be a comma - // separated - // string, a List object or a JSONArray object - if (value instanceof String) - { - if (((String) value).length() == 0) - { - // empty string for multi-valued properties - // should be stored as null - value = null; - } - else - { - // if value is a String convert to List of - // String - List list = Arrays.asList(((String)value).split(",", -1)); - - // persist the List - value = list; - } - } - else if (value instanceof JSONArray) - { - // if value is a JSONArray convert to List of Object - JSONArray jsonArr = (JSONArray) value; - int arrLength = jsonArr.length(); - List list = new ArrayList(arrLength); - try - { - for (int x = 0; x < arrLength; x++) - { - list.add(jsonArr.get(x)); - } - } - catch (JSONException je) - { - throw new FormException("Failed to convert JSONArray to List", je); - } - - // persist the list - value = list; - } - } - else if (propDef.getDataType().getName().equals(DataTypeDefinition.BOOLEAN)) - { - // check for browser representation of true, that being "on" - if (value instanceof String && ON.equals(value)) - { - value = Boolean.TRUE; - } - } - else if (propDef.getDataType().getName().equals(DataTypeDefinition.LOCALE)) - { - value = I18NUtil.parseLocale((String) value); - } - else if ((value instanceof String) && ((String) value).length() == 0) - { - // make sure empty strings stay as empty strings, - // everything else should be represented as null - if (!propDef.getDataType().getName().equals(DataTypeDefinition.TEXT) && - !propDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT)) - { - value = null; - } - else - { - // if the text property has a regex constraint set the empty - // string to null otherwise the integrity checker will reject it - List constraints = propDef.getConstraints(); - if (constraints != null && constraints.size() > 0) - { - for (ConstraintDefinition constraintDef : constraints) - { - if ("REGEX".equals(constraintDef.getConstraint().getType())) - { - value = null; - break; - } - } - } - } - } - - // add the property to the map - propsToPersist.put(fullQName, (Serializable) value); - } - } - else if (getLogger().isWarnEnabled()) - { - getLogger().warn("Ignoring field '" + fieldData.getName() + "' as a property definition can not be found"); - } - } - else - { - // the field is potentially a well know transient property - // check for the ones we know about, anything else is ignored - Matcher tppm = this.transientPropertyPattern.matcher(fieldData.getName()); - if (tppm.matches()) - { - String fieldName = tppm.group(1); - - if (fieldName.equals(MimetypeFieldProcessor.KEY)) - { - processMimetypePropertyPersist(nodeRef, fieldData, propsToPersist); - } - else if (fieldName.equals(EncodingFieldProcessor.KEY)) - { - processEncodingPropertyPersist(nodeRef, fieldData, propsToPersist); - } - else if (fieldName.equals(SizeFieldProcessor.KEY)) - { - // the size property is well known but should never be persisted - // as it is calculated so this is intentionally ignored - } - else if (getLogger().isWarnEnabled()) - { - getLogger().warn("Ignoring unrecognised field '" + fieldData.getName() + "'"); - } - } - else if (getLogger().isWarnEnabled()) - { - getLogger().warn("Ignoring unrecognised field '" + fieldData.getName() + "'"); - } - } - } - - /** - * Processes the given field data for persistence as an association. - * - * @param nodeRef The NodeRef to persist the associations on - * @param fieldData Data to persist for the associations - * @param assocCommands List of associations to be persisted - */ - protected void processAssociationPersist(NodeRef nodeRef, Map assocDefs, - Map childAssocDefs, FieldData fieldData, - List assocCommands) - { - if (getLogger().isDebugEnabled()) - getLogger().debug("Processing field " + fieldData + " for association persistence"); - - String fieldName = fieldData.getName(); - Matcher m = this.associationNamePattern.matcher(fieldName.replaceAll(DOT_CHARACTER_REPLACEMENT, DOT_CHARACTER)); - if (m.matches()) - { - String qNamePrefix = m.group(1); - String localName = m.group(2); - String assocSuffix = m.group(3); - - QName fullQName = QName.createQName(qNamePrefix, localName, namespaceService); - - // ensure that the association being persisted is defined in the model - AssociationDefinition assocDef = assocDefs.get(fullQName); - - // If the association is not defined on this node i.e. it is not defined for this node's content - // type and is not defined for any of the aspects that have already been applied to this node, - // then assocDef here will be null. This is because assocDef is passed in to this method, its value - // having been obtained from dictionaryService.getAnonymousType() - // - // However if the association type is defined on an aspect which has not yet been applied to this node - // then setting a value for this association should lead to the automatic application of the aspect defining it. - if (assocDef == null) - { - // Is it defined on any other type? - AssociationDefinition assocDefFromDictionary = this.dictionaryService.getAssociation(fullQName); - - // If the association is defined on any *aspect* type... - if (assocDefFromDictionary != null && assocDefFromDictionary.getSourceClass().isAspect()) - { - // ... then it should be safe to proceed with applying the association value. - assocDef = assocDefFromDictionary; - } - else - { - // ... else it is either undefined in the dictionary service or is defined on a concrete - // content type which is inconsistent with the node's type. We'll ignore it, which is what - // has been done with unhandled associations up to this point. - if (getLogger().isWarnEnabled()) - { - getLogger().warn("Ignoring field '" + fieldName + "' as a valid association definition can not be found"); - } - - return; - } - } - - String value = (String) fieldData.getValue(); - String[] nodeRefs = value.split(","); - - // Each element in this array will be a new target node in association - // with the current node. - for (String nextTargetNode : nodeRefs) - { - if (nextTargetNode.length() > 0) - { - if (NodeRef.isNodeRef(nextTargetNode)) - { - if (assocSuffix.equals(ASSOC_DATA_ADDED_SUFFIX)) - { - if (assocDef.isChild()) - { - assocCommands.add(new AddChildAssocCommand(nodeRef, new NodeRef(nextTargetNode), - fullQName)); - } - else - { - assocCommands.add(new AddAssocCommand(nodeRef, new NodeRef(nextTargetNode), - fullQName)); - } - } - else if (assocSuffix.equals(ASSOC_DATA_REMOVED_SUFFIX)) - { - if (assocDef.isChild()) - { - assocCommands.add(new RemoveChildAssocCommand(nodeRef, new NodeRef(nextTargetNode), - fullQName)); - } - else - { - assocCommands.add(new RemoveAssocCommand(nodeRef, new NodeRef(nextTargetNode), - fullQName)); - } - } - else - { - if (getLogger().isWarnEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Ignoring 'fieldName ").append(fieldName).append( - "' as it does not have one of the expected suffixes (").append( - ASSOC_DATA_ADDED_SUFFIX).append(" or ").append(ASSOC_DATA_REMOVED_SUFFIX) - .append(")"); - getLogger().warn(msg.toString()); - } - } - } - else - { - if (getLogger().isWarnEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("targetNode ").append(nextTargetNode).append( - " is not a valid NodeRef and has been ignored."); - getLogger().warn(msg.toString()); - } - } - } - } - } - else if (getLogger().isWarnEnabled()) - { - getLogger().warn("Ignoring unrecognised field '" + fieldName + "'"); - } - } - - /** - * Persists the given field data as the name property - * - * @param nodeRef The NodeRef to update the name for - * @param fieldData The data representing the new name value - * @param propsToPersist Map of properties to be persisted - */ - protected void processNamePropertyPersist(NodeRef nodeRef, FieldData fieldData, - Map propsToPersist) - { - // determine whether the file folder service can handle the current node - FileInfo fileInfo = this.fileFolderService.getFileInfo(nodeRef); - if (fileInfo != null) - { - try - { - // if the name property changes the rename method of the file folder - // service should be called rather than updating the property directly - this.fileFolderService.rename(nodeRef, (String) fieldData.getValue()); - } - catch (FileExistsException fee) - { - // ALF-6739: Notification should be more user friendly on editing with duplicated name. - // throwing FormException is not informative, therefore, for now we - // throw the captured runtime exception back, as it gives us better information. - - //throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fee); - throw fee; - } - catch (FileNotFoundException fnne) - { - throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fnne); - } - } - else - { - // as the file folder service can not be used just set the name property, - // the node service will deal with the details of renaming. - propsToPersist.put(ContentModel.PROP_NAME, (Serializable)fieldData.getValue()); - } - } - - /** - * Persists the given field data as the mimetype property - * - * @param nodeRef The NodeRef to update the mimetype for - * @param fieldData The data representing the new mimetype value - * @param propsToPersist Map of properties to be persisted - */ - protected void processMimetypePropertyPersist(NodeRef nodeRef, FieldData fieldData, - Map propsToPersist) - { - ContentData contentData = (ContentData) propsToPersist.get(ContentModel.PROP_CONTENT); - if (contentData == null) - { - // content data has not been persisted yet so get it from the node - contentData = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - } - - if (contentData != null) - { - // update content data if we found the property - contentData = ContentData.setMimetype(contentData, (String) fieldData.getValue()); - propsToPersist.put(ContentModel.PROP_CONTENT, contentData); - } - } - - /** - * Persists the given field data as the encoding property - * - * @param nodeRef The NodeRef to update the encoding for - * @param fieldData The data representing the new encoding value - * @param propsToPersist Map of properties to be persisted - */ - protected void processEncodingPropertyPersist(NodeRef nodeRef, FieldData fieldData, - Map propsToPersist) - { - ContentData contentData = (ContentData) propsToPersist.get(ContentModel.PROP_CONTENT); - if (contentData == null) - { - // content data has not been persisted yet so get it from the node - contentData = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - } - - if (contentData != null) - { - // update content data if we found the property - contentData = ContentData.setEncoding(contentData, (String) fieldData.getValue()); - propsToPersist.put(ContentModel.PROP_CONTENT, contentData); - } - } - - /** - * Persists the given field data as the content - * - * @param nodeRef The NodeRef to update the content for - * @param fieldData The data representing the new content - * @param propsToPersist Map of properties to be persisted - * @param data The form data being persisted - */ - protected void processContentPropertyPersist(NodeRef nodeRef, FieldData fieldData, - Map propsToPersist, FormData data) - { - ContentWriter writer = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - ContentData contentData = null; - - if (writer != null) - { - // determine whether there is any content for the node yet i.e. it's a create - boolean defaultMimetypeRequired = (this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT) == null); - - // write the content - writer.putContent((String)fieldData.getValue()); - - // if there was no content set a sensible default mimetype if necessary - if (defaultMimetypeRequired) - { - // if the transient mimetype property has already set the mimetype don't do anything - contentData = (ContentData) propsToPersist.get(ContentModel.PROP_CONTENT); - if (contentData != null) - { - String mimetype = contentData.getMimetype(); - if (mimetype == null) - { - contentData = ContentData.setMimetype(contentData, determineDefaultMimetype(data)); - } - } - else - { - // content data has not been persisted yet so get it from the node - contentData = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - if (contentData != null) - { - contentData = ContentData.setMimetype(contentData, determineDefaultMimetype(data)); - } - } - - } - else - { - contentData = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - - if (contentData != null) - { - // if the ContentData object already exists in propsToPersist extract the mimetype - // and encoding and set on the ContentData object just retrieved - if (propsToPersist.containsKey(ContentModel.PROP_CONTENT)) - { - ContentData mimetypeEncoding = (ContentData)propsToPersist.get(ContentModel.PROP_CONTENT); - contentData = ContentData.setMimetype(contentData, mimetypeEncoding.getMimetype()); - contentData = ContentData.setEncoding(contentData, mimetypeEncoding.getEncoding()); - } - } - } - - // add the potentially changed content data object back to property map for persistence - if (contentData != null) - { - propsToPersist.put(ContentModel.PROP_CONTENT, contentData); - } - } - } - - /** - * Looks through the form data for the 'mimetype' transient field - * and returns it's value if found, otherwise the default 'text/plain' - * is returned - * - * @param data Form data being persisted - * @return The default mimetype - */ - protected String determineDefaultMimetype(FormData data) - { - String mimetype = DEFAULT_CONTENT_MIMETYPE; - - if (data != null) - { - FieldData mimetypeField = data.getFieldData(PROP_DATA_PREFIX + MimetypeFieldProcessor.KEY); - if (mimetypeField != null) - { - String mimetypeFieldValue = (String)mimetypeField.getValue(); - if (mimetypeFieldValue != null && mimetypeFieldValue.length() > 0) - { - mimetype = mimetypeFieldValue; - } - } - } - - return mimetype; - } - -} - -/** - * This class represents a request to update the value of a node association. - * - * @author Neil McErlean - */ -abstract class AbstractAssocCommand -{ - protected final NodeRef sourceNodeRef; - - protected final NodeRef targetNodeRef; - - protected final QName assocQName; - - public AbstractAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) - { - this.sourceNodeRef = sourceNodeRef; - this.targetNodeRef = targetNodeRef; - this.assocQName = assocQName; - } - - /** - * This method should use the specified nodeService reference to effect the - * update to the supplied associations. - * - * @param nodeService NodeService - */ - protected abstract void updateAssociations(NodeService nodeService); -} - -/** - * A class representing a request to add a new association between two nodes. - * - * @author Neil McErlean - */ -class AddAssocCommand extends AbstractAssocCommand -{ - private static final Log logger = LogFactory.getLog(AddAssocCommand.class); - - public AddAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) - { - super(sourceNodeRef, targetNodeRef, assocQName); - } - - @Override - protected void updateAssociations(NodeService nodeService) - { - List existingAssocs = nodeService.getTargetAssocs(sourceNodeRef, assocQName); - for (AssociationRef assoc : existingAssocs) - { - if (assoc.getTargetRef().equals(targetNodeRef)) - { - if (logger.isWarnEnabled()) - { - logger.warn("Attempt to add existing association prevented. " + assoc); - } - return; - } - } - nodeService.createAssociation(sourceNodeRef, targetNodeRef, assocQName); - } -} - -/** - * A class representing a request to remove an association between two nodes. - * - * @author Neil McErlean - */ -class RemoveAssocCommand extends AbstractAssocCommand -{ - private static final Log logger = LogFactory.getLog(RemoveAssocCommand.class); - - public RemoveAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) - { - super(sourceNodeRef, targetNodeRef, assocQName); - } - - @Override - protected void updateAssociations(NodeService nodeService) - { - List existingAssocs = nodeService.getTargetAssocs(sourceNodeRef, assocQName); - boolean assocDoesNotExist = true; - for (AssociationRef assoc : existingAssocs) - { - if (assoc.getTargetRef().equals(targetNodeRef)) - { - assocDoesNotExist = false; - break; - } - } - if (assocDoesNotExist) - { - if (logger.isWarnEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Attempt to remove non-existent association prevented. ").append(sourceNodeRef).append("|") - .append(targetNodeRef).append(assocQName); - logger.warn(msg.toString()); - } - return; - } - - nodeService.removeAssociation(sourceNodeRef, targetNodeRef, assocQName); - } -} - -/** - * A class representing a request to add a new child association between two nodes. - * - * @author Neil McErlean - */ -class AddChildAssocCommand extends AbstractAssocCommand -{ - private static final Log logger = LogFactory.getLog(AddChildAssocCommand.class); - - public AddChildAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) - { - super(sourceNodeRef, targetNodeRef, assocQName); - } - - @Override - protected void updateAssociations(NodeService nodeService) - { - List existingChildren = nodeService.getChildAssocs(sourceNodeRef); - - for (ChildAssociationRef assoc : existingChildren) - { - if (assoc.getChildRef().equals(targetNodeRef)) - { - if (logger.isWarnEnabled()) - { - logger.warn("Attempt to add existing child association prevented. " + assoc); - } - return; - } - } - - // We are following the behaviour of the JSF client here in using the same - // QName value for the 3rd and 4th parameters in the below call. - nodeService.addChild(sourceNodeRef, targetNodeRef, assocQName, assocQName); - } -} - -/** - * A class representing a request to remove a child association between two nodes. - * - * @author Neil McErlean - */ -class RemoveChildAssocCommand extends AbstractAssocCommand -{ - private static final Log logger = LogFactory.getLog(RemoveChildAssocCommand.class); - - public RemoveChildAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) - { - super(sourceNodeRef, targetNodeRef, assocQName); - } - - @Override - protected void updateAssociations(NodeService nodeService) - { - List existingChildren = nodeService.getChildAssocs(sourceNodeRef); - boolean childAssocDoesNotExist = true; - for (ChildAssociationRef assoc : existingChildren) - { - if (assoc.getChildRef().equals(targetNodeRef)) - { - childAssocDoesNotExist = false; - break; - } - } - if (childAssocDoesNotExist) - { - if (logger.isWarnEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Attempt to remove non-existent child association prevented. ").append(sourceNodeRef) - .append("|").append(targetNodeRef).append(assocQName); - logger.warn(msg.toString()); - } - return; - } - - nodeService.removeChild(sourceNodeRef, targetNodeRef); - } -} +package org.alfresco.repo.forms.processor.node; + +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ASSOC_DATA_ADDED_SUFFIX; +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ASSOC_DATA_PREFIX; +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ASSOC_DATA_REMOVED_SUFFIX; +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.DEFAULT_CONTENT_MIMETYPE; +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.DOT_CHARACTER; +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.DOT_CHARACTER_REPLACEMENT; +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ON; +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP_DATA_PREFIX; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.RenditionModel; +import org.alfresco.repo.forms.Field; +import org.alfresco.repo.forms.Form; +import org.alfresco.repo.forms.FormData; +import org.alfresco.repo.forms.FormData.FieldData; +import org.alfresco.repo.forms.FormException; +import org.alfresco.repo.forms.processor.FilteredFormProcessor; +import org.alfresco.repo.forms.processor.FormCreationData; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONException; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Abstract FormProcessor implementation that provides common functionality for + * form processors that deal with Alfresco content models i.e. types and nodes. + * + * @author Gavin Cornwell + * @author Nick Smith + */ +public abstract class ContentModelFormProcessor extends + FilteredFormProcessor +{ + /** Services */ + protected NodeService nodeService; + + protected FileFolderService fileFolderService; + + protected DictionaryService dictionaryService; + + protected NamespaceService namespaceService; + + protected ContentService contentService; + + protected PermissionService permissionService; + + /** + * A regular expression which can be used to match property names. These + * names will look like "prop_cm_name". The pattern can also be + * used to extract the "cm" and the "name" parts. + */ + protected Pattern propertyNamePattern = Pattern.compile(PROP_DATA_PREFIX + "([a-zA-Z0-9-]+)_(.*)"); + + /** + * A regular expression which can be used to match tranisent property names. + * These names will look like "prop_name". The pattern can also + * be used to extract the "name" part. + */ + protected Pattern transientPropertyPattern = Pattern.compile(PROP_DATA_PREFIX + "(.*){1}?"); + + /** + * A regular expression which can be used to match association names. These + * names will look like "assoc_cm_references_added". The + * pattern can also be used to extract the "cm", the "name" and the suffix + * parts. + */ + protected Pattern associationNamePattern = Pattern.compile(ASSOC_DATA_PREFIX + "([a-zA-Z0-9-]+)_(.*)(_[a-zA-Z]+)"); + + /** + * Sets the node service + * + * @param nodeService The NodeService instance + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the file folder service + * + * @param fileFolderService The FileFolderService instance + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * Sets the data dictionary service + * + * @param dictionaryService The DictionaryService instance + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Sets the namespace service + * + * @param namespaceService The NamespaceService instance + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Sets the content service + * + * @param contentService The ContentService instance + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Sets the content service + * + * @param permissionService The PermissionService instance + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + protected void addPropertyDataIfRequired(QName propName, Form form, ContentModelItemData itemData) + { + String dataKey = makePropDataKey(propName); + if (form.dataExists(dataKey) == false) + { + PropertyFieldProcessor processor = new PropertyFieldProcessor(namespaceService, dictionaryService); + Object value = processor.getValue(propName, itemData); + form.addData(dataKey, value); + } + } + + private String makePropDataKey(QName propName) + { + String propPrefixName = propName.toPrefixString(namespaceService); + String dataKey = FormFieldConstants.PROP_DATA_PREFIX + propPrefixName.replace(':', '_'); + return dataKey; + } + + @Override + protected List generateDefaultFields(FormCreationData data, List fieldsToIgnore) + { + DefaultFieldBuilder defaultFieldBuilder = + new DefaultFieldBuilder(data, fieldProcessorRegistry, namespaceService, fieldsToIgnore); + return defaultFieldBuilder.buildDefaultFields(); + } + + @Override + protected ContentModelItemData makeItemData(ItemType item) + { + TypeDefinition baseType = getBaseType(item); + Set aspects = getAspectNames(item); + TypeDefinition anonType = dictionaryService.getAnonymousType(baseType.getName(), aspects); + Map propDefs = anonType.getProperties(); + Map assocDefs = anonType.getAssociations(); + Map propValues = getPropertyValues(item); + Map assocValues = getAssociationValues(item); + Map transientValues = getTransientValues(item); + return new ContentModelItemData(item, propDefs, assocDefs, propValues, assocValues, transientValues); + } + + protected List getDefaultIgnoredFields() + { + ArrayList fields = new ArrayList(8); + + // ignore system properties by default + fields.add(ContentModel.PROP_NODE_DBID.toPrefixString(this.namespaceService)); + fields.add(ContentModel.PROP_NODE_UUID.toPrefixString(this.namespaceService)); + fields.add(ContentModel.PROP_STORE_IDENTIFIER.toPrefixString(this.namespaceService)); + fields.add(ContentModel.PROP_STORE_PROTOCOL.toPrefixString(this.namespaceService)); + + // ignore associations that are system maintained + fields.add(RenditionModel.ASSOC_RENDITION.toPrefixString(this.namespaceService)); + + return fields; + } + + protected Set getAspectNames(ItemType item) + { + return getBaseType(item).getDefaultAspectNames(); + } + + protected abstract Map getAssociationValues(ItemType item); + + protected abstract Map getPropertyValues(ItemType item); + + protected abstract Map getTransientValues(ItemType item); + + protected abstract TypeDefinition getBaseType(ItemType item); + + /** + * Persists the given FormData on the given NodeRef + * + * @param nodeRef The NodeRef to persist the form data on + * @param data The FormData to persist + */ + protected void persistNode(NodeRef nodeRef, FormData data) + { + // get the property definitions for the type of node being persisted + QName type = this.nodeService.getType(nodeRef); + TypeDefinition typeDef = this.dictionaryService.getAnonymousType(type, this.nodeService.getAspects(nodeRef)); + Map assocDefs = typeDef.getAssociations(); + Map childAssocDefs = typeDef.getChildAssociations(); + Map propDefs = typeDef.getProperties(); + + Map propsToPersist = new HashMap(data.getNumberOfFields()); + List assocsToPersist = new ArrayList(); + + for (FieldData fieldData : data) + { + // NOTE: ignore file fields for now, not supported yet! + if (fieldData.isFile() == false) + { + String fieldName = fieldData.getName(); + + if (fieldName.startsWith(PROP_DATA_PREFIX)) + { + processPropertyPersist(nodeRef, propDefs, fieldData, propsToPersist, data); + } + else if (fieldName.startsWith(ASSOC_DATA_PREFIX)) + { + processAssociationPersist(nodeRef, assocDefs, childAssocDefs, fieldData, assocsToPersist); + } + else if (getLogger().isWarnEnabled()) + { + getLogger().warn("Ignoring unrecognised field '" + fieldName + "'"); + } + } + } + + // persist the properties using addProperties as this changes the repo + // values of + // those properties included in the Map, but leaves any other property + // values unchanged, + // whereas setProperties causes the deletion of properties that are not + // included in the Map. + this.nodeService.addProperties(nodeRef, propsToPersist); + + for (AbstractAssocCommand cmd : assocsToPersist) + { + // TODO If there is an attempt to add and remove the same assoc in + // one request, + // we could drop each request and do nothing. + cmd.updateAssociations(nodeService); + } + } + + /** + * Processes the given field data for persistence as a property. + * + * @param nodeRef The NodeRef to persist the properties on + * @param propDefs Map of PropertyDefinition's for the node being persisted + * @param fieldData Data to persist for the property + * @param propsToPersist Map of properties to be persisted + * @param data The FormData to persist + */ + protected void processPropertyPersist(NodeRef nodeRef, Map propDefs, + FieldData fieldData, Map propsToPersist, FormData data) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Processing field " + fieldData + " for property persistence"); + + // match and extract the prefix and name parts + Matcher m = this.propertyNamePattern.matcher(fieldData.getName().replaceAll(DOT_CHARACTER_REPLACEMENT, DOT_CHARACTER)); + if (m.matches()) + { + String qNamePrefix = m.group(1); + String localName = m.group(2); + QName fullQName = QName.createQName(qNamePrefix, localName, namespaceService); + + // ensure that the property being persisted is defined in the model + PropertyDefinition propDef = propDefs.get(fullQName); + + // if the property is not defined on the node, check for the + // property in all models + if (propDef == null) + { + propDef = this.dictionaryService.getProperty(fullQName); + } + + // if we have a property definition attempt the persist + if (propDef != null) + { + // look for properties that have well known handling + // requirements + if (fullQName.equals(ContentModel.PROP_NAME)) + { + processNamePropertyPersist(nodeRef, fieldData, propsToPersist); + } + else if (propDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) + { + processContentPropertyPersist(nodeRef, fieldData, propsToPersist, data); + } + else + { + Object value = fieldData.getValue(); + + // before persisting check data type of property + if (propDef.isMultiValued()) + { + // depending on client the value could be a comma + // separated + // string, a List object or a JSONArray object + if (value instanceof String) + { + if (((String) value).length() == 0) + { + // empty string for multi-valued properties + // should be stored as null + value = null; + } + else + { + // if value is a String convert to List of + // String + List list = Arrays.asList(((String)value).split(",", -1)); + + // persist the List + value = list; + } + } + else if (value instanceof JSONArray) + { + // if value is a JSONArray convert to List of Object + JSONArray jsonArr = (JSONArray) value; + int arrLength = jsonArr.length(); + List list = new ArrayList(arrLength); + try + { + for (int x = 0; x < arrLength; x++) + { + list.add(jsonArr.get(x)); + } + } + catch (JSONException je) + { + throw new FormException("Failed to convert JSONArray to List", je); + } + + // persist the list + value = list; + } + } + else if (propDef.getDataType().getName().equals(DataTypeDefinition.BOOLEAN)) + { + // check for browser representation of true, that being "on" + if (value instanceof String && ON.equals(value)) + { + value = Boolean.TRUE; + } + } + else if (propDef.getDataType().getName().equals(DataTypeDefinition.LOCALE)) + { + value = I18NUtil.parseLocale((String) value); + } + else if ((value instanceof String) && ((String) value).length() == 0) + { + // make sure empty strings stay as empty strings, + // everything else should be represented as null + if (!propDef.getDataType().getName().equals(DataTypeDefinition.TEXT) && + !propDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT)) + { + value = null; + } + else + { + // if the text property has a regex constraint set the empty + // string to null otherwise the integrity checker will reject it + List constraints = propDef.getConstraints(); + if (constraints != null && constraints.size() > 0) + { + for (ConstraintDefinition constraintDef : constraints) + { + if ("REGEX".equals(constraintDef.getConstraint().getType())) + { + value = null; + break; + } + } + } + } + } + + // add the property to the map + propsToPersist.put(fullQName, (Serializable) value); + } + } + else if (getLogger().isWarnEnabled()) + { + getLogger().warn("Ignoring field '" + fieldData.getName() + "' as a property definition can not be found"); + } + } + else + { + // the field is potentially a well know transient property + // check for the ones we know about, anything else is ignored + Matcher tppm = this.transientPropertyPattern.matcher(fieldData.getName()); + if (tppm.matches()) + { + String fieldName = tppm.group(1); + + if (fieldName.equals(MimetypeFieldProcessor.KEY)) + { + processMimetypePropertyPersist(nodeRef, fieldData, propsToPersist); + } + else if (fieldName.equals(EncodingFieldProcessor.KEY)) + { + processEncodingPropertyPersist(nodeRef, fieldData, propsToPersist); + } + else if (fieldName.equals(SizeFieldProcessor.KEY)) + { + // the size property is well known but should never be persisted + // as it is calculated so this is intentionally ignored + } + else if (getLogger().isWarnEnabled()) + { + getLogger().warn("Ignoring unrecognised field '" + fieldData.getName() + "'"); + } + } + else if (getLogger().isWarnEnabled()) + { + getLogger().warn("Ignoring unrecognised field '" + fieldData.getName() + "'"); + } + } + } + + /** + * Processes the given field data for persistence as an association. + * + * @param nodeRef The NodeRef to persist the associations on + * @param fieldData Data to persist for the associations + * @param assocCommands List of associations to be persisted + */ + protected void processAssociationPersist(NodeRef nodeRef, Map assocDefs, + Map childAssocDefs, FieldData fieldData, + List assocCommands) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Processing field " + fieldData + " for association persistence"); + + String fieldName = fieldData.getName(); + Matcher m = this.associationNamePattern.matcher(fieldName.replaceAll(DOT_CHARACTER_REPLACEMENT, DOT_CHARACTER)); + if (m.matches()) + { + String qNamePrefix = m.group(1); + String localName = m.group(2); + String assocSuffix = m.group(3); + + QName fullQName = QName.createQName(qNamePrefix, localName, namespaceService); + + // ensure that the association being persisted is defined in the model + AssociationDefinition assocDef = assocDefs.get(fullQName); + + // If the association is not defined on this node i.e. it is not defined for this node's content + // type and is not defined for any of the aspects that have already been applied to this node, + // then assocDef here will be null. This is because assocDef is passed in to this method, its value + // having been obtained from dictionaryService.getAnonymousType() + // + // However if the association type is defined on an aspect which has not yet been applied to this node + // then setting a value for this association should lead to the automatic application of the aspect defining it. + if (assocDef == null) + { + // Is it defined on any other type? + AssociationDefinition assocDefFromDictionary = this.dictionaryService.getAssociation(fullQName); + + // If the association is defined on any *aspect* type... + if (assocDefFromDictionary != null && assocDefFromDictionary.getSourceClass().isAspect()) + { + // ... then it should be safe to proceed with applying the association value. + assocDef = assocDefFromDictionary; + } + else + { + // ... else it is either undefined in the dictionary service or is defined on a concrete + // content type which is inconsistent with the node's type. We'll ignore it, which is what + // has been done with unhandled associations up to this point. + if (getLogger().isWarnEnabled()) + { + getLogger().warn("Ignoring field '" + fieldName + "' as a valid association definition can not be found"); + } + + return; + } + } + + String value = (String) fieldData.getValue(); + String[] nodeRefs = value.split(","); + + // Each element in this array will be a new target node in association + // with the current node. + for (String nextTargetNode : nodeRefs) + { + if (nextTargetNode.length() > 0) + { + if (NodeRef.isNodeRef(nextTargetNode)) + { + if (assocSuffix.equals(ASSOC_DATA_ADDED_SUFFIX)) + { + if (assocDef.isChild()) + { + assocCommands.add(new AddChildAssocCommand(nodeRef, new NodeRef(nextTargetNode), + fullQName)); + } + else + { + assocCommands.add(new AddAssocCommand(nodeRef, new NodeRef(nextTargetNode), + fullQName)); + } + } + else if (assocSuffix.equals(ASSOC_DATA_REMOVED_SUFFIX)) + { + if (assocDef.isChild()) + { + assocCommands.add(new RemoveChildAssocCommand(nodeRef, new NodeRef(nextTargetNode), + fullQName)); + } + else + { + assocCommands.add(new RemoveAssocCommand(nodeRef, new NodeRef(nextTargetNode), + fullQName)); + } + } + else + { + if (getLogger().isWarnEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Ignoring 'fieldName ").append(fieldName).append( + "' as it does not have one of the expected suffixes (").append( + ASSOC_DATA_ADDED_SUFFIX).append(" or ").append(ASSOC_DATA_REMOVED_SUFFIX) + .append(")"); + getLogger().warn(msg.toString()); + } + } + } + else + { + if (getLogger().isWarnEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("targetNode ").append(nextTargetNode).append( + " is not a valid NodeRef and has been ignored."); + getLogger().warn(msg.toString()); + } + } + } + } + } + else if (getLogger().isWarnEnabled()) + { + getLogger().warn("Ignoring unrecognised field '" + fieldName + "'"); + } + } + + /** + * Persists the given field data as the name property + * + * @param nodeRef The NodeRef to update the name for + * @param fieldData The data representing the new name value + * @param propsToPersist Map of properties to be persisted + */ + protected void processNamePropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist) + { + // determine whether the file folder service can handle the current node + FileInfo fileInfo = this.fileFolderService.getFileInfo(nodeRef); + if (fileInfo != null) + { + try + { + // if the name property changes the rename method of the file folder + // service should be called rather than updating the property directly + this.fileFolderService.rename(nodeRef, (String) fieldData.getValue()); + } + catch (FileExistsException fee) + { + // ALF-6739: Notification should be more user friendly on editing with duplicated name. + // throwing FormException is not informative, therefore, for now we + // throw the captured runtime exception back, as it gives us better information. + + //throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fee); + throw fee; + } + catch (FileNotFoundException fnne) + { + throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fnne); + } + } + else + { + // as the file folder service can not be used just set the name property, + // the node service will deal with the details of renaming. + propsToPersist.put(ContentModel.PROP_NAME, (Serializable)fieldData.getValue()); + } + } + + /** + * Persists the given field data as the mimetype property + * + * @param nodeRef The NodeRef to update the mimetype for + * @param fieldData The data representing the new mimetype value + * @param propsToPersist Map of properties to be persisted + */ + protected void processMimetypePropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist) + { + ContentData contentData = (ContentData) propsToPersist.get(ContentModel.PROP_CONTENT); + if (contentData == null) + { + // content data has not been persisted yet so get it from the node + contentData = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + } + + if (contentData != null) + { + // update content data if we found the property + contentData = ContentData.setMimetype(contentData, (String) fieldData.getValue()); + propsToPersist.put(ContentModel.PROP_CONTENT, contentData); + } + } + + /** + * Persists the given field data as the encoding property + * + * @param nodeRef The NodeRef to update the encoding for + * @param fieldData The data representing the new encoding value + * @param propsToPersist Map of properties to be persisted + */ + protected void processEncodingPropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist) + { + ContentData contentData = (ContentData) propsToPersist.get(ContentModel.PROP_CONTENT); + if (contentData == null) + { + // content data has not been persisted yet so get it from the node + contentData = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + } + + if (contentData != null) + { + // update content data if we found the property + contentData = ContentData.setEncoding(contentData, (String) fieldData.getValue()); + propsToPersist.put(ContentModel.PROP_CONTENT, contentData); + } + } + + /** + * Persists the given field data as the content + * + * @param nodeRef The NodeRef to update the content for + * @param fieldData The data representing the new content + * @param propsToPersist Map of properties to be persisted + * @param data The form data being persisted + */ + protected void processContentPropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist, FormData data) + { + ContentWriter writer = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + ContentData contentData = null; + + if (writer != null) + { + // determine whether there is any content for the node yet i.e. it's a create + boolean defaultMimetypeRequired = (this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT) == null); + + // write the content + writer.putContent((String)fieldData.getValue()); + + // if there was no content set a sensible default mimetype if necessary + if (defaultMimetypeRequired) + { + // if the transient mimetype property has already set the mimetype don't do anything + contentData = (ContentData) propsToPersist.get(ContentModel.PROP_CONTENT); + if (contentData != null) + { + String mimetype = contentData.getMimetype(); + if (mimetype == null) + { + contentData = ContentData.setMimetype(contentData, determineDefaultMimetype(data)); + } + } + else + { + // content data has not been persisted yet so get it from the node + contentData = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + if (contentData != null) + { + contentData = ContentData.setMimetype(contentData, determineDefaultMimetype(data)); + } + } + + } + else + { + contentData = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + + if (contentData != null) + { + // if the ContentData object already exists in propsToPersist extract the mimetype + // and encoding and set on the ContentData object just retrieved + if (propsToPersist.containsKey(ContentModel.PROP_CONTENT)) + { + ContentData mimetypeEncoding = (ContentData)propsToPersist.get(ContentModel.PROP_CONTENT); + contentData = ContentData.setMimetype(contentData, mimetypeEncoding.getMimetype()); + contentData = ContentData.setEncoding(contentData, mimetypeEncoding.getEncoding()); + } + } + } + + // add the potentially changed content data object back to property map for persistence + if (contentData != null) + { + propsToPersist.put(ContentModel.PROP_CONTENT, contentData); + } + } + } + + /** + * Looks through the form data for the 'mimetype' transient field + * and returns it's value if found, otherwise the default 'text/plain' + * is returned + * + * @param data Form data being persisted + * @return The default mimetype + */ + protected String determineDefaultMimetype(FormData data) + { + String mimetype = DEFAULT_CONTENT_MIMETYPE; + + if (data != null) + { + FieldData mimetypeField = data.getFieldData(PROP_DATA_PREFIX + MimetypeFieldProcessor.KEY); + if (mimetypeField != null) + { + String mimetypeFieldValue = (String)mimetypeField.getValue(); + if (mimetypeFieldValue != null && mimetypeFieldValue.length() > 0) + { + mimetype = mimetypeFieldValue; + } + } + } + + return mimetype; + } + +} + +/** + * This class represents a request to update the value of a node association. + * + * @author Neil McErlean + */ +abstract class AbstractAssocCommand +{ + protected final NodeRef sourceNodeRef; + + protected final NodeRef targetNodeRef; + + protected final QName assocQName; + + public AbstractAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) + { + this.sourceNodeRef = sourceNodeRef; + this.targetNodeRef = targetNodeRef; + this.assocQName = assocQName; + } + + /** + * This method should use the specified nodeService reference to effect the + * update to the supplied associations. + * + * @param nodeService NodeService + */ + protected abstract void updateAssociations(NodeService nodeService); +} + +/** + * A class representing a request to add a new association between two nodes. + * + * @author Neil McErlean + */ +class AddAssocCommand extends AbstractAssocCommand +{ + private static final Log logger = LogFactory.getLog(AddAssocCommand.class); + + public AddAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) + { + super(sourceNodeRef, targetNodeRef, assocQName); + } + + @Override + protected void updateAssociations(NodeService nodeService) + { + List existingAssocs = nodeService.getTargetAssocs(sourceNodeRef, assocQName); + for (AssociationRef assoc : existingAssocs) + { + if (assoc.getTargetRef().equals(targetNodeRef)) + { + if (logger.isWarnEnabled()) + { + logger.warn("Attempt to add existing association prevented. " + assoc); + } + return; + } + } + nodeService.createAssociation(sourceNodeRef, targetNodeRef, assocQName); + } +} + +/** + * A class representing a request to remove an association between two nodes. + * + * @author Neil McErlean + */ +class RemoveAssocCommand extends AbstractAssocCommand +{ + private static final Log logger = LogFactory.getLog(RemoveAssocCommand.class); + + public RemoveAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) + { + super(sourceNodeRef, targetNodeRef, assocQName); + } + + @Override + protected void updateAssociations(NodeService nodeService) + { + List existingAssocs = nodeService.getTargetAssocs(sourceNodeRef, assocQName); + boolean assocDoesNotExist = true; + for (AssociationRef assoc : existingAssocs) + { + if (assoc.getTargetRef().equals(targetNodeRef)) + { + assocDoesNotExist = false; + break; + } + } + if (assocDoesNotExist) + { + if (logger.isWarnEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Attempt to remove non-existent association prevented. ").append(sourceNodeRef).append("|") + .append(targetNodeRef).append(assocQName); + logger.warn(msg.toString()); + } + return; + } + + nodeService.removeAssociation(sourceNodeRef, targetNodeRef, assocQName); + } +} + +/** + * A class representing a request to add a new child association between two nodes. + * + * @author Neil McErlean + */ +class AddChildAssocCommand extends AbstractAssocCommand +{ + private static final Log logger = LogFactory.getLog(AddChildAssocCommand.class); + + public AddChildAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) + { + super(sourceNodeRef, targetNodeRef, assocQName); + } + + @Override + protected void updateAssociations(NodeService nodeService) + { + List existingChildren = nodeService.getChildAssocs(sourceNodeRef); + + for (ChildAssociationRef assoc : existingChildren) + { + if (assoc.getChildRef().equals(targetNodeRef)) + { + if (logger.isWarnEnabled()) + { + logger.warn("Attempt to add existing child association prevented. " + assoc); + } + return; + } + } + + // We are following the behaviour of the JSF client here in using the same + // QName value for the 3rd and 4th parameters in the below call. + nodeService.addChild(sourceNodeRef, targetNodeRef, assocQName, assocQName); + } +} + +/** + * A class representing a request to remove a child association between two nodes. + * + * @author Neil McErlean + */ +class RemoveChildAssocCommand extends AbstractAssocCommand +{ + private static final Log logger = LogFactory.getLog(RemoveChildAssocCommand.class); + + public RemoveChildAssocCommand(NodeRef sourceNodeRef, NodeRef targetNodeRef, QName assocQName) + { + super(sourceNodeRef, targetNodeRef, assocQName); + } + + @Override + protected void updateAssociations(NodeService nodeService) + { + List existingChildren = nodeService.getChildAssocs(sourceNodeRef); + boolean childAssocDoesNotExist = true; + for (ChildAssociationRef assoc : existingChildren) + { + if (assoc.getChildRef().equals(targetNodeRef)) + { + childAssocDoesNotExist = false; + break; + } + } + if (childAssocDoesNotExist) + { + if (logger.isWarnEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Attempt to remove non-existent child association prevented. ").append(sourceNodeRef) + .append("|").append(targetNodeRef).append(assocQName); + logger.warn(msg.toString()); + } + return; + } + + nodeService.removeChild(sourceNodeRef, targetNodeRef); + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/node/NodeFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/node/NodeFormProcessor.java index a44b5ab79f..600a969665 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/NodeFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/node/NodeFormProcessor.java @@ -1,275 +1,275 @@ - -package org.alfresco.repo.forms.processor.node; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.forms.FieldDefinition; -import org.alfresco.repo.forms.Form; -import org.alfresco.repo.forms.FormData; -import org.alfresco.repo.forms.FormNotFoundException; -import org.alfresco.repo.forms.Item; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * FormProcessor implementation that can generate and persist Form objects for - * repository nodes. - * - * @author Gavin Cornwell - * @author Nick Smith - */ -public class NodeFormProcessor extends ContentModelFormProcessor -{ - /** Logger */ - private static Log logger = LogFactory.getLog(NodeFormProcessor.class); - - /* - * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getLogger() - */ - @Override - protected Log getLogger() - { - return logger; - } - - /* - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getTypedItem(org.alfresco.repo.forms.Item) - */ - @Override - protected NodeRef getTypedItem(Item item) - { - // create NodeRef representation, the id could already be in a valid - // NodeRef format or it may be in a URL friendly format - NodeRef nodeRef = null; - if (NodeRef.isNodeRef(item.getId())) - { - nodeRef = new NodeRef(item.getId()); - } - else - { - // split the string into the 3 required parts - String[] parts = item.getId().split("/"); - if (parts.length == 3) - { - try - { - nodeRef = new NodeRef(parts[0], parts[1], parts[2]); - } - catch (IllegalArgumentException iae) - { - // ignored for now, dealt with below - - if (logger.isDebugEnabled()) - logger.debug("NodeRef creation failed for: " + item.getId(), iae); - } - } - } - - // check we have a valid node ref - if (nodeRef == null) - { - throw new FormNotFoundException(item, new IllegalArgumentException(item.getId())); - } - - // check the node itself exists - if (this.nodeService.exists(nodeRef) == false) - { - throw new FormNotFoundException(item, - new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef)); - } - else - { - // all Node based filters can expect to get a NodeRef - return nodeRef; - } - } - - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemType(java.lang.Object) - */ - @Override - protected String getItemType(NodeRef item) - { - QName type = this.nodeService.getType(item); - return type.toPrefixString(this.namespaceService); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemURI(java.lang.Object) - */ - @Override - protected String getItemURI(NodeRef item) - { - StringBuilder builder = new StringBuilder("/api/node/"); - builder.append(item.getStoreRef().getProtocol()).append("/"); - builder.append(item.getStoreRef().getIdentifier()).append("/"); - builder.append(item.getId()); - return builder.toString(); - } - - @Override - protected Map getPropertyValues(NodeRef nodeRef) - { - return nodeService.getProperties(nodeRef); - } - - @Override - protected Map getAssociationValues(NodeRef item) - { - HashMap assocs = new HashMap(); - List targetAssocs = nodeService.getTargetAssocs(item, RegexQNamePattern.MATCH_ALL); - List childAssocs = nodeService.getChildAssocs(item); - for (ChildAssociationRef childAssoc : childAssocs) - { - QName name = childAssoc.getTypeQName(); - NodeRef target = childAssoc.getChildRef(); - addAssocToMap(name, target, assocs); - } - for (AssociationRef associationRef : targetAssocs) - { - QName name = associationRef.getTypeQName(); - NodeRef target = associationRef.getTargetRef(); - if (nodeService.exists(target) && (permissionService.hasPermission(target, PermissionService.READ) == AccessStatus.ALLOWED)) - { - addAssocToMap(name, target, assocs); - } - } - return assocs; - } - - @SuppressWarnings("unchecked") - private void addAssocToMap(QName name, NodeRef target, HashMap assocs) - { - Serializable value = assocs.get(name); - if (value == null) - { - LinkedHashSet values = new LinkedHashSet(); - values.add(target); - assocs.put(name, values); - } - else - { - if (value instanceof Set) - { - ((Set)value).add(target); - } - } - } - - @Override - protected Map getTransientValues(NodeRef item) - { - Map values = new HashMap(3); - ContentData contentData = getContentData(item); - if (contentData != null) - { - values.put(EncodingFieldProcessor.KEY, contentData.getEncoding()); - values.put(MimetypeFieldProcessor.KEY, contentData.getMimetype()); - values.put(SizeFieldProcessor.KEY, contentData.getSize()); - } - return values; - } - - @Override - protected Set getAspectNames(NodeRef nodeRef) - { - return nodeService.getAspects(nodeRef); - } - - @Override - protected TypeDefinition getBaseType(NodeRef nodeRef) - { - QName typeName = nodeService.getType(nodeRef); - return dictionaryService.getType(typeName); - } - - private ContentData getContentData(NodeRef nodeRef) - { - // Checks if the node is content and if so gets the ContentData - QName type = this.nodeService.getType(nodeRef); - ContentData content = null; - if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) - { - content = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - } - return content; - } - - /** - * Determines whether the given node represents a working copy, if it does - * the name field is searched for and set to protected as the name field - * should not be edited for a working copy. - * - * If the node is not a working copy this method has no effect. - * - * @param nodeRef NodeRef of node to check and potentially process - * @param form The generated form - */ - protected void processWorkingCopy(NodeRef nodeRef, Form form) - { - // if the node is a working copy ensure that the name field (id present) - // is set to be protected as it can not be edited - if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY)) - { - // go through fields looking for name field - for (FieldDefinition fieldDef : form.getFieldDefinitions()) - { - if (fieldDef.getName().equals(ContentModel.PROP_NAME.toPrefixString(this.namespaceService))) - { - fieldDef.setProtectedField(true); - - if (getLogger().isDebugEnabled()) - { - getLogger().debug("Set " + ContentModel.PROP_NAME.toPrefixString(this.namespaceService) + - "field to protected as it is a working copy"); - } - - break; - } - } - } - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#internalGenerate(java.lang.Object, java.util.List, java.util.List, org.alfresco.repo.forms.Form, java.util.Map) - */ - @Override - protected void internalGenerate(NodeRef item, List fields, List forcedFields, Form form, - Map context) - { - super.internalGenerate(item, fields, forcedFields, form, context); - processWorkingCopy(item, form); - } - - /* - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#internalPersist(java.lang.Object, org.alfresco.repo.forms.FormData) - */ - @Override - protected NodeRef internalPersist(NodeRef item, FormData data) - { - if (logger.isDebugEnabled()) - logger.debug("Persisting form for: " + item); - - // persist the node - persistNode(item, data); - - return item; - } -} + +package org.alfresco.repo.forms.processor.node; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.forms.FieldDefinition; +import org.alfresco.repo.forms.Form; +import org.alfresco.repo.forms.FormData; +import org.alfresco.repo.forms.FormNotFoundException; +import org.alfresco.repo.forms.Item; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * FormProcessor implementation that can generate and persist Form objects for + * repository nodes. + * + * @author Gavin Cornwell + * @author Nick Smith + */ +public class NodeFormProcessor extends ContentModelFormProcessor +{ + /** Logger */ + private static Log logger = LogFactory.getLog(NodeFormProcessor.class); + + /* + * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getLogger() + */ + @Override + protected Log getLogger() + { + return logger; + } + + /* + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getTypedItem(org.alfresco.repo.forms.Item) + */ + @Override + protected NodeRef getTypedItem(Item item) + { + // create NodeRef representation, the id could already be in a valid + // NodeRef format or it may be in a URL friendly format + NodeRef nodeRef = null; + if (NodeRef.isNodeRef(item.getId())) + { + nodeRef = new NodeRef(item.getId()); + } + else + { + // split the string into the 3 required parts + String[] parts = item.getId().split("/"); + if (parts.length == 3) + { + try + { + nodeRef = new NodeRef(parts[0], parts[1], parts[2]); + } + catch (IllegalArgumentException iae) + { + // ignored for now, dealt with below + + if (logger.isDebugEnabled()) + logger.debug("NodeRef creation failed for: " + item.getId(), iae); + } + } + } + + // check we have a valid node ref + if (nodeRef == null) + { + throw new FormNotFoundException(item, new IllegalArgumentException(item.getId())); + } + + // check the node itself exists + if (this.nodeService.exists(nodeRef) == false) + { + throw new FormNotFoundException(item, + new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef)); + } + else + { + // all Node based filters can expect to get a NodeRef + return nodeRef; + } + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemType(java.lang.Object) + */ + @Override + protected String getItemType(NodeRef item) + { + QName type = this.nodeService.getType(item); + return type.toPrefixString(this.namespaceService); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemURI(java.lang.Object) + */ + @Override + protected String getItemURI(NodeRef item) + { + StringBuilder builder = new StringBuilder("/api/node/"); + builder.append(item.getStoreRef().getProtocol()).append("/"); + builder.append(item.getStoreRef().getIdentifier()).append("/"); + builder.append(item.getId()); + return builder.toString(); + } + + @Override + protected Map getPropertyValues(NodeRef nodeRef) + { + return nodeService.getProperties(nodeRef); + } + + @Override + protected Map getAssociationValues(NodeRef item) + { + HashMap assocs = new HashMap(); + List targetAssocs = nodeService.getTargetAssocs(item, RegexQNamePattern.MATCH_ALL); + List childAssocs = nodeService.getChildAssocs(item); + for (ChildAssociationRef childAssoc : childAssocs) + { + QName name = childAssoc.getTypeQName(); + NodeRef target = childAssoc.getChildRef(); + addAssocToMap(name, target, assocs); + } + for (AssociationRef associationRef : targetAssocs) + { + QName name = associationRef.getTypeQName(); + NodeRef target = associationRef.getTargetRef(); + if (nodeService.exists(target) && (permissionService.hasPermission(target, PermissionService.READ) == AccessStatus.ALLOWED)) + { + addAssocToMap(name, target, assocs); + } + } + return assocs; + } + + @SuppressWarnings("unchecked") + private void addAssocToMap(QName name, NodeRef target, HashMap assocs) + { + Serializable value = assocs.get(name); + if (value == null) + { + LinkedHashSet values = new LinkedHashSet(); + values.add(target); + assocs.put(name, values); + } + else + { + if (value instanceof Set) + { + ((Set)value).add(target); + } + } + } + + @Override + protected Map getTransientValues(NodeRef item) + { + Map values = new HashMap(3); + ContentData contentData = getContentData(item); + if (contentData != null) + { + values.put(EncodingFieldProcessor.KEY, contentData.getEncoding()); + values.put(MimetypeFieldProcessor.KEY, contentData.getMimetype()); + values.put(SizeFieldProcessor.KEY, contentData.getSize()); + } + return values; + } + + @Override + protected Set getAspectNames(NodeRef nodeRef) + { + return nodeService.getAspects(nodeRef); + } + + @Override + protected TypeDefinition getBaseType(NodeRef nodeRef) + { + QName typeName = nodeService.getType(nodeRef); + return dictionaryService.getType(typeName); + } + + private ContentData getContentData(NodeRef nodeRef) + { + // Checks if the node is content and if so gets the ContentData + QName type = this.nodeService.getType(nodeRef); + ContentData content = null; + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) + { + content = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + } + return content; + } + + /** + * Determines whether the given node represents a working copy, if it does + * the name field is searched for and set to protected as the name field + * should not be edited for a working copy. + * + * If the node is not a working copy this method has no effect. + * + * @param nodeRef NodeRef of node to check and potentially process + * @param form The generated form + */ + protected void processWorkingCopy(NodeRef nodeRef, Form form) + { + // if the node is a working copy ensure that the name field (id present) + // is set to be protected as it can not be edited + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY)) + { + // go through fields looking for name field + for (FieldDefinition fieldDef : form.getFieldDefinitions()) + { + if (fieldDef.getName().equals(ContentModel.PROP_NAME.toPrefixString(this.namespaceService))) + { + fieldDef.setProtectedField(true); + + if (getLogger().isDebugEnabled()) + { + getLogger().debug("Set " + ContentModel.PROP_NAME.toPrefixString(this.namespaceService) + + "field to protected as it is a working copy"); + } + + break; + } + } + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#internalGenerate(java.lang.Object, java.util.List, java.util.List, org.alfresco.repo.forms.Form, java.util.Map) + */ + @Override + protected void internalGenerate(NodeRef item, List fields, List forcedFields, Form form, + Map context) + { + super.internalGenerate(item, fields, forcedFields, form, context); + processWorkingCopy(item, form); + } + + /* + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#internalPersist(java.lang.Object, org.alfresco.repo.forms.FormData) + */ + @Override + protected NodeRef internalPersist(NodeRef item, FormData data) + { + if (logger.isDebugEnabled()) + logger.debug("Persisting form for: " + item); + + // persist the node + persistNode(item, data); + + return item; + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/node/PeriodDataTypeParameters.java b/source/java/org/alfresco/repo/forms/processor/node/PeriodDataTypeParameters.java index 8ccf5601ad..13780ecf39 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/PeriodDataTypeParameters.java +++ b/source/java/org/alfresco/repo/forms/processor/node/PeriodDataTypeParameters.java @@ -1,103 +1,103 @@ -package org.alfresco.repo.forms.processor.node; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -import org.alfresco.repo.dictionary.types.period.Cron; -import org.alfresco.repo.dictionary.types.period.XMLDuration; -import org.alfresco.repo.forms.DataTypeParameters; -import org.alfresco.service.cmr.repository.PeriodProvider; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Represents the parameters for the period data type. - * - * @author Gavin Cornwell - */ -public class PeriodDataTypeParameters implements DataTypeParameters, Serializable -{ - private static final long serialVersionUID = -3654041242831123509L; - - protected List providers; - - /** - * Default constructor - */ - public PeriodDataTypeParameters() - { - this.providers = new ArrayList(8); - } - - /** - * Adds a PeriodProvider - * - * @param pp The PeriodProvider to add - */ - public void addPeriodProvider(PeriodProvider pp) - { - // the XML and cron period types are for future use so don't - // return them for now - if (pp.getPeriodType() != XMLDuration.PERIOD_TYPE && - pp.getPeriodType() != Cron.PERIOD_TYPE) - { - this.providers.add(pp); - } - } - - /** - * Retrieves a List of PeriodProvider objects representing - * the valid period options for the property - * - * @see org.alfresco.repo.forms.DataTypeParameters#getAsObject() - * @return List of PeriodProvider objects - */ - public Object getAsObject() - { - return this.providers; - } - - /** - * Returns the valid period options as a JSONArray of JSONObject's. - * - * @see org.alfresco.repo.forms.DataTypeParameters#getAsJSON() - * @return A JSONArray object holding JSONObject's representing the - * period definitions - */ - public Object getAsJSON() - { - JSONArray periods = new JSONArray(); - - try - { - for (PeriodProvider pp : this.providers) - { - boolean hasExpression = !(pp.getExpressionMutiplicity().equals(PeriodProvider.ExpressionMutiplicity.NONE)); - - JSONObject period = new JSONObject(); - period.put("type", pp.getPeriodType()); - period.put("label", pp.getDisplayLabel()); - period.put("hasExpression", hasExpression); - - if (hasExpression) - { - period.put("expressionMandatory", - pp.getExpressionMutiplicity().equals(PeriodProvider.ExpressionMutiplicity.MANDATORY)); - period.put("expressionType", pp.getExpressionDataType().toPrefixString()); - period.put("defaultExpression", pp.getDefaultExpression()); - } - - periods.put(period); - } - } - catch (JSONException je) - { - // return an empty array - periods = new JSONArray(); - } - - return periods; - } -} +package org.alfresco.repo.forms.processor.node; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.repo.dictionary.types.period.Cron; +import org.alfresco.repo.dictionary.types.period.XMLDuration; +import org.alfresco.repo.forms.DataTypeParameters; +import org.alfresco.service.cmr.repository.PeriodProvider; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Represents the parameters for the period data type. + * + * @author Gavin Cornwell + */ +public class PeriodDataTypeParameters implements DataTypeParameters, Serializable +{ + private static final long serialVersionUID = -3654041242831123509L; + + protected List providers; + + /** + * Default constructor + */ + public PeriodDataTypeParameters() + { + this.providers = new ArrayList(8); + } + + /** + * Adds a PeriodProvider + * + * @param pp The PeriodProvider to add + */ + public void addPeriodProvider(PeriodProvider pp) + { + // the XML and cron period types are for future use so don't + // return them for now + if (pp.getPeriodType() != XMLDuration.PERIOD_TYPE && + pp.getPeriodType() != Cron.PERIOD_TYPE) + { + this.providers.add(pp); + } + } + + /** + * Retrieves a List of PeriodProvider objects representing + * the valid period options for the property + * + * @see org.alfresco.repo.forms.DataTypeParameters#getAsObject() + * @return List of PeriodProvider objects + */ + public Object getAsObject() + { + return this.providers; + } + + /** + * Returns the valid period options as a JSONArray of JSONObject's. + * + * @see org.alfresco.repo.forms.DataTypeParameters#getAsJSON() + * @return A JSONArray object holding JSONObject's representing the + * period definitions + */ + public Object getAsJSON() + { + JSONArray periods = new JSONArray(); + + try + { + for (PeriodProvider pp : this.providers) + { + boolean hasExpression = !(pp.getExpressionMutiplicity().equals(PeriodProvider.ExpressionMutiplicity.NONE)); + + JSONObject period = new JSONObject(); + period.put("type", pp.getPeriodType()); + period.put("label", pp.getDisplayLabel()); + period.put("hasExpression", hasExpression); + + if (hasExpression) + { + period.put("expressionMandatory", + pp.getExpressionMutiplicity().equals(PeriodProvider.ExpressionMutiplicity.MANDATORY)); + period.put("expressionType", pp.getExpressionDataType().toPrefixString()); + period.put("defaultExpression", pp.getDefaultExpression()); + } + + periods.put(period); + } + } + catch (JSONException je) + { + // return an empty array + periods = new JSONArray(); + } + + return periods; + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java index 96808123a2..e9b6829988 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java @@ -1,247 +1,247 @@ - -package org.alfresco.repo.forms.processor.node; - -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.DATA_KEY_SEPARATOR; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.forms.FormData; -import org.alfresco.repo.forms.FormException; -import org.alfresco.repo.forms.FormNotFoundException; -import org.alfresco.repo.forms.Item; -import org.alfresco.repo.forms.FormData.FieldData; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.InvalidQNameException; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.GUID; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * FormProcessor implementation that can generate and persist Form objects for - * types in the Alfresco content model. - * - * @author Gavin Cornwell - * @author Nick Smith - * @author 3.4 - */ -public class TypeFormProcessor extends ContentModelFormProcessor -{ - /** Logger */ - private static Log logger = LogFactory.getLog(TypeFormProcessor.class); - - private static QName ASPECT_FILE_PLAN_COMPONENT = QName.createQName("http://www.alfresco.org/model/recordsmanagement/1.0", "filePlanComponent"); - - protected static final String NAME_PROP_DATA = PROP + DATA_KEY_SEPARATOR + "cm" + DATA_KEY_SEPARATOR + "name"; - - /* - * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getLogger() - */ - @Override - protected Log getLogger() - { - return logger; - } - - /* - * @see org.alfresco.repo.forms.processor.node.NodeFormProcessor#getTypedItem(org.alfresco.repo.forms.Item) - */ - @Override - protected TypeDefinition getTypedItem(Item item) - { - TypeDefinition typeDef = null; - - try - { - // the itemId could either be the full form qname i.e. {http://www.alfresco.org/model/content/1.0}content - // or it could be the prefix form i.e. cm:name - QName type = null; - String itemId = item.getId(); - if (itemId.startsWith("{")) - { - // item id looks like a full qname - type = QName.createQName(itemId); - } - else - { - // try and create the QName using the item id as is - type = QName.createQName(itemId, this.namespaceService); - } - - // retrieve the type from the dictionary - typeDef = this.dictionaryService.getType(type); - - if (typeDef == null) - { - throw new FormNotFoundException(item, - new IllegalArgumentException("Type does not exist: " + item.getId())); - } - } - catch (InvalidQNameException iqne) - { - throw new FormNotFoundException(item, iqne); - } - - // return the type definition object for the requested type - return typeDef; - } - - /* - * @see org.alfresco.repo.forms.processor.node.NodeFormProcessor#internalPersist(java.lang.Object, org.alfresco.repo.forms.FormData) - */ - @Override - protected NodeRef internalPersist(TypeDefinition item, final FormData data) - { - if (logger.isDebugEnabled()) - logger.debug("Persisting form for: " + item); - - // create a new instance of the type - final NodeRef nodeRef = createNode(item, data); - - if (nodeService.hasAspect(nodeRef, ASPECT_FILE_PLAN_COMPONENT) == true) - { - // persist the form data as the admin user - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Object doWork() throws Exception - { - persistNode(nodeRef, data); - return null; - } - }, - AuthenticationUtil.getSystemUserName()); - } - else - { - // persist the form data - persistNode(nodeRef, data); - } - - // return the newly created node - return nodeRef; - } - - /** - * Creates a new instance of the given type. - *

- * If the form data has the name property present it is used as the name of - * the node. - *

- *

- * The new node is placed in the location defined by the "destination" data - * item in the form data (this will usually be a hidden field), this will - * also be the NodeRef representation of the parent for the new node. - *

- * - * @param typeDef The type defintion of the type to create - * @param data The form data - * @return NodeRef representing the newly created node - */ - protected NodeRef createNode(TypeDefinition typeDef, FormData data) - { - NodeRef nodeRef = null; - - if (data != null) - { - // firstly, ensure we have a destination to create the node in - NodeRef parentRef = null; - FieldData destination = data.getFieldData(DESTINATION); - if (destination == null) - { - throw new FormException("Failed to persist form for '" - + typeDef.getName().toPrefixString(this.namespaceService) + - "' as '" + DESTINATION + "' data was not provided."); - } - - // create the parent NodeRef - parentRef = new NodeRef((String) destination.getValue()); - - // remove the destination data to avoid warning during persistence, - // this can - // always be retrieved by looking up the created node's parent - data.removeFieldData(DESTINATION); - - // TODO: determine what association to use when creating the node in - // the destination, - // defaults to ContentModel.ASSOC_CONTAINS - - // if a name property is present in the form data use it as the node - // name, - // otherwise generate a guid - String nodeName = null; - FieldData nameData = data.getFieldData(NAME_PROP_DATA); - if (nameData != null) - { - nodeName = (String) nameData.getValue(); - - // remove the name data otherwise 'rename' gets called in - // persistNode - data.removeFieldData(NAME_PROP_DATA); - } - if (nodeName == null || nodeName.length() == 0) - { - nodeName = GUID.generate(); - } - - // create the node - Map nodeProps = new HashMap(1); - nodeProps.put(ContentModel.PROP_NAME, nodeName); - nodeRef = this.nodeService.createNode( - parentRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, - QName.createValidLocalName(nodeName)), typeDef.getName(), nodeProps).getChildRef(); - } - - return nodeRef; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemType(java.lang.Object) - */ - @Override - protected String getItemType(TypeDefinition item) - { - return item.getName().toPrefixString(namespaceService); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemURI(java.lang.Object) - */ - @Override - protected String getItemURI(TypeDefinition item) - { - return "/api/classes/" + getItemType(item).replace(":", "_"); - } - - @Override - protected TypeDefinition getBaseType(TypeDefinition type) - { - return type; - } - - @Override - protected Map getAssociationValues(TypeDefinition item) - { - return null; - } - - @Override - protected Map getPropertyValues(TypeDefinition item) - { - return null; - } - - @Override - protected Map getTransientValues(TypeDefinition item) - { - return null; - } -} + +package org.alfresco.repo.forms.processor.node; + +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.DATA_KEY_SEPARATOR; +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.forms.FormData; +import org.alfresco.repo.forms.FormException; +import org.alfresco.repo.forms.FormNotFoundException; +import org.alfresco.repo.forms.Item; +import org.alfresco.repo.forms.FormData.FieldData; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.InvalidQNameException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * FormProcessor implementation that can generate and persist Form objects for + * types in the Alfresco content model. + * + * @author Gavin Cornwell + * @author Nick Smith + * @author 3.4 + */ +public class TypeFormProcessor extends ContentModelFormProcessor +{ + /** Logger */ + private static Log logger = LogFactory.getLog(TypeFormProcessor.class); + + private static QName ASPECT_FILE_PLAN_COMPONENT = QName.createQName("http://www.alfresco.org/model/recordsmanagement/1.0", "filePlanComponent"); + + protected static final String NAME_PROP_DATA = PROP + DATA_KEY_SEPARATOR + "cm" + DATA_KEY_SEPARATOR + "name"; + + /* + * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getLogger() + */ + @Override + protected Log getLogger() + { + return logger; + } + + /* + * @see org.alfresco.repo.forms.processor.node.NodeFormProcessor#getTypedItem(org.alfresco.repo.forms.Item) + */ + @Override + protected TypeDefinition getTypedItem(Item item) + { + TypeDefinition typeDef = null; + + try + { + // the itemId could either be the full form qname i.e. {http://www.alfresco.org/model/content/1.0}content + // or it could be the prefix form i.e. cm:name + QName type = null; + String itemId = item.getId(); + if (itemId.startsWith("{")) + { + // item id looks like a full qname + type = QName.createQName(itemId); + } + else + { + // try and create the QName using the item id as is + type = QName.createQName(itemId, this.namespaceService); + } + + // retrieve the type from the dictionary + typeDef = this.dictionaryService.getType(type); + + if (typeDef == null) + { + throw new FormNotFoundException(item, + new IllegalArgumentException("Type does not exist: " + item.getId())); + } + } + catch (InvalidQNameException iqne) + { + throw new FormNotFoundException(item, iqne); + } + + // return the type definition object for the requested type + return typeDef; + } + + /* + * @see org.alfresco.repo.forms.processor.node.NodeFormProcessor#internalPersist(java.lang.Object, org.alfresco.repo.forms.FormData) + */ + @Override + protected NodeRef internalPersist(TypeDefinition item, final FormData data) + { + if (logger.isDebugEnabled()) + logger.debug("Persisting form for: " + item); + + // create a new instance of the type + final NodeRef nodeRef = createNode(item, data); + + if (nodeService.hasAspect(nodeRef, ASPECT_FILE_PLAN_COMPONENT) == true) + { + // persist the form data as the admin user + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + persistNode(nodeRef, data); + return null; + } + }, + AuthenticationUtil.getSystemUserName()); + } + else + { + // persist the form data + persistNode(nodeRef, data); + } + + // return the newly created node + return nodeRef; + } + + /** + * Creates a new instance of the given type. + *

+ * If the form data has the name property present it is used as the name of + * the node. + *

+ *

+ * The new node is placed in the location defined by the "destination" data + * item in the form data (this will usually be a hidden field), this will + * also be the NodeRef representation of the parent for the new node. + *

+ * + * @param typeDef The type defintion of the type to create + * @param data The form data + * @return NodeRef representing the newly created node + */ + protected NodeRef createNode(TypeDefinition typeDef, FormData data) + { + NodeRef nodeRef = null; + + if (data != null) + { + // firstly, ensure we have a destination to create the node in + NodeRef parentRef = null; + FieldData destination = data.getFieldData(DESTINATION); + if (destination == null) + { + throw new FormException("Failed to persist form for '" + + typeDef.getName().toPrefixString(this.namespaceService) + + "' as '" + DESTINATION + "' data was not provided."); + } + + // create the parent NodeRef + parentRef = new NodeRef((String) destination.getValue()); + + // remove the destination data to avoid warning during persistence, + // this can + // always be retrieved by looking up the created node's parent + data.removeFieldData(DESTINATION); + + // TODO: determine what association to use when creating the node in + // the destination, + // defaults to ContentModel.ASSOC_CONTAINS + + // if a name property is present in the form data use it as the node + // name, + // otherwise generate a guid + String nodeName = null; + FieldData nameData = data.getFieldData(NAME_PROP_DATA); + if (nameData != null) + { + nodeName = (String) nameData.getValue(); + + // remove the name data otherwise 'rename' gets called in + // persistNode + data.removeFieldData(NAME_PROP_DATA); + } + if (nodeName == null || nodeName.length() == 0) + { + nodeName = GUID.generate(); + } + + // create the node + Map nodeProps = new HashMap(1); + nodeProps.put(ContentModel.PROP_NAME, nodeName); + nodeRef = this.nodeService.createNode( + parentRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, + QName.createValidLocalName(nodeName)), typeDef.getName(), nodeProps).getChildRef(); + } + + return nodeRef; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemType(java.lang.Object) + */ + @Override + protected String getItemType(TypeDefinition item) + { + return item.getName().toPrefixString(namespaceService); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemURI(java.lang.Object) + */ + @Override + protected String getItemURI(TypeDefinition item) + { + return "/api/classes/" + getItemType(item).replace(":", "_"); + } + + @Override + protected TypeDefinition getBaseType(TypeDefinition type) + { + return type; + } + + @Override + protected Map getAssociationValues(TypeDefinition item) + { + return null; + } + + @Override + protected Map getPropertyValues(TypeDefinition item) + { + return null; + } + + @Override + protected Map getTransientValues(TypeDefinition item) + { + return null; + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/DataKeyInfo.java b/source/java/org/alfresco/repo/forms/processor/workflow/DataKeyInfo.java index eb5ba208f2..6147447033 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/DataKeyInfo.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/DataKeyInfo.java @@ -1,80 +1,80 @@ - -package org.alfresco.repo.forms.processor.workflow; - -import org.alfresco.service.namespace.QName; - -/** - * Data transfer object that represents a data key. - * - * @since 3.4 - * @author Nick Smith - */ -public class DataKeyInfo -{ - private final String fieldName; - private final QName qName; - private final FieldType fieldType; - private final boolean isAdd; - - private DataKeyInfo(String dataKey, QName qName, FieldType fieldType, boolean isAdd) - { - this.fieldName = dataKey; - this.qName = qName; - this.fieldType = fieldType; - this.isAdd = isAdd; - } - - public static DataKeyInfo makeAssociationDataKeyInfo(String dataKey, QName qName, boolean isAdd) - { - return new DataKeyInfo(dataKey, qName, FieldType.ASSOCIATION, isAdd); - } - - public static DataKeyInfo makePropertyDataKeyInfo(String dataKey, QName qName) - { - return new DataKeyInfo(dataKey, qName, FieldType.PROPERTY, true); - } - - public static DataKeyInfo makeTransientPropertyDataKeyInfo(String dataKey) - { - return new DataKeyInfo(dataKey, null, FieldType.TRANSIENT_PROPERTY, true); - } - - public static DataKeyInfo makeTransientAssociationDataKeyInfo(String dataKey, boolean isAdd) - { - return new DataKeyInfo(dataKey, null, FieldType.TRANSIENT_ASSOCIATION, isAdd); - } - - /** - * @return the fieldName - */ - public String getFieldName() - { - return fieldName; - } - - /** - * @return the qName - */ - public QName getQName() - { - return qName; - } - - - /** - * @return the fieldType - */ - public FieldType getFieldType() - { - return fieldType; - } - - /** - * @return the isAdd - */ - public boolean isAdd() - { - return isAdd; - } - -} + +package org.alfresco.repo.forms.processor.workflow; + +import org.alfresco.service.namespace.QName; + +/** + * Data transfer object that represents a data key. + * + * @since 3.4 + * @author Nick Smith + */ +public class DataKeyInfo +{ + private final String fieldName; + private final QName qName; + private final FieldType fieldType; + private final boolean isAdd; + + private DataKeyInfo(String dataKey, QName qName, FieldType fieldType, boolean isAdd) + { + this.fieldName = dataKey; + this.qName = qName; + this.fieldType = fieldType; + this.isAdd = isAdd; + } + + public static DataKeyInfo makeAssociationDataKeyInfo(String dataKey, QName qName, boolean isAdd) + { + return new DataKeyInfo(dataKey, qName, FieldType.ASSOCIATION, isAdd); + } + + public static DataKeyInfo makePropertyDataKeyInfo(String dataKey, QName qName) + { + return new DataKeyInfo(dataKey, qName, FieldType.PROPERTY, true); + } + + public static DataKeyInfo makeTransientPropertyDataKeyInfo(String dataKey) + { + return new DataKeyInfo(dataKey, null, FieldType.TRANSIENT_PROPERTY, true); + } + + public static DataKeyInfo makeTransientAssociationDataKeyInfo(String dataKey, boolean isAdd) + { + return new DataKeyInfo(dataKey, null, FieldType.TRANSIENT_ASSOCIATION, isAdd); + } + + /** + * @return the fieldName + */ + public String getFieldName() + { + return fieldName; + } + + /** + * @return the qName + */ + public QName getQName() + { + return qName; + } + + + /** + * @return the fieldType + */ + public FieldType getFieldType() + { + return fieldType; + } + + /** + * @return the isAdd + */ + public boolean isAdd() + { + return isAdd; + } + +} diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/DataKeyMatcher.java b/source/java/org/alfresco/repo/forms/processor/workflow/DataKeyMatcher.java index e0ef62402b..e716c05be1 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/DataKeyMatcher.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/DataKeyMatcher.java @@ -1,117 +1,117 @@ - -package org.alfresco.repo.forms.processor.workflow; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; - -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.*; - -/** - * Utility class used for matching data keys. - * - * @since 3.4 - * @author Nick Smith - */ -public class DataKeyMatcher -{ - /** - * A regular expression which can be used to match property names. These - * names will look like "prop_cm_name". The pattern can also be - * used to extract the "cm" and the "name" parts. - */ - private final static Pattern propertyNamePattern = Pattern.compile("(^[a-zA-Z0-9-]+)_([a-zA-Z0-9_]+$)"); - - /** - * A regular expression which can be used to match association names. These - * names will look like "assoc_cm_references_added". The - * pattern can also be used to extract the "cm", the "name" and the suffix - * parts. - */ - private final static Pattern associationNamePattern = Pattern.compile("(^[a-zA-Z0-9-]+)_([a-zA-Z0-9_]+)(_[a-zA-Z]+$)"); - - private final static Pattern transientAssociationPattern = Pattern.compile("(^[a-zA-Z0-9]+)(_[a-zA-Z]+$)"); - - private final NamespaceService namespaceService; - - public DataKeyMatcher(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * Attempts to match the dataKey to either a property or association pattern. - * If no match can be found then returns null. - * @param dataKey the dataKey to be matched. - * @return a {@link DataKeyInfo} representation or null. - */ - public DataKeyInfo match(String dataKey) - { - if (dataKey.startsWith(PROP_DATA_PREFIX)) - { - return matchProperty(dataKey); - } - else if (dataKey.startsWith(ASSOC_DATA_PREFIX)) - { - return matchAssociation(dataKey); - } - // No match found. - return null; - } - - private DataKeyInfo matchAssociation(String dataKey) - { - String keyName = dataKey.substring(ASSOC_DATA_PREFIX.length()); - Matcher matcher = associationNamePattern.matcher(keyName); - if (matcher.matches()) - { - QName qName = getQName(matcher); - boolean isAdd = isAdd(matcher, 3); - String name = qName.toPrefixString(namespaceService); - return DataKeyInfo.makeAssociationDataKeyInfo(name, qName, isAdd); - } - return matchTransientAssociation(keyName); - } - - private DataKeyInfo matchTransientAssociation(String keyName) - { - Matcher matcher = transientAssociationPattern.matcher(keyName); - if (matcher.matches()) - { - boolean isAdd = isAdd(matcher, 2); - String name = matcher.group(1); - return DataKeyInfo.makeTransientAssociationDataKeyInfo(name, isAdd); - } - return null; - } - - private boolean isAdd(Matcher matcher, int suffixPos) - { - String suffix = matcher.group(suffixPos); - boolean isAdd = !(ASSOC_DATA_REMOVED_SUFFIX.equals(suffix)); - return isAdd; - } - - private DataKeyInfo matchProperty(String dataKey) - { - String keyName = dataKey.substring(PROP_DATA_PREFIX.length()); - Matcher matcher = propertyNamePattern.matcher(keyName); - if (matcher.matches()) - { - QName qName = getQName(matcher); - String name = qName.toPrefixString(namespaceService); - return DataKeyInfo.makePropertyDataKeyInfo(name, qName); - } - return DataKeyInfo.makeTransientPropertyDataKeyInfo(keyName); - } - - private QName getQName(Matcher matcher) - { - String prefix = matcher.group(1); - String localName = matcher.group(2); - QName qName = QName.createQName(prefix, localName, namespaceService); - return qName; - } -} + +package org.alfresco.repo.forms.processor.workflow; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.*; + +/** + * Utility class used for matching data keys. + * + * @since 3.4 + * @author Nick Smith + */ +public class DataKeyMatcher +{ + /** + * A regular expression which can be used to match property names. These + * names will look like "prop_cm_name". The pattern can also be + * used to extract the "cm" and the "name" parts. + */ + private final static Pattern propertyNamePattern = Pattern.compile("(^[a-zA-Z0-9-]+)_([a-zA-Z0-9_]+$)"); + + /** + * A regular expression which can be used to match association names. These + * names will look like "assoc_cm_references_added". The + * pattern can also be used to extract the "cm", the "name" and the suffix + * parts. + */ + private final static Pattern associationNamePattern = Pattern.compile("(^[a-zA-Z0-9-]+)_([a-zA-Z0-9_]+)(_[a-zA-Z]+$)"); + + private final static Pattern transientAssociationPattern = Pattern.compile("(^[a-zA-Z0-9]+)(_[a-zA-Z]+$)"); + + private final NamespaceService namespaceService; + + public DataKeyMatcher(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Attempts to match the dataKey to either a property or association pattern. + * If no match can be found then returns null. + * @param dataKey the dataKey to be matched. + * @return a {@link DataKeyInfo} representation or null. + */ + public DataKeyInfo match(String dataKey) + { + if (dataKey.startsWith(PROP_DATA_PREFIX)) + { + return matchProperty(dataKey); + } + else if (dataKey.startsWith(ASSOC_DATA_PREFIX)) + { + return matchAssociation(dataKey); + } + // No match found. + return null; + } + + private DataKeyInfo matchAssociation(String dataKey) + { + String keyName = dataKey.substring(ASSOC_DATA_PREFIX.length()); + Matcher matcher = associationNamePattern.matcher(keyName); + if (matcher.matches()) + { + QName qName = getQName(matcher); + boolean isAdd = isAdd(matcher, 3); + String name = qName.toPrefixString(namespaceService); + return DataKeyInfo.makeAssociationDataKeyInfo(name, qName, isAdd); + } + return matchTransientAssociation(keyName); + } + + private DataKeyInfo matchTransientAssociation(String keyName) + { + Matcher matcher = transientAssociationPattern.matcher(keyName); + if (matcher.matches()) + { + boolean isAdd = isAdd(matcher, 2); + String name = matcher.group(1); + return DataKeyInfo.makeTransientAssociationDataKeyInfo(name, isAdd); + } + return null; + } + + private boolean isAdd(Matcher matcher, int suffixPos) + { + String suffix = matcher.group(suffixPos); + boolean isAdd = !(ASSOC_DATA_REMOVED_SUFFIX.equals(suffix)); + return isAdd; + } + + private DataKeyInfo matchProperty(String dataKey) + { + String keyName = dataKey.substring(PROP_DATA_PREFIX.length()); + Matcher matcher = propertyNamePattern.matcher(keyName); + if (matcher.matches()) + { + QName qName = getQName(matcher); + String name = qName.toPrefixString(namespaceService); + return DataKeyInfo.makePropertyDataKeyInfo(name, qName); + } + return DataKeyInfo.makeTransientPropertyDataKeyInfo(keyName); + } + + private QName getQName(Matcher matcher) + { + String prefix = matcher.group(1); + String localName = matcher.group(2); + QName qName = QName.createQName(prefix, localName, namespaceService); + return qName; + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/FieldType.java b/source/java/org/alfresco/repo/forms/processor/workflow/FieldType.java index 7f75c16528..05fe54c3e6 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/FieldType.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/FieldType.java @@ -1,16 +1,16 @@ - -package org.alfresco.repo.forms.processor.workflow; - -/** - * Enum representing the various types of fields. - * - * @since 3.4 - * @author Nick Smith - */ -public enum FieldType -{ - ASSOCIATION, - PROPERTY, - TRANSIENT_ASSOCIATION, - TRANSIENT_PROPERTY; -} + +package org.alfresco.repo.forms.processor.workflow; + +/** + * Enum representing the various types of fields. + * + * @since 3.4 + * @author Nick Smith + */ +public enum FieldType +{ + ASSOCIATION, + PROPERTY, + TRANSIENT_ASSOCIATION, + TRANSIENT_PROPERTY; +} diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/MessageFieldProcessor.java b/source/java/org/alfresco/repo/forms/processor/workflow/MessageFieldProcessor.java index 450f8c9213..2b98d25b9b 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/MessageFieldProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/MessageFieldProcessor.java @@ -1,65 +1,65 @@ - -package org.alfresco.repo.forms.processor.workflow; - -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP_DATA_PREFIX; - -import org.alfresco.repo.forms.FieldDefinition; -import org.alfresco.repo.forms.PropertyFieldDefinition; -import org.alfresco.repo.forms.processor.node.TransientFieldProcessor; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * Transient field processor for the "message" property. - * - * @since 3.4 - * @author Gavin Cornwell - */ -public class MessageFieldProcessor extends TransientFieldProcessor -{ - public static final String KEY = "message"; - public static final String DATA_TYPE = DataTypeDefinition.TEXT.getLocalName(); - public static final String MSG_VALUE_NONE = "form_service.message.value.none"; - - private static final String MSG_LABEL = "form_service.message.label"; - private static final String MSG_DESCRIPTION = "form_service.message.description"; - - private static final Log LOGGER = LogFactory.getLog(MessageFieldProcessor.class); - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.node.TransientFieldProcessor#makeTransientPropertyDefinition() - */ - @Override - protected FieldDefinition makeTransientFieldDefinition() - { - PropertyFieldDefinition fieldDef = new PropertyFieldDefinition(KEY, DATA_TYPE); - fieldDef.setRepeating(false); - fieldDef.setProtectedField(true); - - fieldDef.setLabel(I18NUtil.getMessage(MSG_LABEL)); - fieldDef.setDescription(I18NUtil.getMessage(MSG_DESCRIPTION)); - fieldDef.setDataKeyName(PROP_DATA_PREFIX + KEY); - return fieldDef; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.AbstractFieldProcessor#getLogger() - */ - @Override - protected Log getLogger() - { - return LOGGER; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.AbstractFieldProcessor#getRegistryKey() - */ - @Override - protected String getRegistryKey() - { - return KEY; - } - -} + +package org.alfresco.repo.forms.processor.workflow; + +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP_DATA_PREFIX; + +import org.alfresco.repo.forms.FieldDefinition; +import org.alfresco.repo.forms.PropertyFieldDefinition; +import org.alfresco.repo.forms.processor.node.TransientFieldProcessor; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Transient field processor for the "message" property. + * + * @since 3.4 + * @author Gavin Cornwell + */ +public class MessageFieldProcessor extends TransientFieldProcessor +{ + public static final String KEY = "message"; + public static final String DATA_TYPE = DataTypeDefinition.TEXT.getLocalName(); + public static final String MSG_VALUE_NONE = "form_service.message.value.none"; + + private static final String MSG_LABEL = "form_service.message.label"; + private static final String MSG_DESCRIPTION = "form_service.message.description"; + + private static final Log LOGGER = LogFactory.getLog(MessageFieldProcessor.class); + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.node.TransientFieldProcessor#makeTransientPropertyDefinition() + */ + @Override + protected FieldDefinition makeTransientFieldDefinition() + { + PropertyFieldDefinition fieldDef = new PropertyFieldDefinition(KEY, DATA_TYPE); + fieldDef.setRepeating(false); + fieldDef.setProtectedField(true); + + fieldDef.setLabel(I18NUtil.getMessage(MSG_LABEL)); + fieldDef.setDescription(I18NUtil.getMessage(MSG_DESCRIPTION)); + fieldDef.setDataKeyName(PROP_DATA_PREFIX + KEY); + return fieldDef; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.AbstractFieldProcessor#getLogger() + */ + @Override + protected Log getLogger() + { + return LOGGER; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.AbstractFieldProcessor#getRegistryKey() + */ + @Override + protected String getRegistryKey() + { + return KEY; + } + +} diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessor.java index 2890789603..bc2a490b47 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessor.java @@ -1,291 +1,291 @@ - -package org.alfresco.repo.forms.processor.workflow; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.forms.processor.FieldProcessorRegistry; -import org.alfresco.repo.forms.processor.node.ContentModelItemData; -import org.alfresco.repo.workflow.WorkflowModel; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.NoSuchPersonException; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.workflow.WorkflowService; -import org.alfresco.service.cmr.workflow.WorkflowTask; -import org.alfresco.service.cmr.workflow.WorkflowTransition; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * FormProcessor implementation for workflow tasks. - * - * @since 3.4 - * @author Nick Smith - */ -public class TaskFormProcessor extends AbstractWorkflowFormProcessor -{ - /** Logger */ - private static final Log LOGGER = LogFactory.getLog(TaskFormProcessor.class); - - protected AuthenticationService authenticationService; - protected PersonService personService; - - // Constructor for Spring - public TaskFormProcessor() - { - super(); - } - - - - // Constructor for tests. - public TaskFormProcessor(WorkflowService workflowService, NamespaceService namespaceService, - DictionaryService dictionaryService, AuthenticationService authenticationService, - PersonService personService, FieldProcessorRegistry fieldProcessorRegistry) - { - this.workflowService = workflowService; - this.namespaceService = namespaceService; - this.dictionaryService = dictionaryService; - this.authenticationService = authenticationService; - this.personService = personService; - this.fieldProcessorRegistry = fieldProcessorRegistry; - } - - /** - * Sets the authentication service - * - * @param authenticationService The AuthenticationService instance - */ - public void setAuthenticationService(AuthenticationService authenticationService) - { - this.authenticationService = authenticationService; - } - - /** - * Sets the person service - * - * @param personService The PersonService instance - */ - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - /** - * {@inheritDoc} - */ - @Override - protected WorkflowTask getTypedItemForDecodedId(String itemId) - { - WorkflowTask task = workflowService.getTaskById(itemId); - if (task == null) - { - String msg = "Workflow task does not exist: " + itemId; - throw new IllegalArgumentException(msg); - } - return task; - } - - /** - * {@inheritDoc} - */ - @Override - protected String getItemType(WorkflowTask item) - { - TypeDefinition typeDef = item.getDefinition().getMetadata(); - return typeDef.getName().toPrefixString(namespaceService); - } - - /** - * {@inheritDoc} - */ - @Override - protected String getItemURI(WorkflowTask item) - { - return "api/task-instances/" + item.getId(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Log getLogger() - { - return LOGGER; - } - - @Override - protected TypeDefinition getBaseType(WorkflowTask task) - { - return task.getDefinition().getMetadata(); - } - - @Override - protected Map getPropertyValues(WorkflowTask task) - { - return task.getProperties(); - } - - @Override - protected Map getAssociationValues(WorkflowTask item) - { - return item.getProperties(); - } - - @Override - protected Map getTransientValues(WorkflowTask item) - { - Map values = new HashMap(2); - values.put(TransitionFieldProcessor.KEY, getTransitionValues(item)); - values.put(PackageItemsFieldProcessor.KEY, getPackageItemValues(item)); - values.put(MessageFieldProcessor.KEY, getMessageValue(item)); - values.put(TaskOwnerFieldProcessor.KEY, getTaskOwnerValue(item)); - return values; - } - - /** - * @param task WorkflowTask - * @return Object - */ - private Object getPackageItemValues(WorkflowTask task) - { - List items = workflowService.getPackageContents(task.getId()); - ArrayList results = new ArrayList(items.size()); - for (NodeRef item : items) - { - results.add(item.toString()); - } - return results; - } - - private String getMessageValue(WorkflowTask task) - { - String message = I18NUtil.getMessage(MessageFieldProcessor.MSG_VALUE_NONE); - - String description = (String)task.getProperties().get(WorkflowModel.PROP_DESCRIPTION); - if (description != null) - { - String taskTitle = task.getTitle(); - if (taskTitle == null || !taskTitle.equals(description)) - { - message = description; - } - } - - return message; - } - - private String getTaskOwnerValue(WorkflowTask task) - { - String owner = (String)task.getProperties().get(ContentModel.PROP_OWNER); - - if (owner == null || owner.length() == 0) - { - return null; - } - - return buildTaskOwnerString(owner); - } - - private String buildTaskOwnerString(String ownerUsername) - { - StringBuilder builder = new StringBuilder(ownerUsername); - - // get the person node - NodeRef ownerNodeRef = null; - try - { - ownerNodeRef = this.personService.getPerson(ownerUsername); - } - catch (NoSuchPersonException nspe) - { - // just return the username if the user doesn't exist - } - - if (ownerNodeRef != null) - { - Map personProps = this.nodeService.getProperties(ownerNodeRef); - - builder.append("|"); - builder.append(personProps.containsKey(ContentModel.PROP_FIRSTNAME) ? - personProps.get(ContentModel.PROP_FIRSTNAME) : ""); - builder.append("|"); - builder.append(personProps.containsKey(ContentModel.PROP_LASTNAME) ? - personProps.get(ContentModel.PROP_LASTNAME) : ""); - } - - return builder.toString(); - } - - private String getTransitionValues(WorkflowTask item) - { - WorkflowTransition[] transitions = item.getDefinition().getNode().getTransitions(); - - if (transitions == null || transitions.length == 0) - { - return ""; - } - - return buildTransitionString(item, transitions); - } - - private String buildTransitionString(WorkflowTask item, WorkflowTransition[] transitions) - { - StringBuilder builder = new StringBuilder(); - List hiddenStr = getHiddenTransitions(item); - for (WorkflowTransition transition : transitions) - { - String transId = transition.getId(); - if (hiddenStr.contains(transId) == false) - { - builder.append(transId != null ? transId : ""); - builder.append("|"); - builder.append(transition.getTitle()); - builder.append(","); - } - } - builder.deleteCharAt(builder.length()-1); - return builder.toString(); - } - - @SuppressWarnings("unchecked") - private List getHiddenTransitions(WorkflowTask task) - { - Serializable hiddenValues = task.getProperties().get(WorkflowModel.PROP_HIDDEN_TRANSITIONS); - if (hiddenValues != null) - { - if (hiddenValues instanceof List) - { - return (List) hiddenValues; - } - else if (hiddenValues instanceof String && ((String)hiddenValues).length() > 0) - { - return Arrays.asList(((String)hiddenValues).split(",")); - } - } - return Collections.emptyList(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.workflow.AbstractWorkflowFormProcessor#makeFormPersister(java.lang.Object) - */ - @Override - protected ContentModelFormPersister makeFormPersister(WorkflowTask item) - { - ContentModelItemData itemData = makeItemData(item); - return new TaskFormPersister(itemData, namespaceService, dictionaryService, - workflowService, nodeService, authenticationService, behaviourFilter, LOGGER); - } -} + +package org.alfresco.repo.forms.processor.workflow; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.forms.processor.FieldProcessorRegistry; +import org.alfresco.repo.forms.processor.node.ContentModelItemData; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.NoSuchPersonException; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTransition; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * FormProcessor implementation for workflow tasks. + * + * @since 3.4 + * @author Nick Smith + */ +public class TaskFormProcessor extends AbstractWorkflowFormProcessor +{ + /** Logger */ + private static final Log LOGGER = LogFactory.getLog(TaskFormProcessor.class); + + protected AuthenticationService authenticationService; + protected PersonService personService; + + // Constructor for Spring + public TaskFormProcessor() + { + super(); + } + + + + // Constructor for tests. + public TaskFormProcessor(WorkflowService workflowService, NamespaceService namespaceService, + DictionaryService dictionaryService, AuthenticationService authenticationService, + PersonService personService, FieldProcessorRegistry fieldProcessorRegistry) + { + this.workflowService = workflowService; + this.namespaceService = namespaceService; + this.dictionaryService = dictionaryService; + this.authenticationService = authenticationService; + this.personService = personService; + this.fieldProcessorRegistry = fieldProcessorRegistry; + } + + /** + * Sets the authentication service + * + * @param authenticationService The AuthenticationService instance + */ + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + /** + * Sets the person service + * + * @param personService The PersonService instance + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * {@inheritDoc} + */ + @Override + protected WorkflowTask getTypedItemForDecodedId(String itemId) + { + WorkflowTask task = workflowService.getTaskById(itemId); + if (task == null) + { + String msg = "Workflow task does not exist: " + itemId; + throw new IllegalArgumentException(msg); + } + return task; + } + + /** + * {@inheritDoc} + */ + @Override + protected String getItemType(WorkflowTask item) + { + TypeDefinition typeDef = item.getDefinition().getMetadata(); + return typeDef.getName().toPrefixString(namespaceService); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getItemURI(WorkflowTask item) + { + return "api/task-instances/" + item.getId(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Log getLogger() + { + return LOGGER; + } + + @Override + protected TypeDefinition getBaseType(WorkflowTask task) + { + return task.getDefinition().getMetadata(); + } + + @Override + protected Map getPropertyValues(WorkflowTask task) + { + return task.getProperties(); + } + + @Override + protected Map getAssociationValues(WorkflowTask item) + { + return item.getProperties(); + } + + @Override + protected Map getTransientValues(WorkflowTask item) + { + Map values = new HashMap(2); + values.put(TransitionFieldProcessor.KEY, getTransitionValues(item)); + values.put(PackageItemsFieldProcessor.KEY, getPackageItemValues(item)); + values.put(MessageFieldProcessor.KEY, getMessageValue(item)); + values.put(TaskOwnerFieldProcessor.KEY, getTaskOwnerValue(item)); + return values; + } + + /** + * @param task WorkflowTask + * @return Object + */ + private Object getPackageItemValues(WorkflowTask task) + { + List items = workflowService.getPackageContents(task.getId()); + ArrayList results = new ArrayList(items.size()); + for (NodeRef item : items) + { + results.add(item.toString()); + } + return results; + } + + private String getMessageValue(WorkflowTask task) + { + String message = I18NUtil.getMessage(MessageFieldProcessor.MSG_VALUE_NONE); + + String description = (String)task.getProperties().get(WorkflowModel.PROP_DESCRIPTION); + if (description != null) + { + String taskTitle = task.getTitle(); + if (taskTitle == null || !taskTitle.equals(description)) + { + message = description; + } + } + + return message; + } + + private String getTaskOwnerValue(WorkflowTask task) + { + String owner = (String)task.getProperties().get(ContentModel.PROP_OWNER); + + if (owner == null || owner.length() == 0) + { + return null; + } + + return buildTaskOwnerString(owner); + } + + private String buildTaskOwnerString(String ownerUsername) + { + StringBuilder builder = new StringBuilder(ownerUsername); + + // get the person node + NodeRef ownerNodeRef = null; + try + { + ownerNodeRef = this.personService.getPerson(ownerUsername); + } + catch (NoSuchPersonException nspe) + { + // just return the username if the user doesn't exist + } + + if (ownerNodeRef != null) + { + Map personProps = this.nodeService.getProperties(ownerNodeRef); + + builder.append("|"); + builder.append(personProps.containsKey(ContentModel.PROP_FIRSTNAME) ? + personProps.get(ContentModel.PROP_FIRSTNAME) : ""); + builder.append("|"); + builder.append(personProps.containsKey(ContentModel.PROP_LASTNAME) ? + personProps.get(ContentModel.PROP_LASTNAME) : ""); + } + + return builder.toString(); + } + + private String getTransitionValues(WorkflowTask item) + { + WorkflowTransition[] transitions = item.getDefinition().getNode().getTransitions(); + + if (transitions == null || transitions.length == 0) + { + return ""; + } + + return buildTransitionString(item, transitions); + } + + private String buildTransitionString(WorkflowTask item, WorkflowTransition[] transitions) + { + StringBuilder builder = new StringBuilder(); + List hiddenStr = getHiddenTransitions(item); + for (WorkflowTransition transition : transitions) + { + String transId = transition.getId(); + if (hiddenStr.contains(transId) == false) + { + builder.append(transId != null ? transId : ""); + builder.append("|"); + builder.append(transition.getTitle()); + builder.append(","); + } + } + builder.deleteCharAt(builder.length()-1); + return builder.toString(); + } + + @SuppressWarnings("unchecked") + private List getHiddenTransitions(WorkflowTask task) + { + Serializable hiddenValues = task.getProperties().get(WorkflowModel.PROP_HIDDEN_TRANSITIONS); + if (hiddenValues != null) + { + if (hiddenValues instanceof List) + { + return (List) hiddenValues; + } + else if (hiddenValues instanceof String && ((String)hiddenValues).length() > 0) + { + return Arrays.asList(((String)hiddenValues).split(",")); + } + } + return Collections.emptyList(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.workflow.AbstractWorkflowFormProcessor#makeFormPersister(java.lang.Object) + */ + @Override + protected ContentModelFormPersister makeFormPersister(WorkflowTask item) + { + ContentModelItemData itemData = makeItemData(item); + return new TaskFormPersister(itemData, namespaceService, dictionaryService, + workflowService, nodeService, authenticationService, behaviourFilter, LOGGER); + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/TaskOwnerFieldProcessor.java b/source/java/org/alfresco/repo/forms/processor/workflow/TaskOwnerFieldProcessor.java index 737aba796b..3d75161eec 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/TaskOwnerFieldProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/TaskOwnerFieldProcessor.java @@ -1,63 +1,63 @@ - -package org.alfresco.repo.forms.processor.workflow; - -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP_DATA_PREFIX; - -import org.alfresco.repo.forms.FieldDefinition; -import org.alfresco.repo.forms.PropertyFieldDefinition; -import org.alfresco.repo.forms.processor.node.TransientFieldProcessor; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * Transient field processor for the "taskOwner" property. - * - * @since 3.4 - * @author Gavin Cornwell - */ -public class TaskOwnerFieldProcessor extends TransientFieldProcessor -{ - public static final String KEY = "taskOwner"; - public static final String DATA_TYPE = KEY; - - private static final String MSG_LABEL = "form_service.task.owner.label"; - private static final String MSG_DESCRIPTION = "form_service.task.owner.description"; - - private static final Log LOGGER = LogFactory.getLog(TaskOwnerFieldProcessor.class); - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.node.TransientFieldProcessor#makeTransientPropertyDefinition() - */ - @Override - protected FieldDefinition makeTransientFieldDefinition() - { - PropertyFieldDefinition fieldDef = new PropertyFieldDefinition(KEY, DATA_TYPE); - fieldDef.setRepeating(false); - fieldDef.setProtectedField(true); - - fieldDef.setLabel(I18NUtil.getMessage(MSG_LABEL)); - fieldDef.setDescription(I18NUtil.getMessage(MSG_DESCRIPTION)); - fieldDef.setDataKeyName(PROP_DATA_PREFIX + KEY); - return fieldDef; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.AbstractFieldProcessor#getLogger() - */ - @Override - protected Log getLogger() - { - return LOGGER; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.AbstractFieldProcessor#getRegistryKey() - */ - @Override - protected String getRegistryKey() - { - return KEY; - } - -} + +package org.alfresco.repo.forms.processor.workflow; + +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP_DATA_PREFIX; + +import org.alfresco.repo.forms.FieldDefinition; +import org.alfresco.repo.forms.PropertyFieldDefinition; +import org.alfresco.repo.forms.processor.node.TransientFieldProcessor; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Transient field processor for the "taskOwner" property. + * + * @since 3.4 + * @author Gavin Cornwell + */ +public class TaskOwnerFieldProcessor extends TransientFieldProcessor +{ + public static final String KEY = "taskOwner"; + public static final String DATA_TYPE = KEY; + + private static final String MSG_LABEL = "form_service.task.owner.label"; + private static final String MSG_DESCRIPTION = "form_service.task.owner.description"; + + private static final Log LOGGER = LogFactory.getLog(TaskOwnerFieldProcessor.class); + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.node.TransientFieldProcessor#makeTransientPropertyDefinition() + */ + @Override + protected FieldDefinition makeTransientFieldDefinition() + { + PropertyFieldDefinition fieldDef = new PropertyFieldDefinition(KEY, DATA_TYPE); + fieldDef.setRepeating(false); + fieldDef.setProtectedField(true); + + fieldDef.setLabel(I18NUtil.getMessage(MSG_LABEL)); + fieldDef.setDescription(I18NUtil.getMessage(MSG_DESCRIPTION)); + fieldDef.setDataKeyName(PROP_DATA_PREFIX + KEY); + return fieldDef; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.AbstractFieldProcessor#getLogger() + */ + @Override + protected Log getLogger() + { + return LOGGER; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.AbstractFieldProcessor#getRegistryKey() + */ + @Override + protected String getRegistryKey() + { + return KEY; + } + +} diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/TypedPropertyValueGetter.java b/source/java/org/alfresco/repo/forms/processor/workflow/TypedPropertyValueGetter.java index d740e52eac..28a81119a3 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/TypedPropertyValueGetter.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/TypedPropertyValueGetter.java @@ -1,195 +1,195 @@ - -package org.alfresco.repo.forms.processor.workflow; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.TimeZone; - -import org.alfresco.repo.forms.FormException; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.util.ISO8601DateFormat; -import org.json.JSONArray; -import org.json.JSONException; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * Utility class that retrieves the appropriately typed value for a property. - * - * @since 3.4 - * @author Nick Smith - */ -public class TypedPropertyValueGetter -{ - public static final String ON = "on"; - - public Serializable getValue(Object value, PropertyDefinition propDef) - { - if (value == null) - { - return null; - } - - // before persisting check data type of property - if (propDef.isMultiValued()) - { - return processMultiValuedType(value); - } - - - if (isBooleanProperty(propDef)) - { - return processBooleanValue(value); - } - else if (isLocaleProperty(propDef)) - { - return processLocaleValue(value); - } - else if (value instanceof String) - { - String valStr = (String) value; - - // make sure empty strings stay as empty strings, everything else - // should be represented as null - if (isTextProperty(propDef)) - { - return valStr; - } - if(valStr.isEmpty()) - { - return null; - } - if(isDateProperty(propDef) && !ISO8601DateFormat.isTimeComponentDefined(valStr)) - { - // Special handling for day-only date storage (ALF-10243) - return ISO8601DateFormat.parseDayOnly(valStr, TimeZone.getDefault()); - } - } - if (value instanceof Serializable) - { - return (Serializable) DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), value); - } - else - { - throw new FormException("Property values must be of a Serializable type! Value type: " + value.getClass()); - } - } - - private boolean isTextProperty(PropertyDefinition propDef) - { - return propDef.getDataType().getName().equals(DataTypeDefinition.TEXT) || - propDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT); - } - - private Boolean processBooleanValue(Object value) - { - // check for browser representation of true, that being "on" - if (value instanceof String) - { - if (ON.equals(value)) - { - return Boolean.TRUE; - } - else - { - return Boolean.valueOf((String)value); - } - } - else - { - return Boolean.FALSE; - } - } - - private boolean isBooleanProperty(PropertyDefinition propDef) - { - return propDef.getDataType().getName().equals(DataTypeDefinition.BOOLEAN); - } - - private boolean isDateProperty(PropertyDefinition propDef) - { - return propDef.getDataType().getName().equals(DataTypeDefinition.DATE); - } - - private Serializable processLocaleValue(Object value) - { - if (value instanceof String) - { - return I18NUtil.parseLocale((String) value); - } - else - { - throw new FormException("Locale property values must be represented as a String! Value is of type: " - + value.getClass()); - } - } - - private boolean isLocaleProperty(PropertyDefinition propDef) - { - return propDef.getDataType().getName().equals(DataTypeDefinition.LOCALE); - } - - private Serializable processMultiValuedType(Object value) - { - // depending on client the value could be a comma separated string, - // a List object or a JSONArray object - if (value instanceof String) - { - String stringValue = (String) value; - return processMultiValueString(stringValue); - } - else if (value instanceof JSONArray) - { - // if value is a JSONArray convert to List of Object - JSONArray jsonArr = (JSONArray) value; - return processJSONArray(jsonArr); - } - else if (value instanceof List) - { - // persist the list - return (Serializable) value; - } - else - { - throw new FormException("The value is an unsupported multi-value type: " + value); - } - } - - private Serializable processJSONArray(JSONArray jsonArr) - { - int arrLength = jsonArr.length(); - ArrayList list = new ArrayList(arrLength); - try - { - for (int x = 0; x < arrLength; x++) - { - list.add(jsonArr.get(x)); - } - } - catch (JSONException je) - { - throw new FormException("Failed to convert JSONArray to List", je); - } - return list; - } - - private Serializable processMultiValueString(String stringValue) - { - if (stringValue.length() == 0) - { - // empty string for multi-valued properties - // should be stored as null - return null; - } - else - { - // if value is a String convert to List of String persist the List - String[] values = stringValue.split(","); - List valueList = Arrays.asList(values); - return new ArrayList(valueList); - } - } -} + +package org.alfresco.repo.forms.processor.workflow; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.TimeZone; + +import org.alfresco.repo.forms.FormException; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.util.ISO8601DateFormat; +import org.json.JSONArray; +import org.json.JSONException; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Utility class that retrieves the appropriately typed value for a property. + * + * @since 3.4 + * @author Nick Smith + */ +public class TypedPropertyValueGetter +{ + public static final String ON = "on"; + + public Serializable getValue(Object value, PropertyDefinition propDef) + { + if (value == null) + { + return null; + } + + // before persisting check data type of property + if (propDef.isMultiValued()) + { + return processMultiValuedType(value); + } + + + if (isBooleanProperty(propDef)) + { + return processBooleanValue(value); + } + else if (isLocaleProperty(propDef)) + { + return processLocaleValue(value); + } + else if (value instanceof String) + { + String valStr = (String) value; + + // make sure empty strings stay as empty strings, everything else + // should be represented as null + if (isTextProperty(propDef)) + { + return valStr; + } + if(valStr.isEmpty()) + { + return null; + } + if(isDateProperty(propDef) && !ISO8601DateFormat.isTimeComponentDefined(valStr)) + { + // Special handling for day-only date storage (ALF-10243) + return ISO8601DateFormat.parseDayOnly(valStr, TimeZone.getDefault()); + } + } + if (value instanceof Serializable) + { + return (Serializable) DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), value); + } + else + { + throw new FormException("Property values must be of a Serializable type! Value type: " + value.getClass()); + } + } + + private boolean isTextProperty(PropertyDefinition propDef) + { + return propDef.getDataType().getName().equals(DataTypeDefinition.TEXT) || + propDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT); + } + + private Boolean processBooleanValue(Object value) + { + // check for browser representation of true, that being "on" + if (value instanceof String) + { + if (ON.equals(value)) + { + return Boolean.TRUE; + } + else + { + return Boolean.valueOf((String)value); + } + } + else + { + return Boolean.FALSE; + } + } + + private boolean isBooleanProperty(PropertyDefinition propDef) + { + return propDef.getDataType().getName().equals(DataTypeDefinition.BOOLEAN); + } + + private boolean isDateProperty(PropertyDefinition propDef) + { + return propDef.getDataType().getName().equals(DataTypeDefinition.DATE); + } + + private Serializable processLocaleValue(Object value) + { + if (value instanceof String) + { + return I18NUtil.parseLocale((String) value); + } + else + { + throw new FormException("Locale property values must be represented as a String! Value is of type: " + + value.getClass()); + } + } + + private boolean isLocaleProperty(PropertyDefinition propDef) + { + return propDef.getDataType().getName().equals(DataTypeDefinition.LOCALE); + } + + private Serializable processMultiValuedType(Object value) + { + // depending on client the value could be a comma separated string, + // a List object or a JSONArray object + if (value instanceof String) + { + String stringValue = (String) value; + return processMultiValueString(stringValue); + } + else if (value instanceof JSONArray) + { + // if value is a JSONArray convert to List of Object + JSONArray jsonArr = (JSONArray) value; + return processJSONArray(jsonArr); + } + else if (value instanceof List) + { + // persist the list + return (Serializable) value; + } + else + { + throw new FormException("The value is an unsupported multi-value type: " + value); + } + } + + private Serializable processJSONArray(JSONArray jsonArr) + { + int arrLength = jsonArr.length(); + ArrayList list = new ArrayList(arrLength); + try + { + for (int x = 0; x < arrLength; x++) + { + list.add(jsonArr.get(x)); + } + } + catch (JSONException je) + { + throw new FormException("Failed to convert JSONArray to List", je); + } + return list; + } + + private Serializable processMultiValueString(String stringValue) + { + if (stringValue.length() == 0) + { + // empty string for multi-valued properties + // should be stored as null + return null; + } + else + { + // if value is a String convert to List of String persist the List + String[] values = stringValue.split(","); + List valueList = Arrays.asList(values); + return new ArrayList(valueList); + } + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessor.java index b4f17dee98..58ad5f1955 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessor.java @@ -1,146 +1,146 @@ -package org.alfresco.repo.forms.processor.workflow; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.alfresco.repo.forms.processor.node.ContentModelItemData; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; -import org.alfresco.service.cmr.workflow.WorkflowInstance; -import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * FormProcessor implementation that can generate and persist - * Form objects for workflow definitions. - * - * @since 3.4 - * @author Nick Smith - */ -public class WorkflowFormProcessor extends AbstractWorkflowFormProcessor -{ - /** Logger */ - private final static Log logger = LogFactory.getLog(WorkflowFormProcessor.class); - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getAssociationValues(java.lang.Object) - */ - @Override - protected Map getAssociationValues(WorkflowDefinition item) - { - return null; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getBaseType(java.lang.Object) - */ - @Override - protected TypeDefinition getBaseType(WorkflowDefinition item) - { - // TODO: I'm not sure this is safe as getStartTaskDefinition() is 'optional'. - WorkflowTaskDefinition startTask = item.getStartTaskDefinition(); - return startTask.getMetadata(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getPropertyValues(java.lang.Object) - */ - @Override - protected Map getPropertyValues(WorkflowDefinition item) - { - return null; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getTransientValues(java.lang.Object) - */ - @Override - protected Map getTransientValues(WorkflowDefinition item) - { - return Collections.singletonMap( - PackageItemsFieldProcessor.KEY, Collections.emptyList()); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemType(java.lang.Object) - */ - @Override - protected String getItemType(WorkflowDefinition item) - { - return item.getName(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemURI(java.lang.Object) - */ - @Override - protected String getItemURI(WorkflowDefinition item) - { - return "api/workflow-definitions/"+item.getId(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getLogger() - */ - @Override - protected Log getLogger() - { - return logger; - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.forms.processor.workflow.AbstractWorkflowFormProcessor - * #getTypedItemForDecodedId(java.lang.String) - */ - @Override - protected WorkflowDefinition getTypedItemForDecodedId(String itemId) - { - WorkflowDefinition workflowDef = workflowService.getDefinitionByName(itemId); - if (workflowDef == null) - { - String msg = "Workflow definition does not exist: " + itemId; - throw new IllegalArgumentException(msg); - } - return workflowDef; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.workflow.AbstractWorkflowFormProcessor#makeFormPersister(java.lang.Object) - */ - @Override - protected ContentModelFormPersister makeFormPersister(WorkflowDefinition item) - { - ContentModelItemData itemData = makeItemData(item); - return new WorkflowFormPersister(itemData, namespaceService, dictionaryService, workflowService, nodeService, behaviourFilter, logger); - } - - /* - * @see org.alfresco.repo.forms.processor.workflow.AbstractWorkflowFormProcessor#getDefaultIgnoredFields() - */ - @Override - protected List getDefaultIgnoredFields() - { - List fields = super.getDefaultIgnoredFields(); - - if (fields == null) - { - fields = new ArrayList(3); - } - - // for the workflow form processor also hide the task specific - // description, due date and priority fields - fields.add("bpm:description"); - fields.add("bpm:dueDate"); - fields.add("bpm:priority"); - - return fields; - } -} +package org.alfresco.repo.forms.processor.workflow; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.forms.processor.node.ContentModelItemData; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * FormProcessor implementation that can generate and persist + * Form objects for workflow definitions. + * + * @since 3.4 + * @author Nick Smith + */ +public class WorkflowFormProcessor extends AbstractWorkflowFormProcessor +{ + /** Logger */ + private final static Log logger = LogFactory.getLog(WorkflowFormProcessor.class); + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getAssociationValues(java.lang.Object) + */ + @Override + protected Map getAssociationValues(WorkflowDefinition item) + { + return null; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getBaseType(java.lang.Object) + */ + @Override + protected TypeDefinition getBaseType(WorkflowDefinition item) + { + // TODO: I'm not sure this is safe as getStartTaskDefinition() is 'optional'. + WorkflowTaskDefinition startTask = item.getStartTaskDefinition(); + return startTask.getMetadata(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getPropertyValues(java.lang.Object) + */ + @Override + protected Map getPropertyValues(WorkflowDefinition item) + { + return null; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.node.ContentModelFormProcessor#getTransientValues(java.lang.Object) + */ + @Override + protected Map getTransientValues(WorkflowDefinition item) + { + return Collections.singletonMap( + PackageItemsFieldProcessor.KEY, Collections.emptyList()); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemType(java.lang.Object) + */ + @Override + protected String getItemType(WorkflowDefinition item) + { + return item.getName(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemURI(java.lang.Object) + */ + @Override + protected String getItemURI(WorkflowDefinition item) + { + return "api/workflow-definitions/"+item.getId(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.FilteredFormProcessor#getLogger() + */ + @Override + protected Log getLogger() + { + return logger; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.forms.processor.workflow.AbstractWorkflowFormProcessor + * #getTypedItemForDecodedId(java.lang.String) + */ + @Override + protected WorkflowDefinition getTypedItemForDecodedId(String itemId) + { + WorkflowDefinition workflowDef = workflowService.getDefinitionByName(itemId); + if (workflowDef == null) + { + String msg = "Workflow definition does not exist: " + itemId; + throw new IllegalArgumentException(msg); + } + return workflowDef; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.forms.processor.workflow.AbstractWorkflowFormProcessor#makeFormPersister(java.lang.Object) + */ + @Override + protected ContentModelFormPersister makeFormPersister(WorkflowDefinition item) + { + ContentModelItemData itemData = makeItemData(item); + return new WorkflowFormPersister(itemData, namespaceService, dictionaryService, workflowService, nodeService, behaviourFilter, logger); + } + + /* + * @see org.alfresco.repo.forms.processor.workflow.AbstractWorkflowFormProcessor#getDefaultIgnoredFields() + */ + @Override + protected List getDefaultIgnoredFields() + { + List fields = super.getDefaultIgnoredFields(); + + if (fields == null) + { + fields = new ArrayList(3); + } + + // for the workflow form processor also hide the task specific + // description, due date and priority fields + fields.add("bpm:description"); + fields.add("bpm:dueDate"); + fields.add("bpm:priority"); + + return fields; + } +} diff --git a/source/java/org/alfresco/repo/importer/ImportTimerProgress.java b/source/java/org/alfresco/repo/importer/ImportTimerProgress.java index 4fd3e9f31f..064ee42790 100644 --- a/source/java/org/alfresco/repo/importer/ImportTimerProgress.java +++ b/source/java/org/alfresco/repo/importer/ImportTimerProgress.java @@ -1,169 +1,169 @@ -package org.alfresco.repo.importer; - -import java.io.Serializable; -import java.util.Date; - -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.AccessPermission; -import org.alfresco.service.cmr.view.ImporterProgress; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - - -/** - * Import Progress that provides import metrics. - * - * @author davidc - */ -public class ImportTimerProgress implements ImporterProgress -{ - private Date start = null; - private long nodeCreateCount = 0; - private long propCount = 0; - private long contentCount = 0; - private long nodeLinkedCount = 0; - private long aspectAdded = 0; - private long permissionCount = 0; - - private Log logger = LogFactory.getLog(ImportTimerProgress.class);; - - /** - * Construct - */ - public ImportTimerProgress() - { - } - - /** - * Construct - * - * @param logger Log - */ - public ImportTimerProgress(Log logger) - { - this.logger = logger; - } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterProgress#started() - */ - public void started() - { - start = new Date(); - nodeCreateCount = 0; - propCount = 0; - contentCount = 0; - nodeLinkedCount = 0; - aspectAdded = 0; - permissionCount = 0; - - if (logger.isDebugEnabled()) - logger.debug("Import started at " + start + " (" + start.getTime() + ")"); - } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterProgress#completed() - */ - public void completed() - { - if (logger.isDebugEnabled()) - { - Date end = new Date(); - logger.debug("Import completed at " + end + " (" + end.getTime() + ")"); - dumpStats(end); - } - } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterProgress#error(java.lang.Throwable) - */ - public void error(Throwable e) - { - if (logger.isDebugEnabled()) - { - Date end = new Date(); - logger.debug("Import completed at " + end + " (" + end.getTime() + ")"); - logger.debug("Error occured at " + end + " (" + end.getTime() + ")"); - logger.debug("Exception: " + e.toString()); - dumpStats(end); - } - } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterProgress#nodeCreated(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) - */ - public void nodeCreated(NodeRef nodeRef, NodeRef parentRef, QName assocName, QName childName) - { - nodeCreateCount++; - } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterProgress#nodeLinked(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) - */ - public void nodeLinked(NodeRef nodeRef, NodeRef parentRef, QName assocName, QName childName) - { - nodeLinkedCount++; - } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterProgress#contentCreated(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) - */ - public void contentCreated(NodeRef nodeRef, String sourceUrl) - { - contentCount++; - } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterProgress#propertySet(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, java.io.Serializable) - */ - public void propertySet(NodeRef nodeRef, QName property, Serializable value) - { - propCount++; - } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterProgress#permissionSet(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.security.AccessPermission) - */ - public void permissionSet(NodeRef nodeRef, AccessPermission permission) - { - permissionCount++; - } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterProgress#aspectAdded(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) - */ - public void aspectAdded(NodeRef nodeRef, QName aspect) - { - aspectAdded++; - } - - /** - * Dump statistics - * - * @param end Date - */ - private void dumpStats(Date end) - { - if (logger.isDebugEnabled()) - { - logger.debug("Import duration: " + (end.getTime() - start.getTime()) + " ms (Note: excluding commit time)"); - logger.debug(" Nodes created: " + nodeCreateCount); - logger.debug(" Nodes linked: " + nodeLinkedCount); - logger.debug(" Aspects Added: " + aspectAdded); - logger.debug(" Properties set: " + propCount); - logger.debug(" Content set: " + contentCount); - logger.debug(" Permissions set: " + permissionCount); - } - } - -} +package org.alfresco.repo.importer; + +import java.io.Serializable; +import java.util.Date; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.view.ImporterProgress; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Import Progress that provides import metrics. + * + * @author davidc + */ +public class ImportTimerProgress implements ImporterProgress +{ + private Date start = null; + private long nodeCreateCount = 0; + private long propCount = 0; + private long contentCount = 0; + private long nodeLinkedCount = 0; + private long aspectAdded = 0; + private long permissionCount = 0; + + private Log logger = LogFactory.getLog(ImportTimerProgress.class);; + + /** + * Construct + */ + public ImportTimerProgress() + { + } + + /** + * Construct + * + * @param logger Log + */ + public ImportTimerProgress(Log logger) + { + this.logger = logger; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterProgress#started() + */ + public void started() + { + start = new Date(); + nodeCreateCount = 0; + propCount = 0; + contentCount = 0; + nodeLinkedCount = 0; + aspectAdded = 0; + permissionCount = 0; + + if (logger.isDebugEnabled()) + logger.debug("Import started at " + start + " (" + start.getTime() + ")"); + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterProgress#completed() + */ + public void completed() + { + if (logger.isDebugEnabled()) + { + Date end = new Date(); + logger.debug("Import completed at " + end + " (" + end.getTime() + ")"); + dumpStats(end); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterProgress#error(java.lang.Throwable) + */ + public void error(Throwable e) + { + if (logger.isDebugEnabled()) + { + Date end = new Date(); + logger.debug("Import completed at " + end + " (" + end.getTime() + ")"); + logger.debug("Error occured at " + end + " (" + end.getTime() + ")"); + logger.debug("Exception: " + e.toString()); + dumpStats(end); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterProgress#nodeCreated(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) + */ + public void nodeCreated(NodeRef nodeRef, NodeRef parentRef, QName assocName, QName childName) + { + nodeCreateCount++; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterProgress#nodeLinked(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) + */ + public void nodeLinked(NodeRef nodeRef, NodeRef parentRef, QName assocName, QName childName) + { + nodeLinkedCount++; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterProgress#contentCreated(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + public void contentCreated(NodeRef nodeRef, String sourceUrl) + { + contentCount++; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterProgress#propertySet(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, java.io.Serializable) + */ + public void propertySet(NodeRef nodeRef, QName property, Serializable value) + { + propCount++; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterProgress#permissionSet(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.security.AccessPermission) + */ + public void permissionSet(NodeRef nodeRef, AccessPermission permission) + { + permissionCount++; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterProgress#aspectAdded(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) + */ + public void aspectAdded(NodeRef nodeRef, QName aspect) + { + aspectAdded++; + } + + /** + * Dump statistics + * + * @param end Date + */ + private void dumpStats(Date end) + { + if (logger.isDebugEnabled()) + { + logger.debug("Import duration: " + (end.getTime() - start.getTime()) + " ms (Note: excluding commit time)"); + logger.debug(" Nodes created: " + nodeCreateCount); + logger.debug(" Nodes linked: " + nodeLinkedCount); + logger.debug(" Aspects Added: " + aspectAdded); + logger.debug(" Properties set: " + propCount); + logger.debug(" Content set: " + contentCount); + logger.debug(" Permissions set: " + permissionCount); + } + } + +} diff --git a/source/java/org/alfresco/repo/importer/ImporterBootstrapViews.java b/source/java/org/alfresco/repo/importer/ImporterBootstrapViews.java index 3ded657da8..e67e857bcb 100644 --- a/source/java/org/alfresco/repo/importer/ImporterBootstrapViews.java +++ b/source/java/org/alfresco/repo/importer/ImporterBootstrapViews.java @@ -1,46 +1,46 @@ -package org.alfresco.repo.importer; - -import java.util.List; -import java.util.Properties; - -import org.springframework.beans.factory.InitializingBean; - -/** - * Collection of views to import - * - * @author David Caruana - */ -public class ImporterBootstrapViews implements InitializingBean -{ - // Dependencies - private ImporterBootstrap importer; - private List bootstrapViews; - - - /** - * Sets the importer - * - * @param importer ImporterBootstrap - */ - public void setImporter(ImporterBootstrap importer) - { - this.importer = importer; - } - - /** - * Sets the bootstrap views - * - * @param bootstrapViews List - */ - public void setBootstrapViews(List bootstrapViews) - { - this.bootstrapViews = bootstrapViews; - } - - - public void afterPropertiesSet() throws Exception - { - importer.addBootstrapViews(bootstrapViews); - } - -} +package org.alfresco.repo.importer; + +import java.util.List; +import java.util.Properties; + +import org.springframework.beans.factory.InitializingBean; + +/** + * Collection of views to import + * + * @author David Caruana + */ +public class ImporterBootstrapViews implements InitializingBean +{ + // Dependencies + private ImporterBootstrap importer; + private List bootstrapViews; + + + /** + * Sets the importer + * + * @param importer ImporterBootstrap + */ + public void setImporter(ImporterBootstrap importer) + { + this.importer = importer; + } + + /** + * Sets the bootstrap views + * + * @param bootstrapViews List + */ + public void setBootstrapViews(List bootstrapViews) + { + this.bootstrapViews = bootstrapViews; + } + + + public void afterPropertiesSet() throws Exception + { + importer.addBootstrapViews(bootstrapViews); + } + +} diff --git a/source/java/org/alfresco/repo/importer/system/PatchInfo.java b/source/java/org/alfresco/repo/importer/system/PatchInfo.java index d23354b1ba..4c7587b862 100644 --- a/source/java/org/alfresco/repo/importer/system/PatchInfo.java +++ b/source/java/org/alfresco/repo/importer/system/PatchInfo.java @@ -1,23 +1,23 @@ -package org.alfresco.repo.importer.system; - -import java.util.Date; - -/** - * Data holder of patch information that's to be exported and imported - * - * @author davidc - */ -public class PatchInfo -{ - public String id = null; - public String description = null; - public Integer fixesFromSchema = null; - public Integer fixesToSchema = null; - public Integer targetSchema = null; - public Integer appliedToSchema = null; - public String appliedToServer = null; - public Date appliedOnDate = null; - public Boolean wasExecuted = null; - public Boolean succeeded = null; - public String report = null; -} +package org.alfresco.repo.importer.system; + +import java.util.Date; + +/** + * Data holder of patch information that's to be exported and imported + * + * @author davidc + */ +public class PatchInfo +{ + public String id = null; + public String description = null; + public Integer fixesFromSchema = null; + public Integer fixesToSchema = null; + public Integer targetSchema = null; + public Integer appliedToSchema = null; + public String appliedToServer = null; + public Date appliedOnDate = null; + public Boolean wasExecuted = null; + public Boolean succeeded = null; + public String report = null; +} diff --git a/source/java/org/alfresco/repo/importer/system/SystemExporterImporter.java b/source/java/org/alfresco/repo/importer/system/SystemExporterImporter.java index aecb8596fc..6879c71c45 100644 --- a/source/java/org/alfresco/repo/importer/system/SystemExporterImporter.java +++ b/source/java/org/alfresco/repo/importer/system/SystemExporterImporter.java @@ -1,92 +1,92 @@ -package org.alfresco.repo.importer.system; - -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; - -import org.alfresco.repo.admin.patch.AppliedPatch; -import org.alfresco.repo.domain.patch.AppliedPatchDAO; -import org.alfresco.service.cmr.repository.NodeService; - - -/** - * Exporter and Importer of Repository System Information - * - * @author davidc - */ -public class SystemExporterImporter -{ - // dependencies - private NodeService nodeService; - private AppliedPatchDAO appliedPatchDAO; - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setAppliedPatchDAO(AppliedPatchDAO appliedPatchDAO) - { - this.appliedPatchDAO = appliedPatchDAO; - } - - /** - * Export Repository System Information - * - * @param exportStream output stream to export to - */ - public void exportSystem(OutputStream exportStream) - { - SystemInfo systemInfo = new SystemInfo(); - - // capture applied patches - List patches = appliedPatchDAO.getAppliedPatches(); - for (AppliedPatch patch : patches) - { - PatchInfo patchInfo = new PatchInfo(); - patchInfo.appliedOnDate = patch.getAppliedOnDate(); - patchInfo.appliedToSchema = patch.getAppliedToSchema(); - patchInfo.appliedToServer = patch.getAppliedToServer(); - patchInfo.description = patch.getDescription(); - patchInfo.fixesFromSchema = patch.getFixesFromSchema(); - patchInfo.fixesToSchema = patch.getFixesToSchema(); - patchInfo.id = patch.getId(); - patchInfo.report = patch.getReport(); - patchInfo.succeeded = patch.getSucceeded(); - patchInfo.targetSchema = patch.getTargetSchema(); - patchInfo.wasExecuted = patch.getWasExecuted(); - systemInfo.patches.add(patchInfo); - } - - systemInfo.toXML(exportStream); - } - - - /** - * Import Repository System Information - * - * @param importStream input stream to import from - */ - public void importSystem(InputStream importStream) - { - SystemInfo systemInfo = SystemInfo.createSystemInfo(importStream); - - // apply patch info - for (PatchInfo patchInfo : systemInfo.patches) - { - AppliedPatch patch = new AppliedPatch(); - patch.setId(patchInfo.id); - patch.setAppliedOnDate(patchInfo.appliedOnDate); - patch.setAppliedToSchema(patchInfo.appliedToSchema); - patch.setAppliedToServer(patchInfo.appliedToServer); - patch.setDescription(patchInfo.description); - patch.setFixesFromSchema(patchInfo.fixesFromSchema); - patch.setFixesToSchema(patchInfo.fixesToSchema); - patch.setReport(patchInfo.report); - patch.setSucceeded(patchInfo.succeeded); - patch.setTargetSchema(patchInfo.targetSchema); - patch.setWasExecuted(patchInfo.wasExecuted); - appliedPatchDAO.createAppliedPatch(patch); - } - } -} +package org.alfresco.repo.importer.system; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import org.alfresco.repo.admin.patch.AppliedPatch; +import org.alfresco.repo.domain.patch.AppliedPatchDAO; +import org.alfresco.service.cmr.repository.NodeService; + + +/** + * Exporter and Importer of Repository System Information + * + * @author davidc + */ +public class SystemExporterImporter +{ + // dependencies + private NodeService nodeService; + private AppliedPatchDAO appliedPatchDAO; + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setAppliedPatchDAO(AppliedPatchDAO appliedPatchDAO) + { + this.appliedPatchDAO = appliedPatchDAO; + } + + /** + * Export Repository System Information + * + * @param exportStream output stream to export to + */ + public void exportSystem(OutputStream exportStream) + { + SystemInfo systemInfo = new SystemInfo(); + + // capture applied patches + List patches = appliedPatchDAO.getAppliedPatches(); + for (AppliedPatch patch : patches) + { + PatchInfo patchInfo = new PatchInfo(); + patchInfo.appliedOnDate = patch.getAppliedOnDate(); + patchInfo.appliedToSchema = patch.getAppliedToSchema(); + patchInfo.appliedToServer = patch.getAppliedToServer(); + patchInfo.description = patch.getDescription(); + patchInfo.fixesFromSchema = patch.getFixesFromSchema(); + patchInfo.fixesToSchema = patch.getFixesToSchema(); + patchInfo.id = patch.getId(); + patchInfo.report = patch.getReport(); + patchInfo.succeeded = patch.getSucceeded(); + patchInfo.targetSchema = patch.getTargetSchema(); + patchInfo.wasExecuted = patch.getWasExecuted(); + systemInfo.patches.add(patchInfo); + } + + systemInfo.toXML(exportStream); + } + + + /** + * Import Repository System Information + * + * @param importStream input stream to import from + */ + public void importSystem(InputStream importStream) + { + SystemInfo systemInfo = SystemInfo.createSystemInfo(importStream); + + // apply patch info + for (PatchInfo patchInfo : systemInfo.patches) + { + AppliedPatch patch = new AppliedPatch(); + patch.setId(patchInfo.id); + patch.setAppliedOnDate(patchInfo.appliedOnDate); + patch.setAppliedToSchema(patchInfo.appliedToSchema); + patch.setAppliedToServer(patchInfo.appliedToServer); + patch.setDescription(patchInfo.description); + patch.setFixesFromSchema(patchInfo.fixesFromSchema); + patch.setFixesToSchema(patchInfo.fixesToSchema); + patch.setReport(patchInfo.report); + patch.setSucceeded(patchInfo.succeeded); + patch.setTargetSchema(patchInfo.targetSchema); + patch.setWasExecuted(patchInfo.wasExecuted); + appliedPatchDAO.createAppliedPatch(patch); + } + } +} diff --git a/source/java/org/alfresco/repo/importer/system/SystemInfo.java b/source/java/org/alfresco/repo/importer/system/SystemInfo.java index db98076d75..74033e26e9 100644 --- a/source/java/org/alfresco/repo/importer/system/SystemInfo.java +++ b/source/java/org/alfresco/repo/importer/system/SystemInfo.java @@ -1,65 +1,65 @@ -package org.alfresco.repo.importer.system; - -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -import org.alfresco.service.cmr.dictionary.DictionaryException; -import org.jibx.runtime.BindingDirectory; -import org.jibx.runtime.IBindingFactory; -import org.jibx.runtime.IMarshallingContext; -import org.jibx.runtime.IUnmarshallingContext; -import org.jibx.runtime.JiBXException; - - -/** - * Root data holder of Repository system information to be exported and imported - * - * @author davidc - */ -public class SystemInfo -{ - public List patches = new ArrayList(); - - /** - * Create System Info from XML representation - * - * @param xml xml representation of system info - * @return the System Info - */ - public static SystemInfo createSystemInfo(InputStream xml) - { - try - { - IBindingFactory factory = BindingDirectory.getFactory(SystemInfo.class); - IUnmarshallingContext context = factory.createUnmarshallingContext(); - Object obj = context.unmarshalDocument(xml, null); - return (SystemInfo)obj; - } - catch(JiBXException e) - { - throw new DictionaryException("Failed to parse System Info", e); - } - } - - /** - * Create XML representation of System Info - * - * @param xml xml representation of system info - */ - public void toXML(OutputStream xml) - { - try - { - IBindingFactory factory = BindingDirectory.getFactory(SystemInfo.class); - IMarshallingContext context = factory.createMarshallingContext(); - context.setIndent(4); - context.marshalDocument(this, "UTF-8", null, xml); - } - catch(JiBXException e) - { - throw new DictionaryException("Failed to create System Info", e); - } - } -} +package org.alfresco.repo.importer.system; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.jibx.runtime.BindingDirectory; +import org.jibx.runtime.IBindingFactory; +import org.jibx.runtime.IMarshallingContext; +import org.jibx.runtime.IUnmarshallingContext; +import org.jibx.runtime.JiBXException; + + +/** + * Root data holder of Repository system information to be exported and imported + * + * @author davidc + */ +public class SystemInfo +{ + public List patches = new ArrayList(); + + /** + * Create System Info from XML representation + * + * @param xml xml representation of system info + * @return the System Info + */ + public static SystemInfo createSystemInfo(InputStream xml) + { + try + { + IBindingFactory factory = BindingDirectory.getFactory(SystemInfo.class); + IUnmarshallingContext context = factory.createUnmarshallingContext(); + Object obj = context.unmarshalDocument(xml, null); + return (SystemInfo)obj; + } + catch(JiBXException e) + { + throw new DictionaryException("Failed to parse System Info", e); + } + } + + /** + * Create XML representation of System Info + * + * @param xml xml representation of system info + */ + public void toXML(OutputStream xml) + { + try + { + IBindingFactory factory = BindingDirectory.getFactory(SystemInfo.class); + IMarshallingContext context = factory.createMarshallingContext(); + context.setIndent(4); + context.marshalDocument(this, "UTF-8", null, xml); + } + catch(JiBXException e) + { + throw new DictionaryException("Failed to create System Info", e); + } + } +} diff --git a/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java b/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java index c1bc378ed9..f1bdd6983a 100644 --- a/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java +++ b/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java @@ -1,176 +1,176 @@ -package org.alfresco.repo.importer.system; - -import java.io.InputStream; -import java.util.List; - -import javax.transaction.UserTransaction; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.view.ImporterException; -import org.alfresco.service.transaction.TransactionService; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; -import org.springframework.context.ApplicationEvent; - - -/** - * Repository System Information bootstrap - * - * @author davidc - */ -public class SystemInfoBootstrap extends AbstractLifecycleBean -{ - // dependencies - private TransactionService transactionService; - private NodeService nodeService; - private AuthenticationContext authenticationContext; - private SystemExporterImporter systemImporter; - - private List mustNotExistStoreUrls = null; - private String bootstrapView = null; - - - /** - * Sets the Transaction Service - * - * @param transactionService the transaction service - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - /** - * Sets the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the authentication component - * - * @param authenticationContext AuthenticationContext - */ - public void setAuthenticationContext(AuthenticationContext authenticationContext) - { - this.authenticationContext = authenticationContext; - } - - /** - * Set the System Importer - * - * @param systemImporter SystemExporterImporter - */ - public void setSystemImporter(SystemExporterImporter systemImporter) - { - this.systemImporter = systemImporter; - } - - /** - * If any of the store urls exist, the bootstrap does not take place - * - * @param storeUrls the list of store urls to check - */ - public void setMustNotExistStoreUrls(List storeUrls) - { - this.mustNotExistStoreUrls = storeUrls; - } - - /** - * Set the bootstrap view containing the system information - * - * @param bootstrapView String - */ - public void setBootstrapView(String bootstrapView) - { - this.bootstrapView = bootstrapView; - } - - /** - * Bootstrap - */ - public void bootstrap() - { - UserTransaction userTransaction = transactionService.getUserTransaction(); - authenticationContext.setSystemUserAsCurrentUser(); - - try - { - userTransaction.begin(); - - // check the repository exists, create if it doesn't - if (performBootstrap()) - { - InputStream viewStream = getClass().getClassLoader().getResourceAsStream(bootstrapView); - if (viewStream == null) - { - throw new ImporterException("Could not find system info file " + bootstrapView); - } - try - { - systemImporter.importSystem(viewStream); - } - finally - { - viewStream.close(); - } - } - userTransaction.commit(); - } - catch(Throwable e) - { - // rollback the transaction - try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} - try {authenticationContext.clearCurrentSecurityContext(); } catch (Exception ex) {} - throw new AlfrescoRuntimeException("System Info Bootstrap failed", e); - } - finally - { - authenticationContext.clearCurrentSecurityContext(); - } - } - - /** - * Determine if bootstrap should take place - * - * @return true => yes, it should - */ - private boolean performBootstrap() - { - if (bootstrapView == null || bootstrapView.length() == 0) - { - return false; - } - if (mustNotExistStoreUrls != null) - { - for (String storeUrl : mustNotExistStoreUrls) - { - StoreRef storeRef = new StoreRef(storeUrl); - if (nodeService.exists(storeRef)) - { - return false; - } - } - } - return true; - } - - @Override - protected void onBootstrap(ApplicationEvent event) - { - bootstrap(); - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - // NOOP - } - -} +package org.alfresco.repo.importer.system; + +import java.io.InputStream; +import java.util.List; + +import javax.transaction.UserTransaction; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.view.ImporterException; +import org.alfresco.service.transaction.TransactionService; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.context.ApplicationEvent; + + +/** + * Repository System Information bootstrap + * + * @author davidc + */ +public class SystemInfoBootstrap extends AbstractLifecycleBean +{ + // dependencies + private TransactionService transactionService; + private NodeService nodeService; + private AuthenticationContext authenticationContext; + private SystemExporterImporter systemImporter; + + private List mustNotExistStoreUrls = null; + private String bootstrapView = null; + + + /** + * Sets the Transaction Service + * + * @param transactionService the transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Sets the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the authentication component + * + * @param authenticationContext AuthenticationContext + */ + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + /** + * Set the System Importer + * + * @param systemImporter SystemExporterImporter + */ + public void setSystemImporter(SystemExporterImporter systemImporter) + { + this.systemImporter = systemImporter; + } + + /** + * If any of the store urls exist, the bootstrap does not take place + * + * @param storeUrls the list of store urls to check + */ + public void setMustNotExistStoreUrls(List storeUrls) + { + this.mustNotExistStoreUrls = storeUrls; + } + + /** + * Set the bootstrap view containing the system information + * + * @param bootstrapView String + */ + public void setBootstrapView(String bootstrapView) + { + this.bootstrapView = bootstrapView; + } + + /** + * Bootstrap + */ + public void bootstrap() + { + UserTransaction userTransaction = transactionService.getUserTransaction(); + authenticationContext.setSystemUserAsCurrentUser(); + + try + { + userTransaction.begin(); + + // check the repository exists, create if it doesn't + if (performBootstrap()) + { + InputStream viewStream = getClass().getClassLoader().getResourceAsStream(bootstrapView); + if (viewStream == null) + { + throw new ImporterException("Could not find system info file " + bootstrapView); + } + try + { + systemImporter.importSystem(viewStream); + } + finally + { + viewStream.close(); + } + } + userTransaction.commit(); + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + try {authenticationContext.clearCurrentSecurityContext(); } catch (Exception ex) {} + throw new AlfrescoRuntimeException("System Info Bootstrap failed", e); + } + finally + { + authenticationContext.clearCurrentSecurityContext(); + } + } + + /** + * Determine if bootstrap should take place + * + * @return true => yes, it should + */ + private boolean performBootstrap() + { + if (bootstrapView == null || bootstrapView.length() == 0) + { + return false; + } + if (mustNotExistStoreUrls != null) + { + for (String storeUrl : mustNotExistStoreUrls) + { + StoreRef storeRef = new StoreRef(storeUrl); + if (nodeService.exists(storeRef)) + { + return false; + } + } + } + return true; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + bootstrap(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } + +} diff --git a/source/java/org/alfresco/repo/invitation/InvitationImpl.java b/source/java/org/alfresco/repo/invitation/InvitationImpl.java index 0f9be8992c..160e1769b9 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationImpl.java @@ -1,102 +1,102 @@ -package org.alfresco.repo.invitation; - -import java.io.Serializable; -import java.util.Date; -import java.util.Map; - -import org.alfresco.service.cmr.invitation.Invitation; -import org.alfresco.service.cmr.invitation.Invitation.InvitationType; -import org.alfresco.service.cmr.invitation.Invitation.ResourceType; - -/* package scope */ abstract class InvitationImpl -{ - public static final String ID_KEY = "id"; - public static final String INVITEE_KEY = "invitee"; - public static final String RESOURCE_NAME_KEY = "resourceName"; - public static final String RESOURCE_TYPE_KEY = "resourceType"; - public static final String ROLE_KEY = "role"; - public static final String CREATED_AT = "createdAt"; - public static final String MODIFIED_AT = "modifiedAt"; - - /** - * Unique reference for this invitation - */ - private final String inviteId; - - /** - * Which resource is this invitation for ? - */ - private final String resourceName; - - /** - * What sort of invitation is this invitation for e.g. WEB_SITE or WEB_PROJECT - */ - private final Invitation.ResourceType resourceType; - - /** - * What role is the invitation for. - */ - private final String roleName; - - /** - * Who is this invitation for - */ - private final String inviteeUserName; - - private final Date createdAt; - - private final Date modifiedAt; - - public InvitationImpl(Map props) - { - this.inviteId = (String)props.get(ID_KEY); - this.inviteeUserName = (String)props.get(INVITEE_KEY); - this.resourceName = (String)props.get(RESOURCE_NAME_KEY); - this.roleName = (String)props.get(ROLE_KEY); - String type = (String)props.get(RESOURCE_TYPE_KEY); - this.resourceType = type==null ? ResourceType.WEB_SITE : ResourceType.valueOf(type); - this.createdAt = (Date)props.get(CREATED_AT); - this.modifiedAt = (Date)props.get(MODIFIED_AT); - } - - /** - * What sort of resource is it - * @return the resource type - */ - public ResourceType getResourceType() - { - return resourceType; - } - - public Date getCreatedAt() - { - return createdAt; - } - - public Date getModifiedAt() - { - return modifiedAt; - } - - public String getInviteId() - { - return inviteId; - } - - public String getResourceName() - { - return resourceName; - } - - public String getRoleName() - { - return roleName; - } - - public String getInviteeUserName() - { - return inviteeUserName; - } - - public abstract InvitationType getInvitationType(); -} +package org.alfresco.repo.invitation; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +import org.alfresco.service.cmr.invitation.Invitation; +import org.alfresco.service.cmr.invitation.Invitation.InvitationType; +import org.alfresco.service.cmr.invitation.Invitation.ResourceType; + +/* package scope */ abstract class InvitationImpl +{ + public static final String ID_KEY = "id"; + public static final String INVITEE_KEY = "invitee"; + public static final String RESOURCE_NAME_KEY = "resourceName"; + public static final String RESOURCE_TYPE_KEY = "resourceType"; + public static final String ROLE_KEY = "role"; + public static final String CREATED_AT = "createdAt"; + public static final String MODIFIED_AT = "modifiedAt"; + + /** + * Unique reference for this invitation + */ + private final String inviteId; + + /** + * Which resource is this invitation for ? + */ + private final String resourceName; + + /** + * What sort of invitation is this invitation for e.g. WEB_SITE or WEB_PROJECT + */ + private final Invitation.ResourceType resourceType; + + /** + * What role is the invitation for. + */ + private final String roleName; + + /** + * Who is this invitation for + */ + private final String inviteeUserName; + + private final Date createdAt; + + private final Date modifiedAt; + + public InvitationImpl(Map props) + { + this.inviteId = (String)props.get(ID_KEY); + this.inviteeUserName = (String)props.get(INVITEE_KEY); + this.resourceName = (String)props.get(RESOURCE_NAME_KEY); + this.roleName = (String)props.get(ROLE_KEY); + String type = (String)props.get(RESOURCE_TYPE_KEY); + this.resourceType = type==null ? ResourceType.WEB_SITE : ResourceType.valueOf(type); + this.createdAt = (Date)props.get(CREATED_AT); + this.modifiedAt = (Date)props.get(MODIFIED_AT); + } + + /** + * What sort of resource is it + * @return the resource type + */ + public ResourceType getResourceType() + { + return resourceType; + } + + public Date getCreatedAt() + { + return createdAt; + } + + public Date getModifiedAt() + { + return modifiedAt; + } + + public String getInviteId() + { + return inviteId; + } + + public String getResourceName() + { + return resourceName; + } + + public String getRoleName() + { + return roleName; + } + + public String getInviteeUserName() + { + return inviteeUserName; + } + + public abstract InvitationType getInvitationType(); +} diff --git a/source/java/org/alfresco/repo/invitation/InvitationProcessDescription.java b/source/java/org/alfresco/repo/invitation/InvitationProcessDescription.java index b5067dc403..b25316c59a 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationProcessDescription.java +++ b/source/java/org/alfresco/repo/invitation/InvitationProcessDescription.java @@ -1,6 +1,6 @@ -package org.alfresco.repo.invitation; - -public interface InvitationProcessDescription -{ - +package org.alfresco.repo.invitation; + +public interface InvitationProcessDescription +{ + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/invitation/InvitationProcessDescriptionImpl.java b/source/java/org/alfresco/repo/invitation/InvitationProcessDescriptionImpl.java index dda92b5505..74879d882e 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationProcessDescriptionImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationProcessDescriptionImpl.java @@ -1,10 +1,10 @@ - -package org.alfresco.repo.invitation; - -/** - * Description about which invitation processes are available - */ -public class InvitationProcessDescriptionImpl implements InvitationProcessDescription -{ - -} + +package org.alfresco.repo.invitation; + +/** + * Description about which invitation processes are available + */ +public class InvitationProcessDescriptionImpl implements InvitationProcessDescription +{ + +} diff --git a/source/java/org/alfresco/repo/invitation/InvitationSearchCriteriaImpl.java b/source/java/org/alfresco/repo/invitation/InvitationSearchCriteriaImpl.java index 379fa985dd..3be9bc53e1 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationSearchCriteriaImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationSearchCriteriaImpl.java @@ -1,63 +1,63 @@ -package org.alfresco.repo.invitation; - -import org.alfresco.service.cmr.invitation.InvitationSearchCriteria; -import org.alfresco.service.cmr.invitation.Invitation.ResourceType; - -public class InvitationSearchCriteriaImpl implements InvitationSearchCriteria -{ - private String invitee; - private String inviter; - private String resourceName; - private ResourceType resourceType; - private InvitationSearchCriteria.InvitationType invitationType = InvitationSearchCriteria.InvitationType.ALL; - - public void setInvitee(String invitee) - { - this.invitee = invitee; - } - - public String getInvitee() - { - return invitee; - } - - public void setInviter(String inviter) - { - this.inviter = inviter; - } - - public String getInviter() - { - return inviter; - } - - public void setResourceName(String resourceName) - { - this.resourceName = resourceName; - } - - public String getResourceName() - { - return resourceName; - } - - public void setResourceType(ResourceType resourceType) - { - this.resourceType = resourceType; - } - - public ResourceType getResourceType() - { - return resourceType; - } - - public InvitationType getInvitationType() - { - return invitationType; - } - - public void setInvitationType(InvitationType invitationType) - { - this.invitationType = invitationType; - } -} +package org.alfresco.repo.invitation; + +import org.alfresco.service.cmr.invitation.InvitationSearchCriteria; +import org.alfresco.service.cmr.invitation.Invitation.ResourceType; + +public class InvitationSearchCriteriaImpl implements InvitationSearchCriteria +{ + private String invitee; + private String inviter; + private String resourceName; + private ResourceType resourceType; + private InvitationSearchCriteria.InvitationType invitationType = InvitationSearchCriteria.InvitationType.ALL; + + public void setInvitee(String invitee) + { + this.invitee = invitee; + } + + public String getInvitee() + { + return invitee; + } + + public void setInviter(String inviter) + { + this.inviter = inviter; + } + + public String getInviter() + { + return inviter; + } + + public void setResourceName(String resourceName) + { + this.resourceName = resourceName; + } + + public String getResourceName() + { + return resourceName; + } + + public void setResourceType(ResourceType resourceType) + { + this.resourceType = resourceType; + } + + public ResourceType getResourceType() + { + return resourceType; + } + + public InvitationType getInvitationType() + { + return invitationType; + } + + public void setInvitationType(InvitationType invitationType) + { + this.invitationType = invitationType; + } +} diff --git a/source/java/org/alfresco/repo/invitation/ModeratedActionApprove.java b/source/java/org/alfresco/repo/invitation/ModeratedActionApprove.java index 26a863b0a1..0c9b0e7ee3 100644 --- a/source/java/org/alfresco/repo/invitation/ModeratedActionApprove.java +++ b/source/java/org/alfresco/repo/invitation/ModeratedActionApprove.java @@ -1,27 +1,27 @@ -package org.alfresco.repo.invitation; - - -import java.util.Map; - -import org.alfresco.repo.invitation.site.AbstractInvitationAction; -import org.jbpm.graph.exe.ExecutionContext; - -public class ModeratedActionApprove extends AbstractInvitationAction -{ - private static final long serialVersionUID = 4377660284993206875L; - - /** - * {@inheritDoc} - **/ - @SuppressWarnings("unchecked") - public void execute(ExecutionContext executionContext) throws Exception - { - Map variables = executionContext.getContextInstance().getVariables(); - String siteName = (String) variables.get(WorkflowModelModeratedInvitation.wfVarResourceName); - String invitee = (String) variables.get(WorkflowModelModeratedInvitation.wfVarInviteeUserName); - String role = (String) variables.get(WorkflowModelModeratedInvitation.wfVarInviteeRole); - String reviewer = (String) variables.get(WorkflowModelModeratedInvitation.wfVarReviewer); - - invitationService.approveModeratedInvitation(siteName, invitee, role, reviewer); - } +package org.alfresco.repo.invitation; + + +import java.util.Map; + +import org.alfresco.repo.invitation.site.AbstractInvitationAction; +import org.jbpm.graph.exe.ExecutionContext; + +public class ModeratedActionApprove extends AbstractInvitationAction +{ + private static final long serialVersionUID = 4377660284993206875L; + + /** + * {@inheritDoc} + **/ + @SuppressWarnings("unchecked") + public void execute(ExecutionContext executionContext) throws Exception + { + Map variables = executionContext.getContextInstance().getVariables(); + String siteName = (String) variables.get(WorkflowModelModeratedInvitation.wfVarResourceName); + String invitee = (String) variables.get(WorkflowModelModeratedInvitation.wfVarInviteeUserName); + String role = (String) variables.get(WorkflowModelModeratedInvitation.wfVarInviteeRole); + String reviewer = (String) variables.get(WorkflowModelModeratedInvitation.wfVarReviewer); + + invitationService.approveModeratedInvitation(siteName, invitee, role, reviewer); + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/invitation/ModeratedActionReject.java b/source/java/org/alfresco/repo/invitation/ModeratedActionReject.java index e8ac078fd8..ce8fbdba77 100644 --- a/source/java/org/alfresco/repo/invitation/ModeratedActionReject.java +++ b/source/java/org/alfresco/repo/invitation/ModeratedActionReject.java @@ -1,38 +1,38 @@ -package org.alfresco.repo.invitation; - - -import java.util.Map; - -import org.alfresco.repo.invitation.activiti.RejectModeratedInviteDelegate; -import org.alfresco.repo.invitation.site.AbstractInvitationAction; -import org.jbpm.graph.exe.ExecutionContext; - -/** - * JBPM Action fired when a moderated invitation is rejected. - * Note - uses a classpath template, rather than a data dictionary template, - * so behaves slightly differently to many other mail actions, and can't - * currently be localised easily. - * - * Same behaviour as {@link RejectModeratedInviteDelegate} - */ -public class ModeratedActionReject extends AbstractInvitationAction -{ - private static final long serialVersionUID = 4377660284993206875L; - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - public void execute(final ExecutionContext executionContext) throws Exception - { - Map vars = executionContext.getContextInstance().getVariables(); - String siteName = (String) vars.get(WorkflowModelModeratedInvitation.wfVarResourceName); - String invitee = (String) vars.get(WorkflowModelModeratedInvitation.wfVarInviteeUserName); - String role = (String) vars.get(WorkflowModelModeratedInvitation.wfVarInviteeRole); - String reviewer = (String) vars.get(WorkflowModelModeratedInvitation.wfVarReviewer); - String resourceType = (String) vars.get(WorkflowModelModeratedInvitation.wfVarResourceType); - String reviewComments = (String) vars.get(WorkflowModelModeratedInvitation.wfVarReviewComments); - - invitationService.rejectModeratedInvitation(siteName, invitee, role, reviewer, resourceType, reviewComments); - } +package org.alfresco.repo.invitation; + + +import java.util.Map; + +import org.alfresco.repo.invitation.activiti.RejectModeratedInviteDelegate; +import org.alfresco.repo.invitation.site.AbstractInvitationAction; +import org.jbpm.graph.exe.ExecutionContext; + +/** + * JBPM Action fired when a moderated invitation is rejected. + * Note - uses a classpath template, rather than a data dictionary template, + * so behaves slightly differently to many other mail actions, and can't + * currently be localised easily. + * + * Same behaviour as {@link RejectModeratedInviteDelegate} + */ +public class ModeratedActionReject extends AbstractInvitationAction +{ + private static final long serialVersionUID = 4377660284993206875L; + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public void execute(final ExecutionContext executionContext) throws Exception + { + Map vars = executionContext.getContextInstance().getVariables(); + String siteName = (String) vars.get(WorkflowModelModeratedInvitation.wfVarResourceName); + String invitee = (String) vars.get(WorkflowModelModeratedInvitation.wfVarInviteeUserName); + String role = (String) vars.get(WorkflowModelModeratedInvitation.wfVarInviteeRole); + String reviewer = (String) vars.get(WorkflowModelModeratedInvitation.wfVarReviewer); + String resourceType = (String) vars.get(WorkflowModelModeratedInvitation.wfVarResourceType); + String reviewComments = (String) vars.get(WorkflowModelModeratedInvitation.wfVarReviewComments); + + invitationService.rejectModeratedInvitation(siteName, invitee, role, reviewer, resourceType, reviewComments); + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/invitation/ModeratedInvitationImpl.java b/source/java/org/alfresco/repo/invitation/ModeratedInvitationImpl.java index 9c86e57ab8..148300778b 100644 --- a/source/java/org/alfresco/repo/invitation/ModeratedInvitationImpl.java +++ b/source/java/org/alfresco/repo/invitation/ModeratedInvitationImpl.java @@ -1,68 +1,68 @@ -package org.alfresco.repo.invitation; - -import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_INVITEE_COMMENTS; -import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_INVITEE_ROLE; -import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_INVITEE_USER_NAME; -import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_MODIFIED_AT; -import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_NAME; -import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_TYPE; - -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.invitation.ModeratedInvitation; -import org.alfresco.service.namespace.QName; - -/** - * InvitationRequestImpl is a basic InvitationRequest that is processed by the - * InvitationService - */ -/*package scope */ class ModeratedInvitationImpl extends InvitationImpl implements ModeratedInvitation, Serializable -{ - private static final long serialVersionUID = -5557544865169876451L; - - private final String inviteeComments; - - public static ModeratedInvitation getModeratedInvitationImpl(ModeratedInvitation moderatedInvitation) - { - ModeratedInvitation copy = null; - String inviteId = moderatedInvitation.getInviteId(); - Map props = new HashMap(); - props.put(WF_PROP_INVITEE_COMMENTS, moderatedInvitation.getInviteeComments()); - copy = new ModeratedInvitationImpl(inviteId, props); - return copy; - } - - public ModeratedInvitationImpl(String inviteId, Map props) - { - super(getConstructorProps(inviteId, props)); - inviteeComments = (String)props.get(WF_PROP_INVITEE_COMMENTS); - } - - private static Map getConstructorProps(String inviteId, Map props) - { - Map parentProps = new HashMap(); - parentProps.put(ID_KEY, inviteId); - parentProps.put(INVITEE_KEY, (String) props.get(WF_PROP_INVITEE_USER_NAME)); - parentProps.put(ROLE_KEY,(String)props.get(WF_PROP_INVITEE_ROLE)); - parentProps.put(RESOURCE_NAME_KEY,(String)props.get(WF_PROP_RESOURCE_NAME)); - parentProps.put(RESOURCE_TYPE_KEY,(String)props.get(WF_PROP_RESOURCE_TYPE)); - parentProps.put(CREATED_AT,(Date)props.get(ContentModel.PROP_CREATED)); - parentProps.put(MODIFIED_AT,(Date)props.get(WF_PROP_MODIFIED_AT)); - return parentProps; - } - - public String getInviteeComments() - { - return inviteeComments; - } - - @Override - public InvitationType getInvitationType() - { - return InvitationType.MODERATED; - } -} +package org.alfresco.repo.invitation; + +import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_INVITEE_COMMENTS; +import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_INVITEE_ROLE; +import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_INVITEE_USER_NAME; +import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_MODIFIED_AT; +import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_NAME; +import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_TYPE; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.invitation.ModeratedInvitation; +import org.alfresco.service.namespace.QName; + +/** + * InvitationRequestImpl is a basic InvitationRequest that is processed by the + * InvitationService + */ +/*package scope */ class ModeratedInvitationImpl extends InvitationImpl implements ModeratedInvitation, Serializable +{ + private static final long serialVersionUID = -5557544865169876451L; + + private final String inviteeComments; + + public static ModeratedInvitation getModeratedInvitationImpl(ModeratedInvitation moderatedInvitation) + { + ModeratedInvitation copy = null; + String inviteId = moderatedInvitation.getInviteId(); + Map props = new HashMap(); + props.put(WF_PROP_INVITEE_COMMENTS, moderatedInvitation.getInviteeComments()); + copy = new ModeratedInvitationImpl(inviteId, props); + return copy; + } + + public ModeratedInvitationImpl(String inviteId, Map props) + { + super(getConstructorProps(inviteId, props)); + inviteeComments = (String)props.get(WF_PROP_INVITEE_COMMENTS); + } + + private static Map getConstructorProps(String inviteId, Map props) + { + Map parentProps = new HashMap(); + parentProps.put(ID_KEY, inviteId); + parentProps.put(INVITEE_KEY, (String) props.get(WF_PROP_INVITEE_USER_NAME)); + parentProps.put(ROLE_KEY,(String)props.get(WF_PROP_INVITEE_ROLE)); + parentProps.put(RESOURCE_NAME_KEY,(String)props.get(WF_PROP_RESOURCE_NAME)); + parentProps.put(RESOURCE_TYPE_KEY,(String)props.get(WF_PROP_RESOURCE_TYPE)); + parentProps.put(CREATED_AT,(Date)props.get(ContentModel.PROP_CREATED)); + parentProps.put(MODIFIED_AT,(Date)props.get(WF_PROP_MODIFIED_AT)); + return parentProps; + } + + public String getInviteeComments() + { + return inviteeComments; + } + + @Override + public InvitationType getInvitationType() + { + return InvitationType.MODERATED; + } +} diff --git a/source/java/org/alfresco/repo/invitation/ModeratedInvitationProcess.java b/source/java/org/alfresco/repo/invitation/ModeratedInvitationProcess.java index 22d6425ca9..bd41f56e89 100644 --- a/source/java/org/alfresco/repo/invitation/ModeratedInvitationProcess.java +++ b/source/java/org/alfresco/repo/invitation/ModeratedInvitationProcess.java @@ -1,38 +1,38 @@ -package org.alfresco.repo.invitation; - -import org.alfresco.service.cmr.invitation.Invitation; - -/** - * The Moderated Invitation Process has a moderator who approves or rejects - * invitations raised by the invitee themselves. - * - * Upon approval the invitee will be given the requested role for the - * requested resource. - */ - -public interface ModeratedInvitationProcess extends InvitationProcess -{ - /** - * Invitee kicks off process - * @param request Invitation - * @param reason String - */ - public Invitation invite(Invitation request, String reason); - - /** - * Moderator approves this request - * @param request the request to approve. - */ - public void approve(Invitation request, String reason); - - /** - * Moderator rejects this request - * @param request the request to reject - */ - public void reject(Invitation request, String reason); - - /** - * Invitee cancels this request - */ - public void cancel (Invitation request, String reason); -} +package org.alfresco.repo.invitation; + +import org.alfresco.service.cmr.invitation.Invitation; + +/** + * The Moderated Invitation Process has a moderator who approves or rejects + * invitations raised by the invitee themselves. + * + * Upon approval the invitee will be given the requested role for the + * requested resource. + */ + +public interface ModeratedInvitationProcess extends InvitationProcess +{ + /** + * Invitee kicks off process + * @param request Invitation + * @param reason String + */ + public Invitation invite(Invitation request, String reason); + + /** + * Moderator approves this request + * @param request the request to approve. + */ + public void approve(Invitation request, String reason); + + /** + * Moderator rejects this request + * @param request the request to reject + */ + public void reject(Invitation request, String reason); + + /** + * Invitee cancels this request + */ + public void cancel (Invitation request, String reason); +} diff --git a/source/java/org/alfresco/repo/invitation/NominatedInvitationImpl.java b/source/java/org/alfresco/repo/invitation/NominatedInvitationImpl.java index ca1aa59915..2ceae95c5f 100644 --- a/source/java/org/alfresco/repo/invitation/NominatedInvitationImpl.java +++ b/source/java/org/alfresco/repo/invitation/NominatedInvitationImpl.java @@ -1,135 +1,135 @@ -package org.alfresco.repo.invitation; - -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_ACCEPT_URL; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_EMAIL; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_FIRSTNAME; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_LASTNAME; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_ROLE; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_USER_NAME; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITER_USER_NAME; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITE_TICKET; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_REJECT_URL; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_DESCRIPTION; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_NAME; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_TITLE; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_TYPE; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_SERVER_PATH; - -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.service.cmr.invitation.NominatedInvitation; -import org.alfresco.service.namespace.QName; - -/** - * NominatedInvitationImpl is a basic Nominated Invitation Request that - * is processed by the InvitationService. - * - * @see org.alfresco.service.cmr.invitation.NominatedInvitation - */ -/*package scope */ class NominatedInvitationImpl extends InvitationImpl implements NominatedInvitation, Serializable -{ - private static final long serialVersionUID = -8800842866845149466L; - - private final String inviteeFirstName; - private final String inviteeLastName; - private final String inviteeEmail; - private final String inviterUserName; - private final String resourceDescription; - private final String resourceTitle; - private final String serverPath; - private final String acceptUrl; - private final String rejectUrl; - private final Date sentInviteDate; - private final String ticket; - - public NominatedInvitationImpl(String inviteId, Date inviteDate, Map props) - { - super(getConstructorProps(inviteId, props)); - inviteeFirstName = (String)props.get(WF_PROP_INVITEE_FIRSTNAME); - inviteeLastName = (String)props.get(WF_PROP_INVITEE_LASTNAME); - inviteeEmail = (String)props.get(WF_PROP_INVITEE_EMAIL); - inviterUserName = (String)props.get(WF_PROP_INVITER_USER_NAME); - resourceTitle = (String)props.get(WF_PROP_RESOURCE_TITLE); - resourceDescription = (String)props.get(WF_PROP_RESOURCE_DESCRIPTION); - serverPath = (String)props.get(WF_PROP_SERVER_PATH); - acceptUrl = (String)props.get(WF_PROP_ACCEPT_URL); - rejectUrl = (String)props.get(WF_PROP_REJECT_URL); - this.ticket = (String)props.get(WF_PROP_INVITE_TICKET); - this.sentInviteDate =inviteDate; - } - - private static Map getConstructorProps(String inviteId, Map props) - { - Map parentProps = new HashMap(); - parentProps.put(ID_KEY, inviteId); - parentProps.put(INVITEE_KEY, (String) props.get(WF_PROP_INVITEE_USER_NAME)); - parentProps.put(ROLE_KEY,(String)props.get(WF_PROP_INVITEE_ROLE)); - parentProps.put(RESOURCE_NAME_KEY,(String)props.get(WF_PROP_RESOURCE_NAME)); - parentProps.put(RESOURCE_TYPE_KEY,(String)props.get(WF_PROP_RESOURCE_TYPE)); - return parentProps; - } - - public String getInviteeFirstName() - { - return inviteeFirstName; - } - - public String getInviteeLastName() - { - return inviteeLastName; - } - - public String getInviteeEmail() - { - return inviteeEmail; - } - - public String getResourceDescription() - { - return resourceDescription; - } - - public String getResourceTitle() - { - return resourceTitle; - } - - public String getServerPath() - { - return serverPath; - } - - public String getAcceptUrl() - { - return acceptUrl; - } - - public String getRejectUrl() - { - return rejectUrl; - } - - public Date getSentInviteDate() - { - return sentInviteDate; - } - - public String getTicket() - { - return ticket; - } - - public String getInviterUserName() - { - return inviterUserName; - } - - @Override - public InvitationType getInvitationType() - { - return InvitationType.NOMINATED; - } -} +package org.alfresco.repo.invitation; + +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_ACCEPT_URL; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_EMAIL; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_FIRSTNAME; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_LASTNAME; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_ROLE; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITEE_USER_NAME; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITER_USER_NAME; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_INVITE_TICKET; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_REJECT_URL; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_DESCRIPTION; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_NAME; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_TITLE; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_TYPE; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.WF_PROP_SERVER_PATH; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.service.cmr.invitation.NominatedInvitation; +import org.alfresco.service.namespace.QName; + +/** + * NominatedInvitationImpl is a basic Nominated Invitation Request that + * is processed by the InvitationService. + * + * @see org.alfresco.service.cmr.invitation.NominatedInvitation + */ +/*package scope */ class NominatedInvitationImpl extends InvitationImpl implements NominatedInvitation, Serializable +{ + private static final long serialVersionUID = -8800842866845149466L; + + private final String inviteeFirstName; + private final String inviteeLastName; + private final String inviteeEmail; + private final String inviterUserName; + private final String resourceDescription; + private final String resourceTitle; + private final String serverPath; + private final String acceptUrl; + private final String rejectUrl; + private final Date sentInviteDate; + private final String ticket; + + public NominatedInvitationImpl(String inviteId, Date inviteDate, Map props) + { + super(getConstructorProps(inviteId, props)); + inviteeFirstName = (String)props.get(WF_PROP_INVITEE_FIRSTNAME); + inviteeLastName = (String)props.get(WF_PROP_INVITEE_LASTNAME); + inviteeEmail = (String)props.get(WF_PROP_INVITEE_EMAIL); + inviterUserName = (String)props.get(WF_PROP_INVITER_USER_NAME); + resourceTitle = (String)props.get(WF_PROP_RESOURCE_TITLE); + resourceDescription = (String)props.get(WF_PROP_RESOURCE_DESCRIPTION); + serverPath = (String)props.get(WF_PROP_SERVER_PATH); + acceptUrl = (String)props.get(WF_PROP_ACCEPT_URL); + rejectUrl = (String)props.get(WF_PROP_REJECT_URL); + this.ticket = (String)props.get(WF_PROP_INVITE_TICKET); + this.sentInviteDate =inviteDate; + } + + private static Map getConstructorProps(String inviteId, Map props) + { + Map parentProps = new HashMap(); + parentProps.put(ID_KEY, inviteId); + parentProps.put(INVITEE_KEY, (String) props.get(WF_PROP_INVITEE_USER_NAME)); + parentProps.put(ROLE_KEY,(String)props.get(WF_PROP_INVITEE_ROLE)); + parentProps.put(RESOURCE_NAME_KEY,(String)props.get(WF_PROP_RESOURCE_NAME)); + parentProps.put(RESOURCE_TYPE_KEY,(String)props.get(WF_PROP_RESOURCE_TYPE)); + return parentProps; + } + + public String getInviteeFirstName() + { + return inviteeFirstName; + } + + public String getInviteeLastName() + { + return inviteeLastName; + } + + public String getInviteeEmail() + { + return inviteeEmail; + } + + public String getResourceDescription() + { + return resourceDescription; + } + + public String getResourceTitle() + { + return resourceTitle; + } + + public String getServerPath() + { + return serverPath; + } + + public String getAcceptUrl() + { + return acceptUrl; + } + + public String getRejectUrl() + { + return rejectUrl; + } + + public Date getSentInviteDate() + { + return sentInviteDate; + } + + public String getTicket() + { + return ticket; + } + + public String getInviterUserName() + { + return inviterUserName; + } + + @Override + public InvitationType getInvitationType() + { + return InvitationType.NOMINATED; + } +} diff --git a/source/java/org/alfresco/repo/invitation/NominatedInvitationProcess.java b/source/java/org/alfresco/repo/invitation/NominatedInvitationProcess.java index 14d7d304de..db4882db61 100644 --- a/source/java/org/alfresco/repo/invitation/NominatedInvitationProcess.java +++ b/source/java/org/alfresco/repo/invitation/NominatedInvitationProcess.java @@ -1,36 +1,36 @@ -package org.alfresco.repo.invitation; - -import org.alfresco.service.cmr.invitation.Invitation; - -/** - * The Invitation process is the interface provided by the invitation service to be - * implemented by each resource handler - * - * This invitation process is where someone nominates an invitee who then needs to accept or - * reject the nomination. - */ - -public interface NominatedInvitationProcess extends InvitationProcess -{ - /* - * inviter starts the invitation process - */ - public Invitation invite(Invitation request, String comment); - - /** - * invitee accepts this request - * @param request Invitation - */ - public void accept(Invitation request); - - /** - * invitee rejects this request - * @param request Invitation - */ - public void reject(Invitation request); - - /** - * cancel this request - */ - public void cancel (Invitation request); -} +package org.alfresco.repo.invitation; + +import org.alfresco.service.cmr.invitation.Invitation; + +/** + * The Invitation process is the interface provided by the invitation service to be + * implemented by each resource handler + * + * This invitation process is where someone nominates an invitee who then needs to accept or + * reject the nomination. + */ + +public interface NominatedInvitationProcess extends InvitationProcess +{ + /* + * inviter starts the invitation process + */ + public Invitation invite(Invitation request, String comment); + + /** + * invitee accepts this request + * @param request Invitation + */ + public void accept(Invitation request); + + /** + * invitee rejects this request + * @param request Invitation + */ + public void reject(Invitation request); + + /** + * cancel this request + */ + public void cancel (Invitation request); +} diff --git a/source/java/org/alfresco/repo/invitation/WorkflowModelModeratedInvitation.java b/source/java/org/alfresco/repo/invitation/WorkflowModelModeratedInvitation.java index 51359f2f9c..b50c2c7299 100644 --- a/source/java/org/alfresco/repo/invitation/WorkflowModelModeratedInvitation.java +++ b/source/java/org/alfresco/repo/invitation/WorkflowModelModeratedInvitation.java @@ -1,54 +1,54 @@ -package org.alfresco.repo.invitation; - -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; - -/** - * Workflow Model for a Moderated Invitation - */ -public interface WorkflowModelModeratedInvitation -{ - // namespace - public static final String NAMESPACE_URI = "http://www.alfresco.org/model/workflow/invite/moderated/1.0"; - - // process name - public static final QName WF_PROCESS_INVITATION_MODERATED = QName.createQName(NAMESPACE_URI, "invitation-moderated"); - - // workflow definition name - public static final String WORKFLOW_DEFINITION_NAME = "jbpm$imwf:invitation-moderated"; - public static final String WORKFLOW_DEFINITION_NAME_ACTIVITI = "activiti$activitiInvitationModerated"; - - // tasks - public static final QName WF_START_TASK = QName.createQName(NAMESPACE_URI, "moderatedInvitationSubmitTask"); - public static final QName WF_REVIEW_TASK = QName.createQName(NAMESPACE_URI,"moderatedInvitationReviewTask"); - public static final QName WF_ACTIVITI_REVIEW_TASK = QName.createQName(NAMESPACE_URI,"activitiModeratedInvitationReviewTask"); - - // associations - static final QName ASSOC_GROUP_ASSIGNEE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "groupAssignee"); - - // transition names - public static final String WF_TRANSITION_REVIEW = "review"; - public static final String WF_TRANSITION_APPROVE = "approve"; - public static final String WF_TRANSITION_REJECT = "reject"; - public static final String WF_TRANSITION_CANCEL = "cancel"; - public static final String WF_TRANSITION_END = "end"; - - // workflow properties - public static final QName WF_PROP_INVITEE_USER_NAME = QName.createQName(NAMESPACE_URI, "inviteeUserName"); - public static final QName WF_PROP_INVITEE_ROLE = QName.createQName(NAMESPACE_URI, "inviteeRole"); - public static final QName WF_PROP_INVITEE_COMMENTS = QName.createQName(NAMESPACE_URI, "inviteeComments"); - public static final QName WF_PROP_RESOURCE_NAME = QName.createQName(NAMESPACE_URI, "resourceName"); - public static final QName WF_PROP_RESOURCE_TYPE= QName.createQName(NAMESPACE_URI, "resourceType"); - public static final QName WF_PROP_REVIEW_COMMENTS= QName.createQName(NAMESPACE_URI, "reviewComments"); - public static final QName WF_PROP_REVIEWER= QName.createQName(NAMESPACE_URI, "reviewer"); - public static final QName WF_PROP_MODIFIED_AT= QName.createQName(NAMESPACE_URI, "modifiedAt"); - - // workflow execution context variable names - public static final String wfVarInviteeUserName = "imwf_inviteeUserName"; - public static final String wfVarInviteeRole = "imwf_inviteeRole"; - public static final String wfVarWorkflowInstanceId = "workflowinstanceid"; - public static final String wfVarResourceName = "imwf_resourceName"; - public static final String wfVarResourceType = "imwf_resourceType"; - public static final String wfVarReviewer = "imwf_reviewer"; - public static final String wfVarReviewComments = "imwf_reviewComments"; -} +package org.alfresco.repo.invitation; + +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * Workflow Model for a Moderated Invitation + */ +public interface WorkflowModelModeratedInvitation +{ + // namespace + public static final String NAMESPACE_URI = "http://www.alfresco.org/model/workflow/invite/moderated/1.0"; + + // process name + public static final QName WF_PROCESS_INVITATION_MODERATED = QName.createQName(NAMESPACE_URI, "invitation-moderated"); + + // workflow definition name + public static final String WORKFLOW_DEFINITION_NAME = "jbpm$imwf:invitation-moderated"; + public static final String WORKFLOW_DEFINITION_NAME_ACTIVITI = "activiti$activitiInvitationModerated"; + + // tasks + public static final QName WF_START_TASK = QName.createQName(NAMESPACE_URI, "moderatedInvitationSubmitTask"); + public static final QName WF_REVIEW_TASK = QName.createQName(NAMESPACE_URI,"moderatedInvitationReviewTask"); + public static final QName WF_ACTIVITI_REVIEW_TASK = QName.createQName(NAMESPACE_URI,"activitiModeratedInvitationReviewTask"); + + // associations + static final QName ASSOC_GROUP_ASSIGNEE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "groupAssignee"); + + // transition names + public static final String WF_TRANSITION_REVIEW = "review"; + public static final String WF_TRANSITION_APPROVE = "approve"; + public static final String WF_TRANSITION_REJECT = "reject"; + public static final String WF_TRANSITION_CANCEL = "cancel"; + public static final String WF_TRANSITION_END = "end"; + + // workflow properties + public static final QName WF_PROP_INVITEE_USER_NAME = QName.createQName(NAMESPACE_URI, "inviteeUserName"); + public static final QName WF_PROP_INVITEE_ROLE = QName.createQName(NAMESPACE_URI, "inviteeRole"); + public static final QName WF_PROP_INVITEE_COMMENTS = QName.createQName(NAMESPACE_URI, "inviteeComments"); + public static final QName WF_PROP_RESOURCE_NAME = QName.createQName(NAMESPACE_URI, "resourceName"); + public static final QName WF_PROP_RESOURCE_TYPE= QName.createQName(NAMESPACE_URI, "resourceType"); + public static final QName WF_PROP_REVIEW_COMMENTS= QName.createQName(NAMESPACE_URI, "reviewComments"); + public static final QName WF_PROP_REVIEWER= QName.createQName(NAMESPACE_URI, "reviewer"); + public static final QName WF_PROP_MODIFIED_AT= QName.createQName(NAMESPACE_URI, "modifiedAt"); + + // workflow execution context variable names + public static final String wfVarInviteeUserName = "imwf_inviteeUserName"; + public static final String wfVarInviteeRole = "imwf_inviteeRole"; + public static final String wfVarWorkflowInstanceId = "workflowinstanceid"; + public static final String wfVarResourceName = "imwf_resourceName"; + public static final String wfVarResourceType = "imwf_resourceType"; + public static final String wfVarReviewer = "imwf_reviewer"; + public static final String wfVarReviewComments = "imwf_reviewComments"; +} diff --git a/source/java/org/alfresco/repo/invitation/activiti/SendNominatedInviteAddDirectDelegate.java b/source/java/org/alfresco/repo/invitation/activiti/SendNominatedInviteAddDirectDelegate.java index f9bd4d0b60..46614d699e 100644 --- a/source/java/org/alfresco/repo/invitation/activiti/SendNominatedInviteAddDirectDelegate.java +++ b/source/java/org/alfresco/repo/invitation/activiti/SendNominatedInviteAddDirectDelegate.java @@ -1,31 +1,31 @@ -package org.alfresco.repo.invitation.activiti; - -import java.util.Map; - -import org.activiti.engine.delegate.DelegateExecution; -import org.alfresco.repo.workflow.activiti.ActivitiConstants; - -/** - * Activiti delegate that is executed when a invitation request has - * been sent. - * - * @author Nick Smith - * @author Frederik Heremans - * @author Ray Gauss II - * @since 4.0 - */ -public class SendNominatedInviteAddDirectDelegate extends AbstractInvitationDelegate -{ - public static final String EMAIL_TEMPLATE_XPATH = - "app:company_home/app:dictionary/app:email_templates/cm:invite/cm:invite-email-add-direct.html.ftl"; - public static final String EMAIL_SUBJECT_KEY = - "invitation.invitesender.emailAddDirect.subject"; - - @Override - public void execute(DelegateExecution execution) throws Exception - { - String invitationId = ActivitiConstants.ENGINE_ID + "$" + execution.getProcessInstanceId(); - Map variables = execution.getVariables(); - invitationService.sendNominatedInvitation(invitationId, EMAIL_TEMPLATE_XPATH, EMAIL_SUBJECT_KEY, variables); - } -} +package org.alfresco.repo.invitation.activiti; + +import java.util.Map; + +import org.activiti.engine.delegate.DelegateExecution; +import org.alfresco.repo.workflow.activiti.ActivitiConstants; + +/** + * Activiti delegate that is executed when a invitation request has + * been sent. + * + * @author Nick Smith + * @author Frederik Heremans + * @author Ray Gauss II + * @since 4.0 + */ +public class SendNominatedInviteAddDirectDelegate extends AbstractInvitationDelegate +{ + public static final String EMAIL_TEMPLATE_XPATH = + "app:company_home/app:dictionary/app:email_templates/cm:invite/cm:invite-email-add-direct.html.ftl"; + public static final String EMAIL_SUBJECT_KEY = + "invitation.invitesender.emailAddDirect.subject"; + + @Override + public void execute(DelegateExecution execution) throws Exception + { + String invitationId = ActivitiConstants.ENGINE_ID + "$" + execution.getProcessInstanceId(); + Map variables = execution.getVariables(); + invitationService.sendNominatedInvitation(invitationId, EMAIL_TEMPLATE_XPATH, EMAIL_SUBJECT_KEY, variables); + } +} diff --git a/source/java/org/alfresco/repo/invitation/site/SiteModeratedInvitationProcess.java b/source/java/org/alfresco/repo/invitation/site/SiteModeratedInvitationProcess.java index 6c8112d2b3..8687c27933 100644 --- a/source/java/org/alfresco/repo/invitation/site/SiteModeratedInvitationProcess.java +++ b/source/java/org/alfresco/repo/invitation/site/SiteModeratedInvitationProcess.java @@ -1,37 +1,37 @@ -package org.alfresco.repo.invitation.site; - -import org.alfresco.repo.invitation.ModeratedInvitationProcess; -import org.alfresco.service.cmr.invitation.Invitation; - -/** - * The Site Private Invitation Process implements the PrivateInvitatonProcess for - * Web Sites. - */ -public class SiteModeratedInvitationProcess implements ModeratedInvitationProcess -{ - public void approve(Invitation request, String reason) - { - // TODO Auto-generated method stub - } - - public void cancel(Invitation request, String reason) - { - // TODO Auto-generated method stub - } - - public void reject(Invitation request, String reason) - { - // TODO Auto-generated method stub - } - - public Invitation invite(Invitation request, String reason) - { - // TODO Auto-generated method stub - return null; - } - - public void cancel(Invitation request) - { - // TODO Auto-generated method stub - } -} +package org.alfresco.repo.invitation.site; + +import org.alfresco.repo.invitation.ModeratedInvitationProcess; +import org.alfresco.service.cmr.invitation.Invitation; + +/** + * The Site Private Invitation Process implements the PrivateInvitatonProcess for + * Web Sites. + */ +public class SiteModeratedInvitationProcess implements ModeratedInvitationProcess +{ + public void approve(Invitation request, String reason) + { + // TODO Auto-generated method stub + } + + public void cancel(Invitation request, String reason) + { + // TODO Auto-generated method stub + } + + public void reject(Invitation request, String reason) + { + // TODO Auto-generated method stub + } + + public Invitation invite(Invitation request, String reason) + { + // TODO Auto-generated method stub + return null; + } + + public void cancel(Invitation request) + { + // TODO Auto-generated method stub + } +} diff --git a/source/java/org/alfresco/repo/invitation/site/SiteNominatedInvitationProcess.java b/source/java/org/alfresco/repo/invitation/site/SiteNominatedInvitationProcess.java index f516e090ee..fc0039ba92 100644 --- a/source/java/org/alfresco/repo/invitation/site/SiteNominatedInvitationProcess.java +++ b/source/java/org/alfresco/repo/invitation/site/SiteNominatedInvitationProcess.java @@ -1,42 +1,42 @@ -package org.alfresco.repo.invitation.site; - -import org.alfresco.repo.invitation.NominatedInvitationProcess; -import org.alfresco.service.cmr.invitation.Invitation; - -/** - * The Site Private Invitation Process implements the PrivateInvitatonProcess for - * Web Sites. - */ -public class SiteNominatedInvitationProcess implements NominatedInvitationProcess -{ - /** - * inviter starts the invitation process - */ - public Invitation invite(Invitation request, String reason) - { - return null; - } - - /** - * invitee accepts this request - * @param request Invitation - */ - public void accept(Invitation request) - { - } - - /** - * invitee rejects this request - * @param request Invitation - */ - public void reject(Invitation request) - { - } - - /** - * cancel this request - */ - public void cancel (Invitation request) - { - } -} +package org.alfresco.repo.invitation.site; + +import org.alfresco.repo.invitation.NominatedInvitationProcess; +import org.alfresco.service.cmr.invitation.Invitation; + +/** + * The Site Private Invitation Process implements the PrivateInvitatonProcess for + * Web Sites. + */ +public class SiteNominatedInvitationProcess implements NominatedInvitationProcess +{ + /** + * inviter starts the invitation process + */ + public Invitation invite(Invitation request, String reason) + { + return null; + } + + /** + * invitee accepts this request + * @param request Invitation + */ + public void accept(Invitation request) + { + } + + /** + * invitee rejects this request + * @param request Invitation + */ + public void reject(Invitation request) + { + } + + /** + * cancel this request + */ + public void cancel (Invitation request) + { + } +} diff --git a/source/java/org/alfresco/repo/jscript/Actions.java b/source/java/org/alfresco/repo/jscript/Actions.java index 4e59752601..3a66b07a28 100644 --- a/source/java/org/alfresco/repo/jscript/Actions.java +++ b/source/java/org/alfresco/repo/jscript/Actions.java @@ -1,68 +1,68 @@ -package org.alfresco.repo.jscript; - -import java.util.List; - -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionDefinition; -import org.alfresco.service.cmr.action.ActionService; - -/** - * Scripted Action service for describing and executing actions against Nodes. - * - * @author davidc - */ -public final class Actions extends BaseScopableProcessorExtension -{ - /** Repository Service Registry */ - private ServiceRegistry services; - - /** - * Set the service registry - * - * @param serviceRegistry the service registry - */ - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.services = serviceRegistry; - } - - /** - * Gets the list of registered action names - * - * @return the registered action names - */ - public String[] getRegistered() - { - ActionService actionService = services.getActionService(); - List defs = actionService.getActionDefinitions(); - String[] registered = new String[defs.size()]; - int i = 0; - for (ActionDefinition def : defs) - { - registered[i++] = def.getName(); - } - return registered; - } - - /** - * Create an Action - * - * @param actionName - * the action name - * @return the action - */ - public ScriptAction create(String actionName) - { - ScriptAction scriptAction = null; - ActionService actionService = services.getActionService(); - ActionDefinition actionDef = actionService.getActionDefinition(actionName); - if (actionDef != null) - { - Action action = actionService.createAction(actionName); - scriptAction = new ScriptAction(this.services, action, actionDef); - scriptAction.setScope(getScope()); - } - return scriptAction; - } -} +package org.alfresco.repo.jscript; + +import java.util.List; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionService; + +/** + * Scripted Action service for describing and executing actions against Nodes. + * + * @author davidc + */ +public final class Actions extends BaseScopableProcessorExtension +{ + /** Repository Service Registry */ + private ServiceRegistry services; + + /** + * Set the service registry + * + * @param serviceRegistry the service registry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.services = serviceRegistry; + } + + /** + * Gets the list of registered action names + * + * @return the registered action names + */ + public String[] getRegistered() + { + ActionService actionService = services.getActionService(); + List defs = actionService.getActionDefinitions(); + String[] registered = new String[defs.size()]; + int i = 0; + for (ActionDefinition def : defs) + { + registered[i++] = def.getName(); + } + return registered; + } + + /** + * Create an Action + * + * @param actionName + * the action name + * @return the action + */ + public ScriptAction create(String actionName) + { + ScriptAction scriptAction = null; + ActionService actionService = services.getActionService(); + ActionDefinition actionDef = actionService.getActionDefinition(actionName); + if (actionDef != null) + { + Action action = actionService.createAction(actionName); + scriptAction = new ScriptAction(this.services, action, actionDef); + scriptAction.setScope(getScope()); + } + return scriptAction; + } +} diff --git a/source/java/org/alfresco/repo/jscript/Association.java b/source/java/org/alfresco/repo/jscript/Association.java index a20dd699d6..519161d255 100644 --- a/source/java/org/alfresco/repo/jscript/Association.java +++ b/source/java/org/alfresco/repo/jscript/Association.java @@ -1,79 +1,79 @@ -package org.alfresco.repo.jscript; - -import java.io.Serializable; - -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.namespace.QName; -import org.springframework.extensions.surf.util.ParameterCheck; -import org.mozilla.javascript.Scriptable; - -/** - * Object representing an association - * - * @author Roy Wetherall - */ -public class Association implements Scopeable, Serializable -{ - /** Serial version UUID*/ - private static final long serialVersionUID = 897788515655487131L; - - /** Service registry **/ - private ServiceRegistry services; - - /** Script scope **/ - private Scriptable scope; - - /** Association reference **/ - private AssociationRef assocRef; - - public Association(ServiceRegistry services, AssociationRef assocRef) - { - this(services, assocRef, null); - } - - public Association(ServiceRegistry services, AssociationRef assocRef, Scriptable scope) - { - ParameterCheck.mandatory("Service registry", services); - ParameterCheck.mandatory("Association reference", assocRef); - this.services = services; - this.assocRef = assocRef; - if (scope != null) - { - this.scope = scope; - } - } - - /** - * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) - */ - public void setScope(Scriptable scope) - { - this.scope = scope; - } - - public AssociationRef getAssociationRef() - { - return this.assocRef; - } - - public String getType() - { - return assocRef.getTypeQName().toString(); - } - - public QName getTypeQName() - { - return assocRef.getTypeQName(); - } - - public ScriptNode getSource() - { - return (ScriptNode)new ValueConverter().convertValueForScript(this.services, this.scope, null, assocRef.getSourceRef()); - } - - public ScriptNode getTarget() - { - return (ScriptNode)new ValueConverter().convertValueForScript(this.services, this.scope, null, assocRef.getTargetRef()); - } -} +package org.alfresco.repo.jscript; + +import java.io.Serializable; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.namespace.QName; +import org.springframework.extensions.surf.util.ParameterCheck; +import org.mozilla.javascript.Scriptable; + +/** + * Object representing an association + * + * @author Roy Wetherall + */ +public class Association implements Scopeable, Serializable +{ + /** Serial version UUID*/ + private static final long serialVersionUID = 897788515655487131L; + + /** Service registry **/ + private ServiceRegistry services; + + /** Script scope **/ + private Scriptable scope; + + /** Association reference **/ + private AssociationRef assocRef; + + public Association(ServiceRegistry services, AssociationRef assocRef) + { + this(services, assocRef, null); + } + + public Association(ServiceRegistry services, AssociationRef assocRef, Scriptable scope) + { + ParameterCheck.mandatory("Service registry", services); + ParameterCheck.mandatory("Association reference", assocRef); + this.services = services; + this.assocRef = assocRef; + if (scope != null) + { + this.scope = scope; + } + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + public AssociationRef getAssociationRef() + { + return this.assocRef; + } + + public String getType() + { + return assocRef.getTypeQName().toString(); + } + + public QName getTypeQName() + { + return assocRef.getTypeQName(); + } + + public ScriptNode getSource() + { + return (ScriptNode)new ValueConverter().convertValueForScript(this.services, this.scope, null, assocRef.getSourceRef()); + } + + public ScriptNode getTarget() + { + return (ScriptNode)new ValueConverter().convertValueForScript(this.services, this.scope, null, assocRef.getTargetRef()); + } +} diff --git a/source/java/org/alfresco/repo/jscript/BaseScopableProcessorExtension.java b/source/java/org/alfresco/repo/jscript/BaseScopableProcessorExtension.java index 0a699d1742..361e705262 100644 --- a/source/java/org/alfresco/repo/jscript/BaseScopableProcessorExtension.java +++ b/source/java/org/alfresco/repo/jscript/BaseScopableProcessorExtension.java @@ -1,36 +1,36 @@ -package org.alfresco.repo.jscript; - -import org.alfresco.api.AlfrescoPublicApi; -import org.alfresco.repo.processor.BaseProcessorExtension; -import org.mozilla.javascript.Scriptable; - -/** - * Abstract base class for a script implementation that requires a script execution scope. - * - * The scope is local to the currently executing script and therefore a ThreadLocal is required. - * - * @author Kevin Roast - */ -@AlfrescoPublicApi -public class BaseScopableProcessorExtension extends BaseProcessorExtension implements Scopeable -{ - private static ThreadLocal scope = new ThreadLocal(); - - /** - * Set the Scriptable global scope - * - * @param scope relative global scope - */ - public void setScope(Scriptable scope) - { - BaseScopableProcessorExtension.scope.set(scope); - } - - /** - * @return script global scope - */ - public Scriptable getScope() - { - return BaseScopableProcessorExtension.scope.get(); - } -} +package org.alfresco.repo.jscript; + +import org.alfresco.api.AlfrescoPublicApi; +import org.alfresco.repo.processor.BaseProcessorExtension; +import org.mozilla.javascript.Scriptable; + +/** + * Abstract base class for a script implementation that requires a script execution scope. + * + * The scope is local to the currently executing script and therefore a ThreadLocal is required. + * + * @author Kevin Roast + */ +@AlfrescoPublicApi +public class BaseScopableProcessorExtension extends BaseProcessorExtension implements Scopeable +{ + private static ThreadLocal scope = new ThreadLocal(); + + /** + * Set the Scriptable global scope + * + * @param scope relative global scope + */ + public void setScope(Scriptable scope) + { + BaseScopableProcessorExtension.scope.set(scope); + } + + /** + * @return script global scope + */ + public Scriptable getScope() + { + return BaseScopableProcessorExtension.scope.get(); + } +} diff --git a/source/java/org/alfresco/repo/jscript/Behaviour.java b/source/java/org/alfresco/repo/jscript/Behaviour.java index 37eed961fd..05fe3dcb31 100644 --- a/source/java/org/alfresco/repo/jscript/Behaviour.java +++ b/source/java/org/alfresco/repo/jscript/Behaviour.java @@ -1,85 +1,85 @@ -package org.alfresco.repo.jscript; - -import java.io.Serializable; - -import org.alfresco.service.ServiceRegistry; -import org.mozilla.javascript.Scriptable; - -/** - * Object representing the behaviour information - * - * @author Roy Wetherall - */ -public class Behaviour implements Scopeable, Serializable -{ - /** Serial version UID **/ - private static final long serialVersionUID = 1936017361886646100L; - - /** Service registry **/ - private ServiceRegistry services; - - /** Script scope **/ - private Scriptable scope; - - /** The name of the policy that this behaviour is linked to **/ - private String name; - - /** The behaviour argument values **/ - private Object[] args; - - /** Cached js converted argument values **/ - private Serializable[] jsArgs; - - /** - * Constructor - * - * @param services the service registry - * @param name the name of the policy associated with this behaviour - * @param args the argument values - */ - public Behaviour(ServiceRegistry services, String name, Object[] args) - { - this.services = services; - this.name = name; - this.args = args; - } - - /** - * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) - */ - public void setScope(Scriptable scope) - { - this.scope = scope; - } - - /** - * Get the policy name - * - * @return the name of the policy - */ - public String getName() - { - return this.name; - } - - /** - * The argument values - * - * @return array containing the argument values - */ - public Serializable[] getArgs() - { - if (this.jsArgs == null) - { - ValueConverter valueConverter = new ValueConverter(); - this.jsArgs = new Serializable[args.length]; - int index = 0; - for (Object arg : this.args) - { - this.jsArgs[index] = valueConverter.convertValueForScript(services, this.scope, null, (Serializable)arg); - index ++; - } - } - return this.jsArgs; - } -} +package org.alfresco.repo.jscript; + +import java.io.Serializable; + +import org.alfresco.service.ServiceRegistry; +import org.mozilla.javascript.Scriptable; + +/** + * Object representing the behaviour information + * + * @author Roy Wetherall + */ +public class Behaviour implements Scopeable, Serializable +{ + /** Serial version UID **/ + private static final long serialVersionUID = 1936017361886646100L; + + /** Service registry **/ + private ServiceRegistry services; + + /** Script scope **/ + private Scriptable scope; + + /** The name of the policy that this behaviour is linked to **/ + private String name; + + /** The behaviour argument values **/ + private Object[] args; + + /** Cached js converted argument values **/ + private Serializable[] jsArgs; + + /** + * Constructor + * + * @param services the service registry + * @param name the name of the policy associated with this behaviour + * @param args the argument values + */ + public Behaviour(ServiceRegistry services, String name, Object[] args) + { + this.services = services; + this.name = name; + this.args = args; + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + /** + * Get the policy name + * + * @return the name of the policy + */ + public String getName() + { + return this.name; + } + + /** + * The argument values + * + * @return array containing the argument values + */ + public Serializable[] getArgs() + { + if (this.jsArgs == null) + { + ValueConverter valueConverter = new ValueConverter(); + this.jsArgs = new Serializable[args.length]; + int index = 0; + for (Object arg : this.args) + { + this.jsArgs[index] = valueConverter.convertValueForScript(services, this.scope, null, (Serializable)arg); + index ++; + } + } + return this.jsArgs; + } +} diff --git a/source/java/org/alfresco/repo/jscript/CategoryTemplateNode.java b/source/java/org/alfresco/repo/jscript/CategoryTemplateNode.java index dea2e20ba1..ccd45552a5 100644 --- a/source/java/org/alfresco/repo/jscript/CategoryTemplateNode.java +++ b/source/java/org/alfresco/repo/jscript/CategoryTemplateNode.java @@ -1,186 +1,186 @@ -package org.alfresco.repo.jscript; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.template.TemplateNode; -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.TemplateImageResolver; -import org.alfresco.service.cmr.search.CategoryService; -import org.alfresco.service.namespace.QName; - -/** - * Category Nodes from the classification helper have special support. - */ -public class CategoryTemplateNode extends TemplateNode -{ - /** - * Constructor - * - * @param nodeRef NodeRef - * @param services ServiceRegistry - * @param resolver TemplateImageResolver - */ - public CategoryTemplateNode(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver) - { - super(nodeRef, services, resolver); - } - - @Override - public boolean getIsCategory() - { - return true; - } - - /** - * @return all the member of a category - */ - public List getCategoryMembers() - { - if (getIsCategory()) - { - return buildTemplateNodeList(services.getCategoryService().getChildren(getNodeRef(), - CategoryService.Mode.MEMBERS, CategoryService.Depth.ANY)); - } - else - { - return Collections.emptyList(); - } - } - - /** - * @return all the subcategories of a category - */ - public List getSubCategories() - { - if (getIsCategory()) - { - return buildCategoryNodeList(services.getCategoryService().getChildren(getNodeRef(), - CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.ANY)); - } - else - { - return Collections.emptyList(); - } - } - - /** - * @return members and subcategories of a category - */ - public List getMembersAndSubCategories() - { - if (getIsCategory()) - { - - return buildMixedNodeList(services.getCategoryService().getChildren(getNodeRef(), CategoryService.Mode.ALL, - CategoryService.Depth.ANY)); - } - else - { - return Collections.emptyList(); - } - } - - /** - * @return all the immediate member of a category - */ - public List getImmediateCategoryMembers() - { - if (getIsCategory()) - { - return buildTemplateNodeList(services.getCategoryService().getChildren(getNodeRef(), - CategoryService.Mode.MEMBERS, CategoryService.Depth.IMMEDIATE)); - } - else - { - return Collections.emptyList(); - } - } - - /** - * @return all the immediate subcategories of a category - */ - public List getImmediateSubCategories() - { - if (getIsCategory()) - { - return buildCategoryNodeList(services.getCategoryService().getChildren(getNodeRef(), - CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.IMMEDIATE)); - } - else - { - return Collections.emptyList(); - } - } - - /** - * @return immediate members and subcategories of a category - */ - public List getImmediateMembersAndSubCategories() - { - if (getIsCategory()) - { - return buildMixedNodeList(services.getCategoryService().getChildren(getNodeRef(), - CategoryService.Mode.ALL, CategoryService.Depth.IMMEDIATE)); - } - else - { - return Collections.emptyList(); - } - } - - /** - * Support to build node lists from category service API calls. - * - * @param childRefs Collection - * - * @return List of TemplateNode - */ - private List buildTemplateNodeList(Collection childRefs) - { - List answer = new ArrayList(childRefs.size()); - for (ChildAssociationRef ref : childRefs) - { - // create our Node representation from the NodeRef - TemplateNode child = new TemplateNode(ref.getChildRef(), this.services, this.imageResolver); - answer.add(child); - } - return answer; - } - - private List buildCategoryNodeList(Collection childRefs) - { - List answer = new ArrayList(childRefs.size()); - for (ChildAssociationRef ref : childRefs) - { - // create our Node representation from the NodeRef - CategoryTemplateNode child = new CategoryTemplateNode(ref.getChildRef(), this.services, this.imageResolver); - answer.add(child); - } - return answer; - } - - private List buildMixedNodeList(Collection cars) - { - List nodes = new ArrayList(cars.size()); - int i = 0; - for (ChildAssociationRef car : cars) - { - QName type = services.getNodeService().getType(car.getChildRef()); - if (services.getDictionaryService().isSubClass(type, ContentModel.TYPE_CATEGORY)) - { - nodes.add(new CategoryTemplateNode(car.getChildRef(), this.services, this.imageResolver)); - } - else - { - nodes.add(new TemplateNode(car.getChildRef(), this.services, this.imageResolver)); - } - } - return nodes; - } -} +package org.alfresco.repo.jscript; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.template.TemplateNode; +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.TemplateImageResolver; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.service.namespace.QName; + +/** + * Category Nodes from the classification helper have special support. + */ +public class CategoryTemplateNode extends TemplateNode +{ + /** + * Constructor + * + * @param nodeRef NodeRef + * @param services ServiceRegistry + * @param resolver TemplateImageResolver + */ + public CategoryTemplateNode(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver) + { + super(nodeRef, services, resolver); + } + + @Override + public boolean getIsCategory() + { + return true; + } + + /** + * @return all the member of a category + */ + public List getCategoryMembers() + { + if (getIsCategory()) + { + return buildTemplateNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.MEMBERS, CategoryService.Depth.ANY)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return all the subcategories of a category + */ + public List getSubCategories() + { + if (getIsCategory()) + { + return buildCategoryNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.ANY)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return members and subcategories of a category + */ + public List getMembersAndSubCategories() + { + if (getIsCategory()) + { + + return buildMixedNodeList(services.getCategoryService().getChildren(getNodeRef(), CategoryService.Mode.ALL, + CategoryService.Depth.ANY)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return all the immediate member of a category + */ + public List getImmediateCategoryMembers() + { + if (getIsCategory()) + { + return buildTemplateNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.MEMBERS, CategoryService.Depth.IMMEDIATE)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return all the immediate subcategories of a category + */ + public List getImmediateSubCategories() + { + if (getIsCategory()) + { + return buildCategoryNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.IMMEDIATE)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return immediate members and subcategories of a category + */ + public List getImmediateMembersAndSubCategories() + { + if (getIsCategory()) + { + return buildMixedNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.ALL, CategoryService.Depth.IMMEDIATE)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * Support to build node lists from category service API calls. + * + * @param childRefs Collection + * + * @return List of TemplateNode + */ + private List buildTemplateNodeList(Collection childRefs) + { + List answer = new ArrayList(childRefs.size()); + for (ChildAssociationRef ref : childRefs) + { + // create our Node representation from the NodeRef + TemplateNode child = new TemplateNode(ref.getChildRef(), this.services, this.imageResolver); + answer.add(child); + } + return answer; + } + + private List buildCategoryNodeList(Collection childRefs) + { + List answer = new ArrayList(childRefs.size()); + for (ChildAssociationRef ref : childRefs) + { + // create our Node representation from the NodeRef + CategoryTemplateNode child = new CategoryTemplateNode(ref.getChildRef(), this.services, this.imageResolver); + answer.add(child); + } + return answer; + } + + private List buildMixedNodeList(Collection cars) + { + List nodes = new ArrayList(cars.size()); + int i = 0; + for (ChildAssociationRef car : cars) + { + QName type = services.getNodeService().getType(car.getChildRef()); + if (services.getDictionaryService().isSubClass(type, ContentModel.TYPE_CATEGORY)) + { + nodes.add(new CategoryTemplateNode(car.getChildRef(), this.services, this.imageResolver)); + } + else + { + nodes.add(new TemplateNode(car.getChildRef(), this.services, this.imageResolver)); + } + } + return nodes; + } +} diff --git a/source/java/org/alfresco/repo/jscript/ChildAssociation.java b/source/java/org/alfresco/repo/jscript/ChildAssociation.java index 2e6e5f5289..275c4e4cbc 100644 --- a/source/java/org/alfresco/repo/jscript/ChildAssociation.java +++ b/source/java/org/alfresco/repo/jscript/ChildAssociation.java @@ -1,95 +1,95 @@ -package org.alfresco.repo.jscript; - -import java.io.Serializable; - -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.springframework.extensions.surf.util.ParameterCheck; -import org.mozilla.javascript.Scriptable; - -/** - * Object representing a child association - * - * @author Roy Wetherall - */ -public class ChildAssociation implements Scopeable, Serializable -{ - /** Serial version UUID **/ - private static final long serialVersionUID = -2122640697340663213L; - - /** Service registry **/ - private ServiceRegistry services; - - /** Script scope **/ - private Scriptable scope; - - /** Child association reference **/ - private ChildAssociationRef childAssocRef; - - public ChildAssociation(ServiceRegistry services, ChildAssociationRef childAssocRef) - { - this(services, childAssocRef, null); - } - - /** - * Constructor - * - * @param services ServiceRegistry - * @param childAssocRef ChildAssociationRef - * @param scope Scriptable - */ - public ChildAssociation(ServiceRegistry services, ChildAssociationRef childAssocRef, Scriptable scope) - { - ParameterCheck.mandatory("Service registry", services); - ParameterCheck.mandatory("Child association reference", childAssocRef); - this.services = services; - this.childAssocRef = childAssocRef; - if (scope != null) - { - this.scope = scope; - } - } - - /** - * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) - */ - public void setScope(Scriptable scope) - { - this.scope = scope; - } - - public ChildAssociationRef getChildAssociationRef() - { - return this.childAssocRef; - } - - public String getType() - { - return childAssocRef.getTypeQName().toString(); - } - - public String getName() - { - return childAssocRef.getQName().toString(); - } - - public ScriptNode getParent() - { - return (ScriptNode)new ValueConverter().convertValueForScript(this.services, this.scope, null, childAssocRef.getParentRef()); - } - - public ScriptNode getChild() - { - return (ScriptNode)new ValueConverter().convertValueForScript(this.services, this.scope, null, childAssocRef.getChildRef()); - } - - public boolean isPrimary() - { - return this.childAssocRef.isPrimary(); - } - - public int getNthSibling() - { - return this.childAssocRef.getNthSibling(); - } -} +package org.alfresco.repo.jscript; + +import java.io.Serializable; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.springframework.extensions.surf.util.ParameterCheck; +import org.mozilla.javascript.Scriptable; + +/** + * Object representing a child association + * + * @author Roy Wetherall + */ +public class ChildAssociation implements Scopeable, Serializable +{ + /** Serial version UUID **/ + private static final long serialVersionUID = -2122640697340663213L; + + /** Service registry **/ + private ServiceRegistry services; + + /** Script scope **/ + private Scriptable scope; + + /** Child association reference **/ + private ChildAssociationRef childAssocRef; + + public ChildAssociation(ServiceRegistry services, ChildAssociationRef childAssocRef) + { + this(services, childAssocRef, null); + } + + /** + * Constructor + * + * @param services ServiceRegistry + * @param childAssocRef ChildAssociationRef + * @param scope Scriptable + */ + public ChildAssociation(ServiceRegistry services, ChildAssociationRef childAssocRef, Scriptable scope) + { + ParameterCheck.mandatory("Service registry", services); + ParameterCheck.mandatory("Child association reference", childAssocRef); + this.services = services; + this.childAssocRef = childAssocRef; + if (scope != null) + { + this.scope = scope; + } + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + public ChildAssociationRef getChildAssociationRef() + { + return this.childAssocRef; + } + + public String getType() + { + return childAssocRef.getTypeQName().toString(); + } + + public String getName() + { + return childAssocRef.getQName().toString(); + } + + public ScriptNode getParent() + { + return (ScriptNode)new ValueConverter().convertValueForScript(this.services, this.scope, null, childAssocRef.getParentRef()); + } + + public ScriptNode getChild() + { + return (ScriptNode)new ValueConverter().convertValueForScript(this.services, this.scope, null, childAssocRef.getChildRef()); + } + + public boolean isPrimary() + { + return this.childAssocRef.isPrimary(); + } + + public int getNthSibling() + { + return this.childAssocRef.getNthSibling(); + } +} diff --git a/source/java/org/alfresco/repo/jscript/ClasspathScriptLocation.java b/source/java/org/alfresco/repo/jscript/ClasspathScriptLocation.java index 494d68613d..001f877935 100644 --- a/source/java/org/alfresco/repo/jscript/ClasspathScriptLocation.java +++ b/source/java/org/alfresco/repo/jscript/ClasspathScriptLocation.java @@ -1,113 +1,113 @@ -package org.alfresco.repo.jscript; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.scripts.ScriptException; -import org.alfresco.service.cmr.repository.ScriptLocation; -import org.springframework.extensions.surf.util.ParameterCheck; - -/** - * Classpath script location object. - * - * @author Roy Wetherall - */ -public class ClasspathScriptLocation implements ScriptLocation -{ - /** Classpath location **/ - private final String location; - - /** - * Constructor - * - * @param location the classpath location - */ - public ClasspathScriptLocation(String location) - { - ParameterCheck.mandatory("Location", location); - this.location = location; - } - - /** - * @see org.alfresco.service.cmr.repository.ScriptLocation#getInputStream() - */ - public InputStream getInputStream() - { - InputStream stream = getClass().getClassLoader().getResourceAsStream(location); - if (stream == null) - { - throw new AlfrescoRuntimeException("Unable to load classpath resource: " + location); - } - return stream; - } - - /** - * @see org.alfresco.service.cmr.repository.ScriptLocation#getReader() - */ - public Reader getReader() - { - Reader reader = null; - try - { - InputStream stream = getClass().getClassLoader().getResourceAsStream(location); - if (stream == null) - { - throw new AlfrescoRuntimeException("Unable to load classpath resource: " + location); - } - reader = new InputStreamReader(stream); - } - catch (Throwable err) - { - throw new ScriptException("Failed to load classpath resource '" + location + "': " + err.getMessage(), err); - } - - return reader; - } - - /** - * @see org.alfresco.service.cmr.repository.ScriptLocation#getPath() - */ - public String getPath() - { - return this.location; - } - - public boolean isCachable() - { - return true; - } - - public boolean isSecure() - { - return true; - } - - @Override - public boolean equals(Object obj) - { - if (obj == this) - { - return true; - } - else if (obj == null || !(obj instanceof ClasspathScriptLocation)) - { - return false; - } - ClasspathScriptLocation other = (ClasspathScriptLocation)obj; - return this.location.equals(other.location); - } - - @Override - public int hashCode() - { - return 37 * this.location.hashCode(); - } - - @Override - public String toString() - { - return this.location.toString(); - } -} +package org.alfresco.repo.jscript; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.scripts.ScriptException; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * Classpath script location object. + * + * @author Roy Wetherall + */ +public class ClasspathScriptLocation implements ScriptLocation +{ + /** Classpath location **/ + private final String location; + + /** + * Constructor + * + * @param location the classpath location + */ + public ClasspathScriptLocation(String location) + { + ParameterCheck.mandatory("Location", location); + this.location = location; + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptLocation#getInputStream() + */ + public InputStream getInputStream() + { + InputStream stream = getClass().getClassLoader().getResourceAsStream(location); + if (stream == null) + { + throw new AlfrescoRuntimeException("Unable to load classpath resource: " + location); + } + return stream; + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptLocation#getReader() + */ + public Reader getReader() + { + Reader reader = null; + try + { + InputStream stream = getClass().getClassLoader().getResourceAsStream(location); + if (stream == null) + { + throw new AlfrescoRuntimeException("Unable to load classpath resource: " + location); + } + reader = new InputStreamReader(stream); + } + catch (Throwable err) + { + throw new ScriptException("Failed to load classpath resource '" + location + "': " + err.getMessage(), err); + } + + return reader; + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptLocation#getPath() + */ + public String getPath() + { + return this.location; + } + + public boolean isCachable() + { + return true; + } + + public boolean isSecure() + { + return true; + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) + { + return true; + } + else if (obj == null || !(obj instanceof ClasspathScriptLocation)) + { + return false; + } + ClasspathScriptLocation other = (ClasspathScriptLocation)obj; + return this.location.equals(other.location); + } + + @Override + public int hashCode() + { + return 37 * this.location.hashCode(); + } + + @Override + public String toString() + { + return this.location.toString(); + } +} diff --git a/source/java/org/alfresco/repo/jscript/ContentAwareScriptableQNameMap.java b/source/java/org/alfresco/repo/jscript/ContentAwareScriptableQNameMap.java index 8360d81fa4..933d2268c6 100644 --- a/source/java/org/alfresco/repo/jscript/ContentAwareScriptableQNameMap.java +++ b/source/java/org/alfresco/repo/jscript/ContentAwareScriptableQNameMap.java @@ -1,81 +1,81 @@ -package org.alfresco.repo.jscript; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.namespace.NamespacePrefixResolver; -import org.alfresco.service.namespace.NamespacePrefixResolverProvider; -import org.alfresco.service.namespace.QName; - -/** - * Specialised map class for supporting the initialisation of 'cm:content' properties for JavaScript API - * objects. The JavaScript needs supporting objects to be initialised for certain data-types. If the - * 'cm:content' property is not already initialised then it must be created on demand or it will not be - * available to the users of the API. See AR-1673. - * - * @author Kevin Roast - */ -public class ContentAwareScriptableQNameMap extends ScriptableQNameMap -{ - private ServiceRegistry services; - private ScriptNode factory; - - - /** - * Constructor - * - * @param factory Factory to provide further ScriptNode objects - * @param services ServiceRegistry - */ - public ContentAwareScriptableQNameMap(final ScriptNode factory, final ServiceRegistry services) - { - super(new NamespacePrefixResolverProvider(){ - public NamespacePrefixResolver getNamespacePrefixResolver() - { - return services.getNamespaceService(); - } - }); - this.services = services; - this.factory = factory; - } - - /* (non-Javadoc) - * @see org.alfresco.service.namespace.QNameMap#get(java.lang.Object) - */ - @Override - public Object get(Object name) - { - Object value = super.get(name); - - if (value == null) - { - // convert the key to a qname and look up the data-type for the property - QName qname = QName.resolveToQName(getResolver(), name.toString()); - PropertyDefinition propDef = this.services.getDictionaryService().getProperty(qname); - if (propDef != null && DataTypeDefinition.CONTENT.equals(propDef.getDataType().getName())) - { - // found a valid cm:content property that is not initialised - String mimetype = null; - if (qname.equals(ContentModel.PROP_CONTENT)) - { - String fileName = (String)get("cm:name"); - if (fileName != null) - { - // We don't have any content, so just use the filename when - // trying to guess the mimetype for this - mimetype = this.services.getMimetypeService().guessMimetype(fileName); - } - } - ContentData cdata = new ContentData(null, mimetype, 0L, "UTF-8"); - // create the JavaScript API object we need - value = factory.new ScriptContentData(cdata, qname); - // and store it so it is available to the API user - put(name, value); - } - } - - return value; - } -} +package org.alfresco.repo.jscript; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.NamespacePrefixResolverProvider; +import org.alfresco.service.namespace.QName; + +/** + * Specialised map class for supporting the initialisation of 'cm:content' properties for JavaScript API + * objects. The JavaScript needs supporting objects to be initialised for certain data-types. If the + * 'cm:content' property is not already initialised then it must be created on demand or it will not be + * available to the users of the API. See AR-1673. + * + * @author Kevin Roast + */ +public class ContentAwareScriptableQNameMap extends ScriptableQNameMap +{ + private ServiceRegistry services; + private ScriptNode factory; + + + /** + * Constructor + * + * @param factory Factory to provide further ScriptNode objects + * @param services ServiceRegistry + */ + public ContentAwareScriptableQNameMap(final ScriptNode factory, final ServiceRegistry services) + { + super(new NamespacePrefixResolverProvider(){ + public NamespacePrefixResolver getNamespacePrefixResolver() + { + return services.getNamespaceService(); + } + }); + this.services = services; + this.factory = factory; + } + + /* (non-Javadoc) + * @see org.alfresco.service.namespace.QNameMap#get(java.lang.Object) + */ + @Override + public Object get(Object name) + { + Object value = super.get(name); + + if (value == null) + { + // convert the key to a qname and look up the data-type for the property + QName qname = QName.resolveToQName(getResolver(), name.toString()); + PropertyDefinition propDef = this.services.getDictionaryService().getProperty(qname); + if (propDef != null && DataTypeDefinition.CONTENT.equals(propDef.getDataType().getName())) + { + // found a valid cm:content property that is not initialised + String mimetype = null; + if (qname.equals(ContentModel.PROP_CONTENT)) + { + String fileName = (String)get("cm:name"); + if (fileName != null) + { + // We don't have any content, so just use the filename when + // trying to guess the mimetype for this + mimetype = this.services.getMimetypeService().guessMimetype(fileName); + } + } + ContentData cdata = new ContentData(null, mimetype, 0L, "UTF-8"); + // create the JavaScript API object we need + value = factory.new ScriptContentData(cdata, qname); + // and store it so it is available to the API user + put(name, value); + } + } + + return value; + } +} diff --git a/source/java/org/alfresco/repo/jscript/ExecuteScriptJob.java b/source/java/org/alfresco/repo/jscript/ExecuteScriptJob.java index 7434a199a6..2c7d81e542 100644 --- a/source/java/org/alfresco/repo/jscript/ExecuteScriptJob.java +++ b/source/java/org/alfresco/repo/jscript/ExecuteScriptJob.java @@ -1,69 +1,69 @@ -package org.alfresco.repo.jscript; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.service.cmr.repository.ScriptLocation; -import org.alfresco.service.cmr.repository.ScriptService; -import org.quartz.Job; -import org.quartz.JobDataMap; -import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; - -/** - * Quartz job that executes a scheduled JS script. - * - * @author Roy Wetherall - */ -public class ExecuteScriptJob implements Job -{ - private static final String PARAM_SCRIPT_LOCATION = "scriptLocation"; - private static final String PARAM_SCRIPT_SERVICE = "scriptService"; - private static final String PARAM_AUTHENTICATION_COMPONENT = "authenticationComponent"; - - /** - * Executes the scheduled script - * - * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) - */ - public void execute(JobExecutionContext context) throws JobExecutionException - { - JobDataMap jobData = context.getJobDetail().getJobDataMap(); - - // Get the script service from the job map - Object scriptServiceObj = jobData.get(PARAM_SCRIPT_SERVICE); - if (scriptServiceObj == null || !(scriptServiceObj instanceof ScriptService)) - { - throw new AlfrescoRuntimeException( - "ExecuteScriptJob data must contain valid script service"); - } - - // Get the script location from the job map - Object scriptLocationObj = jobData.get(PARAM_SCRIPT_LOCATION); - if (scriptLocationObj == null || !(scriptLocationObj instanceof ScriptLocation)) - { - throw new AlfrescoRuntimeException( - "ExecuteScriptJob data must contain valid script location"); - } - - // Get the authentication component from the job map - Object authenticationComponentObj = jobData.get(PARAM_AUTHENTICATION_COMPONENT); - if (authenticationComponentObj == null || !(authenticationComponentObj instanceof AuthenticationComponent)) - { - throw new AlfrescoRuntimeException( - "ExecuteScriptJob data must contain valid authentication component"); - } - - - // Execute the script as the system user - ((AuthenticationComponent)authenticationComponentObj).setSystemUserAsCurrentUser(); - try - { - // Execute the script - ((ScriptService)scriptServiceObj).executeScript((ScriptLocation)scriptLocationObj, null); - } - finally - { - ((AuthenticationComponent)authenticationComponentObj).clearCurrentSecurityContext(); - } - } -} +package org.alfresco.repo.jscript; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.alfresco.service.cmr.repository.ScriptService; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * Quartz job that executes a scheduled JS script. + * + * @author Roy Wetherall + */ +public class ExecuteScriptJob implements Job +{ + private static final String PARAM_SCRIPT_LOCATION = "scriptLocation"; + private static final String PARAM_SCRIPT_SERVICE = "scriptService"; + private static final String PARAM_AUTHENTICATION_COMPONENT = "authenticationComponent"; + + /** + * Executes the scheduled script + * + * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + + // Get the script service from the job map + Object scriptServiceObj = jobData.get(PARAM_SCRIPT_SERVICE); + if (scriptServiceObj == null || !(scriptServiceObj instanceof ScriptService)) + { + throw new AlfrescoRuntimeException( + "ExecuteScriptJob data must contain valid script service"); + } + + // Get the script location from the job map + Object scriptLocationObj = jobData.get(PARAM_SCRIPT_LOCATION); + if (scriptLocationObj == null || !(scriptLocationObj instanceof ScriptLocation)) + { + throw new AlfrescoRuntimeException( + "ExecuteScriptJob data must contain valid script location"); + } + + // Get the authentication component from the job map + Object authenticationComponentObj = jobData.get(PARAM_AUTHENTICATION_COMPONENT); + if (authenticationComponentObj == null || !(authenticationComponentObj instanceof AuthenticationComponent)) + { + throw new AlfrescoRuntimeException( + "ExecuteScriptJob data must contain valid authentication component"); + } + + + // Execute the script as the system user + ((AuthenticationComponent)authenticationComponentObj).setSystemUserAsCurrentUser(); + try + { + // Execute the script + ((ScriptService)scriptServiceObj).executeScript((ScriptLocation)scriptLocationObj, null); + } + finally + { + ((AuthenticationComponent)authenticationComponentObj).clearCurrentSecurityContext(); + } + } +} diff --git a/source/java/org/alfresco/repo/jscript/NativeMap.java b/source/java/org/alfresco/repo/jscript/NativeMap.java index 1f1d043811..7b647dce16 100644 --- a/source/java/org/alfresco/repo/jscript/NativeMap.java +++ b/source/java/org/alfresco/repo/jscript/NativeMap.java @@ -1,214 +1,214 @@ -package org.alfresco.repo.jscript; - -import java.util.Iterator; -import java.util.Map; - -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.Wrapper; - - -/** - * Wrapper for exposing maps in Rhino scripts. - * - * @author davidc - */ -public class NativeMap implements Scriptable, Wrapper -{ - private static final long serialVersionUID = 3664761893203964569L; - - private Map map; - private Scriptable parentScope; - private Scriptable prototype; - - - /** - * Construct - * - * @param scope Scriptable - * @param map Map - * @return native map - */ - public static NativeMap wrap(Scriptable scope, Map map) - { - return new NativeMap(scope, map); - } - - /** - * Construct - * - * @param scope Scriptable - * @param map Map - */ - public NativeMap(Scriptable scope, Map map) - { - this.parentScope = scope; - this.map = map; - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Wrapper#unwrap() - */ - public Object unwrap() - { - return map; - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#getClassName() - */ - public String getClassName() - { - return "NativeMap"; - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) - */ - public Object get(String name, Scriptable start) - { - // get the property from the underlying QName map - if ("length".equals(name)) - { - return map.size(); - } - else - { - return map.get(name); - } - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) - */ - public Object get(int index, Scriptable start) - { - Object value = null; - int i=0; - Iterator itrValues = map.values().iterator(); - while (i++ <= index && itrValues.hasNext()) - { - value = itrValues.next(); - } - return value; - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable) - */ - public boolean has(String name, Scriptable start) - { - // locate the property in the underlying map - return map.containsKey(name); - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable) - */ - public boolean has(int index, Scriptable start) - { - return (index >= 0 && map.values().size() > index); - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) - */ - @SuppressWarnings("unchecked") - public void put(String name, Scriptable start, Object value) - { - map.put(name, value); - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object) - */ - public void put(int index, Scriptable start, Object value) - { - // TODO: implement? - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) - */ - public void delete(String name) - { - map.remove(name); - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#delete(int) - */ - public void delete(int index) - { - int i=0; - Iterator itrKeys = map.keySet().iterator(); - while (i <= index && itrKeys.hasNext()) - { - Object key = itrKeys.next(); - if (i == index) - { - map.remove(key); - break; - } - } - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#getPrototype() - */ - public Scriptable getPrototype() - { - return this.prototype; - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable) - */ - public void setPrototype(Scriptable prototype) - { - this.prototype = prototype; - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#getParentScope() - */ - public Scriptable getParentScope() - { - return this.parentScope; - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable) - */ - public void setParentScope(Scriptable parent) - { - this.parentScope = parent; - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#getIds() - */ - public Object[] getIds() - { - return map.keySet().toArray(); - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class) - */ - public Object getDefaultValue(@SuppressWarnings("rawtypes") Class hint) - { - return String.valueOf(map); - } - - /* (non-Javadoc) - * @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable) - */ - public boolean hasInstance(Scriptable value) - { - if (!(value instanceof Wrapper)) - return false; - Object instance = ((Wrapper)value).unwrap(); - return Map.class.isInstance(instance); - } - -} +package org.alfresco.repo.jscript; + +import java.util.Iterator; +import java.util.Map; + +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.Wrapper; + + +/** + * Wrapper for exposing maps in Rhino scripts. + * + * @author davidc + */ +public class NativeMap implements Scriptable, Wrapper +{ + private static final long serialVersionUID = 3664761893203964569L; + + private Map map; + private Scriptable parentScope; + private Scriptable prototype; + + + /** + * Construct + * + * @param scope Scriptable + * @param map Map + * @return native map + */ + public static NativeMap wrap(Scriptable scope, Map map) + { + return new NativeMap(scope, map); + } + + /** + * Construct + * + * @param scope Scriptable + * @param map Map + */ + public NativeMap(Scriptable scope, Map map) + { + this.parentScope = scope; + this.map = map; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Wrapper#unwrap() + */ + public Object unwrap() + { + return map; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#getClassName() + */ + public String getClassName() + { + return "NativeMap"; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public Object get(String name, Scriptable start) + { + // get the property from the underlying QName map + if ("length".equals(name)) + { + return map.size(); + } + else + { + return map.get(name); + } + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) + */ + public Object get(int index, Scriptable start) + { + Object value = null; + int i=0; + Iterator itrValues = map.values().iterator(); + while (i++ <= index && itrValues.hasNext()) + { + value = itrValues.next(); + } + return value; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public boolean has(String name, Scriptable start) + { + // locate the property in the underlying map + return map.containsKey(name); + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable) + */ + public boolean has(int index, Scriptable start) + { + return (index >= 0 && map.values().size() > index); + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + @SuppressWarnings("unchecked") + public void put(String name, Scriptable start, Object value) + { + map.put(name, value); + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + public void put(int index, Scriptable start, Object value) + { + // TODO: implement? + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) + */ + public void delete(String name) + { + map.remove(name); + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#delete(int) + */ + public void delete(int index) + { + int i=0; + Iterator itrKeys = map.keySet().iterator(); + while (i <= index && itrKeys.hasNext()) + { + Object key = itrKeys.next(); + if (i == index) + { + map.remove(key); + break; + } + } + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#getPrototype() + */ + public Scriptable getPrototype() + { + return this.prototype; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable) + */ + public void setPrototype(Scriptable prototype) + { + this.prototype = prototype; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#getParentScope() + */ + public Scriptable getParentScope() + { + return this.parentScope; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable) + */ + public void setParentScope(Scriptable parent) + { + this.parentScope = parent; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#getIds() + */ + public Object[] getIds() + { + return map.keySet().toArray(); + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class) + */ + public Object getDefaultValue(@SuppressWarnings("rawtypes") Class hint) + { + return String.valueOf(map); + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable) + */ + public boolean hasInstance(Scriptable value) + { + if (!(value instanceof Wrapper)) + return false; + Object instance = ((Wrapper)value).unwrap(); + return Map.class.isInstance(instance); + } + +} diff --git a/source/java/org/alfresco/repo/jscript/People.java b/source/java/org/alfresco/repo/jscript/People.java index 281a36acdf..5eb07f3894 100644 --- a/source/java/org/alfresco/repo/jscript/People.java +++ b/source/java/org/alfresco/repo/jscript/People.java @@ -1,1289 +1,1289 @@ -package org.alfresco.repo.jscript; - -import java.io.Serializable; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationException; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.UserNameGenerator; -import org.alfresco.repo.security.authority.AuthorityDAO; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.repo.security.person.PersonServiceImpl; -import org.alfresco.repo.security.sync.UserRegistrySynchronizer; -import org.alfresco.repo.tenant.TenantDomainMismatchException; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.LimitBy; -import org.alfresco.service.cmr.search.PermissionEvaluationMode; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.MutableAuthenticationService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.security.PersonService.PersonInfo; -import org.alfresco.service.cmr.usage.ContentUsageService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.Pair; -import org.alfresco.util.PropertyMap; -import org.alfresco.util.ScriptPagingDetails; -import org.alfresco.util.ValueDerivingMapFactory; -import org.alfresco.util.ValueDerivingMapFactory.ValueDeriver; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.Scriptable; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.extensions.surf.util.I18NUtil; -import org.springframework.extensions.surf.util.ParameterCheck; - -/** - * Scripted People service for describing and executing actions against People & Groups. - * - * @author davidc - * @author kevinr - */ -public class People extends BaseScopableProcessorExtension implements InitializingBean -{ - private static Log logger = LogFactory.getLog(People.class); - - /** Repository Service Registry */ - private ServiceRegistry services; - private AuthorityDAO authorityDAO; - private AuthorityService authorityService; - private PersonService personService; - private MutableAuthenticationService authenticationService; - private ContentUsageService contentUsageService; - private UserNameGenerator usernameGenerator; - private UserRegistrySynchronizer userRegistrySynchronizer; - protected TenantService tenantService; - - private StoreRef storeRef; - private ValueDerivingMapFactory valueDerivingMapFactory; - private int numRetries = 10; - - private int defaultListMaxResults = 5000; - private boolean honorHintUseCQ = true; - - protected static final String HINT_CQ_SUFFIX = " [hint:useCQ]"; - - public void afterPropertiesSet() throws Exception - { - Map > capabilityTesters = new HashMap>(5); - capabilityTesters.put("isAdmin", new ValueDeriver() - { - public Boolean deriveValue(ScriptNode source) - { - return isAdmin(source); - } - }); - capabilityTesters.put("isGuest", new ValueDeriver() - { - public Boolean deriveValue(ScriptNode source) - { - return isGuest(source); - } - }); - capabilityTesters.put("isMutable", new ValueDeriver() - { - public Boolean deriveValue(ScriptNode source) - { - // Check whether the account is mutable according to the authentication service - String sourceUser = (String) source.getProperties().get(ContentModel.PROP_USERNAME); - if (!authenticationService.isAuthenticationMutable(sourceUser)) - { - return false; - } - // Only allow non-admin users to mutate their own accounts - String currentUser = authenticationService.getCurrentUserName(); - if (currentUser.equals(sourceUser) || authorityService.isAdminAuthority(currentUser)) - { - return true; - } - return false; - } - }); - this.valueDerivingMapFactory = new ValueDerivingMapFactory(capabilityTesters); - } - - /** - * Set the default store reference - * - * @param storeRef the default store reference - */ - public void setStoreUrl(String storeRef) - { - // ensure this is not set again by a script instance - if (this.storeRef != null) - { - throw new IllegalStateException("Default store URL can only be set once."); - } - this.storeRef = new StoreRef(storeRef); - } - - /** - * Sets the authentication service. - * - * @param authenticationService - * the authentication service - */ - public void setAuthenticationService(MutableAuthenticationService authenticationService) - { - this.authenticationService = authenticationService; - } - - /** - * Set the service registry - * - * @param serviceRegistry the service registry - */ - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.services = serviceRegistry; - } - - /** - * Set the authority DAO - * - * @param authorityDAO authority dao - */ - public void setAuthorityDAO(AuthorityDAO authorityDAO) - { - this.authorityDAO = authorityDAO; - } - - /** - * Set the authority service - * - * @param authorityService The authorityService to set. - */ - public void setAuthorityService(AuthorityService authorityService) - { - this.authorityService = authorityService; - } - - /** - * Set the person service - * - * @param personService The personService to set. - */ - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - /** - * @param contentUsageService the ContentUsageService to set - */ - public void setContentUsageService(ContentUsageService contentUsageService) - { - this.contentUsageService = contentUsageService; - } - - /** - * @param tenantService the tenantService to set - */ - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - /** - * Set the user name generator service - * - * @param userNameGenerator the user name generator - */ - public void setUserNameGenerator(UserNameGenerator userNameGenerator) - { - this.usernameGenerator = userNameGenerator; - } - - /** - * Set the UserRegistrySynchronizer - * - * @param userRegistrySynchronizer UserRegistrySynchronizer - */ - public void setUserRegistrySynchronizer(UserRegistrySynchronizer userRegistrySynchronizer) - { - this.userRegistrySynchronizer = userRegistrySynchronizer; - } - - public void setDefaultListMaxResults(int defaultListMaxResults) - { - this.defaultListMaxResults = defaultListMaxResults; - } - - /** - * Allows customers to choose to use Solr or Lucene rather than a canned query in - * {@link #getPeople(String, int, String, boolean)} when - * {@code " [hint:useCQ]"} is appended to the search term (currently Share's - * User Console does this). The down side is that new users may not appear as they - * will not have been indexed. This is similar to what happened in 4.1.1 prior to - * MNT-7548 (4.1.2 and 4.1.1.1). The down side of using a canned query at the moment - * is that there is a bug, so that it is impossible to search for names such as - * {@code "Carlos Allende García"} where the first or last names may contain spaces. - * See MNT-9719 for more details. The alfresco global property - * {@code people.search.honor.hint.useCQ} is used to set this value (default is true). - */ - public void setHonorHintUseCQ(boolean honorHintUseCQ) - { - this.honorHintUseCQ = honorHintUseCQ; - } - - /** - * Delete a Person with the given username - * - * @param username the username of the person to delete - */ - public void deletePerson(String username) - { - personService.deletePerson(username); - } - - /** - * Create a Person with an optionally generated user name. - * This version doesn't notify them. - * - * @param userName userName or null for a generated user name - * @param firstName firstName - * @param lastName lastName - * @param emailAddress emailAddress - * @param password if not null creates a new authenticator with the given password. - * @param setAccountEnabled - * set to 'true' to create enabled user account, or 'false' to - * create disabled user account for created person. - * @return the person node (type cm:person) created or null if the person - * could not be created - */ - public ScriptNode createPerson(String userName, String firstName, String lastName, String emailAddress, String password, boolean setAccountEnabled) - { - return createPerson(userName, firstName, lastName, emailAddress, password, setAccountEnabled, false); - } - - /** - * Create a Person with an optionally generated user name - * - * @param userName userName or null for a generated user name - * @param firstName firstName - * @param lastName lastName - * @param emailAddress emailAddress - * @param password if not null creates a new authenticator with the given password. - * @param setAccountEnabled - * set to 'true' to create enabled user account, or 'false' to - * create disabled user account for created person. - * @param notifyByEmail - * set to 'true' to have the new user emailed to let them know - * their account details. Only applies if a username and - * password were supplied. - * @return the person node (type cm:person) created or null if the person - * could not be created - */ - public ScriptNode createPerson(String userName, String firstName, String lastName, String emailAddress, - String password, boolean setAccountEnabled, boolean notifyByEmail) - { - ParameterCheck.mandatory("firstName", firstName); - ParameterCheck.mandatory("emailAddress", emailAddress); - - ScriptNode person = null; - - // generate user name if not supplied - if (userName == null) - { - for (int i=0; i < numRetries; i++) - { - userName = usernameGenerator.generateUserName(firstName, lastName, emailAddress, i); - - // create person if user name does not already exist - if (!personService.personExists(userName)) - { - break; - } - } - } - - if (userName != null) - { - try - { - userName = PersonServiceImpl.updateUsernameForTenancy(userName, tenantService); - } - catch (TenantDomainMismatchException re) - { - throw new AuthenticationException("User must belong to same domain as admin: " + re.getTenantA()); - } - - person = createPerson(userName, firstName, lastName, emailAddress); - - if (person != null && password != null) - { - // create account for person with the userName and password - authenticationService.createAuthentication(userName, password.toCharArray()); - authenticationService.setAuthenticationEnabled(userName, setAccountEnabled); - - person.save(); - - if(notifyByEmail) - { - personService.notifyPerson(userName, password); - } - } - } - - return person; - } - - /** - * Enable user account. Can only be called by an Admin authority. - * - * @param userName user name for which to enable user account - */ - public void enableAccount(String userName) - { - if (this.authorityService.isAdminAuthority(AuthenticationUtil.getFullyAuthenticatedUser())) - { - this.authenticationService.setAuthenticationEnabled(userName, true); - } - } - - /** - * Disable user account. Can only be called by an Admin authority. - * - * @param userName user name for which to disable user account - */ - public void disableAccount(String userName) - { - if (this.authorityService.isAdminAuthority(AuthenticationUtil.getFullyAuthenticatedUser())) - { - this.authenticationService.setAuthenticationEnabled(userName, false); - } - } - - /** - * Return true if the specified user account is enabled. - * - * @param userName user name to test account - * - * @return true if account enabled, false if disabled - */ - public boolean isAccountEnabled(String userName) - { - return this.authenticationService.getAuthenticationEnabled(userName); - } - - /** - * Change the password for the currently logged in user. - * Old password must be supplied. - * - * @param oldPassword Old user password - * @param newPassword New user password - */ - public void changePassword(String oldPassword, String newPassword) - { - ParameterCheck.mandatoryString("oldPassword", oldPassword); - ParameterCheck.mandatoryString("newPassword", newPassword); - - this.services.getAuthenticationService().updateAuthentication( - AuthenticationUtil.getFullyAuthenticatedUser(), oldPassword.toCharArray(), newPassword.toCharArray()); - } - - /** - * Set a password for the given user. Note that only an administrator - * can perform this action, otherwise it will be ignored. - * - * @param userName Username to change password for - * @param password Password to set - */ - public void setPassword(String userName, String password) - { - ParameterCheck.mandatoryString("userName", userName); - ParameterCheck.mandatoryString("password", password); - - MutableAuthenticationService authService = this.services.getAuthenticationService(); - if (this.authorityService.hasAdminAuthority() && (userName.equalsIgnoreCase(authService.getCurrentUserName()) == false)) - { - authService.setAuthentication(userName, password.toCharArray()); - } - } - - /** - * Create a Person with the given user name - * - * @param userName the user name of the person to create - * @return the person node (type cm:person) created or null if the user name already exists - */ - public ScriptNode createPerson(String userName) - { - ParameterCheck.mandatoryString("userName", userName); - - ScriptNode person = null; - - PropertyMap properties = new PropertyMap(); - properties.put(ContentModel.PROP_USERNAME, userName); - - if (!personService.personExists(userName)) - { - NodeRef personRef = personService.createPerson(properties); - person = new ScriptNode(personRef, services, getScope()); - } - - return person; - } - - /** - * Create a Person with the given user name, firstName, lastName and emailAddress - * - * @param userName the user name of the person to create - * @return the person node (type cm:person) created or null if the user name already exists - */ - public ScriptNode createPerson(String userName, String firstName, String lastName, String emailAddress) - { - ParameterCheck.mandatoryString("userName", userName); - ParameterCheck.mandatoryString("firstName", firstName); - ParameterCheck.mandatoryString("emailAddress", emailAddress); - - ScriptNode person = null; - - PropertyMap properties = new PropertyMap(); - properties.put(ContentModel.PROP_USERNAME, userName); - properties.put(ContentModel.PROP_FIRSTNAME, firstName); - properties.put(ContentModel.PROP_LASTNAME, lastName); - properties.put(ContentModel.PROP_EMAIL, emailAddress); - - if (!personService.personExists(userName)) - { - NodeRef personRef = personService.createPerson(properties); - person = new ScriptNode(personRef, services, getScope()); - } - - return person; - } - - /** - * Set the content quota in bytes for a person. - * Only the admin authority can set this value. - * - * @param person Person to set quota against. - * @param quota As a string, in bytes, a value of "-1" means no quota is set - */ - public void setQuota(ScriptNode person, String quota) - { - if (this.authorityService.isAdminAuthority(AuthenticationUtil.getFullyAuthenticatedUser())) - { - this.contentUsageService.setUserQuota((String)person.getProperties().get(ContentModel.PROP_USERNAME), Long.parseLong(quota)); - } - } - - /** - * Get the collection of people stored in the repository. - * An optional filter query may be provided by which to filter the people collection. - * Space separate the query terms i.e. "john bob" will find all users who's first or - * second names contain the strings "john" or "bob". - * - * @param filter filter query string by which to filter the collection of people. - * If
null
then all people stored in the repository are returned - * - * @deprecated recated see getPeople(filter, maxResults) - * - * @return people collection as a JavaScript array - */ - public Scriptable getPeople(String filter) - { - return getPeople(filter, 0); - } - - /** - * Get the collection of people stored in the repository. - * An optional filter query may be provided by which to filter the people collection. - * Space separate the query terms i.e. "john bob" will find all users who's first or - * second names contain the strings "john" or "bob". - * - * @param filter filter query string by which to filter the collection of people. - * If
null
then all people stored in the repository are returned - * @param maxResults maximum results to return or all if <= 0 - * - * @return people collection as a JavaScript array - */ - public Scriptable getPeople(String filter, int maxResults) - { - return getPeople(filter, maxResults, null, true); - } - - /** - * Get the collection of people stored in the repository. - * An optional filter query may be provided by which to filter the people collection. - * Space separate the query terms i.e. "john bob" will find all users who's first or - * second names contain the strings "john" or "bob". - * Method supports sorting by specifying sortBy and sortAsc params. - * - * @param filter filter query string by which to filter the collection of people. - * If
null
then all people stored in the repository are returned - * @param maxResults maximum results to return or all if <= 0 - * @param sortBy field for sorting - * @param sortAsc sort ascending or not - * - * @return people collection as a JavaScript array - */ - public Scriptable getPeople(String filter, int maxResults, String sortBy, boolean sortAsc) - { - return getPeoplePaging(filter, new ScriptPagingDetails(maxResults, 0), sortBy, Boolean.valueOf(sortAsc)); - } - - public Scriptable getPeoplePaging(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) - { - List persons = getPeopleImpl(filter, pagingRequest, sortBy, sortAsc); - - Object[] peopleRefs = new Object[persons.size()]; - for (int i = 0; i < peopleRefs.length; i++) - { - peopleRefs[i] = persons.get(i).getNodeRef(); - } - - return Context.getCurrentContext().newArray(getScope(), peopleRefs); - } - - protected List getPeopleImpl(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) - { - ParameterCheck.mandatory("pagingRequest", pagingRequest); - - boolean useCQ = false; - if (filter != null) - { - if (filter.endsWith(HINT_CQ_SUFFIX)) - { - useCQ = honorHintUseCQ; - filter = filter.substring(0, filter.length()-HINT_CQ_SUFFIX.length()); - } - } - else - { - filter = "*"; - } - - List persons = null; - - int maxResults = pagingRequest.getMaxItems(); - if ((maxResults <= 0) || (maxResults > defaultListMaxResults)) - { - // remove open-ended query (eg cutoff at default/configurable max, eg. 5000 people) - maxResults = defaultListMaxResults; - pagingRequest.setMaxItems(maxResults); - } - - // In order to use a SOLR/Lucene search, we must have a non-empty filter string - see ALF-18876 - if ((filter == null || filter.trim().isEmpty()) || useCQ) - { - persons = getPeopleImplDB(filter, pagingRequest, sortBy, sortAsc); - } - else - { - filter = filter.trim(); - - String term = filter.replace("\"", ""); - String[] tokens = term.split("(? personRefs = getPeopleImplSearch(term, tokens, pagingRequest, sortBy, sortAsc); - - if (personRefs != null) - { - persons = new ArrayList(personRefs.size()); - for (NodeRef personRef : personRefs) - { - persons.add(personService.getPerson(personRef)); - } - } - } - catch (Throwable err) - { - if (useCQ) - { - // search unavailable and/or parser exception - try CQ instead - // simple non-FTS filter: firstname or lastname or username starting with term (ignoring case) - persons = getPeopleImplDB(filter, pagingRequest, sortBy, sortAsc); - } - } - } - - return (persons != null ? persons : new ArrayList(0)); - } - - // canned query - protected List getPeopleImplDB(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) - { - List filterProps = null; - - if ((filter != null) && (filter.length() > 0)) - { - filter = filter.trim(); - if (! filter.equals("*")) - { - filter = filter.replace("\\", "").replace("\"", ""); - - // simple non-FTS filter: firstname or lastname or username starting with term (ignoring case) - - filterProps = new ArrayList(3); - filterProps.add(ContentModel.PROP_FIRSTNAME); - filterProps.add(ContentModel.PROP_LASTNAME); - filterProps.add(ContentModel.PROP_USERNAME); - } - } - - // Build the sorting. The user controls the primary sort, we supply - // additional ones automatically - List> sort = new ArrayList>(); - if ("lastName".equals(sortBy)) - { - sort.add(new Pair(ContentModel.PROP_LASTNAME, sortAsc)); - sort.add(new Pair(ContentModel.PROP_FIRSTNAME, sortAsc)); - sort.add(new Pair(ContentModel.PROP_USERNAME, sortAsc)); - } - else if ("firstName".equals(sortBy)) - { - sort.add(new Pair(ContentModel.PROP_FIRSTNAME, sortAsc)); - sort.add(new Pair(ContentModel.PROP_LASTNAME, sortAsc)); - sort.add(new Pair(ContentModel.PROP_USERNAME, sortAsc)); - } - else - { - sort.add(new Pair(ContentModel.PROP_USERNAME, sortAsc)); - sort.add(new Pair(ContentModel.PROP_FIRSTNAME, sortAsc)); - sort.add(new Pair(ContentModel.PROP_LASTNAME, sortAsc)); - } - - return personService.getPeople(filter, filterProps, sort, pagingRequest).getPage(); - } - - // search query - protected List getPeopleImplSearch(String term, String[] tokens, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) throws Throwable - { - List personRefs = null; - - Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); - - int propIndex = term.indexOf(':'); - - int maxResults = pagingRequest.getMaxItems(); - int skipCount = pagingRequest.getSkipCount(); - - SearchParameters params = new SearchParameters(); - params.addQueryTemplate("_PERSON", "|%firstName OR |%lastName OR |%userName"); - params.setDefaultFieldName("_PERSON"); - params.setExcludeTenantFilter(getExcludeTenantFilter()); - params.setPermissionEvaluation(getPermissionEvaluationMode()); - - StringBuilder query = new StringBuilder(256); - - query.append("TYPE:\"").append(ContentModel.TYPE_PERSON).append("\" AND ("); - - if (tokens.length == 1) - { - // single word with no field will go against _PERSON and expand - - // fts-alfresco property search i.e. location:"maidenhead" - query.append(term.substring(0, propIndex + 1)).append('"'); - if (propIndex < 0) - { - query.append('*'); - } - query.append(term.substring(propIndex + 1)); - if (propIndex > 0) - { - query.append('"'); - } - else - { - query.append("*\""); - } - } - else - { - // scan for non-fts-alfresco property search tokens - int nonFtsTokens = 0; - for (String token : tokens) - { - if (token.indexOf(':') == -1) nonFtsTokens++; - } - tokens = term.split("(? 0) - { - query.append("firstName:"); - query.append(multiPartNames); - query.append(" OR lastName:"); - query.append(multiPartNames); - } - } - query.append(")"); - - // define the search parameters - params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); - params.addStore(this.storeRef); - params.setQuery(query.toString()); - - if (logger.isDebugEnabled()) - { - if ((sortBy != null) && (! sortBy.isEmpty())) - { - logger.debug("getPeopleImplSearch: ignoring sortBy ("+sortBy+")- not yet supported by model for search"); - } - } - - /* not yet supported (default property index tokenisation mode = true) - if ("lastName".equals(sortBy)) - { - params.addSort("@{http://www.alfresco.org/model/content/1.0}lastName", sortAsc); - params.addSort("@{http://www.alfresco.org/model/content/1.0}firstName", sortAsc); - params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); - } - else if ("firstName".equals(sortBy)) - { - params.addSort("@{http://www.alfresco.org/model/content/1.0}firstName", sortAsc); - params.addSort("@{http://www.alfresco.org/model/content/1.0}lastName", sortAsc); - params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); - } - else - { - params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); - params.addSort("@{http://www.alfresco.org/model/content/1.0}firstName", sortAsc); - params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); - } - */ - - if (maxResults > 0) - { - params.setLimitBy(LimitBy.FINAL_SIZE); - params.setLimit(maxResults); - } - - if (skipCount > 0) - { - params.setSkipCount(skipCount); - } - - ResultSet results = null; - try - { - results = services.getSearchService().query(params); - - personRefs = getSortedPeopleObjects(results.getNodeRefs(), sortBy, sortAsc); - - if (start != null) - { - logger.debug("getPeople: search - "+personRefs.size()+" items (in "+(System.currentTimeMillis()-start)+" msecs)"); - } - } - catch (Throwable err) - { - if (logger.isDebugEnabled()) - { - logger.debug("Failed to execute people search: " + query.toString(), err); - } - - throw err; - } - finally - { - if (results != null) - { - results.close(); - } - } - - return personRefs; - } - - private List getSortedPeopleObjects(List peopleRefs, final String sortBy, Boolean sortAsc) - { - if (sortBy == null) - { - return peopleRefs; - } - - //make copy of peopleRefs because it can be unmodifiable list. - List sortedPeopleRefs = new ArrayList(peopleRefs); - final Collator col = Collator.getInstance(I18NUtil.getLocale()); - final NodeService nodeService = services.getNodeService(); - final int orderMultiplicator = ((sortAsc == null) || sortAsc) ? 1 : -1; - Collections.sort(sortedPeopleRefs, new Comparator() - { - @Override - public int compare(NodeRef n1, NodeRef n2) - { - Serializable p1 = getProperty(n1); - Serializable p2 = getProperty(n2); - - if ((p1 instanceof Long) && (p2 instanceof Long)) - { - return Long.compare((Long)p1, (Long)p2) * orderMultiplicator; - } - - return col.compare(p1.toString(), p2) * orderMultiplicator; - } - - public Serializable getProperty(NodeRef nodeRef) - { - Serializable result; - - if ("fullName".equalsIgnoreCase(sortBy)) - { - String firstName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_FIRSTNAME); - String lastName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_LASTNAME); - String fullName = firstName; - if (lastName != null && lastName.length() > 0) - { - fullName = fullName + " " + lastName; - } - - result = fullName; - } - else if ("jobtitle".equalsIgnoreCase(sortBy)) - { - result = nodeService.getProperty(nodeRef, ContentModel.PROP_JOBTITLE); - } - else if ("email".equalsIgnoreCase(sortBy)) - { - result = nodeService.getProperty(nodeRef, ContentModel.PROP_EMAIL); - } - else if ("usage".equalsIgnoreCase(sortBy)) - { - result = nodeService.getProperty(nodeRef, ContentModel.PROP_SIZE_CURRENT); - } - else if ("quota".equalsIgnoreCase(sortBy)) - { - result = nodeService.getProperty(nodeRef, ContentModel.PROP_SIZE_QUOTA); - } - else - { - // Default - result = nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); - } - - if (result == null) - { - result = ""; - } - - return result; - } - - }); - - return sortedPeopleRefs; - } - - /** - * Gets the Person given the username - * - * @param username the username of the person to get - * @return the person node (type cm:person) or null if no such person exists - */ - public ScriptNode getPerson(final String username) - { - NodeRef personRef = null; - - ParameterCheck.mandatory("Username", username); - try - { - personRef = personService.getPersonOrNull(username); - } - catch(AccessDeniedException e) - { - // ok, just return null to indicate not found - } - - return personRef == null ? null : new ScriptNode(personRef, services, getScope()); - } - - /** - * Faster helper when the script just wants to build the Full name for a person. - * Avoids complete getProperties() retrieval for a cm:person. - * - * @param username the username of the person to get Full name for - * @return full name for a person or null if the user does not exist in the system. - */ - public String getPersonFullName(final String username) - { - String name = null; - ParameterCheck.mandatoryString("Username", username); - final NodeRef personRef = personService.getPersonOrNull(username); - if (personRef != null) - { - final NodeService nodeService = services.getNodeService(); - final String firstName = (String)nodeService.getProperty(personRef, ContentModel.PROP_FIRSTNAME); - final String lastName = (String)nodeService.getProperty(personRef, ContentModel.PROP_LASTNAME); - name = (firstName != null ? firstName + " " : "") + (lastName != null ? lastName : ""); - } - return name; - } - - /** - * Gets the Group given the group name - * - * @param groupName name of group to get - * @return the group node (type usr:authorityContainer) or null if no such group exists - */ - public ScriptNode getGroup(String groupName) - { - ParameterCheck.mandatoryString("GroupName", groupName); - ScriptNode group = null; - NodeRef groupRef = authorityDAO.getAuthorityNodeRefOrNull(groupName); - if (groupRef != null) - { - group = new ScriptNode(groupRef, services, getScope()); - } - return group; - } - - /** - * Deletes a group from the system. - * - * @param group The group to delete - */ - public void deleteGroup(ScriptNode group) - { - ParameterCheck.mandatory("Group", group); - if (group.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) - { - String groupName = (String)group.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); - authorityService.deleteAuthority(groupName); - } - } - - /** - * Create a new root level group with the specified unique name - * - * @param groupName The unique group name to create - NOTE: do not prefix with "GROUP_" - * - * @return the group reference if successful or null if failed - */ - public ScriptNode createGroup(String groupName) - { - return createGroup(null, groupName); - } - - /** - * Create a new group with the specified unique name - * - * @param parentGroup The parent group node - can be null for a root level group - * @param groupName The unique group name to create - NOTE: do not prefix with "GROUP_" - * - * @return the group reference if successful or null if failed - */ - public ScriptNode createGroup(ScriptNode parentGroup, String groupName) - { - ParameterCheck.mandatoryString("GroupName", groupName); - - ScriptNode group = null; - - String actualName = services.getAuthorityService().getName(AuthorityType.GROUP, groupName); - if (authorityService.authorityExists(actualName) == false) - { - String result = authorityService.createAuthority(AuthorityType.GROUP, groupName); - if (parentGroup != null) - { - String parentGroupName = (String)parentGroup.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); - if (parentGroupName != null) - { - authorityService.addAuthority(parentGroupName, actualName); - } - } - group = getGroup(result); - } - - return group; - } - - /** - * Add an authority (a user or group) to a group container as a new child - * - * @param parentGroup The parent container group - * @param authority The authority (user or group) to add - */ - public void addAuthority(ScriptNode parentGroup, ScriptNode authority) - { - ParameterCheck.mandatory("Authority", authority); - ParameterCheck.mandatory("ParentGroup", parentGroup); - if (parentGroup.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) - { - String parentGroupName = (String)parentGroup.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); - String authorityName; - if (authority.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) - { - authorityName = (String)authority.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); - } - else - { - authorityName = (String)authority.getProperties().get(ContentModel.PROP_USERNAME); - } - authorityService.addAuthority(parentGroupName, authorityName); - } - } - - /** - * Remove an authority (a user or group) from a group - * - * @param parentGroup The parent container group - * @param authority The authority (user or group) to remove - */ - public void removeAuthority(ScriptNode parentGroup, ScriptNode authority) - { - ParameterCheck.mandatory("Authority", authority); - ParameterCheck.mandatory("ParentGroup", parentGroup); - if (parentGroup.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) - { - String parentGroupName = (String)parentGroup.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); - String authorityName; - if (authority.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) - { - authorityName = (String)authority.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); - } - else - { - authorityName = (String)authority.getProperties().get(ContentModel.PROP_USERNAME); - } - authorityService.removeAuthority(parentGroupName, authorityName); - } - } - - /** - * Gets the members (people) of a group (including all sub-groups) - * - * @param group the group to retrieve members for - * - * @return members of the group as a JavaScript array - */ - public Scriptable getMembers(ScriptNode group) - { - ParameterCheck.mandatory("Group", group); - Object[] members = getContainedAuthorities(group, AuthorityType.USER, true); - return Context.getCurrentContext().newArray(getScope(), members); - } - - /** - * Gets the members (people) of a group - * - * @param group the group to retrieve members for - * @param recurse recurse into sub-groups - * - * @return the members of the group as a JavaScript array - */ - public Scriptable getMembers(ScriptNode group, boolean recurse) - { - ParameterCheck.mandatory("Group", group); - Object[] members = getContainedAuthorities(group, AuthorityType.USER, recurse); - return Context.getCurrentContext().newArray(getScope(), members); - } - - /** - * Gets the groups that contain the specified authority - * - * @param person the user (cm:person) to get the containing groups for - * - * @return the containing groups as a JavaScript array - */ - public Scriptable getContainerGroups(ScriptNode person) - { - ParameterCheck.mandatory("Person", person); - Object[] parents = null; - Set authorities = this.authorityService.getContainingAuthoritiesInZone( - AuthorityType.GROUP, - (String)person.getProperties().get(ContentModel.PROP_USERNAME), - AuthorityService.ZONE_APP_DEFAULT, null, 1000); - parents = new Object[authorities.size()]; - int i = 0; - for (String authority : authorities) - { - ScriptNode group = getGroup(authority); - if (group != null) - { - parents[i++] = group; - } - } - return Context.getCurrentContext().newArray(getScope(), parents); - } - - /** - * Return true if the specified user is an Administrator authority. - * - * @param person to test - * - * @return true if an admin, false otherwise - */ - public boolean isAdmin(ScriptNode person) - { - ParameterCheck.mandatory("Person", person); - return this.authorityService.isAdminAuthority((String)person.getProperties().get(ContentModel.PROP_USERNAME)); - } - - /** - * Return true if the specified user is an guest authority. - * - * @param person to test - * - * @return true if an admin, false otherwise - */ - public boolean isGuest(ScriptNode person) - { - ParameterCheck.mandatory("Person", person); - return this.authorityService.isGuestAuthority((String) person.getProperties().get(ContentModel.PROP_USERNAME)); - } - - /** - * Gets a map of capabilities (boolean assertions) for the given person. - * - * @param person - * the person - * @return the capability map - */ - public Map getCapabilities(final ScriptNode person) - { - ParameterCheck.mandatory("Person", person); - Map retVal = new ScriptableHashMap(); - retVal.putAll(this.valueDerivingMapFactory.getMap(person)); - return retVal; - } - - /** - * Return a map of the Person properties that are marked as immutable for the given user. - * This enables a script to interogate which properties are dealt with by an external - * system such as LDAP and should not be mutable in any client UI. - * - * @param username String - * - * @return ScriptableHashMap - */ - public ScriptableHashMap getImmutableProperties(String username) - { - Set props = userRegistrySynchronizer.getPersonMappedProperties(username); - ScriptableHashMap propMap = new ScriptableHashMap(); - for (QName prop : props) - { - propMap.put(prop.toString(), Boolean.TRUE); - } - return propMap; - } - - /** - * Get Contained Authorities - * - * @param container authority containers - * @param type authority type to filter by - * @param recurse recurse into sub-containers - * - * @return contained authorities - */ - private Object[] getContainedAuthorities(ScriptNode container, AuthorityType type, boolean recurse) - { - Object[] members = null; - - if (container.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) - { - String groupName = (String)container.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); - Set authorities = authorityService.getContainedAuthorities(type, groupName, !recurse); - members = new Object[authorities.size()]; - int i = 0; - for (String authority : authorities) - { - AuthorityType authorityType = AuthorityType.getAuthorityType(authority); - if (authorityType.equals(AuthorityType.GROUP)) - { - ScriptNode group = getGroup(authority); - if (group != null) - { - members[i++] = group; - } - } - else if (authorityType.equals(AuthorityType.USER)) - { - ScriptNode person = getPerson(authority); - if (person != null) - { - members[i++] = person; - } - } - } - } - - return members != null ? members : new Object[0]; - } - - public boolean getExcludeTenantFilter() - { - return false; - } - - public PermissionEvaluationMode getPermissionEvaluationMode() - { - return PermissionEvaluationMode.EAGER; - } -} +package org.alfresco.repo.jscript; + +import java.io.Serializable; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.UserNameGenerator; +import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.security.person.PersonServiceImpl; +import org.alfresco.repo.security.sync.UserRegistrySynchronizer; +import org.alfresco.repo.tenant.TenantDomainMismatchException; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.PermissionEvaluationMode; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.security.PersonService.PersonInfo; +import org.alfresco.service.cmr.usage.ContentUsageService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyMap; +import org.alfresco.util.ScriptPagingDetails; +import org.alfresco.util.ValueDerivingMapFactory; +import org.alfresco.util.ValueDerivingMapFactory.ValueDeriver; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.extensions.surf.util.I18NUtil; +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * Scripted People service for describing and executing actions against People & Groups. + * + * @author davidc + * @author kevinr + */ +public class People extends BaseScopableProcessorExtension implements InitializingBean +{ + private static Log logger = LogFactory.getLog(People.class); + + /** Repository Service Registry */ + private ServiceRegistry services; + private AuthorityDAO authorityDAO; + private AuthorityService authorityService; + private PersonService personService; + private MutableAuthenticationService authenticationService; + private ContentUsageService contentUsageService; + private UserNameGenerator usernameGenerator; + private UserRegistrySynchronizer userRegistrySynchronizer; + protected TenantService tenantService; + + private StoreRef storeRef; + private ValueDerivingMapFactory valueDerivingMapFactory; + private int numRetries = 10; + + private int defaultListMaxResults = 5000; + private boolean honorHintUseCQ = true; + + protected static final String HINT_CQ_SUFFIX = " [hint:useCQ]"; + + public void afterPropertiesSet() throws Exception + { + Map > capabilityTesters = new HashMap>(5); + capabilityTesters.put("isAdmin", new ValueDeriver() + { + public Boolean deriveValue(ScriptNode source) + { + return isAdmin(source); + } + }); + capabilityTesters.put("isGuest", new ValueDeriver() + { + public Boolean deriveValue(ScriptNode source) + { + return isGuest(source); + } + }); + capabilityTesters.put("isMutable", new ValueDeriver() + { + public Boolean deriveValue(ScriptNode source) + { + // Check whether the account is mutable according to the authentication service + String sourceUser = (String) source.getProperties().get(ContentModel.PROP_USERNAME); + if (!authenticationService.isAuthenticationMutable(sourceUser)) + { + return false; + } + // Only allow non-admin users to mutate their own accounts + String currentUser = authenticationService.getCurrentUserName(); + if (currentUser.equals(sourceUser) || authorityService.isAdminAuthority(currentUser)) + { + return true; + } + return false; + } + }); + this.valueDerivingMapFactory = new ValueDerivingMapFactory(capabilityTesters); + } + + /** + * Set the default store reference + * + * @param storeRef the default store reference + */ + public void setStoreUrl(String storeRef) + { + // ensure this is not set again by a script instance + if (this.storeRef != null) + { + throw new IllegalStateException("Default store URL can only be set once."); + } + this.storeRef = new StoreRef(storeRef); + } + + /** + * Sets the authentication service. + * + * @param authenticationService + * the authentication service + */ + public void setAuthenticationService(MutableAuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + /** + * Set the service registry + * + * @param serviceRegistry the service registry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.services = serviceRegistry; + } + + /** + * Set the authority DAO + * + * @param authorityDAO authority dao + */ + public void setAuthorityDAO(AuthorityDAO authorityDAO) + { + this.authorityDAO = authorityDAO; + } + + /** + * Set the authority service + * + * @param authorityService The authorityService to set. + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * Set the person service + * + * @param personService The personService to set. + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * @param contentUsageService the ContentUsageService to set + */ + public void setContentUsageService(ContentUsageService contentUsageService) + { + this.contentUsageService = contentUsageService; + } + + /** + * @param tenantService the tenantService to set + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /** + * Set the user name generator service + * + * @param userNameGenerator the user name generator + */ + public void setUserNameGenerator(UserNameGenerator userNameGenerator) + { + this.usernameGenerator = userNameGenerator; + } + + /** + * Set the UserRegistrySynchronizer + * + * @param userRegistrySynchronizer UserRegistrySynchronizer + */ + public void setUserRegistrySynchronizer(UserRegistrySynchronizer userRegistrySynchronizer) + { + this.userRegistrySynchronizer = userRegistrySynchronizer; + } + + public void setDefaultListMaxResults(int defaultListMaxResults) + { + this.defaultListMaxResults = defaultListMaxResults; + } + + /** + * Allows customers to choose to use Solr or Lucene rather than a canned query in + * {@link #getPeople(String, int, String, boolean)} when + * {@code " [hint:useCQ]"} is appended to the search term (currently Share's + * User Console does this). The down side is that new users may not appear as they + * will not have been indexed. This is similar to what happened in 4.1.1 prior to + * MNT-7548 (4.1.2 and 4.1.1.1). The down side of using a canned query at the moment + * is that there is a bug, so that it is impossible to search for names such as + * {@code "Carlos Allende García"} where the first or last names may contain spaces. + * See MNT-9719 for more details. The alfresco global property + * {@code people.search.honor.hint.useCQ} is used to set this value (default is true). + */ + public void setHonorHintUseCQ(boolean honorHintUseCQ) + { + this.honorHintUseCQ = honorHintUseCQ; + } + + /** + * Delete a Person with the given username + * + * @param username the username of the person to delete + */ + public void deletePerson(String username) + { + personService.deletePerson(username); + } + + /** + * Create a Person with an optionally generated user name. + * This version doesn't notify them. + * + * @param userName userName or null for a generated user name + * @param firstName firstName + * @param lastName lastName + * @param emailAddress emailAddress + * @param password if not null creates a new authenticator with the given password. + * @param setAccountEnabled + * set to 'true' to create enabled user account, or 'false' to + * create disabled user account for created person. + * @return the person node (type cm:person) created or null if the person + * could not be created + */ + public ScriptNode createPerson(String userName, String firstName, String lastName, String emailAddress, String password, boolean setAccountEnabled) + { + return createPerson(userName, firstName, lastName, emailAddress, password, setAccountEnabled, false); + } + + /** + * Create a Person with an optionally generated user name + * + * @param userName userName or null for a generated user name + * @param firstName firstName + * @param lastName lastName + * @param emailAddress emailAddress + * @param password if not null creates a new authenticator with the given password. + * @param setAccountEnabled + * set to 'true' to create enabled user account, or 'false' to + * create disabled user account for created person. + * @param notifyByEmail + * set to 'true' to have the new user emailed to let them know + * their account details. Only applies if a username and + * password were supplied. + * @return the person node (type cm:person) created or null if the person + * could not be created + */ + public ScriptNode createPerson(String userName, String firstName, String lastName, String emailAddress, + String password, boolean setAccountEnabled, boolean notifyByEmail) + { + ParameterCheck.mandatory("firstName", firstName); + ParameterCheck.mandatory("emailAddress", emailAddress); + + ScriptNode person = null; + + // generate user name if not supplied + if (userName == null) + { + for (int i=0; i < numRetries; i++) + { + userName = usernameGenerator.generateUserName(firstName, lastName, emailAddress, i); + + // create person if user name does not already exist + if (!personService.personExists(userName)) + { + break; + } + } + } + + if (userName != null) + { + try + { + userName = PersonServiceImpl.updateUsernameForTenancy(userName, tenantService); + } + catch (TenantDomainMismatchException re) + { + throw new AuthenticationException("User must belong to same domain as admin: " + re.getTenantA()); + } + + person = createPerson(userName, firstName, lastName, emailAddress); + + if (person != null && password != null) + { + // create account for person with the userName and password + authenticationService.createAuthentication(userName, password.toCharArray()); + authenticationService.setAuthenticationEnabled(userName, setAccountEnabled); + + person.save(); + + if(notifyByEmail) + { + personService.notifyPerson(userName, password); + } + } + } + + return person; + } + + /** + * Enable user account. Can only be called by an Admin authority. + * + * @param userName user name for which to enable user account + */ + public void enableAccount(String userName) + { + if (this.authorityService.isAdminAuthority(AuthenticationUtil.getFullyAuthenticatedUser())) + { + this.authenticationService.setAuthenticationEnabled(userName, true); + } + } + + /** + * Disable user account. Can only be called by an Admin authority. + * + * @param userName user name for which to disable user account + */ + public void disableAccount(String userName) + { + if (this.authorityService.isAdminAuthority(AuthenticationUtil.getFullyAuthenticatedUser())) + { + this.authenticationService.setAuthenticationEnabled(userName, false); + } + } + + /** + * Return true if the specified user account is enabled. + * + * @param userName user name to test account + * + * @return true if account enabled, false if disabled + */ + public boolean isAccountEnabled(String userName) + { + return this.authenticationService.getAuthenticationEnabled(userName); + } + + /** + * Change the password for the currently logged in user. + * Old password must be supplied. + * + * @param oldPassword Old user password + * @param newPassword New user password + */ + public void changePassword(String oldPassword, String newPassword) + { + ParameterCheck.mandatoryString("oldPassword", oldPassword); + ParameterCheck.mandatoryString("newPassword", newPassword); + + this.services.getAuthenticationService().updateAuthentication( + AuthenticationUtil.getFullyAuthenticatedUser(), oldPassword.toCharArray(), newPassword.toCharArray()); + } + + /** + * Set a password for the given user. Note that only an administrator + * can perform this action, otherwise it will be ignored. + * + * @param userName Username to change password for + * @param password Password to set + */ + public void setPassword(String userName, String password) + { + ParameterCheck.mandatoryString("userName", userName); + ParameterCheck.mandatoryString("password", password); + + MutableAuthenticationService authService = this.services.getAuthenticationService(); + if (this.authorityService.hasAdminAuthority() && (userName.equalsIgnoreCase(authService.getCurrentUserName()) == false)) + { + authService.setAuthentication(userName, password.toCharArray()); + } + } + + /** + * Create a Person with the given user name + * + * @param userName the user name of the person to create + * @return the person node (type cm:person) created or null if the user name already exists + */ + public ScriptNode createPerson(String userName) + { + ParameterCheck.mandatoryString("userName", userName); + + ScriptNode person = null; + + PropertyMap properties = new PropertyMap(); + properties.put(ContentModel.PROP_USERNAME, userName); + + if (!personService.personExists(userName)) + { + NodeRef personRef = personService.createPerson(properties); + person = new ScriptNode(personRef, services, getScope()); + } + + return person; + } + + /** + * Create a Person with the given user name, firstName, lastName and emailAddress + * + * @param userName the user name of the person to create + * @return the person node (type cm:person) created or null if the user name already exists + */ + public ScriptNode createPerson(String userName, String firstName, String lastName, String emailAddress) + { + ParameterCheck.mandatoryString("userName", userName); + ParameterCheck.mandatoryString("firstName", firstName); + ParameterCheck.mandatoryString("emailAddress", emailAddress); + + ScriptNode person = null; + + PropertyMap properties = new PropertyMap(); + properties.put(ContentModel.PROP_USERNAME, userName); + properties.put(ContentModel.PROP_FIRSTNAME, firstName); + properties.put(ContentModel.PROP_LASTNAME, lastName); + properties.put(ContentModel.PROP_EMAIL, emailAddress); + + if (!personService.personExists(userName)) + { + NodeRef personRef = personService.createPerson(properties); + person = new ScriptNode(personRef, services, getScope()); + } + + return person; + } + + /** + * Set the content quota in bytes for a person. + * Only the admin authority can set this value. + * + * @param person Person to set quota against. + * @param quota As a string, in bytes, a value of "-1" means no quota is set + */ + public void setQuota(ScriptNode person, String quota) + { + if (this.authorityService.isAdminAuthority(AuthenticationUtil.getFullyAuthenticatedUser())) + { + this.contentUsageService.setUserQuota((String)person.getProperties().get(ContentModel.PROP_USERNAME), Long.parseLong(quota)); + } + } + + /** + * Get the collection of people stored in the repository. + * An optional filter query may be provided by which to filter the people collection. + * Space separate the query terms i.e. "john bob" will find all users who's first or + * second names contain the strings "john" or "bob". + * + * @param filter filter query string by which to filter the collection of people. + * If
null
then all people stored in the repository are returned + * + * @deprecated recated see getPeople(filter, maxResults) + * + * @return people collection as a JavaScript array + */ + public Scriptable getPeople(String filter) + { + return getPeople(filter, 0); + } + + /** + * Get the collection of people stored in the repository. + * An optional filter query may be provided by which to filter the people collection. + * Space separate the query terms i.e. "john bob" will find all users who's first or + * second names contain the strings "john" or "bob". + * + * @param filter filter query string by which to filter the collection of people. + * If
null
then all people stored in the repository are returned + * @param maxResults maximum results to return or all if <= 0 + * + * @return people collection as a JavaScript array + */ + public Scriptable getPeople(String filter, int maxResults) + { + return getPeople(filter, maxResults, null, true); + } + + /** + * Get the collection of people stored in the repository. + * An optional filter query may be provided by which to filter the people collection. + * Space separate the query terms i.e. "john bob" will find all users who's first or + * second names contain the strings "john" or "bob". + * Method supports sorting by specifying sortBy and sortAsc params. + * + * @param filter filter query string by which to filter the collection of people. + * If
null
then all people stored in the repository are returned + * @param maxResults maximum results to return or all if <= 0 + * @param sortBy field for sorting + * @param sortAsc sort ascending or not + * + * @return people collection as a JavaScript array + */ + public Scriptable getPeople(String filter, int maxResults, String sortBy, boolean sortAsc) + { + return getPeoplePaging(filter, new ScriptPagingDetails(maxResults, 0), sortBy, Boolean.valueOf(sortAsc)); + } + + public Scriptable getPeoplePaging(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) + { + List persons = getPeopleImpl(filter, pagingRequest, sortBy, sortAsc); + + Object[] peopleRefs = new Object[persons.size()]; + for (int i = 0; i < peopleRefs.length; i++) + { + peopleRefs[i] = persons.get(i).getNodeRef(); + } + + return Context.getCurrentContext().newArray(getScope(), peopleRefs); + } + + protected List getPeopleImpl(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) + { + ParameterCheck.mandatory("pagingRequest", pagingRequest); + + boolean useCQ = false; + if (filter != null) + { + if (filter.endsWith(HINT_CQ_SUFFIX)) + { + useCQ = honorHintUseCQ; + filter = filter.substring(0, filter.length()-HINT_CQ_SUFFIX.length()); + } + } + else + { + filter = "*"; + } + + List persons = null; + + int maxResults = pagingRequest.getMaxItems(); + if ((maxResults <= 0) || (maxResults > defaultListMaxResults)) + { + // remove open-ended query (eg cutoff at default/configurable max, eg. 5000 people) + maxResults = defaultListMaxResults; + pagingRequest.setMaxItems(maxResults); + } + + // In order to use a SOLR/Lucene search, we must have a non-empty filter string - see ALF-18876 + if ((filter == null || filter.trim().isEmpty()) || useCQ) + { + persons = getPeopleImplDB(filter, pagingRequest, sortBy, sortAsc); + } + else + { + filter = filter.trim(); + + String term = filter.replace("\"", ""); + String[] tokens = term.split("(? personRefs = getPeopleImplSearch(term, tokens, pagingRequest, sortBy, sortAsc); + + if (personRefs != null) + { + persons = new ArrayList(personRefs.size()); + for (NodeRef personRef : personRefs) + { + persons.add(personService.getPerson(personRef)); + } + } + } + catch (Throwable err) + { + if (useCQ) + { + // search unavailable and/or parser exception - try CQ instead + // simple non-FTS filter: firstname or lastname or username starting with term (ignoring case) + persons = getPeopleImplDB(filter, pagingRequest, sortBy, sortAsc); + } + } + } + + return (persons != null ? persons : new ArrayList(0)); + } + + // canned query + protected List getPeopleImplDB(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) + { + List filterProps = null; + + if ((filter != null) && (filter.length() > 0)) + { + filter = filter.trim(); + if (! filter.equals("*")) + { + filter = filter.replace("\\", "").replace("\"", ""); + + // simple non-FTS filter: firstname or lastname or username starting with term (ignoring case) + + filterProps = new ArrayList(3); + filterProps.add(ContentModel.PROP_FIRSTNAME); + filterProps.add(ContentModel.PROP_LASTNAME); + filterProps.add(ContentModel.PROP_USERNAME); + } + } + + // Build the sorting. The user controls the primary sort, we supply + // additional ones automatically + List> sort = new ArrayList>(); + if ("lastName".equals(sortBy)) + { + sort.add(new Pair(ContentModel.PROP_LASTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_FIRSTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_USERNAME, sortAsc)); + } + else if ("firstName".equals(sortBy)) + { + sort.add(new Pair(ContentModel.PROP_FIRSTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_LASTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_USERNAME, sortAsc)); + } + else + { + sort.add(new Pair(ContentModel.PROP_USERNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_FIRSTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_LASTNAME, sortAsc)); + } + + return personService.getPeople(filter, filterProps, sort, pagingRequest).getPage(); + } + + // search query + protected List getPeopleImplSearch(String term, String[] tokens, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) throws Throwable + { + List personRefs = null; + + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); + + int propIndex = term.indexOf(':'); + + int maxResults = pagingRequest.getMaxItems(); + int skipCount = pagingRequest.getSkipCount(); + + SearchParameters params = new SearchParameters(); + params.addQueryTemplate("_PERSON", "|%firstName OR |%lastName OR |%userName"); + params.setDefaultFieldName("_PERSON"); + params.setExcludeTenantFilter(getExcludeTenantFilter()); + params.setPermissionEvaluation(getPermissionEvaluationMode()); + + StringBuilder query = new StringBuilder(256); + + query.append("TYPE:\"").append(ContentModel.TYPE_PERSON).append("\" AND ("); + + if (tokens.length == 1) + { + // single word with no field will go against _PERSON and expand + + // fts-alfresco property search i.e. location:"maidenhead" + query.append(term.substring(0, propIndex + 1)).append('"'); + if (propIndex < 0) + { + query.append('*'); + } + query.append(term.substring(propIndex + 1)); + if (propIndex > 0) + { + query.append('"'); + } + else + { + query.append("*\""); + } + } + else + { + // scan for non-fts-alfresco property search tokens + int nonFtsTokens = 0; + for (String token : tokens) + { + if (token.indexOf(':') == -1) nonFtsTokens++; + } + tokens = term.split("(? 0) + { + query.append("firstName:"); + query.append(multiPartNames); + query.append(" OR lastName:"); + query.append(multiPartNames); + } + } + query.append(")"); + + // define the search parameters + params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + params.addStore(this.storeRef); + params.setQuery(query.toString()); + + if (logger.isDebugEnabled()) + { + if ((sortBy != null) && (! sortBy.isEmpty())) + { + logger.debug("getPeopleImplSearch: ignoring sortBy ("+sortBy+")- not yet supported by model for search"); + } + } + + /* not yet supported (default property index tokenisation mode = true) + if ("lastName".equals(sortBy)) + { + params.addSort("@{http://www.alfresco.org/model/content/1.0}lastName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}firstName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); + } + else if ("firstName".equals(sortBy)) + { + params.addSort("@{http://www.alfresco.org/model/content/1.0}firstName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}lastName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); + } + else + { + params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}firstName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); + } + */ + + if (maxResults > 0) + { + params.setLimitBy(LimitBy.FINAL_SIZE); + params.setLimit(maxResults); + } + + if (skipCount > 0) + { + params.setSkipCount(skipCount); + } + + ResultSet results = null; + try + { + results = services.getSearchService().query(params); + + personRefs = getSortedPeopleObjects(results.getNodeRefs(), sortBy, sortAsc); + + if (start != null) + { + logger.debug("getPeople: search - "+personRefs.size()+" items (in "+(System.currentTimeMillis()-start)+" msecs)"); + } + } + catch (Throwable err) + { + if (logger.isDebugEnabled()) + { + logger.debug("Failed to execute people search: " + query.toString(), err); + } + + throw err; + } + finally + { + if (results != null) + { + results.close(); + } + } + + return personRefs; + } + + private List getSortedPeopleObjects(List peopleRefs, final String sortBy, Boolean sortAsc) + { + if (sortBy == null) + { + return peopleRefs; + } + + //make copy of peopleRefs because it can be unmodifiable list. + List sortedPeopleRefs = new ArrayList(peopleRefs); + final Collator col = Collator.getInstance(I18NUtil.getLocale()); + final NodeService nodeService = services.getNodeService(); + final int orderMultiplicator = ((sortAsc == null) || sortAsc) ? 1 : -1; + Collections.sort(sortedPeopleRefs, new Comparator() + { + @Override + public int compare(NodeRef n1, NodeRef n2) + { + Serializable p1 = getProperty(n1); + Serializable p2 = getProperty(n2); + + if ((p1 instanceof Long) && (p2 instanceof Long)) + { + return Long.compare((Long)p1, (Long)p2) * orderMultiplicator; + } + + return col.compare(p1.toString(), p2) * orderMultiplicator; + } + + public Serializable getProperty(NodeRef nodeRef) + { + Serializable result; + + if ("fullName".equalsIgnoreCase(sortBy)) + { + String firstName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_FIRSTNAME); + String lastName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_LASTNAME); + String fullName = firstName; + if (lastName != null && lastName.length() > 0) + { + fullName = fullName + " " + lastName; + } + + result = fullName; + } + else if ("jobtitle".equalsIgnoreCase(sortBy)) + { + result = nodeService.getProperty(nodeRef, ContentModel.PROP_JOBTITLE); + } + else if ("email".equalsIgnoreCase(sortBy)) + { + result = nodeService.getProperty(nodeRef, ContentModel.PROP_EMAIL); + } + else if ("usage".equalsIgnoreCase(sortBy)) + { + result = nodeService.getProperty(nodeRef, ContentModel.PROP_SIZE_CURRENT); + } + else if ("quota".equalsIgnoreCase(sortBy)) + { + result = nodeService.getProperty(nodeRef, ContentModel.PROP_SIZE_QUOTA); + } + else + { + // Default + result = nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); + } + + if (result == null) + { + result = ""; + } + + return result; + } + + }); + + return sortedPeopleRefs; + } + + /** + * Gets the Person given the username + * + * @param username the username of the person to get + * @return the person node (type cm:person) or null if no such person exists + */ + public ScriptNode getPerson(final String username) + { + NodeRef personRef = null; + + ParameterCheck.mandatory("Username", username); + try + { + personRef = personService.getPersonOrNull(username); + } + catch(AccessDeniedException e) + { + // ok, just return null to indicate not found + } + + return personRef == null ? null : new ScriptNode(personRef, services, getScope()); + } + + /** + * Faster helper when the script just wants to build the Full name for a person. + * Avoids complete getProperties() retrieval for a cm:person. + * + * @param username the username of the person to get Full name for + * @return full name for a person or null if the user does not exist in the system. + */ + public String getPersonFullName(final String username) + { + String name = null; + ParameterCheck.mandatoryString("Username", username); + final NodeRef personRef = personService.getPersonOrNull(username); + if (personRef != null) + { + final NodeService nodeService = services.getNodeService(); + final String firstName = (String)nodeService.getProperty(personRef, ContentModel.PROP_FIRSTNAME); + final String lastName = (String)nodeService.getProperty(personRef, ContentModel.PROP_LASTNAME); + name = (firstName != null ? firstName + " " : "") + (lastName != null ? lastName : ""); + } + return name; + } + + /** + * Gets the Group given the group name + * + * @param groupName name of group to get + * @return the group node (type usr:authorityContainer) or null if no such group exists + */ + public ScriptNode getGroup(String groupName) + { + ParameterCheck.mandatoryString("GroupName", groupName); + ScriptNode group = null; + NodeRef groupRef = authorityDAO.getAuthorityNodeRefOrNull(groupName); + if (groupRef != null) + { + group = new ScriptNode(groupRef, services, getScope()); + } + return group; + } + + /** + * Deletes a group from the system. + * + * @param group The group to delete + */ + public void deleteGroup(ScriptNode group) + { + ParameterCheck.mandatory("Group", group); + if (group.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + String groupName = (String)group.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); + authorityService.deleteAuthority(groupName); + } + } + + /** + * Create a new root level group with the specified unique name + * + * @param groupName The unique group name to create - NOTE: do not prefix with "GROUP_" + * + * @return the group reference if successful or null if failed + */ + public ScriptNode createGroup(String groupName) + { + return createGroup(null, groupName); + } + + /** + * Create a new group with the specified unique name + * + * @param parentGroup The parent group node - can be null for a root level group + * @param groupName The unique group name to create - NOTE: do not prefix with "GROUP_" + * + * @return the group reference if successful or null if failed + */ + public ScriptNode createGroup(ScriptNode parentGroup, String groupName) + { + ParameterCheck.mandatoryString("GroupName", groupName); + + ScriptNode group = null; + + String actualName = services.getAuthorityService().getName(AuthorityType.GROUP, groupName); + if (authorityService.authorityExists(actualName) == false) + { + String result = authorityService.createAuthority(AuthorityType.GROUP, groupName); + if (parentGroup != null) + { + String parentGroupName = (String)parentGroup.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); + if (parentGroupName != null) + { + authorityService.addAuthority(parentGroupName, actualName); + } + } + group = getGroup(result); + } + + return group; + } + + /** + * Add an authority (a user or group) to a group container as a new child + * + * @param parentGroup The parent container group + * @param authority The authority (user or group) to add + */ + public void addAuthority(ScriptNode parentGroup, ScriptNode authority) + { + ParameterCheck.mandatory("Authority", authority); + ParameterCheck.mandatory("ParentGroup", parentGroup); + if (parentGroup.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + String parentGroupName = (String)parentGroup.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); + String authorityName; + if (authority.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + authorityName = (String)authority.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); + } + else + { + authorityName = (String)authority.getProperties().get(ContentModel.PROP_USERNAME); + } + authorityService.addAuthority(parentGroupName, authorityName); + } + } + + /** + * Remove an authority (a user or group) from a group + * + * @param parentGroup The parent container group + * @param authority The authority (user or group) to remove + */ + public void removeAuthority(ScriptNode parentGroup, ScriptNode authority) + { + ParameterCheck.mandatory("Authority", authority); + ParameterCheck.mandatory("ParentGroup", parentGroup); + if (parentGroup.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + String parentGroupName = (String)parentGroup.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); + String authorityName; + if (authority.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + authorityName = (String)authority.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); + } + else + { + authorityName = (String)authority.getProperties().get(ContentModel.PROP_USERNAME); + } + authorityService.removeAuthority(parentGroupName, authorityName); + } + } + + /** + * Gets the members (people) of a group (including all sub-groups) + * + * @param group the group to retrieve members for + * + * @return members of the group as a JavaScript array + */ + public Scriptable getMembers(ScriptNode group) + { + ParameterCheck.mandatory("Group", group); + Object[] members = getContainedAuthorities(group, AuthorityType.USER, true); + return Context.getCurrentContext().newArray(getScope(), members); + } + + /** + * Gets the members (people) of a group + * + * @param group the group to retrieve members for + * @param recurse recurse into sub-groups + * + * @return the members of the group as a JavaScript array + */ + public Scriptable getMembers(ScriptNode group, boolean recurse) + { + ParameterCheck.mandatory("Group", group); + Object[] members = getContainedAuthorities(group, AuthorityType.USER, recurse); + return Context.getCurrentContext().newArray(getScope(), members); + } + + /** + * Gets the groups that contain the specified authority + * + * @param person the user (cm:person) to get the containing groups for + * + * @return the containing groups as a JavaScript array + */ + public Scriptable getContainerGroups(ScriptNode person) + { + ParameterCheck.mandatory("Person", person); + Object[] parents = null; + Set authorities = this.authorityService.getContainingAuthoritiesInZone( + AuthorityType.GROUP, + (String)person.getProperties().get(ContentModel.PROP_USERNAME), + AuthorityService.ZONE_APP_DEFAULT, null, 1000); + parents = new Object[authorities.size()]; + int i = 0; + for (String authority : authorities) + { + ScriptNode group = getGroup(authority); + if (group != null) + { + parents[i++] = group; + } + } + return Context.getCurrentContext().newArray(getScope(), parents); + } + + /** + * Return true if the specified user is an Administrator authority. + * + * @param person to test + * + * @return true if an admin, false otherwise + */ + public boolean isAdmin(ScriptNode person) + { + ParameterCheck.mandatory("Person", person); + return this.authorityService.isAdminAuthority((String)person.getProperties().get(ContentModel.PROP_USERNAME)); + } + + /** + * Return true if the specified user is an guest authority. + * + * @param person to test + * + * @return true if an admin, false otherwise + */ + public boolean isGuest(ScriptNode person) + { + ParameterCheck.mandatory("Person", person); + return this.authorityService.isGuestAuthority((String) person.getProperties().get(ContentModel.PROP_USERNAME)); + } + + /** + * Gets a map of capabilities (boolean assertions) for the given person. + * + * @param person + * the person + * @return the capability map + */ + public Map getCapabilities(final ScriptNode person) + { + ParameterCheck.mandatory("Person", person); + Map retVal = new ScriptableHashMap(); + retVal.putAll(this.valueDerivingMapFactory.getMap(person)); + return retVal; + } + + /** + * Return a map of the Person properties that are marked as immutable for the given user. + * This enables a script to interogate which properties are dealt with by an external + * system such as LDAP and should not be mutable in any client UI. + * + * @param username String + * + * @return ScriptableHashMap + */ + public ScriptableHashMap getImmutableProperties(String username) + { + Set props = userRegistrySynchronizer.getPersonMappedProperties(username); + ScriptableHashMap propMap = new ScriptableHashMap(); + for (QName prop : props) + { + propMap.put(prop.toString(), Boolean.TRUE); + } + return propMap; + } + + /** + * Get Contained Authorities + * + * @param container authority containers + * @param type authority type to filter by + * @param recurse recurse into sub-containers + * + * @return contained authorities + */ + private Object[] getContainedAuthorities(ScriptNode container, AuthorityType type, boolean recurse) + { + Object[] members = null; + + if (container.getQNameType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + String groupName = (String)container.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); + Set authorities = authorityService.getContainedAuthorities(type, groupName, !recurse); + members = new Object[authorities.size()]; + int i = 0; + for (String authority : authorities) + { + AuthorityType authorityType = AuthorityType.getAuthorityType(authority); + if (authorityType.equals(AuthorityType.GROUP)) + { + ScriptNode group = getGroup(authority); + if (group != null) + { + members[i++] = group; + } + } + else if (authorityType.equals(AuthorityType.USER)) + { + ScriptNode person = getPerson(authority); + if (person != null) + { + members[i++] = person; + } + } + } + } + + return members != null ? members : new Object[0]; + } + + public boolean getExcludeTenantFilter() + { + return false; + } + + public PermissionEvaluationMode getPermissionEvaluationMode() + { + return PermissionEvaluationMode.EAGER; + } +} diff --git a/source/java/org/alfresco/repo/jscript/Presence.java b/source/java/org/alfresco/repo/jscript/Presence.java index e2eadcc127..a5c1158e8c 100644 --- a/source/java/org/alfresco/repo/jscript/Presence.java +++ b/source/java/org/alfresco/repo/jscript/Presence.java @@ -1,61 +1,61 @@ -package org.alfresco.repo.jscript; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.ServiceRegistry; -import org.springframework.extensions.surf.util.ParameterCheck; - -/** - * Scripted Presence service for determining online status of People. - * - * @author Mike Hatfield - */ - -public final class Presence extends BaseScopableProcessorExtension -{ - /** Repository Service Registry */ - private ServiceRegistry services; - - /** - * Set the service registry - * - * @param serviceRegistry the service registry - */ - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.services = serviceRegistry; - } - - /** - * Gets whether the Person has configured Presence parameters - * - * @param person the person to query - * - * @return true if this person is configured for presence - */ - public boolean hasPresence(ScriptNode person) - { - ParameterCheck.mandatory("Person", person); - String presenceProvider = (String)person.getProperties().get(ContentModel.PROP_PRESENCEPROVIDER); - String presenceUsername = (String)person.getProperties().get(ContentModel.PROP_PRESENCEUSERNAME); - - return (!"".equals((presenceProvider)) && (!"".equals(presenceUsername))); - } - - /** - * Query current online status of given person - * - * @param person the person to query - * - * @return string indicating online presence status - */ - public String getDetails(ScriptNode person) - { - ParameterCheck.mandatory("Person", person); - String presenceProvider = (String)person.getProperties().get(ContentModel.PROP_PRESENCEPROVIDER); - String presenceUsername = (String)person.getProperties().get(ContentModel.PROP_PRESENCEUSERNAME); - String detail = presenceProvider + "|" + presenceUsername; - - return detail; - } - -} +package org.alfresco.repo.jscript; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * Scripted Presence service for determining online status of People. + * + * @author Mike Hatfield + */ + +public final class Presence extends BaseScopableProcessorExtension +{ + /** Repository Service Registry */ + private ServiceRegistry services; + + /** + * Set the service registry + * + * @param serviceRegistry the service registry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.services = serviceRegistry; + } + + /** + * Gets whether the Person has configured Presence parameters + * + * @param person the person to query + * + * @return true if this person is configured for presence + */ + public boolean hasPresence(ScriptNode person) + { + ParameterCheck.mandatory("Person", person); + String presenceProvider = (String)person.getProperties().get(ContentModel.PROP_PRESENCEPROVIDER); + String presenceUsername = (String)person.getProperties().get(ContentModel.PROP_PRESENCEUSERNAME); + + return (!"".equals((presenceProvider)) && (!"".equals(presenceUsername))); + } + + /** + * Query current online status of given person + * + * @param person the person to query + * + * @return string indicating online presence status + */ + public String getDetails(ScriptNode person) + { + ParameterCheck.mandatory("Person", person); + String presenceProvider = (String)person.getProperties().get(ContentModel.PROP_PRESENCEPROVIDER); + String presenceUsername = (String)person.getProperties().get(ContentModel.PROP_PRESENCEUSERNAME); + String detail = presenceProvider + "|" + presenceUsername; + + return detail; + } + +} diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptProcessor.java b/source/java/org/alfresco/repo/jscript/RhinoScriptProcessor.java index 22dac43b18..12951d2abd 100644 --- a/source/java/org/alfresco/repo/jscript/RhinoScriptProcessor.java +++ b/source/java/org/alfresco/repo/jscript/RhinoScriptProcessor.java @@ -1,644 +1,644 @@ -package org.alfresco.repo.jscript; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.concurrent.ConcurrentHashMap; - -import net.sf.acegisecurity.AuthenticationException; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.processor.ProcessorExtension; -import org.alfresco.repo.processor.BaseProcessor; -import org.alfresco.scripts.ScriptException; -import org.alfresco.scripts.ScriptResourceHelper; -import org.alfresco.scripts.ScriptResourceLoader; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.ScriptLocation; -import org.alfresco.service.cmr.repository.ScriptProcessor; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.ImporterTopLevel; -import org.mozilla.javascript.Script; -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.ScriptableObject; -import org.mozilla.javascript.WrapFactory; -import org.mozilla.javascript.WrappedException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.FileCopyUtils; - -/** - * Implementation of the ScriptProcessor using the Rhino JavaScript library. - * - * @author Kevin Roast - */ -public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcessor, ScriptResourceLoader, InitializingBean -{ - private static final Log logger = LogFactory.getLog(RhinoScriptProcessor.class); - private static final Log callLogger = LogFactory.getLog(RhinoScriptProcessor.class.getName()+".calls"); - - private static final String PATH_CLASSPATH = "classpath:"; - - /** Wrap Factory */ - private static final WrapFactory wrapFactory = new RhinoWrapFactory(); - - /** Base Value Converter */ - private final ValueConverter valueConverter = new ValueConverter(); - - /** Store into which to resolve cm:name based script paths */ - private StoreRef storeRef; - - /** Store root path to resolve cm:name based scripts path from */ - private String storePath; - - /** Pre initialized secure scope object. */ - private Scriptable secureScope; - - /** Pre initialized non secure scope object. */ - private Scriptable nonSecureScope; - - /** Flag to enable or disable runtime script compliation */ - private boolean compile = true; - - /** Flag to enable the sharing of sealed root scopes between scripts executions */ - private boolean shareSealedScopes = true; - - /** Cache of runtime compiled script instances */ - private final Map scriptCache = new ConcurrentHashMap(256); - - - /** - * Set the default store reference - * - * @param storeRef The default store reference - */ - public void setStoreUrl(String storeRef) - { - this.storeRef = new StoreRef(storeRef); - } - - /** - * @param storePath The store path to set. - */ - public void setStorePath(String storePath) - { - this.storePath = storePath; - } - - /** - * @param compile the compile flag to set - */ - public void setCompile(boolean compile) - { - this.compile = compile; - } - - /** - * @param shareSealedScopes true to allow sharing of sealed scopes between script executions - set to - * false to disable this feature and ensure that a new scope is created for each executed script. - */ - public void setShareSealedScopes(boolean shareSealedScopes) - { - this.shareSealedScopes = shareSealedScopes; - } - - /** - * @see org.alfresco.service.cmr.repository.ScriptProcessor#reset() - */ - public void reset() - { - this.scriptCache.clear(); - } - - /** - * @see org.alfresco.service.cmr.repository.ScriptProcessor#execute(org.alfresco.service.cmr.repository.ScriptLocation, java.util.Map) - */ - public Object execute(ScriptLocation location, Map model) - { - try - { - // test the cache for a pre-compiled script matching our path - Script script = null; - String path = location.getPath(); - if (this.compile && location.isCachable()) - { - script = this.scriptCache.get(path); - } - if (script == null) - { - if (logger.isDebugEnabled()) - logger.debug("Resolving and compiling script path: " + path); - - // retrieve script content and resolve imports - ByteArrayOutputStream os = new ByteArrayOutputStream(); - FileCopyUtils.copy(location.getInputStream(), os); // both streams are closed - byte[] bytes = os.toByteArray(); - String source = new String(bytes, "UTF-8"); - source = resolveScriptImports(new String(bytes)); - - // compile the script and cache the result - Context cx = Context.enter(); - try - { - script = cx.compileString(source, location.toString(), 1, null); - - // We do not worry about more than one user thread compiling the same script. - // If more than one request thread compiles the same script and adds it to the - // cache that does not matter - the results will be the same. Therefore we - // rely on the ConcurrentHashMap impl to deal both with ensuring the safety of the - // underlying structure with asynchronous get/put operations and for fast - // multi-threaded access to the common cache. - if (this.compile && location.isCachable()) - { - this.scriptCache.put(path, script); - } - } - finally - { - Context.exit(); - } - } - - String debugScriptName = null; - if (callLogger.isDebugEnabled()) - { - int i = path.lastIndexOf('/'); - debugScriptName = (i != -1) ? path.substring(i+1) : path; - } - return executeScriptImpl(script, model, location.isSecure(), debugScriptName); - } - catch (Throwable err) - { - throw new ScriptException("Failed to execute script '" + location.toString() + "': " + err.getMessage(), err); - } - } - - /** - * @see org.alfresco.service.cmr.repository.ScriptProcessor#execute(java.lang.String, java.util.Map) - */ - public Object execute(String location, Map model) - { - return execute(new ClasspathScriptLocation(location), model); - } - - /** - * @see org.alfresco.service.cmr.repository.ScriptProcessor#execute(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, java.util.Map) - */ - public Object execute(NodeRef nodeRef, QName contentProp, Map model) - { - try - { - if (this.services.getNodeService().exists(nodeRef) == false) - { - throw new AlfrescoRuntimeException("Script Node does not exist: " + nodeRef); - } - - if (contentProp == null) - { - contentProp = ContentModel.PROP_CONTENT; - } - ContentReader cr = this.services.getContentService().getReader(nodeRef, contentProp); - if (cr == null || cr.exists() == false) - { - throw new AlfrescoRuntimeException("Script Node content not found: " + nodeRef); - } - - // compile the script based on the node content - Script script; - Context cx = Context.enter(); - try - { - script = cx.compileString(resolveScriptImports(cr.getContentString()), nodeRef.toString(), 1, null); - } - finally - { - Context.exit(); - } - - return executeScriptImpl(script, model, false, nodeRef.toString()); - } - catch (Throwable err) - { - throw new ScriptException("Failed to execute script '" + nodeRef.toString() + "': " + err.getMessage(), err); - } - } - - /** - * @see org.alfresco.service.cmr.repository.ScriptProcessor#executeString(java.lang.String, java.util.Map) - */ - public Object executeString(String source, Map model) - { - try - { - // compile the script based on the node content - Script script; - Context cx = Context.enter(); - try - { - script = cx.compileString(resolveScriptImports(source), "AlfrescoJS", 1, null); - } - finally - { - Context.exit(); - } - return executeScriptImpl(script, model, true, "string script"); - } - catch (Throwable err) - { - throw new ScriptException("Failed to execute supplied script: " + err.getMessage(), err); - } - } - - /** - * Resolve the imports in the specified script. Supported include directives are of the following form: - *
-     * <import resource="classpath:alfresco/includeme.js">
-     * <import resource="workspace://SpacesStore/6f73de1b-d3b4-11db-80cb-112e6c2ea048">
-     * <import resource="/Company Home/Data Dictionary/Scripts/includeme.js">
-     * 
- * Either a classpath resource, NodeRef or cm:name path based script can be includes. Multiple includes - * of the same script are dealt with correctly and nested includes of scripts is fully supported. - *

- * Note that for performance reasons the script import directive syntax and placement in the file - * is very strict. The import lines must always be first in the file - even before any comments. - * Immediately that the script service detects a non-import line it will assume the rest of the - * file is executable script and no longer attempt to search for any further import directives. Therefore - * all imports should be at the top of the script, one following the other, in the correct syntax and with - * no comments present - the only separators valid between import directives is white space. - * - * @param script The script content to resolve imports in - * - * @return a valid script with all nested includes resolved into a single script instance - */ - private String resolveScriptImports(String script) - { - return ScriptResourceHelper.resolveScriptImports(script, this, logger); - } - - /** - * Load a script content from the specific resource path. - * - * @param resource Resources can be of the form: - *

-     * classpath:alfresco/includeme.js
-     * workspace://SpacesStore/6f73de1b-d3b4-11db-80cb-112e6c2ea048
-     * /Company Home/Data Dictionary/Scripts/includeme.js
-     * 
- * - * @return the content from the resource, null if not recognised format - * - * @throws AlfrescoRuntimeException on any IO or ContentIO error - */ - public String loadScriptResource(String resource) - { - String result = null; - - if (resource.startsWith(PATH_CLASSPATH)) - { - try - { - // Load from classpath - String scriptClasspath = resource.substring(PATH_CLASSPATH.length()); - URL scriptResource = getClass().getClassLoader().getResource(scriptClasspath); - if (scriptResource == null && scriptClasspath.startsWith("/")) - { - // The Eclipse classloader prefers alfresco/foo to /alfresco/foo, try that - scriptResource = getClass().getClassLoader().getResource(scriptClasspath.substring(1)); - } - if (scriptResource == null) - { - throw new AlfrescoRuntimeException("Unable to locate included script classpath resource: " + resource); - } - InputStream stream = scriptResource.openStream(); - if (stream == null) - { - throw new AlfrescoRuntimeException("Unable to load included script classpath resource: " + resource); - } - ByteArrayOutputStream os = new ByteArrayOutputStream(); - FileCopyUtils.copy(stream, os); // both streams are closed - byte[] bytes = os.toByteArray(); - // create the string from the byte[] using encoding if necessary - result = new String(bytes, "UTF-8"); - } - catch (IOException err) - { - throw new AlfrescoRuntimeException("Unable to load included script classpath resource: " + resource); - } - } - else - { - NodeRef scriptRef; - if (resource.startsWith("/")) - { - // resolve from default SpacesStore as cm:name based path - // TODO: remove this once FFS correctly allows name path resolving from store root! - NodeRef rootNodeRef = this.services.getNodeService().getRootNode(this.storeRef); - List nodes = this.services.getSearchService().selectNodes( - rootNodeRef, this.storePath, null, this.services.getNamespaceService(), false); - if (nodes.size() == 0) - { - throw new AlfrescoRuntimeException("Unable to find store path: " + this.storePath); - } - StringTokenizer tokenizer = new StringTokenizer(resource, "/"); - List elements = new ArrayList(6); - if (tokenizer.hasMoreTokens()) - { - tokenizer.nextToken(); - } - while (tokenizer.hasMoreTokens()) - { - elements.add(tokenizer.nextToken()); - } - try - { - FileInfo fileInfo = this.services.getFileFolderService().resolveNamePath(nodes.get(0), elements); - scriptRef = fileInfo.getNodeRef(); - } - catch (FileNotFoundException err) - { - throw new AlfrescoRuntimeException("Unable to load included script repository resource: " + resource); - } - } - else - { - scriptRef = new NodeRef(resource); - } - - // load from NodeRef default content property - try - { - ContentReader cr = this.services.getContentService().getReader(scriptRef, ContentModel.PROP_CONTENT); - if (cr == null || cr.exists() == false) - { - throw new AlfrescoRuntimeException("Included Script Node content not found: " + resource); - } - result = cr.getContentString(); - } - catch (ContentIOException err) - { - throw new AlfrescoRuntimeException("Unable to load included script repository resource: " + resource); - } - } - - return result; - } - - /** - * Execute the supplied script content. Adds the default data model and custom configured root - * objects into the root scope for access by the script. - * - * @param script The script to execute. - * @param model Data model containing objects to be added to the root scope. - * @param secure True if the script is considered secure and may access java.* libs directly - * @param debugScriptName To identify the script in debug messages. - * - * @return result of the script execution, can be null. - * - * @throws AlfrescoRuntimeException - */ - private Object executeScriptImpl(Script script, Map model, boolean secure, String debugScriptName) - throws AlfrescoRuntimeException - { - long startTime = 0; - if (callLogger.isDebugEnabled()) - { - callLogger.debug(debugScriptName+" Start"); - startTime = System.nanoTime(); - } - - // Convert the model - model = convertToRhinoModel(model); - - Context cx = Context.enter(); - try - { - // Create a thread-specific scope from one of the shared scopes. - // See http://www.mozilla.org/rhino/scopes.html - cx.setWrapFactory(wrapFactory); - Scriptable scope; - if (this.shareSealedScopes) - { - Scriptable sharedScope = secure ? this.nonSecureScope : this.secureScope; - scope = cx.newObject(sharedScope); - scope.setPrototype(sharedScope); - scope.setParentScope(null); - } - else - { - scope = initScope(cx, secure, false); - } - - // there's always a model, if only to hold the util objects - if (model == null) - { - model = new HashMap(); - } - - // add the global scripts - for (ProcessorExtension ex : this.processorExtensions.values()) - { - model.put(ex.getExtensionName(), ex); - } - - // insert supplied object model into root of the default scope - for (String key : model.keySet()) - { - try - { - // set the root scope on appropriate objects - // this is used to allow native JS object creation etc. - Object obj = model.get(key); - if (obj instanceof Scopeable) - { - ((Scopeable)obj).setScope(scope); - } - - // convert/wrap each object to JavaScript compatible - Object jsObject = Context.javaToJS(obj, scope); - - // insert into the root scope ready for access by the script - ScriptableObject.putProperty(scope, key, jsObject); - } - catch(AuthenticationException e) - { - // ok, log and don't add to the root scope - logger.info("Unable to add " + key + " to root scope: ", e); - } - } - - // execute the script and return the result - Object result = script.exec(cx, scope); - - // extract java object result if wrapped by Rhino - return valueConverter.convertValueForJava(result); - } - catch (WrappedException w) - { - if (callLogger.isDebugEnabled()) - { - callLogger.debug(debugScriptName+" Exception", w); - } - Throwable err = w.getWrappedException(); - if (err instanceof RuntimeException) - { - throw (RuntimeException)err; - } - throw new AlfrescoRuntimeException(err.getMessage(), err); - } - catch (Throwable err) - { - if (callLogger.isDebugEnabled()) - { - callLogger.debug(debugScriptName+" Exception", err); - } - throw new AlfrescoRuntimeException(err.getMessage(), err); - } - finally - { - Context.exit(); - - if (callLogger.isDebugEnabled()) - { - long endTime = System.nanoTime(); - callLogger.debug(debugScriptName+" End " + (endTime - startTime)/1000000 + " ms"); - } - } - } - - /** - * Converts the passed model into a Rhino model - * - * @param model the model - * - * @return Map the converted model - */ - private Map convertToRhinoModel(Map model) - { - Map newModel = null; - if (model != null) - { - newModel = new HashMap(model.size()); - for (Map.Entry entry : model.entrySet()) - { - if (entry.getValue() instanceof NodeRef) - { - newModel.put(entry.getKey(), new ScriptNode((NodeRef)entry.getValue(), this.services)); - } - else - { - newModel.put(entry.getKey(), entry.getValue()); - } - } - } - else - { - newModel = new HashMap(1, 1.0f); - } - return newModel; - } - - - /** - * Rhino script value wraper - */ - private static class RhinoWrapFactory extends WrapFactory - { - /* (non-Javadoc) - * @see org.mozilla.javascript.WrapFactory#wrapAsJavaObject(org.mozilla.javascript.Context, org.mozilla.javascript.Scriptable, java.lang.Object, java.lang.Class) - */ - public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class staticType) - { - if (javaObject instanceof Map && !(javaObject instanceof ScriptableHashMap)) - { - return new NativeMap(scope, (Map)javaObject); - } - return super.wrapAsJavaObject(cx, scope, javaObject, staticType); - } - } - - - /** - * Pre initializes two scope objects (one secure and one not) with the standard objects preinitialised. - * This saves on very expensive calls to reinitialize a new scope on every web script execution. See - * http://www.mozilla.org/rhino/scopes.html - * - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() throws Exception - { - // Initialize the secure scope - Context cx = Context.enter(); - try - { - cx.setWrapFactory(wrapFactory); - this.secureScope = initScope(cx, false, true); - } - finally - { - Context.exit(); - } - - // Initialize the non-secure scope - cx = Context.enter(); - try - { - cx.setWrapFactory(wrapFactory); - this.nonSecureScope = initScope(cx, true, true); - } - finally - { - Context.exit(); - } - } - - /** - * Initializes a scope for script execution. The easiest way to embed Rhino is just to create a new scope this - * way whenever you need one. However, initStandardObjects() is an expensive method to call and it allocates a - * fair amount of memory. - * - * @param cx the thread execution context - * @param secure Do we consider the script secure? When false this ensures the script may not - * access insecure java.* libraries or import any other classes for direct access - only the - * configured root host objects will be available to the script writer. - * @param sealed Should the scope be sealed, making it immutable? This should be true if a scope - * is to be reused. - * @return the scope object - */ - protected Scriptable initScope(Context cx, boolean secure, boolean sealed) - { - Scriptable scope; - if (secure) - { - // Initialise the non-secure scope - // allow access to all libraries and objects, including the importer - // @see http://www.mozilla.org/rhino/ScriptingJava.html - scope = new ImporterTopLevel(cx, sealed); - } - else - { - // Initialise the secure scope - scope = cx.initStandardObjects(null, sealed); - // remove security issue related objects - this ensures the script may not access - // unsecure java.* libraries or import any other classes for direct access - only - // the configured root host objects will be available to the script writer - scope.delete("Packages"); - scope.delete("getClass"); - scope.delete("java"); - } - return scope; - } +package org.alfresco.repo.jscript; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; + +import net.sf.acegisecurity.AuthenticationException; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.processor.ProcessorExtension; +import org.alfresco.repo.processor.BaseProcessor; +import org.alfresco.scripts.ScriptException; +import org.alfresco.scripts.ScriptResourceHelper; +import org.alfresco.scripts.ScriptResourceLoader; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.alfresco.service.cmr.repository.ScriptProcessor; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.ImporterTopLevel; +import org.mozilla.javascript.Script; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.WrapFactory; +import org.mozilla.javascript.WrappedException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.FileCopyUtils; + +/** + * Implementation of the ScriptProcessor using the Rhino JavaScript library. + * + * @author Kevin Roast + */ +public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcessor, ScriptResourceLoader, InitializingBean +{ + private static final Log logger = LogFactory.getLog(RhinoScriptProcessor.class); + private static final Log callLogger = LogFactory.getLog(RhinoScriptProcessor.class.getName()+".calls"); + + private static final String PATH_CLASSPATH = "classpath:"; + + /** Wrap Factory */ + private static final WrapFactory wrapFactory = new RhinoWrapFactory(); + + /** Base Value Converter */ + private final ValueConverter valueConverter = new ValueConverter(); + + /** Store into which to resolve cm:name based script paths */ + private StoreRef storeRef; + + /** Store root path to resolve cm:name based scripts path from */ + private String storePath; + + /** Pre initialized secure scope object. */ + private Scriptable secureScope; + + /** Pre initialized non secure scope object. */ + private Scriptable nonSecureScope; + + /** Flag to enable or disable runtime script compliation */ + private boolean compile = true; + + /** Flag to enable the sharing of sealed root scopes between scripts executions */ + private boolean shareSealedScopes = true; + + /** Cache of runtime compiled script instances */ + private final Map scriptCache = new ConcurrentHashMap(256); + + + /** + * Set the default store reference + * + * @param storeRef The default store reference + */ + public void setStoreUrl(String storeRef) + { + this.storeRef = new StoreRef(storeRef); + } + + /** + * @param storePath The store path to set. + */ + public void setStorePath(String storePath) + { + this.storePath = storePath; + } + + /** + * @param compile the compile flag to set + */ + public void setCompile(boolean compile) + { + this.compile = compile; + } + + /** + * @param shareSealedScopes true to allow sharing of sealed scopes between script executions - set to + * false to disable this feature and ensure that a new scope is created for each executed script. + */ + public void setShareSealedScopes(boolean shareSealedScopes) + { + this.shareSealedScopes = shareSealedScopes; + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptProcessor#reset() + */ + public void reset() + { + this.scriptCache.clear(); + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptProcessor#execute(org.alfresco.service.cmr.repository.ScriptLocation, java.util.Map) + */ + public Object execute(ScriptLocation location, Map model) + { + try + { + // test the cache for a pre-compiled script matching our path + Script script = null; + String path = location.getPath(); + if (this.compile && location.isCachable()) + { + script = this.scriptCache.get(path); + } + if (script == null) + { + if (logger.isDebugEnabled()) + logger.debug("Resolving and compiling script path: " + path); + + // retrieve script content and resolve imports + ByteArrayOutputStream os = new ByteArrayOutputStream(); + FileCopyUtils.copy(location.getInputStream(), os); // both streams are closed + byte[] bytes = os.toByteArray(); + String source = new String(bytes, "UTF-8"); + source = resolveScriptImports(new String(bytes)); + + // compile the script and cache the result + Context cx = Context.enter(); + try + { + script = cx.compileString(source, location.toString(), 1, null); + + // We do not worry about more than one user thread compiling the same script. + // If more than one request thread compiles the same script and adds it to the + // cache that does not matter - the results will be the same. Therefore we + // rely on the ConcurrentHashMap impl to deal both with ensuring the safety of the + // underlying structure with asynchronous get/put operations and for fast + // multi-threaded access to the common cache. + if (this.compile && location.isCachable()) + { + this.scriptCache.put(path, script); + } + } + finally + { + Context.exit(); + } + } + + String debugScriptName = null; + if (callLogger.isDebugEnabled()) + { + int i = path.lastIndexOf('/'); + debugScriptName = (i != -1) ? path.substring(i+1) : path; + } + return executeScriptImpl(script, model, location.isSecure(), debugScriptName); + } + catch (Throwable err) + { + throw new ScriptException("Failed to execute script '" + location.toString() + "': " + err.getMessage(), err); + } + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptProcessor#execute(java.lang.String, java.util.Map) + */ + public Object execute(String location, Map model) + { + return execute(new ClasspathScriptLocation(location), model); + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptProcessor#execute(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, java.util.Map) + */ + public Object execute(NodeRef nodeRef, QName contentProp, Map model) + { + try + { + if (this.services.getNodeService().exists(nodeRef) == false) + { + throw new AlfrescoRuntimeException("Script Node does not exist: " + nodeRef); + } + + if (contentProp == null) + { + contentProp = ContentModel.PROP_CONTENT; + } + ContentReader cr = this.services.getContentService().getReader(nodeRef, contentProp); + if (cr == null || cr.exists() == false) + { + throw new AlfrescoRuntimeException("Script Node content not found: " + nodeRef); + } + + // compile the script based on the node content + Script script; + Context cx = Context.enter(); + try + { + script = cx.compileString(resolveScriptImports(cr.getContentString()), nodeRef.toString(), 1, null); + } + finally + { + Context.exit(); + } + + return executeScriptImpl(script, model, false, nodeRef.toString()); + } + catch (Throwable err) + { + throw new ScriptException("Failed to execute script '" + nodeRef.toString() + "': " + err.getMessage(), err); + } + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptProcessor#executeString(java.lang.String, java.util.Map) + */ + public Object executeString(String source, Map model) + { + try + { + // compile the script based on the node content + Script script; + Context cx = Context.enter(); + try + { + script = cx.compileString(resolveScriptImports(source), "AlfrescoJS", 1, null); + } + finally + { + Context.exit(); + } + return executeScriptImpl(script, model, true, "string script"); + } + catch (Throwable err) + { + throw new ScriptException("Failed to execute supplied script: " + err.getMessage(), err); + } + } + + /** + * Resolve the imports in the specified script. Supported include directives are of the following form: + *
+     * <import resource="classpath:alfresco/includeme.js">
+     * <import resource="workspace://SpacesStore/6f73de1b-d3b4-11db-80cb-112e6c2ea048">
+     * <import resource="/Company Home/Data Dictionary/Scripts/includeme.js">
+     * 
+ * Either a classpath resource, NodeRef or cm:name path based script can be includes. Multiple includes + * of the same script are dealt with correctly and nested includes of scripts is fully supported. + *

+ * Note that for performance reasons the script import directive syntax and placement in the file + * is very strict. The import lines must always be first in the file - even before any comments. + * Immediately that the script service detects a non-import line it will assume the rest of the + * file is executable script and no longer attempt to search for any further import directives. Therefore + * all imports should be at the top of the script, one following the other, in the correct syntax and with + * no comments present - the only separators valid between import directives is white space. + * + * @param script The script content to resolve imports in + * + * @return a valid script with all nested includes resolved into a single script instance + */ + private String resolveScriptImports(String script) + { + return ScriptResourceHelper.resolveScriptImports(script, this, logger); + } + + /** + * Load a script content from the specific resource path. + * + * @param resource Resources can be of the form: + *

+     * classpath:alfresco/includeme.js
+     * workspace://SpacesStore/6f73de1b-d3b4-11db-80cb-112e6c2ea048
+     * /Company Home/Data Dictionary/Scripts/includeme.js
+     * 
+ * + * @return the content from the resource, null if not recognised format + * + * @throws AlfrescoRuntimeException on any IO or ContentIO error + */ + public String loadScriptResource(String resource) + { + String result = null; + + if (resource.startsWith(PATH_CLASSPATH)) + { + try + { + // Load from classpath + String scriptClasspath = resource.substring(PATH_CLASSPATH.length()); + URL scriptResource = getClass().getClassLoader().getResource(scriptClasspath); + if (scriptResource == null && scriptClasspath.startsWith("/")) + { + // The Eclipse classloader prefers alfresco/foo to /alfresco/foo, try that + scriptResource = getClass().getClassLoader().getResource(scriptClasspath.substring(1)); + } + if (scriptResource == null) + { + throw new AlfrescoRuntimeException("Unable to locate included script classpath resource: " + resource); + } + InputStream stream = scriptResource.openStream(); + if (stream == null) + { + throw new AlfrescoRuntimeException("Unable to load included script classpath resource: " + resource); + } + ByteArrayOutputStream os = new ByteArrayOutputStream(); + FileCopyUtils.copy(stream, os); // both streams are closed + byte[] bytes = os.toByteArray(); + // create the string from the byte[] using encoding if necessary + result = new String(bytes, "UTF-8"); + } + catch (IOException err) + { + throw new AlfrescoRuntimeException("Unable to load included script classpath resource: " + resource); + } + } + else + { + NodeRef scriptRef; + if (resource.startsWith("/")) + { + // resolve from default SpacesStore as cm:name based path + // TODO: remove this once FFS correctly allows name path resolving from store root! + NodeRef rootNodeRef = this.services.getNodeService().getRootNode(this.storeRef); + List nodes = this.services.getSearchService().selectNodes( + rootNodeRef, this.storePath, null, this.services.getNamespaceService(), false); + if (nodes.size() == 0) + { + throw new AlfrescoRuntimeException("Unable to find store path: " + this.storePath); + } + StringTokenizer tokenizer = new StringTokenizer(resource, "/"); + List elements = new ArrayList(6); + if (tokenizer.hasMoreTokens()) + { + tokenizer.nextToken(); + } + while (tokenizer.hasMoreTokens()) + { + elements.add(tokenizer.nextToken()); + } + try + { + FileInfo fileInfo = this.services.getFileFolderService().resolveNamePath(nodes.get(0), elements); + scriptRef = fileInfo.getNodeRef(); + } + catch (FileNotFoundException err) + { + throw new AlfrescoRuntimeException("Unable to load included script repository resource: " + resource); + } + } + else + { + scriptRef = new NodeRef(resource); + } + + // load from NodeRef default content property + try + { + ContentReader cr = this.services.getContentService().getReader(scriptRef, ContentModel.PROP_CONTENT); + if (cr == null || cr.exists() == false) + { + throw new AlfrescoRuntimeException("Included Script Node content not found: " + resource); + } + result = cr.getContentString(); + } + catch (ContentIOException err) + { + throw new AlfrescoRuntimeException("Unable to load included script repository resource: " + resource); + } + } + + return result; + } + + /** + * Execute the supplied script content. Adds the default data model and custom configured root + * objects into the root scope for access by the script. + * + * @param script The script to execute. + * @param model Data model containing objects to be added to the root scope. + * @param secure True if the script is considered secure and may access java.* libs directly + * @param debugScriptName To identify the script in debug messages. + * + * @return result of the script execution, can be null. + * + * @throws AlfrescoRuntimeException + */ + private Object executeScriptImpl(Script script, Map model, boolean secure, String debugScriptName) + throws AlfrescoRuntimeException + { + long startTime = 0; + if (callLogger.isDebugEnabled()) + { + callLogger.debug(debugScriptName+" Start"); + startTime = System.nanoTime(); + } + + // Convert the model + model = convertToRhinoModel(model); + + Context cx = Context.enter(); + try + { + // Create a thread-specific scope from one of the shared scopes. + // See http://www.mozilla.org/rhino/scopes.html + cx.setWrapFactory(wrapFactory); + Scriptable scope; + if (this.shareSealedScopes) + { + Scriptable sharedScope = secure ? this.nonSecureScope : this.secureScope; + scope = cx.newObject(sharedScope); + scope.setPrototype(sharedScope); + scope.setParentScope(null); + } + else + { + scope = initScope(cx, secure, false); + } + + // there's always a model, if only to hold the util objects + if (model == null) + { + model = new HashMap(); + } + + // add the global scripts + for (ProcessorExtension ex : this.processorExtensions.values()) + { + model.put(ex.getExtensionName(), ex); + } + + // insert supplied object model into root of the default scope + for (String key : model.keySet()) + { + try + { + // set the root scope on appropriate objects + // this is used to allow native JS object creation etc. + Object obj = model.get(key); + if (obj instanceof Scopeable) + { + ((Scopeable)obj).setScope(scope); + } + + // convert/wrap each object to JavaScript compatible + Object jsObject = Context.javaToJS(obj, scope); + + // insert into the root scope ready for access by the script + ScriptableObject.putProperty(scope, key, jsObject); + } + catch(AuthenticationException e) + { + // ok, log and don't add to the root scope + logger.info("Unable to add " + key + " to root scope: ", e); + } + } + + // execute the script and return the result + Object result = script.exec(cx, scope); + + // extract java object result if wrapped by Rhino + return valueConverter.convertValueForJava(result); + } + catch (WrappedException w) + { + if (callLogger.isDebugEnabled()) + { + callLogger.debug(debugScriptName+" Exception", w); + } + Throwable err = w.getWrappedException(); + if (err instanceof RuntimeException) + { + throw (RuntimeException)err; + } + throw new AlfrescoRuntimeException(err.getMessage(), err); + } + catch (Throwable err) + { + if (callLogger.isDebugEnabled()) + { + callLogger.debug(debugScriptName+" Exception", err); + } + throw new AlfrescoRuntimeException(err.getMessage(), err); + } + finally + { + Context.exit(); + + if (callLogger.isDebugEnabled()) + { + long endTime = System.nanoTime(); + callLogger.debug(debugScriptName+" End " + (endTime - startTime)/1000000 + " ms"); + } + } + } + + /** + * Converts the passed model into a Rhino model + * + * @param model the model + * + * @return Map the converted model + */ + private Map convertToRhinoModel(Map model) + { + Map newModel = null; + if (model != null) + { + newModel = new HashMap(model.size()); + for (Map.Entry entry : model.entrySet()) + { + if (entry.getValue() instanceof NodeRef) + { + newModel.put(entry.getKey(), new ScriptNode((NodeRef)entry.getValue(), this.services)); + } + else + { + newModel.put(entry.getKey(), entry.getValue()); + } + } + } + else + { + newModel = new HashMap(1, 1.0f); + } + return newModel; + } + + + /** + * Rhino script value wraper + */ + private static class RhinoWrapFactory extends WrapFactory + { + /* (non-Javadoc) + * @see org.mozilla.javascript.WrapFactory#wrapAsJavaObject(org.mozilla.javascript.Context, org.mozilla.javascript.Scriptable, java.lang.Object, java.lang.Class) + */ + public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class staticType) + { + if (javaObject instanceof Map && !(javaObject instanceof ScriptableHashMap)) + { + return new NativeMap(scope, (Map)javaObject); + } + return super.wrapAsJavaObject(cx, scope, javaObject, staticType); + } + } + + + /** + * Pre initializes two scope objects (one secure and one not) with the standard objects preinitialised. + * This saves on very expensive calls to reinitialize a new scope on every web script execution. See + * http://www.mozilla.org/rhino/scopes.html + * + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + // Initialize the secure scope + Context cx = Context.enter(); + try + { + cx.setWrapFactory(wrapFactory); + this.secureScope = initScope(cx, false, true); + } + finally + { + Context.exit(); + } + + // Initialize the non-secure scope + cx = Context.enter(); + try + { + cx.setWrapFactory(wrapFactory); + this.nonSecureScope = initScope(cx, true, true); + } + finally + { + Context.exit(); + } + } + + /** + * Initializes a scope for script execution. The easiest way to embed Rhino is just to create a new scope this + * way whenever you need one. However, initStandardObjects() is an expensive method to call and it allocates a + * fair amount of memory. + * + * @param cx the thread execution context + * @param secure Do we consider the script secure? When false this ensures the script may not + * access insecure java.* libraries or import any other classes for direct access - only the + * configured root host objects will be available to the script writer. + * @param sealed Should the scope be sealed, making it immutable? This should be true if a scope + * is to be reused. + * @return the scope object + */ + protected Scriptable initScope(Context cx, boolean secure, boolean sealed) + { + Scriptable scope; + if (secure) + { + // Initialise the non-secure scope + // allow access to all libraries and objects, including the importer + // @see http://www.mozilla.org/rhino/ScriptingJava.html + scope = new ImporterTopLevel(cx, sealed); + } + else + { + // Initialise the secure scope + scope = cx.initStandardObjects(null, sealed); + // remove security issue related objects - this ensures the script may not access + // unsecure java.* libraries or import any other classes for direct access - only + // the configured root host objects will be available to the script writer + scope.delete("Packages"); + scope.delete("getClass"); + scope.delete("java"); + } + return scope; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/jscript/Scopeable.java b/source/java/org/alfresco/repo/jscript/Scopeable.java index 78ba670e46..1be5e127a0 100644 --- a/source/java/org/alfresco/repo/jscript/Scopeable.java +++ b/source/java/org/alfresco/repo/jscript/Scopeable.java @@ -1,21 +1,21 @@ -package org.alfresco.repo.jscript; - -import org.mozilla.javascript.Scriptable; - -/** - * Interface contract for objects that supporting setting of the global scripting scope. - * This is used to mark objects that are not themselves natively scriptable (i.e. they are - * wrapped Java objects) but need to access the global scope for the purposes of JavaScript - * object creation etc. - * - * @author Kevin Roast - */ -public interface Scopeable -{ - /** - * Set the Scriptable global scope - * - * @param scope relative global scope - */ - void setScope(Scriptable scope); -} +package org.alfresco.repo.jscript; + +import org.mozilla.javascript.Scriptable; + +/** + * Interface contract for objects that supporting setting of the global scripting scope. + * This is used to mark objects that are not themselves natively scriptable (i.e. they are + * wrapped Java objects) but need to access the global scope for the purposes of JavaScript + * object creation etc. + * + * @author Kevin Roast + */ +public interface Scopeable +{ + /** + * Set the Scriptable global scope + * + * @param scope relative global scope + */ + void setScope(Scriptable scope); +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptAction.java b/source/java/org/alfresco/repo/jscript/ScriptAction.java index 8530334fba..77c53084de 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptAction.java +++ b/source/java/org/alfresco/repo/jscript/ScriptAction.java @@ -1,401 +1,401 @@ -package org.alfresco.repo.jscript; - -import java.io.Serializable; -import java.util.Map; - -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionDefinition; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.Wrapper; - -/** - * Scriptable Action - * - * @author davidc - */ -public class ScriptAction implements Serializable, Scopeable -{ - private static final long serialVersionUID = 5794161358406531996L; - - /** Root scope for this object */ - private Scriptable scope; - - /** Converter with knowledge of action parameter values */ - private ActionValueConverter converter; - - /** Action state */ - protected Action action; - - protected ActionDefinition actionDef; - - protected ServiceRegistry services; - private ActionService actionService; - private NamespaceService namespaceService; - private TransactionService transactionService; - - private ScriptableParameterMap parameters = null; - - /** - * Construct - * - * @param action - * Alfresco action - */ - public ScriptAction(ServiceRegistry services, Action action, ActionDefinition actionDef) - { - this.services = services; - this.actionService = services.getActionService(); - this.namespaceService = services.getNamespaceService(); - this.transactionService = services.getTransactionService(); - - this.action = action; - this.actionDef = actionDef; - this.converter = new ActionValueConverter(); - } - - /** - * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) - */ - public void setScope(Scriptable scope) - { - this.scope = scope; - } - - /** - * Returns the action name - * - * @return action name - */ - public String getName() - { - return this.actionDef.getName(); - } - - /** - * Return all the properties known about this node. The Map returned implements the Scriptable interface to allow access to the properties via JavaScript associative array - * access. This means properties of a node can be access thus: node.properties["name"] - * - * @return Map of properties for this Node. - */ - @SuppressWarnings("synthetic-access") - public Map getParameters() - { - if (this.parameters == null) - { - // this Map implements the Scriptable interface for native JS syntax property access - this.parameters = new ScriptableParameterMap(); - Map actionParams = this.action.getParameterValues(); - for (Map.Entry entry : actionParams.entrySet()) - { - String name = entry.getKey(); - this.parameters.put(name, converter.convertActionParamForScript(name, entry.getValue())); - } - this.parameters.setModified(false); - } - return this.parameters; - } - - /** - * Execute action. The existing transaction will be joined. - * - * @param node - * the node to execute action upon - */ - @SuppressWarnings("synthetic-access") - public void execute(ScriptNode node) - { - performParamConversionForRepo(); - executeImpl(node); - - // Parameters may have been updated by action execution, so reset cache - this.parameters = null; - - // Reset the actioned upon node - node.reset(); - } - - /** - * Execute action. The existing transaction will be joined. - * - * @param node - * the node to execute action upon - */ - @SuppressWarnings("synthetic-access") - public void executeAsynchronously(ScriptNode node) - { - performParamConversionForRepo(); - executeAsynchronouslyImpl(node); - - // Parameters may have been updated by action execution, so reset cache - this.parameters = null; - - // Reset the actioned upon node - node.reset(); - } - - protected void executeImpl(ScriptNode node) - { - actionService.executeAction(action, node.getNodeRef()); - } - - protected void executeAsynchronouslyImpl(ScriptNode node) - { - actionService.executeAction(action, node.getNodeRef(), true, true); - } - - /** - * Execute action, optionally starting a new, potentially read-only transaction. - * - * @param node - * the node to execute action upon - * @param newTxn - * true to start a new, isolated transaction - * - * @see RetryingTransactionHelper#doInTransaction(RetryingTransactionCallback, boolean, boolean) - */ - @SuppressWarnings("synthetic-access") - public void execute(final ScriptNode node, boolean readOnly, boolean newTxn) - { - performParamConversionForRepo(); - RetryingTransactionCallback executionActionCallback = new RetryingTransactionCallback() - { - public Object execute() throws Throwable - { - executeImpl(node); - return null; - } - }; - transactionService.getRetryingTransactionHelper().doInTransaction( - executionActionCallback, - readOnly, - newTxn); - - // Parameters may have been updated by action execution, so reset cache - this.parameters = null; - - // Reset the actioned upon node - node.reset(); - } - - /** - * Execute action. The existing transaction will be joined. - * - * @param nodeRef - * the node to execute action upon - */ - @SuppressWarnings("synthetic-access") - public void execute(NodeRef nodeRef) - { - performParamConversionForRepo(); - actionService.executeAction(action, nodeRef); - - // Parameters may have been updated by action execution, so reset cache - this.parameters = null; - } - - /** - * Execute action, optionally starting a new, potentially read-only transaction. - * - * @param nodeRef - * the node to execute action upon - * @param newTxn - * true to start a new, isolated transaction - * - * @see RetryingTransactionHelper#doInTransaction(RetryingTransactionCallback, boolean, boolean) - */ - @SuppressWarnings("synthetic-access") - public void execute(final NodeRef nodeRef, boolean readOnly, boolean newTxn) - { - performParamConversionForRepo(); - RetryingTransactionCallback executionActionCallback = new RetryingTransactionCallback() - { - public Object execute() throws Throwable - { - actionService.executeAction(action, nodeRef); - return null; - } - }; - transactionService.getRetryingTransactionHelper().doInTransaction( - executionActionCallback, - readOnly, - newTxn); - - // Parameters may have been updated by action execution, so reset cache - this.parameters = null; - } - - protected void performParamConversionForRepo() { - if (this.parameters != null && this.parameters.isModified()) - { - Map actionParams = action.getParameterValues(); - actionParams.clear(); - - for (Map.Entry entry : this.parameters.entrySet()) - { - // perform the conversion from script wrapper object to repo serializable values - String name = entry.getKey(); - Serializable value = converter.convertActionParamForRepo(name, entry.getValue()); - actionParams.put(name, value); - } - } - } - - - /** - * Value converter with specific knowledge of action parameters - * - * @author davidc - */ - private class ActionValueConverter extends ValueConverter - { - /** - * Convert Action Parameter for Script usage - * - * @param paramName - * parameter name - * @param value - * value to convert - * @return converted value - */ - @SuppressWarnings("synthetic-access") - public Serializable convertActionParamForScript(String paramName, Serializable value) - { - ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName); - if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME)) - { - return ((QName) value).toPrefixString(namespaceService); - } - else - { - return convertValueForScript(services, scope, null, value); - } - } - - /** - * Convert Action Parameter for Java usage - * - * @param paramName - * parameter name - * @param value - * value to convert - * @return converted value - */ - @SuppressWarnings("synthetic-access") - public Serializable convertActionParamForRepo(String paramName, Serializable value) - { - ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName); - - if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME)) - { - if (value instanceof Wrapper) - { - // unwrap a Java object from a JavaScript wrapper - // recursively call this method to convert the unwrapped value - return convertActionParamForRepo(paramName, (Serializable) ((Wrapper) value).unwrap()); - } - else - { - if (value instanceof String) - { - String stringQName = (String) value; - if (stringQName.startsWith("{")) - { - return QName.createQName(stringQName); - - } - else - { - return QName.createQName(stringQName, namespaceService); - } - } - else - { - return value; - } - } - } - else - { - return convertValueForRepo(value); - } - } - } - - /** - * Scripted Parameter map with modified flag. - * - * @author davidc - */ - public static final class ScriptableParameterMap extends ScriptableHashMap - { - private static final long serialVersionUID = 574661815973241554L; - - private boolean modified = false; - - /** - * Is this a modified parameter map? - * - * @return true => modified - */ - /* package */boolean isModified() - { - return modified; - } - - /** - * Set explicitly whether this map is modified - * - * @param modified - * true => modified, false => not modified - */ - /* package */void setModified(boolean modified) - { - this.modified = modified; - } - - /* - * (non-Javadoc) - * - * @see org.mozilla.javascript.Scriptable#getClassName() - */ - @Override - public String getClassName() - { - return "ScriptableParameterMap"; - } - - /* - * (non-Javadoc) - * - * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) - */ - @Override - public void delete(String name) - { - super.delete(name); - setModified(true); - } - - /* - * (non-Javadoc) - * - * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) - */ - @Override - public void put(String name, Scriptable start, Object value) - { - super.put(name, start, value); - setModified(true); - } - } -} +package org.alfresco.repo.jscript; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.Wrapper; + +/** + * Scriptable Action + * + * @author davidc + */ +public class ScriptAction implements Serializable, Scopeable +{ + private static final long serialVersionUID = 5794161358406531996L; + + /** Root scope for this object */ + private Scriptable scope; + + /** Converter with knowledge of action parameter values */ + private ActionValueConverter converter; + + /** Action state */ + protected Action action; + + protected ActionDefinition actionDef; + + protected ServiceRegistry services; + private ActionService actionService; + private NamespaceService namespaceService; + private TransactionService transactionService; + + private ScriptableParameterMap parameters = null; + + /** + * Construct + * + * @param action + * Alfresco action + */ + public ScriptAction(ServiceRegistry services, Action action, ActionDefinition actionDef) + { + this.services = services; + this.actionService = services.getActionService(); + this.namespaceService = services.getNamespaceService(); + this.transactionService = services.getTransactionService(); + + this.action = action; + this.actionDef = actionDef; + this.converter = new ActionValueConverter(); + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + /** + * Returns the action name + * + * @return action name + */ + public String getName() + { + return this.actionDef.getName(); + } + + /** + * Return all the properties known about this node. The Map returned implements the Scriptable interface to allow access to the properties via JavaScript associative array + * access. This means properties of a node can be access thus: node.properties["name"] + * + * @return Map of properties for this Node. + */ + @SuppressWarnings("synthetic-access") + public Map getParameters() + { + if (this.parameters == null) + { + // this Map implements the Scriptable interface for native JS syntax property access + this.parameters = new ScriptableParameterMap(); + Map actionParams = this.action.getParameterValues(); + for (Map.Entry entry : actionParams.entrySet()) + { + String name = entry.getKey(); + this.parameters.put(name, converter.convertActionParamForScript(name, entry.getValue())); + } + this.parameters.setModified(false); + } + return this.parameters; + } + + /** + * Execute action. The existing transaction will be joined. + * + * @param node + * the node to execute action upon + */ + @SuppressWarnings("synthetic-access") + public void execute(ScriptNode node) + { + performParamConversionForRepo(); + executeImpl(node); + + // Parameters may have been updated by action execution, so reset cache + this.parameters = null; + + // Reset the actioned upon node + node.reset(); + } + + /** + * Execute action. The existing transaction will be joined. + * + * @param node + * the node to execute action upon + */ + @SuppressWarnings("synthetic-access") + public void executeAsynchronously(ScriptNode node) + { + performParamConversionForRepo(); + executeAsynchronouslyImpl(node); + + // Parameters may have been updated by action execution, so reset cache + this.parameters = null; + + // Reset the actioned upon node + node.reset(); + } + + protected void executeImpl(ScriptNode node) + { + actionService.executeAction(action, node.getNodeRef()); + } + + protected void executeAsynchronouslyImpl(ScriptNode node) + { + actionService.executeAction(action, node.getNodeRef(), true, true); + } + + /** + * Execute action, optionally starting a new, potentially read-only transaction. + * + * @param node + * the node to execute action upon + * @param newTxn + * true to start a new, isolated transaction + * + * @see RetryingTransactionHelper#doInTransaction(RetryingTransactionCallback, boolean, boolean) + */ + @SuppressWarnings("synthetic-access") + public void execute(final ScriptNode node, boolean readOnly, boolean newTxn) + { + performParamConversionForRepo(); + RetryingTransactionCallback executionActionCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + executeImpl(node); + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction( + executionActionCallback, + readOnly, + newTxn); + + // Parameters may have been updated by action execution, so reset cache + this.parameters = null; + + // Reset the actioned upon node + node.reset(); + } + + /** + * Execute action. The existing transaction will be joined. + * + * @param nodeRef + * the node to execute action upon + */ + @SuppressWarnings("synthetic-access") + public void execute(NodeRef nodeRef) + { + performParamConversionForRepo(); + actionService.executeAction(action, nodeRef); + + // Parameters may have been updated by action execution, so reset cache + this.parameters = null; + } + + /** + * Execute action, optionally starting a new, potentially read-only transaction. + * + * @param nodeRef + * the node to execute action upon + * @param newTxn + * true to start a new, isolated transaction + * + * @see RetryingTransactionHelper#doInTransaction(RetryingTransactionCallback, boolean, boolean) + */ + @SuppressWarnings("synthetic-access") + public void execute(final NodeRef nodeRef, boolean readOnly, boolean newTxn) + { + performParamConversionForRepo(); + RetryingTransactionCallback executionActionCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + actionService.executeAction(action, nodeRef); + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction( + executionActionCallback, + readOnly, + newTxn); + + // Parameters may have been updated by action execution, so reset cache + this.parameters = null; + } + + protected void performParamConversionForRepo() { + if (this.parameters != null && this.parameters.isModified()) + { + Map actionParams = action.getParameterValues(); + actionParams.clear(); + + for (Map.Entry entry : this.parameters.entrySet()) + { + // perform the conversion from script wrapper object to repo serializable values + String name = entry.getKey(); + Serializable value = converter.convertActionParamForRepo(name, entry.getValue()); + actionParams.put(name, value); + } + } + } + + + /** + * Value converter with specific knowledge of action parameters + * + * @author davidc + */ + private class ActionValueConverter extends ValueConverter + { + /** + * Convert Action Parameter for Script usage + * + * @param paramName + * parameter name + * @param value + * value to convert + * @return converted value + */ + @SuppressWarnings("synthetic-access") + public Serializable convertActionParamForScript(String paramName, Serializable value) + { + ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName); + if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME)) + { + return ((QName) value).toPrefixString(namespaceService); + } + else + { + return convertValueForScript(services, scope, null, value); + } + } + + /** + * Convert Action Parameter for Java usage + * + * @param paramName + * parameter name + * @param value + * value to convert + * @return converted value + */ + @SuppressWarnings("synthetic-access") + public Serializable convertActionParamForRepo(String paramName, Serializable value) + { + ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName); + + if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME)) + { + if (value instanceof Wrapper) + { + // unwrap a Java object from a JavaScript wrapper + // recursively call this method to convert the unwrapped value + return convertActionParamForRepo(paramName, (Serializable) ((Wrapper) value).unwrap()); + } + else + { + if (value instanceof String) + { + String stringQName = (String) value; + if (stringQName.startsWith("{")) + { + return QName.createQName(stringQName); + + } + else + { + return QName.createQName(stringQName, namespaceService); + } + } + else + { + return value; + } + } + } + else + { + return convertValueForRepo(value); + } + } + } + + /** + * Scripted Parameter map with modified flag. + * + * @author davidc + */ + public static final class ScriptableParameterMap extends ScriptableHashMap + { + private static final long serialVersionUID = 574661815973241554L; + + private boolean modified = false; + + /** + * Is this a modified parameter map? + * + * @return true => modified + */ + /* package */boolean isModified() + { + return modified; + } + + /** + * Set explicitly whether this map is modified + * + * @param modified + * true => modified, false => not modified + */ + /* package */void setModified(boolean modified) + { + this.modified = modified; + } + + /* + * (non-Javadoc) + * + * @see org.mozilla.javascript.Scriptable#getClassName() + */ + @Override + public String getClassName() + { + return "ScriptableParameterMap"; + } + + /* + * (non-Javadoc) + * + * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) + */ + @Override + public void delete(String name) + { + super.delete(name); + setModified(true); + } + + /* + * (non-Javadoc) + * + * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + @Override + public void put(String name, Scriptable start, Object value) + { + super.put(name, start, value); + setModified(true); + } + } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptBehaviour.java b/source/java/org/alfresco/repo/jscript/ScriptBehaviour.java index f99c1519ba..e16656af90 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptBehaviour.java +++ b/source/java/org/alfresco/repo/jscript/ScriptBehaviour.java @@ -1,173 +1,173 @@ -package org.alfresco.repo.jscript; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.repo.policy.BaseBehaviour; -import org.alfresco.repo.policy.PolicyException; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.ScriptLocation; -import org.springframework.extensions.surf.util.ParameterCheck; - - -/** - * JavaScript behaviour implementation - * - * @author Roy Wetherall - */ -public class ScriptBehaviour extends BaseBehaviour -{ - private ServiceRegistry serviceRegistry; - - private ScriptLocation location; - - public ScriptBehaviour() - { - super(); - } - - public ScriptBehaviour(ServiceRegistry serviceRegistry, ScriptLocation location) - { - this(serviceRegistry, location, NotificationFrequency.EVERY_EVENT); - } - - public ScriptBehaviour(ServiceRegistry serviceRegistry, ScriptLocation location, NotificationFrequency frequency) - { - super(frequency); - ParameterCheck.mandatory("Location", location); - ParameterCheck.mandatory("ServiceRegistry", serviceRegistry); - this.location = location; - this.serviceRegistry = serviceRegistry; - } - - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.serviceRegistry = serviceRegistry; - } - - public void setLocation(ScriptLocation location) - { - this.location = location; - } - - - @Override - public String toString() - { - return "JavaScript behaviour[location = " + this.location.toString() + "]"; - } - - @SuppressWarnings("unchecked") - public synchronized T getInterface(Class policy) - { - ParameterCheck.mandatory("Policy class", policy); - Object proxy = proxies.get(policy); - if (proxy == null) - { - Method[] policyIFMethods = policy.getMethods(); - if (policyIFMethods.length != 1) - { - throw new PolicyException("Policy interface " + policy.getCanonicalName() + " must have only one method"); - } - - InvocationHandler handler = new JavaScriptInvocationHandler(this); - proxy = Proxy.newProxyInstance(policy.getClassLoader(), new Class[]{policy}, handler); - proxies.put(policy, proxy); - } - return (T)proxy; - } - - /** - * JavaScript Invocation Handler - * - * @author Roy Wetherall - */ - private static class JavaScriptInvocationHandler implements InvocationHandler - { - private ScriptBehaviour behaviour; - - private JavaScriptInvocationHandler(ScriptBehaviour behaviour) - { - this.behaviour = behaviour; - } - - /** - * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) - */ - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable - { - // Handle Object level methods - if (method.getName().equals("toString")) - { - return toString(); - } - else if (method.getName().equals("hashCode")) - { - return hashCode(); - } - else if (method.getName().equals("equals")) - { - if (Proxy.isProxyClass(args[0].getClass())) - { - return equals(Proxy.getInvocationHandler(args[0])); - } - return false; - } - - // Delegate to designated method pointer - if (behaviour.isEnabled()) - { - try - { - behaviour.disable(); - return invokeScript(method, args); - } - finally - { - behaviour.enable(); - } - } - return null; - } - - private Object invokeScript(Method method, Object[] args) - { - // Build the model - Map model = new HashMap(1); - model.put("behaviour", new org.alfresco.repo.jscript.Behaviour(this.behaviour.serviceRegistry, method.getName(), args)); - - // Execute the script - return this.behaviour.serviceRegistry.getScriptService().executeScript(this.behaviour.location, model); - } - - @Override - public boolean equals(Object obj) - { - if (obj == this) - { - return true; - } - else if (obj == null || !(obj instanceof JavaScriptInvocationHandler)) - { - return false; - } - JavaScriptInvocationHandler other = (JavaScriptInvocationHandler)obj; - return behaviour.location.equals(other.behaviour.location); - } - - @Override - public int hashCode() - { - return 37 * behaviour.location.hashCode(); - } - - @Override - public String toString() - { - return "JavaScriptBehaviour[location=" + behaviour.location.toString() + "]"; - } - } -} +package org.alfresco.repo.jscript; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.repo.policy.BaseBehaviour; +import org.alfresco.repo.policy.PolicyException; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.springframework.extensions.surf.util.ParameterCheck; + + +/** + * JavaScript behaviour implementation + * + * @author Roy Wetherall + */ +public class ScriptBehaviour extends BaseBehaviour +{ + private ServiceRegistry serviceRegistry; + + private ScriptLocation location; + + public ScriptBehaviour() + { + super(); + } + + public ScriptBehaviour(ServiceRegistry serviceRegistry, ScriptLocation location) + { + this(serviceRegistry, location, NotificationFrequency.EVERY_EVENT); + } + + public ScriptBehaviour(ServiceRegistry serviceRegistry, ScriptLocation location, NotificationFrequency frequency) + { + super(frequency); + ParameterCheck.mandatory("Location", location); + ParameterCheck.mandatory("ServiceRegistry", serviceRegistry); + this.location = location; + this.serviceRegistry = serviceRegistry; + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public void setLocation(ScriptLocation location) + { + this.location = location; + } + + + @Override + public String toString() + { + return "JavaScript behaviour[location = " + this.location.toString() + "]"; + } + + @SuppressWarnings("unchecked") + public synchronized T getInterface(Class policy) + { + ParameterCheck.mandatory("Policy class", policy); + Object proxy = proxies.get(policy); + if (proxy == null) + { + Method[] policyIFMethods = policy.getMethods(); + if (policyIFMethods.length != 1) + { + throw new PolicyException("Policy interface " + policy.getCanonicalName() + " must have only one method"); + } + + InvocationHandler handler = new JavaScriptInvocationHandler(this); + proxy = Proxy.newProxyInstance(policy.getClassLoader(), new Class[]{policy}, handler); + proxies.put(policy, proxy); + } + return (T)proxy; + } + + /** + * JavaScript Invocation Handler + * + * @author Roy Wetherall + */ + private static class JavaScriptInvocationHandler implements InvocationHandler + { + private ScriptBehaviour behaviour; + + private JavaScriptInvocationHandler(ScriptBehaviour behaviour) + { + this.behaviour = behaviour; + } + + /** + * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) + */ + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + // Handle Object level methods + if (method.getName().equals("toString")) + { + return toString(); + } + else if (method.getName().equals("hashCode")) + { + return hashCode(); + } + else if (method.getName().equals("equals")) + { + if (Proxy.isProxyClass(args[0].getClass())) + { + return equals(Proxy.getInvocationHandler(args[0])); + } + return false; + } + + // Delegate to designated method pointer + if (behaviour.isEnabled()) + { + try + { + behaviour.disable(); + return invokeScript(method, args); + } + finally + { + behaviour.enable(); + } + } + return null; + } + + private Object invokeScript(Method method, Object[] args) + { + // Build the model + Map model = new HashMap(1); + model.put("behaviour", new org.alfresco.repo.jscript.Behaviour(this.behaviour.serviceRegistry, method.getName(), args)); + + // Execute the script + return this.behaviour.serviceRegistry.getScriptService().executeScript(this.behaviour.location, model); + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) + { + return true; + } + else if (obj == null || !(obj instanceof JavaScriptInvocationHandler)) + { + return false; + } + JavaScriptInvocationHandler other = (JavaScriptInvocationHandler)obj; + return behaviour.location.equals(other.behaviour.location); + } + + @Override + public int hashCode() + { + return 37 * behaviour.location.hashCode(); + } + + @Override + public String toString() + { + return "JavaScriptBehaviour[location=" + behaviour.location.toString() + "]"; + } + } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptLogger.java b/source/java/org/alfresco/repo/jscript/ScriptLogger.java index fd0ae965aa..879f0c071e 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptLogger.java +++ b/source/java/org/alfresco/repo/jscript/ScriptLogger.java @@ -1,77 +1,77 @@ -package org.alfresco.repo.jscript; - -import org.alfresco.repo.processor.BaseProcessorExtension; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * @author Kevin Roast - */ -public final class ScriptLogger extends BaseProcessorExtension -{ - private static final Log logger = LogFactory.getLog(ScriptLogger.class); - private static final SystemOut systemOut = new SystemOut(); - - public boolean isLoggingEnabled() - { - return isDebugLoggingEnabled(); - } - - public void log(String str) - { - debug(str); - } - - public boolean isDebugLoggingEnabled() - { - return logger.isDebugEnabled(); - } - - public void debug(String str) - { - logger.debug(str); - } - - public boolean isInfoLoggingEnabled() - { - return logger.isInfoEnabled(); - } - - public void info(String str) - { - logger.info(str); - } - - public boolean isWarnLoggingEnabled() - { - return logger.isWarnEnabled(); - } - - public void warn(String str) - { - logger.warn(str); - } - - public boolean isErrorLoggingEnabled() - { - return logger.isErrorEnabled(); - } - - public void error(String str) - { - logger.error(str); - } - - public SystemOut getSystem() - { - return systemOut; - } - - public static class SystemOut - { - public void out(Object str) - { - System.out.println(str); - } - } -} +package org.alfresco.repo.jscript; + +import org.alfresco.repo.processor.BaseProcessorExtension; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Kevin Roast + */ +public final class ScriptLogger extends BaseProcessorExtension +{ + private static final Log logger = LogFactory.getLog(ScriptLogger.class); + private static final SystemOut systemOut = new SystemOut(); + + public boolean isLoggingEnabled() + { + return isDebugLoggingEnabled(); + } + + public void log(String str) + { + debug(str); + } + + public boolean isDebugLoggingEnabled() + { + return logger.isDebugEnabled(); + } + + public void debug(String str) + { + logger.debug(str); + } + + public boolean isInfoLoggingEnabled() + { + return logger.isInfoEnabled(); + } + + public void info(String str) + { + logger.info(str); + } + + public boolean isWarnLoggingEnabled() + { + return logger.isWarnEnabled(); + } + + public void warn(String str) + { + logger.warn(str); + } + + public boolean isErrorLoggingEnabled() + { + return logger.isErrorEnabled(); + } + + public void error(String str) + { + logger.error(str); + } + + public SystemOut getSystem() + { + return systemOut; + } + + public static class SystemOut + { + public void out(Object str) + { + System.out.println(str); + } + } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index a7aaca424b..c497036f8b 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -1,4103 +1,4103 @@ -package org.alfresco.repo.jscript; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.Reader; -import java.io.Serializable; -import java.nio.charset.Charset; -import java.text.Collator; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ApplicationModel; -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.query.PagingRequest; -import org.alfresco.query.PagingResults; -import org.alfresco.repo.action.executer.TransformActionExecuter; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.content.transform.UnimportantTransformException; -import org.alfresco.repo.content.transform.UnsupportedTransformationException; -import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; -import org.alfresco.repo.model.filefolder.FileFolderServiceImpl.InvalidTypeException; -import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; -import org.alfresco.repo.search.QueryParameterDefImpl; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.tagging.script.TagScope; -import org.alfresco.repo.thumbnail.ThumbnailDefinition; -import org.alfresco.repo.thumbnail.ThumbnailHelper; -import org.alfresco.repo.thumbnail.ThumbnailRegistry; -import org.alfresco.repo.thumbnail.script.ScriptThumbnail; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.repo.version.VersionModel; -import org.alfresco.repo.workflow.jscript.JscriptWorkflowInstance; -import org.alfresco.scripts.ScriptException; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.lock.LockStatus; -import org.alfresco.service.cmr.model.FileExistsException; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NoTransformerException; -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.TemplateImageResolver; -import org.alfresco.service.cmr.repository.TransformationOptions; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.search.QueryParameterDefinition; -import org.alfresco.service.cmr.security.AccessPermission; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.thumbnail.ThumbnailService; -import org.alfresco.service.cmr.version.Version; -import org.alfresco.service.cmr.version.VersionHistory; -import org.alfresco.service.cmr.version.VersionType; -import org.alfresco.service.cmr.workflow.WorkflowInstance; -import org.alfresco.service.cmr.workflow.WorkflowService; -import org.alfresco.service.namespace.NamespaceException; -import org.alfresco.service.namespace.NamespacePrefixResolver; -import org.alfresco.service.namespace.NamespacePrefixResolverProvider; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.util.FileFilterMode; -import org.alfresco.util.FileFilterMode.Client; -import org.alfresco.util.GUID; -import org.alfresco.util.ISO8601DateFormat; -import org.alfresco.util.ISO9075; -import org.alfresco.util.Pair; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONException; -import org.json.JSONObject; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.ScriptableObject; -import org.mozilla.javascript.UniqueTag; -import org.mozilla.javascript.Wrapper; -import org.springframework.extensions.surf.util.Content; -import org.springframework.extensions.surf.util.I18NUtil; -import org.springframework.extensions.surf.util.ParameterCheck; -import org.springframework.extensions.surf.util.URLEncoder; - -/** - * Script Node class implementation, specific for use by ScriptService as part of the object model. - *

- * The class exposes Node properties, children and assocs as dynamically populated maps and lists. The various collection classes are mirrored as JavaScript properties. So can be - * accessed using standard JavaScript property syntax, such as node.children[0].properties.name. - *

- * Various helper methods are provided to access common and useful node variables such as the content url and type information. - * - * @author Kevin Roast - */ -public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider -{ - private static final long serialVersionUID = -3378946227712939601L; - - private static Log logger = LogFactory.getLog(ScriptNode.class); - - private final static String NAMESPACE_BEGIN = "" + QName.NAMESPACE_BEGIN; - - private final static String CONTENT_DEFAULT_URL = "/d/d/{0}/{1}/{2}/{3}"; - private final static String CONTENT_DOWNLOAD_URL = "/d/a/{0}/{1}/{2}/{3}"; - private final static String CONTENT_PROP_URL = "/d/d/{0}/{1}/{2}/{3}?property={4}"; - private final static String CONTENT_DOWNLOAD_PROP_URL = "/d/a/{0}/{1}/{2}/{3}?property={4}"; - private final static String FOLDER_BROWSE_URL = "/n/browse/{0}/{1}/{2}"; - - /** Root scope for this object */ - protected Scriptable scope; - - /** Node Value Converter */ - protected NodeValueConverter converter = null; - - /** Cached values */ - protected NodeRef nodeRef; - - private FileInfo nodeInfo; - - private String name; - private QName type; - protected String id; - protected String siteName; - protected boolean siteNameResolved = false; - - /** The aspects applied to this node */ - protected Set aspects = null; - - /** The target associations from this node */ - private ScriptableQNameMap targetAssocs = null; - - /** The source associations to this node */ - private ScriptableQNameMap sourceAssocs = null; - - /** The child associations for this node */ - private ScriptableQNameMap childAssocs = null; - - /** The children of this node */ - private Scriptable children = null; - - /** The properties of this node */ - private ScriptableQNameMap properties = null; - - /** The versions of this node */ - private Scriptable versions = null; - - /** The active workflows acting on this node */ - private Scriptable activeWorkflows = null; - - protected ServiceRegistry services = null; - private NodeService nodeService = null; - private FileFolderService fileFolderService = null; - private RetryingTransactionHelper retryingTransactionHelper = null; - private Boolean isDocument = null; - private Boolean isContainer = null; - private Boolean isLinkToDocument = null; - private Boolean isLinkToContainer = null; - private Boolean hasChildren = null; - private String displayPath = null; - private String qnamePath = null; - protected TemplateImageResolver imageResolver = null; - protected ScriptNode parent = null; - private ChildAssociationRef primaryParentAssoc = null; - private ScriptableQNameMap parentAssocs = null; - // NOTE: see the reset() method when adding new cached members! - - - // ------------------------------------------------------------------------------ - // Construction - - /** - * Constructor - * - * @param nodeRef The NodeRef this Node wrapper represents - * @param services The ServiceRegistry the Node can use to access services - */ - public ScriptNode(NodeRef nodeRef, ServiceRegistry services) - { - this(nodeRef, services, null); - } - - /** - * Constructor - * - * @param nodeInfo The FileInfo this Node wrapper represents - * @param services The ServiceRegistry the Node can use to access services - * @param scope Root scope for this Node - */ - public ScriptNode(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope) - { - this(nodeInfo.getNodeRef(), services, scope); - - this.nodeInfo = nodeInfo; - } - - /** - * Constructor - * - * @param nodeRef The NodeRef this Node wrapper represents - * @param services The ServiceRegistry the Node can use to access services - * @param scope Root scope for this Node - */ - public ScriptNode(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) - { - if (nodeRef == null) - { - throw new IllegalArgumentException("NodeRef must be supplied."); - } - - if (services == null) - { - throw new IllegalArgumentException("The ServiceRegistry must be supplied."); - } - - this.nodeRef = nodeRef; - this.id = nodeRef.getId(); - this.services = services; - this.nodeService = services.getNodeService(); - this.fileFolderService = services.getFileFolderService(); - this.retryingTransactionHelper = services.getTransactionService().getRetryingTransactionHelper(); - this.scope = scope; - } - - @Override - public int hashCode() - { - final int PRIME = 31; - int result = 1; - result = PRIME * result + ((nodeRef == null) ? 0 : nodeRef.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - if (!nodeRef.equals(((ScriptNode)obj).nodeRef)) return false; - return true; - } - - /** - * Factory method - */ - public ScriptNode newInstance(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) - { - return new ScriptNode(nodeRef, services, scope); - } - - public ScriptNode newInstance(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope) - { - return new ScriptNode(nodeInfo, services, scope); - } - - /** - * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) - */ - public void setScope(Scriptable scope) - { - this.scope = scope; - } - - - // ------------------------------------------------------------------------------ - // Node Wrapper API - - /** - * @return The GUID for the node - */ - public String getId() - { - return this.id; - } - - /** - * @return the store type for the node - */ - public String getStoreType() - { - return this.nodeRef.getStoreRef().getProtocol(); - } - - /** - * @return the store id for the node - */ - public String getStoreId() - { - return this.nodeRef.getStoreRef().getIdentifier(); - } - - /** - * @return Returns the NodeRef this Node object represents - */ - public NodeRef getNodeRef() - { - return this.nodeRef; - } - - /** - * @return Returns the QName type. - */ - public QName getQNameType() - { - if (this.type == null) - { - this.type = this.nodeService.getType(this.nodeRef); - } - - return type; - } - - /** - * @return Returns the type. - */ - public String getType() - { - return getQNameType().toString(); - } - - /** - * @return Returns the type in short format. - */ - public String getTypeShort() - { - return this.getShortQName(getQNameType()); - } - - /** - * @return Helper to return the 'name' property for the node - */ - public String getName() - { - if (this.name == null) - { - // try and get the name from the properties first - this.name = (String) getProperties().get("cm:name"); - - // if we didn't find it as a property get the name from the association name - if (this.name == null) - { - ChildAssociationRef parentRef = this.nodeService.getPrimaryParent(this.nodeRef); - if (parentRef != null && parentRef.getQName() != null) - { - this.name = parentRef.getQName().getLocalName(); - } - else - { - this.name = ""; - } - } - } - - return this.name; - } - - /** - * Helper to set the 'name' property for the node. - * - * @param name Name to set - */ - public void setName(String name) - { - if (name != null) - { - QName typeQName = getQNameType(); - if ((services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_FOLDER) && - !services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_SYSTEM_FOLDER)) || - services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_CONTENT)) - { - try - { - this.services.getFileFolderService().rename(this.nodeRef, name); - } - catch (FileNotFoundException e) - { - throw new AlfrescoRuntimeException("Failed to rename node " + nodeRef + " to " + name, e); - } - } - this.getProperties().put(ContentModel.PROP_NAME.toString(), name.toString()); - } - } - - /** - * @return The children of this Node as JavaScript array of Node object wrappers - */ - public Scriptable getChildren() - { - if (this.children == null) - { - List childRefs = this.nodeService.getChildAssocs(this.nodeRef); - Object[] children = new Object[childRefs.size()]; - for (int i = 0; i < childRefs.size(); i++) - { - // create our Node representation from the NodeRef - children[i] = newInstance(childRefs.get(i).getChildRef(), this.services, this.scope); - } - - // Do a locale-sensitive sort by name - sort(children); - - this.children = Context.getCurrentContext().newArray(this.scope, children); - this.hasChildren = (children.length != 0); - } - - return this.children; - } - - /** - * Performs a locale-sensitive sort by name of a node array - * @param nodes the node array - */ - private static void sort(Object[] nodes) - { - final Collator col = Collator.getInstance(I18NUtil.getLocale()); - Arrays.sort(nodes, new Comparator(){ - @Override - public int compare(Object o1, Object o2) - { - return col.compare(((ScriptNode)o1).getName(), ((ScriptNode)o2).getName()); - }}); - } - - /** - * @return true if the Node has children - */ - public boolean getHasChildren() - { - if (this.hasChildren == null) - { - this.hasChildren = !this.services.getNodeService().getChildAssocs( - getNodeRef(), RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false).isEmpty(); - } - return hasChildren; - } - - /** - * childByNamePath returns the Node at the specified 'cm:name' based Path walking the children of this Node. - * So a valid call might be: - * mynode.childByNamePath("/QA/Testing/Docs"); - * - * @param path the relative path of the descendant node to find e.g. {@code "/QA/Testing/Docs"} - * @return The ScriptNode or {@code null} if the node is not found. - * {@code null} if the specified path is {@code ""}. - * @throws NullPointerException if the provided path is {@code null}. - */ - public ScriptNode childByNamePath(String path) - { - // Ensure that paths that do not represent descendants are not needlessly tokenised. See ALF-20896. - if (path == null) { throw new NullPointerException("Illegal null path"); } - else if (path.isEmpty()) { return null; } - - // We have a path worth looking at... - ScriptNode child = null; - - if (this.services.getDictionaryService().isSubClass(getQNameType(), ContentModel.TYPE_FOLDER)) - { - // The current node is a folder e.g. Company Home and standard child folders. - // optimized code path for cm:folder and sub-types supporting getChildrenByName() method - final StringTokenizer t = new StringTokenizer(path, "/"); - // allow traversal of a cm:name based path even if user cannot retrieve the node directly - NodeRef result = AuthenticationUtil.runAs(new RunAsWork() - { - @Override - public NodeRef doWork() throws Exception - { - NodeRef child = ScriptNode.this.nodeRef; - while (t.hasMoreTokens() && child != null) - { - String name = t.nextToken(); - child = nodeService.getChildByName(child, ContentModel.ASSOC_CONTAINS, name); - } - return child; - } - }, AuthenticationUtil.getSystemUserName()); - - // final node must be accessible to the user via the usual ACL permission checks - if (result != null - && services.getPublicServiceAccessService().hasAccess("NodeService", "getProperties", result) != AccessStatus.ALLOWED) - { - result = null; - } - - child = (result != null ? newInstance(result, this.services, this.scope) : null); - } - else - { - // The current node is not a folder. It does not support the cm:contains association. - // Convert the name based path to a valid XPath query - StringBuilder xpath = new StringBuilder(path.length() << 1); - StringTokenizer t = new StringTokenizer(path, "/"); - int count = 0; - QueryParameterDefinition[] params = new QueryParameterDefinition[t.countTokens()]; - DataTypeDefinition ddText = - this.services.getDictionaryService().getDataType(DataTypeDefinition.TEXT); - NamespaceService ns = this.services.getNamespaceService(); - while (t.hasMoreTokens()) - { - if (xpath.length() != 0) - { - xpath.append('/'); - } - String strCount = Integer.toString(count); - xpath.append("*[@cm:name=$cm:name") - .append(strCount) - .append(']'); - params[count++] = new QueryParameterDefImpl( - QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, "name" + strCount, ns), - ddText, - true, - t.nextToken()); - } - - Object[] nodes = getChildrenByXPath(xpath.toString(), params, true); - - child = (nodes.length != 0) ? (ScriptNode)nodes[0] : null; - } - - return child; - } - - /** - * @return Returns a JavaScript array of Nodes at the specified XPath starting at this Node. - * So a valid call might be mynode.childrenByXPath("*[@cm:name='Testing']/*"); - */ - public Scriptable childrenByXPath(String xpath) - { - return Context.getCurrentContext().newArray(this.scope, getChildrenByXPath(xpath, null, false)); - } - - /** - * @return Returns a JavaScript array of child file/folder nodes for this nodes. - * Automatically retrieves all sub-types of cm:content and cm:folder, also removes - * system folder types from the results. - * This is equivalent to @see FileFolderService.list() - */ - public Scriptable childFileFolders() - { - return childFileFolders(true, true, null); - } - - /** - * @param files Return files extending from cm:content - * @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder - * - * @return Returns a JavaScript array of child file/folder nodes for this nodes. - * Automatically retrieves all sub-types of cm:content and cm:folder, also removes - * system folder types from the results. - * This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders() - */ - public Scriptable childFileFolders(boolean files, boolean folders) - { - return childFileFolders(files, folders, null); - } - - /** - * @param files Return files extending from cm:content - * @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder - * @param ignoreTypes Also optionally removes additional type qnames. The additional type can be - * specified in short or long qname string form as a single string or an Array e.g. "fm:forum". - * - * @return Returns a JavaScript array of child file/folder nodes for this nodes. - * Automatically retrieves all sub-types of cm:content and cm:folder, also removes - * system folder types from the results. - * This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders() - */ - public Scriptable childFileFolders(boolean files, boolean folders, Object ignoreTypes) - { - return childFileFolders(files, folders, ignoreTypes, -1, -1, 0, null, null, null).getPage(); - } - - /** - * @param files Return files extending from cm:content - * @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder - * @param ignoreTypes Also optionally removes additional type qnames. The additional type can be - * specified in short or long qname string form as a single string or an Array e.g. "fm:forum". - * @param maxItems Max number of items - * - * @return Returns ScriptPagingNodes which includes a JavaScript array of child file/folder nodes for this nodes. - * Automatically retrieves all sub-types of cm:content and cm:folder, also removes - * system folder types from the results. - * This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders() - * - * @deprecated API for review (subject to change prior to release) - * - *
author janv - * @since 4.0 - */ - public ScriptPagingNodes childFileFolders(boolean files, boolean folders, Object ignoreTypes, int maxItems) - { - return childFileFolders(files, folders, ignoreTypes, 0, maxItems, 0, null, null, null); - } - - @SuppressWarnings("unchecked") - /** - * @param files Return files extending from cm:content - * @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder - * @param ignoreTypes Also optionally removes additional type qnames. The additional type can be - * specified in short or long qname string form as a single string or an Array e.g. "fm:forum". - * @param skipOffset Items to skip (e.g. 0 or (num pages to skip * size of page) - * @param maxItems Max number of items (eg. size of page) - * @param requestTotalCountMax Request total count (upto a given max total count) - * Note: if 0 then total count is not requested and the query may be able to optimise/cutoff for max items) - * @param sortProp Optional sort property as a prefix qname string (e.g. "cm:name"). Also supports special - * content case (i.e. "cm:content.size" and "cm:content.mimetype") - * @param sortAsc Given a sort property, true => ascending, false => descending - * @param queryExecutionId If paging then can pass back the previous query execution (as a hint for possible query optimisation) - * - * @return Returns ScriptPagingNodes which includes a JavaScript array of child file/folder nodes for this nodes. - * Automatically retrieves all sub-types of cm:content and cm:folder, also removes - * system folder types from the results. - * This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders() - * - *

author janv - * @since 4.0 - */ - public ScriptPagingNodes childFileFolders(boolean files, boolean folders, Object ignoreTypes, int skipOffset, int maxItems, int requestTotalCountMax, String sortProp, Boolean sortAsc, String queryExecutionId) - { - Object[] results; - - Set ignoreTypeQNames = new HashSet(5); - - // Add user defined types to ignore - if (ignoreTypes instanceof ScriptableObject) - { - Serializable types = getValueConverter().convertValueForRepo((ScriptableObject)ignoreTypes); - if (types instanceof List) - { - for (Serializable typeObj : (List)types) - { - ignoreTypeQNames.add(createQName(typeObj.toString())); - } - } - else if (types instanceof String) - { - ignoreTypeQNames.add(createQName(types.toString())); - } - } - else if (ignoreTypes instanceof String) - { - ignoreTypeQNames.add(createQName(ignoreTypes.toString())); - } - - // ALF-13968 - sort folders before files (for Share) - TODO should be optional sort param - List> sortProps = new ArrayList>(2); - if ((sortProp == null) || (! sortProp.equals(GetChildrenCannedQuery.SORT_QNAME_NODE_TYPE.getLocalName()))) - { - sortProps.add(new Pair(GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER, false)); - } - if (sortProp != null) - { - sortProps.add(new Pair(createQName(sortProp), sortAsc)); - } - - PagingRequest pageRequest = new PagingRequest(skipOffset, maxItems, queryExecutionId); - pageRequest.setRequestTotalCountMax(requestTotalCountMax); - - PagingResults pageOfNodeInfos = null; - FileFilterMode.setClient(Client.script); - try - { - pageOfNodeInfos = this.fileFolderService.list(this.nodeRef, files, folders, null, ignoreTypeQNames, sortProps, pageRequest); - } - finally - { - FileFilterMode.clearClient(); - } - - List nodeInfos = pageOfNodeInfos.getPage(); - int size = nodeInfos.size(); - results = new Object[size]; - for (int i=0; i totalResultCount = pageOfNodeInfos.getTotalResultCount(); - if (totalResultCount != null) - { - totalResultCountLower = (totalResultCount.getFirst() != null ? totalResultCount.getFirst() : -1); - totalResultCountUpper = (totalResultCount.getSecond() != null ? totalResultCount.getSecond() : -1); - } - - return new ScriptPagingNodes(Context.getCurrentContext().newArray(this.scope, results), pageOfNodeInfos.hasMoreItems(), totalResultCountLower, totalResultCountUpper); - } - - /** - * Return the target associations from this Node. As a Map of assoc type to a JavaScript array of Nodes. - * The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript - * associative array access. This means associations of this node can be access thus: - * node.assocs["translations"][0] - * - * @return target associations as a Map of assoc name to a JavaScript array of Nodes. - */ - @SuppressWarnings("unchecked") - public Map getAssocs() - { - if (this.targetAssocs == null) - { - // this Map implements the Scriptable interface for native JS syntax property access - this.targetAssocs = new ScriptableQNameMap(this); - - // get the list of target nodes for each association type - List refs = this.nodeService.getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); - for (AssociationRef ref : refs) - { - String qname = ref.getTypeQName().toString(); - List nodes = (List)this.targetAssocs.get(qname); - if (nodes == null) - { - // first access of the list for this qname - nodes = new ArrayList(4); - } - this.targetAssocs.put(ref.getTypeQName().toString(), nodes); - nodes.add(newInstance(ref.getTargetRef(), this.services, this.scope)); - } - - // convert each Node list into a JavaScript array object - for (String qname : this.targetAssocs.keySet()) - { - List nodes = (List)this.targetAssocs.get(qname); - Object[] objs = nodes.toArray(new Object[nodes.size()]); - this.targetAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); - } - } - - return this.targetAssocs; - } - - public Map getAssociations() - { - return getAssocs(); - } - - /** - * Return the source associations to this Node. As a Map of assoc name to a JavaScript array of Nodes. - * The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript - * associative array access. This means source associations to this node can be access thus: - * node.sourceAssocs["translations"][0] - * - * @return source associations as a Map of assoc name to a JavaScript array of Nodes. - */ - @SuppressWarnings("unchecked") - public Map getSourceAssocs() - { - if (this.sourceAssocs == null) - { - // this Map implements the Scriptable interface for native JS syntax property access - this.sourceAssocs = new ScriptableQNameMap(this); - - // get the list of source nodes for each association type - List refs = this.nodeService.getSourceAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); - for (AssociationRef ref : refs) - { - String qname = ref.getTypeQName().toString(); - List nodes = (List)this.sourceAssocs.get(qname); - if (nodes == null) - { - // first access of the list for this qname - nodes = new ArrayList(4); - this.sourceAssocs.put(ref.getTypeQName().toString(), nodes); - } - nodes.add(newInstance(ref.getSourceRef(), this.services, this.scope)); - } - - // convert each Node list into a JavaScript array object - for (String qname : this.sourceAssocs.keySet()) - { - List nodes = (List)this.sourceAssocs.get(qname); - Object[] objs = nodes.toArray(new Object[nodes.size()]); - this.sourceAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); - } - } - - return this.sourceAssocs; - } - - public Map getSourceAssociations() - { - return getSourceAssocs(); - } - - /** - * Return the child associations from this Node. As a Map of assoc name to a JavaScript array of Nodes. - * The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript - * associative array access. This means associations of this node can be access thus: - * node.childAssocs["contains"][0] - * - * @return child associations as a Map of assoc name to a JavaScript array of Nodes. - */ - @SuppressWarnings("unchecked") - public Map getChildAssocs() - { - if (this.childAssocs == null) - { - // this Map implements the Scriptable interface for native JS syntax property access - this.childAssocs = new ScriptableQNameMap(this); - - // get the list of child assoc nodes for each association type - List refs = this.nodeService.getChildAssocs(nodeRef); - for (ChildAssociationRef ref : refs) - { - String qname = ref.getTypeQName().toString(); - List nodes = (List)this.childAssocs.get(qname); - if (nodes == null) - { - // first access of the list for this qname - nodes = new ArrayList(4); - this.childAssocs.put(ref.getTypeQName().toString(), nodes); - } - nodes.add(newInstance(ref.getChildRef(), this.services, this.scope)); - } - - // convert each Node list into a JavaScript array object - for (String qname : this.childAssocs.keySet()) - { - List nodes = (List)this.childAssocs.get(qname); - Object[] objs = nodes.toArray(new Object[nodes.size()]); - this.childAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); - } - } - - return this.childAssocs; - } - - public Map getChildAssociations() - { - return getChildAssocs(); - } - - /** - * Return an Array of the associations from this Node that match a specific object type. - * node.getChildAssocsByType("cm:folder")[0] - * - * @return Array of child associations from this Node that match a specific object type. - */ - public Scriptable getChildAssocsByType(String type) - { - // get the list of child assoc nodes for each association type - Set types = new HashSet(1, 1.0f); - types.add(createQName(type)); - List refs = this.nodeService.getChildAssocs(this.nodeRef, types); - Object[] nodes = new Object[refs.size()]; - for (int i=0; inode.parentAssocs["contains"][0] - * - * @return parent associations as a Map of assoc name to a JavaScript array of Nodes. - */ - @SuppressWarnings("unchecked") - public Map getParentAssocs() - { - if (this.parentAssocs == null) - { - // this Map implements the Scriptable interface for native JS syntax property access - this.parentAssocs = new ScriptableQNameMap(this); - - // get the list of child assoc nodes for each association type - List refs = this.nodeService.getParentAssocs(nodeRef); - for (ChildAssociationRef ref : refs) - { - String qname = ref.getTypeQName().toString(); - List nodes = (List)this.parentAssocs.get(qname); - if (nodes == null) - { - // first access of the list for this qname - nodes = new ArrayList(4); - this.parentAssocs.put(ref.getTypeQName().toString(), nodes); - } - nodes.add(newInstance(ref.getParentRef(), this.services, this.scope)); - } - - // convert each Node list into a JavaScript array object - for (String qname : this.parentAssocs.keySet()) - { - List nodes = (List)this.parentAssocs.get(qname); - Object[] objs = nodes.toArray(new Object[nodes.size()]); - this.parentAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); - } - } - - return this.parentAssocs; - } - - public Map getParentAssociations() - { - return getParentAssocs(); - } - - /** - * Checks whether the {@link ScriptNode} exists in the repository. - * @return boolean - */ - public boolean exists() - { - return nodeService.exists(nodeRef); - } - - /** - * Return all the properties known about this node. The Map returned implements the Scriptable interface to - * allow access to the properties via JavaScript associative array access. This means properties of a node can - * be access thus: node.properties["name"] - * - * @return Map of properties for this Node. - */ - @SuppressWarnings("unchecked") - public Map getProperties() - { - if (this.properties == null) - { - // this Map implements the Scriptable interface for native JS syntax property access - // this impl of the QNameMap is capable of creating ScriptContentData on demand for 'cm:content' - // properties that have not been initialised - see AR-1673. - this.properties = new ContentAwareScriptableQNameMap(this, this.services); - - Map props = null; - if (this.nodeInfo != null) - { - props = this.nodeInfo.getProperties(); - } - else - { - props = this.nodeService.getProperties(this.nodeRef); - } - - for (QName qname : props.keySet()) - { - Serializable propValue = props.get(qname); - - // perform the conversion to a script safe value and store - this.properties.put(qname.toString(), getValueConverter().convertValueForScript(qname, propValue)); - } - } - - return this.properties; - } - - /** - * Return all the property names defined for this node's type as an array of short QNames. - * - * @return Array of property names for this node's type. - */ - public Scriptable getTypePropertyNames() - { - return getTypePropertyNames(true); - } - - /** - * Return all the property names defined for this node's type as an array. - * - * @param useShortQNames if true short-form qnames will be returned, else long-form. - * @return Array of property names for this node's type. - */ - public Scriptable getTypePropertyNames(boolean useShortQNames) - { - Set props = this.services.getDictionaryService().getClass(this.getQNameType()).getProperties().keySet(); - Object[] result = new Object[props.size()]; - int count = 0; - for (QName qname : props) - { - result[count++] = useShortQNames ? getShortQName(qname).toString() : qname.toString(); - } - return Context.getCurrentContext().newArray(this.scope, result); - } - - /** - * Return all the property names defined for this node as an array. - * - * @param useShortQNames if true short-form qnames will be returned, else long-form. - * @return Array of property names for this node type and optionally parent properties. - */ - public Scriptable getPropertyNames(boolean useShortQNames) - { - Set props = this.nodeService.getProperties(this.nodeRef).keySet(); - Object[] result = new Object[props.size()]; - int count = 0; - for (QName qname : props) - { - result[count++] = useShortQNames ? getShortQName(qname).toString() : qname.toString(); - } - return Context.getCurrentContext().newArray(this.scope, result); - } - - /** - * @return true if this Node is a container (i.e. a folder) - */ - public boolean getIsContainer() - { - if (isContainer == null) - { - DictionaryService dd = this.services.getDictionaryService(); - isContainer = Boolean.valueOf((dd.isSubClass(getQNameType(), ContentModel.TYPE_FOLDER) == true && - dd.isSubClass(getQNameType(), ContentModel.TYPE_SYSTEM_FOLDER) == false)); - } - - return isContainer.booleanValue(); - } - - /** - * @return true if this Node is a Document (i.e. with content) - */ - public boolean getIsDocument() - { - if (isDocument == null) - { - DictionaryService dd = this.services.getDictionaryService(); - isDocument = Boolean.valueOf(dd.isSubClass(getQNameType(), ContentModel.TYPE_CONTENT)); - } - - return isDocument.booleanValue(); - } - - /** - * @return true if this Node is a Link to a Container (i.e. a folderlink) - */ - public boolean getIsLinkToContainer() - { - if (isLinkToContainer == null) - { - DictionaryService dd = this.services.getDictionaryService(); - isLinkToContainer = Boolean.valueOf(dd.isSubClass(getQNameType(), ApplicationModel.TYPE_FOLDERLINK)); - } - - return isLinkToContainer.booleanValue(); - } - - /** - * @return true if this Node is a Link to a Document (i.e. a filelink) - */ - public boolean getIsLinkToDocument() - { - if (isLinkToDocument == null) - { - DictionaryService dd = this.services.getDictionaryService(); - isLinkToDocument = Boolean.valueOf(dd.isSubClass(getQNameType(), ApplicationModel.TYPE_FILELINK)); - } - - return isLinkToDocument.booleanValue(); - } - - /** - * @return true if the Node is a Category - */ - public boolean getIsCategory() - { - // this valid is overriden by the CategoryNode sub-class - return false; - } - - /** - * @return The list of aspects applied to this node - */ - public Set getAspectsSet() - { - if (this.aspects == null) - { - this.aspects = this.nodeService.getAspects(this.nodeRef); - } - - return this.aspects; - } - - /** - * @return The array of aspects applied to this node as fully qualified qname strings - */ - public Scriptable getAspects() - { - Set aspects = getAspectsSet(); - Object[] result = new Object[aspects.size()]; - int count = 0; - for (QName qname : aspects) - { - result[count++] = qname.toString(); - } - return Context.getCurrentContext().newArray(this.scope, result); - } - - /** - * @return The array of aspects applied to this node as short prefix qname strings - */ - public Scriptable getAspectsShort() - { - final NamespaceService ns = this.services.getNamespaceService(); - final Map cache = new HashMap(); - final Set aspects = getAspectsSet(); - final Object[] result = new Object[aspects.size()]; - int count = 0; - for (final QName qname : aspects) - { - String prefix = cache.get(qname.getNamespaceURI()); - if (prefix == null) - { - // first request for this namespace prefix, get and cache result - Collection prefixes = ns.getPrefixes(qname.getNamespaceURI()); - prefix = prefixes.size() != 0 ? prefixes.iterator().next() : ""; - cache.put(qname.getNamespaceURI(), prefix); - } - result[count++] = prefix + QName.NAMESPACE_PREFIX + qname.getLocalName(); - } - return Context.getCurrentContext().newArray(this.scope, result); - } - - /** - * @param aspect The aspect name to test for (fully qualified or short-name form) - * @return true if the node has the aspect false otherwise - */ - public boolean hasAspect(String aspect) - { - return getAspectsSet().contains(createQName(aspect)); - } - - /** - * @param type The qname type to test this object against (fully qualified or short-name form) - * @return true if this Node is a sub-type of the specified class (or itself of that class) - */ - public boolean isSubType(String type) - { - ParameterCheck.mandatoryString("Type", type); - - QName qnameType = createQName(type); - - return this.services.getDictionaryService().isSubClass(getQNameType(), qnameType); - } - - /** - * @return QName path to this node. This can be used for Lucene PATH: style queries - */ - public String getQnamePath() - { - if (this.qnamePath == null) - { - final NamespaceService ns = this.services.getNamespaceService(); - final Map cache = new HashMap(); - final StringBuilder buf = new StringBuilder(128); - final Path path = this.services.getNodeService().getPath(getNodeRef()); - for (final Path.Element e : path) - { - if (e instanceof Path.ChildAssocElement) - { - final QName qname = ((Path.ChildAssocElement)e).getRef().getQName(); - if (qname != null) - { - String prefix = cache.get(qname.getNamespaceURI()); - if (prefix == null) - { - // first request for this namespace prefix, get and cache result - Collection prefixes = ns.getPrefixes(qname.getNamespaceURI()); - prefix = prefixes.size() != 0 ? prefixes.iterator().next() : ""; - cache.put(qname.getNamespaceURI(), prefix); - } - buf.append('/').append(prefix).append(':').append(ISO9075.encode(qname.getLocalName())); - } - } - else - { - buf.append('/').append(e.toString()); - } - } - this.qnamePath = buf.toString(); - } - - return this.qnamePath; - } - - /** - * @return Display path to this node - */ - public String getDisplayPath() - { - if (this.displayPath == null) - { - this.displayPath = this.nodeService.getPath(this.nodeRef).toDisplayPath( - this.nodeService, this.services.getPermissionService()); - } - - return this.displayPath; - } - - /** - * @return the small icon image for this node - */ - public String getIcon16() - { - return "/images/filetypes/_default.gif"; - } - - /** - * @return the large icon image for this node - */ - public String getIcon32() - { - return "/images/filetypes32/_default.gif"; - } - - /** - * @return true if the node is currently locked - */ - public boolean getIsLocked() - { - boolean locked = false; - - if (getAspectsSet().contains(ContentModel.ASPECT_LOCKABLE)) - { - LockStatus status = this.services.getLockService().getLockStatus(this.nodeRef); - if (status == LockStatus.LOCKED || status == LockStatus.LOCK_OWNER) - { - locked = true; - } - } - - return locked; - } - - /** - * @return the primary parent node - */ - public ScriptNode getParent() - { - if (parent == null) - { - NodeRef parentRef = getPrimaryParentAssoc().getParentRef(); - // handle root node (no parent!) - if (parentRef != null) - { - parent = newInstance(parentRef, this.services, this.scope); - } - } - - return parent; - } - - /** - * @return all parent nodes - */ - public Scriptable getParents() - { - List parentRefs = this.nodeService.getParentAssocs(this.nodeRef); - Object[] parents = new Object[parentRefs.size()]; - for (int i = 0; i < parentRefs.size(); i++) - { - NodeRef ref = parentRefs.get(i).getParentRef(); - parents[i] = newInstance(ref, this.services, this.scope); - } - return Context.getCurrentContext().newArray(this.scope, parents); - } - - /** - * @return the primary parent association so we can get at the association QName and the association type QName. - */ - public ChildAssociationRef getPrimaryParentAssoc() - { - if (primaryParentAssoc == null) - { - primaryParentAssoc = this.nodeService.getPrimaryParent(nodeRef); - } - return primaryParentAssoc; - } - - - // ------------------------------------------------------------------------------ - // Content API - - /** - * @return the content String for this node from the default content property (@see ContentModel.PROP_CONTENT) - */ - public String getContent() - { - String content = ""; - - ScriptContentData contentData = (ScriptContentData)getProperties().get(ContentModel.PROP_CONTENT); - if (contentData != null) - { - content = contentData.getContent(); - } - - return content; - } - - /** - * Set the content for this node - * - * @param content Content string to set - */ - public void setContent(String content) - { - ScriptContentData contentData = (ScriptContentData)getProperties().get(ContentModel.PROP_CONTENT); - if (contentData != null) - { - contentData.setContent(content); - } - } - - /** - * @return For a content document, this method returns the URL to the content stream for the default content - * property (@see ContentModel.PROP_CONTENT) - *

- * For a container node, this method return the URL to browse to the folder in the web-client - */ - public String getUrl() - { - if (getIsDocument() == true) - { - return MessageFormat.format(CONTENT_DEFAULT_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(), - URLEncoder.encode(getName())}); - } - else - { - return MessageFormat.format(FOLDER_BROWSE_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), nodeRef.getId() }); - } - } - - /** - * @return For a content document, this method returns the download URL to the content for - * the default content property (@see ContentModel.PROP_CONTENT) - *

- * For a container node, this method returns an empty string - */ - public String getDownloadUrl() - { - if (getIsDocument() == true) - { - return MessageFormat.format(CONTENT_DOWNLOAD_URL, new Object[] { - nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), - nodeRef.getId(), - URLEncoder.encode(getName()) }); - } - else - { - return ""; - } - } - - public String jsGet_downloadUrl() - { - return getDownloadUrl(); - } - - /** - * @return The WebDav cm:name based path to the content for the default content property - * (@see ContentModel.PROP_CONTENT) - */ - public String getWebdavUrl() - { - String url = ""; - try - { - if (getIsContainer() || getIsDocument()) - { - List paths = this.services.getFileFolderService().getNameOnlyPath(null, getNodeRef()); - - // build up the webdav url - StringBuilder path = new StringBuilder(128); - path.append("/webdav"); - - // build up the path skipping the first path as it is the root folder - for (int i=1; i - * The default permissions are found in org.alfresco.service.cmr.security.PermissionService. - * Most commonly used are "Write", "Delete" and "AddChildren". - * - * @param permission as found in org.alfresco.service.cmr.security.PermissionService - * @return true if the user has the specified permission on the node. - */ - public boolean hasPermission(String permission) - { - ParameterCheck.mandatory("Permission Name", permission); - - boolean allowed = false; - - if (permission != null && permission.length() != 0) - { - AccessStatus status = this.services.getPermissionService().hasPermission(this.nodeRef, permission); - allowed = (AccessStatus.ALLOWED == status); - } - - return allowed; - } - - /** - * @return Array of permissions applied to this Node, including inherited. - * Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION for example - * ALLOWED;kevinr;Consumer so can be easily tokenized on the ';' character. - */ - public Scriptable getPermissions() - { - return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(false, false)); - } - - /** - * @return Array of permissions applied directly to this Node (does not include inherited). - * Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION for example - * ALLOWED;kevinr;Consumer so can be easily tokenized on the ';' character. - */ - public Scriptable getDirectPermissions() - { - return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(true, false)); - } - - /** - * @return Array of all permissions applied to this Node, including inherited. - * Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION;[INHERITED|DIRECT] - * for example: ALLOWED;kevinr;Consumer;DIRECT so can be easily tokenized on the ';' character. - */ - public Scriptable getFullPermissions() - { - return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(false, true)); - } - - /** - * @return Sorted list of AccessPermission based on CMISConnector.AccessPermissionComparator - * and AccessStatus of the permission for an authority. - */ - public static List getSortedACLs(Set acls) - { - ArrayList ordered = new ArrayList(acls); - Map deDuplicatedPermissions = new HashMap(acls.size()); - Collections.sort(ordered, new CMISConnector.AccessPermissionComparator()); - for (AccessPermission current : ordered) - { - String composedKey = current.getAuthority() + current.getPermission(); - if (current.getAccessStatus() == AccessStatus.ALLOWED) - { - deDuplicatedPermissions.put(composedKey, current); - } - else if (current.getAccessStatus() == AccessStatus.DENIED) - { - deDuplicatedPermissions.remove(composedKey); - } - } - - return new ArrayList(deDuplicatedPermissions.values()); - } - - /** - * Helper to construct the response object for the various getPermissions() calls. - * - * @param direct True to only retrieve direct permissions, false to get inherited also - * @param full True to retrieve full data string with [INHERITED|DIRECT] element - * This exists to maintain backward compatibility with existing permission APIs. - * - * @return Object[] of packed permission strings. - */ - protected Object[] retrieveAllSetPermissions(boolean direct, boolean full) - { - Set acls = this.services.getPermissionService().getAllSetPermissions(getNodeRef()); - List permissions = new ArrayList(acls.size()); - List ordered = getSortedACLs(acls); - for (AccessPermission permission : ordered) - { - if (!direct || permission.isSetDirectly()) - { - StringBuilder buf = new StringBuilder(64); - buf.append(permission.getAccessStatus()) - .append(';') - .append(permission.getAuthority()) - .append(';') - .append(permission.getPermission()); - if (full) - { - buf.append(';').append(permission.isSetDirectly() ? "DIRECT" : "INHERITED"); - } - permissions.add(buf.toString()); - } - } - return (Object[])permissions.toArray(new Object[permissions.size()]); - } - - /** - * @return Array of settable permissions for this Node - */ - public Scriptable getSettablePermissions() - { - Set permissions = this.services.getPermissionService().getSettablePermissions(getNodeRef()); - Object[] result = permissions.toArray(new Object[0]); - return Context.getCurrentContext().newArray(this.scope, result); - } - - /** - * @return true if the node inherits permissions from the parent node, false otherwise - */ - public boolean inheritsPermissions() - { - return this.services.getPermissionService().getInheritParentPermissions(this.nodeRef); - } - - /** - * Set whether this node should inherit permissions from the parent node. - * - * @param inherit True to inherit parent permissions, false otherwise. - */ - public void setInheritsPermissions(boolean inherit) - { - this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit); - } - - /** - * Set whether this node should inherit permissions from the parent node. If the operation takes - * too long and asyncCall parameter set accordingly, fixed ACLs method will be asynchronously called. - * - * @param inherit True to inherit parent permissions, false otherwise. - * @param asyncCall True if fixed ACLs should be asynchronously set when operation execution takes too long, false otherwise. - */ - public void setInheritsPermissions(boolean inherit, boolean asyncCall) - { - this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit, asyncCall); - } - - /** - * Apply a permission for ALL users to the node. - * - * @param permission Permission to apply - * @see org.alfresco.service.cmr.security.PermissionService - */ - public void setPermission(String permission) - { - ParameterCheck.mandatoryString("Permission Name", permission); - this.services.getPermissionService().setPermission( - this.nodeRef, PermissionService.ALL_AUTHORITIES, permission, true); - } - - /** - * Apply a permission for the specified authority (e.g. username or group) to the node. - * - * @param permission Permission to apply @see org.alfresco.service.cmr.security.PermissionService - * @param authority Authority (generally a username or group name) to apply the permission for - */ - public void setPermission(String permission, String authority) - { - ParameterCheck.mandatoryString("Permission Name", permission); - ParameterCheck.mandatoryString("Authority", authority); - this.services.getPermissionService().setPermission( - this.nodeRef, authority, permission, true); - } - - /** - * Remove a permission for ALL user from the node. - * - * @param permission Permission to remove @see org.alfresco.service.cmr.security.PermissionService - */ - public void removePermission(String permission) - { - ParameterCheck.mandatoryString("Permission Name", permission); - this.services.getPermissionService().deletePermission( - this.nodeRef, PermissionService.ALL_AUTHORITIES, permission); - } - - /** - * Remove a permission for the specified authority (e.g. username or group) from the node. - * - * @param permission Permission to remove @see org.alfresco.service.cmr.security.PermissionService - * @param authority Authority (generally a username or group name) to apply the permission for - */ - public void removePermission(String permission, String authority) - { - ParameterCheck.mandatoryString("Permission Name", permission); - ParameterCheck.mandatoryString("Authority", authority); - this.services.getPermissionService().deletePermission( - this.nodeRef, authority, permission); - } - - - // ------------------------------------------------------------------------------ - // Ownership API - - /** - * Set the owner of the node - */ - public void setOwner(String userId) - { - this.services.getOwnableService().setOwner(this.nodeRef, userId); - } - - /** - * Take ownership of the node. - */ - public void takeOwnership() - { - this.services.getOwnableService().takeOwnership(this.nodeRef); - } - - /** - * Get the owner of the node. - * - * @return String - */ - public String getOwner() - { - return this.services.getOwnableService().getOwner(this.nodeRef); - } - - - // ------------------------------------------------------------------------------ - // Create and Modify API - - /** - * Persist the modified properties of this Node. - */ - public void save() - { - // persist properties back to the node in the DB - Map props = new HashMap(getProperties().size()); - for (String key : this.properties.keySet()) - { - Serializable value = (Serializable) this.properties.get(key); - - QName qname = createQName(key); - - // MNT-15798 - if (ContentModel.PROP_CONTENT.equals(qname) && isScriptContent(value)) - { - ScriptContentData contentData = (ScriptContentData) value; - // Do not persist the contentData if it was not touched - if (!contentData.isDirty()) - { - continue; - } - } - - // perform the conversion from script wrapper object to repo serializable values - value = getValueConverter().convertValueForRepo(value); - - props.put(qname, value); - } - this.nodeService.setProperties(this.nodeRef, props); - } - - /** - * Re-sets the type of the node. Can be called in order specialise a node to a sub-type. This should be used - * with caution since calling it changes the type of the node and thus* implies a different set of aspects, - * properties and associations. It is the responsibility of the caller to ensure that the node is in a - * approriate state after changing the type. - * - * @param type Type to specialize the node - * - * @return true if successful, false otherwise - */ - public boolean specializeType(String type) - { - ParameterCheck.mandatoryString("Type", type); - - QName qnameType = createQName(type); - - // Ensure that we are performing a specialise - if (getQNameType().equals(qnameType) == false && - this.services.getDictionaryService().isSubClass(qnameType, getQNameType()) == true) - { - // Specialise the type of the node - this.nodeService.setType(this.nodeRef, qnameType); - this.type = qnameType; - - return true; - } - return false; - } - - /** - * Create a new File (cm:content) node as a child of this node. - *

- * Once created the file should have content set using the content property. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param name Name of the file to create - * - * @return Newly created Node or null if failed to create. - */ - public ScriptNode createFile(String name) - { - return createFile(name, null); - } - - /** - * Create a new File (cm:content) node as a child of this node. - *

- * Once created the file should have content set using the content property. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param name Name of the file to create - * @param type Type of the file to create (if null, defaults to ContentModel.TYPE_CONTENT) - * - * @return Newly created Node or null if failed to create. - */ - public ScriptNode createFile(String name, String type) - { - ParameterCheck.mandatoryString("Node Name", name); - - FileInfo fileInfo = this.services.getFileFolderService().create( - this.nodeRef, name, type == null ? ContentModel.TYPE_CONTENT : createQName(type)); - - reset(); - - ScriptNode file = newInstance(fileInfo.getNodeRef(), this.services, this.scope); - file.setMimetype(this.services.getMimetypeService().guessMimetype(name)); - - return file; - } - - /** - * Create a new folder (cm:folder) node as a child of this node. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param name Name of the folder to create - * - * @return Newly created Node or null if failed to create. - */ - public ScriptNode createFolder(String name) - { - return createFolder(name, null); - } - - /** - * Create a new folder (cm:folder) node as a child of this node. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param name Name of the folder to create - * @param type Type of the folder to create (if null, defaults to ContentModel.TYPE_FOLDER) - * - * @return Newly created Node or null if failed to create. - */ - public ScriptNode createFolder(String name, String type) - { - ParameterCheck.mandatoryString("Node Name", name); - - FileInfo fileInfo = this.services.getFileFolderService().create( - this.nodeRef, name, type == null ? ContentModel.TYPE_FOLDER : createQName(type)); - - reset(); - - return newInstance(fileInfo.getNodeRef(), this.services, this.scope); - } - - /** - * Create a new Node of the specified type as a child of this node. - * - * @param name Name of the node to create (can be null for a node without a 'cm:name' property) - * @param type QName type (fully qualified or short form such as 'cm:content') - * - * @return Newly created Node or null if failed to create. - */ - public ScriptNode createNode(String name, String type) - { - return createNode(name, type, null, ContentModel.ASSOC_CONTAINS.toString()); - } - - /** - * Create a new Node of the specified type as a child of this node. - * - * @param name Name of the node to create (can be null for a node without a 'cm:name' property) - * @param type QName type (fully qualified or short form such as 'cm:content') - * @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains') - * - * @return Newly created Node or null if failed to create. - */ - public ScriptNode createNode(String name, String type, String assocType) - { - return createNode(name, type, null, assocType); - } - - /** - * Create a new Node of the specified type as a child of this node. - * - * @param name Name of the node to create (can be null for a node without a 'cm:name' property) - * @param type QName type (fully qualified or short form such as 'cm:content') - * @param properties Associative array of the default properties for the node. - * - * @return Newly created Node or null if failed to create. - */ - public ScriptNode createNode(String name, String type, Object properties) - { - return createNode(name, type, properties, ContentModel.ASSOC_CONTAINS.toString()); - } - - /** - * Create a new Node of the specified type as a child of this node. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param name Name of the node to create (can be null for a node without a 'cm:name' property) - * @param type QName type (fully qualified or short form such as 'cm:content') - * @param properties Associative array of the default properties for the node. - * @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains') - * - * @return Newly created Node or null if failed to create. - */ - public ScriptNode createNode(String name, String type, Object properties, String assocType) - { - return createNode(name, type, properties, assocType, null); - } - - /** - * Create a new Node of the specified type as a child of this node. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param name Name of the node to create (can be null for a node without a 'cm:name' property) - * @param type QName type (fully qualified or short form such as 'cm:content') - * @param properties Associative array of the default properties for the node. - * @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains') - * @param assocName QName of the child association name (fully qualified or short form e.g. 'fm:discussion') - * - * @return Newly created Node or null if failed to create. - */ - public ScriptNode createNode(String name, String type, Object properties, String assocType, String assocName) - { - ParameterCheck.mandatoryString("Node Type", type); - ParameterCheck.mandatoryString("Association Type", assocType); - - Map props = null; - - if (properties instanceof ScriptableObject) - { - props = new HashMap(4, 1.0f); - extractScriptableProperties((ScriptableObject)properties, props); - } - - if (name != null) - { - if (props == null) props = new HashMap(1, 1.0f); - props.put(ContentModel.PROP_NAME, name); - } - else - { - // set name for the assoc local name - name = GUID.generate(); - } - - ChildAssociationRef childAssocRef = this.nodeService.createNode( - this.nodeRef, - createQName(assocType), - assocName == null ? - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)) : - createQName(assocName), - createQName(type), - props); - - reset(); - - return newInstance(childAssocRef.getChildRef(), this.services, this.scope); - } - - /** - * Creates a new secondary association between the current node and the specified child node. - * The association is given the same name as the child node's primary association. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param node node to add as a child of this node - */ - public void addNode(ScriptNode node) - { - ParameterCheck.mandatory("node", node); - ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(node.nodeRef); - nodeService.addChild(this.nodeRef, node.nodeRef, ContentModel.ASSOC_CONTAINS, childAssocRef.getQName()); - reset(); - } - - /** - * Remove an existing child node of this node. - * - * Severs all parent-child relationships between two nodes. - *

- * The child node will be cascade deleted if one of the associations was the - * primary association, i.e. the one with which the child node was created. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param node child node to remove - */ - public void removeNode(ScriptNode node) - { - ParameterCheck.mandatory("node", node); - nodeService.removeChild(this.nodeRef, node.nodeRef); - reset(); - } - - /** - * Create an association between this node and the specified target node. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param target Destination node for the association - * @param assocType Association type qname (short form or fully qualified) - */ - public Association createAssociation(ScriptNode target, String assocType) - { - ParameterCheck.mandatory("Target", target); - ParameterCheck.mandatoryString("Association Type Name", assocType); - - AssociationRef assocRef = this.nodeService.createAssociation(this.nodeRef, target.nodeRef, createQName(assocType)); - reset(); - return new Association(this.services, assocRef); - } - - /** - * Remove an association between this node and the specified target node. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param target Destination node on the end of the association - * @param assocType Association type qname (short form or fully qualified) - */ - public void removeAssociation(ScriptNode target, String assocType) - { - ParameterCheck.mandatory("Target", target); - ParameterCheck.mandatoryString("Association Type Name", assocType); - - this.nodeService.removeAssociation(this.nodeRef, target.nodeRef, createQName(assocType)); - reset(); - } - - /** - * Remove this node. Any references to this Node or its NodeRef should be - * discarded! - * - * Beware: Any unsaved property changes will be lost when this is called. To - * preserve property changes call {@link save()} first. - * - */ - public boolean remove() - { - return remove(false); - } - - /** - * Remove this node in a new transaction or not as specified. - * Any references to this Node or its NodeRef should be discarded! - * - * Beware: Any unsaved property changes will be lost when this is called. To - * preserve property changes call {@link save()} first. - * - */ - public boolean remove(boolean newTransaction) - { - boolean success = false; - - if (nodeService.exists(this.nodeRef)) - { - retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - nodeService.deleteNode(nodeRef); - return null; - } - }, false, newTransaction); - success = true; - } - - reset(); - - return success; - } - - /** - * Copy this Node to a new parent destination. Note that children of the source Node are not copied. - * - * @param destination Node - * - * @return The newly copied Node instance or null if failed to copy. - */ - public ScriptNode copy(ScriptNode destination) - { - ScriptNode copy = copy(destination, false); - - // ALF-9517 fix - if (copy != null && copy.hasAspect(ContentModel.ASPECT_VERSIONABLE.toString())) - { - copy.ensureVersioningEnabled(true, true); - } - return copy; - } - - /** - * Copy this Node and potentially all child nodes to a new parent destination. - * - * @param destination Node - * @param deepCopy True for a deep copy, false otherwise. - * - * @return The newly copied Node instance or null if failed to copy. - */ - public ScriptNode copy(ScriptNode destination, boolean deepCopy) - { - ParameterCheck.mandatory("Destination Node", destination); - - NodeRef copyRef = this.services.getCopyService().copyAndRename(this.nodeRef, destination.getNodeRef(), - ContentModel.ASSOC_CONTAINS, null, deepCopy); - ScriptNode copy = newInstance(copyRef, this.services, this.scope); - - return copy; - } - - /** - * Revert this Node to the specified version. Note this is not a deep revert of - * associations. - * This node must have the cm:versionable aspect. It will be checked out if required - * but will be checked in after the call. - * - * @param versionLabel to revert from - * - * @return the original Node that was checked out if reverted, {@code null} otherwise - * (if the version does not exist). - */ - public ScriptNode revert(String history, boolean majorVersion, String versionLabel) - { - return revert(history, majorVersion, versionLabel, false); - } - - /** - * Revert this Node to the specified version and potentially all child nodes. - * This node must have the cm:versionable aspect. It will be checked out if required - * but will be checked in after the call. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param history Version history note - * @param majorVersion True to save as a major version increment, false for minor version. - * @param versionLabel to revert from - * @param deep {@code true} for a deep revert, {@code false} otherwise. - * - * @return the original Node that was checked out if reverted, {@code null} otherwise - * (if the version does not exist). - */ - public ScriptNode revert(String history, boolean majorVersion, String versionLabel, boolean deep) - { - if (!getIsVersioned()) - { - return null; - } - - // Get the Version - needed to do the revert - Version version = services.getVersionService().getVersionHistory(nodeRef).getVersion(versionLabel); - if (version == null) - { - return null; - } - - ScriptNode originalNode = this; - //cancel editing if we want to revert - if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY)) - { - originalNode = cancelCheckout(); - } - - // Revert the new (current) version of the node - services.getVersionService().revert(originalNode.getNodeRef(), version, deep); - - // Checkout/Checkin the node - to store the new version in version history - ScriptNode workingCopy = originalNode.checkout(); - originalNode = workingCopy.checkin(history, majorVersion); - - - return originalNode; - } - - /** - * Move this Node to a new parent destination. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param destination Node - * - * @return true on successful move, false on failure to move. - */ - public boolean move(ScriptNode destination) - { - ParameterCheck.mandatory("Destination Node", destination); - - this.primaryParentAssoc = this.nodeService.moveNode(this.nodeRef, destination.getNodeRef(), - ContentModel.ASSOC_CONTAINS, getPrimaryParentAssoc().getQName()); - - // reset cached values - reset(); - - return true; - } - - /** - * Move this Node from specified parent to a new parent destination. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param source Node - * @param destination Node - * @return true on successful move, false on failure to move. - */ - public boolean move(ScriptNode source, ScriptNode destination) - { - ParameterCheck.mandatory("Destination Node", destination); - - if (source == null) - { - return move(destination); - } - else - { - try - { - this.services.getFileFolderService().moveFrom(this.nodeRef, source.getNodeRef(), destination.getNodeRef(), null); - } - //MNT-7514 Uninformational error message on move when file name conflicts - catch (FileExistsException ex) - { - throw ex; - } - catch (Exception e) - { - throw new ScriptException("Can't move node", e); - } - } - - // reset cached values - reset(); - - return true; - } - - /** - * Add an aspect to the Node. As no properties are provided in this call, it can only be used to add aspects that do not require any mandatory properties. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * @param type Type name of the aspect to add - * - * @return true if the aspect was added successfully, false if an error occured. - */ - public boolean addAspect(String type) - { - return addAspect(type, null); - } - - /** - * Add an aspect to the Node. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param type Type name of the aspect to add - * @param props ScriptableObject (generally an assocative array) providing the named properties for the aspect - * - any mandatory properties for the aspect must be provided! - * - * @return true if the aspect was added successfully, false if an error occured. - */ - public boolean addAspect(String type, Object props) - { - ParameterCheck.mandatoryString("Aspect Type", type); - - Map aspectProps = null; - if (props instanceof ScriptableObject) - { - aspectProps = new HashMap(4, 1.0f); - extractScriptableProperties((ScriptableObject)props, aspectProps); - } - QName aspectQName = createQName(type); - if (aspectQName.equals(ContentModel.ASPECT_VERSIONABLE)) - { - // ALF-13719 need to taking into account script properties for versionable aspect - if (aspectProps != null) - { - Serializable autoVersionObj, autoVersionPropsObj; - autoVersionObj = aspectProps.get(ContentModel.PROP_AUTO_VERSION); - autoVersionPropsObj = aspectProps.get(ContentModel.PROP_AUTO_VERSION_PROPS); - ensureVersioningEnabled(autoVersionObj instanceof Boolean ? ((Boolean) autoVersionObj) : true, - autoVersionPropsObj instanceof Boolean ? ((Boolean) autoVersionPropsObj) : true); - } - else - { - // MNT-9369, read props from contentModel.xml, sets to false, false if there is no defaults. - Map versionableProps = services.getDictionaryService().getAspect(ContentModel.ASPECT_VERSIONABLE).getProperties(); - boolean autoVersion = Boolean.parseBoolean(versionableProps.get(ContentModel.PROP_AUTO_VERSION).getDefaultValue()); - boolean autoVersionProps = Boolean.parseBoolean(versionableProps.get(ContentModel.PROP_AUTO_VERSION_PROPS).getDefaultValue()); - ensureVersioningEnabled(autoVersion, autoVersionProps); - } - } - else - { - this.nodeService.addAspect(this.nodeRef, aspectQName, aspectProps); - } - - // reset the relevant cached node members - reset(); - - return true; - } - - /** - * Extract a map of properties from a scriptable object (generally an associative array) - * - * @param scriptable The scriptable object to extract name/value pairs from. - * @param map The map to add the converted name/value pairs to. - */ - private void extractScriptableProperties(ScriptableObject scriptable, Map map) - { - // we need to get all the keys to the properties provided - // and convert them to a Map of QName to Serializable objects - Object[] propIds = scriptable.getIds(); - for (int i = 0; i < propIds.length; i++) - { - // work on each key in turn - Object propId = propIds[i]; - - // we are only interested in keys that are formed of Strings i.e. QName.toString() - if (propId instanceof String) - { - // get the value out for the specified key - it must be Serializable - String key = (String)propId; - Object value = scriptable.get(key, scriptable); - if (value instanceof Serializable) - { - value = getValueConverter().convertValueForRepo((Serializable)value); - map.put(createQName(key), (Serializable)value); - } - } - } - } - - /** - * Remove aspect from the node. - * - * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. - * - * @param type the aspect type - * - * @return true if successful, false otherwise - */ - public boolean removeAspect(String type) - { - ParameterCheck.mandatoryString("Aspect Type", type); - - QName aspectQName = createQName(type); - this.nodeService.removeAspect(this.nodeRef, aspectQName); - - // reset the relevant cached node members - reset(); - - return true; - } - - - // ------------------------------------------------------------------------------ - // Checkout/Checkin Services - - /** - * Ensures that this document has the cm:versionable aspect applied to it, - * and that it has the initial version in the version store. - * Calling this on a versioned node with a version store entry will have - * no effect. - * Calling this on a newly uploaded share node will have versioning enabled - * for it (Share currently does lazy versioning to improve performance of - * documents that are uploaded but never edited, and multi upload performance). - * - * @param autoVersion If the cm:versionable aspect is applied, should auto versioning be requested? - * @param autoVersionProps If the cm:versionable aspect is applied, should auto versioning of properties be requested? - */ - public void ensureVersioningEnabled(boolean autoVersion, boolean autoVersionProps) - { - Map props = new HashMap(1, 1.0f); - props.put(ContentModel.PROP_AUTO_VERSION, autoVersion); - props.put(ContentModel.PROP_AUTO_VERSION_PROPS, autoVersionProps); - - this.services.getVersionService().ensureVersioningEnabled(nodeRef, props); - } - - /** - * Ensures that this document has the cm:versionable aspect applied to it, - * and that it has the initial version in the version store. - * Calling this on a versioned node with a version store entry will have - * no effect. - * Calling this on a newly uploaded share node will have versioning enabled - * for it (Share currently does lazy versioning to improve performance of - * documents that are uploaded but never edited, and multi upload performance). - * - */ - public void ensureVersioningEnabled() - { - this.services.getVersionService().ensureVersioningEnabled(nodeRef, null); - } - - /** - * Create a version of this document. Note: this will add the cm:versionable aspect. - * - * @param history Version history note - * @param majorVersion True to save as a major version increment, false for minor version. - * - * @return ScriptVersion object representing the newly added version node - */ - public ScriptVersion createVersion(String history, boolean majorVersion) - { - Map props = new HashMap(2, 1.0f); - props.put(Version.PROP_DESCRIPTION, history); - props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR); - ScriptVersion version = new ScriptVersion(this.services.getVersionService().createVersion(this.nodeRef, props), this.services, this.scope); - this.versions = null; - return version; - } - - /** - * Determines if this node is versioned - * - * @return true => is versioned - */ - public boolean getIsVersioned() - { - return this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); - } - - /** - * Gets the version history - * - * @return version history - */ - public Scriptable getVersionHistory() - { - if (this.versions == null && getIsVersioned()) - { - VersionHistory history = this.services.getVersionService().getVersionHistory(this.nodeRef); - if (history != null) - { - Collection allVersions = history.getAllVersions(); - Object[] versions = new Object[allVersions.size()]; - int i = 0; - for (Version version : allVersions) - { - versions[i++] = new ScriptVersion(version, this.services, this.scope); - } - this.versions = Context.getCurrentContext().newArray(this.scope, versions); - } - } - return this.versions; - } - - /** - * Gets the version of this node specified by version label - * - * @param versionLabel version label - * @return version of node, or null if node is not versioned, or label does not exist - */ - public ScriptVersion getVersion(String versionLabel) - { - if (!getIsVersioned()) - { - return null; - } - VersionHistory history = this.services.getVersionService().getVersionHistory(this.nodeRef); - Version version = history.getVersion(versionLabel); - if (version == null) - { - return null; - } - return new ScriptVersion(version, this.services, this.scope); - } - - /** - * Perform a check-out of this document into the current parent space. - * - * @return the working copy Node for the checked out document - */ - public ScriptNode checkout() - { - NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef); - ScriptNode workingCopy = newInstance(workingCopyRef, this.services, this.scope); - - // reset the aspect and properties as checking out a document causes changes - this.properties = null; - this.aspects = null; - - return workingCopy; - } - - /** - * Performs a check-out of this document for the purposes of an upload - * - * @return ScriptNode - */ - public ScriptNode checkoutForUpload() - { - AlfrescoTransactionSupport.bindResource("checkoutforupload", Boolean.TRUE.toString()); - services.getRuleService().disableRules(); - try - { - return checkout(); - } - finally - { - services.getRuleService().enableRules(); - } - } - - /** - * Perform a check-out of this document into the specified destination space. - * - * @param destination - * Destination for the checked out document working copy Node. - * @return the working copy Node for the checked out document - */ - public ScriptNode checkout(ScriptNode destination) - { - ParameterCheck.mandatory("Destination Node", destination); - - ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(destination.getNodeRef()); - NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef, - destination.getNodeRef(), ContentModel.ASSOC_CONTAINS, childAssocRef.getQName()); - ScriptNode workingCopy = newInstance(workingCopyRef, this.services, this.scope); - - // reset the aspect and properties as checking out a document causes changes - this.properties = null; - this.aspects = null; - - return workingCopy; - } - - /** - * Check-in a working copy document. The current state of the working copy is copied to the original node, - * this will include any content updated in the working node. Note that this method can only be called on a - * working copy Node. - * - * @return the original Node that was checked out. - */ - public ScriptNode checkin() - { - return checkin("", false); - } - - /** - * Check-in a working copy document. The current state of the working copy is copied to the original node, - * this will include any content updated in the working node. Note that this method can only be called on a - * working copy Node. - * - * @param history Version history note - * - * @return the original Node that was checked out. - */ - public ScriptNode checkin(String history) - { - return checkin(history, false); - } - - /** - * Check-in a working copy document. The current state of the working copy is copied to the original node, - * this will include any content updated in the working node. Note that this method can only be called on a - * working copy Node. - * - * @param history Version history note - * @param majorVersion True to save as a major version increment, false for minor version. - * - * @return the original Node that was checked out. - */ - public ScriptNode checkin(String history, boolean majorVersion) - { - Map props = new HashMap(2, 1.0f); - props.put(Version.PROP_DESCRIPTION, history); - props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR); - NodeRef original = this.services.getCheckOutCheckInService().checkin(this.nodeRef, props); - this.versions = null; - return newInstance(original, this.services, this.scope); - } - - /** - * Removes the lock on a node. - * - */ - public void unlock() - { - this.services.getLockService().unlock(this.nodeRef); - } - - /** - * Gets the check-out of a working copy document - * @return the original Node that was checked out or null if it's not a working copy - */ - public ScriptNode getCheckedOut() - { - NodeRef original = this.services.getCheckOutCheckInService().getCheckedOut(this.nodeRef); - - if(original != null) - { - return newInstance(original, this.services, this.scope); - } - else - { - return null; - } - } - - /** - * Cancel the check-out of a working copy document. The working copy will be deleted and any changes made to it - * are lost. Note that this method can only be called on a working copy Node. The reference to this working copy - * Node should be discarded. - * - * @return the original Node that was checked out. - */ - public ScriptNode cancelCheckout() - { - NodeRef original = this.services.getCheckOutCheckInService().cancelCheckout(this.nodeRef); - return newInstance(original, this.services, this.scope); - } - - // ------------------------------------------------------------------------------ - // Transformation and Rendering API - - /** - * Transform a document to a new document mimetype format. A copy of the document is made and the extension - * changed to match the new mimetype, then the transformation isapplied. - * - * @param mimetype Mimetype destination for the transformation - * - * @return Node representing the newly transformed document. - */ - public ScriptNode transformDocument(String mimetype) - { - return transformDocument(mimetype, getPrimaryParentAssoc().getParentRef()); - } - - /** - * Transform a document to a new document mimetype format. A copy of the document is made in the specified - * destination folder and the extension changed to match the new mimetype, then then transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * @param destination Destination folder location - * - * @return Node representing the newly transformed document. - */ - public ScriptNode transformDocument(String mimetype, ScriptNode destination) - { - return transformDocument(mimetype, destination.getNodeRef()); - } - - private ScriptNode transformDocument(String mimetype, NodeRef destination) - { - ParameterCheck.mandatoryString("Mimetype", mimetype); - ParameterCheck.mandatory("Destination Node", destination); - final NodeRef sourceNodeRef = nodeRef; - - // the delegate definition for transforming a document - Transformer transformer = new Transformer() - { - public ScriptNode transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, - ContentWriter writer) - { - ScriptNode transformedNode = null; - TransformationOptions options = new TransformationOptions(); - options.setSourceNodeRef(sourceNodeRef); - - try - { - contentService.transform(reader, writer, options); - transformedNode = newInstance(nodeRef, services, scope); - } - catch (NoTransformerException e) - { - // ignore - } - catch (AlfrescoRuntimeException e) - { - Throwable rootCause = ((AlfrescoRuntimeException)e).getRootCause(); - String message = rootCause.getMessage(); - message = message == null ? "" : message; - if (rootCause instanceof UnimportantTransformException) - { - logger.debug(message); - // ignore - } - else if (rootCause instanceof UnsupportedTransformationException) - { - logger.error(message); - // ignore - } - else - { - throw e; - } - } - return transformedNode; - } - }; - - return transformNode(transformer, mimetype, destination); - } - - /** - * Generic method to transform Node content from one mimetype to another. - * - * @param transformer The Transformer delegate supplying the transformation logic - * @param mimetype Mimetype of the destination content - * @param destination Destination folder location for the resulting document - * - * @return Node representing the transformed content - or null if the transform failed - */ - private ScriptNode transformNode(Transformer transformer, String mimetype, NodeRef destination) - { - ScriptNode transformedNode = null; - - // get the content reader - ContentService contentService = this.services.getContentService(); - ContentReader reader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT); - - // only perform the transformation if some content is available - if (reader != null) - { - // Copy the content node to a new node - String copyName = TransformActionExecuter.transformName(this.services.getMimetypeService(), getName(), - mimetype, true); - NodeRef copyNodeRef = this.services.getCopyService().copy(this.nodeRef, destination, - ContentModel.ASSOC_CONTAINS, - QName.createQName(ContentModel.PROP_CONTENT.getNamespaceURI(), QName.createValidLocalName(copyName)), - false); - - // modify the name of the copy to reflect the new mimetype - this.nodeService.setProperty(copyNodeRef, ContentModel.PROP_NAME, copyName); - - // get the writer and set it up - ContentWriter writer = contentService.getWriter(copyNodeRef, ContentModel.PROP_CONTENT, true); - writer.setMimetype(mimetype); // new mimetype - writer.setEncoding(reader.getEncoding()); // original encoding - - // Try and transform the content using the supplied delegate - transformedNode = transformer.transform(contentService, copyNodeRef, reader, writer); - } - - return transformedNode; - } - - /** - * Transform an image to a new image format. A copy of the image document is made and the extension changed to - * match the new mimetype, then the transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * - * @return Node representing the newly transformed image. - */ - public ScriptNode transformImage(String mimetype) - { - return transformImage(mimetype, null, getPrimaryParentAssoc().getParentRef()); - } - - /** - * Transform an image to a new image format. A copy of the image document is made and the extension changed to - * match the new mimetype, then the transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * @param options Image convert command options - * - * @return Node representing the newly transformed image. - */ - public ScriptNode transformImage(String mimetype, String options) - { - return transformImage(mimetype, options, getPrimaryParentAssoc().getParentRef()); - } - - /** - * Transform an image to a new image mimetype format. A copy of the image document is made in the specified - * destination folder and the extension changed to match the newmimetype, then then transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * @param destination Destination folder location - * - * @return Node representing the newly transformed image. - */ - public ScriptNode transformImage(String mimetype, ScriptNode destination) - { - ParameterCheck.mandatory("Destination Node", destination); - return transformImage(mimetype, null, destination.getNodeRef()); - } - - /** - * Transform an image to a new image mimetype format. A copy of the image document is made in the specified - * destination folder and the extension changed to match the new - * mimetype, then then transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * @param options Image convert command options - * @param destination Destination folder location - * - * @return Node representing the newly transformed image. - */ - public ScriptNode transformImage(String mimetype, String options, ScriptNode destination) - { - ParameterCheck.mandatory("Destination Node", destination); - return transformImage(mimetype, options, destination.getNodeRef()); - } - - private ScriptNode transformImage(String mimetype, final String options, NodeRef destination) - { - ParameterCheck.mandatoryString("Mimetype", mimetype); - final NodeRef sourceNodeRef = nodeRef; - - // the delegate definition for transforming an image - Transformer transformer = new Transformer() - { - public ScriptNode transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, - ContentWriter writer) - { - ImageTransformationOptions imageOptions = new ImageTransformationOptions(); - imageOptions.setSourceNodeRef(sourceNodeRef); - - if (options != null) - { - imageOptions.setCommandOptions(options); - } - contentService.getImageTransformer().transform(reader, writer, imageOptions); - - return newInstance(nodeRef, services, scope); - } - }; - - return transformNode(transformer, mimetype, destination); - } - - /** - * Process a FreeMarker Template against the current node. - * - * @param template Node of the template to execute - * - * @return output of the template execution - */ - public String processTemplate(ScriptNode template) - { - ParameterCheck.mandatory("Template Node", template); - return processTemplate(template.getContent(), null, null); - } - - /** - * Process a FreeMarker Template against the current node. - * - * @param template Node of the template to execute - * @param args Scriptable object (generally an associative array) containing the name/value pairs of - * arguments to be passed to the template - * - * @return output of the template execution - */ - public String processTemplate(ScriptNode template, Object args) - { - ParameterCheck.mandatory("Template Node", template); - return processTemplate(template.getContent(), null, (ScriptableObject)args); - } - - /** - * Process a FreeMarker Template against the current node. - * - * @param template The template to execute - * - * @return output of the template execution - */ - public String processTemplate(String template) - { - ParameterCheck.mandatoryString("Template", template); - return processTemplate(template, null, null); - } - - /** - * Process a FreeMarker Template against the current node. - * - * @param template The template to execute - * @param args Scriptable object (generally an associative array) containing the name/value pairs of - * arguments to be passed to the template - * - * @return output of the template execution - */ - public String processTemplate(String template, Object args) - { - ParameterCheck.mandatoryString("Template", template); - return processTemplate(template, null, (ScriptableObject)args); - } - - private String processTemplate(String template, NodeRef templateRef, ScriptableObject args) - { - Object person = (Object)scope.get("person", scope); - Object companyhome = (Object)scope.get("companyhome", scope); - Object userhome = (Object)scope.get("userhome", scope); - - // build default model for the template processing - Map model = this.services.getTemplateService().buildDefaultModel( - (person.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)person).unwrap()).getNodeRef(), - (companyhome.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)companyhome).unwrap()).getNodeRef(), - (userhome.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)userhome).unwrap()).getNodeRef(), - templateRef, - null); - - // add the current node as either the document/space as appropriate - if (this.getIsDocument()) - { - model.put("document", this.nodeRef); - model.put("space", getPrimaryParentAssoc().getParentRef()); - } - else - { - model.put("space", this.nodeRef); - } - - // add the supplied args to the 'args' root object - if (args != null) - { - // we need to get all the keys to the properties provided - // and convert them to a Map of QName to Serializable objects - Object[] propIds = args.getIds(); - Map templateArgs = new HashMap(propIds.length); - for (int i = 0; i < propIds.length; i++) - { - // work on each key in turn - Object propId = propIds[i]; - - // we are only interested in keys that are formed of Strings i.e. QName.toString() - if (propId instanceof String) - { - // get the value out for the specified key - make sure it is Serializable - Object value = args.get((String) propId, args); - value = getValueConverter().convertValueForRepo((Serializable)value); - if (value != null) - { - templateArgs.put((String) propId, value.toString()); - } - } - } - // add the args to the model as the 'args' root object - model.put("args", templateArgs); - } - - // execute template! - // TODO: check that script modified nodes are reflected... - return this.services.getTemplateService().processTemplateString(null, template, model); - } - - // ------------------------------------------------------------------------------ - // Thumbnail Methods - - /** - * Creates a thumbnail for the content property of the node. - * - * The thumbnail name correspionds to pre-set thumbnail details stored in the - * repository. - * - * @param thumbnailName the name of the thumbnail - * @return ScriptThumbnail the newly create thumbnail node - */ - public ScriptThumbnail createThumbnail(String thumbnailName) - { - return createThumbnail(thumbnailName, false); - } - - /** - * Creates a thumbnail for the content property of the node. - * - * The thumbnail name corresponds to pre-set thumbnail details stored in the - * repository. - * - * If the thumbnail is created asynchronously then the result will be null and creation - * of the thumbnail will occure at some point in the background. - * - * @param thumbnailName the name of the thumbnail - * @param async indicates whether the thumbnail is create asynchronously or not - * @return ScriptThumbnail the newly create thumbnail node or null if async creation occures - */ - public ScriptThumbnail createThumbnail(String thumbnailName, boolean async) - { - return createThumbnail(thumbnailName, async, false); - } - - /** - * Creates a thumbnail for the content property of the node. - * - * The thumbnail name corresponds to pre-set thumbnail details stored in the - * repository. - * - * If the thumbnail is created asynchronously then the result will be null and creation - * of the thumbnail will occure at some point in the background. - * - * If foce param specified system.thumbnail.generate is ignoring. Could be used for preview creation - * - * @param thumbnailName the name of the thumbnail - * @param async indicates whether the thumbnail is create asynchronously or not - * @param force ignore system.thumbnail.generate=false - * @return ScriptThumbnail the newly create thumbnail node or null if async creation occures - */ - public ScriptThumbnail createThumbnail(String thumbnailName, boolean async, boolean force) - { - final ThumbnailService thumbnailService = services.getThumbnailService(); - - ScriptThumbnail result = null; - - // If thumbnail generation has been configured off, then don't bother with any of this. - // We need to create preview for node even if system.thumbnail.generate=false - if (force || thumbnailService.getThumbnailsEnabled()) - { - // Use the thumbnail registy to get the details of the thumbail - ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry(); - ThumbnailDefinition details = registry.getThumbnailDefinition(thumbnailName); - if (details == null) - { - // Throw exception - throw new ScriptException("The thumbnail name '" + thumbnailName + "' is not registered"); - } - - // If there's nothing currently registered to generate thumbnails for the - // specified mimetype, then log a message and bail out - String nodeMimeType = getMimetype(); - Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); - if (!ContentData.hasContent(contentData)) - { - if (logger.isDebugEnabled()) - logger.debug("Unable to create thumbnail '" + details.getName() + "' as there is no content"); - return null; - } - if (!registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), nodeMimeType, getSize(), nodeRef, details)) - { - logger.info("Unable to create thumbnail '" + details.getName() + "' for " + - nodeMimeType + " as no transformer is currently available."); - return null; - } - - // Have the thumbnail created - if (async == false) - { - try - { - // Create the thumbnail - NodeRef thumbnailNodeRef = thumbnailService.createThumbnail( - this.nodeRef, - ContentModel.PROP_CONTENT, - details.getMimetype(), - details.getTransformationOptions(), - details.getName()); - - // Create the thumbnail script object - result = new ScriptThumbnail(thumbnailNodeRef, this.services, this.scope); - } - catch (AlfrescoRuntimeException e) - { - Throwable rootCause = e.getRootCause(); - if (rootCause instanceof UnimportantTransformException) - { - logger.debug("Unable to create thumbnail '" + details.getName() + "' as "+rootCause.getMessage()); - return null; - } - throw e; - } - } - else - { - Action action = ThumbnailHelper.createCreateThumbnailAction(details, services); - - // Queue async creation of thumbnail - this.services.getActionService().executeAction(action, this.nodeRef, true, true); - } - } - return result; - } - - /** - * Get the given thumbnail for the content property - * - * @param thumbnailName the thumbnail name - * @return ScriptThumbnail the thumbnail - */ - public ScriptThumbnail getThumbnail(String thumbnailName) - { - ScriptThumbnail result = null; - NodeRef thumbnailNodeRef = this.services.getThumbnailService().getThumbnailByName( - this.nodeRef, - ContentModel.PROP_CONTENT, - thumbnailName); - if (thumbnailNodeRef != null) - { - result = new ScriptThumbnail(thumbnailNodeRef, this.services, this.scope); - } - return result; - } - - /** - * Get the all the thumbnails for a given node's content property. - * - * @return Scriptable list of thumbnails, empty if none available - */ - public ScriptThumbnail[] getThumbnails() - { - List thumbnails = this.services.getThumbnailService().getThumbnails( - this.nodeRef, - ContentModel.PROP_CONTENT, - null, - null); - - List result = new ArrayList(thumbnails.size()); - for (NodeRef thumbnail : thumbnails) - { - ScriptThumbnail scriptThumbnail = new ScriptThumbnail(thumbnail, this.services, this.scope); - result.add(scriptThumbnail); - } - return (ScriptThumbnail[])result.toArray(new ScriptThumbnail[result.size()]); - } - - /** - * Returns the names of the thumbnail defintions that can be applied to the content property of - * this node. - *

- * Thumbanil defintions only appear in this list if they can produce a thumbnail for the content - * found in the content property. This will be determined by looking at the mimetype of the content - * and the destinatino mimetype of the thumbnail. - * - * @return String[] array of thumbnail names that are valid for the current content type - */ - public String[] getThumbnailDefinitions() - { - ThumbnailService thumbnailService = this.services.getThumbnailService(); - - List result = new ArrayList(7); - - Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); - - if (ContentData.hasContent(contentData)) - { - String mimetype = contentData.getMimetype(); - List thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(mimetype, contentData.getSize()); - for (ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions) - { - result.add(thumbnailDefinition.getName()); - } - } - - return (String[])result.toArray(new String[result.size()]); - } - /** - * This version of the method name spelling is retained (for now) for backwards compatibility - * @see #getThumbnailDefinitions() - */ - @Deprecated - public String[] getThumbnailDefintions() - { - return getThumbnailDefinitions(); - } - - - // ------------------------------------------------------------------------------ - // Tag methods - - /** - * Clear the node's tags - */ - public void clearTags() - { - this.services.getTaggingService().clearTags(this.nodeRef); - updateTagProperty(); - } - - /** - * Adds a tag to the node - * - * @param tag tag name - */ - public void addTag(String tag) - { - this.services.getTaggingService().addTag(this.nodeRef, tag); - updateTagProperty(); - } - - /** - * Adds all the tags to the node - * - * @param tags array of tag names - */ - public void addTags(String[] tags) - { - this.services.getTaggingService().addTags(this.nodeRef, Arrays.asList(tags)); - updateTagProperty(); - } - - /** - * Removes a tag from the node - * - * @param tag tag name - */ - public void removeTag(String tag) - { - this.services.getTaggingService().removeTag(this.nodeRef, tag); - updateTagProperty(); - } - - /** - * Removes all the tags from the node - * - * @param tags array of tag names - */ - public void removeTags(String[] tags) - { - this.services.getTaggingService().removeTags(this.nodeRef, Arrays.asList(tags)); - updateTagProperty(); - } - - /** - * Get all the tags applied to this node - * - * @return String[] array containing all the tag applied to this node - */ - public String[] getTags() - { - String[] result = null; - List tags = this.services.getTaggingService().getTags(this.nodeRef); - if (tags.isEmpty() == true) - { - result = new String[0]; - } - else - { - result = (String[])tags.toArray(new String[tags.size()]); - } - return result; - } - - /** - * Set the tags applied to this node. This overwirtes the list of tags currently applied to the - * node. - * - * @param tags array of tags - */ - public void setTags(String[] tags) - { - this.services.getTaggingService().setTags(this.nodeRef, Arrays.asList(tags)); - updateTagProperty(); - } - - private void updateTagProperty() - { - Serializable tags = this.services.getNodeService().getProperty(this.nodeRef, ContentModel.PROP_TAGS); - if (this.properties != null) - { - this.properties.put(ContentModel.PROP_TAGS.toString(), getValueConverter().convertValueForScript(ContentModel.PROP_TAGS, tags)); - } - } - - /** - * Sets whether this node is a tag scope or not - * - * @param value true if this node is a tag scope, false otherwise - */ - public void setIsTagScope(boolean value) - { - boolean currentValue = this.services.getTaggingService().isTagScope(this.nodeRef); - if (currentValue != value) - { - if (value == true) - { - // Add the tag scope - this.services.getTaggingService().addTagScope(this.nodeRef); - } - else - { - // Remove the tag scope - this.services.getTaggingService().removeTagScope(this.nodeRef); - } - } - } - - /** - * Gets whether this node is a tag scope - * - * @return boolean true if this node is a tag scope, false otherwise - */ - public boolean getIsTagScope() - { - return this.services.getTaggingService().isTagScope(this.nodeRef); - } - - /** - * Gets the 'nearest' tag scope to this node by travesing up the parent hierarchy untill one is found. - *

- * If none is found, null is returned. - * - * @return TagScope the 'nearest' tag scope - */ - public TagScope getTagScope() - { - TagScope tagScope = null; - org.alfresco.service.cmr.tagging.TagScope tagScopeImpl = this.services.getTaggingService().findTagScope(this.nodeRef); - if (tagScopeImpl != null) - { - tagScope = new TagScope(this.services.getTaggingService(), tagScopeImpl); - } - return tagScope; - } - - /** - * Gets all (deep) children of this node that have the tag specified. - * - * @param tag tag name - * @return ScriptNode[] nodes that are deep children of the node with the tag - */ - public ScriptNode[] childrenByTags(String tag) - { - List nodeRefs = this.services.getTaggingService().findTaggedNodes(this.nodeRef.getStoreRef(), tag, this.nodeRef); - ScriptNode[] nodes = new ScriptNode[nodeRefs.size()]; - int index = 0; - for (NodeRef node : nodeRefs) - { - nodes[index] = new ScriptNode(node, this.services, this.scope); - index ++; - } - return nodes; - } - - - // ------------------------------------------------------------------------------ - // Workflow methods - - /** - * Get active workflow instances this node belongs to - * - * @return the active workflow instances this node belongs to - */ - public Scriptable getActiveWorkflows() - { - if (this.activeWorkflows == null) - { - WorkflowService workflowService = this.services.getWorkflowService(); - - List workflowInstances = workflowService.getWorkflowsForContent(this.nodeRef, true); - Object[] jsWorkflowInstances = new Object[workflowInstances.size()]; - int index = 0; - for (WorkflowInstance workflowInstance : workflowInstances) - { - jsWorkflowInstances[index++] = new JscriptWorkflowInstance(workflowInstance, this.services, this.scope); - } - this.activeWorkflows = Context.getCurrentContext().newArray(this.scope, jsWorkflowInstances); - } - - return this.activeWorkflows; - } - - // ------------------------------------------------------------------------------ - // Site methods - - /** - * Returns the short name of the site this node is located within. If the - * node is not located within a site null is returned. - * - * @return The short name of the site this node is located within, null - * if the node is not located within a site. - */ - public String getSiteShortName() - { - if (!this.siteNameResolved) - { - this.siteNameResolved = true; - - Path path = this.services.getNodeService().getPath(getNodeRef()); - - if (logger.isDebugEnabled()) - logger.debug("Determing if node is within a site using path: " + path); - - for (int i = 0; i < path.size(); i++) - { - if ("st:sites".equals(path.get(i).getPrefixedString(this.services.getNamespaceService()))) - { - // we now know the node is in a site, find the next element in the array (if there is one) - if ((i+1) < path.size()) - { - // get the site name - Path.Element siteName = path.get(i+1); - - // remove the "cm:" prefix and add to result object - this.siteName = ISO9075.decode(siteName.getPrefixedString( - this.services.getNamespaceService()).substring(3)); - } - - break; - } - } - } - - if (logger.isDebugEnabled()) - { - logger.debug(this.siteName != null ? - "Node is in the site named \"" + this.siteName + "\"" : "Node is not in a site"); - } - - return this.siteName; - } - - // ------------------------------------------------------------------------------ - // Helper methods - - /** - * Override Object.toString() to provide useful debug output - */ - public String toString() - { - if (this.nodeService.exists(nodeRef)) - { - if (this.services.getPermissionService().hasPermission(nodeRef, PermissionService.READ_PROPERTIES) == AccessStatus.ALLOWED) - { - // TODO: DC: Allow debug output of property values - for now it's disabled as this could potentially - // follow a large network of nodes. Unfortunately, JBPM issues unprotected debug statements - // where node.toString is used - will request this is fixed in next release of JBPM. - return "Node Type: " + getType() + ", Node Aspects: " + getAspectsSet().toString(); - } - else - { - return "Access denied to node " + nodeRef; - } - - } - else - { - return "Node no longer exists: " + nodeRef; - } - } - - /** - * Returns the JSON representation of this node. - * - * @param useShortQNames if true short-form qnames will be returned, else long-form. - * @return The JSON representation of this node - */ - public String toJSON(boolean useShortQNames) - { - // This method is used by the /api/metadata web script - String jsonStr = "{}"; - - if (this.nodeService.exists(nodeRef)) - { - if(this.services.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "getProperties", this.nodeRef) == AccessStatus.ALLOWED) - { - JSONObject json = new JSONObject(); - - try - { - // add type info - json.put("nodeRef", this.getNodeRef().toString()); - - String typeString = useShortQNames ? getShortQName(this.getQNameType()) - : this.getType(); - json.put("type", typeString); - json.put("mimetype", this.getMimetype()); - - // Fetch all properties - Map nodeProperties = this.nodeService.getProperties(this.nodeRef); - - // Do any special conversion steps that are needed - for (QName longQName : nodeProperties.keySet()) - { - Serializable value = nodeProperties.get(longQName); - - if (value instanceof Date) - { - value = ISO8601DateFormat.format((Date)value); - nodeProperties.put(longQName, value); - } - if (value instanceof NodeRef) - { - value = ((NodeRef)value).toString(); - nodeProperties.put(longQName, value); - } - } - - if (useShortQNames) - { - Map nodePropertiesShortQNames - = new LinkedHashMap(nodeProperties.size()); - for (QName nextLongQName : nodeProperties.keySet()) - { - try - { - String nextShortQName = getShortQName(nextLongQName); - nodePropertiesShortQNames.put(nextShortQName, nodeProperties.get(nextLongQName)); - } - catch (NamespaceException ne) - { - // ignore properties that do not have a registered namespace - - if (logger.isDebugEnabled()) - logger.debug("Ignoring property '" + nextLongQName + "' as it's namespace is not registered"); - } - } - json.put("properties", nodePropertiesShortQNames); - } - else - { - json.put("properties", nodeProperties); - } - - // add aspects as an array - Set nodeAspects = this.nodeService.getAspects(this.nodeRef); - if (useShortQNames) - { - Set nodeAspectsShortQNames - = new LinkedHashSet(nodeAspects.size()); - for (QName nextLongQName : nodeAspects) - { - String nextShortQName = getShortQName(nextLongQName); - nodeAspectsShortQNames.add(nextShortQName); - } - json.put("aspects", nodeAspectsShortQNames); - } - else - { - json.put("aspects", nodeAspects); - } - } - catch (JSONException error) - { - error.printStackTrace(); - } - - jsonStr = json.toString(); - } - } - - return jsonStr; - } - - /** - * Returns the JSON representation of this node. Long-form QNames are used in the - * result. - * - * @return The JSON representation of this node - */ - public String toJSON() - { - return this.toJSON(false); - } - - /** - * Given a long-form QName, this method uses the namespace service to create a - * short-form QName string. - * - * @param longQName QName - * @return the short form of the QName string, e.g. "cm:content" - */ - protected String getShortQName(QName longQName) - { - return longQName.toPrefixString(services.getNamespaceService()); - } - - /** - * Helper to create a QName from either a fully qualified or short-name QName string - * - * @param s Fully qualified or short-name QName string - * - * @return QName - */ - protected QName createQName(String s) - { - QName qname; - if (s.indexOf(NAMESPACE_BEGIN) != -1) - { - qname = QName.createQName(s); - } - else - { - qname = QName.createQName(s, this.services.getNamespaceService()); - } - return qname; - } - - /** - * Reset the Node cached state - */ - public void reset() - { - this.name = null; - this.type = null; - this.properties = null; - this.aspects = null; - this.targetAssocs = null; - this.sourceAssocs = null; - this.childAssocs = null; - this.children = null; - this.hasChildren = null; - this.parentAssocs = null; - this.displayPath = null; - this.qnamePath = null; - this.isDocument = null; - this.isContainer = null; - this.parent = null; - this.primaryParentAssoc = null; - this.activeWorkflows = null; - this.siteName = null; - this.siteNameResolved = false; - } - - /** - * Return a list or a single Node from executing an xpath against the parent Node. - * - * @param xpath XPath to execute - * @param firstOnly True to return the first result only - * - * @return Object[] can be empty but never null - */ - private Object[] getChildrenByXPath(String xpath, QueryParameterDefinition[] params, boolean firstOnly) - { - Object[] result = null; - - if (xpath.length() != 0) - { - if (logger.isDebugEnabled()) - { - logger.debug("Executing xpath: " + xpath); - if (params != null) - { - for (int i=0; itrue if the contentData has a binary (content URL) associated and the updates on contentData and related properties should be saved. - * false if the contentData has a temporary value and no actual binary to be persisted. - */ - public boolean isDirty() - { - return this.isDirty; - } - - /** - * Set the content stream - * - * @param content Content string to set - */ - public void setContent(String content) - { - ContentService contentService = services.getContentService(); - ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); - writer.setMimetype(getMimetype()); // use existing mimetype value - writer.putContent(content); - - // update cached variables after putContent() - updateContentData(true); - } - - /** - * Set the content stream from another content object. - * - * @param content ScriptContent to set - */ - public void write(Content content) - { - ContentService contentService = services.getContentService(); - ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); - writer.setMimetype(content.getMimetype()); - writer.setEncoding(content.getEncoding()); - writer.putContent(content.getInputStream()); - - // update cached variables after putContent() - updateContentData(true); - } - - /** - * Set the content stream from another content object. - * - * @param content ScriptContent to set - * @param applyMimetype If true, apply the mimetype from the Content object, else leave the original mimetype - * @param guessEncoding If true, guess the encoding from the underlying input stream, else use encoding set in - * the Content object as supplied. - */ - public void write(Content content, boolean applyMimetype, boolean guessEncoding) - { - ContentService contentService = services.getContentService(); - ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); - InputStream is = null; - if (applyMimetype) - { - writer.setMimetype(content.getMimetype()); - } - if (guessEncoding) - { - is = new BufferedInputStream(content.getInputStream()); - is.mark(1024); - writer.setEncoding(guessEncoding(is, false)); - try - { - is.reset(); - } - catch (IOException e) - { - } - } - else - { - writer.setEncoding(content.getEncoding()); - is = content.getInputStream(); - } - writer.putContent(is); - - // update cached variables after putContent() - updateContentData(true); - } - - /** - * Set the content stream from another input stream. - * - * @param inputStream InputStream - */ - public void write(InputStream inputStream) - { - ContentService contentService = services.getContentService(); - ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); - writer.putContent(inputStream); - - // update cached variables after putContent() - updateContentData(true); - } - - /** - * Delete the content stream - */ - public void delete() - { - ContentService contentService = services.getContentService(); - ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); - OutputStream output = writer.getContentOutputStream(); - try - { - output.close(); - } - catch (IOException e) - { - // NOTE: fall-through - } - writer.setMimetype(null); - writer.setEncoding(null); - - // update cached variables after putContent() - updateContentData(true); - } - - /** - * @return download URL to the content - */ - public String getUrl() - { - return MessageFormat.format(CONTENT_PROP_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(), - URLEncoder.encode(getName()), - URLEncoder.encode(property.toString()) }); - } - - /** - * @return download URL to the content for a document item only - */ - public String getDownloadUrl() - { - if (getIsDocument() == true) - { - return MessageFormat.format(CONTENT_DOWNLOAD_PROP_URL, new Object[] { - nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), - nodeRef.getId(), - URLEncoder.encode(getName()), - URLEncoder.encode(property.toString()) }); - } - else - { - return ""; - } - } - - public long getSize() - { - return contentData.getSize(); - } - - public String getMimetype() - { - return contentData.getMimetype(); - } - - public String getEncoding() - { - return contentData.getEncoding(); - } - - public void setEncoding(String encoding) - { - this.contentData = ContentData.setEncoding(this.contentData, encoding); - services.getNodeService().setProperty(nodeRef, this.property, this.contentData); - updateContentData(false); - } - - public void setMimetype(String mimetype) - { - this.contentData = ContentData.setMimetype(this.contentData, mimetype); - services.getNodeService().setProperty(nodeRef, this.property, this.contentData); - updateContentData(false); - } - - /** - * Guess the mimetype for the given filename - uses the extension to match on system mimetype map - */ - public void guessMimetype(String filename) - { - ContentService contentService = services.getContentService(); - ContentReader reader = contentService.getReader(nodeRef, property); - // MNT-12265 Browser sets a mimetype based on extension of file. But mimeType from browser can be - // different as mapped in Alfresco for current extension. Therefore we need to guess a mimetype based on - // map in Alfresco - String typeByExt = services.getMimetypeService().guessMimetype(filename); - if (reader != null && reader.getMimetype() != null && !typeByExt.equals(MimetypeMap.MIMETYPE_BINARY)) - { - setMimetype(typeByExt); - } - else - { - setMimetype(services.getMimetypeService().guessMimetype(filename, reader)); - } - } - - /** - * Guess the character encoding of a file. For non-text files UTF-8 default is applied, otherwise - * the appropriate encoding (such as UTF-16 or similar) will be appiled if detected. - */ - public void guessEncoding() - { - setEncoding(guessEncoding(getInputStream(), true)); - } - - private String guessEncoding(InputStream in, boolean close) - { - String encoding = "UTF-8"; - try - { - if (in != null) - { - Charset charset = services.getMimetypeService().getContentCharsetFinder().getCharset(in, getMimetype()); - encoding = charset.name(); - } - } - finally - { - try - { - if (close && in != null) - { - in.close(); - } - } - catch (IOException ioErr) - { - } - } - return encoding; - } - - /** - * Update cached contentData and the isDirty flag - */ - private void updateContentData(boolean touchContent) - { - this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property); - this.isDirty = touchContent ? true : this.isDirty; - } - - private ContentData contentData; - private QName property; - private boolean isDirty; - } - - /** - * Interface contract for simple anonymous classes that implement document transformations - */ - private interface Transformer - { - /** - * Transform the reader to the specified writer - * - * @param contentService ContentService - * @param noderef NodeRef of the destination for the transform - * @param reader Source reader - * @param writer Destination writer - * - * @return Node representing the transformed entity - */ - ScriptNode transform(ContentService contentService, NodeRef noderef, ContentReader reader, ContentWriter writer); - } - - - /** - * NamespacePrefixResolverProvider getter implementation - */ - public NamespacePrefixResolver getNamespacePrefixResolver() - { - return this.services.getNamespaceService(); - } -} +package org.alfresco.repo.jscript; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.text.Collator; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.action.executer.TransformActionExecuter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.transform.UnimportantTransformException; +import org.alfresco.repo.content.transform.UnsupportedTransformationException; +import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.repo.model.filefolder.FileFolderServiceImpl.InvalidTypeException; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.repo.search.QueryParameterDefImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tagging.script.TagScope; +import org.alfresco.repo.thumbnail.ThumbnailDefinition; +import org.alfresco.repo.thumbnail.ThumbnailHelper; +import org.alfresco.repo.thumbnail.ThumbnailRegistry; +import org.alfresco.repo.thumbnail.script.ScriptThumbnail; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.repo.workflow.jscript.JscriptWorkflowInstance; +import org.alfresco.scripts.ScriptException; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NoTransformerException; +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.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.search.QueryParameterDefinition; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.NamespacePrefixResolverProvider; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.FileFilterMode; +import org.alfresco.util.FileFilterMode.Client; +import org.alfresco.util.GUID; +import org.alfresco.util.ISO8601DateFormat; +import org.alfresco.util.ISO9075; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.json.JSONObject; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.UniqueTag; +import org.mozilla.javascript.Wrapper; +import org.springframework.extensions.surf.util.Content; +import org.springframework.extensions.surf.util.I18NUtil; +import org.springframework.extensions.surf.util.ParameterCheck; +import org.springframework.extensions.surf.util.URLEncoder; + +/** + * Script Node class implementation, specific for use by ScriptService as part of the object model. + *

+ * The class exposes Node properties, children and assocs as dynamically populated maps and lists. The various collection classes are mirrored as JavaScript properties. So can be + * accessed using standard JavaScript property syntax, such as node.children[0].properties.name. + *

+ * Various helper methods are provided to access common and useful node variables such as the content url and type information. + * + * @author Kevin Roast + */ +public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider +{ + private static final long serialVersionUID = -3378946227712939601L; + + private static Log logger = LogFactory.getLog(ScriptNode.class); + + private final static String NAMESPACE_BEGIN = "" + QName.NAMESPACE_BEGIN; + + private final static String CONTENT_DEFAULT_URL = "/d/d/{0}/{1}/{2}/{3}"; + private final static String CONTENT_DOWNLOAD_URL = "/d/a/{0}/{1}/{2}/{3}"; + private final static String CONTENT_PROP_URL = "/d/d/{0}/{1}/{2}/{3}?property={4}"; + private final static String CONTENT_DOWNLOAD_PROP_URL = "/d/a/{0}/{1}/{2}/{3}?property={4}"; + private final static String FOLDER_BROWSE_URL = "/n/browse/{0}/{1}/{2}"; + + /** Root scope for this object */ + protected Scriptable scope; + + /** Node Value Converter */ + protected NodeValueConverter converter = null; + + /** Cached values */ + protected NodeRef nodeRef; + + private FileInfo nodeInfo; + + private String name; + private QName type; + protected String id; + protected String siteName; + protected boolean siteNameResolved = false; + + /** The aspects applied to this node */ + protected Set aspects = null; + + /** The target associations from this node */ + private ScriptableQNameMap targetAssocs = null; + + /** The source associations to this node */ + private ScriptableQNameMap sourceAssocs = null; + + /** The child associations for this node */ + private ScriptableQNameMap childAssocs = null; + + /** The children of this node */ + private Scriptable children = null; + + /** The properties of this node */ + private ScriptableQNameMap properties = null; + + /** The versions of this node */ + private Scriptable versions = null; + + /** The active workflows acting on this node */ + private Scriptable activeWorkflows = null; + + protected ServiceRegistry services = null; + private NodeService nodeService = null; + private FileFolderService fileFolderService = null; + private RetryingTransactionHelper retryingTransactionHelper = null; + private Boolean isDocument = null; + private Boolean isContainer = null; + private Boolean isLinkToDocument = null; + private Boolean isLinkToContainer = null; + private Boolean hasChildren = null; + private String displayPath = null; + private String qnamePath = null; + protected TemplateImageResolver imageResolver = null; + protected ScriptNode parent = null; + private ChildAssociationRef primaryParentAssoc = null; + private ScriptableQNameMap parentAssocs = null; + // NOTE: see the reset() method when adding new cached members! + + + // ------------------------------------------------------------------------------ + // Construction + + /** + * Constructor + * + * @param nodeRef The NodeRef this Node wrapper represents + * @param services The ServiceRegistry the Node can use to access services + */ + public ScriptNode(NodeRef nodeRef, ServiceRegistry services) + { + this(nodeRef, services, null); + } + + /** + * Constructor + * + * @param nodeInfo The FileInfo this Node wrapper represents + * @param services The ServiceRegistry the Node can use to access services + * @param scope Root scope for this Node + */ + public ScriptNode(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope) + { + this(nodeInfo.getNodeRef(), services, scope); + + this.nodeInfo = nodeInfo; + } + + /** + * Constructor + * + * @param nodeRef The NodeRef this Node wrapper represents + * @param services The ServiceRegistry the Node can use to access services + * @param scope Root scope for this Node + */ + public ScriptNode(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) + { + if (nodeRef == null) + { + throw new IllegalArgumentException("NodeRef must be supplied."); + } + + if (services == null) + { + throw new IllegalArgumentException("The ServiceRegistry must be supplied."); + } + + this.nodeRef = nodeRef; + this.id = nodeRef.getId(); + this.services = services; + this.nodeService = services.getNodeService(); + this.fileFolderService = services.getFileFolderService(); + this.retryingTransactionHelper = services.getTransactionService().getRetryingTransactionHelper(); + this.scope = scope; + } + + @Override + public int hashCode() + { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((nodeRef == null) ? 0 : nodeRef.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + if (!nodeRef.equals(((ScriptNode)obj).nodeRef)) return false; + return true; + } + + /** + * Factory method + */ + public ScriptNode newInstance(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) + { + return new ScriptNode(nodeRef, services, scope); + } + + public ScriptNode newInstance(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope) + { + return new ScriptNode(nodeInfo, services, scope); + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + + // ------------------------------------------------------------------------------ + // Node Wrapper API + + /** + * @return The GUID for the node + */ + public String getId() + { + return this.id; + } + + /** + * @return the store type for the node + */ + public String getStoreType() + { + return this.nodeRef.getStoreRef().getProtocol(); + } + + /** + * @return the store id for the node + */ + public String getStoreId() + { + return this.nodeRef.getStoreRef().getIdentifier(); + } + + /** + * @return Returns the NodeRef this Node object represents + */ + public NodeRef getNodeRef() + { + return this.nodeRef; + } + + /** + * @return Returns the QName type. + */ + public QName getQNameType() + { + if (this.type == null) + { + this.type = this.nodeService.getType(this.nodeRef); + } + + return type; + } + + /** + * @return Returns the type. + */ + public String getType() + { + return getQNameType().toString(); + } + + /** + * @return Returns the type in short format. + */ + public String getTypeShort() + { + return this.getShortQName(getQNameType()); + } + + /** + * @return Helper to return the 'name' property for the node + */ + public String getName() + { + if (this.name == null) + { + // try and get the name from the properties first + this.name = (String) getProperties().get("cm:name"); + + // if we didn't find it as a property get the name from the association name + if (this.name == null) + { + ChildAssociationRef parentRef = this.nodeService.getPrimaryParent(this.nodeRef); + if (parentRef != null && parentRef.getQName() != null) + { + this.name = parentRef.getQName().getLocalName(); + } + else + { + this.name = ""; + } + } + } + + return this.name; + } + + /** + * Helper to set the 'name' property for the node. + * + * @param name Name to set + */ + public void setName(String name) + { + if (name != null) + { + QName typeQName = getQNameType(); + if ((services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_FOLDER) && + !services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_SYSTEM_FOLDER)) || + services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_CONTENT)) + { + try + { + this.services.getFileFolderService().rename(this.nodeRef, name); + } + catch (FileNotFoundException e) + { + throw new AlfrescoRuntimeException("Failed to rename node " + nodeRef + " to " + name, e); + } + } + this.getProperties().put(ContentModel.PROP_NAME.toString(), name.toString()); + } + } + + /** + * @return The children of this Node as JavaScript array of Node object wrappers + */ + public Scriptable getChildren() + { + if (this.children == null) + { + List childRefs = this.nodeService.getChildAssocs(this.nodeRef); + Object[] children = new Object[childRefs.size()]; + for (int i = 0; i < childRefs.size(); i++) + { + // create our Node representation from the NodeRef + children[i] = newInstance(childRefs.get(i).getChildRef(), this.services, this.scope); + } + + // Do a locale-sensitive sort by name + sort(children); + + this.children = Context.getCurrentContext().newArray(this.scope, children); + this.hasChildren = (children.length != 0); + } + + return this.children; + } + + /** + * Performs a locale-sensitive sort by name of a node array + * @param nodes the node array + */ + private static void sort(Object[] nodes) + { + final Collator col = Collator.getInstance(I18NUtil.getLocale()); + Arrays.sort(nodes, new Comparator(){ + @Override + public int compare(Object o1, Object o2) + { + return col.compare(((ScriptNode)o1).getName(), ((ScriptNode)o2).getName()); + }}); + } + + /** + * @return true if the Node has children + */ + public boolean getHasChildren() + { + if (this.hasChildren == null) + { + this.hasChildren = !this.services.getNodeService().getChildAssocs( + getNodeRef(), RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false).isEmpty(); + } + return hasChildren; + } + + /** + * childByNamePath returns the Node at the specified 'cm:name' based Path walking the children of this Node. + * So a valid call might be: + * mynode.childByNamePath("/QA/Testing/Docs"); + * + * @param path the relative path of the descendant node to find e.g. {@code "/QA/Testing/Docs"} + * @return The ScriptNode or {@code null} if the node is not found. + * {@code null} if the specified path is {@code ""}. + * @throws NullPointerException if the provided path is {@code null}. + */ + public ScriptNode childByNamePath(String path) + { + // Ensure that paths that do not represent descendants are not needlessly tokenised. See ALF-20896. + if (path == null) { throw new NullPointerException("Illegal null path"); } + else if (path.isEmpty()) { return null; } + + // We have a path worth looking at... + ScriptNode child = null; + + if (this.services.getDictionaryService().isSubClass(getQNameType(), ContentModel.TYPE_FOLDER)) + { + // The current node is a folder e.g. Company Home and standard child folders. + // optimized code path for cm:folder and sub-types supporting getChildrenByName() method + final StringTokenizer t = new StringTokenizer(path, "/"); + // allow traversal of a cm:name based path even if user cannot retrieve the node directly + NodeRef result = AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public NodeRef doWork() throws Exception + { + NodeRef child = ScriptNode.this.nodeRef; + while (t.hasMoreTokens() && child != null) + { + String name = t.nextToken(); + child = nodeService.getChildByName(child, ContentModel.ASSOC_CONTAINS, name); + } + return child; + } + }, AuthenticationUtil.getSystemUserName()); + + // final node must be accessible to the user via the usual ACL permission checks + if (result != null + && services.getPublicServiceAccessService().hasAccess("NodeService", "getProperties", result) != AccessStatus.ALLOWED) + { + result = null; + } + + child = (result != null ? newInstance(result, this.services, this.scope) : null); + } + else + { + // The current node is not a folder. It does not support the cm:contains association. + // Convert the name based path to a valid XPath query + StringBuilder xpath = new StringBuilder(path.length() << 1); + StringTokenizer t = new StringTokenizer(path, "/"); + int count = 0; + QueryParameterDefinition[] params = new QueryParameterDefinition[t.countTokens()]; + DataTypeDefinition ddText = + this.services.getDictionaryService().getDataType(DataTypeDefinition.TEXT); + NamespaceService ns = this.services.getNamespaceService(); + while (t.hasMoreTokens()) + { + if (xpath.length() != 0) + { + xpath.append('/'); + } + String strCount = Integer.toString(count); + xpath.append("*[@cm:name=$cm:name") + .append(strCount) + .append(']'); + params[count++] = new QueryParameterDefImpl( + QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, "name" + strCount, ns), + ddText, + true, + t.nextToken()); + } + + Object[] nodes = getChildrenByXPath(xpath.toString(), params, true); + + child = (nodes.length != 0) ? (ScriptNode)nodes[0] : null; + } + + return child; + } + + /** + * @return Returns a JavaScript array of Nodes at the specified XPath starting at this Node. + * So a valid call might be mynode.childrenByXPath("*[@cm:name='Testing']/*"); + */ + public Scriptable childrenByXPath(String xpath) + { + return Context.getCurrentContext().newArray(this.scope, getChildrenByXPath(xpath, null, false)); + } + + /** + * @return Returns a JavaScript array of child file/folder nodes for this nodes. + * Automatically retrieves all sub-types of cm:content and cm:folder, also removes + * system folder types from the results. + * This is equivalent to @see FileFolderService.list() + */ + public Scriptable childFileFolders() + { + return childFileFolders(true, true, null); + } + + /** + * @param files Return files extending from cm:content + * @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder + * + * @return Returns a JavaScript array of child file/folder nodes for this nodes. + * Automatically retrieves all sub-types of cm:content and cm:folder, also removes + * system folder types from the results. + * This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders() + */ + public Scriptable childFileFolders(boolean files, boolean folders) + { + return childFileFolders(files, folders, null); + } + + /** + * @param files Return files extending from cm:content + * @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder + * @param ignoreTypes Also optionally removes additional type qnames. The additional type can be + * specified in short or long qname string form as a single string or an Array e.g. "fm:forum". + * + * @return Returns a JavaScript array of child file/folder nodes for this nodes. + * Automatically retrieves all sub-types of cm:content and cm:folder, also removes + * system folder types from the results. + * This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders() + */ + public Scriptable childFileFolders(boolean files, boolean folders, Object ignoreTypes) + { + return childFileFolders(files, folders, ignoreTypes, -1, -1, 0, null, null, null).getPage(); + } + + /** + * @param files Return files extending from cm:content + * @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder + * @param ignoreTypes Also optionally removes additional type qnames. The additional type can be + * specified in short or long qname string form as a single string or an Array e.g. "fm:forum". + * @param maxItems Max number of items + * + * @return Returns ScriptPagingNodes which includes a JavaScript array of child file/folder nodes for this nodes. + * Automatically retrieves all sub-types of cm:content and cm:folder, also removes + * system folder types from the results. + * This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders() + * + * @deprecated API for review (subject to change prior to release) + * + *
author janv + * @since 4.0 + */ + public ScriptPagingNodes childFileFolders(boolean files, boolean folders, Object ignoreTypes, int maxItems) + { + return childFileFolders(files, folders, ignoreTypes, 0, maxItems, 0, null, null, null); + } + + @SuppressWarnings("unchecked") + /** + * @param files Return files extending from cm:content + * @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder + * @param ignoreTypes Also optionally removes additional type qnames. The additional type can be + * specified in short or long qname string form as a single string or an Array e.g. "fm:forum". + * @param skipOffset Items to skip (e.g. 0 or (num pages to skip * size of page) + * @param maxItems Max number of items (eg. size of page) + * @param requestTotalCountMax Request total count (upto a given max total count) + * Note: if 0 then total count is not requested and the query may be able to optimise/cutoff for max items) + * @param sortProp Optional sort property as a prefix qname string (e.g. "cm:name"). Also supports special + * content case (i.e. "cm:content.size" and "cm:content.mimetype") + * @param sortAsc Given a sort property, true => ascending, false => descending + * @param queryExecutionId If paging then can pass back the previous query execution (as a hint for possible query optimisation) + * + * @return Returns ScriptPagingNodes which includes a JavaScript array of child file/folder nodes for this nodes. + * Automatically retrieves all sub-types of cm:content and cm:folder, also removes + * system folder types from the results. + * This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders() + * + *

author janv + * @since 4.0 + */ + public ScriptPagingNodes childFileFolders(boolean files, boolean folders, Object ignoreTypes, int skipOffset, int maxItems, int requestTotalCountMax, String sortProp, Boolean sortAsc, String queryExecutionId) + { + Object[] results; + + Set ignoreTypeQNames = new HashSet(5); + + // Add user defined types to ignore + if (ignoreTypes instanceof ScriptableObject) + { + Serializable types = getValueConverter().convertValueForRepo((ScriptableObject)ignoreTypes); + if (types instanceof List) + { + for (Serializable typeObj : (List)types) + { + ignoreTypeQNames.add(createQName(typeObj.toString())); + } + } + else if (types instanceof String) + { + ignoreTypeQNames.add(createQName(types.toString())); + } + } + else if (ignoreTypes instanceof String) + { + ignoreTypeQNames.add(createQName(ignoreTypes.toString())); + } + + // ALF-13968 - sort folders before files (for Share) - TODO should be optional sort param + List> sortProps = new ArrayList>(2); + if ((sortProp == null) || (! sortProp.equals(GetChildrenCannedQuery.SORT_QNAME_NODE_TYPE.getLocalName()))) + { + sortProps.add(new Pair(GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER, false)); + } + if (sortProp != null) + { + sortProps.add(new Pair(createQName(sortProp), sortAsc)); + } + + PagingRequest pageRequest = new PagingRequest(skipOffset, maxItems, queryExecutionId); + pageRequest.setRequestTotalCountMax(requestTotalCountMax); + + PagingResults pageOfNodeInfos = null; + FileFilterMode.setClient(Client.script); + try + { + pageOfNodeInfos = this.fileFolderService.list(this.nodeRef, files, folders, null, ignoreTypeQNames, sortProps, pageRequest); + } + finally + { + FileFilterMode.clearClient(); + } + + List nodeInfos = pageOfNodeInfos.getPage(); + int size = nodeInfos.size(); + results = new Object[size]; + for (int i=0; i totalResultCount = pageOfNodeInfos.getTotalResultCount(); + if (totalResultCount != null) + { + totalResultCountLower = (totalResultCount.getFirst() != null ? totalResultCount.getFirst() : -1); + totalResultCountUpper = (totalResultCount.getSecond() != null ? totalResultCount.getSecond() : -1); + } + + return new ScriptPagingNodes(Context.getCurrentContext().newArray(this.scope, results), pageOfNodeInfos.hasMoreItems(), totalResultCountLower, totalResultCountUpper); + } + + /** + * Return the target associations from this Node. As a Map of assoc type to a JavaScript array of Nodes. + * The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript + * associative array access. This means associations of this node can be access thus: + * node.assocs["translations"][0] + * + * @return target associations as a Map of assoc name to a JavaScript array of Nodes. + */ + @SuppressWarnings("unchecked") + public Map getAssocs() + { + if (this.targetAssocs == null) + { + // this Map implements the Scriptable interface for native JS syntax property access + this.targetAssocs = new ScriptableQNameMap(this); + + // get the list of target nodes for each association type + List refs = this.nodeService.getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); + for (AssociationRef ref : refs) + { + String qname = ref.getTypeQName().toString(); + List nodes = (List)this.targetAssocs.get(qname); + if (nodes == null) + { + // first access of the list for this qname + nodes = new ArrayList(4); + } + this.targetAssocs.put(ref.getTypeQName().toString(), nodes); + nodes.add(newInstance(ref.getTargetRef(), this.services, this.scope)); + } + + // convert each Node list into a JavaScript array object + for (String qname : this.targetAssocs.keySet()) + { + List nodes = (List)this.targetAssocs.get(qname); + Object[] objs = nodes.toArray(new Object[nodes.size()]); + this.targetAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); + } + } + + return this.targetAssocs; + } + + public Map getAssociations() + { + return getAssocs(); + } + + /** + * Return the source associations to this Node. As a Map of assoc name to a JavaScript array of Nodes. + * The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript + * associative array access. This means source associations to this node can be access thus: + * node.sourceAssocs["translations"][0] + * + * @return source associations as a Map of assoc name to a JavaScript array of Nodes. + */ + @SuppressWarnings("unchecked") + public Map getSourceAssocs() + { + if (this.sourceAssocs == null) + { + // this Map implements the Scriptable interface for native JS syntax property access + this.sourceAssocs = new ScriptableQNameMap(this); + + // get the list of source nodes for each association type + List refs = this.nodeService.getSourceAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); + for (AssociationRef ref : refs) + { + String qname = ref.getTypeQName().toString(); + List nodes = (List)this.sourceAssocs.get(qname); + if (nodes == null) + { + // first access of the list for this qname + nodes = new ArrayList(4); + this.sourceAssocs.put(ref.getTypeQName().toString(), nodes); + } + nodes.add(newInstance(ref.getSourceRef(), this.services, this.scope)); + } + + // convert each Node list into a JavaScript array object + for (String qname : this.sourceAssocs.keySet()) + { + List nodes = (List)this.sourceAssocs.get(qname); + Object[] objs = nodes.toArray(new Object[nodes.size()]); + this.sourceAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); + } + } + + return this.sourceAssocs; + } + + public Map getSourceAssociations() + { + return getSourceAssocs(); + } + + /** + * Return the child associations from this Node. As a Map of assoc name to a JavaScript array of Nodes. + * The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript + * associative array access. This means associations of this node can be access thus: + * node.childAssocs["contains"][0] + * + * @return child associations as a Map of assoc name to a JavaScript array of Nodes. + */ + @SuppressWarnings("unchecked") + public Map getChildAssocs() + { + if (this.childAssocs == null) + { + // this Map implements the Scriptable interface for native JS syntax property access + this.childAssocs = new ScriptableQNameMap(this); + + // get the list of child assoc nodes for each association type + List refs = this.nodeService.getChildAssocs(nodeRef); + for (ChildAssociationRef ref : refs) + { + String qname = ref.getTypeQName().toString(); + List nodes = (List)this.childAssocs.get(qname); + if (nodes == null) + { + // first access of the list for this qname + nodes = new ArrayList(4); + this.childAssocs.put(ref.getTypeQName().toString(), nodes); + } + nodes.add(newInstance(ref.getChildRef(), this.services, this.scope)); + } + + // convert each Node list into a JavaScript array object + for (String qname : this.childAssocs.keySet()) + { + List nodes = (List)this.childAssocs.get(qname); + Object[] objs = nodes.toArray(new Object[nodes.size()]); + this.childAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); + } + } + + return this.childAssocs; + } + + public Map getChildAssociations() + { + return getChildAssocs(); + } + + /** + * Return an Array of the associations from this Node that match a specific object type. + * node.getChildAssocsByType("cm:folder")[0] + * + * @return Array of child associations from this Node that match a specific object type. + */ + public Scriptable getChildAssocsByType(String type) + { + // get the list of child assoc nodes for each association type + Set types = new HashSet(1, 1.0f); + types.add(createQName(type)); + List refs = this.nodeService.getChildAssocs(this.nodeRef, types); + Object[] nodes = new Object[refs.size()]; + for (int i=0; inode.parentAssocs["contains"][0] + * + * @return parent associations as a Map of assoc name to a JavaScript array of Nodes. + */ + @SuppressWarnings("unchecked") + public Map getParentAssocs() + { + if (this.parentAssocs == null) + { + // this Map implements the Scriptable interface for native JS syntax property access + this.parentAssocs = new ScriptableQNameMap(this); + + // get the list of child assoc nodes for each association type + List refs = this.nodeService.getParentAssocs(nodeRef); + for (ChildAssociationRef ref : refs) + { + String qname = ref.getTypeQName().toString(); + List nodes = (List)this.parentAssocs.get(qname); + if (nodes == null) + { + // first access of the list for this qname + nodes = new ArrayList(4); + this.parentAssocs.put(ref.getTypeQName().toString(), nodes); + } + nodes.add(newInstance(ref.getParentRef(), this.services, this.scope)); + } + + // convert each Node list into a JavaScript array object + for (String qname : this.parentAssocs.keySet()) + { + List nodes = (List)this.parentAssocs.get(qname); + Object[] objs = nodes.toArray(new Object[nodes.size()]); + this.parentAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); + } + } + + return this.parentAssocs; + } + + public Map getParentAssociations() + { + return getParentAssocs(); + } + + /** + * Checks whether the {@link ScriptNode} exists in the repository. + * @return boolean + */ + public boolean exists() + { + return nodeService.exists(nodeRef); + } + + /** + * Return all the properties known about this node. The Map returned implements the Scriptable interface to + * allow access to the properties via JavaScript associative array access. This means properties of a node can + * be access thus: node.properties["name"] + * + * @return Map of properties for this Node. + */ + @SuppressWarnings("unchecked") + public Map getProperties() + { + if (this.properties == null) + { + // this Map implements the Scriptable interface for native JS syntax property access + // this impl of the QNameMap is capable of creating ScriptContentData on demand for 'cm:content' + // properties that have not been initialised - see AR-1673. + this.properties = new ContentAwareScriptableQNameMap(this, this.services); + + Map props = null; + if (this.nodeInfo != null) + { + props = this.nodeInfo.getProperties(); + } + else + { + props = this.nodeService.getProperties(this.nodeRef); + } + + for (QName qname : props.keySet()) + { + Serializable propValue = props.get(qname); + + // perform the conversion to a script safe value and store + this.properties.put(qname.toString(), getValueConverter().convertValueForScript(qname, propValue)); + } + } + + return this.properties; + } + + /** + * Return all the property names defined for this node's type as an array of short QNames. + * + * @return Array of property names for this node's type. + */ + public Scriptable getTypePropertyNames() + { + return getTypePropertyNames(true); + } + + /** + * Return all the property names defined for this node's type as an array. + * + * @param useShortQNames if true short-form qnames will be returned, else long-form. + * @return Array of property names for this node's type. + */ + public Scriptable getTypePropertyNames(boolean useShortQNames) + { + Set props = this.services.getDictionaryService().getClass(this.getQNameType()).getProperties().keySet(); + Object[] result = new Object[props.size()]; + int count = 0; + for (QName qname : props) + { + result[count++] = useShortQNames ? getShortQName(qname).toString() : qname.toString(); + } + return Context.getCurrentContext().newArray(this.scope, result); + } + + /** + * Return all the property names defined for this node as an array. + * + * @param useShortQNames if true short-form qnames will be returned, else long-form. + * @return Array of property names for this node type and optionally parent properties. + */ + public Scriptable getPropertyNames(boolean useShortQNames) + { + Set props = this.nodeService.getProperties(this.nodeRef).keySet(); + Object[] result = new Object[props.size()]; + int count = 0; + for (QName qname : props) + { + result[count++] = useShortQNames ? getShortQName(qname).toString() : qname.toString(); + } + return Context.getCurrentContext().newArray(this.scope, result); + } + + /** + * @return true if this Node is a container (i.e. a folder) + */ + public boolean getIsContainer() + { + if (isContainer == null) + { + DictionaryService dd = this.services.getDictionaryService(); + isContainer = Boolean.valueOf((dd.isSubClass(getQNameType(), ContentModel.TYPE_FOLDER) == true && + dd.isSubClass(getQNameType(), ContentModel.TYPE_SYSTEM_FOLDER) == false)); + } + + return isContainer.booleanValue(); + } + + /** + * @return true if this Node is a Document (i.e. with content) + */ + public boolean getIsDocument() + { + if (isDocument == null) + { + DictionaryService dd = this.services.getDictionaryService(); + isDocument = Boolean.valueOf(dd.isSubClass(getQNameType(), ContentModel.TYPE_CONTENT)); + } + + return isDocument.booleanValue(); + } + + /** + * @return true if this Node is a Link to a Container (i.e. a folderlink) + */ + public boolean getIsLinkToContainer() + { + if (isLinkToContainer == null) + { + DictionaryService dd = this.services.getDictionaryService(); + isLinkToContainer = Boolean.valueOf(dd.isSubClass(getQNameType(), ApplicationModel.TYPE_FOLDERLINK)); + } + + return isLinkToContainer.booleanValue(); + } + + /** + * @return true if this Node is a Link to a Document (i.e. a filelink) + */ + public boolean getIsLinkToDocument() + { + if (isLinkToDocument == null) + { + DictionaryService dd = this.services.getDictionaryService(); + isLinkToDocument = Boolean.valueOf(dd.isSubClass(getQNameType(), ApplicationModel.TYPE_FILELINK)); + } + + return isLinkToDocument.booleanValue(); + } + + /** + * @return true if the Node is a Category + */ + public boolean getIsCategory() + { + // this valid is overriden by the CategoryNode sub-class + return false; + } + + /** + * @return The list of aspects applied to this node + */ + public Set getAspectsSet() + { + if (this.aspects == null) + { + this.aspects = this.nodeService.getAspects(this.nodeRef); + } + + return this.aspects; + } + + /** + * @return The array of aspects applied to this node as fully qualified qname strings + */ + public Scriptable getAspects() + { + Set aspects = getAspectsSet(); + Object[] result = new Object[aspects.size()]; + int count = 0; + for (QName qname : aspects) + { + result[count++] = qname.toString(); + } + return Context.getCurrentContext().newArray(this.scope, result); + } + + /** + * @return The array of aspects applied to this node as short prefix qname strings + */ + public Scriptable getAspectsShort() + { + final NamespaceService ns = this.services.getNamespaceService(); + final Map cache = new HashMap(); + final Set aspects = getAspectsSet(); + final Object[] result = new Object[aspects.size()]; + int count = 0; + for (final QName qname : aspects) + { + String prefix = cache.get(qname.getNamespaceURI()); + if (prefix == null) + { + // first request for this namespace prefix, get and cache result + Collection prefixes = ns.getPrefixes(qname.getNamespaceURI()); + prefix = prefixes.size() != 0 ? prefixes.iterator().next() : ""; + cache.put(qname.getNamespaceURI(), prefix); + } + result[count++] = prefix + QName.NAMESPACE_PREFIX + qname.getLocalName(); + } + return Context.getCurrentContext().newArray(this.scope, result); + } + + /** + * @param aspect The aspect name to test for (fully qualified or short-name form) + * @return true if the node has the aspect false otherwise + */ + public boolean hasAspect(String aspect) + { + return getAspectsSet().contains(createQName(aspect)); + } + + /** + * @param type The qname type to test this object against (fully qualified or short-name form) + * @return true if this Node is a sub-type of the specified class (or itself of that class) + */ + public boolean isSubType(String type) + { + ParameterCheck.mandatoryString("Type", type); + + QName qnameType = createQName(type); + + return this.services.getDictionaryService().isSubClass(getQNameType(), qnameType); + } + + /** + * @return QName path to this node. This can be used for Lucene PATH: style queries + */ + public String getQnamePath() + { + if (this.qnamePath == null) + { + final NamespaceService ns = this.services.getNamespaceService(); + final Map cache = new HashMap(); + final StringBuilder buf = new StringBuilder(128); + final Path path = this.services.getNodeService().getPath(getNodeRef()); + for (final Path.Element e : path) + { + if (e instanceof Path.ChildAssocElement) + { + final QName qname = ((Path.ChildAssocElement)e).getRef().getQName(); + if (qname != null) + { + String prefix = cache.get(qname.getNamespaceURI()); + if (prefix == null) + { + // first request for this namespace prefix, get and cache result + Collection prefixes = ns.getPrefixes(qname.getNamespaceURI()); + prefix = prefixes.size() != 0 ? prefixes.iterator().next() : ""; + cache.put(qname.getNamespaceURI(), prefix); + } + buf.append('/').append(prefix).append(':').append(ISO9075.encode(qname.getLocalName())); + } + } + else + { + buf.append('/').append(e.toString()); + } + } + this.qnamePath = buf.toString(); + } + + return this.qnamePath; + } + + /** + * @return Display path to this node + */ + public String getDisplayPath() + { + if (this.displayPath == null) + { + this.displayPath = this.nodeService.getPath(this.nodeRef).toDisplayPath( + this.nodeService, this.services.getPermissionService()); + } + + return this.displayPath; + } + + /** + * @return the small icon image for this node + */ + public String getIcon16() + { + return "/images/filetypes/_default.gif"; + } + + /** + * @return the large icon image for this node + */ + public String getIcon32() + { + return "/images/filetypes32/_default.gif"; + } + + /** + * @return true if the node is currently locked + */ + public boolean getIsLocked() + { + boolean locked = false; + + if (getAspectsSet().contains(ContentModel.ASPECT_LOCKABLE)) + { + LockStatus status = this.services.getLockService().getLockStatus(this.nodeRef); + if (status == LockStatus.LOCKED || status == LockStatus.LOCK_OWNER) + { + locked = true; + } + } + + return locked; + } + + /** + * @return the primary parent node + */ + public ScriptNode getParent() + { + if (parent == null) + { + NodeRef parentRef = getPrimaryParentAssoc().getParentRef(); + // handle root node (no parent!) + if (parentRef != null) + { + parent = newInstance(parentRef, this.services, this.scope); + } + } + + return parent; + } + + /** + * @return all parent nodes + */ + public Scriptable getParents() + { + List parentRefs = this.nodeService.getParentAssocs(this.nodeRef); + Object[] parents = new Object[parentRefs.size()]; + for (int i = 0; i < parentRefs.size(); i++) + { + NodeRef ref = parentRefs.get(i).getParentRef(); + parents[i] = newInstance(ref, this.services, this.scope); + } + return Context.getCurrentContext().newArray(this.scope, parents); + } + + /** + * @return the primary parent association so we can get at the association QName and the association type QName. + */ + public ChildAssociationRef getPrimaryParentAssoc() + { + if (primaryParentAssoc == null) + { + primaryParentAssoc = this.nodeService.getPrimaryParent(nodeRef); + } + return primaryParentAssoc; + } + + + // ------------------------------------------------------------------------------ + // Content API + + /** + * @return the content String for this node from the default content property (@see ContentModel.PROP_CONTENT) + */ + public String getContent() + { + String content = ""; + + ScriptContentData contentData = (ScriptContentData)getProperties().get(ContentModel.PROP_CONTENT); + if (contentData != null) + { + content = contentData.getContent(); + } + + return content; + } + + /** + * Set the content for this node + * + * @param content Content string to set + */ + public void setContent(String content) + { + ScriptContentData contentData = (ScriptContentData)getProperties().get(ContentModel.PROP_CONTENT); + if (contentData != null) + { + contentData.setContent(content); + } + } + + /** + * @return For a content document, this method returns the URL to the content stream for the default content + * property (@see ContentModel.PROP_CONTENT) + *

+ * For a container node, this method return the URL to browse to the folder in the web-client + */ + public String getUrl() + { + if (getIsDocument() == true) + { + return MessageFormat.format(CONTENT_DEFAULT_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(), + URLEncoder.encode(getName())}); + } + else + { + return MessageFormat.format(FOLDER_BROWSE_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), nodeRef.getId() }); + } + } + + /** + * @return For a content document, this method returns the download URL to the content for + * the default content property (@see ContentModel.PROP_CONTENT) + *

+ * For a container node, this method returns an empty string + */ + public String getDownloadUrl() + { + if (getIsDocument() == true) + { + return MessageFormat.format(CONTENT_DOWNLOAD_URL, new Object[] { + nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), + nodeRef.getId(), + URLEncoder.encode(getName()) }); + } + else + { + return ""; + } + } + + public String jsGet_downloadUrl() + { + return getDownloadUrl(); + } + + /** + * @return The WebDav cm:name based path to the content for the default content property + * (@see ContentModel.PROP_CONTENT) + */ + public String getWebdavUrl() + { + String url = ""; + try + { + if (getIsContainer() || getIsDocument()) + { + List paths = this.services.getFileFolderService().getNameOnlyPath(null, getNodeRef()); + + // build up the webdav url + StringBuilder path = new StringBuilder(128); + path.append("/webdav"); + + // build up the path skipping the first path as it is the root folder + for (int i=1; i + * The default permissions are found in org.alfresco.service.cmr.security.PermissionService. + * Most commonly used are "Write", "Delete" and "AddChildren". + * + * @param permission as found in org.alfresco.service.cmr.security.PermissionService + * @return true if the user has the specified permission on the node. + */ + public boolean hasPermission(String permission) + { + ParameterCheck.mandatory("Permission Name", permission); + + boolean allowed = false; + + if (permission != null && permission.length() != 0) + { + AccessStatus status = this.services.getPermissionService().hasPermission(this.nodeRef, permission); + allowed = (AccessStatus.ALLOWED == status); + } + + return allowed; + } + + /** + * @return Array of permissions applied to this Node, including inherited. + * Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION for example + * ALLOWED;kevinr;Consumer so can be easily tokenized on the ';' character. + */ + public Scriptable getPermissions() + { + return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(false, false)); + } + + /** + * @return Array of permissions applied directly to this Node (does not include inherited). + * Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION for example + * ALLOWED;kevinr;Consumer so can be easily tokenized on the ';' character. + */ + public Scriptable getDirectPermissions() + { + return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(true, false)); + } + + /** + * @return Array of all permissions applied to this Node, including inherited. + * Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION;[INHERITED|DIRECT] + * for example: ALLOWED;kevinr;Consumer;DIRECT so can be easily tokenized on the ';' character. + */ + public Scriptable getFullPermissions() + { + return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(false, true)); + } + + /** + * @return Sorted list of AccessPermission based on CMISConnector.AccessPermissionComparator + * and AccessStatus of the permission for an authority. + */ + public static List getSortedACLs(Set acls) + { + ArrayList ordered = new ArrayList(acls); + Map deDuplicatedPermissions = new HashMap(acls.size()); + Collections.sort(ordered, new CMISConnector.AccessPermissionComparator()); + for (AccessPermission current : ordered) + { + String composedKey = current.getAuthority() + current.getPermission(); + if (current.getAccessStatus() == AccessStatus.ALLOWED) + { + deDuplicatedPermissions.put(composedKey, current); + } + else if (current.getAccessStatus() == AccessStatus.DENIED) + { + deDuplicatedPermissions.remove(composedKey); + } + } + + return new ArrayList(deDuplicatedPermissions.values()); + } + + /** + * Helper to construct the response object for the various getPermissions() calls. + * + * @param direct True to only retrieve direct permissions, false to get inherited also + * @param full True to retrieve full data string with [INHERITED|DIRECT] element + * This exists to maintain backward compatibility with existing permission APIs. + * + * @return Object[] of packed permission strings. + */ + protected Object[] retrieveAllSetPermissions(boolean direct, boolean full) + { + Set acls = this.services.getPermissionService().getAllSetPermissions(getNodeRef()); + List permissions = new ArrayList(acls.size()); + List ordered = getSortedACLs(acls); + for (AccessPermission permission : ordered) + { + if (!direct || permission.isSetDirectly()) + { + StringBuilder buf = new StringBuilder(64); + buf.append(permission.getAccessStatus()) + .append(';') + .append(permission.getAuthority()) + .append(';') + .append(permission.getPermission()); + if (full) + { + buf.append(';').append(permission.isSetDirectly() ? "DIRECT" : "INHERITED"); + } + permissions.add(buf.toString()); + } + } + return (Object[])permissions.toArray(new Object[permissions.size()]); + } + + /** + * @return Array of settable permissions for this Node + */ + public Scriptable getSettablePermissions() + { + Set permissions = this.services.getPermissionService().getSettablePermissions(getNodeRef()); + Object[] result = permissions.toArray(new Object[0]); + return Context.getCurrentContext().newArray(this.scope, result); + } + + /** + * @return true if the node inherits permissions from the parent node, false otherwise + */ + public boolean inheritsPermissions() + { + return this.services.getPermissionService().getInheritParentPermissions(this.nodeRef); + } + + /** + * Set whether this node should inherit permissions from the parent node. + * + * @param inherit True to inherit parent permissions, false otherwise. + */ + public void setInheritsPermissions(boolean inherit) + { + this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit); + } + + /** + * Set whether this node should inherit permissions from the parent node. If the operation takes + * too long and asyncCall parameter set accordingly, fixed ACLs method will be asynchronously called. + * + * @param inherit True to inherit parent permissions, false otherwise. + * @param asyncCall True if fixed ACLs should be asynchronously set when operation execution takes too long, false otherwise. + */ + public void setInheritsPermissions(boolean inherit, boolean asyncCall) + { + this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit, asyncCall); + } + + /** + * Apply a permission for ALL users to the node. + * + * @param permission Permission to apply + * @see org.alfresco.service.cmr.security.PermissionService + */ + public void setPermission(String permission) + { + ParameterCheck.mandatoryString("Permission Name", permission); + this.services.getPermissionService().setPermission( + this.nodeRef, PermissionService.ALL_AUTHORITIES, permission, true); + } + + /** + * Apply a permission for the specified authority (e.g. username or group) to the node. + * + * @param permission Permission to apply @see org.alfresco.service.cmr.security.PermissionService + * @param authority Authority (generally a username or group name) to apply the permission for + */ + public void setPermission(String permission, String authority) + { + ParameterCheck.mandatoryString("Permission Name", permission); + ParameterCheck.mandatoryString("Authority", authority); + this.services.getPermissionService().setPermission( + this.nodeRef, authority, permission, true); + } + + /** + * Remove a permission for ALL user from the node. + * + * @param permission Permission to remove @see org.alfresco.service.cmr.security.PermissionService + */ + public void removePermission(String permission) + { + ParameterCheck.mandatoryString("Permission Name", permission); + this.services.getPermissionService().deletePermission( + this.nodeRef, PermissionService.ALL_AUTHORITIES, permission); + } + + /** + * Remove a permission for the specified authority (e.g. username or group) from the node. + * + * @param permission Permission to remove @see org.alfresco.service.cmr.security.PermissionService + * @param authority Authority (generally a username or group name) to apply the permission for + */ + public void removePermission(String permission, String authority) + { + ParameterCheck.mandatoryString("Permission Name", permission); + ParameterCheck.mandatoryString("Authority", authority); + this.services.getPermissionService().deletePermission( + this.nodeRef, authority, permission); + } + + + // ------------------------------------------------------------------------------ + // Ownership API + + /** + * Set the owner of the node + */ + public void setOwner(String userId) + { + this.services.getOwnableService().setOwner(this.nodeRef, userId); + } + + /** + * Take ownership of the node. + */ + public void takeOwnership() + { + this.services.getOwnableService().takeOwnership(this.nodeRef); + } + + /** + * Get the owner of the node. + * + * @return String + */ + public String getOwner() + { + return this.services.getOwnableService().getOwner(this.nodeRef); + } + + + // ------------------------------------------------------------------------------ + // Create and Modify API + + /** + * Persist the modified properties of this Node. + */ + public void save() + { + // persist properties back to the node in the DB + Map props = new HashMap(getProperties().size()); + for (String key : this.properties.keySet()) + { + Serializable value = (Serializable) this.properties.get(key); + + QName qname = createQName(key); + + // MNT-15798 + if (ContentModel.PROP_CONTENT.equals(qname) && isScriptContent(value)) + { + ScriptContentData contentData = (ScriptContentData) value; + // Do not persist the contentData if it was not touched + if (!contentData.isDirty()) + { + continue; + } + } + + // perform the conversion from script wrapper object to repo serializable values + value = getValueConverter().convertValueForRepo(value); + + props.put(qname, value); + } + this.nodeService.setProperties(this.nodeRef, props); + } + + /** + * Re-sets the type of the node. Can be called in order specialise a node to a sub-type. This should be used + * with caution since calling it changes the type of the node and thus* implies a different set of aspects, + * properties and associations. It is the responsibility of the caller to ensure that the node is in a + * approriate state after changing the type. + * + * @param type Type to specialize the node + * + * @return true if successful, false otherwise + */ + public boolean specializeType(String type) + { + ParameterCheck.mandatoryString("Type", type); + + QName qnameType = createQName(type); + + // Ensure that we are performing a specialise + if (getQNameType().equals(qnameType) == false && + this.services.getDictionaryService().isSubClass(qnameType, getQNameType()) == true) + { + // Specialise the type of the node + this.nodeService.setType(this.nodeRef, qnameType); + this.type = qnameType; + + return true; + } + return false; + } + + /** + * Create a new File (cm:content) node as a child of this node. + *

+ * Once created the file should have content set using the content property. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param name Name of the file to create + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createFile(String name) + { + return createFile(name, null); + } + + /** + * Create a new File (cm:content) node as a child of this node. + *

+ * Once created the file should have content set using the content property. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param name Name of the file to create + * @param type Type of the file to create (if null, defaults to ContentModel.TYPE_CONTENT) + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createFile(String name, String type) + { + ParameterCheck.mandatoryString("Node Name", name); + + FileInfo fileInfo = this.services.getFileFolderService().create( + this.nodeRef, name, type == null ? ContentModel.TYPE_CONTENT : createQName(type)); + + reset(); + + ScriptNode file = newInstance(fileInfo.getNodeRef(), this.services, this.scope); + file.setMimetype(this.services.getMimetypeService().guessMimetype(name)); + + return file; + } + + /** + * Create a new folder (cm:folder) node as a child of this node. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param name Name of the folder to create + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createFolder(String name) + { + return createFolder(name, null); + } + + /** + * Create a new folder (cm:folder) node as a child of this node. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param name Name of the folder to create + * @param type Type of the folder to create (if null, defaults to ContentModel.TYPE_FOLDER) + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createFolder(String name, String type) + { + ParameterCheck.mandatoryString("Node Name", name); + + FileInfo fileInfo = this.services.getFileFolderService().create( + this.nodeRef, name, type == null ? ContentModel.TYPE_FOLDER : createQName(type)); + + reset(); + + return newInstance(fileInfo.getNodeRef(), this.services, this.scope); + } + + /** + * Create a new Node of the specified type as a child of this node. + * + * @param name Name of the node to create (can be null for a node without a 'cm:name' property) + * @param type QName type (fully qualified or short form such as 'cm:content') + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createNode(String name, String type) + { + return createNode(name, type, null, ContentModel.ASSOC_CONTAINS.toString()); + } + + /** + * Create a new Node of the specified type as a child of this node. + * + * @param name Name of the node to create (can be null for a node without a 'cm:name' property) + * @param type QName type (fully qualified or short form such as 'cm:content') + * @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains') + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createNode(String name, String type, String assocType) + { + return createNode(name, type, null, assocType); + } + + /** + * Create a new Node of the specified type as a child of this node. + * + * @param name Name of the node to create (can be null for a node without a 'cm:name' property) + * @param type QName type (fully qualified or short form such as 'cm:content') + * @param properties Associative array of the default properties for the node. + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createNode(String name, String type, Object properties) + { + return createNode(name, type, properties, ContentModel.ASSOC_CONTAINS.toString()); + } + + /** + * Create a new Node of the specified type as a child of this node. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param name Name of the node to create (can be null for a node without a 'cm:name' property) + * @param type QName type (fully qualified or short form such as 'cm:content') + * @param properties Associative array of the default properties for the node. + * @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains') + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createNode(String name, String type, Object properties, String assocType) + { + return createNode(name, type, properties, assocType, null); + } + + /** + * Create a new Node of the specified type as a child of this node. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param name Name of the node to create (can be null for a node without a 'cm:name' property) + * @param type QName type (fully qualified or short form such as 'cm:content') + * @param properties Associative array of the default properties for the node. + * @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains') + * @param assocName QName of the child association name (fully qualified or short form e.g. 'fm:discussion') + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createNode(String name, String type, Object properties, String assocType, String assocName) + { + ParameterCheck.mandatoryString("Node Type", type); + ParameterCheck.mandatoryString("Association Type", assocType); + + Map props = null; + + if (properties instanceof ScriptableObject) + { + props = new HashMap(4, 1.0f); + extractScriptableProperties((ScriptableObject)properties, props); + } + + if (name != null) + { + if (props == null) props = new HashMap(1, 1.0f); + props.put(ContentModel.PROP_NAME, name); + } + else + { + // set name for the assoc local name + name = GUID.generate(); + } + + ChildAssociationRef childAssocRef = this.nodeService.createNode( + this.nodeRef, + createQName(assocType), + assocName == null ? + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)) : + createQName(assocName), + createQName(type), + props); + + reset(); + + return newInstance(childAssocRef.getChildRef(), this.services, this.scope); + } + + /** + * Creates a new secondary association between the current node and the specified child node. + * The association is given the same name as the child node's primary association. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param node node to add as a child of this node + */ + public void addNode(ScriptNode node) + { + ParameterCheck.mandatory("node", node); + ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(node.nodeRef); + nodeService.addChild(this.nodeRef, node.nodeRef, ContentModel.ASSOC_CONTAINS, childAssocRef.getQName()); + reset(); + } + + /** + * Remove an existing child node of this node. + * + * Severs all parent-child relationships between two nodes. + *

+ * The child node will be cascade deleted if one of the associations was the + * primary association, i.e. the one with which the child node was created. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param node child node to remove + */ + public void removeNode(ScriptNode node) + { + ParameterCheck.mandatory("node", node); + nodeService.removeChild(this.nodeRef, node.nodeRef); + reset(); + } + + /** + * Create an association between this node and the specified target node. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param target Destination node for the association + * @param assocType Association type qname (short form or fully qualified) + */ + public Association createAssociation(ScriptNode target, String assocType) + { + ParameterCheck.mandatory("Target", target); + ParameterCheck.mandatoryString("Association Type Name", assocType); + + AssociationRef assocRef = this.nodeService.createAssociation(this.nodeRef, target.nodeRef, createQName(assocType)); + reset(); + return new Association(this.services, assocRef); + } + + /** + * Remove an association between this node and the specified target node. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param target Destination node on the end of the association + * @param assocType Association type qname (short form or fully qualified) + */ + public void removeAssociation(ScriptNode target, String assocType) + { + ParameterCheck.mandatory("Target", target); + ParameterCheck.mandatoryString("Association Type Name", assocType); + + this.nodeService.removeAssociation(this.nodeRef, target.nodeRef, createQName(assocType)); + reset(); + } + + /** + * Remove this node. Any references to this Node or its NodeRef should be + * discarded! + * + * Beware: Any unsaved property changes will be lost when this is called. To + * preserve property changes call {@link save()} first. + * + */ + public boolean remove() + { + return remove(false); + } + + /** + * Remove this node in a new transaction or not as specified. + * Any references to this Node or its NodeRef should be discarded! + * + * Beware: Any unsaved property changes will be lost when this is called. To + * preserve property changes call {@link save()} first. + * + */ + public boolean remove(boolean newTransaction) + { + boolean success = false; + + if (nodeService.exists(this.nodeRef)) + { + retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + nodeService.deleteNode(nodeRef); + return null; + } + }, false, newTransaction); + success = true; + } + + reset(); + + return success; + } + + /** + * Copy this Node to a new parent destination. Note that children of the source Node are not copied. + * + * @param destination Node + * + * @return The newly copied Node instance or null if failed to copy. + */ + public ScriptNode copy(ScriptNode destination) + { + ScriptNode copy = copy(destination, false); + + // ALF-9517 fix + if (copy != null && copy.hasAspect(ContentModel.ASPECT_VERSIONABLE.toString())) + { + copy.ensureVersioningEnabled(true, true); + } + return copy; + } + + /** + * Copy this Node and potentially all child nodes to a new parent destination. + * + * @param destination Node + * @param deepCopy True for a deep copy, false otherwise. + * + * @return The newly copied Node instance or null if failed to copy. + */ + public ScriptNode copy(ScriptNode destination, boolean deepCopy) + { + ParameterCheck.mandatory("Destination Node", destination); + + NodeRef copyRef = this.services.getCopyService().copyAndRename(this.nodeRef, destination.getNodeRef(), + ContentModel.ASSOC_CONTAINS, null, deepCopy); + ScriptNode copy = newInstance(copyRef, this.services, this.scope); + + return copy; + } + + /** + * Revert this Node to the specified version. Note this is not a deep revert of + * associations. + * This node must have the cm:versionable aspect. It will be checked out if required + * but will be checked in after the call. + * + * @param versionLabel to revert from + * + * @return the original Node that was checked out if reverted, {@code null} otherwise + * (if the version does not exist). + */ + public ScriptNode revert(String history, boolean majorVersion, String versionLabel) + { + return revert(history, majorVersion, versionLabel, false); + } + + /** + * Revert this Node to the specified version and potentially all child nodes. + * This node must have the cm:versionable aspect. It will be checked out if required + * but will be checked in after the call. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param history Version history note + * @param majorVersion True to save as a major version increment, false for minor version. + * @param versionLabel to revert from + * @param deep {@code true} for a deep revert, {@code false} otherwise. + * + * @return the original Node that was checked out if reverted, {@code null} otherwise + * (if the version does not exist). + */ + public ScriptNode revert(String history, boolean majorVersion, String versionLabel, boolean deep) + { + if (!getIsVersioned()) + { + return null; + } + + // Get the Version - needed to do the revert + Version version = services.getVersionService().getVersionHistory(nodeRef).getVersion(versionLabel); + if (version == null) + { + return null; + } + + ScriptNode originalNode = this; + //cancel editing if we want to revert + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY)) + { + originalNode = cancelCheckout(); + } + + // Revert the new (current) version of the node + services.getVersionService().revert(originalNode.getNodeRef(), version, deep); + + // Checkout/Checkin the node - to store the new version in version history + ScriptNode workingCopy = originalNode.checkout(); + originalNode = workingCopy.checkin(history, majorVersion); + + + return originalNode; + } + + /** + * Move this Node to a new parent destination. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param destination Node + * + * @return true on successful move, false on failure to move. + */ + public boolean move(ScriptNode destination) + { + ParameterCheck.mandatory("Destination Node", destination); + + this.primaryParentAssoc = this.nodeService.moveNode(this.nodeRef, destination.getNodeRef(), + ContentModel.ASSOC_CONTAINS, getPrimaryParentAssoc().getQName()); + + // reset cached values + reset(); + + return true; + } + + /** + * Move this Node from specified parent to a new parent destination. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param source Node + * @param destination Node + * @return true on successful move, false on failure to move. + */ + public boolean move(ScriptNode source, ScriptNode destination) + { + ParameterCheck.mandatory("Destination Node", destination); + + if (source == null) + { + return move(destination); + } + else + { + try + { + this.services.getFileFolderService().moveFrom(this.nodeRef, source.getNodeRef(), destination.getNodeRef(), null); + } + //MNT-7514 Uninformational error message on move when file name conflicts + catch (FileExistsException ex) + { + throw ex; + } + catch (Exception e) + { + throw new ScriptException("Can't move node", e); + } + } + + // reset cached values + reset(); + + return true; + } + + /** + * Add an aspect to the Node. As no properties are provided in this call, it can only be used to add aspects that do not require any mandatory properties. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * @param type Type name of the aspect to add + * + * @return true if the aspect was added successfully, false if an error occured. + */ + public boolean addAspect(String type) + { + return addAspect(type, null); + } + + /** + * Add an aspect to the Node. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param type Type name of the aspect to add + * @param props ScriptableObject (generally an assocative array) providing the named properties for the aspect + * - any mandatory properties for the aspect must be provided! + * + * @return true if the aspect was added successfully, false if an error occured. + */ + public boolean addAspect(String type, Object props) + { + ParameterCheck.mandatoryString("Aspect Type", type); + + Map aspectProps = null; + if (props instanceof ScriptableObject) + { + aspectProps = new HashMap(4, 1.0f); + extractScriptableProperties((ScriptableObject)props, aspectProps); + } + QName aspectQName = createQName(type); + if (aspectQName.equals(ContentModel.ASPECT_VERSIONABLE)) + { + // ALF-13719 need to taking into account script properties for versionable aspect + if (aspectProps != null) + { + Serializable autoVersionObj, autoVersionPropsObj; + autoVersionObj = aspectProps.get(ContentModel.PROP_AUTO_VERSION); + autoVersionPropsObj = aspectProps.get(ContentModel.PROP_AUTO_VERSION_PROPS); + ensureVersioningEnabled(autoVersionObj instanceof Boolean ? ((Boolean) autoVersionObj) : true, + autoVersionPropsObj instanceof Boolean ? ((Boolean) autoVersionPropsObj) : true); + } + else + { + // MNT-9369, read props from contentModel.xml, sets to false, false if there is no defaults. + Map versionableProps = services.getDictionaryService().getAspect(ContentModel.ASPECT_VERSIONABLE).getProperties(); + boolean autoVersion = Boolean.parseBoolean(versionableProps.get(ContentModel.PROP_AUTO_VERSION).getDefaultValue()); + boolean autoVersionProps = Boolean.parseBoolean(versionableProps.get(ContentModel.PROP_AUTO_VERSION_PROPS).getDefaultValue()); + ensureVersioningEnabled(autoVersion, autoVersionProps); + } + } + else + { + this.nodeService.addAspect(this.nodeRef, aspectQName, aspectProps); + } + + // reset the relevant cached node members + reset(); + + return true; + } + + /** + * Extract a map of properties from a scriptable object (generally an associative array) + * + * @param scriptable The scriptable object to extract name/value pairs from. + * @param map The map to add the converted name/value pairs to. + */ + private void extractScriptableProperties(ScriptableObject scriptable, Map map) + { + // we need to get all the keys to the properties provided + // and convert them to a Map of QName to Serializable objects + Object[] propIds = scriptable.getIds(); + for (int i = 0; i < propIds.length; i++) + { + // work on each key in turn + Object propId = propIds[i]; + + // we are only interested in keys that are formed of Strings i.e. QName.toString() + if (propId instanceof String) + { + // get the value out for the specified key - it must be Serializable + String key = (String)propId; + Object value = scriptable.get(key, scriptable); + if (value instanceof Serializable) + { + value = getValueConverter().convertValueForRepo((Serializable)value); + map.put(createQName(key), (Serializable)value); + } + } + } + } + + /** + * Remove aspect from the node. + * + * Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link #save()} first. + * + * @param type the aspect type + * + * @return true if successful, false otherwise + */ + public boolean removeAspect(String type) + { + ParameterCheck.mandatoryString("Aspect Type", type); + + QName aspectQName = createQName(type); + this.nodeService.removeAspect(this.nodeRef, aspectQName); + + // reset the relevant cached node members + reset(); + + return true; + } + + + // ------------------------------------------------------------------------------ + // Checkout/Checkin Services + + /** + * Ensures that this document has the cm:versionable aspect applied to it, + * and that it has the initial version in the version store. + * Calling this on a versioned node with a version store entry will have + * no effect. + * Calling this on a newly uploaded share node will have versioning enabled + * for it (Share currently does lazy versioning to improve performance of + * documents that are uploaded but never edited, and multi upload performance). + * + * @param autoVersion If the cm:versionable aspect is applied, should auto versioning be requested? + * @param autoVersionProps If the cm:versionable aspect is applied, should auto versioning of properties be requested? + */ + public void ensureVersioningEnabled(boolean autoVersion, boolean autoVersionProps) + { + Map props = new HashMap(1, 1.0f); + props.put(ContentModel.PROP_AUTO_VERSION, autoVersion); + props.put(ContentModel.PROP_AUTO_VERSION_PROPS, autoVersionProps); + + this.services.getVersionService().ensureVersioningEnabled(nodeRef, props); + } + + /** + * Ensures that this document has the cm:versionable aspect applied to it, + * and that it has the initial version in the version store. + * Calling this on a versioned node with a version store entry will have + * no effect. + * Calling this on a newly uploaded share node will have versioning enabled + * for it (Share currently does lazy versioning to improve performance of + * documents that are uploaded but never edited, and multi upload performance). + * + */ + public void ensureVersioningEnabled() + { + this.services.getVersionService().ensureVersioningEnabled(nodeRef, null); + } + + /** + * Create a version of this document. Note: this will add the cm:versionable aspect. + * + * @param history Version history note + * @param majorVersion True to save as a major version increment, false for minor version. + * + * @return ScriptVersion object representing the newly added version node + */ + public ScriptVersion createVersion(String history, boolean majorVersion) + { + Map props = new HashMap(2, 1.0f); + props.put(Version.PROP_DESCRIPTION, history); + props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR); + ScriptVersion version = new ScriptVersion(this.services.getVersionService().createVersion(this.nodeRef, props), this.services, this.scope); + this.versions = null; + return version; + } + + /** + * Determines if this node is versioned + * + * @return true => is versioned + */ + public boolean getIsVersioned() + { + return this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); + } + + /** + * Gets the version history + * + * @return version history + */ + public Scriptable getVersionHistory() + { + if (this.versions == null && getIsVersioned()) + { + VersionHistory history = this.services.getVersionService().getVersionHistory(this.nodeRef); + if (history != null) + { + Collection allVersions = history.getAllVersions(); + Object[] versions = new Object[allVersions.size()]; + int i = 0; + for (Version version : allVersions) + { + versions[i++] = new ScriptVersion(version, this.services, this.scope); + } + this.versions = Context.getCurrentContext().newArray(this.scope, versions); + } + } + return this.versions; + } + + /** + * Gets the version of this node specified by version label + * + * @param versionLabel version label + * @return version of node, or null if node is not versioned, or label does not exist + */ + public ScriptVersion getVersion(String versionLabel) + { + if (!getIsVersioned()) + { + return null; + } + VersionHistory history = this.services.getVersionService().getVersionHistory(this.nodeRef); + Version version = history.getVersion(versionLabel); + if (version == null) + { + return null; + } + return new ScriptVersion(version, this.services, this.scope); + } + + /** + * Perform a check-out of this document into the current parent space. + * + * @return the working copy Node for the checked out document + */ + public ScriptNode checkout() + { + NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef); + ScriptNode workingCopy = newInstance(workingCopyRef, this.services, this.scope); + + // reset the aspect and properties as checking out a document causes changes + this.properties = null; + this.aspects = null; + + return workingCopy; + } + + /** + * Performs a check-out of this document for the purposes of an upload + * + * @return ScriptNode + */ + public ScriptNode checkoutForUpload() + { + AlfrescoTransactionSupport.bindResource("checkoutforupload", Boolean.TRUE.toString()); + services.getRuleService().disableRules(); + try + { + return checkout(); + } + finally + { + services.getRuleService().enableRules(); + } + } + + /** + * Perform a check-out of this document into the specified destination space. + * + * @param destination + * Destination for the checked out document working copy Node. + * @return the working copy Node for the checked out document + */ + public ScriptNode checkout(ScriptNode destination) + { + ParameterCheck.mandatory("Destination Node", destination); + + ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(destination.getNodeRef()); + NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef, + destination.getNodeRef(), ContentModel.ASSOC_CONTAINS, childAssocRef.getQName()); + ScriptNode workingCopy = newInstance(workingCopyRef, this.services, this.scope); + + // reset the aspect and properties as checking out a document causes changes + this.properties = null; + this.aspects = null; + + return workingCopy; + } + + /** + * Check-in a working copy document. The current state of the working copy is copied to the original node, + * this will include any content updated in the working node. Note that this method can only be called on a + * working copy Node. + * + * @return the original Node that was checked out. + */ + public ScriptNode checkin() + { + return checkin("", false); + } + + /** + * Check-in a working copy document. The current state of the working copy is copied to the original node, + * this will include any content updated in the working node. Note that this method can only be called on a + * working copy Node. + * + * @param history Version history note + * + * @return the original Node that was checked out. + */ + public ScriptNode checkin(String history) + { + return checkin(history, false); + } + + /** + * Check-in a working copy document. The current state of the working copy is copied to the original node, + * this will include any content updated in the working node. Note that this method can only be called on a + * working copy Node. + * + * @param history Version history note + * @param majorVersion True to save as a major version increment, false for minor version. + * + * @return the original Node that was checked out. + */ + public ScriptNode checkin(String history, boolean majorVersion) + { + Map props = new HashMap(2, 1.0f); + props.put(Version.PROP_DESCRIPTION, history); + props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR); + NodeRef original = this.services.getCheckOutCheckInService().checkin(this.nodeRef, props); + this.versions = null; + return newInstance(original, this.services, this.scope); + } + + /** + * Removes the lock on a node. + * + */ + public void unlock() + { + this.services.getLockService().unlock(this.nodeRef); + } + + /** + * Gets the check-out of a working copy document + * @return the original Node that was checked out or null if it's not a working copy + */ + public ScriptNode getCheckedOut() + { + NodeRef original = this.services.getCheckOutCheckInService().getCheckedOut(this.nodeRef); + + if(original != null) + { + return newInstance(original, this.services, this.scope); + } + else + { + return null; + } + } + + /** + * Cancel the check-out of a working copy document. The working copy will be deleted and any changes made to it + * are lost. Note that this method can only be called on a working copy Node. The reference to this working copy + * Node should be discarded. + * + * @return the original Node that was checked out. + */ + public ScriptNode cancelCheckout() + { + NodeRef original = this.services.getCheckOutCheckInService().cancelCheckout(this.nodeRef); + return newInstance(original, this.services, this.scope); + } + + // ------------------------------------------------------------------------------ + // Transformation and Rendering API + + /** + * Transform a document to a new document mimetype format. A copy of the document is made and the extension + * changed to match the new mimetype, then the transformation isapplied. + * + * @param mimetype Mimetype destination for the transformation + * + * @return Node representing the newly transformed document. + */ + public ScriptNode transformDocument(String mimetype) + { + return transformDocument(mimetype, getPrimaryParentAssoc().getParentRef()); + } + + /** + * Transform a document to a new document mimetype format. A copy of the document is made in the specified + * destination folder and the extension changed to match the new mimetype, then then transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param destination Destination folder location + * + * @return Node representing the newly transformed document. + */ + public ScriptNode transformDocument(String mimetype, ScriptNode destination) + { + return transformDocument(mimetype, destination.getNodeRef()); + } + + private ScriptNode transformDocument(String mimetype, NodeRef destination) + { + ParameterCheck.mandatoryString("Mimetype", mimetype); + ParameterCheck.mandatory("Destination Node", destination); + final NodeRef sourceNodeRef = nodeRef; + + // the delegate definition for transforming a document + Transformer transformer = new Transformer() + { + public ScriptNode transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, + ContentWriter writer) + { + ScriptNode transformedNode = null; + TransformationOptions options = new TransformationOptions(); + options.setSourceNodeRef(sourceNodeRef); + + try + { + contentService.transform(reader, writer, options); + transformedNode = newInstance(nodeRef, services, scope); + } + catch (NoTransformerException e) + { + // ignore + } + catch (AlfrescoRuntimeException e) + { + Throwable rootCause = ((AlfrescoRuntimeException)e).getRootCause(); + String message = rootCause.getMessage(); + message = message == null ? "" : message; + if (rootCause instanceof UnimportantTransformException) + { + logger.debug(message); + // ignore + } + else if (rootCause instanceof UnsupportedTransformationException) + { + logger.error(message); + // ignore + } + else + { + throw e; + } + } + return transformedNode; + } + }; + + return transformNode(transformer, mimetype, destination); + } + + /** + * Generic method to transform Node content from one mimetype to another. + * + * @param transformer The Transformer delegate supplying the transformation logic + * @param mimetype Mimetype of the destination content + * @param destination Destination folder location for the resulting document + * + * @return Node representing the transformed content - or null if the transform failed + */ + private ScriptNode transformNode(Transformer transformer, String mimetype, NodeRef destination) + { + ScriptNode transformedNode = null; + + // get the content reader + ContentService contentService = this.services.getContentService(); + ContentReader reader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT); + + // only perform the transformation if some content is available + if (reader != null) + { + // Copy the content node to a new node + String copyName = TransformActionExecuter.transformName(this.services.getMimetypeService(), getName(), + mimetype, true); + NodeRef copyNodeRef = this.services.getCopyService().copy(this.nodeRef, destination, + ContentModel.ASSOC_CONTAINS, + QName.createQName(ContentModel.PROP_CONTENT.getNamespaceURI(), QName.createValidLocalName(copyName)), + false); + + // modify the name of the copy to reflect the new mimetype + this.nodeService.setProperty(copyNodeRef, ContentModel.PROP_NAME, copyName); + + // get the writer and set it up + ContentWriter writer = contentService.getWriter(copyNodeRef, ContentModel.PROP_CONTENT, true); + writer.setMimetype(mimetype); // new mimetype + writer.setEncoding(reader.getEncoding()); // original encoding + + // Try and transform the content using the supplied delegate + transformedNode = transformer.transform(contentService, copyNodeRef, reader, writer); + } + + return transformedNode; + } + + /** + * Transform an image to a new image format. A copy of the image document is made and the extension changed to + * match the new mimetype, then the transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * + * @return Node representing the newly transformed image. + */ + public ScriptNode transformImage(String mimetype) + { + return transformImage(mimetype, null, getPrimaryParentAssoc().getParentRef()); + } + + /** + * Transform an image to a new image format. A copy of the image document is made and the extension changed to + * match the new mimetype, then the transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param options Image convert command options + * + * @return Node representing the newly transformed image. + */ + public ScriptNode transformImage(String mimetype, String options) + { + return transformImage(mimetype, options, getPrimaryParentAssoc().getParentRef()); + } + + /** + * Transform an image to a new image mimetype format. A copy of the image document is made in the specified + * destination folder and the extension changed to match the newmimetype, then then transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param destination Destination folder location + * + * @return Node representing the newly transformed image. + */ + public ScriptNode transformImage(String mimetype, ScriptNode destination) + { + ParameterCheck.mandatory("Destination Node", destination); + return transformImage(mimetype, null, destination.getNodeRef()); + } + + /** + * Transform an image to a new image mimetype format. A copy of the image document is made in the specified + * destination folder and the extension changed to match the new + * mimetype, then then transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param options Image convert command options + * @param destination Destination folder location + * + * @return Node representing the newly transformed image. + */ + public ScriptNode transformImage(String mimetype, String options, ScriptNode destination) + { + ParameterCheck.mandatory("Destination Node", destination); + return transformImage(mimetype, options, destination.getNodeRef()); + } + + private ScriptNode transformImage(String mimetype, final String options, NodeRef destination) + { + ParameterCheck.mandatoryString("Mimetype", mimetype); + final NodeRef sourceNodeRef = nodeRef; + + // the delegate definition for transforming an image + Transformer transformer = new Transformer() + { + public ScriptNode transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, + ContentWriter writer) + { + ImageTransformationOptions imageOptions = new ImageTransformationOptions(); + imageOptions.setSourceNodeRef(sourceNodeRef); + + if (options != null) + { + imageOptions.setCommandOptions(options); + } + contentService.getImageTransformer().transform(reader, writer, imageOptions); + + return newInstance(nodeRef, services, scope); + } + }; + + return transformNode(transformer, mimetype, destination); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template Node of the template to execute + * + * @return output of the template execution + */ + public String processTemplate(ScriptNode template) + { + ParameterCheck.mandatory("Template Node", template); + return processTemplate(template.getContent(), null, null); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template Node of the template to execute + * @param args Scriptable object (generally an associative array) containing the name/value pairs of + * arguments to be passed to the template + * + * @return output of the template execution + */ + public String processTemplate(ScriptNode template, Object args) + { + ParameterCheck.mandatory("Template Node", template); + return processTemplate(template.getContent(), null, (ScriptableObject)args); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template The template to execute + * + * @return output of the template execution + */ + public String processTemplate(String template) + { + ParameterCheck.mandatoryString("Template", template); + return processTemplate(template, null, null); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template The template to execute + * @param args Scriptable object (generally an associative array) containing the name/value pairs of + * arguments to be passed to the template + * + * @return output of the template execution + */ + public String processTemplate(String template, Object args) + { + ParameterCheck.mandatoryString("Template", template); + return processTemplate(template, null, (ScriptableObject)args); + } + + private String processTemplate(String template, NodeRef templateRef, ScriptableObject args) + { + Object person = (Object)scope.get("person", scope); + Object companyhome = (Object)scope.get("companyhome", scope); + Object userhome = (Object)scope.get("userhome", scope); + + // build default model for the template processing + Map model = this.services.getTemplateService().buildDefaultModel( + (person.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)person).unwrap()).getNodeRef(), + (companyhome.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)companyhome).unwrap()).getNodeRef(), + (userhome.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)userhome).unwrap()).getNodeRef(), + templateRef, + null); + + // add the current node as either the document/space as appropriate + if (this.getIsDocument()) + { + model.put("document", this.nodeRef); + model.put("space", getPrimaryParentAssoc().getParentRef()); + } + else + { + model.put("space", this.nodeRef); + } + + // add the supplied args to the 'args' root object + if (args != null) + { + // we need to get all the keys to the properties provided + // and convert them to a Map of QName to Serializable objects + Object[] propIds = args.getIds(); + Map templateArgs = new HashMap(propIds.length); + for (int i = 0; i < propIds.length; i++) + { + // work on each key in turn + Object propId = propIds[i]; + + // we are only interested in keys that are formed of Strings i.e. QName.toString() + if (propId instanceof String) + { + // get the value out for the specified key - make sure it is Serializable + Object value = args.get((String) propId, args); + value = getValueConverter().convertValueForRepo((Serializable)value); + if (value != null) + { + templateArgs.put((String) propId, value.toString()); + } + } + } + // add the args to the model as the 'args' root object + model.put("args", templateArgs); + } + + // execute template! + // TODO: check that script modified nodes are reflected... + return this.services.getTemplateService().processTemplateString(null, template, model); + } + + // ------------------------------------------------------------------------------ + // Thumbnail Methods + + /** + * Creates a thumbnail for the content property of the node. + * + * The thumbnail name correspionds to pre-set thumbnail details stored in the + * repository. + * + * @param thumbnailName the name of the thumbnail + * @return ScriptThumbnail the newly create thumbnail node + */ + public ScriptThumbnail createThumbnail(String thumbnailName) + { + return createThumbnail(thumbnailName, false); + } + + /** + * Creates a thumbnail for the content property of the node. + * + * The thumbnail name corresponds to pre-set thumbnail details stored in the + * repository. + * + * If the thumbnail is created asynchronously then the result will be null and creation + * of the thumbnail will occure at some point in the background. + * + * @param thumbnailName the name of the thumbnail + * @param async indicates whether the thumbnail is create asynchronously or not + * @return ScriptThumbnail the newly create thumbnail node or null if async creation occures + */ + public ScriptThumbnail createThumbnail(String thumbnailName, boolean async) + { + return createThumbnail(thumbnailName, async, false); + } + + /** + * Creates a thumbnail for the content property of the node. + * + * The thumbnail name corresponds to pre-set thumbnail details stored in the + * repository. + * + * If the thumbnail is created asynchronously then the result will be null and creation + * of the thumbnail will occure at some point in the background. + * + * If foce param specified system.thumbnail.generate is ignoring. Could be used for preview creation + * + * @param thumbnailName the name of the thumbnail + * @param async indicates whether the thumbnail is create asynchronously or not + * @param force ignore system.thumbnail.generate=false + * @return ScriptThumbnail the newly create thumbnail node or null if async creation occures + */ + public ScriptThumbnail createThumbnail(String thumbnailName, boolean async, boolean force) + { + final ThumbnailService thumbnailService = services.getThumbnailService(); + + ScriptThumbnail result = null; + + // If thumbnail generation has been configured off, then don't bother with any of this. + // We need to create preview for node even if system.thumbnail.generate=false + if (force || thumbnailService.getThumbnailsEnabled()) + { + // Use the thumbnail registy to get the details of the thumbail + ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry(); + ThumbnailDefinition details = registry.getThumbnailDefinition(thumbnailName); + if (details == null) + { + // Throw exception + throw new ScriptException("The thumbnail name '" + thumbnailName + "' is not registered"); + } + + // If there's nothing currently registered to generate thumbnails for the + // specified mimetype, then log a message and bail out + String nodeMimeType = getMimetype(); + Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); + if (!ContentData.hasContent(contentData)) + { + if (logger.isDebugEnabled()) + logger.debug("Unable to create thumbnail '" + details.getName() + "' as there is no content"); + return null; + } + if (!registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), nodeMimeType, getSize(), nodeRef, details)) + { + logger.info("Unable to create thumbnail '" + details.getName() + "' for " + + nodeMimeType + " as no transformer is currently available."); + return null; + } + + // Have the thumbnail created + if (async == false) + { + try + { + // Create the thumbnail + NodeRef thumbnailNodeRef = thumbnailService.createThumbnail( + this.nodeRef, + ContentModel.PROP_CONTENT, + details.getMimetype(), + details.getTransformationOptions(), + details.getName()); + + // Create the thumbnail script object + result = new ScriptThumbnail(thumbnailNodeRef, this.services, this.scope); + } + catch (AlfrescoRuntimeException e) + { + Throwable rootCause = e.getRootCause(); + if (rootCause instanceof UnimportantTransformException) + { + logger.debug("Unable to create thumbnail '" + details.getName() + "' as "+rootCause.getMessage()); + return null; + } + throw e; + } + } + else + { + Action action = ThumbnailHelper.createCreateThumbnailAction(details, services); + + // Queue async creation of thumbnail + this.services.getActionService().executeAction(action, this.nodeRef, true, true); + } + } + return result; + } + + /** + * Get the given thumbnail for the content property + * + * @param thumbnailName the thumbnail name + * @return ScriptThumbnail the thumbnail + */ + public ScriptThumbnail getThumbnail(String thumbnailName) + { + ScriptThumbnail result = null; + NodeRef thumbnailNodeRef = this.services.getThumbnailService().getThumbnailByName( + this.nodeRef, + ContentModel.PROP_CONTENT, + thumbnailName); + if (thumbnailNodeRef != null) + { + result = new ScriptThumbnail(thumbnailNodeRef, this.services, this.scope); + } + return result; + } + + /** + * Get the all the thumbnails for a given node's content property. + * + * @return Scriptable list of thumbnails, empty if none available + */ + public ScriptThumbnail[] getThumbnails() + { + List thumbnails = this.services.getThumbnailService().getThumbnails( + this.nodeRef, + ContentModel.PROP_CONTENT, + null, + null); + + List result = new ArrayList(thumbnails.size()); + for (NodeRef thumbnail : thumbnails) + { + ScriptThumbnail scriptThumbnail = new ScriptThumbnail(thumbnail, this.services, this.scope); + result.add(scriptThumbnail); + } + return (ScriptThumbnail[])result.toArray(new ScriptThumbnail[result.size()]); + } + + /** + * Returns the names of the thumbnail defintions that can be applied to the content property of + * this node. + *

+ * Thumbanil defintions only appear in this list if they can produce a thumbnail for the content + * found in the content property. This will be determined by looking at the mimetype of the content + * and the destinatino mimetype of the thumbnail. + * + * @return String[] array of thumbnail names that are valid for the current content type + */ + public String[] getThumbnailDefinitions() + { + ThumbnailService thumbnailService = this.services.getThumbnailService(); + + List result = new ArrayList(7); + + Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); + + if (ContentData.hasContent(contentData)) + { + String mimetype = contentData.getMimetype(); + List thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(mimetype, contentData.getSize()); + for (ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions) + { + result.add(thumbnailDefinition.getName()); + } + } + + return (String[])result.toArray(new String[result.size()]); + } + /** + * This version of the method name spelling is retained (for now) for backwards compatibility + * @see #getThumbnailDefinitions() + */ + @Deprecated + public String[] getThumbnailDefintions() + { + return getThumbnailDefinitions(); + } + + + // ------------------------------------------------------------------------------ + // Tag methods + + /** + * Clear the node's tags + */ + public void clearTags() + { + this.services.getTaggingService().clearTags(this.nodeRef); + updateTagProperty(); + } + + /** + * Adds a tag to the node + * + * @param tag tag name + */ + public void addTag(String tag) + { + this.services.getTaggingService().addTag(this.nodeRef, tag); + updateTagProperty(); + } + + /** + * Adds all the tags to the node + * + * @param tags array of tag names + */ + public void addTags(String[] tags) + { + this.services.getTaggingService().addTags(this.nodeRef, Arrays.asList(tags)); + updateTagProperty(); + } + + /** + * Removes a tag from the node + * + * @param tag tag name + */ + public void removeTag(String tag) + { + this.services.getTaggingService().removeTag(this.nodeRef, tag); + updateTagProperty(); + } + + /** + * Removes all the tags from the node + * + * @param tags array of tag names + */ + public void removeTags(String[] tags) + { + this.services.getTaggingService().removeTags(this.nodeRef, Arrays.asList(tags)); + updateTagProperty(); + } + + /** + * Get all the tags applied to this node + * + * @return String[] array containing all the tag applied to this node + */ + public String[] getTags() + { + String[] result = null; + List tags = this.services.getTaggingService().getTags(this.nodeRef); + if (tags.isEmpty() == true) + { + result = new String[0]; + } + else + { + result = (String[])tags.toArray(new String[tags.size()]); + } + return result; + } + + /** + * Set the tags applied to this node. This overwirtes the list of tags currently applied to the + * node. + * + * @param tags array of tags + */ + public void setTags(String[] tags) + { + this.services.getTaggingService().setTags(this.nodeRef, Arrays.asList(tags)); + updateTagProperty(); + } + + private void updateTagProperty() + { + Serializable tags = this.services.getNodeService().getProperty(this.nodeRef, ContentModel.PROP_TAGS); + if (this.properties != null) + { + this.properties.put(ContentModel.PROP_TAGS.toString(), getValueConverter().convertValueForScript(ContentModel.PROP_TAGS, tags)); + } + } + + /** + * Sets whether this node is a tag scope or not + * + * @param value true if this node is a tag scope, false otherwise + */ + public void setIsTagScope(boolean value) + { + boolean currentValue = this.services.getTaggingService().isTagScope(this.nodeRef); + if (currentValue != value) + { + if (value == true) + { + // Add the tag scope + this.services.getTaggingService().addTagScope(this.nodeRef); + } + else + { + // Remove the tag scope + this.services.getTaggingService().removeTagScope(this.nodeRef); + } + } + } + + /** + * Gets whether this node is a tag scope + * + * @return boolean true if this node is a tag scope, false otherwise + */ + public boolean getIsTagScope() + { + return this.services.getTaggingService().isTagScope(this.nodeRef); + } + + /** + * Gets the 'nearest' tag scope to this node by travesing up the parent hierarchy untill one is found. + *

+ * If none is found, null is returned. + * + * @return TagScope the 'nearest' tag scope + */ + public TagScope getTagScope() + { + TagScope tagScope = null; + org.alfresco.service.cmr.tagging.TagScope tagScopeImpl = this.services.getTaggingService().findTagScope(this.nodeRef); + if (tagScopeImpl != null) + { + tagScope = new TagScope(this.services.getTaggingService(), tagScopeImpl); + } + return tagScope; + } + + /** + * Gets all (deep) children of this node that have the tag specified. + * + * @param tag tag name + * @return ScriptNode[] nodes that are deep children of the node with the tag + */ + public ScriptNode[] childrenByTags(String tag) + { + List nodeRefs = this.services.getTaggingService().findTaggedNodes(this.nodeRef.getStoreRef(), tag, this.nodeRef); + ScriptNode[] nodes = new ScriptNode[nodeRefs.size()]; + int index = 0; + for (NodeRef node : nodeRefs) + { + nodes[index] = new ScriptNode(node, this.services, this.scope); + index ++; + } + return nodes; + } + + + // ------------------------------------------------------------------------------ + // Workflow methods + + /** + * Get active workflow instances this node belongs to + * + * @return the active workflow instances this node belongs to + */ + public Scriptable getActiveWorkflows() + { + if (this.activeWorkflows == null) + { + WorkflowService workflowService = this.services.getWorkflowService(); + + List workflowInstances = workflowService.getWorkflowsForContent(this.nodeRef, true); + Object[] jsWorkflowInstances = new Object[workflowInstances.size()]; + int index = 0; + for (WorkflowInstance workflowInstance : workflowInstances) + { + jsWorkflowInstances[index++] = new JscriptWorkflowInstance(workflowInstance, this.services, this.scope); + } + this.activeWorkflows = Context.getCurrentContext().newArray(this.scope, jsWorkflowInstances); + } + + return this.activeWorkflows; + } + + // ------------------------------------------------------------------------------ + // Site methods + + /** + * Returns the short name of the site this node is located within. If the + * node is not located within a site null is returned. + * + * @return The short name of the site this node is located within, null + * if the node is not located within a site. + */ + public String getSiteShortName() + { + if (!this.siteNameResolved) + { + this.siteNameResolved = true; + + Path path = this.services.getNodeService().getPath(getNodeRef()); + + if (logger.isDebugEnabled()) + logger.debug("Determing if node is within a site using path: " + path); + + for (int i = 0; i < path.size(); i++) + { + if ("st:sites".equals(path.get(i).getPrefixedString(this.services.getNamespaceService()))) + { + // we now know the node is in a site, find the next element in the array (if there is one) + if ((i+1) < path.size()) + { + // get the site name + Path.Element siteName = path.get(i+1); + + // remove the "cm:" prefix and add to result object + this.siteName = ISO9075.decode(siteName.getPrefixedString( + this.services.getNamespaceService()).substring(3)); + } + + break; + } + } + } + + if (logger.isDebugEnabled()) + { + logger.debug(this.siteName != null ? + "Node is in the site named \"" + this.siteName + "\"" : "Node is not in a site"); + } + + return this.siteName; + } + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Override Object.toString() to provide useful debug output + */ + public String toString() + { + if (this.nodeService.exists(nodeRef)) + { + if (this.services.getPermissionService().hasPermission(nodeRef, PermissionService.READ_PROPERTIES) == AccessStatus.ALLOWED) + { + // TODO: DC: Allow debug output of property values - for now it's disabled as this could potentially + // follow a large network of nodes. Unfortunately, JBPM issues unprotected debug statements + // where node.toString is used - will request this is fixed in next release of JBPM. + return "Node Type: " + getType() + ", Node Aspects: " + getAspectsSet().toString(); + } + else + { + return "Access denied to node " + nodeRef; + } + + } + else + { + return "Node no longer exists: " + nodeRef; + } + } + + /** + * Returns the JSON representation of this node. + * + * @param useShortQNames if true short-form qnames will be returned, else long-form. + * @return The JSON representation of this node + */ + public String toJSON(boolean useShortQNames) + { + // This method is used by the /api/metadata web script + String jsonStr = "{}"; + + if (this.nodeService.exists(nodeRef)) + { + if(this.services.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "getProperties", this.nodeRef) == AccessStatus.ALLOWED) + { + JSONObject json = new JSONObject(); + + try + { + // add type info + json.put("nodeRef", this.getNodeRef().toString()); + + String typeString = useShortQNames ? getShortQName(this.getQNameType()) + : this.getType(); + json.put("type", typeString); + json.put("mimetype", this.getMimetype()); + + // Fetch all properties + Map nodeProperties = this.nodeService.getProperties(this.nodeRef); + + // Do any special conversion steps that are needed + for (QName longQName : nodeProperties.keySet()) + { + Serializable value = nodeProperties.get(longQName); + + if (value instanceof Date) + { + value = ISO8601DateFormat.format((Date)value); + nodeProperties.put(longQName, value); + } + if (value instanceof NodeRef) + { + value = ((NodeRef)value).toString(); + nodeProperties.put(longQName, value); + } + } + + if (useShortQNames) + { + Map nodePropertiesShortQNames + = new LinkedHashMap(nodeProperties.size()); + for (QName nextLongQName : nodeProperties.keySet()) + { + try + { + String nextShortQName = getShortQName(nextLongQName); + nodePropertiesShortQNames.put(nextShortQName, nodeProperties.get(nextLongQName)); + } + catch (NamespaceException ne) + { + // ignore properties that do not have a registered namespace + + if (logger.isDebugEnabled()) + logger.debug("Ignoring property '" + nextLongQName + "' as it's namespace is not registered"); + } + } + json.put("properties", nodePropertiesShortQNames); + } + else + { + json.put("properties", nodeProperties); + } + + // add aspects as an array + Set nodeAspects = this.nodeService.getAspects(this.nodeRef); + if (useShortQNames) + { + Set nodeAspectsShortQNames + = new LinkedHashSet(nodeAspects.size()); + for (QName nextLongQName : nodeAspects) + { + String nextShortQName = getShortQName(nextLongQName); + nodeAspectsShortQNames.add(nextShortQName); + } + json.put("aspects", nodeAspectsShortQNames); + } + else + { + json.put("aspects", nodeAspects); + } + } + catch (JSONException error) + { + error.printStackTrace(); + } + + jsonStr = json.toString(); + } + } + + return jsonStr; + } + + /** + * Returns the JSON representation of this node. Long-form QNames are used in the + * result. + * + * @return The JSON representation of this node + */ + public String toJSON() + { + return this.toJSON(false); + } + + /** + * Given a long-form QName, this method uses the namespace service to create a + * short-form QName string. + * + * @param longQName QName + * @return the short form of the QName string, e.g. "cm:content" + */ + protected String getShortQName(QName longQName) + { + return longQName.toPrefixString(services.getNamespaceService()); + } + + /** + * Helper to create a QName from either a fully qualified or short-name QName string + * + * @param s Fully qualified or short-name QName string + * + * @return QName + */ + protected QName createQName(String s) + { + QName qname; + if (s.indexOf(NAMESPACE_BEGIN) != -1) + { + qname = QName.createQName(s); + } + else + { + qname = QName.createQName(s, this.services.getNamespaceService()); + } + return qname; + } + + /** + * Reset the Node cached state + */ + public void reset() + { + this.name = null; + this.type = null; + this.properties = null; + this.aspects = null; + this.targetAssocs = null; + this.sourceAssocs = null; + this.childAssocs = null; + this.children = null; + this.hasChildren = null; + this.parentAssocs = null; + this.displayPath = null; + this.qnamePath = null; + this.isDocument = null; + this.isContainer = null; + this.parent = null; + this.primaryParentAssoc = null; + this.activeWorkflows = null; + this.siteName = null; + this.siteNameResolved = false; + } + + /** + * Return a list or a single Node from executing an xpath against the parent Node. + * + * @param xpath XPath to execute + * @param firstOnly True to return the first result only + * + * @return Object[] can be empty but never null + */ + private Object[] getChildrenByXPath(String xpath, QueryParameterDefinition[] params, boolean firstOnly) + { + Object[] result = null; + + if (xpath.length() != 0) + { + if (logger.isDebugEnabled()) + { + logger.debug("Executing xpath: " + xpath); + if (params != null) + { + for (int i=0; itrue if the contentData has a binary (content URL) associated and the updates on contentData and related properties should be saved. + * false if the contentData has a temporary value and no actual binary to be persisted. + */ + public boolean isDirty() + { + return this.isDirty; + } + + /** + * Set the content stream + * + * @param content Content string to set + */ + public void setContent(String content) + { + ContentService contentService = services.getContentService(); + ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); + writer.setMimetype(getMimetype()); // use existing mimetype value + writer.putContent(content); + + // update cached variables after putContent() + updateContentData(true); + } + + /** + * Set the content stream from another content object. + * + * @param content ScriptContent to set + */ + public void write(Content content) + { + ContentService contentService = services.getContentService(); + ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); + writer.setMimetype(content.getMimetype()); + writer.setEncoding(content.getEncoding()); + writer.putContent(content.getInputStream()); + + // update cached variables after putContent() + updateContentData(true); + } + + /** + * Set the content stream from another content object. + * + * @param content ScriptContent to set + * @param applyMimetype If true, apply the mimetype from the Content object, else leave the original mimetype + * @param guessEncoding If true, guess the encoding from the underlying input stream, else use encoding set in + * the Content object as supplied. + */ + public void write(Content content, boolean applyMimetype, boolean guessEncoding) + { + ContentService contentService = services.getContentService(); + ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); + InputStream is = null; + if (applyMimetype) + { + writer.setMimetype(content.getMimetype()); + } + if (guessEncoding) + { + is = new BufferedInputStream(content.getInputStream()); + is.mark(1024); + writer.setEncoding(guessEncoding(is, false)); + try + { + is.reset(); + } + catch (IOException e) + { + } + } + else + { + writer.setEncoding(content.getEncoding()); + is = content.getInputStream(); + } + writer.putContent(is); + + // update cached variables after putContent() + updateContentData(true); + } + + /** + * Set the content stream from another input stream. + * + * @param inputStream InputStream + */ + public void write(InputStream inputStream) + { + ContentService contentService = services.getContentService(); + ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); + writer.putContent(inputStream); + + // update cached variables after putContent() + updateContentData(true); + } + + /** + * Delete the content stream + */ + public void delete() + { + ContentService contentService = services.getContentService(); + ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); + OutputStream output = writer.getContentOutputStream(); + try + { + output.close(); + } + catch (IOException e) + { + // NOTE: fall-through + } + writer.setMimetype(null); + writer.setEncoding(null); + + // update cached variables after putContent() + updateContentData(true); + } + + /** + * @return download URL to the content + */ + public String getUrl() + { + return MessageFormat.format(CONTENT_PROP_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(), + URLEncoder.encode(getName()), + URLEncoder.encode(property.toString()) }); + } + + /** + * @return download URL to the content for a document item only + */ + public String getDownloadUrl() + { + if (getIsDocument() == true) + { + return MessageFormat.format(CONTENT_DOWNLOAD_PROP_URL, new Object[] { + nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), + nodeRef.getId(), + URLEncoder.encode(getName()), + URLEncoder.encode(property.toString()) }); + } + else + { + return ""; + } + } + + public long getSize() + { + return contentData.getSize(); + } + + public String getMimetype() + { + return contentData.getMimetype(); + } + + public String getEncoding() + { + return contentData.getEncoding(); + } + + public void setEncoding(String encoding) + { + this.contentData = ContentData.setEncoding(this.contentData, encoding); + services.getNodeService().setProperty(nodeRef, this.property, this.contentData); + updateContentData(false); + } + + public void setMimetype(String mimetype) + { + this.contentData = ContentData.setMimetype(this.contentData, mimetype); + services.getNodeService().setProperty(nodeRef, this.property, this.contentData); + updateContentData(false); + } + + /** + * Guess the mimetype for the given filename - uses the extension to match on system mimetype map + */ + public void guessMimetype(String filename) + { + ContentService contentService = services.getContentService(); + ContentReader reader = contentService.getReader(nodeRef, property); + // MNT-12265 Browser sets a mimetype based on extension of file. But mimeType from browser can be + // different as mapped in Alfresco for current extension. Therefore we need to guess a mimetype based on + // map in Alfresco + String typeByExt = services.getMimetypeService().guessMimetype(filename); + if (reader != null && reader.getMimetype() != null && !typeByExt.equals(MimetypeMap.MIMETYPE_BINARY)) + { + setMimetype(typeByExt); + } + else + { + setMimetype(services.getMimetypeService().guessMimetype(filename, reader)); + } + } + + /** + * Guess the character encoding of a file. For non-text files UTF-8 default is applied, otherwise + * the appropriate encoding (such as UTF-16 or similar) will be appiled if detected. + */ + public void guessEncoding() + { + setEncoding(guessEncoding(getInputStream(), true)); + } + + private String guessEncoding(InputStream in, boolean close) + { + String encoding = "UTF-8"; + try + { + if (in != null) + { + Charset charset = services.getMimetypeService().getContentCharsetFinder().getCharset(in, getMimetype()); + encoding = charset.name(); + } + } + finally + { + try + { + if (close && in != null) + { + in.close(); + } + } + catch (IOException ioErr) + { + } + } + return encoding; + } + + /** + * Update cached contentData and the isDirty flag + */ + private void updateContentData(boolean touchContent) + { + this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property); + this.isDirty = touchContent ? true : this.isDirty; + } + + private ContentData contentData; + private QName property; + private boolean isDirty; + } + + /** + * Interface contract for simple anonymous classes that implement document transformations + */ + private interface Transformer + { + /** + * Transform the reader to the specified writer + * + * @param contentService ContentService + * @param noderef NodeRef of the destination for the transform + * @param reader Source reader + * @param writer Destination writer + * + * @return Node representing the transformed entity + */ + ScriptNode transform(ContentService contentService, NodeRef noderef, ContentReader reader, ContentWriter writer); + } + + + /** + * NamespacePrefixResolverProvider getter implementation + */ + public NamespacePrefixResolver getNamespacePrefixResolver() + { + return this.services.getNamespaceService(); + } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptUtils.java b/source/java/org/alfresco/repo/jscript/ScriptUtils.java index 40f47961ca..3ba0128ce5 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptUtils.java +++ b/source/java/org/alfresco/repo/jscript/ScriptUtils.java @@ -1,375 +1,375 @@ -package org.alfresco.repo.jscript; - -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import org.alfresco.repo.nodelocator.CompanyHomeNodeLocator; -import org.alfresco.repo.nodelocator.NodeLocatorService; -import org.alfresco.repo.nodelocator.SharedHomeNodeLocator; -import org.alfresco.repo.nodelocator.SitesHomeNodeLocator; -import org.alfresco.repo.nodelocator.UserHomeNodeLocator; -import org.alfresco.repo.nodelocator.XPathNodeLocator; -import org.alfresco.repo.security.permissions.noop.PermissionServiceNOOPImpl; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.module.ModuleDetails; -import org.alfresco.service.cmr.module.ModuleService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.ScriptPagingDetails; -import org.springframework.extensions.surf.util.I18NUtil; -import org.springframework.extensions.surf.util.ISO8601DateFormat; - -/** - * Place for general and miscellaneous utility functions not already found in generic JavaScript. - * - * @author Kevin Roast - */ -public class ScriptUtils extends BaseScopableProcessorExtension -{ - private final static String NAMESPACE_BEGIN = "" + QName.NAMESPACE_BEGIN; - - /** Services */ - protected ServiceRegistry services; - - private NodeService unprotNodeService; - - - /** - * Sets the service registry - * - * @param services the service registry - */ - public void setServiceRegistry(ServiceRegistry services) - { - this.services = services; - } - - /** - * @param nodeService the NodeService to set - */ - public void setNodeService(NodeService nodeService) - { - this.unprotNodeService = nodeService; - } - - /** - * Function to return the cm:name display path for a node with minimum performance overhead. - * - * @param node ScriptNode - * @return cm:name based human readable display path for the give node. - */ - public String displayPath(ScriptNode node) - { - return this.unprotNodeService.getPath(node.nodeRef).toDisplayPath( - this.unprotNodeService, new PermissionServiceNOOPImpl()); - } - - /** - * Function to pad a string with zero '0' characters to the required length - * - * @param s String to pad with leading zero '0' characters - * @param len Length to pad to - * - * @return padded string or the original if already at >=len characters - */ - public String pad(String s, int len) - { - String result = s; - for (int i=0; i<(len - s.length()); i++) - { - result = "0" + result; - } - return result; - } - - /** - * Gets a JS node object from a string noderef - * - * @param nodeRefString string reference to a node - * @return a JS node object - */ - public ScriptNode getNodeFromString(String nodeRefString) - { - NodeRef nodeRef = new NodeRef(nodeRefString); - return (ScriptNode)new ValueConverter().convertValueForScript(this.services, getScope(), null, nodeRef); - } - - /** - * Use the Node Locator Service to find the a node reference from a number of possible locator types. - * This method is responsible for determining the locator type and then calling the Service as the - * Service does not know how to guess which locator to use. - *

- * This service supports 'virtual' nodes including the following: - *

- * alfresco://company/home The Company Home root node
- * alfresco://user/home The User Home node under Company Home
- * alfresco://company/shared The Shared node under Company Home
- * alfresco://sites/home The Sites home node under Company Home
- * workspace://.../... Any standard NodeRef
- * /app:company_home/cm:... XPath QName style node reference
- * - * @param reference The node reference - See above for list of possible node references supported. - * - * @return ScriptNode representing the node or null if not found - */ - public ScriptNode resolveNodeReference(final String reference) - { - if (reference == null) - { - throw new IllegalArgumentException("Node 'reference' argument is mandatory."); - } - - final NodeLocatorService locatorService = this.services.getNodeLocatorService(); - - NodeRef nodeRef = null; - - switch (reference) - { - case "alfresco://company/home": - nodeRef = locatorService.getNode(CompanyHomeNodeLocator.NAME, null, null); - break; - case "alfresco://user/home": - nodeRef = locatorService.getNode(UserHomeNodeLocator.NAME, null, null); - break; - case "alfresco://company/shared": - nodeRef = locatorService.getNode(SharedHomeNodeLocator.NAME, null, null); - break; - case "alfresco://sites/home": - nodeRef = locatorService.getNode(SitesHomeNodeLocator.NAME, null, null); - break; - default: - if (reference.indexOf("://") > 0) - { - NodeRef ref = new NodeRef(reference); - if (this.services.getNodeService().exists(ref) && - this.services.getPermissionService().hasPermission(ref, PermissionService.READ) == AccessStatus.ALLOWED) - { - nodeRef = ref; - } - } - else if (reference.startsWith("/")) - { - final Map params = new HashMap<>(1, 1.0f); - params.put(XPathNodeLocator.QUERY_KEY, reference); - nodeRef = locatorService.getNode(XPathNodeLocator.NAME, null, params); - } - break; - } - - return nodeRef != null ? (ScriptNode)new ValueConverter().convertValueForScript(this.services, getScope(), null, nodeRef) : null; - } - - /** - * Gets a boolean value from a string - * - * @see Boolean#parseBoolean(String) - * - * @param booleanString boolean string - * @return boolean the boolean value - */ - public boolean toBoolean(String booleanString) - { - return Boolean.parseBoolean(booleanString); - } - - /** - * Function to check if a module is installed - * - * @param moduleName module name (e.g. "org.alfresco.module.foo") - * @return boolean true if the module is currently installed - */ - public boolean moduleInstalled(String moduleName) - { - ModuleService moduleService = (ModuleService)this.services.getService(QName.createQName(NamespaceService.ALFRESCO_URI, "ModuleService")); - if (moduleService != null) - { - ModuleDetails moduleDetail = (ModuleDetails)moduleService.getModule(moduleName); - return (moduleDetail != null); - } - return false; - } - - /** - * Format timeInMillis to ISO 8601 formatted string - * - * @param timeInMillis long - * @return String - */ - public String toISO8601(long timeInMillis) - { - return ISO8601DateFormat.format(new Date(timeInMillis)); - } - - /** - * Format date to ISO 8601 formatted string - * - * @param date Date - * @return String - */ - public String toISO8601(Date date) - { - return ISO8601DateFormat.format(date); - } - - /** - * Parse date from ISO formatted string - * - * @param isoDateString String - * @return Date - */ - public Date fromISO8601(String isoDateString) - { - return ISO8601DateFormat.parse(isoDateString); - } - - /** - * Given a long-form QName string, this method uses the namespace service to create a - * short-form QName string. - * - * @param s Fully qualified QName string - * @return the short form of the QName string, e.g. "cm:content" - */ - public String shortQName(String s) - { - return createQName(s).toPrefixString(services.getNamespaceService()); - } - - /** - * Given a short-form QName string, this method returns the fully qualified QName string. - * - * @param s Short form QName string, e.g. "cm:content" - * @return Fully qualified QName string - */ - public String longQName(String s) - { - return createQName(s).toString(); - } - - /** - * Builds a paging object, from the supplied - * Max Items and Skip Count - */ - public ScriptPagingDetails createPaging(int maxItems, int skipCount) - { - return new ScriptPagingDetails(maxItems, skipCount); - } - - /** - * Builds a paging object, from the supplied - * Max Items, Skip Count and Query Execution ID - */ - public ScriptPagingDetails createPaging(int maxItems, int skipCount, String queryExecutionId) - { - return new ScriptPagingDetails(maxItems, skipCount, queryExecutionId); - } - - /** - * Builds a paging object, from the supplied Args object. - * Requires that the parameters have their standard names, - * i.e. "maxItems" and "skipCount" - * - * @param args Mandatory hash of paging arguments

- * Possible arguments include:

- * maxItems - max count of items to return, default -1 (all)
- * skipCount - number of items to skip, default -1 (none)
- * queryId
- * queryExecutionId - */ - public ScriptPagingDetails createPaging(Map args) - { - int maxItems = -1; - int skipCount = -1; - String queryId = null; - - if (args.containsKey("maxItems")) - { - try - { - maxItems = Integer.parseInt(args.get("maxItems")); - } - catch(NumberFormatException e) - {} - } - if (args.containsKey("skipCount")) - { - try - { - skipCount = Integer.parseInt(args.get("skipCount")); - } - catch(NumberFormatException e) - {} - } - - if (args.containsKey("queryId")) - { - queryId = args.get("queryId"); - } - else if(args.containsKey("queryExecutionId")) - { - queryId = args.get("queryExecutionId"); - } - - return new ScriptPagingDetails(maxItems, skipCount, queryId); - } - - /** - * Helper to create a QName from either a fully qualified or short-name QName string - * - * @param s Fully qualified or short-name QName string - * - * @return QName - */ - private QName createQName(String s) - { - QName qname; - if (s.indexOf(NAMESPACE_BEGIN) != -1) - { - qname = QName.createQName(s); - } - else - { - qname = QName.createQName(s, this.services.getNamespaceService()); - } - return qname; - } - - /** - * Disable rule execution for this thread - */ - public void disableRules() - { - services.getRuleService().disableRules(); - } - - /** - * Enable rule execution for this thread - */ - public void enableRules() - { - services.getRuleService().enableRules(); - } - - /** - * Sets current Locale from string - */ - public void setLocale(String localeStr) - { - Locale newLocale = I18NUtil.parseLocale(localeStr); - I18NUtil.setLocale(newLocale); - } - - /** - * Returns current thread's locale - */ - public String getLocale() - { - return I18NUtil.getLocale().toString(); - } -} +package org.alfresco.repo.jscript; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.alfresco.repo.nodelocator.CompanyHomeNodeLocator; +import org.alfresco.repo.nodelocator.NodeLocatorService; +import org.alfresco.repo.nodelocator.SharedHomeNodeLocator; +import org.alfresco.repo.nodelocator.SitesHomeNodeLocator; +import org.alfresco.repo.nodelocator.UserHomeNodeLocator; +import org.alfresco.repo.nodelocator.XPathNodeLocator; +import org.alfresco.repo.security.permissions.noop.PermissionServiceNOOPImpl; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.module.ModuleDetails; +import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ScriptPagingDetails; +import org.springframework.extensions.surf.util.I18NUtil; +import org.springframework.extensions.surf.util.ISO8601DateFormat; + +/** + * Place for general and miscellaneous utility functions not already found in generic JavaScript. + * + * @author Kevin Roast + */ +public class ScriptUtils extends BaseScopableProcessorExtension +{ + private final static String NAMESPACE_BEGIN = "" + QName.NAMESPACE_BEGIN; + + /** Services */ + protected ServiceRegistry services; + + private NodeService unprotNodeService; + + + /** + * Sets the service registry + * + * @param services the service registry + */ + public void setServiceRegistry(ServiceRegistry services) + { + this.services = services; + } + + /** + * @param nodeService the NodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.unprotNodeService = nodeService; + } + + /** + * Function to return the cm:name display path for a node with minimum performance overhead. + * + * @param node ScriptNode + * @return cm:name based human readable display path for the give node. + */ + public String displayPath(ScriptNode node) + { + return this.unprotNodeService.getPath(node.nodeRef).toDisplayPath( + this.unprotNodeService, new PermissionServiceNOOPImpl()); + } + + /** + * Function to pad a string with zero '0' characters to the required length + * + * @param s String to pad with leading zero '0' characters + * @param len Length to pad to + * + * @return padded string or the original if already at >=len characters + */ + public String pad(String s, int len) + { + String result = s; + for (int i=0; i<(len - s.length()); i++) + { + result = "0" + result; + } + return result; + } + + /** + * Gets a JS node object from a string noderef + * + * @param nodeRefString string reference to a node + * @return a JS node object + */ + public ScriptNode getNodeFromString(String nodeRefString) + { + NodeRef nodeRef = new NodeRef(nodeRefString); + return (ScriptNode)new ValueConverter().convertValueForScript(this.services, getScope(), null, nodeRef); + } + + /** + * Use the Node Locator Service to find the a node reference from a number of possible locator types. + * This method is responsible for determining the locator type and then calling the Service as the + * Service does not know how to guess which locator to use. + *

+ * This service supports 'virtual' nodes including the following: + *

+ * alfresco://company/home The Company Home root node
+ * alfresco://user/home The User Home node under Company Home
+ * alfresco://company/shared The Shared node under Company Home
+ * alfresco://sites/home The Sites home node under Company Home
+ * workspace://.../... Any standard NodeRef
+ * /app:company_home/cm:... XPath QName style node reference
+ * + * @param reference The node reference - See above for list of possible node references supported. + * + * @return ScriptNode representing the node or null if not found + */ + public ScriptNode resolveNodeReference(final String reference) + { + if (reference == null) + { + throw new IllegalArgumentException("Node 'reference' argument is mandatory."); + } + + final NodeLocatorService locatorService = this.services.getNodeLocatorService(); + + NodeRef nodeRef = null; + + switch (reference) + { + case "alfresco://company/home": + nodeRef = locatorService.getNode(CompanyHomeNodeLocator.NAME, null, null); + break; + case "alfresco://user/home": + nodeRef = locatorService.getNode(UserHomeNodeLocator.NAME, null, null); + break; + case "alfresco://company/shared": + nodeRef = locatorService.getNode(SharedHomeNodeLocator.NAME, null, null); + break; + case "alfresco://sites/home": + nodeRef = locatorService.getNode(SitesHomeNodeLocator.NAME, null, null); + break; + default: + if (reference.indexOf("://") > 0) + { + NodeRef ref = new NodeRef(reference); + if (this.services.getNodeService().exists(ref) && + this.services.getPermissionService().hasPermission(ref, PermissionService.READ) == AccessStatus.ALLOWED) + { + nodeRef = ref; + } + } + else if (reference.startsWith("/")) + { + final Map params = new HashMap<>(1, 1.0f); + params.put(XPathNodeLocator.QUERY_KEY, reference); + nodeRef = locatorService.getNode(XPathNodeLocator.NAME, null, params); + } + break; + } + + return nodeRef != null ? (ScriptNode)new ValueConverter().convertValueForScript(this.services, getScope(), null, nodeRef) : null; + } + + /** + * Gets a boolean value from a string + * + * @see Boolean#parseBoolean(String) + * + * @param booleanString boolean string + * @return boolean the boolean value + */ + public boolean toBoolean(String booleanString) + { + return Boolean.parseBoolean(booleanString); + } + + /** + * Function to check if a module is installed + * + * @param moduleName module name (e.g. "org.alfresco.module.foo") + * @return boolean true if the module is currently installed + */ + public boolean moduleInstalled(String moduleName) + { + ModuleService moduleService = (ModuleService)this.services.getService(QName.createQName(NamespaceService.ALFRESCO_URI, "ModuleService")); + if (moduleService != null) + { + ModuleDetails moduleDetail = (ModuleDetails)moduleService.getModule(moduleName); + return (moduleDetail != null); + } + return false; + } + + /** + * Format timeInMillis to ISO 8601 formatted string + * + * @param timeInMillis long + * @return String + */ + public String toISO8601(long timeInMillis) + { + return ISO8601DateFormat.format(new Date(timeInMillis)); + } + + /** + * Format date to ISO 8601 formatted string + * + * @param date Date + * @return String + */ + public String toISO8601(Date date) + { + return ISO8601DateFormat.format(date); + } + + /** + * Parse date from ISO formatted string + * + * @param isoDateString String + * @return Date + */ + public Date fromISO8601(String isoDateString) + { + return ISO8601DateFormat.parse(isoDateString); + } + + /** + * Given a long-form QName string, this method uses the namespace service to create a + * short-form QName string. + * + * @param s Fully qualified QName string + * @return the short form of the QName string, e.g. "cm:content" + */ + public String shortQName(String s) + { + return createQName(s).toPrefixString(services.getNamespaceService()); + } + + /** + * Given a short-form QName string, this method returns the fully qualified QName string. + * + * @param s Short form QName string, e.g. "cm:content" + * @return Fully qualified QName string + */ + public String longQName(String s) + { + return createQName(s).toString(); + } + + /** + * Builds a paging object, from the supplied + * Max Items and Skip Count + */ + public ScriptPagingDetails createPaging(int maxItems, int skipCount) + { + return new ScriptPagingDetails(maxItems, skipCount); + } + + /** + * Builds a paging object, from the supplied + * Max Items, Skip Count and Query Execution ID + */ + public ScriptPagingDetails createPaging(int maxItems, int skipCount, String queryExecutionId) + { + return new ScriptPagingDetails(maxItems, skipCount, queryExecutionId); + } + + /** + * Builds a paging object, from the supplied Args object. + * Requires that the parameters have their standard names, + * i.e. "maxItems" and "skipCount" + * + * @param args Mandatory hash of paging arguments

+ * Possible arguments include:

+ * maxItems - max count of items to return, default -1 (all)
+ * skipCount - number of items to skip, default -1 (none)
+ * queryId
+ * queryExecutionId + */ + public ScriptPagingDetails createPaging(Map args) + { + int maxItems = -1; + int skipCount = -1; + String queryId = null; + + if (args.containsKey("maxItems")) + { + try + { + maxItems = Integer.parseInt(args.get("maxItems")); + } + catch(NumberFormatException e) + {} + } + if (args.containsKey("skipCount")) + { + try + { + skipCount = Integer.parseInt(args.get("skipCount")); + } + catch(NumberFormatException e) + {} + } + + if (args.containsKey("queryId")) + { + queryId = args.get("queryId"); + } + else if(args.containsKey("queryExecutionId")) + { + queryId = args.get("queryExecutionId"); + } + + return new ScriptPagingDetails(maxItems, skipCount, queryId); + } + + /** + * Helper to create a QName from either a fully qualified or short-name QName string + * + * @param s Fully qualified or short-name QName string + * + * @return QName + */ + private QName createQName(String s) + { + QName qname; + if (s.indexOf(NAMESPACE_BEGIN) != -1) + { + qname = QName.createQName(s); + } + else + { + qname = QName.createQName(s, this.services.getNamespaceService()); + } + return qname; + } + + /** + * Disable rule execution for this thread + */ + public void disableRules() + { + services.getRuleService().disableRules(); + } + + /** + * Enable rule execution for this thread + */ + public void enableRules() + { + services.getRuleService().enableRules(); + } + + /** + * Sets current Locale from string + */ + public void setLocale(String localeStr) + { + Locale newLocale = I18NUtil.parseLocale(localeStr); + I18NUtil.setLocale(newLocale); + } + + /** + * Returns current thread's locale + */ + public String getLocale() + { + return I18NUtil.getLocale().toString(); + } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptVersion.java b/source/java/org/alfresco/repo/jscript/ScriptVersion.java index 93ab1b0994..c0426fe651 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptVersion.java +++ b/source/java/org/alfresco/repo/jscript/ScriptVersion.java @@ -1,136 +1,136 @@ -package org.alfresco.repo.jscript; - -import java.io.Serializable; -import java.util.Date; -import java.util.Map; - -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.version.Version; -import org.alfresco.util.ParameterCheck; -import org.mozilla.javascript.Scriptable; - -/** - * Scriptable Version - * - * @author davidc - */ -public final class ScriptVersion implements Serializable -{ - private static final long serialVersionUID = 3896177303419746778L; - - /** Root scope for this object */ - private Scriptable scope; - private ServiceRegistry services; - private Version version; - - /** - * Construct - */ - public ScriptVersion(Version version, ServiceRegistry services, Scriptable scope) - { - this.version = version; - this.services = services; - this.scope = scope; - } - - /** - * Gets the date the version was created - * - * @return the date the version was created - */ - public Date getCreatedDate() - { - return version.getFrozenModifiedDate(); - } - - /** - * Gets the creator of the version - * - * @return the creator of the version - */ - public String getCreator() - { - return version.getFrozenModifier(); - } - - /** - * Gets the version label - * - * @return the version label - */ - public String getLabel() - { - return version.getVersionLabel(); - } - - /** - * Gets the version type - * - * @return "MAJOR", "MINOR" - */ - public String getType() - { - if (version.getVersionType() != null) - { - return version.getVersionType().name(); - } - else - { - return ""; - } - } - - /** - * Gets the version description (or checkin comment) - * - * @return the version description - */ - public String getDescription() - { - String desc = version.getDescription(); - return (desc == null) ? "" : desc; - } - - /** - * Gets the node ref represented by this version - * - * @return node ref - */ - public NodeRef getNodeRef() - { - return version.getVersionedNodeRef(); - } - - /** - * Gets the node represented by this version - * - * @return node - */ - public ScriptNode getNode() - { - return new ScriptNode(version.getFrozenStateNodeRef(), services, scope); - } - - /** - * Get the map containing the version property values - * - * @return the map containing the version properties - */ - public Map getVersionProperties() - { - return version.getVersionProperties(); - } - - /** - * Gets the value of a named version property. - * - * @param name the name of the property - * @return the value of the property - */ - public Serializable getVersionProperty(String name) - { - ParameterCheck.mandatoryString("name", name); - return version.getVersionProperty(name); - } -} +package org.alfresco.repo.jscript; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.util.ParameterCheck; +import org.mozilla.javascript.Scriptable; + +/** + * Scriptable Version + * + * @author davidc + */ +public final class ScriptVersion implements Serializable +{ + private static final long serialVersionUID = 3896177303419746778L; + + /** Root scope for this object */ + private Scriptable scope; + private ServiceRegistry services; + private Version version; + + /** + * Construct + */ + public ScriptVersion(Version version, ServiceRegistry services, Scriptable scope) + { + this.version = version; + this.services = services; + this.scope = scope; + } + + /** + * Gets the date the version was created + * + * @return the date the version was created + */ + public Date getCreatedDate() + { + return version.getFrozenModifiedDate(); + } + + /** + * Gets the creator of the version + * + * @return the creator of the version + */ + public String getCreator() + { + return version.getFrozenModifier(); + } + + /** + * Gets the version label + * + * @return the version label + */ + public String getLabel() + { + return version.getVersionLabel(); + } + + /** + * Gets the version type + * + * @return "MAJOR", "MINOR" + */ + public String getType() + { + if (version.getVersionType() != null) + { + return version.getVersionType().name(); + } + else + { + return ""; + } + } + + /** + * Gets the version description (or checkin comment) + * + * @return the version description + */ + public String getDescription() + { + String desc = version.getDescription(); + return (desc == null) ? "" : desc; + } + + /** + * Gets the node ref represented by this version + * + * @return node ref + */ + public NodeRef getNodeRef() + { + return version.getVersionedNodeRef(); + } + + /** + * Gets the node represented by this version + * + * @return node + */ + public ScriptNode getNode() + { + return new ScriptNode(version.getFrozenStateNodeRef(), services, scope); + } + + /** + * Get the map containing the version property values + * + * @return the map containing the version properties + */ + public Map getVersionProperties() + { + return version.getVersionProperties(); + } + + /** + * Gets the value of a named version property. + * + * @param name the name of the property + * @return the value of the property + */ + public Serializable getVersionProperty(String name) + { + ParameterCheck.mandatoryString("name", name); + return version.getVersionProperty(name); + } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java b/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java index fb351cbad8..2dbf33646e 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java +++ b/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java @@ -1,198 +1,198 @@ -package org.alfresco.repo.jscript; - -import java.util.Iterator; -import java.util.LinkedHashMap; - -import org.mozilla.javascript.Callable; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.Scriptable; - -/** - * @author Kevin Roast - */ -public class ScriptableHashMap extends LinkedHashMap implements Scriptable -{ - private static final long serialVersionUID = 3664761893203964569L; - - private Scriptable parentScope; - private Scriptable prototype; - - /** - * @see org.mozilla.javascript.Scriptable#getClassName() - */ - public String getClassName() - { - return "ScriptableHashMap"; - } - - /** - * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) - */ - public Object get(String name, Scriptable start) - { - // get the property from the underlying QName map - if ("length".equals(name)) - { - return this.size(); - } - else if ("hasOwnProperty".equals(name)) - { - return new Callable() - { - @Override - public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) - { - return (args.length > 0 ? hasOwnProperty(args[0]) : null); - } - }; - } - else - { - return get(name); - } - } - - /** - * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) - */ - public Object get(int index, Scriptable start) - { - Object value = null; - int i=0; - Iterator itrValues = this.values().iterator(); - while (i++ <= index && itrValues.hasNext()) - { - value = itrValues.next(); - } - return value; - } - - /** - * ECMAScript 5 hasOwnProperty method support. - * - * @param key Object key to test for - * @return true if found, false otherwise - */ - public boolean hasOwnProperty(Object key) - { - return containsKey(key); - } - - /** - * @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable) - */ - public boolean has(String name, Scriptable start) - { - // locate the property in the underlying map - return containsKey(name); - } - - /** - * @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable) - */ - public boolean has(int index, Scriptable start) - { - return (index >= 0 && this.values().size() > index); - } - - /** - * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) - */ - @SuppressWarnings("unchecked") - public void put(String name, Scriptable start, Object value) - { - // add the property to the underlying QName map - put((K)name, (V)value); - } - - /** - * @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object) - */ - public void put(int index, Scriptable start, Object value) - { - // TODO: implement? - } - - /** - * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) - */ - public void delete(String name) - { - // remove the property from the underlying QName map - remove(name); - } - - /** - * @see org.mozilla.javascript.Scriptable#delete(int) - */ - public void delete(int index) - { - int i=0; - Iterator itrKeys = this.keySet().iterator(); - while (i <= index && itrKeys.hasNext()) - { - Object key = itrKeys.next(); - if (i == index) - { - remove(key); - break; - } - } - } - - /** - * @see org.mozilla.javascript.Scriptable#getPrototype() - */ - public Scriptable getPrototype() - { - return this.prototype; - } - - /** - * @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable) - */ - public void setPrototype(Scriptable prototype) - { - this.prototype = prototype; - } - - /** - * @see org.mozilla.javascript.Scriptable#getParentScope() - */ - public Scriptable getParentScope() - { - return this.parentScope; - } - - /** - * @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable) - */ - public void setParentScope(Scriptable parent) - { - this.parentScope = parent; - } - - /** - * @see org.mozilla.javascript.Scriptable#getIds() - */ - public Object[] getIds() - { - return keySet().toArray(); - } - - /** - * @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class) - */ - public Object getDefaultValue(@SuppressWarnings("rawtypes") Class hint) - { - return this.toString(); - } - - /** - * @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable) - */ - public boolean hasInstance(Scriptable instance) - { - return instance instanceof ScriptableHashMap; - } -} +package org.alfresco.repo.jscript; + +import java.util.Iterator; +import java.util.LinkedHashMap; + +import org.mozilla.javascript.Callable; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** + * @author Kevin Roast + */ +public class ScriptableHashMap extends LinkedHashMap implements Scriptable +{ + private static final long serialVersionUID = 3664761893203964569L; + + private Scriptable parentScope; + private Scriptable prototype; + + /** + * @see org.mozilla.javascript.Scriptable#getClassName() + */ + public String getClassName() + { + return "ScriptableHashMap"; + } + + /** + * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public Object get(String name, Scriptable start) + { + // get the property from the underlying QName map + if ("length".equals(name)) + { + return this.size(); + } + else if ("hasOwnProperty".equals(name)) + { + return new Callable() + { + @Override + public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + { + return (args.length > 0 ? hasOwnProperty(args[0]) : null); + } + }; + } + else + { + return get(name); + } + } + + /** + * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) + */ + public Object get(int index, Scriptable start) + { + Object value = null; + int i=0; + Iterator itrValues = this.values().iterator(); + while (i++ <= index && itrValues.hasNext()) + { + value = itrValues.next(); + } + return value; + } + + /** + * ECMAScript 5 hasOwnProperty method support. + * + * @param key Object key to test for + * @return true if found, false otherwise + */ + public boolean hasOwnProperty(Object key) + { + return containsKey(key); + } + + /** + * @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public boolean has(String name, Scriptable start) + { + // locate the property in the underlying map + return containsKey(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable) + */ + public boolean has(int index, Scriptable start) + { + return (index >= 0 && this.values().size() > index); + } + + /** + * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + @SuppressWarnings("unchecked") + public void put(String name, Scriptable start, Object value) + { + // add the property to the underlying QName map + put((K)name, (V)value); + } + + /** + * @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + public void put(int index, Scriptable start, Object value) + { + // TODO: implement? + } + + /** + * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) + */ + public void delete(String name) + { + // remove the property from the underlying QName map + remove(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#delete(int) + */ + public void delete(int index) + { + int i=0; + Iterator itrKeys = this.keySet().iterator(); + while (i <= index && itrKeys.hasNext()) + { + Object key = itrKeys.next(); + if (i == index) + { + remove(key); + break; + } + } + } + + /** + * @see org.mozilla.javascript.Scriptable#getPrototype() + */ + public Scriptable getPrototype() + { + return this.prototype; + } + + /** + * @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable) + */ + public void setPrototype(Scriptable prototype) + { + this.prototype = prototype; + } + + /** + * @see org.mozilla.javascript.Scriptable#getParentScope() + */ + public Scriptable getParentScope() + { + return this.parentScope; + } + + /** + * @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable) + */ + public void setParentScope(Scriptable parent) + { + this.parentScope = parent; + } + + /** + * @see org.mozilla.javascript.Scriptable#getIds() + */ + public Object[] getIds() + { + return keySet().toArray(); + } + + /** + * @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class) + */ + public Object getDefaultValue(@SuppressWarnings("rawtypes") Class hint) + { + return this.toString(); + } + + /** + * @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable) + */ + public boolean hasInstance(Scriptable instance) + { + return instance instanceof ScriptableHashMap; + } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java b/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java index e7d9e60cf0..d3dbc5b828 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java +++ b/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java @@ -1,178 +1,178 @@ -package org.alfresco.repo.jscript; - -import org.alfresco.service.namespace.NamespacePrefixResolverProvider; -import org.alfresco.service.namespace.QNameMap; -import org.mozilla.javascript.Callable; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.Scriptable; - -/** - * @author Kevin Roast - */ -public class ScriptableQNameMap extends QNameMap implements Scriptable -{ - /** - * @param resolver NamespacePrefixResolverProvider - */ - public ScriptableQNameMap(NamespacePrefixResolverProvider resolver) - { - super(resolver); - } - - /** - * @see org.mozilla.javascript.Scriptable#getClassName() - */ - public String getClassName() - { - return "ScriptableQNameMap"; - } - - /** - * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) - */ - public Object get(String name, Scriptable start) - { - // get the property from the underlying QName map - if ("length".equals(name)) - { - return this.size(); - } - else if ("hasOwnProperty".equals(name)) - { - return new Callable() - { - @Override - public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) - { - return (args.length > 0 ? hasOwnProperty(args[0]) : null); - } - }; - } - else - { - return get(name); - } - } - - /** - * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) - */ - public Object get(int index, Scriptable start) - { - return null; - } - - /** - * ECMAScript 5 hasOwnProperty method support. - * - * @param key Object key to test for - * @return true if found, false otherwise - */ - public boolean hasOwnProperty(Object key) - { - return containsKey(key); - } - - /** - * @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable) - */ - public boolean has(String name, Scriptable start) - { - // locate the property in the underlying QName map - return containsKey(name); - } - - /** - * @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable) - */ - public boolean has(int index, Scriptable start) - { - return false; - } - - /** - * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) - */ - public void put(String name, Scriptable start, Object value) - { - // add the property to the underlying QName map - put(name, value); - } - - /** - * @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object) - */ - public void put(int index, Scriptable start, Object value) - { - } - - /** - * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) - */ - public void delete(String name) - { - // remove the property from the underlying QName map - remove(name); - } - - /** - * @see org.mozilla.javascript.Scriptable#delete(int) - */ - public void delete(int index) - { - } - - /** - * @see org.mozilla.javascript.Scriptable#getPrototype() - */ - public Scriptable getPrototype() - { - return null; - } - - /** - * @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable) - */ - public void setPrototype(Scriptable prototype) - { - } - - /** - * @see org.mozilla.javascript.Scriptable#getParentScope() - */ - public Scriptable getParentScope() - { - return null; - } - - /** - * @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable) - */ - public void setParentScope(Scriptable parent) - { - } - - /** - * @see org.mozilla.javascript.Scriptable#getIds() - */ - public Object[] getIds() - { - return keySet().toArray(); - } - - /** - * @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class) - */ - public Object getDefaultValue(@SuppressWarnings("rawtypes") Class hint) - { - return toString(); - } - - /** - * @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable) - */ - public boolean hasInstance(Scriptable instance) - { - return instance instanceof ScriptableQNameMap; - } -} +package org.alfresco.repo.jscript; + +import org.alfresco.service.namespace.NamespacePrefixResolverProvider; +import org.alfresco.service.namespace.QNameMap; +import org.mozilla.javascript.Callable; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** + * @author Kevin Roast + */ +public class ScriptableQNameMap extends QNameMap implements Scriptable +{ + /** + * @param resolver NamespacePrefixResolverProvider + */ + public ScriptableQNameMap(NamespacePrefixResolverProvider resolver) + { + super(resolver); + } + + /** + * @see org.mozilla.javascript.Scriptable#getClassName() + */ + public String getClassName() + { + return "ScriptableQNameMap"; + } + + /** + * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public Object get(String name, Scriptable start) + { + // get the property from the underlying QName map + if ("length".equals(name)) + { + return this.size(); + } + else if ("hasOwnProperty".equals(name)) + { + return new Callable() + { + @Override + public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + { + return (args.length > 0 ? hasOwnProperty(args[0]) : null); + } + }; + } + else + { + return get(name); + } + } + + /** + * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) + */ + public Object get(int index, Scriptable start) + { + return null; + } + + /** + * ECMAScript 5 hasOwnProperty method support. + * + * @param key Object key to test for + * @return true if found, false otherwise + */ + public boolean hasOwnProperty(Object key) + { + return containsKey(key); + } + + /** + * @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public boolean has(String name, Scriptable start) + { + // locate the property in the underlying QName map + return containsKey(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable) + */ + public boolean has(int index, Scriptable start) + { + return false; + } + + /** + * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + public void put(String name, Scriptable start, Object value) + { + // add the property to the underlying QName map + put(name, value); + } + + /** + * @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + public void put(int index, Scriptable start, Object value) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) + */ + public void delete(String name) + { + // remove the property from the underlying QName map + remove(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#delete(int) + */ + public void delete(int index) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#getPrototype() + */ + public Scriptable getPrototype() + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable) + */ + public void setPrototype(Scriptable prototype) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#getParentScope() + */ + public Scriptable getParentScope() + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable) + */ + public void setParentScope(Scriptable parent) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#getIds() + */ + public Object[] getIds() + { + return keySet().toArray(); + } + + /** + * @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class) + */ + public Object getDefaultValue(@SuppressWarnings("rawtypes") Class hint) + { + return toString(); + } + + /** + * @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable) + */ + public boolean hasInstance(Scriptable instance) + { + return instance instanceof ScriptableQNameMap; + } +} diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java index 90652b2bf3..44715e13d2 100644 --- a/source/java/org/alfresco/repo/jscript/Search.java +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -1,1074 +1,1074 @@ -package org.alfresco.repo.jscript; - -import java.io.Serializable; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.management.subsystems.SwitchableApplicationContextFactory; -import org.alfresco.repo.model.Repository; -import org.alfresco.repo.search.impl.solr.facet.SolrFacetHelper; -import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabel; -import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabelDisplayHandler; -import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabelDisplayHandlerRegistry; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.LimitBy; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.ResultSetRow; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SearchParameters.FieldFacet; -import org.alfresco.service.cmr.search.SearchParameters.Operator; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.search.SpellCheckResult; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.util.ISO9075; -import org.alfresco.util.Pair; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.dom4j.Document; -import org.dom4j.Element; -import org.dom4j.io.SAXReader; -import org.jaxen.saxpath.base.XPathReader; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.Scriptable; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.extensions.surf.util.ParameterCheck; - -/** - * Search component for use by the ScriptService. - *

- * Provides access to Lucene search facilities including saved search objects. The results - * from a search are returned as an array (collection) of scriptable Node wrapper objects. - *

- * The object is added to the root of the model to provide syntax such as: - * var results = search.luceneSearch(statement); - * and - * var results = search.savedSearch(node); - * - * @author Kevin Roast - */ -public class Search extends BaseScopableProcessorExtension implements InitializingBean -{ - private static Log logger = LogFactory.getLog(Search.class); - - /** Service registry */ - protected ServiceRegistry services; - - /** Default store reference */ - protected StoreRef storeRef; - - /** Repository helper */ - protected Repository repository; - - private SwitchableApplicationContextFactory searchSubsystem; - - @Override - public void afterPropertiesSet() throws Exception - { - PropertyCheck.mandatory(this, "services", services); - } - - /** - * Set the default store reference - * - * @param storeRef the default store reference - */ - public void setStoreUrl(String storeRef) - { - // ensure this is not set again by a script instance! - if (this.storeRef != null) - { - throw new IllegalStateException("Default store URL can only be set once."); - } - this.storeRef = new StoreRef(storeRef); - } - - /** - * Set the service registry - * - * @param services the service registry - */ - public void setServiceRegistry(ServiceRegistry services) - { - this.services = services; - } - - /** - * Set the repository helper - * - * @param repository the repository helper - */ - public void setRepositoryHelper(Repository repository) - { - this.repository = repository; - } - - public void setSearchSubsystemSwitchableApplicationContextFactory(SwitchableApplicationContextFactory searchSubsystem) - { - this.searchSubsystem = searchSubsystem; - } - - // JavaScript API - - public String getSearchSubsystem() - { - return (searchSubsystem == null) ? "" : searchSubsystem.getCurrentSourceBeanName(); - } - - /** - * Find a single Node by the Node reference - * - * @param ref The NodeRef of the Node to find - * - * @return the Node if found or null if failed to find - */ - public ScriptNode findNode(NodeRef ref) - { - ParameterCheck.mandatory("ref", ref); - if (this.services.getNodeService().exists(ref) - && (this.services.getPermissionService().hasPermission(ref, - PermissionService.READ) == AccessStatus.ALLOWED)) - { - return new ScriptNode(ref, this.services, getScope()); - } - return null; - } - - /** - * Find a single Node by the Node reference - * - * @param ref The fully qualified NodeRef in String format - * - * @return the Node if found or null if failed to find - */ - public ScriptNode findNode(String ref) - { - ParameterCheck.mandatoryString("ref", ref); - return findNode(new NodeRef(ref)); - } - - /** - * Helper to convert a Web Script Request URL to a Node Ref - * - * 1) Node - {store_type}/{store_id}/{node_id} - * - * Resolve to node via its Node Reference. - * - * 2) Path - {store_type}/{store_id}/{path} - * - * Resolve to node via its display path. - * - * @param referenceType one of "node", "path" - * @param reference array of reference segments (as described above for each reference type) - * @return ScriptNode the script node - */ - public ScriptNode findNode(String referenceType, String[] reference) - { - ParameterCheck.mandatoryString("referenceType", referenceType); - ParameterCheck.mandatory("reference", reference); - ScriptNode result = null; - NodeRef nodeRef = this.repository.findNodeRef(referenceType, reference); - if (nodeRef != null) - { - result = new ScriptNode(nodeRef, this.services, getScope()); - } - return result; - } - - /** - * Execute a XPath search - * - * @param search XPath search string to execute - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable xpathSearch(String search) - { - return xpathSearch(null, search); - } - - /** - * Execute a XPath search - * - * @param store Store reference to search against i.e. workspace://SpacesStore - * @param search XPath search string to execute - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable xpathSearch(String store, String search) - { - if (search != null && search.length() != 0) - { - Object[] results = query(store, search, null, SearchService.LANGUAGE_XPATH); - return Context.getCurrentContext().newArray(getScope(), results); - } - else - { - return Context.getCurrentContext().newArray(getScope(), 0); - } - } - - /** - * Execute a SelectNodes XPath search - * - * @param search SelectNodes XPath search string to execute - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable selectNodes(String search) - { - return selectNodes(null, search); - } - - /** - * Execute a SelectNodes XPath search - * - * @param store Store reference to search against i.e. workspace://SpacesStore - * @param search SelectNodes XPath search string to execute - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable selectNodes(String store, String search) - { - if (search != null && search.length() != 0) - { - Object[] nodeArray = new Object[0]; - if (store == null) - { - store = "workspace://SpacesStore"; - } - try - { - NodeService nodeService = this.services.getNodeService(); - List nodes = this.services.getSearchService().selectNodes( - nodeService.getRootNode(new StoreRef(store)), search, null, this.services.getNamespaceService(), false); - if (nodes.size() != 0) - { - int index = 0; - nodeArray = new Object[nodes.size()]; - for (NodeRef node: nodes) - { - nodeArray[index++] = new ScriptNode(node, this.services, getScope()); - } - } - } - catch (Throwable err) - { - throw new AlfrescoRuntimeException("Failed to execute search: " + search, err); - } - - return Context.getCurrentContext().newArray(getScope(), nodeArray); - } - else - { - return Context.getCurrentContext().newArray(getScope(), 0); - } - } - - /** - * Validation Xpath query - * - * @param query xpath query - * @return true if xpath query valid - */ - public boolean isValidXpathQuery(String query) - { - try - { - XPathReader reader = new XPathReader(); - reader.parse(query); - } - catch (Exception e) - { - return false; - } - return true; - } - - /** - * Execute a Lucene search - * - * @param search Lucene search string to execute - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable luceneSearch(String search) - { - return luceneSearch(null, search); - } - - /** - * Execute a Lucene search - * - * @param store Store reference to search against i.e. workspace://SpacesStore - * @param search Lucene search string to execute - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable luceneSearch(String store, String search) - { - if (search != null && search.length() != 0) - { - Object[] results = query(store, search, null, SearchService.LANGUAGE_LUCENE); - return Context.getCurrentContext().newArray(getScope(), results); - } - else - { - return Context.getCurrentContext().newArray(getScope(), 0); - } - } - - /** - * Execute a Lucene search (sorted) - * - * @param search Lucene search string to execute - * @param sortColumn column to sort on - * @param asc true => ascending sort - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable luceneSearch(String search, String sortColumn, boolean asc) - { - return luceneSearch(null, search, sortColumn, asc, 0); - } - - public Scriptable luceneSearch(String search, String sortColumn, boolean asc, int max) - { - return luceneSearch(null, search, sortColumn, asc, max); - } - - public Scriptable luceneSearch(String store, String search, String sortColumn, boolean asc) - { - return luceneSearch(store, search, sortColumn, asc, 0); - } - - /** - * Execute a Lucene search (sorted) - * - * @param store Store reference to search against i.e. workspace://SpacesStore - * @param search Lucene search string to execute - * @param sortColumn column to sort on - * @param asc true => ascending sort - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable luceneSearch(String store, String search, String sortColumn, boolean asc, int max) - { - if (search == null || search.length() == 0) - { - return Context.getCurrentContext().newArray(getScope(), 0); - } - - SortColumn[] sort = null; - if (sortColumn != null && sortColumn.length() != 0) - { - sort = new SortColumn[1]; - sort[0] = new SortColumn(sortColumn, asc); - } - Object[] results = query(store, search, sort, SearchService.LANGUAGE_LUCENE, max, 0); - return Context.getCurrentContext().newArray(getScope(), results); - } - - /** - * Execute a saved Lucene search - * - * @param savedSearch Node that contains the saved search XML content - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable savedSearch(ScriptNode savedSearch) - { - String search = null; - - // read the Saved Search XML on the specified node - and get the Lucene search from it - try - { - if (savedSearch != null) - { - ContentReader content = this.services.getContentService().getReader( - savedSearch.getNodeRef(), ContentModel.PROP_CONTENT); - if (content != null && content.exists()) - { - // get the root element - SAXReader reader = new SAXReader(); - Document document = reader.read(new StringReader(content.getContentString())); - Element rootElement = document.getRootElement(); - - Element queryElement = rootElement.element("query"); - if (queryElement != null) - { - search = queryElement.getText(); - } - } - } - } - catch (Throwable err) - { - throw new AlfrescoRuntimeException("Failed to find or load saved Search: " + savedSearch.getNodeRef(), err); - } - - if (search != null) - { - Object[] results = query(null, search, null, SearchService.LANGUAGE_LUCENE); - return Context.getCurrentContext().newArray(getScope(), results); - } - else - { - return Context.getCurrentContext().newArray(getScope(), 0); - } - } - - /** - * Execute a saved Lucene search - * - * @param searchRef NodeRef string that points to the node containing saved search XML content - * - * @return JavaScript array of Node results from the search - can be empty but not null - */ - public Scriptable savedSearch(String searchRef) - { - if (searchRef != null) - { - return savedSearch(new ScriptNode(new NodeRef(searchRef), services, null)); - } - else - { - return Context.getCurrentContext().newArray(getScope(), 0); - } - } - - /** - * Searchs the store for all nodes with the given tag applied. - * - * @param store store ref string, default used if null provided - * @param tag tag name - * @return ScriptNode[] nodes with tag applied - */ - public ScriptNode[] tagSearch(String store, String tag) - { - StoreRef searchStoreRef = null; - if (store != null) - { - searchStoreRef = new StoreRef(store); - } - else - { - searchStoreRef = this.storeRef; - } - - List nodeRefs = this.services.getTaggingService().findTaggedNodes(searchStoreRef, tag); - ScriptNode[] nodes = new ScriptNode[nodeRefs.size()]; - int index = 0; - for (NodeRef node : nodeRefs) - { - nodes[index] = new ScriptNode(node, this.services, getScope()); - index ++; - } - return nodes; - } - - /** - * Execute a query based on the supplied search definition object. - * - * Search object is defined in JavaScript thus: - *

-     * search
-     * {
-     *    query: string,          mandatory, in appropriate format and encoded for the given language
-     *    store: string,          optional, defaults to 'workspace://SpacesStore'
-     *    language: string,       optional, one of: lucene, xpath, fts-alfresco - defaults to 'lucene'
-     *    templates: [],          optional, Array of query language template objects (see below) - if supported by the language 
-     *    sort: [],               optional, Array of sort column objects (see below) - if supported by the language
-     *    page: object,           optional, paging information object (see below) - if supported by the language
-     *    namespace: string,      optional, the default namespace for properties
-     *    defaultField: string,   optional, the default field for query elements when not explicit in the query
-     *    defaultOperator: string,optional, the default operator for query elements when they are not explicit in the query AND or OR
-     *    fieldFacets: [],        optional, Array of fields (as full QName strings) to facet against
-     *    onerror: string         optional, result on error - one of: exception, no-results - defaults to 'exception'
-     * }
-     * 
-     * sort
-     * {
-     *    column: string,         mandatory, sort column in appropriate format for the language
-     *    ascending: boolean      optional, defaults to false
-     * }
-     * 
-     * page
-     * {
-     *    maxItems: int,          optional, max number of items to return in result set
-     *    skipCount: int          optional, number of items to skip over before returning results
-     * }
-     * 
-     * template
-     * {
-     *    field: string,          mandatory, custom field name for the template
-     *    template: string        mandatory, query template replacement for the template
-     * }
-     * 
-     * Note that only some query languages support custom query templates, such as 'fts-alfresco'. 
-     * See the following documentation for more details:
-     * Templates
-     * 
- * - * @param search Search definition object as above - * - * @return Array of ScriptNode results - */ - public Scriptable query(Object search) - { - return (Scriptable)queryResultSet(search).get("nodes", getScope()); - } - - public Scriptable queryResultSet(Object search) - { - Object[] results = null; - Map meta = null; - - // convert values from JS to Java - may contain native JS object such as ConsString etc. - search = new ValueConverter().convertValueForJava(search); - if (search instanceof Serializable) - { - Serializable obj = new ValueConverter().convertValueForRepo((Serializable)search); - if (obj instanceof Map) - { - Map def = (Map)obj; - - // test for mandatory values - String query = (String)def.get("query"); - if (query == null || query.length() == 0) - { - throw new AlfrescoRuntimeException("Failed to search: Missing mandatory 'query' value."); - } - - // collect optional values - String store = (String)def.get("store"); - String language = (String)def.get("language"); - List> sort = (List>)def.get("sort"); - Map page = (Map)def.get("page"); - List facets = (List)def.get("fieldFacets"); - List filterQueries = (List)def.get("filterQueries"); - String namespace = (String)def.get("namespace"); - String onerror = (String)def.get("onerror"); - String defaultField = (String)def.get("defaultField"); - String defaultOperator = (String)def.get("defaultOperator"); - String searchTerm = (String) def.get("searchTerm"); - boolean spellCheck = Boolean.TRUE.equals(def.get("spellCheck")); - - // extract supplied values - - // sorting columns - SortColumn[] sortColumns = null; - if (sort != null) - { - sortColumns = new SortColumn[sort.size()]; - int index = 0; - for (Map column : sort) - { - String strCol = (String)column.get("column"); - if (strCol == null || strCol.length() == 0) - { - throw new AlfrescoRuntimeException("Failed to search: Missing mandatory 'sort: column' value."); - } - Boolean boolAsc = (Boolean)column.get("ascending"); - boolean ascending = (boolAsc != null ? boolAsc.booleanValue() : false); - sortColumns[index++] = new SortColumn(strCol, ascending); - } - } - - // paging settings - int maxResults = -1; - int skipResults = 0; - if (page != null) - { - if (page.get("maxItems") != null) - { - Object maxItems = page.get("maxItems"); - if (maxItems instanceof Number) - { - maxResults = ((Number)maxItems).intValue(); - } - else if (maxItems instanceof String) - { - // try and convert to int (which it what it should be!) - maxResults = Integer.parseInt((String)maxItems); - } - } - if (page.get("skipCount") != null) - { - Object skipCount = page.get("skipCount"); - if (skipCount instanceof Number) - { - skipResults = ((Number)page.get("skipCount")).intValue(); - } - else if (skipCount instanceof String) - { - skipResults = Integer.parseInt((String)skipCount); - } - } - } - - // query templates - Map queryTemplates = null; - List> templates = (List>)def.get("templates"); - if (templates != null) - { - queryTemplates = new HashMap(templates.size(), 1.0f); - - for (Map template : templates) - { - String field = (String)template.get("field"); - if (field == null || field.length() == 0) - { - throw new AlfrescoRuntimeException("Failed to search: Missing mandatory 'template: field' value."); - } - String t = (String)template.get("template"); - if (t == null || t.length() == 0) - { - throw new AlfrescoRuntimeException("Failed to search: Missing mandatory 'template: template' value."); - } - queryTemplates.put(field, t); - } - } - - SearchParameters sp = new SearchParameters(); - sp.addStore(store != null ? new StoreRef(store) : this.storeRef); - sp.setLanguage(language != null ? language : SearchService.LANGUAGE_LUCENE); - sp.setQuery(query); - sp.setSearchTerm(searchTerm); - sp.setSpellCheck(spellCheck); - if (defaultField != null) - { - sp.setDefaultFieldName(defaultField); - } - if (defaultOperator != null && defaultOperator.length() != 0) - { - try - { - sp.setDefaultOperator(Operator.valueOf(defaultOperator.toUpperCase())); - } - catch (IllegalArgumentException e) - { - // ignore invalid Operator and the default value will be used - } - } - if (namespace != null) - { - sp.setNamespace(namespace); - } - if (maxResults > 0) - { - sp.setLimit(maxResults); - sp.setLimitBy(LimitBy.FINAL_SIZE); - } - if (skipResults > 0) - { - sp.setSkipCount(skipResults); - } - if (sort != null) - { - for (SortColumn sd : sortColumns) - { - sp.addSort(sd.column, sd.asc); - } - } - if (queryTemplates != null) - { - for (String field: queryTemplates.keySet()) - { - sp.addQueryTemplate(field, queryTemplates.get(field)); - } - } - if (facets != null) - { - SolrFacetHelper solrFacetHelper = services.getSolrFacetHelper(); - for (String field: facets) - { - if (field.isEmpty()) - { - continue; - } - final String modifiedField = "@" + field; - if (solrFacetHelper.hasFacetQueries(modifiedField)) - { - List facetQueries = solrFacetHelper.getFacetQueries(modifiedField); - addFacetQuery(sp, field, facetQueries, query); - } - else - { - final FieldFacet fieldFacet; - if (solrFacetHelper.isSpecialFacetId(field)) - { - fieldFacet = new FieldFacet(field); - } - else - { - fieldFacet = new FieldFacet(modifiedField); - } - sp.addFieldFacet(fieldFacet); - } - } - } - if (filterQueries != null) - { - for (String filter: filterQueries) - { - sp.addFilterQuery(filter); - } - } - - // error handling opions - boolean exceptionOnError = true; - if (onerror != null) - { - if (onerror.equals("exception")) - { - // default value, do nothing - } - else if (onerror.equals("no-results")) - { - exceptionOnError = false; - } - else - { - throw new AlfrescoRuntimeException("Failed to search: Unknown value supplied for 'onerror': " + onerror); - } - } - - // execute search based on search definition - Pair> r = queryResultMeta(sp, exceptionOnError); - results = r.getFirst(); - meta = r.getSecond(); - } - } - - if (results == null) - { - results = new Object[0]; - } - - // construct a JS return object - // { - // nodes: [], // Array of ScriptNode results - // meta: { - // numberFound: long, // total number found in index, or -1 if not known or not supported by this resultset - // facets: { // facets are returned for each field as requested in the SearchParameters fieldfacets - // field: { // each field contains a map of facet to value - // facet: value, - // ... - // }, - // ... - // } - // } - // } - Scriptable scope = getScope(); - Scriptable res = Context.getCurrentContext().newObject(scope); - res.put("nodes", res, Context.getCurrentContext().newArray(scope, results)); - res.put("meta", res, meta); - return res; - } - - /** - * Encode a string to ISO9075 - used to build valid paths for Lucene queries etc. - * - * @param s Value to encode - * - * @return encoded value - */ - public String ISO9075Encode(String s) - { - return ISO9075.encode(s); - } - - /** - * Decode a string from ISO9075 - * - * @param s Value to decode - * - * @return decoded value - */ - public String ISO9075Decode(String s) - { - return ISO9075.decode(s); - } - - /** - * Execute the query - * - * Removes any duplicates that may be present (ID search can cause duplicates - - * it is better to remove them here) - * - * @param store StoreRef to search against - null for default configured store - * @param search Lucene search to execute - * @param sort Columns to sort by - * @param language Search language to use e.g. SearchService.LANGUAGE_LUCENE - * - * @return Array of Node objects - */ - protected Object[] query(String store, String search, SortColumn[] sort, String language) - { - return query(store, search, sort, language, -1, 0); - } - - /** - * Execute the query - * - * Removes any duplicates that may be present (ID search can cause duplicates - - * it is better to remove them here) - * - * @param store StoreRef to search against - null for default configured store - * @param search Lucene search to execute - * @param sort Columns to sort by - * @param language Search language to use e.g. SearchService.LANGUAGE_LUCENE - * @param maxResults Maximum results to return if > 0 - * @param skipResults Results to skip in the result set - * - * @return Array of Node objects - */ - protected Object[] query(String store, String search, SortColumn[] sort, String language, int maxResults, int skipResults) - { - SearchParameters sp = new SearchParameters(); - sp.addStore(store != null ? new StoreRef(store) : this.storeRef); - sp.setLanguage(language != null ? language : SearchService.LANGUAGE_LUCENE); - sp.setQuery(search); - if (maxResults > 0) - { - sp.setLimit(maxResults); - sp.setLimitBy(LimitBy.FINAL_SIZE); - } - if (skipResults > 0) - { - sp.setSkipCount(skipResults); - } - if (sort != null) - { - for (SortColumn sd : sort) - { - sp.addSort(sd.column, sd.asc); - } - } - - return query(sp, true); - } - - /** - * Execute the query - * - * Removes any duplicates that may be present (ID search can cause duplicates - - * it is better to remove them here) - * - * @param sp SearchParameters describing the search to execute. - * @param exceptionOnError True to throw a runtime exception on error, false to return empty resultset - * - * @return Array of Node objects - */ - protected Object[] query(SearchParameters sp, boolean exceptionOnError) - { - return queryResultMeta(sp, exceptionOnError).getFirst(); - } - - /** - * Execute the query - * - * Removes any duplicates that may be present (ID search can cause duplicates - - * it is better to remove them here) - * - * @param sp SearchParameters describing the search to execute. - * @param exceptionOnError True to throw a runtime exception on error, false to return empty resultset - * - * @return Pair containing Object[] of Node objects, and the ResultSet metadata hash. - */ - protected Pair> queryResultMeta(SearchParameters sp, boolean exceptionOnError) - { - Collection set = null; - Map meta = new HashMap<>(8); - - long time = 0L; - if (logger.isDebugEnabled()) - { - logger.debug("query=" + sp.getQuery() + " limit=" + (sp.getLimitBy() != LimitBy.UNLIMITED ? sp.getLimit() : "none")); - time = System.currentTimeMillis(); - } - - // perform the search against the repo - ResultSet results = null; - try - { - results = this.services.getSearchService().query(sp); - - // results nodes - if (results.length() != 0) - { - NodeService nodeService = this.services.getNodeService(); - set = new LinkedHashSet(results.length(), 1.0f); - for (ResultSetRow row: results) - { - NodeRef nodeRef = row.getNodeRef(); - if (nodeService.exists(nodeRef)) - { - set.add(new ScriptNode(nodeRef, this.services, getScope())); - } - } - } - // results metadata - meta.put("numberFound", results.getNumberFound()); - meta.put("hasMore", results.hasMore()); - // results facets - FacetLabelDisplayHandlerRegistry facetLabelDisplayHandlerRegistry = services.getFacetLabelDisplayHandlerRegistry(); - Map> facetMeta = new HashMap<>(); - for (FieldFacet ff: sp.getFieldFacets()) - { - // for each field facet, get the facet results - List> fs = results.getFieldFacet(ff.getField()); - List facets = new ArrayList<>(); - for (Pair f : fs) - { - // ignore zero hit fields - if (f.getSecond() > 0) - { - String facetValue = f.getFirst(); - FacetLabelDisplayHandler handler = facetLabelDisplayHandlerRegistry.getDisplayHandler(ff.getField()); - String label = (handler == null) ? facetValue : handler.getDisplayLabel(facetValue).getLabel(); - - facets.add(new ScriptFacetResult(facetValue, label, -1, f.getSecond())); - } - } - // store facet results per field - facetMeta.put(ff.getField(), facets); - } - - // Start of bucketing - // ACE-1615: Populate the facetMeta map with empty lists. If there is a - // facet query with >0 hits, the relevant list will be populated - // with the results, otherwise the list remains empty. - for(String bucketedField : services.getSolrFacetHelper().getBucketedFieldFacets()) - { - facetMeta.put(bucketedField, new ArrayList()); - } - Set> facetQueries = results.getFacetQueries().entrySet(); - for(Entry entry : facetQueries) - { - // ignore zero hit facet queries - if (entry.getValue() > 0) - { - String key = entry.getKey(); - // for example the key could be: {!afts}@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-1DAY TO NOW/DAY+1DAY] - // qName => @{http://www.alfresco.org/model/content/1.0}created - // 7 => {!afts} - key = key.substring(7); - String qName = key.substring(0, key.lastIndexOf(':')); - - // Retrieve the previous facet queries - List fqs = facetMeta.get(qName); - if (fqs == null) - { - fqs = new ArrayList<>(); - logger.info("Field facet [" + key + "] has not been registered."); - } - // Get the handler for this qName - FacetLabelDisplayHandler handler = facetLabelDisplayHandlerRegistry.getDisplayHandler(qName); - FacetLabel facetLabel = (handler == null) ? new FacetLabel(key, key, -1) : handler.getDisplayLabel(key); - - fqs.add(new ScriptFacetResult(facetLabel.getValue(), facetLabel.getLabel(), facetLabel.getLabelIndex(), entry.getValue())); - } - }// End of bucketing - meta.put("facets", facetMeta); - SpellCheckResult spellCheckResult = results.getSpellCheckResult(); - meta.put("spellcheck", new ScriptSpellCheckResult( - sp.getSearchTerm(), - spellCheckResult.getResultName(), - spellCheckResult.isSearchedFor(), - spellCheckResult.getResults(), - spellCheckResult.isSpellCheckExist())); - } - catch (Throwable err) - { - if (exceptionOnError) - { - throw new AlfrescoRuntimeException("Failed to execute search: " + sp.getQuery(), err); - } - else - { - if (logger.isDebugEnabled()) - logger.debug("Failed to execute search: " + sp.getQuery(), err); - // put expected values to handle case where exception occurs in search - meta.put("numberFound", 0); - meta.put("hasMore", false); - } - } - finally - { - if (results != null) - { - results.close(); - } - if (logger.isDebugEnabled()) - logger.debug("query time: " + (System.currentTimeMillis()-time) + "ms"); - } - - Object[] res = set != null ? set.toArray(new Object[(set.size())]) : new Object[0]; - return new Pair>(res, meta); - } - - /** - * Adds facet queries to the {@code SearchParameters} - * - * @param sp the SearchParameters - * @param field the requested field facet - * @param facetQueries list of generated facet queries - * @param query the requested search query - */ - protected void addFacetQuery(SearchParameters sp, String field, List facetQueries, String query) - { - // Workaround for ACE-1605 - if (query.indexOf(field) < 0) - { - for (String fq : facetQueries) - { - sp.addFacetQuery(fq); - } - } - else - { - String fq = services.getSolrFacetHelper().createFacetQueriesFromSearchQuery(field, query); - if (fq != null) - { - sp.addFacetQuery(fq); - } - } - } - - /** - * Search sort column - */ - public class SortColumn - { - /** - * Constructor - * - * @param column column to sort on - * @param asc sort direction - */ - public SortColumn(String column, boolean asc) - { - this.column = column; - this.asc = asc; - } - - public String column; - public boolean asc; - } -} +package org.alfresco.repo.jscript; + +import java.io.Serializable; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.management.subsystems.SwitchableApplicationContextFactory; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.search.impl.solr.facet.SolrFacetHelper; +import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabel; +import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabelDisplayHandler; +import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabelDisplayHandlerRegistry; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchParameters.FieldFacet; +import org.alfresco.service.cmr.search.SearchParameters.Operator; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.search.SpellCheckResult; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.util.ISO9075; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.jaxen.saxpath.base.XPathReader; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * Search component for use by the ScriptService. + *

+ * Provides access to Lucene search facilities including saved search objects. The results + * from a search are returned as an array (collection) of scriptable Node wrapper objects. + *

+ * The object is added to the root of the model to provide syntax such as: + * var results = search.luceneSearch(statement); + * and + * var results = search.savedSearch(node); + * + * @author Kevin Roast + */ +public class Search extends BaseScopableProcessorExtension implements InitializingBean +{ + private static Log logger = LogFactory.getLog(Search.class); + + /** Service registry */ + protected ServiceRegistry services; + + /** Default store reference */ + protected StoreRef storeRef; + + /** Repository helper */ + protected Repository repository; + + private SwitchableApplicationContextFactory searchSubsystem; + + @Override + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "services", services); + } + + /** + * Set the default store reference + * + * @param storeRef the default store reference + */ + public void setStoreUrl(String storeRef) + { + // ensure this is not set again by a script instance! + if (this.storeRef != null) + { + throw new IllegalStateException("Default store URL can only be set once."); + } + this.storeRef = new StoreRef(storeRef); + } + + /** + * Set the service registry + * + * @param services the service registry + */ + public void setServiceRegistry(ServiceRegistry services) + { + this.services = services; + } + + /** + * Set the repository helper + * + * @param repository the repository helper + */ + public void setRepositoryHelper(Repository repository) + { + this.repository = repository; + } + + public void setSearchSubsystemSwitchableApplicationContextFactory(SwitchableApplicationContextFactory searchSubsystem) + { + this.searchSubsystem = searchSubsystem; + } + + // JavaScript API + + public String getSearchSubsystem() + { + return (searchSubsystem == null) ? "" : searchSubsystem.getCurrentSourceBeanName(); + } + + /** + * Find a single Node by the Node reference + * + * @param ref The NodeRef of the Node to find + * + * @return the Node if found or null if failed to find + */ + public ScriptNode findNode(NodeRef ref) + { + ParameterCheck.mandatory("ref", ref); + if (this.services.getNodeService().exists(ref) + && (this.services.getPermissionService().hasPermission(ref, + PermissionService.READ) == AccessStatus.ALLOWED)) + { + return new ScriptNode(ref, this.services, getScope()); + } + return null; + } + + /** + * Find a single Node by the Node reference + * + * @param ref The fully qualified NodeRef in String format + * + * @return the Node if found or null if failed to find + */ + public ScriptNode findNode(String ref) + { + ParameterCheck.mandatoryString("ref", ref); + return findNode(new NodeRef(ref)); + } + + /** + * Helper to convert a Web Script Request URL to a Node Ref + * + * 1) Node - {store_type}/{store_id}/{node_id} + * + * Resolve to node via its Node Reference. + * + * 2) Path - {store_type}/{store_id}/{path} + * + * Resolve to node via its display path. + * + * @param referenceType one of "node", "path" + * @param reference array of reference segments (as described above for each reference type) + * @return ScriptNode the script node + */ + public ScriptNode findNode(String referenceType, String[] reference) + { + ParameterCheck.mandatoryString("referenceType", referenceType); + ParameterCheck.mandatory("reference", reference); + ScriptNode result = null; + NodeRef nodeRef = this.repository.findNodeRef(referenceType, reference); + if (nodeRef != null) + { + result = new ScriptNode(nodeRef, this.services, getScope()); + } + return result; + } + + /** + * Execute a XPath search + * + * @param search XPath search string to execute + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable xpathSearch(String search) + { + return xpathSearch(null, search); + } + + /** + * Execute a XPath search + * + * @param store Store reference to search against i.e. workspace://SpacesStore + * @param search XPath search string to execute + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable xpathSearch(String store, String search) + { + if (search != null && search.length() != 0) + { + Object[] results = query(store, search, null, SearchService.LANGUAGE_XPATH); + return Context.getCurrentContext().newArray(getScope(), results); + } + else + { + return Context.getCurrentContext().newArray(getScope(), 0); + } + } + + /** + * Execute a SelectNodes XPath search + * + * @param search SelectNodes XPath search string to execute + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable selectNodes(String search) + { + return selectNodes(null, search); + } + + /** + * Execute a SelectNodes XPath search + * + * @param store Store reference to search against i.e. workspace://SpacesStore + * @param search SelectNodes XPath search string to execute + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable selectNodes(String store, String search) + { + if (search != null && search.length() != 0) + { + Object[] nodeArray = new Object[0]; + if (store == null) + { + store = "workspace://SpacesStore"; + } + try + { + NodeService nodeService = this.services.getNodeService(); + List nodes = this.services.getSearchService().selectNodes( + nodeService.getRootNode(new StoreRef(store)), search, null, this.services.getNamespaceService(), false); + if (nodes.size() != 0) + { + int index = 0; + nodeArray = new Object[nodes.size()]; + for (NodeRef node: nodes) + { + nodeArray[index++] = new ScriptNode(node, this.services, getScope()); + } + } + } + catch (Throwable err) + { + throw new AlfrescoRuntimeException("Failed to execute search: " + search, err); + } + + return Context.getCurrentContext().newArray(getScope(), nodeArray); + } + else + { + return Context.getCurrentContext().newArray(getScope(), 0); + } + } + + /** + * Validation Xpath query + * + * @param query xpath query + * @return true if xpath query valid + */ + public boolean isValidXpathQuery(String query) + { + try + { + XPathReader reader = new XPathReader(); + reader.parse(query); + } + catch (Exception e) + { + return false; + } + return true; + } + + /** + * Execute a Lucene search + * + * @param search Lucene search string to execute + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable luceneSearch(String search) + { + return luceneSearch(null, search); + } + + /** + * Execute a Lucene search + * + * @param store Store reference to search against i.e. workspace://SpacesStore + * @param search Lucene search string to execute + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable luceneSearch(String store, String search) + { + if (search != null && search.length() != 0) + { + Object[] results = query(store, search, null, SearchService.LANGUAGE_LUCENE); + return Context.getCurrentContext().newArray(getScope(), results); + } + else + { + return Context.getCurrentContext().newArray(getScope(), 0); + } + } + + /** + * Execute a Lucene search (sorted) + * + * @param search Lucene search string to execute + * @param sortColumn column to sort on + * @param asc true => ascending sort + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable luceneSearch(String search, String sortColumn, boolean asc) + { + return luceneSearch(null, search, sortColumn, asc, 0); + } + + public Scriptable luceneSearch(String search, String sortColumn, boolean asc, int max) + { + return luceneSearch(null, search, sortColumn, asc, max); + } + + public Scriptable luceneSearch(String store, String search, String sortColumn, boolean asc) + { + return luceneSearch(store, search, sortColumn, asc, 0); + } + + /** + * Execute a Lucene search (sorted) + * + * @param store Store reference to search against i.e. workspace://SpacesStore + * @param search Lucene search string to execute + * @param sortColumn column to sort on + * @param asc true => ascending sort + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable luceneSearch(String store, String search, String sortColumn, boolean asc, int max) + { + if (search == null || search.length() == 0) + { + return Context.getCurrentContext().newArray(getScope(), 0); + } + + SortColumn[] sort = null; + if (sortColumn != null && sortColumn.length() != 0) + { + sort = new SortColumn[1]; + sort[0] = new SortColumn(sortColumn, asc); + } + Object[] results = query(store, search, sort, SearchService.LANGUAGE_LUCENE, max, 0); + return Context.getCurrentContext().newArray(getScope(), results); + } + + /** + * Execute a saved Lucene search + * + * @param savedSearch Node that contains the saved search XML content + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable savedSearch(ScriptNode savedSearch) + { + String search = null; + + // read the Saved Search XML on the specified node - and get the Lucene search from it + try + { + if (savedSearch != null) + { + ContentReader content = this.services.getContentService().getReader( + savedSearch.getNodeRef(), ContentModel.PROP_CONTENT); + if (content != null && content.exists()) + { + // get the root element + SAXReader reader = new SAXReader(); + Document document = reader.read(new StringReader(content.getContentString())); + Element rootElement = document.getRootElement(); + + Element queryElement = rootElement.element("query"); + if (queryElement != null) + { + search = queryElement.getText(); + } + } + } + } + catch (Throwable err) + { + throw new AlfrescoRuntimeException("Failed to find or load saved Search: " + savedSearch.getNodeRef(), err); + } + + if (search != null) + { + Object[] results = query(null, search, null, SearchService.LANGUAGE_LUCENE); + return Context.getCurrentContext().newArray(getScope(), results); + } + else + { + return Context.getCurrentContext().newArray(getScope(), 0); + } + } + + /** + * Execute a saved Lucene search + * + * @param searchRef NodeRef string that points to the node containing saved search XML content + * + * @return JavaScript array of Node results from the search - can be empty but not null + */ + public Scriptable savedSearch(String searchRef) + { + if (searchRef != null) + { + return savedSearch(new ScriptNode(new NodeRef(searchRef), services, null)); + } + else + { + return Context.getCurrentContext().newArray(getScope(), 0); + } + } + + /** + * Searchs the store for all nodes with the given tag applied. + * + * @param store store ref string, default used if null provided + * @param tag tag name + * @return ScriptNode[] nodes with tag applied + */ + public ScriptNode[] tagSearch(String store, String tag) + { + StoreRef searchStoreRef = null; + if (store != null) + { + searchStoreRef = new StoreRef(store); + } + else + { + searchStoreRef = this.storeRef; + } + + List nodeRefs = this.services.getTaggingService().findTaggedNodes(searchStoreRef, tag); + ScriptNode[] nodes = new ScriptNode[nodeRefs.size()]; + int index = 0; + for (NodeRef node : nodeRefs) + { + nodes[index] = new ScriptNode(node, this.services, getScope()); + index ++; + } + return nodes; + } + + /** + * Execute a query based on the supplied search definition object. + * + * Search object is defined in JavaScript thus: + *

+     * search
+     * {
+     *    query: string,          mandatory, in appropriate format and encoded for the given language
+     *    store: string,          optional, defaults to 'workspace://SpacesStore'
+     *    language: string,       optional, one of: lucene, xpath, fts-alfresco - defaults to 'lucene'
+     *    templates: [],          optional, Array of query language template objects (see below) - if supported by the language 
+     *    sort: [],               optional, Array of sort column objects (see below) - if supported by the language
+     *    page: object,           optional, paging information object (see below) - if supported by the language
+     *    namespace: string,      optional, the default namespace for properties
+     *    defaultField: string,   optional, the default field for query elements when not explicit in the query
+     *    defaultOperator: string,optional, the default operator for query elements when they are not explicit in the query AND or OR
+     *    fieldFacets: [],        optional, Array of fields (as full QName strings) to facet against
+     *    onerror: string         optional, result on error - one of: exception, no-results - defaults to 'exception'
+     * }
+     * 
+     * sort
+     * {
+     *    column: string,         mandatory, sort column in appropriate format for the language
+     *    ascending: boolean      optional, defaults to false
+     * }
+     * 
+     * page
+     * {
+     *    maxItems: int,          optional, max number of items to return in result set
+     *    skipCount: int          optional, number of items to skip over before returning results
+     * }
+     * 
+     * template
+     * {
+     *    field: string,          mandatory, custom field name for the template
+     *    template: string        mandatory, query template replacement for the template
+     * }
+     * 
+     * Note that only some query languages support custom query templates, such as 'fts-alfresco'. 
+     * See the following documentation for more details:
+     * Templates
+     * 
+ * + * @param search Search definition object as above + * + * @return Array of ScriptNode results + */ + public Scriptable query(Object search) + { + return (Scriptable)queryResultSet(search).get("nodes", getScope()); + } + + public Scriptable queryResultSet(Object search) + { + Object[] results = null; + Map meta = null; + + // convert values from JS to Java - may contain native JS object such as ConsString etc. + search = new ValueConverter().convertValueForJava(search); + if (search instanceof Serializable) + { + Serializable obj = new ValueConverter().convertValueForRepo((Serializable)search); + if (obj instanceof Map) + { + Map def = (Map)obj; + + // test for mandatory values + String query = (String)def.get("query"); + if (query == null || query.length() == 0) + { + throw new AlfrescoRuntimeException("Failed to search: Missing mandatory 'query' value."); + } + + // collect optional values + String store = (String)def.get("store"); + String language = (String)def.get("language"); + List> sort = (List>)def.get("sort"); + Map page = (Map)def.get("page"); + List facets = (List)def.get("fieldFacets"); + List filterQueries = (List)def.get("filterQueries"); + String namespace = (String)def.get("namespace"); + String onerror = (String)def.get("onerror"); + String defaultField = (String)def.get("defaultField"); + String defaultOperator = (String)def.get("defaultOperator"); + String searchTerm = (String) def.get("searchTerm"); + boolean spellCheck = Boolean.TRUE.equals(def.get("spellCheck")); + + // extract supplied values + + // sorting columns + SortColumn[] sortColumns = null; + if (sort != null) + { + sortColumns = new SortColumn[sort.size()]; + int index = 0; + for (Map column : sort) + { + String strCol = (String)column.get("column"); + if (strCol == null || strCol.length() == 0) + { + throw new AlfrescoRuntimeException("Failed to search: Missing mandatory 'sort: column' value."); + } + Boolean boolAsc = (Boolean)column.get("ascending"); + boolean ascending = (boolAsc != null ? boolAsc.booleanValue() : false); + sortColumns[index++] = new SortColumn(strCol, ascending); + } + } + + // paging settings + int maxResults = -1; + int skipResults = 0; + if (page != null) + { + if (page.get("maxItems") != null) + { + Object maxItems = page.get("maxItems"); + if (maxItems instanceof Number) + { + maxResults = ((Number)maxItems).intValue(); + } + else if (maxItems instanceof String) + { + // try and convert to int (which it what it should be!) + maxResults = Integer.parseInt((String)maxItems); + } + } + if (page.get("skipCount") != null) + { + Object skipCount = page.get("skipCount"); + if (skipCount instanceof Number) + { + skipResults = ((Number)page.get("skipCount")).intValue(); + } + else if (skipCount instanceof String) + { + skipResults = Integer.parseInt((String)skipCount); + } + } + } + + // query templates + Map queryTemplates = null; + List> templates = (List>)def.get("templates"); + if (templates != null) + { + queryTemplates = new HashMap(templates.size(), 1.0f); + + for (Map template : templates) + { + String field = (String)template.get("field"); + if (field == null || field.length() == 0) + { + throw new AlfrescoRuntimeException("Failed to search: Missing mandatory 'template: field' value."); + } + String t = (String)template.get("template"); + if (t == null || t.length() == 0) + { + throw new AlfrescoRuntimeException("Failed to search: Missing mandatory 'template: template' value."); + } + queryTemplates.put(field, t); + } + } + + SearchParameters sp = new SearchParameters(); + sp.addStore(store != null ? new StoreRef(store) : this.storeRef); + sp.setLanguage(language != null ? language : SearchService.LANGUAGE_LUCENE); + sp.setQuery(query); + sp.setSearchTerm(searchTerm); + sp.setSpellCheck(spellCheck); + if (defaultField != null) + { + sp.setDefaultFieldName(defaultField); + } + if (defaultOperator != null && defaultOperator.length() != 0) + { + try + { + sp.setDefaultOperator(Operator.valueOf(defaultOperator.toUpperCase())); + } + catch (IllegalArgumentException e) + { + // ignore invalid Operator and the default value will be used + } + } + if (namespace != null) + { + sp.setNamespace(namespace); + } + if (maxResults > 0) + { + sp.setLimit(maxResults); + sp.setLimitBy(LimitBy.FINAL_SIZE); + } + if (skipResults > 0) + { + sp.setSkipCount(skipResults); + } + if (sort != null) + { + for (SortColumn sd : sortColumns) + { + sp.addSort(sd.column, sd.asc); + } + } + if (queryTemplates != null) + { + for (String field: queryTemplates.keySet()) + { + sp.addQueryTemplate(field, queryTemplates.get(field)); + } + } + if (facets != null) + { + SolrFacetHelper solrFacetHelper = services.getSolrFacetHelper(); + for (String field: facets) + { + if (field.isEmpty()) + { + continue; + } + final String modifiedField = "@" + field; + if (solrFacetHelper.hasFacetQueries(modifiedField)) + { + List facetQueries = solrFacetHelper.getFacetQueries(modifiedField); + addFacetQuery(sp, field, facetQueries, query); + } + else + { + final FieldFacet fieldFacet; + if (solrFacetHelper.isSpecialFacetId(field)) + { + fieldFacet = new FieldFacet(field); + } + else + { + fieldFacet = new FieldFacet(modifiedField); + } + sp.addFieldFacet(fieldFacet); + } + } + } + if (filterQueries != null) + { + for (String filter: filterQueries) + { + sp.addFilterQuery(filter); + } + } + + // error handling opions + boolean exceptionOnError = true; + if (onerror != null) + { + if (onerror.equals("exception")) + { + // default value, do nothing + } + else if (onerror.equals("no-results")) + { + exceptionOnError = false; + } + else + { + throw new AlfrescoRuntimeException("Failed to search: Unknown value supplied for 'onerror': " + onerror); + } + } + + // execute search based on search definition + Pair> r = queryResultMeta(sp, exceptionOnError); + results = r.getFirst(); + meta = r.getSecond(); + } + } + + if (results == null) + { + results = new Object[0]; + } + + // construct a JS return object + // { + // nodes: [], // Array of ScriptNode results + // meta: { + // numberFound: long, // total number found in index, or -1 if not known or not supported by this resultset + // facets: { // facets are returned for each field as requested in the SearchParameters fieldfacets + // field: { // each field contains a map of facet to value + // facet: value, + // ... + // }, + // ... + // } + // } + // } + Scriptable scope = getScope(); + Scriptable res = Context.getCurrentContext().newObject(scope); + res.put("nodes", res, Context.getCurrentContext().newArray(scope, results)); + res.put("meta", res, meta); + return res; + } + + /** + * Encode a string to ISO9075 - used to build valid paths for Lucene queries etc. + * + * @param s Value to encode + * + * @return encoded value + */ + public String ISO9075Encode(String s) + { + return ISO9075.encode(s); + } + + /** + * Decode a string from ISO9075 + * + * @param s Value to decode + * + * @return decoded value + */ + public String ISO9075Decode(String s) + { + return ISO9075.decode(s); + } + + /** + * Execute the query + * + * Removes any duplicates that may be present (ID search can cause duplicates - + * it is better to remove them here) + * + * @param store StoreRef to search against - null for default configured store + * @param search Lucene search to execute + * @param sort Columns to sort by + * @param language Search language to use e.g. SearchService.LANGUAGE_LUCENE + * + * @return Array of Node objects + */ + protected Object[] query(String store, String search, SortColumn[] sort, String language) + { + return query(store, search, sort, language, -1, 0); + } + + /** + * Execute the query + * + * Removes any duplicates that may be present (ID search can cause duplicates - + * it is better to remove them here) + * + * @param store StoreRef to search against - null for default configured store + * @param search Lucene search to execute + * @param sort Columns to sort by + * @param language Search language to use e.g. SearchService.LANGUAGE_LUCENE + * @param maxResults Maximum results to return if > 0 + * @param skipResults Results to skip in the result set + * + * @return Array of Node objects + */ + protected Object[] query(String store, String search, SortColumn[] sort, String language, int maxResults, int skipResults) + { + SearchParameters sp = new SearchParameters(); + sp.addStore(store != null ? new StoreRef(store) : this.storeRef); + sp.setLanguage(language != null ? language : SearchService.LANGUAGE_LUCENE); + sp.setQuery(search); + if (maxResults > 0) + { + sp.setLimit(maxResults); + sp.setLimitBy(LimitBy.FINAL_SIZE); + } + if (skipResults > 0) + { + sp.setSkipCount(skipResults); + } + if (sort != null) + { + for (SortColumn sd : sort) + { + sp.addSort(sd.column, sd.asc); + } + } + + return query(sp, true); + } + + /** + * Execute the query + * + * Removes any duplicates that may be present (ID search can cause duplicates - + * it is better to remove them here) + * + * @param sp SearchParameters describing the search to execute. + * @param exceptionOnError True to throw a runtime exception on error, false to return empty resultset + * + * @return Array of Node objects + */ + protected Object[] query(SearchParameters sp, boolean exceptionOnError) + { + return queryResultMeta(sp, exceptionOnError).getFirst(); + } + + /** + * Execute the query + * + * Removes any duplicates that may be present (ID search can cause duplicates - + * it is better to remove them here) + * + * @param sp SearchParameters describing the search to execute. + * @param exceptionOnError True to throw a runtime exception on error, false to return empty resultset + * + * @return Pair containing Object[] of Node objects, and the ResultSet metadata hash. + */ + protected Pair> queryResultMeta(SearchParameters sp, boolean exceptionOnError) + { + Collection set = null; + Map meta = new HashMap<>(8); + + long time = 0L; + if (logger.isDebugEnabled()) + { + logger.debug("query=" + sp.getQuery() + " limit=" + (sp.getLimitBy() != LimitBy.UNLIMITED ? sp.getLimit() : "none")); + time = System.currentTimeMillis(); + } + + // perform the search against the repo + ResultSet results = null; + try + { + results = this.services.getSearchService().query(sp); + + // results nodes + if (results.length() != 0) + { + NodeService nodeService = this.services.getNodeService(); + set = new LinkedHashSet(results.length(), 1.0f); + for (ResultSetRow row: results) + { + NodeRef nodeRef = row.getNodeRef(); + if (nodeService.exists(nodeRef)) + { + set.add(new ScriptNode(nodeRef, this.services, getScope())); + } + } + } + // results metadata + meta.put("numberFound", results.getNumberFound()); + meta.put("hasMore", results.hasMore()); + // results facets + FacetLabelDisplayHandlerRegistry facetLabelDisplayHandlerRegistry = services.getFacetLabelDisplayHandlerRegistry(); + Map> facetMeta = new HashMap<>(); + for (FieldFacet ff: sp.getFieldFacets()) + { + // for each field facet, get the facet results + List> fs = results.getFieldFacet(ff.getField()); + List facets = new ArrayList<>(); + for (Pair f : fs) + { + // ignore zero hit fields + if (f.getSecond() > 0) + { + String facetValue = f.getFirst(); + FacetLabelDisplayHandler handler = facetLabelDisplayHandlerRegistry.getDisplayHandler(ff.getField()); + String label = (handler == null) ? facetValue : handler.getDisplayLabel(facetValue).getLabel(); + + facets.add(new ScriptFacetResult(facetValue, label, -1, f.getSecond())); + } + } + // store facet results per field + facetMeta.put(ff.getField(), facets); + } + + // Start of bucketing + // ACE-1615: Populate the facetMeta map with empty lists. If there is a + // facet query with >0 hits, the relevant list will be populated + // with the results, otherwise the list remains empty. + for(String bucketedField : services.getSolrFacetHelper().getBucketedFieldFacets()) + { + facetMeta.put(bucketedField, new ArrayList()); + } + Set> facetQueries = results.getFacetQueries().entrySet(); + for(Entry entry : facetQueries) + { + // ignore zero hit facet queries + if (entry.getValue() > 0) + { + String key = entry.getKey(); + // for example the key could be: {!afts}@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-1DAY TO NOW/DAY+1DAY] + // qName => @{http://www.alfresco.org/model/content/1.0}created + // 7 => {!afts} + key = key.substring(7); + String qName = key.substring(0, key.lastIndexOf(':')); + + // Retrieve the previous facet queries + List fqs = facetMeta.get(qName); + if (fqs == null) + { + fqs = new ArrayList<>(); + logger.info("Field facet [" + key + "] has not been registered."); + } + // Get the handler for this qName + FacetLabelDisplayHandler handler = facetLabelDisplayHandlerRegistry.getDisplayHandler(qName); + FacetLabel facetLabel = (handler == null) ? new FacetLabel(key, key, -1) : handler.getDisplayLabel(key); + + fqs.add(new ScriptFacetResult(facetLabel.getValue(), facetLabel.getLabel(), facetLabel.getLabelIndex(), entry.getValue())); + } + }// End of bucketing + meta.put("facets", facetMeta); + SpellCheckResult spellCheckResult = results.getSpellCheckResult(); + meta.put("spellcheck", new ScriptSpellCheckResult( + sp.getSearchTerm(), + spellCheckResult.getResultName(), + spellCheckResult.isSearchedFor(), + spellCheckResult.getResults(), + spellCheckResult.isSpellCheckExist())); + } + catch (Throwable err) + { + if (exceptionOnError) + { + throw new AlfrescoRuntimeException("Failed to execute search: " + sp.getQuery(), err); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Failed to execute search: " + sp.getQuery(), err); + // put expected values to handle case where exception occurs in search + meta.put("numberFound", 0); + meta.put("hasMore", false); + } + } + finally + { + if (results != null) + { + results.close(); + } + if (logger.isDebugEnabled()) + logger.debug("query time: " + (System.currentTimeMillis()-time) + "ms"); + } + + Object[] res = set != null ? set.toArray(new Object[(set.size())]) : new Object[0]; + return new Pair>(res, meta); + } + + /** + * Adds facet queries to the {@code SearchParameters} + * + * @param sp the SearchParameters + * @param field the requested field facet + * @param facetQueries list of generated facet queries + * @param query the requested search query + */ + protected void addFacetQuery(SearchParameters sp, String field, List facetQueries, String query) + { + // Workaround for ACE-1605 + if (query.indexOf(field) < 0) + { + for (String fq : facetQueries) + { + sp.addFacetQuery(fq); + } + } + else + { + String fq = services.getSolrFacetHelper().createFacetQueriesFromSearchQuery(field, query); + if (fq != null) + { + sp.addFacetQuery(fq); + } + } + } + + /** + * Search sort column + */ + public class SortColumn + { + /** + * Constructor + * + * @param column column to sort on + * @param asc sort direction + */ + public SortColumn(String column, boolean asc) + { + this.column = column; + this.asc = asc; + } + + public String column; + public boolean asc; + } +} diff --git a/source/java/org/alfresco/repo/jscript/ValueConverter.java b/source/java/org/alfresco/repo/jscript/ValueConverter.java index bd1a2e8641..01f3d4a444 100644 --- a/source/java/org/alfresco/repo/jscript/ValueConverter.java +++ b/source/java/org/alfresco/repo/jscript/ValueConverter.java @@ -1,289 +1,289 @@ -package org.alfresco.repo.jscript; - -import java.io.Serializable; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.AssociationRef; -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.namespace.QName; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.IdScriptableObject; -import org.mozilla.javascript.NativeArray; -import org.mozilla.javascript.ScriptRuntime; -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.Wrapper; - - -/** - * Value conversion allowing safe usage of values in Script and Java. - */ -public class ValueConverter -{ - private static final String TYPE_DATE = "Date"; - - /** - * Convert an object from any repository serialized value to a valid script object. - * This includes converting Collection multi-value properties into JavaScript Array objects. - * - * @param services Repository Services Registry - * @param scope Scripting scope - * @param qname QName of the property value for conversion - * @param value Property value - * - * @return Value safe for scripting usage - */ - @SuppressWarnings("unchecked") - public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname, Serializable value) - { - // perform conversions from Java objects to JavaScript scriptable instances - if (value == null) - { - return null; - } - else if (value instanceof NodeRef) - { - // NodeRef object properties are converted to new Node objects - // so they can be used as objects within a template - value = new ScriptNode(((NodeRef)value), services, scope); - } - else if (value instanceof QName || value instanceof StoreRef) - { - value = value.toString(); - } - else if (value instanceof ChildAssociationRef) - { - value = new ChildAssociation(services, (ChildAssociationRef)value, scope); - } - else if (value instanceof AssociationRef) - { - value = new Association(services, (AssociationRef)value, scope); - } - else if (value instanceof Date) - { - // convert Date to JavaScript native Date object - // call the "Date" constructor on the root scope object - passing in the millisecond - // value from the Java date - this will construct a JavaScript Date with the same value - Date date = (Date)value; - - try - { - Context.enter(); - Object val = ScriptRuntime.newObject( - Context.getCurrentContext(), scope, TYPE_DATE, new Object[] {date.getTime()}); - value = (Serializable)val; - } - finally - { - Context.exit(); - } - } - else if (value instanceof Collection) - { - // recursively convert each value in the collection - Collection collection = (Collection)value; - Object[] array = new Object[collection.size()]; - int index = 0; - for (Serializable obj : collection) - { - array[index++] = convertValueForScript(services, scope, qname, obj); - } - try - { - Context.enter(); - // Convert array to a native JavaScript Array - // Note - a scope is usually required for this to work - value = (Serializable)Context.getCurrentContext().newArray(scope, array); - } - finally - { - Context.exit(); - } - } - // simple numbers and strings are wrapped automatically by Rhino - - return value; - } - - /** - * Convert an object from any script wrapper value to a valid repository serializable value. - * This includes converting JavaScript Array objects to Lists of valid objects. - * - * @param value Value to convert from script wrapper object to repo serializable value - * - * @return valid repo value - */ - public Serializable convertValueForRepo(Serializable value) - { - Object converted = convertValueForJava((Object)value); - return converted instanceof Serializable ? (Serializable)converted : value; - } - - public final Object convertValueForJava(Object value) - { - if (value == null) - { - return null; - } - else if (value instanceof ScriptNode) - { - // convert back to NodeRef - value = ((ScriptNode)value).getNodeRef(); - } - else if (value instanceof ChildAssociation) - { - value = ((ChildAssociation)value).getChildAssociationRef(); - } - else if (value instanceof Association) - { - value = ((Association)value).getAssociationRef(); - } - else if (value instanceof Wrapper) - { - // unwrap a Java object from a JavaScript wrapper - // recursively call this method to convert the unwrapped value - value = convertValueForJava(((Wrapper)value).unwrap()); - } - else if (value instanceof Scriptable) - { - // a scriptable object will probably indicate a multi-value property - // set using a JavaScript Array object - Scriptable values = (Scriptable)value; - - if (value instanceof IdScriptableObject) - { - // TODO: add code here to use the dictionary and convert to correct value type - if (TYPE_DATE.equals(((IdScriptableObject)value).getClassName())) - { - value = Context.jsToJava(value, Date.class); - } - else if (value instanceof NativeArray) - { - // convert JavaScript array of values to a List of objects - Object[] propIds = values.getIds(); - if (isArray(propIds) == true) - { - List propValues = new ArrayList(propIds.length); - for (int i=0; i propValues = new HashMap(propIds.length); - for (Object propId : propIds) - { - // Get the value and add to the map - Object val = values.get(propId.toString(), values); - propValues.put(convertValueForJava(propId), convertValueForJava(val)); - } - - value = propValues; - } - } - else - { - // convert Scriptable object of values to a Map of objects - Object[] propIds = values.getIds(); - Map propValues = new HashMap(propIds.length); - for (int i=0; i propValues = new HashMap(propIds.length); - for (int i=0; i list = new ArrayList(length); - for (int i=0; i collection = (Collection)value; + Object[] array = new Object[collection.size()]; + int index = 0; + for (Serializable obj : collection) + { + array[index++] = convertValueForScript(services, scope, qname, obj); + } + try + { + Context.enter(); + // Convert array to a native JavaScript Array + // Note - a scope is usually required for this to work + value = (Serializable)Context.getCurrentContext().newArray(scope, array); + } + finally + { + Context.exit(); + } + } + // simple numbers and strings are wrapped automatically by Rhino + + return value; + } + + /** + * Convert an object from any script wrapper value to a valid repository serializable value. + * This includes converting JavaScript Array objects to Lists of valid objects. + * + * @param value Value to convert from script wrapper object to repo serializable value + * + * @return valid repo value + */ + public Serializable convertValueForRepo(Serializable value) + { + Object converted = convertValueForJava((Object)value); + return converted instanceof Serializable ? (Serializable)converted : value; + } + + public final Object convertValueForJava(Object value) + { + if (value == null) + { + return null; + } + else if (value instanceof ScriptNode) + { + // convert back to NodeRef + value = ((ScriptNode)value).getNodeRef(); + } + else if (value instanceof ChildAssociation) + { + value = ((ChildAssociation)value).getChildAssociationRef(); + } + else if (value instanceof Association) + { + value = ((Association)value).getAssociationRef(); + } + else if (value instanceof Wrapper) + { + // unwrap a Java object from a JavaScript wrapper + // recursively call this method to convert the unwrapped value + value = convertValueForJava(((Wrapper)value).unwrap()); + } + else if (value instanceof Scriptable) + { + // a scriptable object will probably indicate a multi-value property + // set using a JavaScript Array object + Scriptable values = (Scriptable)value; + + if (value instanceof IdScriptableObject) + { + // TODO: add code here to use the dictionary and convert to correct value type + if (TYPE_DATE.equals(((IdScriptableObject)value).getClassName())) + { + value = Context.jsToJava(value, Date.class); + } + else if (value instanceof NativeArray) + { + // convert JavaScript array of values to a List of objects + Object[] propIds = values.getIds(); + if (isArray(propIds) == true) + { + List propValues = new ArrayList(propIds.length); + for (int i=0; i propValues = new HashMap(propIds.length); + for (Object propId : propIds) + { + // Get the value and add to the map + Object val = values.get(propId.toString(), values); + propValues.put(convertValueForJava(propId), convertValueForJava(val)); + } + + value = propValues; + } + } + else + { + // convert Scriptable object of values to a Map of objects + Object[] propIds = values.getIds(); + Map propValues = new HashMap(propIds.length); + for (int i=0; i propValues = new HashMap(propIds.length); + for (int i=0; i list = new ArrayList(length); + for (int i=0; i