diff --git a/fine-spring/lib/connector-api-1.5.jar b/fine-spring/lib/connector-api-1.5.jar new file mode 100644 index 000000000..683762e1f Binary files /dev/null and b/fine-spring/lib/connector-api-1.5.jar differ diff --git a/fine-spring/lib/javax.transaction-api-1.2.jar b/fine-spring/lib/javax.transaction-api-1.2.jar new file mode 100644 index 000000000..e87adb89f Binary files /dev/null and b/fine-spring/lib/javax.transaction-api-1.2.jar differ diff --git a/fine-spring/resources/META-INF/com/fr/third/springframework/ldap/config/spring-ldap-2.0.xsd b/fine-spring/resources/META-INF/com/fr/third/springframework/ldap/config/spring-ldap-2.0.xsd new file mode 100644 index 000000000..67ef7bb90 --- /dev/null +++ b/fine-spring/resources/META-INF/com/fr/third/springframework/ldap/config/spring-ldap-2.0.xsd @@ -0,0 +1,685 @@ + + + + + + + + + + A bean identifier, used for referring to the bean elsewhere in the context. + "contextSource". + + + + + + + Defines whether read-only operations will be performed using an anonymous (unauthenticated) context. + + + + + + + Id of the AuthenticationSource instance to use. If not specified, a SimpleAuthenticationSource will + be used. + + + + + + + Id of the DirContextAuthenticationStrategy instance to use. If not specified, a SimpleDirContextAuthenticationStrategy + will be used. + + + + + + + The base DN. If configured, all LDAP operations on contexts retrieved from this ContextSource will + be relative to this DN. Default is an empty distinguished name (i.e. all operations will be + relative to the directory root). + + + + + + + The password to use for authentication. + + + + + + + Specify whether native Java LDAP connection pooling should be used. Default is false. + + + + + + + Defines the strategy to handle referrals, as described on http://docs.oracle.com/javase/jndi/tutorial/ldap/referral/jndi.html. + Default is null. + + + + + + + + + + + + + + URL of the LDAP server to use. If fail-over functionality is desired, more than one URL can + be specified, separated using comma (,). + + + + + + + The username (principal) to use for authentication. This will normally be the distinguished name + of an admin user. + + + + + + + Reference to a Map of custom environment properties that should supplied with the environment + sent to the DirContext on construction. + + + + + + + + + + The maximum number of active connections of each type (read-only|read-write) + that can be allocated from the pool at the same time, or non-positive for no limit. + Default is 8. + + + + + + + The overall maximum number of active connections (for all types) that can be allocated from + this pool at the same time, or non-positive for no limit. Default is -1 (no limit). + + + + + + + The maximum number of active connections of each type (read-only|read-write) that can remain idle in the pool, + without extra ones being released, or non-positive for no limit. Default is 8. + + + + + + + The minimum number of active connections of each type (read-only|read-write) that can remain + idle in the pool, without extra ones being created, or zero to create none. Default is 0. + + + + + + + The maximum number of milliseconds that the pool will wait (when there are no available connections) + for a connection to be returned before throwing an exception, or non-positive to wait indefinitely. + Default is -1. + + + + + + + Specifies the behaviour when the pool is exhausted. + + + + + + + + Throw a NoSuchElementException when the pool is exhausted + + + + + + + Wait until a new object is available. If max-wait is positive a NoSuchElementException + is thrown if no new object is available after the maxWait time expires. + + + + + + + Create and return a new object (essentially making maxActive meaningless). + + + + + + + + + + The indication of whether objects will be validated before being borrowed from the pool. + If the object fails to validate, it will be dropped from the pool, and an attempt to borrow another will be made. + Default is false. + + + + + + + The indication of whether objects will be validated before being returned to the pool. + Default is false. + + + + + + + The indication of whether objects will be validated by the idle object evictor (if any). + If an object fails to validate, it will be dropped from the pool. + Default is false. + + + + + + + The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, + no idle object evictor thread will be run. Default is -1. + + + + + + + The number of objects to examine during each run of the idle object evictor thread (if any). + Default is 3. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible + for eviction by the idle object evictor (if any). Default is 1000 * 60 * 30. + + + + + + + The base dn to use for validation searches. Default is LdapUtils.emptyPath(). + + + + + + + The filter to use for validation queries. Default is (objectclass=*). + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + + + + The overall maximum number of active connections (for all types) that can be allocated from + this pool at the same time, or non-positive for no limit. Default is -1 (no limit). + + + + + + + The limit on the number of object instances allocated by the pool (checked out or idle), + per key. When the limit is reached, the sub-pool is said to be exhausted. A negative value + indicates no limit. Default is 8. + + + + + + + The maximum number of active connections per type (read-only|read-write) that can remain idle in the pool, + without extra ones being released, or non-positive for no limit. Default is 8. + + + + + + + The minimum number of active connections per type (read-only|read-write) that can remain + idle in the pool, without extra ones being created, or zero to create none. Default is 0. + + + + + + + The maximum number of milliseconds that the pool will wait (when there are no available connections) + for a connection to be returned before throwing an exception, or non-positive to wait indefinitely. + Default is -1. + + + + + + + Sets to wait until a new object is available. If max-wait is positive a NoSuchElementException + is thrown if no new object is available after the maxWait time expires.. + + + + + + + Sets whether objects created for the pool will be validated before borrowing. If the object + fails to validate, then borrowing will fail. Default is false. + + + + + + + The indication of whether objects will be validated before being borrowed from the pool. + If the object fails to validate, it will be dropped from the pool, and an attempt to borrow another will be made. + Default is false. + + + + + + + The indication of whether objects will be validated before being returned to the pool. + Default is false. + + + + + + + The indication of whether objects will be validated by the idle object evictor (if any). + If an object fails to validate, it will be dropped from the pool. + Default is false. + + + + + + + The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, + no idle object evictor thread will be run. Default is -1. + + + + + + + The number of objects to examine during each run of the idle object evictor thread (if any). + Default is 3. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible + for eviction by the idle object evictor (if any). Default is 1000 * 60 * 30. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible for + eviction by the idle object evictor, with the extra condition that at least minimum number + of object instances per key remain in the pool. This settings is overridden by min-evictable-time-millis if + it is set to a positive value. Default is -1. + + + + + + + The name of the eviction policy implementation that is used by this pool. The Pool will + attempt to load the class using the thread context class loader. If that fails, the Pool + will attempt to load the class using the class loader that loaded this class. Default is + com.fr.third.org.apache.commons.pool2.impl.DefaultEvictionPolicy. + + + + + + + Sets whether or not the pool serves threads waiting to borrow connections fairly. + True means that waiting threads are served as if waiting in a FIFO queue. Default is false. + + + + + + + Sets whether JMX will be enabled with the platform MBean server for the pool. Default + is true. + + + + + + + The value of the JMX name base that will be used as part of the name assigned + to JMX enabled pools. Default is null. + + + + + + + The value of the JMX name prefix that will be used as part of the name assigned + to JMX enabled pools. Default value is pool. + + + + + + + Sets whether the pool has LIFO (last in, first out) behaviour with + respect to idle objects - always returning the most recently used object + from the pool, or as a FIFO (first in, first out) queue, where the pool + always returns the oldest object in the idle object pool. Default is true. + + + + + + + The base dn to use for validation searches. Default is LdapUtils.emptyPath(). + + + + + + + The filter to use for validation queries. Default is (objectclass=*). + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + + + Creates a ContextSource instance to be used to get LdapContexts for communicating with an LDAP server. + + + + + + + + + Defines the settings to use for the Spring LDAP connection pooling support. + + + + + + + + + + + + Defines the settings to use for the Spring LDAP connection pooling support based on commons-pool2 library. + + + + + + + + + + + + + + + + + A bean identifier, used for referring to the bean elsewhere in the context. + Default is "ldapTemplate". + + + + + + + Id of the ContextSource instance to use. Default is "contextSource". + + + + + + + The default count limit for searches. Default is 0 (no limit). + + + + + + + The default time limit for searches. Default is 0 (no limit). + + + + + + + The default search scope for searches. Default is SUBTREE. + + + + + + + + + + + + + + Specifies whether NameNotFoundException should be ignored in searches. Setting this + attribute to true will cause errors caused by invalid search base to be silently swallowed. + Default is false. + + + + + + + Specifies whether PartialResultException should be ignored in searches. Some LDAP servers + have problems with referrals; these should normally be followed automatically, but if this + doesn't work it will manifest itself with a PartialResultException. Setting this attribute + to true presents a work-around to this problem. Default is false. + + + + + + + Id of the ObjectDirectoryMapper instance to use. Default is a default-configured DefaultObjectDirectoryMapper. + + + + + + + + + Creates an LdapTemplate instance. + + + + + + + + + + + + Id of this instance. Default is "transactionManager". + + + + + + + Id of the ContextSource instance to use. "contextSource". + + + + + + + Id of the DataSource instance to use. + + + + + + + Id of the Hibernate SessionFactory instance to use. + + + + + + + + + Creates an ContextSourceTransactionManager. If data-source-ref or session-factory-ref is specified, + a DataSourceAndContextSourceTransactionManager/HibernateAndContextSourceTransactionManager will be + created. + + + + + + + + The default (simplistic) TempEntryRenamingStrategy. Please note that this + strategy will not work for more advanced scenarios. See reference documentation + for details. + + + + + + + The default suffix that will be added to modified entries. + Default is "_temp". + + + + + + + + + TempEntryRenamingStrategy that moves the entry to a different subtree than + the original entry. + + + + + + + The subtree base where changed entries should be moved. + + + + + + + + + + + + + + + + + + The reference to an LdapTemplate. Will default to 'ldapTemplate'. + + + + + + + + diff --git a/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-2.0.xsd b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-2.0.xsd new file mode 100644 index 000000000..740ce9e4a --- /dev/null +++ b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-2.0.xsd @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-2.5.xsd b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-2.5.xsd new file mode 100644 index 000000000..41212b527 --- /dev/null +++ b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-2.5.xsd @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-3.0.xsd b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-3.0.xsd new file mode 100644 index 000000000..7fc987776 --- /dev/null +++ b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-3.0.xsd @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-3.1.xsd b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-3.1.xsd new file mode 100644 index 000000000..7536daf3d --- /dev/null +++ b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-3.1.xsd @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-3.2.xsd b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-3.2.xsd new file mode 100644 index 000000000..b5d9ac100 --- /dev/null +++ b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-3.2.xsd @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-4.0.xsd b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-4.0.xsd new file mode 100644 index 000000000..ebd5c8df9 --- /dev/null +++ b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx-4.0.xsd @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx.gif b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx.gif new file mode 100644 index 000000000..20ed1f9a4 Binary files /dev/null and b/fine-spring/resources/META-INF/com/fr/third/springframework/transaction/config/spring-tx.gif differ diff --git a/fine-spring/resources/META-INF/fine-spring.handlers b/fine-spring/resources/META-INF/fine-spring.handlers index f0c858aba..13996c1e4 100644 --- a/fine-spring/resources/META-INF/fine-spring.handlers +++ b/fine-spring/resources/META-INF/fine-spring.handlers @@ -5,3 +5,6 @@ http\://www.springframework.org/schema/task=com.fr.third.springframework.schedul http\://www.springframework.org/schema/cache=com.fr.third.springframework.cache.config.CacheNamespaceHandler http\://www.springframework.org/schema/mvc=com.fr.third.springframework.web.servlet.config.MvcNamespaceHandler +http\://www.springframework.org/schema/tx=com.fr.third.springframework.transaction.config.TxNamespaceHandler + +http\://www.springframework.org/schema/ldap=com.fr.third.springframework.ldap.config.LdapNamespaceHandler \ No newline at end of file diff --git a/fine-spring/resources/META-INF/fine-spring.schemas b/fine-spring/resources/META-INF/fine-spring.schemas index dc698ce0d..a908c9b79 100644 --- a/fine-spring/resources/META-INF/fine-spring.schemas +++ b/fine-spring/resources/META-INF/fine-spring.schemas @@ -53,3 +53,14 @@ http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=com/fr/third/spr http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=com/fr/third/springframework/beans/factory/xml/spring-tool-3.2.xsd http\://www.springframework.org/schema/tool/spring-tool-4.0.xsd=com/fr/third/springframework/beans/factory/xml/spring-tool-4.0.xsd http\://www.springframework.org/schema/tool/spring-tool.xsd=com/fr/third/springframework/beans/factory/xml/spring-tool-4.0.xsd + +http\://www.springframework.org/schema/tx/spring-tx-2.0.xsd=com/fr/third/springframework/transaction/config/spring-tx-2.0.xsd +http\://www.springframework.org/schema/tx/spring-tx-2.5.xsd=com/fr/third/springframework/transaction/config/spring-tx-2.5.xsd +http\://www.springframework.org/schema/tx/spring-tx-3.0.xsd=com/fr/third/springframework/transaction/config/spring-tx-3.0.xsd +http\://www.springframework.org/schema/tx/spring-tx-3.1.xsd=com/fr/third/springframework/transaction/config/spring-tx-3.1.xsd +http\://www.springframework.org/schema/tx/spring-tx-3.2.xsd=com/fr/third/springframework/transaction/config/spring-tx-3.2.xsd +http\://www.springframework.org/schema/tx/spring-tx-4.0.xsd=com/fr/third/springframework/transaction/config/spring-tx-4.0.xsd +http\://www.springframework.org/schema/tx/spring-tx.xsd=com/fr/third/springframework/transaction/config/spring-tx-4.0.xsd + +http\://www.springframework.org/schema/ldap/spring-ldap.xsd=com/fr/third/springframework/ldap/config/spring-ldap-2.0.xsd +http\://www.springframework.org/schema/ldap/spring-ldap-2.0.xsd=com/fr/third/springframework/ldap/config/spring-ldap-2.0.xsd \ No newline at end of file diff --git a/fine-spring/resources/META-INF/fine-spring.tooling b/fine-spring/resources/META-INF/fine-spring.tooling index 9c620f349..c8e96c805 100644 --- a/fine-spring/resources/META-INF/fine-spring.tooling +++ b/fine-spring/resources/META-INF/fine-spring.tooling @@ -22,3 +22,9 @@ http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/c http\://www.springframework.org/schema/cache@name=cache Namespace http\://www.springframework.org/schema/cache@prefix=cache http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif + +# Tooling related information for the tx namespace +http\://www.springframework.org/schema/tx@name=tx Namespace +http\://www.springframework.org/schema/tx@prefix=tx +http\://www.springframework.org/schema/tx@icon=org/springframework/transaction/config/spring-tx.gif + diff --git a/fine-spring/src/com/fr/third/springframework/LdapDataEntry.java b/fine-spring/src/com/fr/third/springframework/LdapDataEntry.java new file mode 100644 index 000000000..1ce6454d1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/LdapDataEntry.java @@ -0,0 +1,212 @@ +package com.fr.third.springframework; + +import javax.naming.Name; +import javax.naming.directory.Attributes; +import java.util.SortedSet; + +/** + * Common data access methods for entries in an LDAP tree. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public interface LdapDataEntry { + /** + * Get the value of a String attribute. If more than one attribute value + * exists for the specified attribute, only the first one will be returned. + * If an attribute has no value, null will be returned. + * + * @param name name of the attribute. + * @return the value of the attribute if it exists, or null if + * the attribute doesn't exist or if it exists but with no value. + * @throws ClassCastException if the value of the entry is not a String. + */ + String getStringAttribute(String name); + + /** + * Get the value of an Object attribute. If more than one attribute value + * exists for the specified attribute, only the first one will be returned. + * If an attribute has no value, null will be returned. + * + * @param name name of the attribute. + * @return the attribute value as an object if it exists, or + * null if the attribute doesn't exist or if it exists but with + * no value. + */ + Object getObjectAttribute(String name); + + /** + * Check if an Object attribute exists, regardless of whether it has a value + * or not. + * + * @param name name of the attribute + * @return true if the attribute exists, false + * otherwise + */ + boolean attributeExists(String name); + + /** + * Set the with the name name to the value. + * If the value is a {@link Name} instance, equality for Distinguished + * Names will be used for calculating attribute modifications. + * + * @param name name of the attribute. + * @param value value to set the attribute to. + * @throws IllegalArgumentException if the value is a {@link Name} instance + * and one or several of the currently present attribute values is not + * {@link Name} instances or Strings representing valid Distinguished Names. + */ + void setAttributeValue(String name, Object value); + + /** + * Sets a multivalue attribute, disregarding the order of the values. + * + * If value is null or value.length == 0 then the attribute will be removed. + * + * If update mode, changes will be made only if the array has more or less + * objects or if one or more object has changed. Reordering the objects will + * not cause an update. + * + * If the values are {@link Name} instances, equality for Distinguished + * Names will be used for calculating attribute modifications. + * + * @param name The id of the attribute. + * @param values Attribute values. + * @throws IllegalArgumentException if value is a {@link Name} instance + * and one or several of the currently present attribute values is not + * {@link Name} instances or Strings representing valid Distinguished Names. + */ + void setAttributeValues(String name, Object[] values); + + /** + * Sets a multivalue attribute. + * + * If value is null or value.length == 0 then the attribute will be removed. + * + * If update mode, changes will be made if the array has more or less + * objects or if one or more string has changed. + * + * Reordering the objects will only cause an update if orderMatters is set + * to true. + * + * If the values are {@link Name} instances, equality for Distinguished + * Names will be used for calculating attribute modifications. + * @param name The id of the attribute. + * @param values Attribute values. + * @param orderMatters If true, it will be changed even if data + * was just reordered. + * @throws IllegalArgumentException if value is a {@link Name} instance + * and one or several of the currently present attribute values is not + * {@link Name} instances or Strings representing valid Distinguished Names. + */ + void setAttributeValues(String name, Object[] values, boolean orderMatters); + + /** + * Add a value to the Attribute with the specified name. If the Attribute + * doesn't exist it will be created. This method makes sure that the there + * will be no duplicates of an added value - it the value exists it will not + * be added again. + * + * If the value is a {@link Name} instance, equality for Distinguished + * Names will be used for calculating attribute modifications. + * + * @param name the name of the Attribute to which the specified value should + * be added. + * @param value the Attribute value to add. + * @throws IllegalArgumentException if value is a {@link Name} instance + * and one or several of the currently present attribute values is not + * {@link Name} instances or Strings representing valid Distinguished Names. + */ + void addAttributeValue(String name, Object value); + + /** + * Add a value to the Attribute with the specified name. If the Attribute + * doesn't exist it will be created. The addIfDuplicateExists + * parameter controls the handling of duplicates. It false, + * this method makes sure that the there will be no duplicates of an added + * value - it the value exists it will not be added again. + * + * If the value is a {@link Name} instance, equality for Distinguished + * Names will be used for calculating attribute modifications. + * + * @param name the name of the Attribute to which the specified value should + * be added. + * @param value the Attribute value to add. + * @param addIfDuplicateExists true will add the value + * regardless of whether there is an identical value already, allowing for + * duplicate attribute values; false will not add the value if + * it already exists. + * @throws IllegalArgumentException if value is a {@link Name} instance + * and one or several of the currently present attribute values is not + * {@link Name} instances or Strings representing valid Distinguished Names. + */ + void addAttributeValue(String name, Object value, + boolean addIfDuplicateExists); + + /** + * Remove a value from the Attribute with the specified name. If the + * Attribute doesn't exist, do nothing. + * + * If the value is a {@link Name} instance, equality for Distinguished + * Names will be used for calculating attribute modifications. + * + * @param name the name of the Attribute from which the specified value + * should be removed. + * @param value the value to remove. + * @throws IllegalArgumentException if value is a {@link Name} instance + * and one or several of the currently present attribute values is not + * {@link Name} instances or Strings representing valid Distinguished Names. + */ + void removeAttributeValue(String name, Object value); + + /** + * Get all values of a String attribute. + * + * @param name name of the attribute. + * @return a (possibly empty) array containing all registered values of the + * attribute as Strings if the attribute is defined or null + * otherwise. + * @throws IllegalArgumentException if any of the attribute values is not a + * String. + */ + String[] getStringAttributes(String name); + + /** + * Get all values of an Object attribute. + * + * @param name name of the attribute. + * @return a (possibly empty) array containing all registered values of the + * attribute if the attribute is defined or null otherwise. + * @since 1.3 + */ + Object[] getObjectAttributes(String name); + + /** + * Get all String values of the attribute as a SortedSet. + * + * @param name name of the attribute. + * @return a SortedSet containing all values of the attribute, + * or null if the attribute does not exist. + * @throws IllegalArgumentException if one of the found attribute values cannot be cast to a String. + */ + SortedSet getAttributeSortedStringSet(String name); + + /** + * Returns the DN relative to the base path. + * NB: as of version 2.0 the returned name will be an LdapName instance. + * + * @return The distinguished name of the current context. + * + * @see com.fr.third.springframework.ldap.core.DirContextAdapter#getNameInNamespace() + */ + Name getDn(); + + + /** + * Get all the Attributes. + * + * @return all the Attributes. + * @since 1.3 + */ + Attributes getAttributes(); +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/CannotAcquireLockException.java b/fine-spring/src/com/fr/third/springframework/dao/CannotAcquireLockException.java new file mode 100644 index 000000000..5bca784b1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/CannotAcquireLockException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown on failure to aquire a lock during an update, + * for example during a "select for update" statement. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public class CannotAcquireLockException extends PessimisticLockingFailureException { + + /** + * Constructor for CannotAcquireLockException. + * @param msg the detail message + */ + public CannotAcquireLockException(String msg) { + super(msg); + } + + /** + * Constructor for CannotAcquireLockException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public CannotAcquireLockException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/CannotSerializeTransactionException.java b/fine-spring/src/com/fr/third/springframework/dao/CannotSerializeTransactionException.java new file mode 100644 index 000000000..b87940c8f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/CannotSerializeTransactionException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown on failure to complete a transaction in serialized mode + * due to update conflicts. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public class CannotSerializeTransactionException extends PessimisticLockingFailureException { + + /** + * Constructor for CannotSerializeTransactionException. + * @param msg the detail message + */ + public CannotSerializeTransactionException(String msg) { + super(msg); + } + + /** + * Constructor for CannotSerializeTransactionException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public CannotSerializeTransactionException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/CleanupFailureDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/CleanupFailureDataAccessException.java new file mode 100644 index 000000000..3ec16417d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/CleanupFailureDataAccessException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown when we couldn't cleanup after a data access operation, + * but the actual operation went OK. + * + *

For example, this exception or a subclass might be thrown if a JDBC + * Connection couldn't be closed after it had been used successfully. + * + *

Note that data access code might perform resources cleanup in a + * finally block and therefore log cleanup failure rather than rethrow it, + * to keep the original data access exception, if any. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public class CleanupFailureDataAccessException extends NonTransientDataAccessException { + + /** + * Constructor for CleanupFailureDataAccessException. + * @param msg the detail message + * @param cause the root cause from the underlying data access API, + * such as JDBC + */ + public CleanupFailureDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/ConcurrencyFailureException.java b/fine-spring/src/com/fr/third/springframework/dao/ConcurrencyFailureException.java new file mode 100644 index 000000000..fb15b5274 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/ConcurrencyFailureException.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown on concurrency failure. + * + *

This exception should be subclassed to indicate the type of failure: + * optimistic locking, failure to acquire lock, etc. + * + * @author Thomas Risberg + * @since 1.1 + * @see OptimisticLockingFailureException + * @see PessimisticLockingFailureException + * @see CannotAcquireLockException + * @see DeadlockLoserDataAccessException + */ +@SuppressWarnings("serial") +public class ConcurrencyFailureException extends TransientDataAccessException { + + /** + * Constructor for ConcurrencyFailureException. + * @param msg the detail message + */ + public ConcurrencyFailureException(String msg) { + super(msg); + } + + /** + * Constructor for ConcurrencyFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public ConcurrencyFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/DataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/DataAccessException.java new file mode 100644 index 000000000..183599e19 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/DataAccessException.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +import com.fr.third.springframework.core.NestedRuntimeException; + +/** + * Root of the hierarchy of data access exceptions discussed in + * Expert One-On-One J2EE Design and Development. + * Please see Chapter 9 of this book for detailed discussion of the + * motivation for this package. + * + *

This exception hierarchy aims to let user code find and handle the + * kind of error encountered without knowing the details of the particular + * data access API in use (e.g. JDBC). Thus it is possible to react to an + * optimistic locking failure without knowing that JDBC is being used. + * + *

As this class is a runtime exception, there is no need for user code + * to catch it or subclasses if any error is to be considered fatal + * (the usual case). + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public abstract class DataAccessException extends NestedRuntimeException { + + /** + * Constructor for DataAccessException. + * @param msg the detail message + */ + public DataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for DataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public DataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/DataAccessResourceFailureException.java b/fine-spring/src/com/fr/third/springframework/dao/DataAccessResourceFailureException.java new file mode 100644 index 000000000..b832c2f2f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/DataAccessResourceFailureException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Data access exception thrown when a resource fails completely: + * for example, if we can't connect to a database using JDBC. + * + * @author Rod Johnson + * @author Thomas Risberg + */ +@SuppressWarnings("serial") +public class DataAccessResourceFailureException extends NonTransientDataAccessResourceException { + + /** + * Constructor for DataAccessResourceFailureException. + * @param msg the detail message + */ + public DataAccessResourceFailureException(String msg) { + super(msg); + } + + /** + * Constructor for DataAccessResourceFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public DataAccessResourceFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/DataIntegrityViolationException.java b/fine-spring/src/com/fr/third/springframework/dao/DataIntegrityViolationException.java new file mode 100644 index 000000000..96978ee1a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/DataIntegrityViolationException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown when an attempt to insert or update data + * results in violation of an integrity constraint. Note that this + * is not purely a relational concept; unique primary keys are + * required by most database types. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public class DataIntegrityViolationException extends NonTransientDataAccessException { + + /** + * Constructor for DataIntegrityViolationException. + * @param msg the detail message + */ + public DataIntegrityViolationException(String msg) { + super(msg); + } + + /** + * Constructor for DataIntegrityViolationException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public DataIntegrityViolationException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/DataRetrievalFailureException.java b/fine-spring/src/com/fr/third/springframework/dao/DataRetrievalFailureException.java new file mode 100644 index 000000000..a7b233ab7 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/DataRetrievalFailureException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown if certain expected data could not be retrieved, e.g. + * when looking up specific data via a known identifier. This exception + * will be thrown either by O/R mapping tools or by DAO implementations. + * + * @author Juergen Hoeller + * @since 13.10.2003 + */ +@SuppressWarnings("serial") +public class DataRetrievalFailureException extends NonTransientDataAccessException { + + /** + * Constructor for DataRetrievalFailureException. + * @param msg the detail message + */ + public DataRetrievalFailureException(String msg) { + super(msg); + } + + /** + * Constructor for DataRetrievalFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public DataRetrievalFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/DeadlockLoserDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/DeadlockLoserDataAccessException.java new file mode 100644 index 000000000..e6361fcd3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/DeadlockLoserDataAccessException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Generic exception thrown when the current process was + * a deadlock loser, and its transaction rolled back. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public class DeadlockLoserDataAccessException extends PessimisticLockingFailureException { + + /** + * Constructor for DeadlockLoserDataAccessException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public DeadlockLoserDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/DuplicateKeyException.java b/fine-spring/src/com/fr/third/springframework/dao/DuplicateKeyException.java new file mode 100644 index 000000000..9b4270fcd --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/DuplicateKeyException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown when an attempt to insert or update data + * results in violation of an primary key or unique constraint. + * Note that this is not necessarily a purely relational concept; + * unique primary keys are required by most database types. + * + * @author Thomas Risberg + */ +@SuppressWarnings("serial") +public class DuplicateKeyException extends DataIntegrityViolationException { + + /** + * Constructor for DuplicateKeyException. + * @param msg the detail message + */ + public DuplicateKeyException(String msg) { + super(msg); + } + + /** + * Constructor for DuplicateKeyException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public DuplicateKeyException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/EmptyResultDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/EmptyResultDataAccessException.java new file mode 100644 index 000000000..1837cfaf3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/EmptyResultDataAccessException.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Data access exception thrown when a result was expected to have at least + * one row (or element) but zero rows (or elements) were actually returned. + * + * @author Juergen Hoeller + * @since 2.0 + * @see IncorrectResultSizeDataAccessException + */ +@SuppressWarnings("serial") +public class EmptyResultDataAccessException extends IncorrectResultSizeDataAccessException { + + /** + * Constructor for EmptyResultDataAccessException. + * @param expectedSize the expected result size + */ + public EmptyResultDataAccessException(int expectedSize) { + super(expectedSize, 0); + } + + /** + * Constructor for EmptyResultDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + */ + public EmptyResultDataAccessException(String msg, int expectedSize) { + super(msg, expectedSize, 0); + } + + /** + * Constructor for EmptyResultDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + * @param ex the wrapped exception + */ + public EmptyResultDataAccessException(String msg, int expectedSize, Throwable ex) { + super(msg, expectedSize, 0, ex); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/IncorrectResultSizeDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/IncorrectResultSizeDataAccessException.java new file mode 100644 index 000000000..ef47fa1f4 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/IncorrectResultSizeDataAccessException.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Data access exception thrown when a result was not of the expected size, + * for example when expecting a single row but getting 0 or more than 1 rows. + * + * @author Juergen Hoeller + * @author Chris Beams + * @since 1.0.2 + * @see EmptyResultDataAccessException + */ +@SuppressWarnings("serial") +public class IncorrectResultSizeDataAccessException extends DataRetrievalFailureException { + + private int expectedSize; + + private int actualSize; + + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param expectedSize the expected result size + */ + public IncorrectResultSizeDataAccessException(int expectedSize) { + super("Incorrect result size: expected " + expectedSize); + this.expectedSize = expectedSize; + this.actualSize = -1; + } + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param expectedSize the expected result size + * @param actualSize the actual result size (or -1 if unknown) + */ + public IncorrectResultSizeDataAccessException(int expectedSize, int actualSize) { + super("Incorrect result size: expected " + expectedSize + ", actual " + actualSize); + this.expectedSize = expectedSize; + this.actualSize = actualSize; + } + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + */ + public IncorrectResultSizeDataAccessException(String msg, int expectedSize) { + super(msg); + this.expectedSize = expectedSize; + this.actualSize = -1; + } + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + * @param ex the wrapped exception + */ + public IncorrectResultSizeDataAccessException(String msg, int expectedSize, Throwable ex) { + super(msg, ex); + this.expectedSize = expectedSize; + this.actualSize = -1; + } + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + * @param actualSize the actual result size (or -1 if unknown) + */ + public IncorrectResultSizeDataAccessException(String msg, int expectedSize, int actualSize) { + super(msg); + this.expectedSize = expectedSize; + this.actualSize = actualSize; + } + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + * @param actualSize the actual result size (or -1 if unknown) + * @param ex the wrapped exception + */ + public IncorrectResultSizeDataAccessException(String msg, int expectedSize, int actualSize, Throwable ex) { + super(msg, ex); + this.expectedSize = expectedSize; + this.actualSize = actualSize; + } + + + /** + * Return the expected result size. + */ + public int getExpectedSize() { + return this.expectedSize; + } + + /** + * Return the actual result size (or -1 if unknown). + */ + public int getActualSize() { + return this.actualSize; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/IncorrectUpdateSemanticsDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/IncorrectUpdateSemanticsDataAccessException.java new file mode 100644 index 000000000..21714916c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/IncorrectUpdateSemanticsDataAccessException.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Data access exception thrown when something unintended appears to have + * happened with an update, but the transaction hasn't already been rolled back. + * Thrown, for example, when we wanted to update 1 row in an RDBMS but actually + * updated 3. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public class IncorrectUpdateSemanticsDataAccessException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for IncorrectUpdateSemanticsDataAccessException. + * @param msg the detail message + */ + public IncorrectUpdateSemanticsDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for IncorrectUpdateSemanticsDataAccessException. + * @param msg the detail message + * @param cause the root cause from the underlying API, such as JDBC + */ + public IncorrectUpdateSemanticsDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Return whether data was updated. + * If this method returns false, there's nothing to roll back. + *

The default implementation always returns true. + * This can be overridden in subclasses. + */ + public boolean wasDataUpdated() { + return true; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/InvalidDataAccessApiUsageException.java b/fine-spring/src/com/fr/third/springframework/dao/InvalidDataAccessApiUsageException.java new file mode 100644 index 000000000..8d254e8f3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/InvalidDataAccessApiUsageException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown on incorrect usage of the API, such as failing to + * "compile" a query object that needed compilation before execution. + * + *

This represents a problem in our Java data access framework, + * not the underlying data access infrastructure. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public class InvalidDataAccessApiUsageException extends NonTransientDataAccessException { + + /** + * Constructor for InvalidDataAccessApiUsageException. + * @param msg the detail message + */ + public InvalidDataAccessApiUsageException(String msg) { + super(msg); + } + + /** + * Constructor for InvalidDataAccessApiUsageException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public InvalidDataAccessApiUsageException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/InvalidDataAccessResourceUsageException.java b/fine-spring/src/com/fr/third/springframework/dao/InvalidDataAccessResourceUsageException.java new file mode 100644 index 000000000..d34ac12bb --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/InvalidDataAccessResourceUsageException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Root for exceptions thrown when we use a data access resource incorrectly. + * Thrown for example on specifying bad SQL when using a RDBMS. + * Resource-specific subclasses are supplied by concrete data access packages. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public class InvalidDataAccessResourceUsageException extends NonTransientDataAccessException { + + /** + * Constructor for InvalidDataAccessResourceUsageException. + * @param msg the detail message + */ + public InvalidDataAccessResourceUsageException(String msg) { + super(msg); + } + + /** + * Constructor for InvalidDataAccessResourceUsageException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public InvalidDataAccessResourceUsageException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/NonTransientDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/NonTransientDataAccessException.java new file mode 100644 index 000000000..68c56a4a2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/NonTransientDataAccessException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Root of the hierarchy of data access exceptions that are considered non-transient - + * where a retry of the same operation would fail unless the cause of the Exception + * is corrected. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLNonTransientException + */ +@SuppressWarnings("serial") +public abstract class NonTransientDataAccessException extends DataAccessException { + + /** + * Constructor for NonTransientDataAccessException. + * @param msg the detail message + */ + public NonTransientDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for NonTransientDataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public NonTransientDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/NonTransientDataAccessResourceException.java b/fine-spring/src/com/fr/third/springframework/dao/NonTransientDataAccessResourceException.java new file mode 100644 index 000000000..32f8f23d8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/NonTransientDataAccessResourceException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Data access exception thrown when a resource fails completely and the failure is permanent. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLNonTransientConnectionException + */ +@SuppressWarnings("serial") +public class NonTransientDataAccessResourceException extends NonTransientDataAccessException { + + /** + * Constructor for NonTransientDataAccessResourceException. + * @param msg the detail message + */ + public NonTransientDataAccessResourceException(String msg) { + super(msg); + } + + /** + * Constructor for NonTransientDataAccessResourceException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public NonTransientDataAccessResourceException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/OptimisticLockingFailureException.java b/fine-spring/src/com/fr/third/springframework/dao/OptimisticLockingFailureException.java new file mode 100644 index 000000000..656942938 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/OptimisticLockingFailureException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown on an optimistic locking violation. + * + *

This exception will be thrown either by O/R mapping tools + * or by custom DAO implementations. Optimistic locking failure + * is typically not detected by the database itself. + * + * @author Rod Johnson + * @see PessimisticLockingFailureException + */ +@SuppressWarnings("serial") +public class OptimisticLockingFailureException extends ConcurrencyFailureException { + + /** + * Constructor for OptimisticLockingFailureException. + * @param msg the detail message + */ + public OptimisticLockingFailureException(String msg) { + super(msg); + } + + /** + * Constructor for OptimisticLockingFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public OptimisticLockingFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/PermissionDeniedDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/PermissionDeniedDataAccessException.java new file mode 100644 index 000000000..500ec4247 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/PermissionDeniedDataAccessException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown when the underlying resource denied a permission + * to access a specific element, such as a specific database table. + * + * @author Juergen Hoeller + * @since 2.0 + */ +@SuppressWarnings("serial") +public class PermissionDeniedDataAccessException extends NonTransientDataAccessException { + + /** + * Constructor for PermissionDeniedDataAccessException. + * @param msg the detail message + * @param cause the root cause from the underlying data access API, + * such as JDBC + */ + public PermissionDeniedDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/PessimisticLockingFailureException.java b/fine-spring/src/com/fr/third/springframework/dao/PessimisticLockingFailureException.java new file mode 100644 index 000000000..c511ad268 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/PessimisticLockingFailureException.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown on a pessimistic locking violation. + * Thrown by Spring's SQLException translation mechanism + * if a corresponding database error is encountered. + * + *

Serves as superclass for more specific exceptions, like + * CannotAcquireLockException and DeadlockLoserDataAccessException. + * + * @author Thomas Risberg + * @since 1.2 + * @see CannotAcquireLockException + * @see DeadlockLoserDataAccessException + * @see OptimisticLockingFailureException + */ +@SuppressWarnings("serial") +public class PessimisticLockingFailureException extends ConcurrencyFailureException { + + /** + * Constructor for PessimisticLockingFailureException. + * @param msg the detail message + */ + public PessimisticLockingFailureException(String msg) { + super(msg); + } + + /** + * Constructor for PessimisticLockingFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public PessimisticLockingFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/QueryTimeoutException.java b/fine-spring/src/com/fr/third/springframework/dao/QueryTimeoutException.java new file mode 100644 index 000000000..14fd55558 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/QueryTimeoutException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception to be thrown on a query timeout. This could have different causes depending on + * the database API in use but most likely thrown after the database interrupts or stops + * the processing of a query before it has completed. + * + *

This exception can be thrown by user code trapping the native database exception or + * by exception translation. + * + * @author Thomas Risberg + * @since 3.1 + */ +@SuppressWarnings("serial") +public class QueryTimeoutException extends TransientDataAccessException { + + /** + * Constructor for QueryTimeoutException. + * @param msg the detail message + */ + public QueryTimeoutException(String msg) { + super(msg); + } + + /** + * Constructor for QueryTimeoutException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public QueryTimeoutException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/RecoverableDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/RecoverableDataAccessException.java new file mode 100644 index 000000000..641c80112 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/RecoverableDataAccessException.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Data access exception thrown when a previously failed operation might be able + * to succeed if the application performs some recovery steps and retries the entire + * transaction or in the case of a distributed transaction, the transaction branch. + * At a minimum, the recovery operation must include closing the current connection + * and getting a new connection. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLRecoverableException + */ +@SuppressWarnings("serial") +public class RecoverableDataAccessException extends DataAccessException { + + /** + * Constructor for RecoverableDataAccessException. + * @param msg the detail message + */ + public RecoverableDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for RecoverableDataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public RecoverableDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/TransientDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/TransientDataAccessException.java new file mode 100644 index 000000000..d5665f37b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/TransientDataAccessException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Root of the hierarchy of data access exceptions that are considered transient - + * where a previously failed operation might be able to succeed when the operation + * is retried without any intervention by application-level functionality. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLTransientException + */ +@SuppressWarnings("serial") +public abstract class TransientDataAccessException extends DataAccessException { + + /** + * Constructor for TransientDataAccessException. + * @param msg the detail message + */ + public TransientDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for TransientDataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public TransientDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/TransientDataAccessResourceException.java b/fine-spring/src/com/fr/third/springframework/dao/TransientDataAccessResourceException.java new file mode 100644 index 000000000..2f0b47cc8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/TransientDataAccessResourceException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Data access exception thrown when a resource fails temporarily + * and the operation can be retried. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLTransientConnectionException + */ +@SuppressWarnings("serial") +public class TransientDataAccessResourceException extends TransientDataAccessException { + + /** + * Constructor for TransientDataAccessResourceException. + * @param msg the detail message + */ + public TransientDataAccessResourceException(String msg) { + super(msg); + } + + /** + * Constructor for TransientDataAccessResourceException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public TransientDataAccessResourceException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/TypeMismatchDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/TypeMismatchDataAccessException.java new file mode 100644 index 000000000..4cbfdca20 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/TypeMismatchDataAccessException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Exception thrown on mismatch between Java type and database type: + * for example on an attempt to set an object of the wrong type + * in an RDBMS column. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public class TypeMismatchDataAccessException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for TypeMismatchDataAccessException. + * @param msg the detail message + */ + public TypeMismatchDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for TypeMismatchDataAccessException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public TypeMismatchDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/UncategorizedDataAccessException.java b/fine-spring/src/com/fr/third/springframework/dao/UncategorizedDataAccessException.java new file mode 100644 index 000000000..40c2f69c1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/UncategorizedDataAccessException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao; + +/** + * Normal superclass when we can't distinguish anything more specific + * than "something went wrong with the underlying resource": for example, + * a SQLException from JDBC we can't pinpoint more precisely. + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public abstract class UncategorizedDataAccessException extends NonTransientDataAccessException { + + /** + * Constructor for UncategorizedDataAccessException. + * @param msg the detail message + * @param cause the exception thrown by underlying data access API + */ + public UncategorizedDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/annotation/PersistenceExceptionTranslationAdvisor.java b/fine-spring/src/com/fr/third/springframework/dao/annotation/PersistenceExceptionTranslationAdvisor.java new file mode 100644 index 000000000..c895b7652 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/annotation/PersistenceExceptionTranslationAdvisor.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao.annotation; + +import java.lang.annotation.Annotation; + +import org.aopalliance.aop.Advice; + +import com.fr.third.springframework.aop.Pointcut; +import com.fr.third.springframework.aop.support.AbstractPointcutAdvisor; +import com.fr.third.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import com.fr.third.springframework.beans.factory.ListableBeanFactory; +import com.fr.third.springframework.dao.support.PersistenceExceptionTranslationInterceptor; +import com.fr.third.springframework.dao.support.PersistenceExceptionTranslator; + +/** + * Spring AOP exception translation aspect for use at Repository or DAO layer level. + * Translates native persistence exceptions into Spring's DataAccessException hierarchy, + * based on a given PersistenceExceptionTranslator. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see com.fr.third.springframework.dao.DataAccessException + * @see com.fr.third.springframework.dao.support.PersistenceExceptionTranslator + */ +@SuppressWarnings("serial") +public class PersistenceExceptionTranslationAdvisor extends AbstractPointcutAdvisor { + + private final PersistenceExceptionTranslationInterceptor advice; + + private final AnnotationMatchingPointcut pointcut; + + + /** + * Create a new PersistenceExceptionTranslationAdvisor. + * @param persistenceExceptionTranslator the PersistenceExceptionTranslator to use + * @param repositoryAnnotationType the annotation type to check for + */ + public PersistenceExceptionTranslationAdvisor( + PersistenceExceptionTranslator persistenceExceptionTranslator, + Class repositoryAnnotationType) { + + this.advice = new PersistenceExceptionTranslationInterceptor(persistenceExceptionTranslator); + this.pointcut = new AnnotationMatchingPointcut(repositoryAnnotationType, true); + } + + /** + * Create a new PersistenceExceptionTranslationAdvisor. + * @param beanFactory the ListableBeanFactory to obtaining all + * PersistenceExceptionTranslators from + * @param repositoryAnnotationType the annotation type to check for + */ + PersistenceExceptionTranslationAdvisor( + ListableBeanFactory beanFactory, Class repositoryAnnotationType) { + + this.advice = new PersistenceExceptionTranslationInterceptor(beanFactory); + this.pointcut = new AnnotationMatchingPointcut(repositoryAnnotationType, true); + } + + + @Override + public Advice getAdvice() { + return this.advice; + } + + @Override + public Pointcut getPointcut() { + return this.pointcut; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java b/fine-spring/src/com/fr/third/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java new file mode 100644 index 000000000..a0d65337a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao.annotation; + +import java.lang.annotation.Annotation; + +import com.fr.third.springframework.aop.framework.AbstractAdvisingBeanPostProcessor; +import com.fr.third.springframework.beans.factory.BeanFactory; +import com.fr.third.springframework.beans.factory.BeanFactoryAware; +import com.fr.third.springframework.beans.factory.ListableBeanFactory; +import com.fr.third.springframework.stereotype.Repository; +import com.fr.third.springframework.util.Assert; + +/** + * Bean post-processor that automatically applies persistence exception translation to any + * bean marked with Spring's @{@link com.fr.third.springframework.stereotype.Repository Repository} + * annotation, adding a corresponding {@link PersistenceExceptionTranslationAdvisor} to + * the exposed proxy (either an existing AOP proxy or a newly generated proxy that + * implements all of the target's interfaces). + * + *

Translates native resource exceptions to Spring's + * {@link com.fr.third.springframework.dao.DataAccessException DataAccessException} hierarchy. + * Autodetects beans that implement the + * {@link com.fr.third.springframework.dao.support.PersistenceExceptionTranslator + * PersistenceExceptionTranslator} interface, which are subsequently asked to translate + * candidate exceptions. + * + + *

All of Spring's applicable resource factories (e.g. + * {@link com.fr.third.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean}) + * implement the {@code PersistenceExceptionTranslator} interface out of the box. + * As a consequence, all that is usually needed to enable automatic exception + * translation is marking all affected beans (such as Repositories or DAOs) + * with the {@code @Repository} annotation, along with defining this post-processor + * as a bean in the application context. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see PersistenceExceptionTranslationAdvisor + * @see com.fr.third.springframework.stereotype.Repository + * @see com.fr.third.springframework.dao.DataAccessException + * @see com.fr.third.springframework.dao.support.PersistenceExceptionTranslator + */ +@SuppressWarnings("serial") +public class PersistenceExceptionTranslationPostProcessor extends AbstractAdvisingBeanPostProcessor + implements BeanFactoryAware { + + private Class repositoryAnnotationType = Repository.class; + + + /** + * Set the 'repository' annotation type. + * The default repository annotation type is the {@link Repository} annotation. + *

This setter property exists so that developers can provide their own + * (non-Spring-specific) annotation type to indicate that a class has a + * repository role. + * @param repositoryAnnotationType the desired annotation type + */ + public void setRepositoryAnnotationType(Class repositoryAnnotationType) { + Assert.notNull(repositoryAnnotationType, "'repositoryAnnotationType' must not be null"); + this.repositoryAnnotationType = repositoryAnnotationType; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + if (!(beanFactory instanceof ListableBeanFactory)) { + throw new IllegalArgumentException( + "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory"); + } + this.advisor = new PersistenceExceptionTranslationAdvisor( + (ListableBeanFactory) beanFactory, this.repositoryAnnotationType); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/annotation/package-info.java b/fine-spring/src/com/fr/third/springframework/dao/annotation/package-info.java new file mode 100644 index 000000000..b3f9edecf --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/annotation/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * Annotation support for DAOs. Contains a bean post-processor for translating + * persistence exceptions based on a repository stereotype annotation. + * + */ +package com.fr.third.springframework.dao.annotation; + diff --git a/fine-spring/src/com/fr/third/springframework/dao/package-info.java b/fine-spring/src/com/fr/third/springframework/dao/package-info.java new file mode 100644 index 000000000..13b7cbe50 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/package-info.java @@ -0,0 +1,20 @@ + +/** + * + * Exception hierarchy enabling sophisticated error handling independent + * of the data access approach in use. For example, when DAOs and data + * access frameworks use the exceptions in this package (and custom + * subclasses), calling code can detect and handle common problems such + * as deadlocks without being tied to a particular data access strategy, + * such as JDBC. + * + *

All these exceptions are unchecked, meaning that calling code can + * leave them uncaught and treat all data access exceptions as fatal. + * + *

The classes in this package are discussed in Chapter 9 of + * Expert One-On-One J2EE Design and Development + * by Rod Johnson (Wrox, 2002). + * + */ +package com.fr.third.springframework.dao; + diff --git a/fine-spring/src/com/fr/third/springframework/dao/support/ChainedPersistenceExceptionTranslator.java b/fine-spring/src/com/fr/third/springframework/dao/support/ChainedPersistenceExceptionTranslator.java new file mode 100644 index 000000000..52ae95015 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/support/ChainedPersistenceExceptionTranslator.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao.support; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.springframework.dao.DataAccessException; +import com.fr.third.springframework.util.Assert; + +/** + * Implementation of {@link PersistenceExceptionTranslator} that supports chaining, + * allowing the addition of PersistenceExceptionTranslator instances in order. + * Returns {@code non-null} on the first (if any) match. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public class ChainedPersistenceExceptionTranslator implements PersistenceExceptionTranslator { + + /** List of PersistenceExceptionTranslators */ + private final List delegates = new ArrayList(4); + + + /** + * Add a PersistenceExceptionTranslator to the chained delegate list. + */ + public final void addDelegate(PersistenceExceptionTranslator pet) { + Assert.notNull(pet, "PersistenceExceptionTranslator must not be null"); + this.delegates.add(pet); + } + + /** + * Return all registered PersistenceExceptionTranslator delegates (as array). + */ + public final PersistenceExceptionTranslator[] getDelegates() { + return this.delegates.toArray(new PersistenceExceptionTranslator[this.delegates.size()]); + } + + + @Override + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + for (PersistenceExceptionTranslator pet : this.delegates) { + DataAccessException translatedDex = pet.translateExceptionIfPossible(ex); + if (translatedDex != null) { + return translatedDex; + } + } + return null; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/support/DaoSupport.java b/fine-spring/src/com/fr/third/springframework/dao/support/DaoSupport.java new file mode 100644 index 000000000..bc7e0b40c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/support/DaoSupport.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.beans.factory.BeanInitializationException; +import com.fr.third.springframework.beans.factory.InitializingBean; + +/** + * Generic base class for DAOs, defining template methods for DAO initialization. + * + *

Extended by Spring's specific DAO support classes, such as: + * JdbcDaoSupport, JdoDaoSupport, etc. + * + * @author Juergen Hoeller + * @since 1.2.2 + * @see com.fr.third.springframework.jdbc.core.support.JdbcDaoSupport + */ +public abstract class DaoSupport implements InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + + @Override + public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { + // Let abstract subclasses check their configuration. + checkDaoConfig(); + + // Let concrete implementations initialize themselves. + try { + initDao(); + } + catch (Exception ex) { + throw new BeanInitializationException("Initialization of DAO failed", ex); + } + } + + /** + * Abstract subclasses must override this to check their configuration. + *

Implementors should be marked as {@code final} if concrete subclasses + * are not supposed to override this template method themselves. + * @throws IllegalArgumentException in case of illegal configuration + */ + protected abstract void checkDaoConfig() throws IllegalArgumentException; + + /** + * Concrete subclasses can override this for custom initialization behavior. + * Gets called after population of this instance's bean properties. + * @throws Exception if DAO initialization fails + * (will be rethrown as a BeanInitializationException) + * @see com.fr.third.springframework.beans.factory.BeanInitializationException + */ + protected void initDao() throws Exception { + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/support/DataAccessUtils.java b/fine-spring/src/com/fr/third/springframework/dao/support/DataAccessUtils.java new file mode 100644 index 000000000..9d7cdf4c7 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/support/DataAccessUtils.java @@ -0,0 +1,217 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao.support; + +import java.util.Collection; + +import com.fr.third.springframework.dao.DataAccessException; +import com.fr.third.springframework.dao.EmptyResultDataAccessException; +import com.fr.third.springframework.dao.IncorrectResultSizeDataAccessException; +import com.fr.third.springframework.dao.TypeMismatchDataAccessException; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.CollectionUtils; +import com.fr.third.springframework.util.NumberUtils; + +/** + * Miscellaneous utility methods for DAO implementations. + * Useful with any data access technology. + * + * @author Juergen Hoeller + * @since 1.0.2 + */ +public abstract class DataAccessUtils { + + /** + * Return a single result object from the given Collection. + *

Returns {@code null} if 0 result objects found; + * throws an exception if more than 1 element found. + * @param results the result Collection (can be {@code null}) + * @return the single result object, or {@code null} if none + * @throws IncorrectResultSizeDataAccessException if more than one + * element has been found in the given Collection + */ + public static T singleResult(Collection results) throws IncorrectResultSizeDataAccessException { + int size = (results != null ? results.size() : 0); + if (size == 0) { + return null; + } + if (results.size() > 1) { + throw new IncorrectResultSizeDataAccessException(1, size); + } + return results.iterator().next(); + } + + /** + * Return a single result object from the given Collection. + *

Throws an exception if 0 or more than 1 element found. + * @param results the result Collection (can be {@code null}) + * @return the single result object + * @throws IncorrectResultSizeDataAccessException if more than one + * element has been found in the given Collection + * @throws EmptyResultDataAccessException if no element at all + * has been found in the given Collection + */ + public static T requiredSingleResult(Collection results) throws IncorrectResultSizeDataAccessException { + int size = (results != null ? results.size() : 0); + if (size == 0) { + throw new EmptyResultDataAccessException(1); + } + if (results.size() > 1) { + throw new IncorrectResultSizeDataAccessException(1, size); + } + return results.iterator().next(); + } + + /** + * Return a unique result object from the given Collection. + *

Returns {@code null} if 0 result objects found; + * throws an exception if more than 1 instance found. + * @param results the result Collection (can be {@code null}) + * @return the unique result object, or {@code null} if none + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @see com.fr.third.springframework.util.CollectionUtils#hasUniqueObject + */ + public static T uniqueResult(Collection results) throws IncorrectResultSizeDataAccessException { + int size = (results != null ? results.size() : 0); + if (size == 0) { + return null; + } + if (!CollectionUtils.hasUniqueObject(results)) { + throw new IncorrectResultSizeDataAccessException(1, size); + } + return results.iterator().next(); + } + + /** + * Return a unique result object from the given Collection. + *

Throws an exception if 0 or more than 1 instance found. + * @param results the result Collection (can be {@code null}) + * @return the unique result object + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @throws EmptyResultDataAccessException if no result object at all + * has been found in the given Collection + * @see com.fr.third.springframework.util.CollectionUtils#hasUniqueObject + */ + public static T requiredUniqueResult(Collection results) throws IncorrectResultSizeDataAccessException { + int size = (results != null ? results.size() : 0); + if (size == 0) { + throw new EmptyResultDataAccessException(1); + } + if (!CollectionUtils.hasUniqueObject(results)) { + throw new IncorrectResultSizeDataAccessException(1, size); + } + return results.iterator().next(); + } + + /** + * Return a unique result object from the given Collection. + * Throws an exception if 0 or more than 1 result objects found, + * of if the unique result object is not convertable to the + * specified required type. + * @param results the result Collection (can be {@code null}) + * @return the unique result object + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @throws EmptyResultDataAccessException if no result object + * at all has been found in the given Collection + * @throws TypeMismatchDataAccessException if the unique object does + * not match the specified required type + */ + @SuppressWarnings("unchecked") + public static T objectResult(Collection results, Class requiredType) + throws IncorrectResultSizeDataAccessException, TypeMismatchDataAccessException { + + Object result = requiredUniqueResult(results); + if (requiredType != null && !requiredType.isInstance(result)) { + if (String.class.equals(requiredType)) { + result = result.toString(); + } + else if (Number.class.isAssignableFrom(requiredType) && Number.class.isInstance(result)) { + try { + result = NumberUtils.convertNumberToTargetClass(((Number) result), (Class) requiredType); + } + catch (IllegalArgumentException ex) { + throw new TypeMismatchDataAccessException(ex.getMessage()); + } + } + else { + throw new TypeMismatchDataAccessException( + "Result object is of type [" + result.getClass().getName() + + "] and could not be converted to required type [" + requiredType.getName() + "]"); + } + } + return (T) result; + } + + /** + * Return a unique int result from the given Collection. + * Throws an exception if 0 or more than 1 result objects found, + * of if the unique result object is not convertable to an int. + * @param results the result Collection (can be {@code null}) + * @return the unique int result + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @throws EmptyResultDataAccessException if no result object + * at all has been found in the given Collection + * @throws TypeMismatchDataAccessException if the unique object + * in the collection is not convertable to an int + */ + public static int intResult(Collection results) + throws IncorrectResultSizeDataAccessException, TypeMismatchDataAccessException { + + return objectResult(results, Number.class).intValue(); + } + + /** + * Return a unique long result from the given Collection. + * Throws an exception if 0 or more than 1 result objects found, + * of if the unique result object is not convertable to a long. + * @param results the result Collection (can be {@code null}) + * @return the unique long result + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @throws EmptyResultDataAccessException if no result object + * at all has been found in the given Collection + * @throws TypeMismatchDataAccessException if the unique object + * in the collection is not convertable to a long + */ + public static long longResult(Collection results) + throws IncorrectResultSizeDataAccessException, TypeMismatchDataAccessException { + + return objectResult(results, Number.class).longValue(); + } + + + /** + * Return a translated exception if this is appropriate, + * otherwise return the input exception. + * @param rawException exception we may wish to translate + * @param pet PersistenceExceptionTranslator to use to perform the translation + * @return a translated exception if translation is possible, or + * the raw exception if it is not + */ + public static RuntimeException translateIfNecessary( + RuntimeException rawException, PersistenceExceptionTranslator pet) { + + Assert.notNull(pet, "PersistenceExceptionTranslator must not be null"); + DataAccessException dex = pet.translateExceptionIfPossible(rawException); + return (dex != null ? dex : rawException); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java b/fine-spring/src/com/fr/third/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java new file mode 100644 index 000000000..21bbba2ec --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java @@ -0,0 +1,171 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao.support; + +import java.util.Map; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import com.fr.third.springframework.beans.BeansException; +import com.fr.third.springframework.beans.factory.BeanFactory; +import com.fr.third.springframework.beans.factory.BeanFactoryAware; +import com.fr.third.springframework.beans.factory.BeanFactoryUtils; +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.beans.factory.ListableBeanFactory; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.ReflectionUtils; + +/** + * AOP Alliance MethodInterceptor that provides persistence exception translation + * based on a given PersistenceExceptionTranslator. + * + *

Delegates to the given {@link PersistenceExceptionTranslator} to translate + * a RuntimeException thrown into Spring's DataAccessException hierarchy + * (if appropriate). If the RuntimeException in question is declared on the + * target method, it is always propagated as-is (with no translation applied). + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see PersistenceExceptionTranslator + */ +public class PersistenceExceptionTranslationInterceptor + implements MethodInterceptor, BeanFactoryAware, InitializingBean { + + private volatile PersistenceExceptionTranslator persistenceExceptionTranslator; + + private boolean alwaysTranslate = false; + + private ListableBeanFactory beanFactory; + + + /** + * Create a new PersistenceExceptionTranslationInterceptor. + * Needs to be configured with a PersistenceExceptionTranslator afterwards. + * @see #setPersistenceExceptionTranslator + */ + public PersistenceExceptionTranslationInterceptor() { + } + + /** + * Create a new PersistenceExceptionTranslationInterceptor + * for the given PersistenceExceptionTranslator. + * @param pet the PersistenceExceptionTranslator to use + */ + public PersistenceExceptionTranslationInterceptor(PersistenceExceptionTranslator pet) { + Assert.notNull(pet, "PersistenceExceptionTranslator must not be null"); + this.persistenceExceptionTranslator = pet; + } + + /** + * Create a new PersistenceExceptionTranslationInterceptor, autodetecting + * PersistenceExceptionTranslators in the given BeanFactory. + * @param beanFactory the ListableBeanFactory to obtaining all + * PersistenceExceptionTranslators from + */ + public PersistenceExceptionTranslationInterceptor(ListableBeanFactory beanFactory) { + Assert.notNull(beanFactory, "ListableBeanFactory must not be null"); + this.beanFactory = beanFactory; + } + + + /** + * Specify the PersistenceExceptionTranslator to use. + *

Default is to autodetect all PersistenceExceptionTranslators + * in the containing BeanFactory, using them in a chain. + * @see #detectPersistenceExceptionTranslators + */ + public void setPersistenceExceptionTranslator(PersistenceExceptionTranslator pet) { + this.persistenceExceptionTranslator = pet; + } + + /** + * Specify whether to always translate the exception ("true"), or whether throw the + * raw exception when declared, i.e. when the originating method signature's exception + * declarations allow for the raw exception to be thrown ("false"). + *

Default is "false". Switch this flag to "true" in order to always translate + * applicable exceptions, independent from the originating method signature. + *

Note that the originating method does not have to declare the specific exception. + * Any base class will do as well, even {@code throws Exception}: As long as the + * originating method does explicitly declare compatible exceptions, the raw exception + * will be rethrown. If you would like to avoid throwing raw exceptions in any case, + * switch this flag to "true". + */ + public void setAlwaysTranslate(boolean alwaysTranslate) { + this.alwaysTranslate = alwaysTranslate; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (this.persistenceExceptionTranslator == null) { + // No explicit exception translator specified - perform autodetection. + if (!(beanFactory instanceof ListableBeanFactory)) { + throw new IllegalArgumentException( + "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory"); + } + this.beanFactory = (ListableBeanFactory) beanFactory; + } + } + + @Override + public void afterPropertiesSet() { + if (this.persistenceExceptionTranslator == null && this.beanFactory == null) { + throw new IllegalArgumentException("Property 'persistenceExceptionTranslator' is required"); + } + } + + + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + try { + return mi.proceed(); + } + catch (RuntimeException ex) { + // Let it throw raw if the type of the exception is on the throws clause of the method. + if (!this.alwaysTranslate && ReflectionUtils.declaresException(mi.getMethod(), ex.getClass())) { + throw ex; + } + else { + if (this.persistenceExceptionTranslator == null) { + this.persistenceExceptionTranslator = detectPersistenceExceptionTranslators(this.beanFactory); + } + throw DataAccessUtils.translateIfNecessary(ex, this.persistenceExceptionTranslator); + } + } + } + + /** + * Detect all PersistenceExceptionTranslators in the given BeanFactory. + * @param beanFactory the ListableBeanFactory to obtaining all + * PersistenceExceptionTranslators from + * @return a chained PersistenceExceptionTranslator, combining all + * PersistenceExceptionTranslators found in the factory + * @see ChainedPersistenceExceptionTranslator + */ + protected PersistenceExceptionTranslator detectPersistenceExceptionTranslators(ListableBeanFactory beanFactory) { + // Find all translators, being careful not to activate FactoryBeans. + Map pets = BeanFactoryUtils.beansOfTypeIncludingAncestors( + beanFactory, PersistenceExceptionTranslator.class, false, false); + ChainedPersistenceExceptionTranslator cpet = new ChainedPersistenceExceptionTranslator(); + for (PersistenceExceptionTranslator pet : pets.values()) { + cpet.addDelegate(pet); + } + return cpet; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/support/PersistenceExceptionTranslator.java b/fine-spring/src/com/fr/third/springframework/dao/support/PersistenceExceptionTranslator.java new file mode 100644 index 000000000..a7e3efec2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/support/PersistenceExceptionTranslator.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.dao.support; + +import com.fr.third.springframework.dao.DataAccessException; + +/** + * Interface implemented by Spring integrations with data access technologies + * that throw runtime exceptions, such as JPA, TopLink, JDO and Hibernate. + * + *

This allows consistent usage of combined exception translation functionality, + * without forcing a single translator to understand every single possible type + * of exception. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public interface PersistenceExceptionTranslator { + + /** + * Translate the given runtime exception thrown by a persistence framework to a + * corresponding exception from Spring's generic DataAccessException hierarchy, + * if possible. + *

Do not translate exceptions that are not understand by this translator: + * for example, if coming from another persistence framework, or resulting + * from user code and unrelated to persistence. + *

Of particular importance is the correct translation to + * DataIntegrityViolationException, for example on constraint violation. + * Implementations may use Spring JDBC's sophisticated exception translation + * to provide further information in the event of SQLException as a root cause. + * @param ex a RuntimeException thrown + * @return the corresponding DataAccessException (or {@code null} if the + * exception could not be translated, as in this case it may result from + * user code rather than an actual persistence problem) + * @see com.fr.third.springframework.dao.DataIntegrityViolationException + * @see com.fr.third.springframework.jdbc.support.SQLExceptionTranslator + */ + DataAccessException translateExceptionIfPossible(RuntimeException ex); + +} diff --git a/fine-spring/src/com/fr/third/springframework/dao/support/package-info.java b/fine-spring/src/com/fr/third/springframework/dao/support/package-info.java new file mode 100644 index 000000000..5940dd4a9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/dao/support/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * Support classes for DAO implementations, + * providing miscellaneous utility methods. + * + */ +package com.fr.third.springframework.dao.support; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/CannotCreateRecordException.java b/fine-spring/src/com/fr/third/springframework/jca/cci/CannotCreateRecordException.java new file mode 100644 index 000000000..287fb9679 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/CannotCreateRecordException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci; + +import javax.resource.ResourceException; + +import com.fr.third.springframework.dao.DataAccessResourceFailureException; + +/** + * Exception thrown when the creating of a CCI Record failed + * for connector-internal reasons. + * + * @author Juergen Hoeller + * @since 1.2 + */ +@SuppressWarnings("serial") +public class CannotCreateRecordException extends DataAccessResourceFailureException { + + /** + * Constructor for CannotCreateRecordException. + * @param msg message + * @param ex ResourceException root cause + */ + public CannotCreateRecordException(String msg, ResourceException ex) { + super(msg, ex); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/CannotGetCciConnectionException.java b/fine-spring/src/com/fr/third/springframework/jca/cci/CannotGetCciConnectionException.java new file mode 100644 index 000000000..96fcd2468 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/CannotGetCciConnectionException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci; + +import javax.resource.ResourceException; + +import com.fr.third.springframework.dao.DataAccessResourceFailureException; + +/** + * Fatal exception thrown when we can't connect to an EIS using CCI. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + */ +@SuppressWarnings("serial") +public class CannotGetCciConnectionException extends DataAccessResourceFailureException { + + /** + * Constructor for CannotGetCciConnectionException. + * @param msg message + * @param ex ResourceException root cause + */ + public CannotGetCciConnectionException(String msg, ResourceException ex) { + super(msg, ex); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/CciOperationNotSupportedException.java b/fine-spring/src/com/fr/third/springframework/jca/cci/CciOperationNotSupportedException.java new file mode 100644 index 000000000..84a660093 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/CciOperationNotSupportedException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci; + +import javax.resource.ResourceException; + +import com.fr.third.springframework.dao.InvalidDataAccessResourceUsageException; + +/** + * Exception thrown when the connector doesn't support a specific CCI operation. + * + * @author Juergen Hoeller + * @since 1.2 + */ +@SuppressWarnings("serial") +public class CciOperationNotSupportedException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for CciOperationNotSupportedException. + * @param msg message + * @param ex ResourceException root cause + */ + public CciOperationNotSupportedException(String msg, ResourceException ex) { + super(msg, ex); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/InvalidResultSetAccessException.java b/fine-spring/src/com/fr/third/springframework/jca/cci/InvalidResultSetAccessException.java new file mode 100644 index 000000000..044c7c8f5 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/InvalidResultSetAccessException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci; + +import java.sql.SQLException; + +import com.fr.third.springframework.dao.InvalidDataAccessResourceUsageException; + +/** + * Exception thrown when a ResultSet has been accessed in an invalid fashion. + * Such exceptions always have a {@code java.sql.SQLException} root cause. + * + *

This typically happens when an invalid ResultSet column index or name + * has been specified. + * + * @author Juergen Hoeller + * @since 1.2 + * @see javax.resource.cci.ResultSet + */ +@SuppressWarnings("serial") +public class InvalidResultSetAccessException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for InvalidResultSetAccessException. + * @param msg message + * @param ex the root cause + */ + public InvalidResultSetAccessException(String msg, SQLException ex) { + super(ex.getMessage(), ex); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/RecordTypeNotSupportedException.java b/fine-spring/src/com/fr/third/springframework/jca/cci/RecordTypeNotSupportedException.java new file mode 100644 index 000000000..eb90de2dd --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/RecordTypeNotSupportedException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci; + +import javax.resource.ResourceException; + +import com.fr.third.springframework.dao.InvalidDataAccessResourceUsageException; + +/** + * Exception thrown when the creating of a CCI Record failed because + * the connector doesn't support the desired CCI Record type. + * + * @author Juergen Hoeller + * @since 1.2 + */ +@SuppressWarnings("serial") +public class RecordTypeNotSupportedException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for RecordTypeNotSupportedException. + * @param msg message + * @param ex ResourceException root cause + */ + public RecordTypeNotSupportedException(String msg, ResourceException ex) { + super(msg, ex); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/connection/CciLocalTransactionManager.java b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/CciLocalTransactionManager.java new file mode 100644 index 000000000..77bd729e6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/CciLocalTransactionManager.java @@ -0,0 +1,274 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.connection; + +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.spi.LocalTransactionException; + +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.transaction.CannotCreateTransactionException; +import com.fr.third.springframework.transaction.TransactionDefinition; +import com.fr.third.springframework.transaction.TransactionException; +import com.fr.third.springframework.transaction.TransactionSystemException; +import com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager; +import com.fr.third.springframework.transaction.support.DefaultTransactionStatus; +import com.fr.third.springframework.transaction.support.ResourceTransactionManager; +import com.fr.third.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * {@link com.fr.third.springframework.transaction.PlatformTransactionManager} implementation + * that manages local transactions for a single CCI ConnectionFactory. + * Binds a CCI Connection from the specified ConnectionFactory to the thread, + * potentially allowing for one thread-bound Connection per ConnectionFactory. + * + *

Application code is required to retrieve the CCI Connection via + * {@link ConnectionFactoryUtils#getConnection(ConnectionFactory)} instead of a standard + * J2EE-style {@link ConnectionFactory#getConnection()} call. Spring classes such as + * {@link com.fr.third.springframework.jca.cci.core.CciTemplate} use this strategy implicitly. + * If not used in combination with this transaction manager, the + * {@link ConnectionFactoryUtils} lookup strategy behaves exactly like the native + * DataSource lookup; it can thus be used in a portable fashion. + * + *

Alternatively, you can allow application code to work with the standard + * J2EE lookup pattern {@link ConnectionFactory#getConnection()}, for example + * for legacy code that is not aware of Spring at all. In that case, define a + * {@link TransactionAwareConnectionFactoryProxy} for your target ConnectionFactory, + * which will automatically participate in Spring-managed transactions. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see ConnectionFactoryUtils#getConnection(javax.resource.cci.ConnectionFactory) + * @see ConnectionFactoryUtils#releaseConnection + * @see TransactionAwareConnectionFactoryProxy + * @see com.fr.third.springframework.jca.cci.core.CciTemplate + */ +@SuppressWarnings("serial") +public class CciLocalTransactionManager extends AbstractPlatformTransactionManager + implements ResourceTransactionManager, InitializingBean { + + private ConnectionFactory connectionFactory; + + + /** + * Create a new CciLocalTransactionManager instance. + * A ConnectionFactory has to be set to be able to use it. + * @see #setConnectionFactory + */ + public CciLocalTransactionManager() { + } + + /** + * Create a new CciLocalTransactionManager instance. + * @param connectionFactory CCI ConnectionFactory to manage local transactions for + */ + public CciLocalTransactionManager(ConnectionFactory connectionFactory) { + setConnectionFactory(connectionFactory); + afterPropertiesSet(); + } + + + /** + * Set the CCI ConnectionFactory that this instance should manage local + * transactions for. + */ + public void setConnectionFactory(ConnectionFactory cf) { + if (cf instanceof TransactionAwareConnectionFactoryProxy) { + // If we got a TransactionAwareConnectionFactoryProxy, we need to perform transactions + // for its underlying target ConnectionFactory, else JMS access code won't see + // properly exposed transactions (i.e. transactions for the target ConnectionFactory). + this.connectionFactory = ((TransactionAwareConnectionFactoryProxy) cf).getTargetConnectionFactory(); + } + else { + this.connectionFactory = cf; + } + } + + /** + * Return the CCI ConnectionFactory that this instance manages local + * transactions for. + */ + public ConnectionFactory getConnectionFactory() { + return this.connectionFactory; + } + + @Override + public void afterPropertiesSet() { + if (getConnectionFactory() == null) { + throw new IllegalArgumentException("Property 'connectionFactory' is required"); + } + } + + + @Override + public Object getResourceFactory() { + return getConnectionFactory(); + } + + @Override + protected Object doGetTransaction() { + CciLocalTransactionObject txObject = new CciLocalTransactionObject(); + ConnectionHolder conHolder = + (ConnectionHolder) TransactionSynchronizationManager.getResource(getConnectionFactory()); + txObject.setConnectionHolder(conHolder); + return txObject; + } + + @Override + protected boolean isExistingTransaction(Object transaction) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + // Consider a pre-bound connection as transaction. + return (txObject.getConnectionHolder() != null); + } + + @Override + protected void doBegin(Object transaction, TransactionDefinition definition) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + Connection con = null; + + try { + con = getConnectionFactory().getConnection(); + if (logger.isDebugEnabled()) { + logger.debug("Acquired Connection [" + con + "] for local CCI transaction"); + } + + txObject.setConnectionHolder(new ConnectionHolder(con)); + txObject.getConnectionHolder().setSynchronizedWithTransaction(true); + + con.getLocalTransaction().begin(); + int timeout = determineTimeout(definition); + if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { + txObject.getConnectionHolder().setTimeoutInSeconds(timeout); + } + TransactionSynchronizationManager.bindResource(getConnectionFactory(), txObject.getConnectionHolder()); + } + catch (NotSupportedException ex) { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + throw new CannotCreateTransactionException("CCI Connection does not support local transactions", ex); + } + catch (LocalTransactionException ex) { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + throw new CannotCreateTransactionException("Could not begin local CCI transaction", ex); + } + catch (Throwable ex) { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + throw new TransactionSystemException("Unexpected failure on begin of CCI local transaction", ex); + } + } + + @Override + protected Object doSuspend(Object transaction) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + txObject.setConnectionHolder(null); + return TransactionSynchronizationManager.unbindResource(getConnectionFactory()); + } + + @Override + protected void doResume(Object transaction, Object suspendedResources) { + ConnectionHolder conHolder = (ConnectionHolder) suspendedResources; + TransactionSynchronizationManager.bindResource(getConnectionFactory(), conHolder); + } + + protected boolean isRollbackOnly(Object transaction) throws TransactionException { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + return txObject.getConnectionHolder().isRollbackOnly(); + } + + @Override + protected void doCommit(DefaultTransactionStatus status) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction(); + Connection con = txObject.getConnectionHolder().getConnection(); + if (status.isDebug()) { + logger.debug("Committing CCI local transaction on Connection [" + con + "]"); + } + try { + con.getLocalTransaction().commit(); + } + catch (LocalTransactionException ex) { + throw new TransactionSystemException("Could not commit CCI local transaction", ex); + } + catch (ResourceException ex) { + throw new TransactionSystemException("Unexpected failure on commit of CCI local transaction", ex); + } + } + + @Override + protected void doRollback(DefaultTransactionStatus status) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction(); + Connection con = txObject.getConnectionHolder().getConnection(); + if (status.isDebug()) { + logger.debug("Rolling back CCI local transaction on Connection [" + con + "]"); + } + try { + con.getLocalTransaction().rollback(); + } + catch (LocalTransactionException ex) { + throw new TransactionSystemException("Could not roll back CCI local transaction", ex); + } + catch (ResourceException ex) { + throw new TransactionSystemException("Unexpected failure on rollback of CCI local transaction", ex); + } + } + + @Override + protected void doSetRollbackOnly(DefaultTransactionStatus status) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Setting CCI local transaction [" + txObject.getConnectionHolder().getConnection() + + "] rollback-only"); + } + txObject.getConnectionHolder().setRollbackOnly(); + } + + @Override + protected void doCleanupAfterCompletion(Object transaction) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + + // Remove the connection holder from the thread. + TransactionSynchronizationManager.unbindResource(getConnectionFactory()); + txObject.getConnectionHolder().clear(); + + Connection con = txObject.getConnectionHolder().getConnection(); + if (logger.isDebugEnabled()) { + logger.debug("Releasing CCI Connection [" + con + "] after transaction"); + } + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + } + + + /** + * CCI local transaction object, representing a ConnectionHolder. + * Used as transaction object by CciLocalTransactionManager. + * @see ConnectionHolder + */ + private static class CciLocalTransactionObject { + + private ConnectionHolder connectionHolder; + + public void setConnectionHolder(ConnectionHolder connectionHolder) { + this.connectionHolder = connectionHolder; + } + + public ConnectionHolder getConnectionHolder() { + return this.connectionHolder; + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/connection/ConnectionFactoryUtils.java b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/ConnectionFactoryUtils.java new file mode 100644 index 000000000..f3c746f5c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/ConnectionFactoryUtils.java @@ -0,0 +1,214 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.connection; + +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.jca.cci.CannotGetCciConnectionException; +import com.fr.third.springframework.transaction.support.ResourceHolderSynchronization; +import com.fr.third.springframework.transaction.support.TransactionSynchronizationManager; +import com.fr.third.springframework.util.Assert; + +/** + * Helper class that provides static methods for obtaining CCI Connections + * from a {@link javax.resource.cci.ConnectionFactory}. Includes special + * support for Spring-managed transactional Connections, e.g. managed + * by {@link CciLocalTransactionManager} or + * {@link com.fr.third.springframework.transaction.jta.JtaTransactionManager}. + * + *

Used internally by {@link com.fr.third.springframework.jca.cci.core.CciTemplate}, + * Spring's CCI operation objects and the {@link CciLocalTransactionManager}. + * Can also be used directly in application code. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see #getConnection + * @see #releaseConnection + * @see CciLocalTransactionManager + * @see com.fr.third.springframework.transaction.jta.JtaTransactionManager + * @see com.fr.third.springframework.transaction.support.TransactionSynchronizationManager + */ +public abstract class ConnectionFactoryUtils { + + private static final Log logger = LogFactory.getLog(ConnectionFactoryUtils.class); + + + /** + * Obtain a Connection from the given ConnectionFactory. Translates ResourceExceptions + * into the Spring hierarchy of unchecked generic data access exceptions, simplifying + * calling code and making any exception that is thrown more meaningful. + *

Is aware of a corresponding Connection bound to the current thread, for example + * when using {@link CciLocalTransactionManager}. Will bind a Connection to the thread + * if transaction synchronization is active (e.g. if in a JTA transaction). + * @param cf the ConnectionFactory to obtain Connection from + * @return a CCI Connection from the given ConnectionFactory + * @throws com.fr.third.springframework.jca.cci.CannotGetCciConnectionException + * if the attempt to get a Connection failed + * @see #releaseConnection + */ + public static Connection getConnection(ConnectionFactory cf) throws CannotGetCciConnectionException { + return getConnection(cf, null); + } + + /** + * Obtain a Connection from the given ConnectionFactory. Translates ResourceExceptions + * into the Spring hierarchy of unchecked generic data access exceptions, simplifying + * calling code and making any exception that is thrown more meaningful. + *

Is aware of a corresponding Connection bound to the current thread, for example + * when using {@link CciLocalTransactionManager}. Will bind a Connection to the thread + * if transaction synchronization is active (e.g. if in a JTA transaction). + * @param cf the ConnectionFactory to obtain Connection from + * @param spec the ConnectionSpec for the desired Connection (may be {@code null}). + * Note: If this is specified, a new Connection will be obtained for every call, + * without participating in a shared transactional Connection. + * @return a CCI Connection from the given ConnectionFactory + * @throws com.fr.third.springframework.jca.cci.CannotGetCciConnectionException + * if the attempt to get a Connection failed + * @see #releaseConnection + */ + public static Connection getConnection(ConnectionFactory cf, ConnectionSpec spec) + throws CannotGetCciConnectionException { + try { + if (spec != null) { + Assert.notNull(cf, "No ConnectionFactory specified"); + return cf.getConnection(spec); + } + else { + return doGetConnection(cf); + } + } + catch (ResourceException ex) { + throw new CannotGetCciConnectionException("Could not get CCI Connection", ex); + } + } + + /** + * Actually obtain a CCI Connection from the given ConnectionFactory. + * Same as {@link #getConnection}, but throwing the original ResourceException. + *

Is aware of a corresponding Connection bound to the current thread, for example + * when using {@link CciLocalTransactionManager}. Will bind a Connection to the thread + * if transaction synchronization is active (e.g. if in a JTA transaction). + *

Directly accessed by {@link TransactionAwareConnectionFactoryProxy}. + * @param cf the ConnectionFactory to obtain Connection from + * @return a CCI Connection from the given ConnectionFactory + * @throws ResourceException if thrown by CCI API methods + * @see #doReleaseConnection + */ + public static Connection doGetConnection(ConnectionFactory cf) throws ResourceException { + Assert.notNull(cf, "No ConnectionFactory specified"); + + ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(cf); + if (conHolder != null) { + return conHolder.getConnection(); + } + + logger.debug("Opening CCI Connection"); + Connection con = cf.getConnection(); + + if (TransactionSynchronizationManager.isSynchronizationActive()) { + logger.debug("Registering transaction synchronization for CCI Connection"); + conHolder = new ConnectionHolder(con); + conHolder.setSynchronizedWithTransaction(true); + TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(conHolder, cf)); + TransactionSynchronizationManager.bindResource(cf, conHolder); + } + + return con; + } + + /** + * Determine whether the given JCA CCI Connection is transactional, that is, + * bound to the current thread by Spring's transaction facilities. + * @param con the Connection to check + * @param cf the ConnectionFactory that the Connection was obtained from + * (may be {@code null}) + * @return whether the Connection is transactional + */ + public static boolean isConnectionTransactional(Connection con, ConnectionFactory cf) { + if (cf == null) { + return false; + } + ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(cf); + return (conHolder != null && conHolder.getConnection() == con); + } + + /** + * Close the given Connection, obtained from the given ConnectionFactory, + * if it is not managed externally (that is, not bound to the thread). + * @param con the Connection to close if necessary + * (if this is {@code null}, the call will be ignored) + * @param cf the ConnectionFactory that the Connection was obtained from + * (can be {@code null}) + * @see #getConnection + */ + public static void releaseConnection(Connection con, ConnectionFactory cf) { + try { + doReleaseConnection(con, cf); + } + catch (ResourceException ex) { + logger.debug("Could not close CCI Connection", ex); + } + catch (Throwable ex) { + // We don't trust the CCI driver: It might throw RuntimeException or Error. + logger.debug("Unexpected exception on closing CCI Connection", ex); + } + } + + /** + * Actually close the given Connection, obtained from the given ConnectionFactory. + * Same as {@link #releaseConnection}, but throwing the original ResourceException. + *

Directly accessed by {@link TransactionAwareConnectionFactoryProxy}. + * @param con the Connection to close if necessary + * (if this is {@code null}, the call will be ignored) + * @param cf the ConnectionFactory that the Connection was obtained from + * (can be {@code null}) + * @throws ResourceException if thrown by JCA CCI methods + * @see #doGetConnection + */ + public static void doReleaseConnection(Connection con, ConnectionFactory cf) throws ResourceException { + if (con == null || isConnectionTransactional(con, cf)) { + return; + } + con.close(); + } + + + /** + * Callback for resource cleanup at the end of a non-native CCI transaction + * (e.g. when participating in a JTA transaction). + */ + private static class ConnectionSynchronization + extends ResourceHolderSynchronization { + + public ConnectionSynchronization(ConnectionHolder connectionHolder, ConnectionFactory connectionFactory) { + super(connectionHolder, connectionFactory); + } + + @Override + protected void releaseResource(ConnectionHolder resourceHolder, ConnectionFactory resourceKey) { + releaseConnection(resourceHolder.getConnection(), resourceKey); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/connection/ConnectionHolder.java b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/ConnectionHolder.java new file mode 100644 index 000000000..f6d801c0a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/ConnectionHolder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.connection; + +import javax.resource.cci.Connection; + +import com.fr.third.springframework.transaction.support.ResourceHolderSupport; + +/** + * Connection holder, wrapping a CCI Connection. + * + *

CciLocalTransactionManager binds instances of this class + * to the thread, for a given ConnectionFactory. + * + *

Note: This is an SPI class, not intended to be used by applications. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciLocalTransactionManager + * @see ConnectionFactoryUtils + */ +public class ConnectionHolder extends ResourceHolderSupport { + + private final Connection connection; + + public ConnectionHolder(Connection connection) { + this.connection = connection; + } + + public Connection getConnection() { + return this.connection; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/connection/ConnectionSpecConnectionFactoryAdapter.java b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/ConnectionSpecConnectionFactoryAdapter.java new file mode 100644 index 000000000..788492954 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/ConnectionSpecConnectionFactoryAdapter.java @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.connection; + +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionSpec; + +import com.fr.third.springframework.core.NamedThreadLocal; + +/** + * An adapter for a target CCI {@link javax.resource.cci.ConnectionFactory}, + * applying the given ConnectionSpec to every standard {@code getConnection()} + * call, that is, implicitly invoking {@code getConnection(ConnectionSpec)} + * on the target. All other methods simply delegate to the corresponding methods + * of the target ConnectionFactory. + * + *

Can be used to proxy a target JNDI ConnectionFactory that does not have a + * ConnectionSpec configured. Client code can work with the ConnectionFactory + * without passing in a ConnectionSpec on every {@code getConnection()} call. + * + *

In the following example, client code can simply transparently work with + * the preconfigured "myConnectionFactory", implicitly accessing + * "myTargetConnectionFactory" with the specified user credentials. + * + *

+ * <bean id="myTargetConnectionFactory" class="com.fr.third.springframework.jndi.JndiObjectFactoryBean">
+ *   <property name="jndiName" value="java:comp/env/cci/mycf"/>
+ * </bean>
+ *
+ * <bean id="myConnectionFactory" class="com.fr.third.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
+ *   <property name="targetConnectionFactory" ref="myTargetConnectionFactory"/>
+ *   <property name="connectionSpec">
+ *     <bean class="your.resource.adapter.ConnectionSpecImpl">
+ *       <property name="username" value="myusername"/>
+ *       <property name="password" value="mypassword"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ * + *

If the "connectionSpec" is empty, this proxy will simply delegate to the + * standard {@code getConnection()} method of the target ConnectionFactory. + * This can be used to keep a UserCredentialsConnectionFactoryAdapter bean definition + * just for the option of implicitly passing in a ConnectionSpec if the + * particular target ConnectionFactory requires it. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #getConnection + */ +@SuppressWarnings("serial") +public class ConnectionSpecConnectionFactoryAdapter extends DelegatingConnectionFactory { + + private ConnectionSpec connectionSpec; + + private final ThreadLocal threadBoundSpec = + new NamedThreadLocal("Current CCI ConnectionSpec"); + + + /** + * Set the ConnectionSpec that this adapter should use for retrieving Connections. + * Default is none. + */ + public void setConnectionSpec(ConnectionSpec connectionSpec) { + this.connectionSpec = connectionSpec; + } + + /** + * Set a ConnectionSpec for this proxy and the current thread. + * The given ConnectionSpec will be applied to all subsequent + * {@code getConnection()} calls on this ConnectionFactory proxy. + *

This will override any statically specified "connectionSpec" property. + * @param spec the ConnectionSpec to apply + * @see #removeConnectionSpecFromCurrentThread + */ + public void setConnectionSpecForCurrentThread(ConnectionSpec spec) { + this.threadBoundSpec.set(spec); + } + + /** + * Remove any ConnectionSpec for this proxy from the current thread. + * A statically specified ConnectionSpec applies again afterwards. + * @see #setConnectionSpecForCurrentThread + */ + public void removeConnectionSpecFromCurrentThread() { + this.threadBoundSpec.remove(); + } + + + /** + * Determine whether there is currently a thread-bound ConnectionSpec, + * using it if available, falling back to the statically specified + * "connectionSpec" property else. + * @see #doGetConnection + */ + @Override + public final Connection getConnection() throws ResourceException { + ConnectionSpec threadSpec = this.threadBoundSpec.get(); + if (threadSpec != null) { + return doGetConnection(threadSpec); + } + else { + return doGetConnection(this.connectionSpec); + } + } + + /** + * This implementation delegates to the {@code getConnection(ConnectionSpec)} + * method of the target ConnectionFactory, passing in the specified user credentials. + * If the specified username is empty, it will simply delegate to the standard + * {@code getConnection()} method of the target ConnectionFactory. + * @param spec the ConnectionSpec to apply + * @return the Connection + * @see javax.resource.cci.ConnectionFactory#getConnection(javax.resource.cci.ConnectionSpec) + * @see javax.resource.cci.ConnectionFactory#getConnection() + */ + protected Connection doGetConnection(ConnectionSpec spec) throws ResourceException { + if (getTargetConnectionFactory() == null) { + throw new IllegalStateException("targetConnectionFactory is required"); + } + if (spec != null) { + return getTargetConnectionFactory().getConnection(spec); + } + else { + return getTargetConnectionFactory().getConnection(); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/connection/DelegatingConnectionFactory.java b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/DelegatingConnectionFactory.java new file mode 100644 index 000000000..8e97db872 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/DelegatingConnectionFactory.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.connection; + +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; +import javax.resource.cci.RecordFactory; +import javax.resource.cci.ResourceAdapterMetaData; + +import com.fr.third.springframework.beans.factory.InitializingBean; + +/** + * CCI {@link ConnectionFactory} implementation that delegates all calls + * to a given target {@link ConnectionFactory}. + * + *

This class is meant to be subclassed, with subclasses overriding only + * those methods (such as {@link #getConnection()}) that should not simply + * delegate to the target {@link ConnectionFactory}. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #getConnection + */ +@SuppressWarnings("serial") +public class DelegatingConnectionFactory implements ConnectionFactory, InitializingBean { + + private ConnectionFactory targetConnectionFactory; + + + /** + * Set the target ConnectionFactory that this ConnectionFactory should delegate to. + */ + public void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) { + this.targetConnectionFactory = targetConnectionFactory; + } + + /** + * Return the target ConnectionFactory that this ConnectionFactory should delegate to. + */ + public ConnectionFactory getTargetConnectionFactory() { + return this.targetConnectionFactory; + } + + + @Override + public void afterPropertiesSet() { + if (getTargetConnectionFactory() == null) { + throw new IllegalArgumentException("Property 'targetConnectionFactory' is required"); + } + } + + + @Override + public Connection getConnection() throws ResourceException { + return getTargetConnectionFactory().getConnection(); + } + + @Override + public Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException { + return getTargetConnectionFactory().getConnection(connectionSpec); + } + + @Override + public RecordFactory getRecordFactory() throws ResourceException { + return getTargetConnectionFactory().getRecordFactory(); + } + + @Override + public ResourceAdapterMetaData getMetaData() throws ResourceException { + return getTargetConnectionFactory().getMetaData(); + } + + @Override + public Reference getReference() throws NamingException { + return getTargetConnectionFactory().getReference(); + } + + @Override + public void setReference(Reference reference) { + getTargetConnectionFactory().setReference(reference); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/connection/NotSupportedRecordFactory.java b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/NotSupportedRecordFactory.java new file mode 100644 index 000000000..6c74809fe --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/NotSupportedRecordFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.connection; + +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.cci.IndexedRecord; +import javax.resource.cci.MappedRecord; +import javax.resource.cci.RecordFactory; + +/** + * Implementation of the CCI RecordFactory interface that always throws + * NotSupportedException. + * + *

Useful as a placeholder for a RecordFactory argument (for example as + * defined by the RecordCreator callback), in particular when the connector's + * {@code ConnectionFactory.getRecordFactory()} implementation happens to + * throw NotSupportedException early rather than throwing the exception from + * RecordFactory's methods. + * + * @author Juergen Hoeller + * @since 1.2.4 + * @see com.fr.third.springframework.jca.cci.core.RecordCreator#createRecord(javax.resource.cci.RecordFactory) + * @see com.fr.third.springframework.jca.cci.core.CciTemplate#getRecordFactory(javax.resource.cci.ConnectionFactory) + * @see javax.resource.cci.ConnectionFactory#getRecordFactory() + * @see javax.resource.NotSupportedException + */ +public class NotSupportedRecordFactory implements RecordFactory { + + @Override + public MappedRecord createMappedRecord(String name) throws ResourceException { + throw new NotSupportedException("The RecordFactory facility is not supported by the connector"); + } + + @Override + public IndexedRecord createIndexedRecord(String name) throws ResourceException { + throw new NotSupportedException("The RecordFactory facility is not supported by the connector"); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/connection/SingleConnectionFactory.java b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/SingleConnectionFactory.java new file mode 100644 index 000000000..b64633883 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/SingleConnectionFactory.java @@ -0,0 +1,255 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.connection; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.beans.factory.DisposableBean; +import com.fr.third.springframework.util.Assert; + +/** + * A CCI ConnectionFactory adapter that returns the same Connection on all + * {@code getConnection} calls, and ignores calls to + * {@code Connection.close()}. + * + *

Useful for testing and standalone environments, to keep using the same + * Connection for multiple CciTemplate calls, without having a pooling + * ConnectionFactory, also spanning any number of transactions. + * + *

You can either pass in a CCI Connection directly, or let this + * factory lazily create a Connection via a given target ConnectionFactory. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #getConnection() + * @see javax.resource.cci.Connection#close() + * @see com.fr.third.springframework.jca.cci.core.CciTemplate + */ +@SuppressWarnings("serial") +public class SingleConnectionFactory extends DelegatingConnectionFactory implements DisposableBean { + + protected final Log logger = LogFactory.getLog(getClass()); + + /** Wrapped Connection */ + private Connection target; + + /** Proxy Connection */ + private Connection connection; + + /** Synchronization monitor for the shared Connection */ + private final Object connectionMonitor = new Object(); + + + /** + * Create a new SingleConnectionFactory for bean-style usage. + * @see #setTargetConnectionFactory + */ + public SingleConnectionFactory() { + } + + /** + * Create a new SingleConnectionFactory that always returns the + * given Connection. + * @param target the single Connection + */ + public SingleConnectionFactory(Connection target) { + Assert.notNull(target, "Target Connection must not be null"); + this.target = target; + this.connection = getCloseSuppressingConnectionProxy(target); + } + + /** + * Create a new SingleConnectionFactory that always returns a single + * Connection which it will lazily create via the given target + * ConnectionFactory. + * @param targetConnectionFactory the target ConnectionFactory + */ + public SingleConnectionFactory(ConnectionFactory targetConnectionFactory) { + Assert.notNull(targetConnectionFactory, "Target ConnectionFactory must not be null"); + setTargetConnectionFactory(targetConnectionFactory); + } + + + /** + * Make sure a Connection or ConnectionFactory has been set. + */ + @Override + public void afterPropertiesSet() { + if (this.connection == null && getTargetConnectionFactory() == null) { + throw new IllegalArgumentException("Connection or 'targetConnectionFactory' is required"); + } + } + + + @Override + public Connection getConnection() throws ResourceException { + synchronized (this.connectionMonitor) { + if (this.connection == null) { + initConnection(); + } + return this.connection; + } + } + + @Override + public Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException { + throw new NotSupportedException( + "SingleConnectionFactory does not support custom ConnectionSpec"); + } + + /** + * Close the underlying Connection. + * The provider of this ConnectionFactory needs to care for proper shutdown. + *

As this bean implements DisposableBean, a bean factory will + * automatically invoke this on destruction of its cached singletons. + */ + @Override + public void destroy() { + resetConnection(); + } + + + /** + * Initialize the single underlying Connection. + *

Closes and reinitializes the Connection if an underlying + * Connection is present already. + * @throws javax.resource.ResourceException if thrown by CCI API methods + */ + public void initConnection() throws ResourceException { + if (getTargetConnectionFactory() == null) { + throw new IllegalStateException( + "'targetConnectionFactory' is required for lazily initializing a Connection"); + } + synchronized (this.connectionMonitor) { + if (this.target != null) { + closeConnection(this.target); + } + this.target = doCreateConnection(); + prepareConnection(this.target); + if (logger.isInfoEnabled()) { + logger.info("Established shared CCI Connection: " + this.target); + } + this.connection = getCloseSuppressingConnectionProxy(this.target); + } + } + + /** + * Reset the underlying shared Connection, to be reinitialized on next access. + */ + public void resetConnection() { + synchronized (this.connectionMonitor) { + if (this.target != null) { + closeConnection(this.target); + } + this.target = null; + this.connection = null; + } + } + + /** + * Create a CCI Connection via this template's ConnectionFactory. + * @return the new CCI Connection + * @throws javax.resource.ResourceException if thrown by CCI API methods + */ + protected Connection doCreateConnection() throws ResourceException { + return getTargetConnectionFactory().getConnection(); + } + + /** + * Prepare the given Connection before it is exposed. + *

The default implementation is empty. Can be overridden in subclasses. + * @param con the Connection to prepare + */ + protected void prepareConnection(Connection con) throws ResourceException { + } + + /** + * Close the given Connection. + * @param con the Connection to close + */ + protected void closeConnection(Connection con) { + try { + con.close(); + } + catch (Throwable ex) { + logger.warn("Could not close shared CCI Connection", ex); + } + } + + /** + * Wrap the given Connection with a proxy that delegates every method call to it + * but suppresses close calls. This is useful for allowing application code to + * handle a special framework Connection just like an ordinary Connection from a + * CCI ConnectionFactory. + * @param target the original Connection to wrap + * @return the wrapped Connection + */ + protected Connection getCloseSuppressingConnectionProxy(Connection target) { + return (Connection) Proxy.newProxyInstance( + Connection.class.getClassLoader(), + new Class[] {Connection.class}, + new CloseSuppressingInvocationHandler(target)); + } + + + /** + * Invocation handler that suppresses close calls on CCI Connections. + */ + private static class CloseSuppressingInvocationHandler implements InvocationHandler { + + private final Connection target; + + private CloseSuppressingInvocationHandler(Connection target) { + this.target = target; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0]); + } + else if (method.getName().equals("hashCode")) { + // Use hashCode of Connection proxy. + return System.identityHashCode(proxy); + } + else if (method.getName().equals("close")) { + // Handle close method: don't pass the call on. + return null; + } + try { + return method.invoke(this.target, args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/connection/TransactionAwareConnectionFactoryProxy.java b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/TransactionAwareConnectionFactoryProxy.java new file mode 100644 index 000000000..7af3d5a94 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/TransactionAwareConnectionFactoryProxy.java @@ -0,0 +1,165 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.connection; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; + +/** + * Proxy for a target CCI {@link javax.resource.cci.ConnectionFactory}, adding + * awareness of Spring-managed transactions. Similar to a transactional JNDI + * ConnectionFactory as provided by a J2EE server. + * + *

Data access code that should remain unaware of Spring's data access support + * can work with this proxy to seamlessly participate in Spring-managed transactions. + * Note that the transaction manager, for example the {@link CciLocalTransactionManager}, + * still needs to work with underlying ConnectionFactory, not with this proxy. + * + *

Make sure that TransactionAwareConnectionFactoryProxy is the outermost + * ConnectionFactory of a chain of ConnectionFactory proxies/adapters. + * TransactionAwareConnectionFactoryProxy can delegate either directly to the + * target connection pool or to some intermediate proxy/adapter like + * {@link ConnectionSpecConnectionFactoryAdapter}. + * + *

Delegates to {@link ConnectionFactoryUtils} for automatically participating in + * thread-bound transactions, for example managed by {@link CciLocalTransactionManager}. + * {@code getConnection} calls and {@code close} calls on returned Connections + * will behave properly within a transaction, i.e. always operate on the transactional + * Connection. If not within a transaction, normal ConnectionFactory behavior applies. + * + *

This proxy allows data access code to work with the plain JCA CCI API and still + * participate in Spring-managed transactions, similar to CCI code in a J2EE/JTA + * environment. However, if possible, use Spring's ConnectionFactoryUtils, CciTemplate or + * CCI operation objects to get transaction participation even without a proxy for + * the target ConnectionFactory, avoiding the need to define such a proxy in the first place. + * + *

NOTE: This ConnectionFactory proxy needs to return wrapped Connections + * in order to handle close calls properly. Therefore, the returned Connections cannot + * be cast to a native CCI Connection type or to a connection pool implementation type. + * + * @author Juergen Hoeller + * @since 1.2 + * @see javax.resource.cci.ConnectionFactory#getConnection + * @see javax.resource.cci.Connection#close + * @see ConnectionFactoryUtils#doGetConnection + * @see ConnectionFactoryUtils#doReleaseConnection + */ +@SuppressWarnings("serial") +public class TransactionAwareConnectionFactoryProxy extends DelegatingConnectionFactory { + + /** + * Create a new TransactionAwareConnectionFactoryProxy. + * @see #setTargetConnectionFactory + */ + public TransactionAwareConnectionFactoryProxy() { + } + + /** + * Create a new TransactionAwareConnectionFactoryProxy. + * @param targetConnectionFactory the target ConnectionFactory + */ + public TransactionAwareConnectionFactoryProxy(ConnectionFactory targetConnectionFactory) { + setTargetConnectionFactory(targetConnectionFactory); + afterPropertiesSet(); + } + + + /** + * Delegate to ConnectionFactoryUtils for automatically participating in Spring-managed + * transactions. Throws the original ResourceException, if any. + * @return a transactional Connection if any, a new one else + * @see com.fr.third.springframework.jca.cci.connection.ConnectionFactoryUtils#doGetConnection + */ + @Override + public Connection getConnection() throws ResourceException { + Connection con = ConnectionFactoryUtils.doGetConnection(getTargetConnectionFactory()); + return getTransactionAwareConnectionProxy(con, getTargetConnectionFactory()); + } + + /** + * Wrap the given Connection with a proxy that delegates every method call to it + * but delegates {@code close} calls to ConnectionFactoryUtils. + * @param target the original Connection to wrap + * @param cf ConnectionFactory that the Connection came from + * @return the wrapped Connection + * @see javax.resource.cci.Connection#close() + * @see ConnectionFactoryUtils#doReleaseConnection + */ + protected Connection getTransactionAwareConnectionProxy(Connection target, ConnectionFactory cf) { + return (Connection) Proxy.newProxyInstance( + Connection.class.getClassLoader(), + new Class[] {Connection.class}, + new TransactionAwareInvocationHandler(target, cf)); + } + + + /** + * Invocation handler that delegates close calls on CCI Connections + * to ConnectionFactoryUtils for being aware of thread-bound transactions. + */ + private static class TransactionAwareInvocationHandler implements InvocationHandler { + + private final Connection target; + + private final ConnectionFactory connectionFactory; + + public TransactionAwareInvocationHandler(Connection target, ConnectionFactory cf) { + this.target = target; + this.connectionFactory = cf; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // Invocation on Connection interface coming in... + + if (method.getName().equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0]); + } + else if (method.getName().equals("hashCode")) { + // Use hashCode of Connection proxy. + return System.identityHashCode(proxy); + } + else if (method.getName().equals("getLocalTransaction")) { + if (ConnectionFactoryUtils.isConnectionTransactional(this.target, this.connectionFactory)) { + throw new javax.resource.spi.IllegalStateException( + "Local transaction handling not allowed within a managed transaction"); + } + } + else if (method.getName().equals("close")) { + // Handle close method: only close if not within a transaction. + ConnectionFactoryUtils.doReleaseConnection(this.target, this.connectionFactory); + return null; + } + + // Invoke method on target Connection. + try { + return method.invoke(this.target, args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/connection/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/package-info.java new file mode 100644 index 000000000..9eaeaad94 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/connection/package-info.java @@ -0,0 +1,10 @@ + +/** + * + * Provides a utility class for easy ConnectionFactory access, + * a PlatformTransactionManager for local CCI transactions, + * and various simple ConnectionFactory proxies/adapters. + * + */ +package com.fr.third.springframework.jca.cci.connection; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/CciOperations.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/CciOperations.java new file mode 100644 index 000000000..e5da4b379 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/CciOperations.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.core; + +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.Record; + +import com.fr.third.springframework.dao.DataAccessException; + +/** + * Interface that specifies a basic set of CCI operations on an EIS. + * Implemented by CciTemplate. Not often used, but a useful option + * to enhance testability, as it can easily be mocked or stubbed. + * + *

Alternatively, the standard CCI infrastructure can be mocked. + * However, mocking this interface constitutes significantly less work. + * + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate + */ +public interface CciOperations { + + /** + * Execute a request on an EIS with CCI, implemented as callback action + * working on a CCI Connection. This allows for implementing arbitrary + * data access operations, within Spring's managed CCI environment: + * that is, participating in Spring-managed transactions and converting + * JCA ResourceExceptions into Spring's DataAccessException hierarchy. + *

The callback action can return a result object, for example a + * domain object or a collection of domain objects. + * @param action the callback object that specifies the action + * @return the result object returned by the action, if any + * @throws DataAccessException if there is any problem + */ + T execute(ConnectionCallback action) throws DataAccessException; + + /** + * Execute a request on an EIS with CCI, implemented as callback action + * working on a CCI Interaction. This allows for implementing arbitrary + * data access operations on a single Interaction, within Spring's managed + * CCI environment: that is, participating in Spring-managed transactions + * and converting JCA ResourceExceptions into Spring's DataAccessException + * hierarchy. + *

The callback action can return a result object, for example a + * domain object or a collection of domain objects. + * @param action the callback object that specifies the action + * @return the result object returned by the action, if any + * @throws DataAccessException if there is any problem + */ + T execute(InteractionCallback action) throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputRecord the input record + * @return the output record + * @throws DataAccessException if there is any problem + */ + Record execute(InteractionSpec spec, Record inputRecord) throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputRecord the input record + * @param outputRecord the output record + * @throws DataAccessException if there is any problem + */ + void execute(InteractionSpec spec, Record inputRecord, Record outputRecord) throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputCreator object that creates the input record to use + * @return the output record + * @throws DataAccessException if there is any problem + */ + Record execute(InteractionSpec spec, RecordCreator inputCreator) throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputRecord the input record + * @param outputExtractor object to convert the output record to a result object + * @return the output data extracted with the RecordExtractor object + * @throws DataAccessException if there is any problem + */ + T execute(InteractionSpec spec, Record inputRecord, RecordExtractor outputExtractor) + throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputCreator object that creates the input record to use + * @param outputExtractor object to convert the output record to a result object + * @return the output data extracted with the RecordExtractor object + * @throws DataAccessException if there is any problem + */ + T execute(InteractionSpec spec, RecordCreator inputCreator, RecordExtractor outputExtractor) + throws DataAccessException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/CciTemplate.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/CciTemplate.java new file mode 100644 index 000000000..09cec7357 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/CciTemplate.java @@ -0,0 +1,439 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.core; + +import java.sql.SQLException; +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; +import javax.resource.cci.IndexedRecord; +import javax.resource.cci.Interaction; +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.MappedRecord; +import javax.resource.cci.Record; +import javax.resource.cci.RecordFactory; +import javax.resource.cci.ResultSet; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.dao.DataAccessException; +import com.fr.third.springframework.dao.DataAccessResourceFailureException; +import com.fr.third.springframework.jca.cci.CannotCreateRecordException; +import com.fr.third.springframework.jca.cci.CciOperationNotSupportedException; +import com.fr.third.springframework.jca.cci.InvalidResultSetAccessException; +import com.fr.third.springframework.jca.cci.RecordTypeNotSupportedException; +import com.fr.third.springframework.jca.cci.connection.ConnectionFactoryUtils; +import com.fr.third.springframework.jca.cci.connection.NotSupportedRecordFactory; +import com.fr.third.springframework.util.Assert; + +/** + * This is the central class in the CCI core package. + * It simplifies the use of CCI and helps to avoid common errors. + * It executes core CCI workflow, leaving application code to provide parameters + * to CCI and extract results. This class executes EIS queries or updates, + * catching ResourceExceptions and translating them to the generic exception + * hierarchy defined in the {@code com.fr.third.springframework.dao} package. + * + *

Code using this class can pass in and receive {@link javax.resource.cci.Record} + * instances, or alternatively implement callback interfaces for creating input + * Records and extracting result objects from output Records (or CCI ResultSets). + * + *

Can be used within a service implementation via direct instantiation + * with a ConnectionFactory reference, or get prepared in an application context + * and given to services as bean reference. Note: The ConnectionFactory should + * always be configured as a bean in the application context, in the first case + * given to the service directly, in the second case to the prepared template. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see RecordCreator + * @see RecordExtractor + */ +public class CciTemplate implements CciOperations { + + private final Log logger = LogFactory.getLog(getClass()); + + private ConnectionFactory connectionFactory; + + private ConnectionSpec connectionSpec; + + private RecordCreator outputRecordCreator; + + + /** + * Construct a new CciTemplate for bean usage. + *

Note: The ConnectionFactory has to be set before using the instance. + * @see #setConnectionFactory + */ + public CciTemplate() { + } + + /** + * Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from. + * Note: This will trigger eager initialization of the exception translator. + * @param connectionFactory JCA ConnectionFactory to obtain Connections from + */ + public CciTemplate(ConnectionFactory connectionFactory) { + setConnectionFactory(connectionFactory); + afterPropertiesSet(); + } + + /** + * Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from. + * Note: This will trigger eager initialization of the exception translator. + * @param connectionFactory JCA ConnectionFactory to obtain Connections from + * @param connectionSpec the CCI ConnectionSpec to obtain Connections for + * (may be {@code null}) + */ + public CciTemplate(ConnectionFactory connectionFactory, ConnectionSpec connectionSpec) { + setConnectionFactory(connectionFactory); + setConnectionSpec(connectionSpec); + afterPropertiesSet(); + } + + + /** + * Set the CCI ConnectionFactory to obtain Connections from. + */ + public void setConnectionFactory(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + /** + * Return the CCI ConnectionFactory used by this template. + */ + public ConnectionFactory getConnectionFactory() { + return this.connectionFactory; + } + + /** + * Set the CCI ConnectionSpec that this template instance is + * supposed to obtain Connections for. + */ + public void setConnectionSpec(ConnectionSpec connectionSpec) { + this.connectionSpec = connectionSpec; + } + + /** + * Return the CCI ConnectionSpec used by this template, if any. + */ + public ConnectionSpec getConnectionSpec() { + return this.connectionSpec; + } + + /** + * Set a RecordCreator that should be used for creating default output Records. + *

Default is none: When no explicit output Record gets passed into an + * {@code execute} method, CCI's {@code Interaction.execute} variant + * that returns an output Record will be called. + *

Specify a RecordCreator here if you always need to call CCI's + * {@code Interaction.execute} variant with a passed-in output Record. + * Unless there is an explicitly specified output Record, CciTemplate will + * then invoke this RecordCreator to create a default output Record instance. + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record) + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record, Record) + */ + public void setOutputRecordCreator(RecordCreator creator) { + this.outputRecordCreator = creator; + } + + /** + * Return a RecordCreator that should be used for creating default output Records. + */ + public RecordCreator getOutputRecordCreator() { + return this.outputRecordCreator; + } + + public void afterPropertiesSet() { + if (getConnectionFactory() == null) { + throw new IllegalArgumentException("Property 'connectionFactory' is required"); + } + } + + + /** + * Create a template derived from this template instance, + * inheriting the ConnectionFactory and other settings but + * overriding the ConnectionSpec used for obtaining Connections. + * @param connectionSpec the CCI ConnectionSpec that the derived template + * instance is supposed to obtain Connections for + * @return the derived template instance + * @see #setConnectionSpec + */ + public CciTemplate getDerivedTemplate(ConnectionSpec connectionSpec) { + CciTemplate derived = new CciTemplate(); + derived.setConnectionFactory(getConnectionFactory()); + derived.setConnectionSpec(connectionSpec); + derived.setOutputRecordCreator(getOutputRecordCreator()); + return derived; + } + + + @Override + public T execute(ConnectionCallback action) throws DataAccessException { + Assert.notNull(action, "Callback object must not be null"); + Connection con = ConnectionFactoryUtils.getConnection(getConnectionFactory(), getConnectionSpec()); + try { + return action.doInConnection(con, getConnectionFactory()); + } + catch (NotSupportedException ex) { + throw new CciOperationNotSupportedException("CCI operation not supported by connector", ex); + } + catch (ResourceException ex) { + throw new DataAccessResourceFailureException("CCI operation failed", ex); + } + catch (SQLException ex) { + throw new InvalidResultSetAccessException("Parsing of CCI ResultSet failed", ex); + } + finally { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + } + } + + @Override + public T execute(final InteractionCallback action) throws DataAccessException { + Assert.notNull(action, "Callback object must not be null"); + return execute(new ConnectionCallback() { + @Override + public T doInConnection(Connection connection, ConnectionFactory connectionFactory) + throws ResourceException, SQLException, DataAccessException { + Interaction interaction = connection.createInteraction(); + try { + return action.doInInteraction(interaction, connectionFactory); + } + finally { + closeInteraction(interaction); + } + } + }); + } + + @Override + public Record execute(InteractionSpec spec, Record inputRecord) throws DataAccessException { + return doExecute(spec, inputRecord, null, new SimpleRecordExtractor()); + } + + @Override + public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord) throws DataAccessException { + doExecute(spec, inputRecord, outputRecord, null); + } + + @Override + public Record execute(InteractionSpec spec, RecordCreator inputCreator) throws DataAccessException { + return doExecute(spec, createRecord(inputCreator), null, new SimpleRecordExtractor()); + } + + @Override + public T execute(InteractionSpec spec, Record inputRecord, RecordExtractor outputExtractor) + throws DataAccessException { + + return doExecute(spec, inputRecord, null, outputExtractor); + } + + @Override + public T execute(InteractionSpec spec, RecordCreator inputCreator, RecordExtractor outputExtractor) + throws DataAccessException { + + return doExecute(spec, createRecord(inputCreator), null, outputExtractor); + } + + /** + * Execute the specified interaction on an EIS with CCI. + * All other interaction execution methods go through this. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputRecord the input record + * @param outputRecord output record (can be {@code null}) + * @param outputExtractor object to convert the output record to a result object + * @return the output data extracted with the RecordExtractor object + * @throws DataAccessException if there is any problem + */ + protected T doExecute( + final InteractionSpec spec, final Record inputRecord, final Record outputRecord, + final RecordExtractor outputExtractor) throws DataAccessException { + + return execute(new InteractionCallback() { + @Override + public T doInInteraction(Interaction interaction, ConnectionFactory connectionFactory) + throws ResourceException, SQLException, DataAccessException { + Record outputRecordToUse = outputRecord; + try { + if (outputRecord != null || getOutputRecordCreator() != null) { + // Use the CCI execute method with output record as parameter. + if (outputRecord == null) { + RecordFactory recordFactory = getRecordFactory(connectionFactory); + outputRecordToUse = getOutputRecordCreator().createRecord(recordFactory); + } + interaction.execute(spec, inputRecord, outputRecordToUse); + } + else { + outputRecordToUse = interaction.execute(spec, inputRecord); + } + return (outputExtractor != null ? outputExtractor.extractData(outputRecordToUse) : null); + } + finally { + if (outputRecordToUse instanceof ResultSet) { + closeResultSet((ResultSet) outputRecordToUse); + } + } + } + }); + } + + + /** + * Create an indexed Record through the ConnectionFactory's RecordFactory. + * @param name the name of the record + * @return the Record + * @throws DataAccessException if creation of the Record failed + * @see #getRecordFactory(javax.resource.cci.ConnectionFactory) + * @see javax.resource.cci.RecordFactory#createIndexedRecord(String) + */ + public IndexedRecord createIndexedRecord(String name) throws DataAccessException { + try { + RecordFactory recordFactory = getRecordFactory(getConnectionFactory()); + return recordFactory.createIndexedRecord(name); + } + catch (NotSupportedException ex) { + throw new RecordTypeNotSupportedException("Creation of indexed Record not supported by connector", ex); + } + catch (ResourceException ex) { + throw new CannotCreateRecordException("Creation of indexed Record failed", ex); + } + } + + /** + * Create a mapped Record from the ConnectionFactory's RecordFactory. + * @param name record name + * @return the Record + * @throws DataAccessException if creation of the Record failed + * @see #getRecordFactory(javax.resource.cci.ConnectionFactory) + * @see javax.resource.cci.RecordFactory#createMappedRecord(String) + */ + public MappedRecord createMappedRecord(String name) throws DataAccessException { + try { + RecordFactory recordFactory = getRecordFactory(getConnectionFactory()); + return recordFactory.createMappedRecord(name); + } + catch (NotSupportedException ex) { + throw new RecordTypeNotSupportedException("Creation of mapped Record not supported by connector", ex); + } + catch (ResourceException ex) { + throw new CannotCreateRecordException("Creation of mapped Record failed", ex); + } + } + + /** + * Invoke the given RecordCreator, converting JCA ResourceExceptions + * to Spring's DataAccessException hierarchy. + * @param recordCreator the RecordCreator to invoke + * @return the created Record + * @throws DataAccessException if creation of the Record failed + * @see #getRecordFactory(javax.resource.cci.ConnectionFactory) + * @see RecordCreator#createRecord(javax.resource.cci.RecordFactory) + */ + protected Record createRecord(RecordCreator recordCreator) throws DataAccessException { + try { + RecordFactory recordFactory = getRecordFactory(getConnectionFactory()); + return recordCreator.createRecord(recordFactory); + } + catch (NotSupportedException ex) { + throw new RecordTypeNotSupportedException( + "Creation of the desired Record type not supported by connector", ex); + } + catch (ResourceException ex) { + throw new CannotCreateRecordException("Creation of the desired Record failed", ex); + } + } + + /** + * Return a RecordFactory for the given ConnectionFactory. + *

Default implementation returns the connector's RecordFactory if + * available, falling back to a NotSupportedRecordFactory placeholder. + * This allows to invoke a RecordCreator callback with a non-null + * RecordFactory reference in any case. + * @param connectionFactory the CCI ConnectionFactory + * @return the CCI RecordFactory for the ConnectionFactory + * @throws ResourceException if thrown by CCI methods + * @see com.fr.third.springframework.jca.cci.connection.NotSupportedRecordFactory + */ + protected RecordFactory getRecordFactory(ConnectionFactory connectionFactory) throws ResourceException { + try { + return connectionFactory.getRecordFactory(); + } + catch (NotSupportedException ex) { + return new NotSupportedRecordFactory(); + } + } + + + /** + * Close the given CCI Interaction and ignore any thrown exception. + * This is useful for typical finally blocks in manual CCI code. + * @param interaction the CCI Interaction to close + * @see javax.resource.cci.Interaction#close() + */ + private void closeInteraction(Interaction interaction) { + if (interaction != null) { + try { + interaction.close(); + } + catch (ResourceException ex) { + logger.trace("Could not close CCI Interaction", ex); + } + catch (Throwable ex) { + // We don't trust the CCI driver: It might throw RuntimeException or Error. + logger.trace("Unexpected exception on closing CCI Interaction", ex); + } + } + } + + /** + * Close the given CCI ResultSet and ignore any thrown exception. + * This is useful for typical finally blocks in manual CCI code. + * @param resultSet the CCI ResultSet to close + * @see javax.resource.cci.ResultSet#close() + */ + private void closeResultSet(ResultSet resultSet) { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException ex) { + logger.trace("Could not close CCI ResultSet", ex); + } + catch (Throwable ex) { + // We don't trust the CCI driver: It might throw RuntimeException or Error. + logger.trace("Unexpected exception on closing CCI ResultSet", ex); + } + } + } + + + private static class SimpleRecordExtractor implements RecordExtractor { + + @Override + public Record extractData(Record record) { + return record; + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/ConnectionCallback.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/ConnectionCallback.java new file mode 100644 index 000000000..297a910d5 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/ConnectionCallback.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.core; + +import java.sql.SQLException; + +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; + +import com.fr.third.springframework.dao.DataAccessException; + +/** + * Generic callback interface for code that operates on a CCI Connection. + * Allows to execute any number of operations on a single Connection, + * using any type and number of Interaction. + * + *

This is particularly useful for delegating to existing data access code + * that expects a Connection to work on and throws ResourceException. For newly + * written code, it is strongly recommended to use CciTemplate's more specific + * {@code execute} variants. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate#execute(ConnectionCallback) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, javax.resource.cci.Record) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + */ +public interface ConnectionCallback { + + /** + * Gets called by {@code CciTemplate.execute} with an active CCI Connection. + * Does not need to care about activating or closing the Connection, or handling + * transactions. + * + *

If called without a thread-bound CCI transaction (initiated by + * CciLocalTransactionManager), the code will simply get executed on the CCI + * Connection with its transactional semantics. If CciTemplate is configured + * to use a JTA-aware ConnectionFactory, the CCI Connection and thus the callback + * code will be transactional if a JTA transaction is active. + * + *

Allows for returning a result object created within the callback, i.e. + * a domain object or a collection of domain objects. Note that there's special + * support for single step actions: see the {@code CciTemplate.execute} + * variants. A thrown RuntimeException is treated as application exception: + * it gets propagated to the caller of the template. + * + * @param connection active CCI Connection + * @param connectionFactory the CCI ConnectionFactory that the Connection was + * created with (gives access to RecordFactory and ResourceAdapterMetaData) + * @return a result object, or {@code null} if none + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @throws SQLException if thrown by a ResultSet method, to be auto-converted + * to a DataAccessException + * @throws DataAccessException in case of custom exceptions + * @see javax.resource.cci.ConnectionFactory#getRecordFactory() + * @see javax.resource.cci.ConnectionFactory#getMetaData() + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + */ + T doInConnection(Connection connection, ConnectionFactory connectionFactory) + throws ResourceException, SQLException, DataAccessException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/InteractionCallback.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/InteractionCallback.java new file mode 100644 index 000000000..2fc332f61 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/InteractionCallback.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.core; + +import java.sql.SQLException; + +import javax.resource.ResourceException; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.Interaction; + +import com.fr.third.springframework.dao.DataAccessException; + +/** + * Generic callback interface for code that operates on a CCI Interaction. + * Allows to execute any number of operations on a single Interaction, for + * example a single execute call or repeated execute calls with varying + * parameters. + * + *

This is particularly useful for delegating to existing data access code + * that expects an Interaction to work on and throws ResourceException. For newly + * written code, it is strongly recommended to use CciTemplate's more specific + * {@code execute} variants. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate#execute(InteractionCallback) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, javax.resource.cci.Record) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + */ +public interface InteractionCallback { + + /** + * Gets called by {@code CciTemplate.execute} with an active CCI Interaction. + * Does not need to care about activating or closing the Interaction, or + * handling transactions. + * + *

If called without a thread-bound CCI transaction (initiated by + * CciLocalTransactionManager), the code will simply get executed on the CCI + * Interaction with its transactional semantics. If CciTemplate is configured + * to use a JTA-aware ConnectionFactory, the CCI Interaction and thus the callback + * code will be transactional if a JTA transaction is active. + * + *

Allows for returning a result object created within the callback, i.e. + * a domain object or a collection of domain objects. Note that there's special + * support for single step actions: see the {@code CciTemplate.execute} + * variants. A thrown RuntimeException is treated as application exception: + * it gets propagated to the caller of the template. + * + * @param interaction active CCI Interaction + * @param connectionFactory the CCI ConnectionFactory that the Connection was + * created with (gives access to RecordFactory and ResourceAdapterMetaData) + * @return a result object, or {@code null} if none + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @throws SQLException if thrown by a ResultSet method, to be auto-converted + * to a DataAccessException + * @throws DataAccessException in case of custom exceptions + * @see javax.resource.cci.ConnectionFactory#getRecordFactory() + * @see javax.resource.cci.ConnectionFactory#getMetaData() + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + */ + T doInInteraction(Interaction interaction, ConnectionFactory connectionFactory) + throws ResourceException, SQLException, DataAccessException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/RecordCreator.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/RecordCreator.java new file mode 100644 index 000000000..d3287e98c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/RecordCreator.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.core; + +import javax.resource.ResourceException; +import javax.resource.cci.Record; +import javax.resource.cci.RecordFactory; + +import com.fr.third.springframework.dao.DataAccessException; + +/** + * Callback interface for creating a CCI Record instance, + * usually based on the passed-in CCI RecordFactory. + * + *

Used for input Record creation in CciTemplate. Alternatively, + * Record instances can be passed into CciTemplate's corresponding + * {@code execute} methods directly, either instantiated manually + * or created through CciTemplate's Record factory methods. + * + *

Also used for creating default output Records in CciTemplate. + * This is useful when the JCA connector needs an explicit output Record + * instance, but no output Records should be passed into CciTemplate's + * {@code execute} methods. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + * @see CciTemplate#createIndexedRecord(String) + * @see CciTemplate#createMappedRecord(String) + * @see CciTemplate#setOutputRecordCreator(RecordCreator) + */ +public interface RecordCreator { + + /** + * Create a CCI Record instance, usually based on the passed-in CCI RecordFactory. + *

For use as input creator with CciTemplate's {@code execute} methods, + * this method should create a populated Record instance. For use as + * output Record creator, it should return an empty Record instance. + * @param recordFactory the CCI RecordFactory (never {@code null}, but not guaranteed to be + * supported by the connector: its create methods might throw NotSupportedException) + * @return the Record instance + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @throws DataAccessException in case of custom exceptions + */ + Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/RecordExtractor.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/RecordExtractor.java new file mode 100644 index 000000000..2edcc5934 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/RecordExtractor.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.core; + +import java.sql.SQLException; + +import javax.resource.ResourceException; +import javax.resource.cci.Record; + +import com.fr.third.springframework.dao.DataAccessException; + +/** + * Callback interface for extracting a result object from a CCI Record instance. + * + *

Used for output object creation in CciTemplate. Alternatively, output + * Records can also be returned to client code as-is. In case of a CCI ResultSet + * as execution result, you will almost always want to implement a RecordExtractor, + * to be able to read the ResultSet in a managed fashion, with the CCI Connection + * still open while reading the ResultSet. + * + *

Implementations of this interface perform the actual work of extracting + * results, but don't need to worry about exception handling. ResourceExceptions + * will be caught and handled correctly by the CciTemplate class. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, Record, RecordExtractor) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + * @see javax.resource.cci.ResultSet + */ +public interface RecordExtractor { + + /** + * Process the data in the given Record, creating a corresponding result object. + * @param record the Record to extract data from + * (possibly a CCI ResultSet) + * @return an arbitrary result object, or {@code null} if none + * (the extractor will typically be stateful in the latter case) + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @throws SQLException if thrown by a ResultSet method, to be auto-converted + * to a DataAccessException + * @throws DataAccessException in case of custom exceptions + * @see javax.resource.cci.ResultSet + */ + T extractData(Record record) throws ResourceException, SQLException, DataAccessException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/package-info.java new file mode 100644 index 000000000..151c601d8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * Provides the core JCA CCI support, based on CciTemplate + * and its associated callback interfaces. + * + */ +package com.fr.third.springframework.jca.cci.core; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/support/CciDaoSupport.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/support/CciDaoSupport.java new file mode 100644 index 000000000..1d90705ac --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/support/CciDaoSupport.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.core.support; + +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; + +import com.fr.third.springframework.dao.support.DaoSupport; +import com.fr.third.springframework.jca.cci.CannotGetCciConnectionException; +import com.fr.third.springframework.jca.cci.connection.ConnectionFactoryUtils; +import com.fr.third.springframework.jca.cci.core.CciTemplate; + +/** + * Convenient super class for CCI-based data access objects. + * + *

Requires a {@link javax.resource.cci.ConnectionFactory} to be set, + * providing a {@link com.fr.third.springframework.jca.cci.core.CciTemplate} based + * on it to subclasses through the {@link #getCciTemplate()} method. + * + *

This base class is mainly intended for CciTemplate usage but can + * also be used when working with a Connection directly or when using + * {@code com.fr.third.springframework.jca.cci.object} classes. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see #setConnectionFactory + * @see #getCciTemplate + * @see com.fr.third.springframework.jca.cci.core.CciTemplate + */ +public abstract class CciDaoSupport extends DaoSupport { + + private CciTemplate cciTemplate; + + + /** + * Set the ConnectionFactory to be used by this DAO. + */ + public final void setConnectionFactory(ConnectionFactory connectionFactory) { + if (this.cciTemplate == null || connectionFactory != this.cciTemplate.getConnectionFactory()) { + this.cciTemplate = createCciTemplate(connectionFactory); + } + } + + /** + * Create a CciTemplate for the given ConnectionFactory. + * Only invoked if populating the DAO with a ConnectionFactory reference! + *

Can be overridden in subclasses to provide a CciTemplate instance + * with different configuration, or a custom CciTemplate subclass. + * @param connectionFactory the CCI ConnectionFactory to create a CciTemplate for + * @return the new CciTemplate instance + * @see #setConnectionFactory(javax.resource.cci.ConnectionFactory) + */ + protected CciTemplate createCciTemplate(ConnectionFactory connectionFactory) { + return new CciTemplate(connectionFactory); + } + + /** + * Return the ConnectionFactory used by this DAO. + */ + public final ConnectionFactory getConnectionFactory() { + return this.cciTemplate.getConnectionFactory(); + } + + /** + * Set the CciTemplate for this DAO explicitly, + * as an alternative to specifying a ConnectionFactory. + */ + public final void setCciTemplate(CciTemplate cciTemplate) { + this.cciTemplate = cciTemplate; + } + + /** + * Return the CciTemplate for this DAO, + * pre-initialized with the ConnectionFactory or set explicitly. + */ + public final CciTemplate getCciTemplate() { + return this.cciTemplate; + } + + @Override + protected final void checkDaoConfig() { + if (this.cciTemplate == null) { + throw new IllegalArgumentException("'connectionFactory' or 'cciTemplate' is required"); + } + } + + + /** + * Obtain a CciTemplate derived from the main template instance, + * inheriting the ConnectionFactory and other settings but + * overriding the ConnectionSpec used for obtaining Connections. + * @param connectionSpec the CCI ConnectionSpec that the returned + * template instance is supposed to obtain Connections for + * @return the derived template instance + * @see com.fr.third.springframework.jca.cci.core.CciTemplate#getDerivedTemplate(javax.resource.cci.ConnectionSpec) + */ + protected final CciTemplate getCciTemplate(ConnectionSpec connectionSpec) { + return getCciTemplate().getDerivedTemplate(connectionSpec); + } + + /** + * Get a CCI Connection, either from the current transaction or a new one. + * @return the CCI Connection + * @throws com.fr.third.springframework.jca.cci.CannotGetCciConnectionException + * if the attempt to get a Connection failed + * @see com.fr.third.springframework.jca.cci.connection.ConnectionFactoryUtils#getConnection(javax.resource.cci.ConnectionFactory) + */ + protected final Connection getConnection() throws CannotGetCciConnectionException { + return ConnectionFactoryUtils.getConnection(getConnectionFactory()); + } + + /** + * Close the given CCI Connection, created via this bean's ConnectionFactory, + * if it isn't bound to the thread. + * @param con Connection to close + * @see com.fr.third.springframework.jca.cci.connection.ConnectionFactoryUtils#releaseConnection + */ + protected final void releaseConnection(Connection con) { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/support/CommAreaRecord.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/support/CommAreaRecord.java new file mode 100644 index 000000000..d481d8e06 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/support/CommAreaRecord.java @@ -0,0 +1,104 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.core.support; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.resource.cci.Record; +import javax.resource.cci.Streamable; + +import com.fr.third.springframework.util.FileCopyUtils; + +/** + * CCI Record implementation for a COMMAREA, holding a byte array. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see com.fr.third.springframework.jca.cci.object.MappingCommAreaOperation + */ +@SuppressWarnings("serial") +public class CommAreaRecord implements Record, Streamable { + + private byte[] bytes; + + private String recordName; + + private String recordShortDescription; + + + /** + * Create a new CommAreaRecord. + * @see #read(java.io.InputStream) + */ + public CommAreaRecord() { + } + + /** + * Create a new CommAreaRecord. + * @param bytes the bytes to fill the record with + */ + public CommAreaRecord(byte[] bytes) { + this.bytes = bytes; + } + + + @Override + public void setRecordName(String recordName) { + this.recordName=recordName; + } + + @Override + public String getRecordName() { + return recordName; + } + + @Override + public void setRecordShortDescription(String recordShortDescription) { + this.recordShortDescription=recordShortDescription; + } + + @Override + public String getRecordShortDescription() { + return recordShortDescription; + } + + + @Override + public void read(InputStream in) throws IOException { + this.bytes = FileCopyUtils.copyToByteArray(in); + } + + @Override + public void write(OutputStream out) throws IOException { + out.write(this.bytes); + out.flush(); + } + + public byte[] toByteArray() { + return this.bytes; + } + + + @Override + public Object clone() { + return new CommAreaRecord(this.bytes); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/core/support/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/cci/core/support/package-info.java new file mode 100644 index 000000000..c42047655 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/core/support/package-info.java @@ -0,0 +1,8 @@ +/** + * + * Classes supporting the {@code com.fr.third.springframework.jca.cci.core} package. + * Contains a DAO base class for CciTemplate usage. + * + */ +package com.fr.third.springframework.jca.cci.core.support; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/object/EisOperation.java b/fine-spring/src/com/fr/third/springframework/jca/cci/object/EisOperation.java new file mode 100644 index 000000000..a6d13deb2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/object/EisOperation.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.object; + +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.InteractionSpec; + +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.jca.cci.core.CciTemplate; + +/** + * Base class for EIS operation objects that work with the CCI API. + * Encapsulates a CCI ConnectionFactory and a CCI InteractionSpec. + * + *

Works with a CciTemplate instance underneath. EIS operation objects + * are an alternative to working with a CciTemplate directly. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #setConnectionFactory + * @see #setInteractionSpec + */ +public abstract class EisOperation implements InitializingBean { + + private CciTemplate cciTemplate = new CciTemplate(); + + private InteractionSpec interactionSpec; + + + /** + * Set the CciTemplate to be used by this operation. + * Alternatively, specify a CCI ConnectionFactory. + * @see #setConnectionFactory + */ + public void setCciTemplate(CciTemplate cciTemplate) { + if (cciTemplate == null) { + throw new IllegalArgumentException("cciTemplate must not be null"); + } + this.cciTemplate = cciTemplate; + } + + /** + * Return the CciTemplate used by this operation. + */ + public CciTemplate getCciTemplate() { + return this.cciTemplate; + } + + /** + * Set the CCI ConnectionFactory to be used by this operation. + */ + public void setConnectionFactory(ConnectionFactory connectionFactory) { + this.cciTemplate.setConnectionFactory(connectionFactory); + } + + /** + * Set the CCI InteractionSpec for this operation. + */ + public void setInteractionSpec(InteractionSpec interactionSpec) { + this.interactionSpec = interactionSpec; + } + + /** + * Return the CCI InteractionSpec for this operation. + */ + public InteractionSpec getInteractionSpec() { + return this.interactionSpec; + } + + + @Override + public void afterPropertiesSet() { + this.cciTemplate.afterPropertiesSet(); + + if (this.interactionSpec == null) { + throw new IllegalArgumentException("interactionSpec is required"); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/object/MappingCommAreaOperation.java b/fine-spring/src/com/fr/third/springframework/jca/cci/object/MappingCommAreaOperation.java new file mode 100644 index 000000000..b6d35d9fc --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/object/MappingCommAreaOperation.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.object; + +import java.io.IOException; + +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.Record; +import javax.resource.cci.RecordFactory; + +import com.fr.third.springframework.dao.DataAccessException; +import com.fr.third.springframework.dao.DataRetrievalFailureException; +import com.fr.third.springframework.jca.cci.core.support.CommAreaRecord; + +/** + * EIS operation object for access to COMMAREA records. + * Subclass of the generic MappingRecordOperation class. + * + * @author Thierry Templier + * @since 1.2 + */ +public abstract class MappingCommAreaOperation extends MappingRecordOperation { + + /** + * Create a new MappingCommAreaQuery. + * @see #setConnectionFactory + * @see #setInteractionSpec + */ + public MappingCommAreaOperation() { + } + + /** + * Create a new MappingCommAreaQuery. + * @param connectionFactory ConnectionFactory to use to obtain connections + * @param interactionSpec specification to configure the interaction + */ + public MappingCommAreaOperation(ConnectionFactory connectionFactory, InteractionSpec interactionSpec) { + super(connectionFactory, interactionSpec); + } + + + @Override + protected final Record createInputRecord(RecordFactory recordFactory, Object inObject) { + try { + return new CommAreaRecord(objectToBytes(inObject)); + } + catch (IOException ex) { + throw new DataRetrievalFailureException("I/O exception during bytes conversion", ex); + } + } + + @Override + protected final Object extractOutputData(Record record) throws DataAccessException { + CommAreaRecord commAreaRecord = (CommAreaRecord) record; + try { + return bytesToObject(commAreaRecord.toByteArray()); + } + catch (IOException ex) { + throw new DataRetrievalFailureException("I/O exception during bytes conversion", ex); + } + } + + + /** + * Method used to convert an object into COMMAREA bytes. + * @param inObject the input data + * @return the COMMAREA's bytes + * @throws IOException if thrown by I/O methods + * @throws DataAccessException if conversion failed + */ + protected abstract byte[] objectToBytes(Object inObject) throws IOException, DataAccessException; + + /** + * Method used to convert the COMMAREA's bytes to an object. + * @param bytes the COMMAREA's bytes + * @return the output data + * @throws IOException if thrown by I/O methods + * @throws DataAccessException if conversion failed + */ + protected abstract Object bytesToObject(byte[] bytes) throws IOException, DataAccessException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/object/MappingRecordOperation.java b/fine-spring/src/com/fr/third/springframework/jca/cci/object/MappingRecordOperation.java new file mode 100644 index 000000000..3e7951d0d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/object/MappingRecordOperation.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.object; + +import java.sql.SQLException; + +import javax.resource.ResourceException; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.Record; +import javax.resource.cci.RecordFactory; + +import com.fr.third.springframework.dao.DataAccessException; +import com.fr.third.springframework.jca.cci.core.RecordCreator; +import com.fr.third.springframework.jca.cci.core.RecordExtractor; + +/** + * EIS operation object that expects mapped input and output objects, + * converting to and from CCI Records. + * + *

Concrete subclasses must implement the abstract + * {@code createInputRecord(RecordFactory, Object)} and + * {@code extractOutputData(Record)} methods, to create an input + * Record from an object and to convert an output Record into an object, + * respectively. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see #createInputRecord(javax.resource.cci.RecordFactory, Object) + * @see #extractOutputData(javax.resource.cci.Record) + */ +public abstract class MappingRecordOperation extends EisOperation { + + /** + * Constructor that allows use as a JavaBean. + */ + public MappingRecordOperation() { + } + + /** + * Convenient constructor with ConnectionFactory and specifications + * (connection and interaction). + * @param connectionFactory ConnectionFactory to use to obtain connections + */ + public MappingRecordOperation(ConnectionFactory connectionFactory, InteractionSpec interactionSpec) { + getCciTemplate().setConnectionFactory(connectionFactory); + setInteractionSpec(interactionSpec); + } + + /** + * Set a RecordCreator that should be used for creating default output Records. + *

Default is none: CCI's {@code Interaction.execute} variant + * that returns an output Record will be called. + *

Specify a RecordCreator here if you always need to call CCI's + * {@code Interaction.execute} variant with a passed-in output Record. + * This RecordCreator will then be invoked to create a default output Record instance. + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record) + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record, Record) + * @see com.fr.third.springframework.jca.cci.core.CciTemplate#setOutputRecordCreator + */ + public void setOutputRecordCreator(RecordCreator creator) { + getCciTemplate().setOutputRecordCreator(creator); + } + + /** + * Execute the interaction encapsulated by this operation object. + * @param inputObject the input data, to be converted to a Record + * by the {@code createInputRecord} method + * @return the output data extracted with the {@code extractOutputData} method + * @throws DataAccessException if there is any problem + * @see #createInputRecord + * @see #extractOutputData + */ + public Object execute(Object inputObject) throws DataAccessException { + return getCciTemplate().execute( + getInteractionSpec(), new RecordCreatorImpl(inputObject), new RecordExtractorImpl()); + } + + + /** + * Subclasses must implement this method to generate an input Record + * from an input object passed into the {@code execute} method. + * @param inputObject the passed-in input object + * @return the CCI input Record + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @see #execute(Object) + */ + protected abstract Record createInputRecord(RecordFactory recordFactory, Object inputObject) + throws ResourceException, DataAccessException; + + /** + * Subclasses must implement this method to convert the Record returned + * by CCI execution into a result object for the {@code execute} method. + * @param outputRecord the Record returned by CCI execution + * @return the result object + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @see #execute(Object) + */ + protected abstract Object extractOutputData(Record outputRecord) + throws ResourceException, SQLException, DataAccessException; + + + /** + * Implementation of RecordCreator that calls the enclosing + * class's {@code createInputRecord} method. + */ + protected class RecordCreatorImpl implements RecordCreator { + + private final Object inputObject; + + public RecordCreatorImpl(Object inObject) { + this.inputObject = inObject; + } + + @Override + public Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException { + return createInputRecord(recordFactory, this.inputObject); + } + } + + + /** + * Implementation of RecordExtractor that calls the enclosing + * class's {@code extractOutputData} method. + */ + protected class RecordExtractorImpl implements RecordExtractor { + + @Override + public Object extractData(Record record) throws ResourceException, SQLException, DataAccessException { + return extractOutputData(record); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/object/SimpleRecordOperation.java b/fine-spring/src/com/fr/third/springframework/jca/cci/object/SimpleRecordOperation.java new file mode 100644 index 000000000..91b405aca --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/object/SimpleRecordOperation.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.cci.object; + +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.Record; + +import com.fr.third.springframework.dao.DataAccessException; + +/** + * EIS operation object that accepts a passed-in CCI input Record + * and returns a corresponding CCI output Record. + * + * @author Juergen Hoeller + * @since 1.2 + */ +public class SimpleRecordOperation extends EisOperation { + + /** + * Constructor that allows use as a JavaBean. + */ + public SimpleRecordOperation() { + } + + /** + * Convenient constructor with ConnectionFactory and specifications + * (connection and interaction). + * @param connectionFactory ConnectionFactory to use to obtain connections + */ + public SimpleRecordOperation(ConnectionFactory connectionFactory, InteractionSpec interactionSpec) { + getCciTemplate().setConnectionFactory(connectionFactory); + setInteractionSpec(interactionSpec); + } + + /** + * Execute the CCI interaction encapsulated by this operation object. + *

This method will call CCI's {@code Interaction.execute} variant + * that returns an output Record. + * @param inputRecord the input record + * @return the output record + * @throws DataAccessException if there is any problem + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record) + */ + public Record execute(Record inputRecord) throws DataAccessException { + return getCciTemplate().execute(getInteractionSpec(), inputRecord); + } + + /** + * Execute the CCI interaction encapsulated by this operation object. + *

This method will call CCI's {@code Interaction.execute} variant + * with a passed-in output Record. + * @param inputRecord the input record + * @param outputRecord the output record + * @throws DataAccessException if there is any problem + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record, Record) + */ + public void execute(Record inputRecord, Record outputRecord) throws DataAccessException { + getCciTemplate().execute(getInteractionSpec(), inputRecord, outputRecord); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/object/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/cci/object/package-info.java new file mode 100644 index 000000000..91701d0ee --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/object/package-info.java @@ -0,0 +1,11 @@ +/** + * + * The classes in this package represent EIS operations as threadsafe, + * reusable objects. This higher level of CCI abstraction depends on the + * lower-level abstraction in the {@code com.fr.third.springframework.jca.cci.core} package. + * Exceptions thrown are as in the {@code com.fr.third.springframework.dao} package, + * meaning that code using this package does not need to worry about error handling. + * + */ +package com.fr.third.springframework.jca.cci.object; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/cci/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/cci/package-info.java new file mode 100644 index 000000000..b1ea2d76e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/cci/package-info.java @@ -0,0 +1,10 @@ +/** + * + * This package contains Spring's support for the Common Client Interface (CCI), + * as defined by the J2EE Connector Architecture. It is conceptually similar + * to the {@code com.fr.third.springframework.jdbc} package, providing the same + * levels of data access abstraction. + * + */ +package com.fr.third.springframework.jca.cci; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/context/BootstrapContextAware.java b/fine-spring/src/com/fr/third/springframework/jca/context/BootstrapContextAware.java new file mode 100644 index 000000000..20f402224 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/context/BootstrapContextAware.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.context; + +import javax.resource.spi.BootstrapContext; + +import com.fr.third.springframework.beans.factory.Aware; + +/** + * Interface to be implemented by any object that wishes to be + * notified of the BootstrapContext (typically determined by the + * {@link ResourceAdapterApplicationContext}) that it runs in. + * + * @author Juergen Hoeller + * @author Chris Beams + * @since 2.5 + * @see javax.resource.spi.BootstrapContext + */ +public interface BootstrapContextAware extends Aware { + + /** + * Set the BootstrapContext that this object runs in. + *

Invoked after population of normal bean properties but before an init + * callback like InitializingBean's {@code afterPropertiesSet} or a + * custom init-method. Invoked after ApplicationContextAware's + * {@code setApplicationContext}. + * @param bootstrapContext BootstrapContext object to be used by this object + * @see com.fr.third.springframework.beans.factory.InitializingBean#afterPropertiesSet + * @see com.fr.third.springframework.context.ApplicationContextAware#setApplicationContext + */ + void setBootstrapContext(BootstrapContext bootstrapContext); + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/context/BootstrapContextAwareProcessor.java b/fine-spring/src/com/fr/third/springframework/jca/context/BootstrapContextAwareProcessor.java new file mode 100644 index 000000000..231d18493 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/context/BootstrapContextAwareProcessor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.context; + +import javax.resource.spi.BootstrapContext; + +import com.fr.third.springframework.beans.BeansException; +import com.fr.third.springframework.beans.factory.config.BeanPostProcessor; + +/** + * {@link com.fr.third.springframework.beans.factory.config.BeanPostProcessor} + * implementation that passes the BootstrapContext to beans that implement + * the {@link BootstrapContextAware} interface. + * + *

{@link ResourceAdapterApplicationContext} automatically registers + * this processor with its underlying bean factory. + * + * @author Juergen Hoeller + * @since 2.5 + * @see BootstrapContextAware + */ +class BootstrapContextAwareProcessor implements BeanPostProcessor { + + private final BootstrapContext bootstrapContext; + + + /** + * Create a new BootstrapContextAwareProcessor for the given context. + */ + public BootstrapContextAwareProcessor(BootstrapContext bootstrapContext) { + this.bootstrapContext = bootstrapContext; + } + + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (this.bootstrapContext != null && bean instanceof BootstrapContextAware) { + ((BootstrapContextAware) bean).setBootstrapContext(this.bootstrapContext); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/context/ResourceAdapterApplicationContext.java b/fine-spring/src/com/fr/third/springframework/jca/context/ResourceAdapterApplicationContext.java new file mode 100644 index 000000000..4585551ee --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/context/ResourceAdapterApplicationContext.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.context; + +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.work.WorkManager; + +import com.fr.third.springframework.beans.BeansException; +import com.fr.third.springframework.beans.factory.ObjectFactory; +import com.fr.third.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import com.fr.third.springframework.context.support.GenericApplicationContext; +import com.fr.third.springframework.util.Assert; + +/** + * {@link com.fr.third.springframework.context.ApplicationContext} implementation + * for a JCA ResourceAdapter. Needs to be initialized with the JCA + * {@link javax.resource.spi.BootstrapContext}, passing it on to + * Spring-managed beans that implement {@link BootstrapContextAware}. + * + * @author Juergen Hoeller + * @since 2.5 + * @see SpringContextResourceAdapter + * @see BootstrapContextAware + */ +public class ResourceAdapterApplicationContext extends GenericApplicationContext { + + private final BootstrapContext bootstrapContext; + + + /** + * Create a new ResourceAdapterApplicationContext for the given BootstrapContext. + * @param bootstrapContext the JCA BootstrapContext that the ResourceAdapter + * has been started with + */ + public ResourceAdapterApplicationContext(BootstrapContext bootstrapContext) { + Assert.notNull(bootstrapContext, "BootstrapContext must not be null"); + this.bootstrapContext = bootstrapContext; + } + + + @Override + protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + beanFactory.addBeanPostProcessor(new BootstrapContextAwareProcessor(this.bootstrapContext)); + beanFactory.ignoreDependencyInterface(BootstrapContextAware.class); + beanFactory.registerResolvableDependency(BootstrapContext.class, this.bootstrapContext); + + // JCA WorkManager resolved lazily - may not be available. + beanFactory.registerResolvableDependency(WorkManager.class, new ObjectFactory() { + @Override + public WorkManager getObject() { + return bootstrapContext.getWorkManager(); + } + }); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/context/SpringContextResourceAdapter.java b/fine-spring/src/com/fr/third/springframework/jca/context/SpringContextResourceAdapter.java new file mode 100644 index 000000000..7334fe145 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/context/SpringContextResourceAdapter.java @@ -0,0 +1,250 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.context; + +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.spi.ActivationSpec; +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.ResourceAdapter; +import javax.resource.spi.ResourceAdapterInternalException; +import javax.resource.spi.endpoint.MessageEndpointFactory; +import javax.transaction.xa.XAResource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.beans.factory.support.BeanDefinitionRegistry; +import com.fr.third.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import com.fr.third.springframework.context.ConfigurableApplicationContext; +import com.fr.third.springframework.core.env.ConfigurableEnvironment; +import com.fr.third.springframework.core.env.StandardEnvironment; +import com.fr.third.springframework.util.ObjectUtils; +import com.fr.third.springframework.util.StringUtils; + +/** + * JCA 1.5 {@link javax.resource.spi.ResourceAdapter} implementation + * that loads a Spring {@link com.fr.third.springframework.context.ApplicationContext}, + * starting and stopping Spring-managed beans as part of the ResourceAdapter's + * lifecycle. + * + *

Ideal for application contexts that do not need any HTTP entry points + * but rather just consist of message endpoints and scheduled jobs etc. + * Beans in such a context may use application server resources such as the + * JTA transaction manager and JNDI-bound JDBC DataSources and JMS + * ConnectionFactory instances, and may also register with the platform's + * JMX server - all through Spring's standard transaction management and + * JNDI and JMX support facilities. + * + *

If the need for scheduling asynchronous work arises, consider using + * Spring's {@link com.fr.third.springframework.jca.work.WorkManagerTaskExecutor} + * as a standard bean definition, to be injected into application beans + * through dependency injection. This WorkManagerTaskExecutor will automatically + * use the JCA WorkManager from the BootstrapContext that has been provided + * to this ResourceAdapter. + * + *

The JCA {@link javax.resource.spi.BootstrapContext} may also be + * accessed directly, through application components that implement the + * {@link BootstrapContextAware} interface. When deployed using this + * ResourceAdapter, the BootstrapContext is guaranteed to be passed on + * to such components. + * + *

This ResourceAdapter is to be defined in a "META-INF/ra.xml" file + * within a J2EE ".rar" deployment unit like as follows: + * + *

+ * <?xml version="1.0" encoding="UTF-8"?>
+ * <connector xmlns="http://java.sun.com/xml/ns/j2ee"
+ *		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ *		 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/connector_1_5.xsd"
+ *		 version="1.5">
+ *	 <vendor-name>Spring Framework</vendor-name>
+ *	 <eis-type>Spring Connector</eis-type>
+ *	 <resourceadapter-version>1.0</resourceadapter-version>
+ *	 <resourceadapter>
+ *		 <resourceadapter-class>com.fr.third.springframework.jca.context.SpringContextResourceAdapter</resourceadapter-class>
+ *		 <config-property>
+ *			 <config-property-name>ContextConfigLocation</config-property-name>
+ *			 <config-property-type>java.lang.String</config-property-type>
+ *			 <config-property-value>META-INF/applicationContext.xml</config-property-value>
+ *		 </config-property>
+ *	 </resourceadapter>
+ * </connector>
+ * + * Note that "META-INF/applicationContext.xml" is the default context config + * location, so it doesn't have to specified unless you intend to specify + * different/additional config files. So in the default case, you may remove + * the entire {@code config-property} section above. + * + *

For simple deployment needs, all you need to do is the following: + * Package all application classes into a RAR file (which is just a standard + * JAR file with a different file extension), add all required library jars + * into the root of the RAR archive, add a "META-INF/ra.xml" deployment + * descriptor as shown above as well as the corresponding Spring XML bean + * definition file(s) (typically "META-INF/applicationContext.xml"), + * and drop the resulting RAR file into your application server's + * deployment directory! + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setContextConfigLocation + * @see #loadBeanDefinitions + * @see ResourceAdapterApplicationContext + */ +public class SpringContextResourceAdapter implements ResourceAdapter { + + /** + * Any number of these characters are considered delimiters between + * multiple context config paths in a single String value. + * @see #setContextConfigLocation + */ + public static final String CONFIG_LOCATION_DELIMITERS = ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS; + + public static final String DEFAULT_CONTEXT_CONFIG_LOCATION = "META-INF/applicationContext.xml"; + + + protected final Log logger = LogFactory.getLog(getClass()); + + private String contextConfigLocation = DEFAULT_CONTEXT_CONFIG_LOCATION; + + private ConfigurableApplicationContext applicationContext; + + + /** + * Set the location of the context configuration files, within the + * resource adapter's deployment unit. This can be a delimited + * String that consists of multiple resource location, separated + * by commas, semicolons, whitespace, or line breaks. + *

This can be specified as "ContextConfigLocation" config + * property in the {@code ra.xml} deployment descriptor. + *

The default is "classpath:META-INF/applicationContext.xml". + */ + public void setContextConfigLocation(String contextConfigLocation) { + this.contextConfigLocation = contextConfigLocation; + } + + /** + * Return the specified context configuration files. + */ + protected String getContextConfigLocation() { + return this.contextConfigLocation; + } + + /** + * Return a new {@link StandardEnvironment}. + *

Subclasses may override this method in order to supply + * a custom {@link ConfigurableEnvironment} implementation. + */ + protected ConfigurableEnvironment createEnvironment() { + return new StandardEnvironment(); + } + + /** + * This implementation loads a Spring ApplicationContext through the + * {@link #createApplicationContext} template method. + */ + @Override + public void start(BootstrapContext bootstrapContext) throws ResourceAdapterInternalException { + if (logger.isInfoEnabled()) { + logger.info("Starting SpringContextResourceAdapter with BootstrapContext: " + bootstrapContext); + } + this.applicationContext = createApplicationContext(bootstrapContext); + } + + /** + * Build a Spring ApplicationContext for the given JCA BootstrapContext. + *

The default implementation builds a {@link ResourceAdapterApplicationContext} + * and delegates to {@link #loadBeanDefinitions} for actually parsing the + * specified configuration files. + * @param bootstrapContext this ResourceAdapter's BootstrapContext + * @return the Spring ApplicationContext instance + */ + protected ConfigurableApplicationContext createApplicationContext(BootstrapContext bootstrapContext) { + ResourceAdapterApplicationContext applicationContext = + new ResourceAdapterApplicationContext(bootstrapContext); + // Set ResourceAdapter's ClassLoader as bean class loader. + applicationContext.setClassLoader(getClass().getClassLoader()); + // Extract individual config locations. + String[] configLocations = + StringUtils.tokenizeToStringArray(getContextConfigLocation(), CONFIG_LOCATION_DELIMITERS); + if (configLocations != null) { + loadBeanDefinitions(applicationContext, configLocations); + } + applicationContext.refresh(); + return applicationContext; + } + + /** + * Load the bean definitions into the given registry, + * based on the specified configuration files. + * @param registry the registry to load into + * @param configLocations the parsed config locations + * @see #setContextConfigLocation + */ + protected void loadBeanDefinitions(BeanDefinitionRegistry registry, String[] configLocations) { + new XmlBeanDefinitionReader(registry).loadBeanDefinitions(configLocations); + } + + /** + * This implementation closes the Spring ApplicationContext. + */ + @Override + public void stop() { + logger.info("Stopping SpringContextResourceAdapter"); + this.applicationContext.close(); + } + + + /** + * This implementation always throws a NotSupportedException. + */ + @Override + public void endpointActivation(MessageEndpointFactory messageEndpointFactory, ActivationSpec activationSpec) + throws ResourceException { + + throw new NotSupportedException("SpringContextResourceAdapter does not support message endpoints"); + } + + /** + * This implementation does nothing. + */ + @Override + public void endpointDeactivation(MessageEndpointFactory messageEndpointFactory, ActivationSpec activationSpec) { + } + + /** + * This implementation always returns {@code null}. + */ + @Override + public XAResource[] getXAResources(ActivationSpec[] activationSpecs) throws ResourceException { + return null; + } + + + @Override + public boolean equals(Object obj) { + return (obj instanceof SpringContextResourceAdapter && + ObjectUtils.nullSafeEquals(getContextConfigLocation(), + ((SpringContextResourceAdapter) obj).getContextConfigLocation())); + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getContextConfigLocation()); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/context/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/context/package-info.java new file mode 100644 index 000000000..56a75a5a2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/context/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * Integration package that allows for deploying a Spring application context + * as a JCA 1.5 compliant RAR file. + * + */ +package com.fr.third.springframework.jca.context; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/context/ra.xml b/fine-spring/src/com/fr/third/springframework/jca/context/ra.xml new file mode 100644 index 000000000..4f901f56a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/context/ra.xml @@ -0,0 +1,17 @@ + + + Spring Framework + Spring Connector + 1.0 + + com.fr.third.springframework.jca.context.SpringContextResourceAdapter + + ContextConfigLocation + java.lang.String + META-INF/applicationContext.xml + + + diff --git a/fine-spring/src/com/fr/third/springframework/jca/endpoint/AbstractMessageEndpointFactory.java b/fine-spring/src/com/fr/third/springframework/jca/endpoint/AbstractMessageEndpointFactory.java new file mode 100644 index 000000000..a57c5b38f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/endpoint/AbstractMessageEndpointFactory.java @@ -0,0 +1,341 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.endpoint; + +import java.lang.reflect.Method; +import javax.resource.ResourceException; +import javax.resource.spi.ApplicationServerInternalException; +import javax.resource.spi.UnavailableException; +import javax.resource.spi.endpoint.MessageEndpoint; +import javax.resource.spi.endpoint.MessageEndpointFactory; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.xa.XAResource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.beans.factory.BeanNameAware; +import com.fr.third.springframework.transaction.jta.SimpleTransactionFactory; +import com.fr.third.springframework.transaction.jta.TransactionFactory; + +/** + * Abstract base implementation of the JCA 1.5/1.6/1.7 + * {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface, + * providing transaction management capabilities as well as ClassLoader + * exposure for endpoint invocations. + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setTransactionManager + */ +public abstract class AbstractMessageEndpointFactory implements MessageEndpointFactory, BeanNameAware { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private TransactionFactory transactionFactory; + + private String transactionName; + + private int transactionTimeout = -1; + + private String beanName; + + + /** + * Set the the XA transaction manager to use for wrapping endpoint + * invocations, enlisting the endpoint resource in each such transaction. + *

The passed-in object may be a transaction manager which implements + * Spring's {@link com.fr.third.springframework.transaction.jta.TransactionFactory} + * interface, or a plain {@link javax.transaction.TransactionManager}. + *

If no transaction manager is specified, the endpoint invocation + * will simply not be wrapped in an XA transaction. Check out your + * resource provider's ActivationSpec documentation for local + * transaction options of your particular provider. + * @see #setTransactionName + * @see #setTransactionTimeout + */ + public void setTransactionManager(Object transactionManager) { + if (transactionManager instanceof TransactionFactory) { + this.transactionFactory = (TransactionFactory) transactionManager; + } + else if (transactionManager instanceof TransactionManager) { + this.transactionFactory = new SimpleTransactionFactory((TransactionManager) transactionManager); + } + else { + throw new IllegalArgumentException("Transaction manager [" + transactionManager + + "] is neither a [com.fr.third.springframework.transaction.jta.TransactionFactory} nor a " + + "[javax.transaction.TransactionManager]"); + } + } + + /** + * Set the Spring TransactionFactory to use for wrapping endpoint + * invocations, enlisting the endpoint resource in each such transaction. + *

Alternatively, specify an appropriate transaction manager through + * the {@link #setTransactionManager "transactionManager"} property. + *

If no transaction factory is specified, the endpoint invocation + * will simply not be wrapped in an XA transaction. Check out your + * resource provider's ActivationSpec documentation for local + * transaction options of your particular provider. + * @see #setTransactionName + * @see #setTransactionTimeout + */ + public void setTransactionFactory(TransactionFactory transactionFactory) { + this.transactionFactory = transactionFactory; + } + + /** + * Specify the name of the transaction, if any. + *

Default is none. A specified name will be passed on to the transaction + * manager, allowing to identify the transaction in a transaction monitor. + */ + public void setTransactionName(String transactionName) { + this.transactionName = transactionName; + } + + /** + * Specify the transaction timeout, if any. + *

Default is -1: rely on the transaction manager's default timeout. + * Specify a concrete timeout to restrict the maximum duration of each + * endpoint invocation. + */ + public void setTransactionTimeout(int transactionTimeout) { + this.transactionTimeout = transactionTimeout; + } + + /** + * Set the name of this message endpoint. Populated with the bean name + * automatically when defined within Spring's bean factory. + */ + @Override + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + + /** + * Implementation of the JCA 1.7 {@code #getActivationName()} method, + * returning the bean name as set on this MessageEndpointFactory. + * @see #setBeanName + */ + public String getActivationName() { + return this.beanName; + } + + /** + * This implementation returns {@code true} if a transaction manager + * has been specified; {@code false} otherwise. + * @see #setTransactionManager + * @see #setTransactionFactory + */ + @Override + public boolean isDeliveryTransacted(Method method) throws NoSuchMethodException { + return (this.transactionFactory != null); + } + + /** + * The standard JCA 1.5 version of {@code createEndpoint}. + *

This implementation delegates to {@link #createEndpointInternal()}, + * initializing the endpoint's XAResource before the endpoint gets invoked. + */ + @Override + public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException { + AbstractMessageEndpoint endpoint = createEndpointInternal(); + endpoint.initXAResource(xaResource); + return endpoint; + } + + /** + * The alternative JCA 1.6 version of {@code createEndpoint}. + *

This implementation delegates to {@link #createEndpointInternal()}, + * ignoring the specified timeout. It is only here for JCA 1.6 compliance. + */ + public MessageEndpoint createEndpoint(XAResource xaResource, long timeout) throws UnavailableException { + AbstractMessageEndpoint endpoint = createEndpointInternal(); + endpoint.initXAResource(xaResource); + return endpoint; + } + + /** + * Create the actual endpoint instance, as a subclass of the + * {@link AbstractMessageEndpoint} inner class of this factory. + * @return the actual endpoint instance (never {@code null}) + * @throws UnavailableException if no endpoint is available at present + */ + protected abstract AbstractMessageEndpoint createEndpointInternal() throws UnavailableException; + + + /** + * Inner class for actual endpoint implementations, based on template + * method to allow for any kind of concrete endpoint implementation. + */ + protected abstract class AbstractMessageEndpoint implements MessageEndpoint { + + private TransactionDelegate transactionDelegate; + + private boolean beforeDeliveryCalled = false; + + private ClassLoader previousContextClassLoader; + + /** + * Initialize this endpoint's TransactionDelegate. + * @param xaResource the XAResource for this endpoint + */ + void initXAResource(XAResource xaResource) { + this.transactionDelegate = new TransactionDelegate(xaResource); + } + + /** + * This {@code beforeDelivery} implementation starts a transaction, + * if necessary, and exposes the endpoint ClassLoader as current + * thread context ClassLoader. + *

Note that the JCA 1.5 specification does not require a ResourceAdapter + * to call this method before invoking the concrete endpoint. If this method + * has not been called (check {@link #hasBeforeDeliveryBeenCalled()}), the + * concrete endpoint method should call {@code beforeDelivery} and its + * sibling {@link #afterDelivery()} explicitly, as part of its own processing. + */ + @Override + public void beforeDelivery(Method method) throws ResourceException { + this.beforeDeliveryCalled = true; + try { + this.transactionDelegate.beginTransaction(); + } + catch (Throwable ex) { + throw new ApplicationServerInternalException("Failed to begin transaction", ex); + } + Thread currentThread = Thread.currentThread(); + this.previousContextClassLoader = currentThread.getContextClassLoader(); + currentThread.setContextClassLoader(getEndpointClassLoader()); + } + + /** + * Template method for exposing the endpoint's ClassLoader + * (typically the ClassLoader that the message listener class + * has been loaded with). + * @return the endpoint ClassLoader (never {@code null}) + */ + protected abstract ClassLoader getEndpointClassLoader(); + + /** + * Return whether the {@link #beforeDelivery} method of this endpoint + * has already been called. + */ + protected final boolean hasBeforeDeliveryBeenCalled() { + return this.beforeDeliveryCalled; + } + + /** + * Callback method for notifying the endpoint base class + * that the concrete endpoint invocation led to an exception. + *

To be invoked by subclasses in case of the concrete + * endpoint throwing an exception. + * @param ex the exception thrown from the concrete endpoint + */ + protected final void onEndpointException(Throwable ex) { + this.transactionDelegate.setRollbackOnly(); + } + + /** + * This {@code afterDelivery} implementation resets the thread context + * ClassLoader and completes the transaction, if any. + *

Note that the JCA 1.5 specification does not require a ResourceAdapter + * to call this method after invoking the concrete endpoint. See the + * explanation in {@link #beforeDelivery}'s javadoc. + */ + @Override + public void afterDelivery() throws ResourceException { + this.beforeDeliveryCalled = false; + Thread.currentThread().setContextClassLoader(this.previousContextClassLoader); + this.previousContextClassLoader = null; + try { + this.transactionDelegate.endTransaction(); + } + catch (Throwable ex) { + throw new ApplicationServerInternalException("Failed to complete transaction", ex); + } + } + + @Override + public void release() { + try { + this.transactionDelegate.setRollbackOnly(); + this.transactionDelegate.endTransaction(); + } + catch (Throwable ex) { + logger.error("Could not complete unfinished transaction on endpoint release", ex); + } + } + } + + + /** + * Private inner class that performs the actual transaction handling, + * including enlistment of the endpoint's XAResource. + */ + private class TransactionDelegate { + + private final XAResource xaResource; + + private Transaction transaction; + + private boolean rollbackOnly; + + public TransactionDelegate(XAResource xaResource) { + if (xaResource == null) { + if (transactionFactory != null && !transactionFactory.supportsResourceAdapterManagedTransactions()) { + throw new IllegalStateException("ResourceAdapter-provided XAResource is required for " + + "transaction management. Check your ResourceAdapter's configuration."); + } + } + this.xaResource = xaResource; + } + + public void beginTransaction() throws Exception { + if (transactionFactory != null && this.xaResource != null) { + this.transaction = transactionFactory.createTransaction(transactionName, transactionTimeout); + this.transaction.enlistResource(this.xaResource); + } + } + + public void setRollbackOnly() { + if (this.transaction != null) { + this.rollbackOnly = true; + } + } + + public void endTransaction() throws Exception { + if (this.transaction != null) { + try { + if (this.rollbackOnly) { + this.transaction.rollback(); + } + else { + this.transaction.commit(); + } + } + finally { + this.transaction = null; + this.rollbackOnly = false; + } + } + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/endpoint/GenericMessageEndpointFactory.java b/fine-spring/src/com/fr/third/springframework/jca/endpoint/GenericMessageEndpointFactory.java new file mode 100644 index 000000000..d067f8d32 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/endpoint/GenericMessageEndpointFactory.java @@ -0,0 +1,160 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.endpoint; + +import javax.resource.ResourceException; +import javax.resource.spi.UnavailableException; +import javax.resource.spi.endpoint.MessageEndpoint; +import javax.transaction.xa.XAResource; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import com.fr.third.springframework.aop.framework.ProxyFactory; +import com.fr.third.springframework.aop.support.DelegatingIntroductionInterceptor; +import com.fr.third.springframework.util.ReflectionUtils; + +/** + * Generic implementation of the JCA 1.5 + * {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface, + * providing transaction management capabilities for any kind of message + * listener object (e.g. {@link javax.jms.MessageListener} objects or + * {@link javax.resource.cci.MessageListener} objects. + * + *

Uses AOP proxies for concrete endpoint instances, simply wrapping + * the specified message listener object and exposing all of its implemented + * interfaces on the endpoint instance. + * + *

Typically used with Spring's {@link GenericMessageEndpointManager}, + * but not tied to it. As a consequence, this endpoint factory could + * also be used with programmatic endpoint management on a native + * {@link javax.resource.spi.ResourceAdapter} instance. + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setMessageListener + * @see #setTransactionManager + * @see GenericMessageEndpointManager + */ +public class GenericMessageEndpointFactory extends AbstractMessageEndpointFactory { + + private Object messageListener; + + + /** + * Specify the message listener object that the endpoint should expose + * (e.g. a {@link javax.jms.MessageListener} objects or + * {@link javax.resource.cci.MessageListener} implementation). + */ + public void setMessageListener(Object messageListener) { + this.messageListener = messageListener; + } + + /** + * Wrap each concrete endpoint instance with an AOP proxy, + * exposing the message listener's interfaces as well as the + * endpoint SPI through an AOP introduction. + */ + @Override + public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException { + GenericMessageEndpoint endpoint = (GenericMessageEndpoint) super.createEndpoint(xaResource); + ProxyFactory proxyFactory = new ProxyFactory(this.messageListener); + DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(endpoint); + introduction.suppressInterface(MethodInterceptor.class); + proxyFactory.addAdvice(introduction); + return (MessageEndpoint) proxyFactory.getProxy(); + } + + /** + * Creates a concrete generic message endpoint, internal to this factory. + */ + @Override + protected AbstractMessageEndpoint createEndpointInternal() throws UnavailableException { + return new GenericMessageEndpoint(); + } + + + /** + * Private inner class that implements the concrete generic message endpoint, + * as an AOP Alliance MethodInterceptor that will be invoked by a proxy. + */ + private class GenericMessageEndpoint extends AbstractMessageEndpoint implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + boolean applyDeliveryCalls = !hasBeforeDeliveryBeenCalled(); + if (applyDeliveryCalls) { + try { + beforeDelivery(null); + } + catch (ResourceException ex) { + if (ReflectionUtils.declaresException(methodInvocation.getMethod(), ex.getClass())) { + throw ex; + } + else { + throw new InternalResourceException(ex); + } + } + } + try { + return methodInvocation.proceed(); + } + catch (Throwable ex) { + onEndpointException(ex); + throw ex; + } + finally { + if (applyDeliveryCalls) { + try { + afterDelivery(); + } + catch (ResourceException ex) { + if (ReflectionUtils.declaresException(methodInvocation.getMethod(), ex.getClass())) { + throw ex; + } + else { + throw new InternalResourceException(ex); + } + } + } + } + } + + @Override + protected ClassLoader getEndpointClassLoader() { + return messageListener.getClass().getClassLoader(); + } + } + + + /** + * Internal exception thrown when a ResourceException has been encountered + * during the endpoint invocation. + *

Will only be used if the ResourceAdapter does not invoke the + * endpoint's {@code beforeDelivery} and {@code afterDelivery} + * directly, leaving it up to the concrete endpoint to apply those - + * and to handle any ResourceExceptions thrown from them. + */ + @SuppressWarnings("serial") + public static class InternalResourceException extends RuntimeException { + + protected InternalResourceException(ResourceException cause) { + super(cause); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/endpoint/GenericMessageEndpointManager.java b/fine-spring/src/com/fr/third/springframework/jca/endpoint/GenericMessageEndpointManager.java new file mode 100644 index 000000000..3fe670cb1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/endpoint/GenericMessageEndpointManager.java @@ -0,0 +1,334 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.endpoint; + +import javax.resource.ResourceException; +import javax.resource.spi.ActivationSpec; +import javax.resource.spi.ResourceAdapter; +import javax.resource.spi.endpoint.MessageEndpointFactory; + +import com.fr.third.springframework.beans.factory.DisposableBean; +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.context.SmartLifecycle; + +/** + * Generic bean that manages JCA 1.5 message endpoints within a Spring + * application context, activating and deactivating the endpoint as part + * of the application context's lifecycle. + * + *

This class is completely generic in that it may work with any + * ResourceAdapter, any MessageEndpointFactory, and any ActivationSpec. + * It can be configured in standard bean style, for example through + * Spring's XML bean definition format, as follows: + * + *

+ * <bean class="com.fr.third.springframework.jca.endpoint.GenericMessageEndpointManager">
+ * 	 <property name="resourceAdapter" ref="resourceAdapter"/>
+ * 	 <property name="messageEndpointFactory">
+ *     <bean class="com.fr.third.springframework.jca.endpoint.GenericMessageEndpointFactory">
+ *       <property name="messageListener" ref="messageListener"/>
+ *     </bean>
+ * 	 </property>
+ * 	 <property name="activationSpec">
+ *     <bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
+ *       <property name="destination" value="myQueue"/>
+ *       <property name="destinationType" value="javax.jms.Queue"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ * + * In this example, Spring's own {@link GenericMessageEndpointFactory} is used + * to point to a standard message listener object that happens to be supported + * by the specified target ResourceAdapter: in this case, a JMS + * {@link javax.jms.MessageListener} object as supported by the ActiveMQ + * message broker, defined as a Spring bean: + * + *
+ * <bean id="messageListener" class="com.myorg.messaging.myMessageListener">
+ *   ...
+ * </bean>
+ * + * The target ResourceAdapter may be configured as a local Spring bean as well + * (the typical case) or obtained from JNDI (e.g. on WebLogic). For the + * example above, a local ResourceAdapter bean could be defined as follows + * (matching the "resourceAdapter" bean reference above): + * + *
+ * <bean id="resourceAdapter" class="com.fr.third.springframework.jca.support.ResourceAdapterFactoryBean">
+ *   <property name="resourceAdapter">
+ *     <bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
+ *       <property name="serverUrl" value="tcp://localhost:61616"/>
+ *     </bean>
+ *   </property>
+ *   <property name="workManager">
+ *     <bean class="com.fr.third.springframework.jca.work.SimpleTaskWorkManager"/>
+ *   </property>
+ * </bean>
+ * + * For a different target resource, the configuration would simply point to a + * different ResourceAdapter and a different ActivationSpec object (which are + * both specific to the resource provider), and possibly a different message + * listener (e.g. a CCI {@link javax.resource.cci.MessageListener} for a + * resource adapter which is based on the JCA Common Client Interface). + * + *

The asynchronous execution strategy can be customized through the + * "workManager" property on the ResourceAdapterFactoryBean (as shown above). + * Check out {@link com.fr.third.springframework.jca.work.SimpleTaskWorkManager}'s + * javadoc for its configuration options; alternatively, any other + * JCA-compliant WorkManager can be used (e.g. Geronimo's). + * + *

Transactional execution is a responsibility of the concrete message endpoint, + * as built by the specified MessageEndpointFactory. {@link GenericMessageEndpointFactory} + * supports XA transaction participation through its "transactionManager" property, + * typically with a Spring {@link com.fr.third.springframework.transaction.jta.JtaTransactionManager} + * or a plain {@link javax.transaction.TransactionManager} implementation specified there. + * + *

+ * <bean class="com.fr.third.springframework.jca.endpoint.GenericMessageEndpointManager">
+ * 	 <property name="resourceAdapter" ref="resourceAdapter"/>
+ * 	 <property name="messageEndpointFactory">
+ *     <bean class="com.fr.third.springframework.jca.endpoint.GenericMessageEndpointFactory">
+ *       <property name="messageListener" ref="messageListener"/>
+ *       <property name="transactionManager" ref="transactionManager"/>
+ *     </bean>
+ * 	 </property>
+ * 	 <property name="activationSpec">
+ *     <bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
+ *       <property name="destination" value="myQueue"/>
+ *       <property name="destinationType" value="javax.jms.Queue"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ *
+ * <bean id="transactionManager" class="com.fr.third.springframework.transaction.jta.JtaTransactionManager"/>
+ * + * Alternatively, check out your resource provider's ActivationSpec object, + * which should support local transactions through a provider-specific config flag, + * e.g. ActiveMQActivationSpec's "useRAManagedTransaction" bean property. + * + *
+ * <bean class="com.fr.third.springframework.jca.endpoint.GenericMessageEndpointManager">
+ * 	 <property name="resourceAdapter" ref="resourceAdapter"/>
+ * 	 <property name="messageEndpointFactory">
+ *     <bean class="com.fr.third.springframework.jca.endpoint.GenericMessageEndpointFactory">
+ *       <property name="messageListener" ref="messageListener"/>
+ *     </bean>
+ * 	 </property>
+ * 	 <property name="activationSpec">
+ *     <bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
+ *       <property name="destination" value="myQueue"/>
+ *       <property name="destinationType" value="javax.jms.Queue"/>
+ *       <property name="useRAManagedTransaction" value="true"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.resource.spi.ResourceAdapter#endpointActivation + * @see javax.resource.spi.ResourceAdapter#endpointDeactivation + * @see javax.resource.spi.endpoint.MessageEndpointFactory + * @see javax.resource.spi.ActivationSpec + */ +public class GenericMessageEndpointManager implements SmartLifecycle, InitializingBean, DisposableBean { + + private ResourceAdapter resourceAdapter; + + private MessageEndpointFactory messageEndpointFactory; + + private ActivationSpec activationSpec; + + private boolean autoStartup = true; + + private int phase = Integer.MAX_VALUE; + + private boolean running = false; + + private final Object lifecycleMonitor = new Object(); + + + /** + * Set the JCA ResourceAdapter to manage endpoints for. + */ + public void setResourceAdapter(ResourceAdapter resourceAdapter) { + this.resourceAdapter = resourceAdapter; + } + + /** + * Return the JCA ResourceAdapter to manage endpoints for. + */ + public ResourceAdapter getResourceAdapter() { + return this.resourceAdapter; + } + + /** + * Set the JCA MessageEndpointFactory to activate, pointing to a + * MessageListener object that the endpoints will delegate to. + *

A MessageEndpointFactory instance may be shared across multiple + * endpoints (i.e. multiple GenericMessageEndpointManager instances), + * with different {@link #setActivationSpec ActivationSpec} objects applied. + * @see GenericMessageEndpointFactory#setMessageListener + */ + public void setMessageEndpointFactory(MessageEndpointFactory messageEndpointFactory) { + this.messageEndpointFactory = messageEndpointFactory; + } + + /** + * Return the JCA MessageEndpointFactory to activate. + */ + public MessageEndpointFactory getMessageEndpointFactory() { + return this.messageEndpointFactory; + } + + /** + * Set the JCA ActivationSpec to use for activating the endpoint. + *

Note that this ActivationSpec instance should not be shared + * across multiple ResourceAdapter instances. + */ + public void setActivationSpec(ActivationSpec activationSpec) { + this.activationSpec = activationSpec; + } + + /** + * Return the JCA ActivationSpec to use for activating the endpoint. + */ + public ActivationSpec getActivationSpec() { + return this.activationSpec; + } + + /** + * Set whether to auto-start the endpoint activation after this endpoint + * manager has been initialized and the context has been refreshed. + *

Default is "true". Turn this flag off to defer the endpoint + * activation until an explicit {#start()} call. + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * Return the value for the 'autoStartup' property. If "true", this + * endpoint manager will start upon a ContextRefreshedEvent. + */ + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * Specify the phase in which this endpoint manager should be started + * and stopped. The startup order proceeds from lowest to highest, and + * the shutdown order is the reverse of that. By default this value is + * Integer.MAX_VALUE meaning that this endpoint manager starts as late + * as possible and stops as soon as possible. + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * Return the phase in which this endpoint manager will be started and stopped. + */ + @Override + public int getPhase() { + return this.phase; + } + + /** + * Prepares the message endpoint, and automatically activates it + * if the "autoStartup" flag is set to "true". + */ + @Override + public void afterPropertiesSet() throws ResourceException { + if (getResourceAdapter() == null) { + throw new IllegalArgumentException("Property 'resourceAdapter' is required"); + } + if (getMessageEndpointFactory() == null) { + throw new IllegalArgumentException("Property 'messageEndpointFactory' is required"); + } + ActivationSpec activationSpec = getActivationSpec(); + if (activationSpec == null) { + throw new IllegalArgumentException("Property 'activationSpec' is required"); + } + + if (activationSpec.getResourceAdapter() == null) { + activationSpec.setResourceAdapter(getResourceAdapter()); + } + else if (activationSpec.getResourceAdapter() != getResourceAdapter()) { + throw new IllegalArgumentException("ActivationSpec [" + activationSpec + + "] is associated with a different ResourceAdapter: " + activationSpec.getResourceAdapter()); + } + } + + /** + * Activates the configured message endpoint. + */ + @Override + public void start() { + synchronized (this.lifecycleMonitor) { + if (!this.running) { + try { + getResourceAdapter().endpointActivation(getMessageEndpointFactory(), getActivationSpec()); + } + catch (ResourceException ex) { + throw new IllegalStateException("Could not activate message endpoint", ex); + } + this.running = true; + } + } + } + + /** + * Deactivates the configured message endpoint. + */ + @Override + public void stop() { + synchronized (this.lifecycleMonitor) { + if (this.running) { + getResourceAdapter().endpointDeactivation(getMessageEndpointFactory(), getActivationSpec()); + this.running = false; + } + } + } + + @Override + public void stop(Runnable callback) { + synchronized (this.lifecycleMonitor) { + this.stop(); + callback.run(); + } + } + + /** + * Return whether the configured message endpoint is currently active. + */ + @Override + public boolean isRunning() { + synchronized (this.lifecycleMonitor) { + return this.running; + } + } + + /** + * Deactivates the message endpoint, preparing it for shutdown. + */ + @Override + public void destroy() { + stop(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/endpoint/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/endpoint/package-info.java new file mode 100644 index 000000000..1fddfb7e3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/endpoint/package-info.java @@ -0,0 +1,8 @@ + +/** + * + * This package provides a facility for generic JCA message endpoint management. + * + */ +package com.fr.third.springframework.jca.endpoint; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/support/LocalConnectionFactoryBean.java b/fine-spring/src/com/fr/third/springframework/jca/support/LocalConnectionFactoryBean.java new file mode 100644 index 000000000..fb2119252 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/support/LocalConnectionFactoryBean.java @@ -0,0 +1,141 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.support; + +import javax.resource.ResourceException; +import javax.resource.spi.ConnectionManager; +import javax.resource.spi.ManagedConnectionFactory; + +import com.fr.third.springframework.beans.factory.FactoryBean; +import com.fr.third.springframework.beans.factory.InitializingBean; + +/** + * {@link com.fr.third.springframework.beans.factory.FactoryBean} that creates + * a local JCA connection factory in "non-managed" mode (as defined by the + * Java Connector Architecture specification). This is a direct alternative + * to a {@link com.fr.third.springframework.jndi.JndiObjectFactoryBean} definition that + * obtains a connection factory handle from a J2EE server's naming environment. + * + *

The type of the connection factory is dependent on the actual connector: + * the connector can either expose its native API (such as a JDBC + * {@link javax.sql.DataSource} or a JMS {@link javax.jms.ConnectionFactory}) + * or follow the standard Common Client Interface (CCI), as defined by the JCA spec. + * The exposed interface in the CCI case is {@link javax.resource.cci.ConnectionFactory}. + * + *

In order to use this FactoryBean, you must specify the connector's + * {@link #setManagedConnectionFactory "managedConnectionFactory"} (usually + * configured as separate JavaBean), which will be used to create the actual + * connection factory reference as exposed to the application. Optionally, + * you can also specify a {@link #setConnectionManager "connectionManager"}, + * in order to use a custom ConnectionManager instead of the connector's default. + * + *

NOTE: In non-managed mode, a connector is not deployed on an + * application server, or more specificially not interacting with an application + * server. Consequently, it cannot use a J2EE server's system contracts: + * connection management, transaction management, and security management. + * A custom ConnectionManager implementation has to be used for applying those + * services in conjunction with a standalone transaction coordinator etc. + * + *

The connector will use a local ConnectionManager (included in the connector) + * by default, which cannot participate in global transactions due to the lack + * of XA enlistment. You need to specify an XA-capable ConnectionManager in + * order to make the connector interact with an XA transaction coordinator. + * Alternatively, simply use the native local transaction facilities of the + * exposed API (e.g. CCI local transactions), or use a corresponding + * implementation of Spring's PlatformTransactionManager SPI + * (e.g. {@link com.fr.third.springframework.jca.cci.connection.CciLocalTransactionManager}) + * to drive local transactions. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #setManagedConnectionFactory + * @see #setConnectionManager + * @see javax.resource.cci.ConnectionFactory + * @see javax.resource.cci.Connection#getLocalTransaction + * @see com.fr.third.springframework.jca.cci.connection.CciLocalTransactionManager + */ +public class LocalConnectionFactoryBean implements FactoryBean, InitializingBean { + + private ManagedConnectionFactory managedConnectionFactory; + + private ConnectionManager connectionManager; + + private Object connectionFactory; + + + /** + * Set the JCA ManagerConnectionFactory that should be used to create + * the desired connection factory. + *

The ManagerConnectionFactory will usually be set up as separate bean + * (potentially as inner bean), populated with JavaBean properties: + * a ManagerConnectionFactory is encouraged to follow the JavaBean pattern + * by the JCA specification, analogous to a JDBC DataSource and a JDO + * PersistenceManagerFactory. + *

Note that the ManagerConnectionFactory implementation might expect + * a reference to its JCA 1.5 ResourceAdapter, expressed through the + * {@link javax.resource.spi.ResourceAdapterAssociation} interface. + * Simply inject the corresponding ResourceAdapter instance into its + * "resourceAdapter" bean property in this case, before passing the + * ManagerConnectionFactory into this LocalConnectionFactoryBean. + * @see javax.resource.spi.ManagedConnectionFactory#createConnectionFactory() + */ + public void setManagedConnectionFactory(ManagedConnectionFactory managedConnectionFactory) { + this.managedConnectionFactory = managedConnectionFactory; + } + + /** + * Set the JCA ConnectionManager that should be used to create the + * desired connection factory. + *

A ConnectionManager implementation for local usage is often + * included with a JCA connector. Such an included ConnectionManager + * might be set as default, with no need to explicitly specify one. + * @see javax.resource.spi.ManagedConnectionFactory#createConnectionFactory(javax.resource.spi.ConnectionManager) + */ + public void setConnectionManager(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + @Override + public void afterPropertiesSet() throws ResourceException { + if (this.managedConnectionFactory == null) { + throw new IllegalArgumentException("Property 'managedConnectionFactory' is required"); + } + if (this.connectionManager != null) { + this.connectionFactory = this.managedConnectionFactory.createConnectionFactory(this.connectionManager); + } + else { + this.connectionFactory = this.managedConnectionFactory.createConnectionFactory(); + } + } + + + @Override + public Object getObject() { + return this.connectionFactory; + } + + @Override + public Class getObjectType() { + return (this.connectionFactory != null ? this.connectionFactory.getClass() : null); + } + + @Override + public boolean isSingleton() { + return true; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/support/ResourceAdapterFactoryBean.java b/fine-spring/src/com/fr/third/springframework/jca/support/ResourceAdapterFactoryBean.java new file mode 100644 index 000000000..ddb51ef98 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/support/ResourceAdapterFactoryBean.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.support; + +import javax.resource.ResourceException; +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.ResourceAdapter; +import javax.resource.spi.XATerminator; +import javax.resource.spi.work.WorkManager; + +import com.fr.third.springframework.beans.BeanUtils; +import com.fr.third.springframework.beans.factory.DisposableBean; +import com.fr.third.springframework.beans.factory.FactoryBean; +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.util.Assert; + +/** + * {@link com.fr.third.springframework.beans.factory.FactoryBean} that bootstraps + * the specified JCA 1.5 {@link javax.resource.spi.ResourceAdapter}, + * starting it with a local {@link javax.resource.spi.BootstrapContext} + * and exposing it for bean references. It will also stop the ResourceAdapter + * on context shutdown. This corresponds to 'non-managed' bootstrap in a + * local environment, according to the JCA 1.5 specification. + * + *

This is essentially an adapter for bean-style bootstrapping of a + * JCA ResourceAdapter, allowing the BootstrapContext or its elements + * (such as the JCA WorkManager) to be specified through bean properties. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see #setResourceAdapter + * @see #setBootstrapContext + * @see #setWorkManager + * @see javax.resource.spi.ResourceAdapter#start(javax.resource.spi.BootstrapContext) + * @see javax.resource.spi.ResourceAdapter#stop() + */ +public class ResourceAdapterFactoryBean implements FactoryBean, InitializingBean, DisposableBean { + + private ResourceAdapter resourceAdapter; + + private BootstrapContext bootstrapContext; + + private WorkManager workManager; + + private XATerminator xaTerminator; + + + /** + * Specify the target JCA ResourceAdapter as class, to be instantiated + * with its default configuration. + *

Alternatively, specify a pre-configured ResourceAdapter instance + * through the "resourceAdapter" property. + * @see #setResourceAdapter + */ + public void setResourceAdapterClass(Class resourceAdapterClass) { + Assert.isAssignable(ResourceAdapter.class, resourceAdapterClass); + this.resourceAdapter = (ResourceAdapter) BeanUtils.instantiateClass(resourceAdapterClass); + } + + /** + * Specify the target JCA ResourceAdapter, passed in as configured instance + * which hasn't been started yet. This will typically happen as an + * inner bean definition, configuring the ResourceAdapter instance + * through its vendor-specific bean properties. + */ + public void setResourceAdapter(ResourceAdapter resourceAdapter) { + this.resourceAdapter = resourceAdapter; + } + + /** + * Specify the JCA BootstrapContext to use for starting the ResourceAdapter. + *

Alternatively, you can specify the individual parts (such as the + * JCA WorkManager) as individual references. + * @see #setWorkManager + * @see #setXaTerminator + */ + public void setBootstrapContext(BootstrapContext bootstrapContext) { + this.bootstrapContext = bootstrapContext; + } + + /** + * Specify the JCA WorkManager to use for bootstrapping the ResourceAdapter. + * @see #setBootstrapContext + */ + public void setWorkManager(WorkManager workManager) { + this.workManager = workManager; + } + + /** + * Specify the JCA XATerminator to use for bootstrapping the ResourceAdapter. + * @see #setBootstrapContext + */ + public void setXaTerminator(XATerminator xaTerminator) { + this.xaTerminator = xaTerminator; + } + + + /** + * Builds the BootstrapContext and starts the ResourceAdapter with it. + * @see javax.resource.spi.ResourceAdapter#start(javax.resource.spi.BootstrapContext) + */ + @Override + public void afterPropertiesSet() throws ResourceException { + if (this.resourceAdapter == null) { + throw new IllegalArgumentException("'resourceAdapter' or 'resourceAdapterClass' is required"); + } + if (this.bootstrapContext == null) { + this.bootstrapContext = new SimpleBootstrapContext(this.workManager, this.xaTerminator); + } + this.resourceAdapter.start(this.bootstrapContext); + } + + + @Override + public ResourceAdapter getObject() { + return this.resourceAdapter; + } + + @Override + public Class getObjectType() { + return (this.resourceAdapter != null ? this.resourceAdapter.getClass() : ResourceAdapter.class); + } + + @Override + public boolean isSingleton() { + return true; + } + + + /** + * Stops the ResourceAdapter. + * @see javax.resource.spi.ResourceAdapter#stop() + */ + @Override + public void destroy() { + this.resourceAdapter.stop(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/support/SimpleBootstrapContext.java b/fine-spring/src/com/fr/third/springframework/jca/support/SimpleBootstrapContext.java new file mode 100644 index 000000000..0de9de5a9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/support/SimpleBootstrapContext.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.support; + +import java.util.Timer; + +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.UnavailableException; +import javax.resource.spi.XATerminator; +import javax.resource.spi.work.WorkManager; + +/** + * Simple implementation of the JCA 1.5 {@link javax.resource.spi.BootstrapContext} + * interface, used for bootstrapping a JCA ResourceAdapter in a local environment. + * + *

Delegates to the given WorkManager and XATerminator, if any. Creates simple + * local instances of {@code java.util.Timer}. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see javax.resource.spi.ResourceAdapter#start(javax.resource.spi.BootstrapContext) + * @see ResourceAdapterFactoryBean + */ +public class SimpleBootstrapContext implements BootstrapContext { + + private WorkManager workManager; + + private XATerminator xaTerminator; + + + /** + * Create a new SimpleBootstrapContext for the given WorkManager, + * with no XATerminator available. + * @param workManager the JCA WorkManager to use (may be {@code null}) + */ + public SimpleBootstrapContext(WorkManager workManager) { + this.workManager = workManager; + } + + /** + * Create a new SimpleBootstrapContext for the given WorkManager and XATerminator. + * @param workManager the JCA WorkManager to use (may be {@code null}) + * @param xaTerminator the JCA XATerminator to use (may be {@code null}) + */ + public SimpleBootstrapContext(WorkManager workManager, XATerminator xaTerminator) { + this.workManager = workManager; + this.xaTerminator = xaTerminator; + } + + + @Override + public WorkManager getWorkManager() { + if (this.workManager == null) { + throw new IllegalStateException("No WorkManager available"); + } + return this.workManager; + } + + @Override + public XATerminator getXATerminator() { + return this.xaTerminator; + } + + @Override + public Timer createTimer() throws UnavailableException { + return new Timer(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/support/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/support/package-info.java new file mode 100644 index 000000000..9387c4b2e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/support/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * Provides generic support classes for JCA usage within Spring, + * mainly for local setup of a JCA ResourceAdapter and/or ConnectionFactory. + * + */ +package com.fr.third.springframework.jca.support; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/work/DelegatingWork.java b/fine-spring/src/com/fr/third/springframework/jca/work/DelegatingWork.java new file mode 100644 index 000000000..ca2ffe300 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/work/DelegatingWork.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.work; + +import javax.resource.spi.work.Work; + +import com.fr.third.springframework.util.Assert; + +/** + * Simple Work adapter that delegates to a given Runnable. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see javax.resource.spi.work.Work + * @see Runnable + */ +public class DelegatingWork implements Work { + + private final Runnable delegate; + + + /** + * Create a new DelegatingWork. + * @param delegate the Runnable implementation to delegate to + */ + public DelegatingWork(Runnable delegate) { + Assert.notNull(delegate, "Delegate must not be null"); + this.delegate = delegate; + } + + /** + * Return the wrapped Runnable implementation. + */ + public final Runnable getDelegate() { + return this.delegate; + } + + + /** + * Delegates execution to the underlying Runnable. + */ + @Override + public void run() { + this.delegate.run(); + } + + /** + * This implementation is empty, since we expect the Runnable + * to terminate based on some specific shutdown signal. + */ + @Override + public void release() { + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/work/SimpleTaskWorkManager.java b/fine-spring/src/com/fr/third/springframework/jca/work/SimpleTaskWorkManager.java new file mode 100644 index 000000000..370d4baa6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/work/SimpleTaskWorkManager.java @@ -0,0 +1,260 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.work; + +import javax.resource.spi.work.ExecutionContext; +import javax.resource.spi.work.Work; +import javax.resource.spi.work.WorkAdapter; +import javax.resource.spi.work.WorkCompletedException; +import javax.resource.spi.work.WorkEvent; +import javax.resource.spi.work.WorkException; +import javax.resource.spi.work.WorkListener; +import javax.resource.spi.work.WorkManager; +import javax.resource.spi.work.WorkRejectedException; + +import com.fr.third.springframework.core.task.AsyncTaskExecutor; +import com.fr.third.springframework.core.task.SimpleAsyncTaskExecutor; +import com.fr.third.springframework.core.task.SyncTaskExecutor; +import com.fr.third.springframework.core.task.TaskExecutor; +import com.fr.third.springframework.core.task.TaskRejectedException; +import com.fr.third.springframework.core.task.TaskTimeoutException; +import com.fr.third.springframework.util.Assert; + +/** + * Simple JCA 1.5 {@link javax.resource.spi.work.WorkManager} implementation that + * delegates to a Spring {@link com.fr.third.springframework.core.task.TaskExecutor}. + * Provides simple task execution including start timeouts, but without support + * for a JCA ExecutionContext (i.e. without support for imported transactions). + * + *

Uses a {@link com.fr.third.springframework.core.task.SyncTaskExecutor} for {@link #doWork} + * calls and a {@link com.fr.third.springframework.core.task.SimpleAsyncTaskExecutor} + * for {@link #startWork} and {@link #scheduleWork} calls, by default. + * These default task executors can be overridden through configuration. + * + *

NOTE: This WorkManager does not provide thread pooling by default! + * Specify a {@link com.fr.third.springframework.scheduling.concurrent.ThreadPoolTaskExecutor} + * (or any other thread-pooling TaskExecutor) as "asyncTaskExecutor" in order to + * achieve actual thread pooling. + * + *

This WorkManager automatically detects a specified + * {@link com.fr.third.springframework.core.task.AsyncTaskExecutor} implementation + * and uses its extended timeout functionality where appropriate. + * JCA WorkListeners are fully supported in any case. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see #setSyncTaskExecutor + * @see #setAsyncTaskExecutor + */ +public class SimpleTaskWorkManager implements WorkManager { + + private TaskExecutor syncTaskExecutor = new SyncTaskExecutor(); + + private AsyncTaskExecutor asyncTaskExecutor = new SimpleAsyncTaskExecutor(); + + + /** + * Specify the TaskExecutor to use for synchronous work execution + * (i.e. {@link #doWork} calls). + *

Default is a {@link com.fr.third.springframework.core.task.SyncTaskExecutor}. + */ + public void setSyncTaskExecutor(TaskExecutor syncTaskExecutor) { + this.syncTaskExecutor = syncTaskExecutor; + } + + /** + * Specify the TaskExecutor to use for asynchronous work execution + * (i.e. {@link #startWork} and {@link #scheduleWork} calls). + *

This will typically (but not necessarily) be an + * {@link com.fr.third.springframework.core.task.AsyncTaskExecutor} implementation. + * Default is a {@link com.fr.third.springframework.core.task.SimpleAsyncTaskExecutor}. + */ + public void setAsyncTaskExecutor(AsyncTaskExecutor asyncTaskExecutor) { + this.asyncTaskExecutor = asyncTaskExecutor; + } + + + @Override + public void doWork(Work work) throws WorkException { + doWork(work, WorkManager.INDEFINITE, null, null); + } + + @Override + public void doWork(Work work, long startTimeout, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + Assert.state(this.syncTaskExecutor != null, "No 'syncTaskExecutor' set"); + executeWork(this.syncTaskExecutor, work, startTimeout, false, executionContext, workListener); + } + + @Override + public long startWork(Work work) throws WorkException { + return startWork(work, WorkManager.INDEFINITE, null, null); + } + + @Override + public long startWork(Work work, long startTimeout, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + Assert.state(this.asyncTaskExecutor != null, "No 'asyncTaskExecutor' set"); + return executeWork(this.asyncTaskExecutor, work, startTimeout, true, executionContext, workListener); + } + + @Override + public void scheduleWork(Work work) throws WorkException { + scheduleWork(work, WorkManager.INDEFINITE, null, null); + } + + @Override + public void scheduleWork(Work work, long startTimeout, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + Assert.state(this.asyncTaskExecutor != null, "No 'asyncTaskExecutor' set"); + executeWork(this.asyncTaskExecutor, work, startTimeout, false, executionContext, workListener); + } + + + /** + * Execute the given Work on the specified TaskExecutor. + * @param taskExecutor the TaskExecutor to use + * @param work the Work to execute + * @param startTimeout the time duration within which the Work is supposed to start + * @param blockUntilStarted whether to block until the Work has started + * @param executionContext the JCA ExecutionContext for the given Work + * @param workListener the WorkListener to clal for the given Work + * @return the time elapsed from Work acceptance until start of execution + * (or -1 if not applicable or not known) + * @throws WorkException if the TaskExecutor did not accept the Work + */ + protected long executeWork(TaskExecutor taskExecutor, Work work, long startTimeout, + boolean blockUntilStarted, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + if (executionContext != null && executionContext.getXid() != null) { + throw new WorkException("SimpleTaskWorkManager does not supported imported XIDs: " + executionContext.getXid()); + } + WorkListener workListenerToUse = workListener; + if (workListenerToUse == null) { + workListenerToUse = new WorkAdapter(); + } + + boolean isAsync = (taskExecutor instanceof AsyncTaskExecutor); + DelegatingWorkAdapter workHandle = new DelegatingWorkAdapter(work, workListenerToUse, !isAsync); + try { + if (isAsync) { + ((AsyncTaskExecutor) taskExecutor).execute(workHandle, startTimeout); + } + else { + taskExecutor.execute(workHandle); + } + } + catch (TaskTimeoutException ex) { + WorkException wex = new WorkRejectedException("TaskExecutor rejected Work because of timeout: " + work, ex); + wex.setErrorCode(WorkException.START_TIMED_OUT); + workListenerToUse.workRejected(new WorkEvent(this, WorkEvent.WORK_REJECTED, work, wex)); + throw wex; + } + catch (TaskRejectedException ex) { + WorkException wex = new WorkRejectedException("TaskExecutor rejected Work: " + work, ex); + wex.setErrorCode(WorkException.INTERNAL); + workListenerToUse.workRejected(new WorkEvent(this, WorkEvent.WORK_REJECTED, work, wex)); + throw wex; + } + catch (Throwable ex) { + WorkException wex = new WorkException("TaskExecutor failed to execute Work: " + work, ex); + wex.setErrorCode(WorkException.INTERNAL); + throw wex; + } + if (isAsync) { + workListenerToUse.workAccepted(new WorkEvent(this, WorkEvent.WORK_ACCEPTED, work, null)); + } + + if (blockUntilStarted) { + long acceptanceTime = System.currentTimeMillis(); + synchronized (workHandle.monitor) { + try { + while (!workHandle.started) { + workHandle.monitor.wait(); + } + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + return (System.currentTimeMillis() - acceptanceTime); + } + else { + return WorkManager.UNKNOWN; + } + } + + + /** + * Work adapter that supports start timeouts and WorkListener callbacks + * for a given Work that it delegates to. + */ + private static class DelegatingWorkAdapter implements Work { + + private final Work work; + + private final WorkListener workListener; + + private final boolean acceptOnExecution; + + public final Object monitor = new Object(); + + public boolean started = false; + + public DelegatingWorkAdapter(Work work, WorkListener workListener, boolean acceptOnExecution) { + this.work = work; + this.workListener = workListener; + this.acceptOnExecution = acceptOnExecution; + } + + @Override + public void run() { + if (this.acceptOnExecution) { + this.workListener.workAccepted(new WorkEvent(this, WorkEvent.WORK_ACCEPTED, work, null)); + } + synchronized (this.monitor) { + this.started = true; + this.monitor.notify(); + } + this.workListener.workStarted(new WorkEvent(this, WorkEvent.WORK_STARTED, this.work, null)); + try { + this.work.run(); + } + catch (RuntimeException ex) { + this.workListener.workCompleted( + new WorkEvent(this, WorkEvent.WORK_COMPLETED, this.work, new WorkCompletedException(ex))); + throw ex; + } + catch (Error err) { + this.workListener.workCompleted( + new WorkEvent(this, WorkEvent.WORK_COMPLETED, this.work, new WorkCompletedException(err))); + throw err; + } + this.workListener.workCompleted(new WorkEvent(this, WorkEvent.WORK_COMPLETED, this.work, null)); + } + + @Override + public void release() { + this.work.release(); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/work/WorkManagerTaskExecutor.java b/fine-spring/src/com/fr/third/springframework/jca/work/WorkManagerTaskExecutor.java new file mode 100644 index 000000000..ac86faef8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/work/WorkManagerTaskExecutor.java @@ -0,0 +1,319 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.work; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import javax.naming.NamingException; +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.work.ExecutionContext; +import javax.resource.spi.work.Work; +import javax.resource.spi.work.WorkException; +import javax.resource.spi.work.WorkListener; +import javax.resource.spi.work.WorkManager; +import javax.resource.spi.work.WorkRejectedException; + +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.core.task.AsyncListenableTaskExecutor; +import com.fr.third.springframework.core.task.TaskRejectedException; +import com.fr.third.springframework.core.task.TaskTimeoutException; +import com.fr.third.springframework.jca.context.BootstrapContextAware; +import com.fr.third.springframework.jndi.JndiLocatorSupport; +import com.fr.third.springframework.scheduling.SchedulingException; +import com.fr.third.springframework.scheduling.SchedulingTaskExecutor; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.concurrent.ListenableFuture; +import com.fr.third.springframework.util.concurrent.ListenableFutureTask; + +/** + * {@link com.fr.third.springframework.core.task.TaskExecutor} implementation + * that delegates to a JCA 1.5 WorkManager, implementing the + * {@link javax.resource.spi.work.WorkManager} interface. + * + *

This is mainly intended for use within a JCA ResourceAdapter implementation, + * but may also be used in a standalone environment, delegating to a locally + * embedded WorkManager implementation (such as Geronimo's). + * + *

Also implements the JCA 1.5 WorkManager interface itself, delegating all + * calls to the target WorkManager. Hence, a caller can choose whether it wants + * to talk to this executor through the Spring TaskExecutor interface or the + * JCA 1.5 WorkManager interface. + * + *

This adapter is also capable of obtaining a JCA WorkManager from JNDI. + * This is for example appropriate on the Geronimo application server, where + * WorkManager GBeans (e.g. Geronimo's default "DefaultWorkManager" GBean) + * can be linked into the J2EE environment through "gbean-ref" entries + * in the {@code geronimo-web.xml} deployment descriptor. + * + *

On JBoss and GlassFish, obtaining the default JCA WorkManager + * requires special lookup steps. See the + * {@link com.fr.third.springframework.jca.work.jboss.JBossWorkManagerTaskExecutor} + * {@link com.fr.third.springframework.jca.work.glassfish.GlassFishWorkManagerTaskExecutor} + * classes which are the direct equivalent of this generic JCA adapter class. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see #setWorkManager + * @see javax.resource.spi.work.WorkManager#scheduleWork + */ +public class WorkManagerTaskExecutor extends JndiLocatorSupport + implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, WorkManager, BootstrapContextAware, InitializingBean { + + private WorkManager workManager; + + private String workManagerName; + + private boolean blockUntilStarted = false; + + private boolean blockUntilCompleted = false; + + private WorkListener workListener; + + + /** + * Create a new WorkManagerTaskExecutor, expecting bean-style configuration. + * @see #setWorkManager + */ + public WorkManagerTaskExecutor() { + } + + /** + * Create a new WorkManagerTaskExecutor for the given WorkManager. + * @param workManager the JCA WorkManager to delegate to + */ + public WorkManagerTaskExecutor(WorkManager workManager) { + setWorkManager(workManager); + } + + + /** + * Specify the JCA WorkManager instance to delegate to. + */ + public void setWorkManager(WorkManager workManager) { + Assert.notNull(workManager, "WorkManager must not be null"); + this.workManager = workManager; + } + + /** + * Set the JNDI name of the JCA WorkManager. + *

This can either be a fully qualified JNDI name, + * or the JNDI name relative to the current environment + * naming context if "resourceRef" is set to "true". + * @see #setWorkManager + * @see #setResourceRef + */ + public void setWorkManagerName(String workManagerName) { + this.workManagerName = workManagerName; + } + + /** + * Specify the JCA BootstrapContext that contains the + * WorkManager to delegate to. + */ + @Override + public void setBootstrapContext(BootstrapContext bootstrapContext) { + Assert.notNull(bootstrapContext, "BootstrapContext must not be null"); + this.workManager = bootstrapContext.getWorkManager(); + } + + /** + * Set whether to let {@link #execute} block until the work + * has been actually started. + *

Uses the JCA {@code startWork} operation underneath, + * instead of the default {@code scheduleWork}. + * @see javax.resource.spi.work.WorkManager#startWork + * @see javax.resource.spi.work.WorkManager#scheduleWork + */ + public void setBlockUntilStarted(boolean blockUntilStarted) { + this.blockUntilStarted = blockUntilStarted; + } + + /** + * Set whether to let {@link #execute} block until the work + * has been completed. + *

Uses the JCA {@code doWork} operation underneath, + * instead of the default {@code scheduleWork}. + * @see javax.resource.spi.work.WorkManager#doWork + * @see javax.resource.spi.work.WorkManager#scheduleWork + */ + public void setBlockUntilCompleted(boolean blockUntilCompleted) { + this.blockUntilCompleted = blockUntilCompleted; + } + + /** + * Specify a JCA 1.5 WorkListener to apply, if any. + *

This shared WorkListener instance will be passed on to the + * WorkManager by all {@link #execute} calls on this TaskExecutor. + */ + public void setWorkListener(WorkListener workListener) { + this.workListener = workListener; + } + + @Override + public void afterPropertiesSet() throws NamingException { + if (this.workManager == null) { + if (this.workManagerName != null) { + this.workManager = lookup(this.workManagerName, WorkManager.class); + } + else { + this.workManager = getDefaultWorkManager(); + } + } + } + + /** + * Obtain a default WorkManager to delegate to. + * Called if no explicit WorkManager or WorkManager JNDI name has been specified. + *

The default implementation returns a {@link SimpleTaskWorkManager}. + * Can be overridden in subclasses. + */ + protected WorkManager getDefaultWorkManager() { + return new SimpleTaskWorkManager(); + } + + + //------------------------------------------------------------------------- + // Implementation of the Spring SchedulingTaskExecutor interface + //------------------------------------------------------------------------- + + @Override + public void execute(Runnable task) { + execute(task, TIMEOUT_INDEFINITE); + } + + @Override + public void execute(Runnable task, long startTimeout) { + Assert.state(this.workManager != null, "No WorkManager specified"); + Work work = new DelegatingWork(task); + try { + if (this.blockUntilCompleted) { + if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) { + this.workManager.doWork(work, startTimeout, null, this.workListener); + } + else { + this.workManager.doWork(work); + } + } + else if (this.blockUntilStarted) { + if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) { + this.workManager.startWork(work, startTimeout, null, this.workListener); + } + else { + this.workManager.startWork(work); + } + } + else { + if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) { + this.workManager.scheduleWork(work, startTimeout, null, this.workListener); + } + else { + this.workManager.scheduleWork(work); + } + } + } + catch (WorkRejectedException ex) { + if (WorkException.START_TIMED_OUT.equals(ex.getErrorCode())) { + throw new TaskTimeoutException("JCA WorkManager rejected task because of timeout: " + task, ex); + } + else { + throw new TaskRejectedException("JCA WorkManager rejected task: " + task, ex); + } + } + catch (WorkException ex) { + throw new SchedulingException("Could not schedule task on JCA WorkManager", ex); + } + } + + @Override + public Future submit(Runnable task) { + FutureTask future = new FutureTask(task, null); + execute(future, TIMEOUT_INDEFINITE); + return future; + } + + @Override + public Future submit(Callable task) { + FutureTask future = new FutureTask(task); + execute(future, TIMEOUT_INDEFINITE); + return future; + } + + @Override + public ListenableFuture submitListenable(Runnable task) { + ListenableFutureTask future = new ListenableFutureTask(task, null); + execute(future, TIMEOUT_INDEFINITE); + return future; + } + + @Override + public ListenableFuture submitListenable(Callable task) { + ListenableFutureTask future = new ListenableFutureTask(task); + execute(future, TIMEOUT_INDEFINITE); + return future; + } + + /** + * This task executor prefers short-lived work units. + */ + @Override + public boolean prefersShortLivedTasks() { + return true; + } + + + //------------------------------------------------------------------------- + // Implementation of the JCA WorkManager interface + //------------------------------------------------------------------------- + + @Override + public void doWork(Work work) throws WorkException { + this.workManager.doWork(work); + } + + @Override + public void doWork(Work work, long delay, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + this.workManager.doWork(work, delay, executionContext, workListener); + } + + @Override + public long startWork(Work work) throws WorkException { + return this.workManager.startWork(work); + } + + @Override + public long startWork(Work work, long delay, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + return this.workManager.startWork(work, delay, executionContext, workListener); + } + + @Override + public void scheduleWork(Work work) throws WorkException { + this.workManager.scheduleWork(work); + } + + @Override + public void scheduleWork(Work work, long delay, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + this.workManager.scheduleWork(work, delay, executionContext, workListener); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java b/fine-spring/src/com/fr/third/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java new file mode 100644 index 000000000..14f0793ee --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.work.glassfish; + +import java.lang.reflect.Method; +import javax.resource.spi.work.WorkManager; + +import com.fr.third.springframework.jca.work.WorkManagerTaskExecutor; +import com.fr.third.springframework.util.ReflectionUtils; + +/** + * Spring TaskExecutor adapter for the GlassFish JCA WorkManager. + * Can be defined in web applications to make a TaskExecutor reference + * available, talking to the GlassFish WorkManager (thread pool) underneath. + * + *

This is the GlassFish equivalent of the CommonJ + * {@link com.fr.third.springframework.scheduling.commonj.WorkManagerTaskExecutor} + * adapter for WebLogic and WebSphere. + * + *

Note: On GlassFish 4 and higher, a + * {@link com.fr.third.springframework.scheduling.concurrent.DefaultManagedTaskExecutor} + * should be preferred, following JSR-236 support in Java EE 7. + * + * @author Juergen Hoeller + * @since 2.5.2 + */ +public class GlassFishWorkManagerTaskExecutor extends WorkManagerTaskExecutor { + + private static final String WORK_MANAGER_FACTORY_CLASS = "com.sun.enterprise.connectors.work.WorkManagerFactory"; + + private final Method getWorkManagerMethod; + + + public GlassFishWorkManagerTaskExecutor() { + try { + Class wmf = getClass().getClassLoader().loadClass(WORK_MANAGER_FACTORY_CLASS); + this.getWorkManagerMethod = wmf.getMethod("getWorkManager", String.class); + } + catch (Exception ex) { + throw new IllegalStateException( + "Could not initialize GlassFishWorkManagerTaskExecutor because GlassFish API is not available", ex); + } + } + + /** + * Identify a specific GlassFish thread pool to talk to. + *

The thread pool name matches the resource adapter name + * in default RAR deployment scenarios. + */ + public void setThreadPoolName(String threadPoolName) { + WorkManager wm = (WorkManager) ReflectionUtils.invokeMethod(this.getWorkManagerMethod, null, threadPoolName); + if (wm == null) { + throw new IllegalArgumentException("Specified thread pool name '" + threadPoolName + + "' does not correspond to an actual pool definition in GlassFish. Check your configuration!"); + } + setWorkManager(wm); + } + + /** + * Obtains GlassFish's default thread pool. + */ + @Override + protected WorkManager getDefaultWorkManager() { + return (WorkManager) ReflectionUtils.invokeMethod(this.getWorkManagerMethod, null, new Object[] {null}); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/work/glassfish/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/work/glassfish/package-info.java new file mode 100644 index 000000000..d2a74703e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/work/glassfish/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * Convenience package for obtaining a GlassFish JCA WorkManager for use in + * web applications. Provides a Spring TaskExecutor adapter for GlassFish. + * + */ +package com.fr.third.springframework.jca.work.glassfish; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java b/fine-spring/src/com/fr/third/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java new file mode 100644 index 000000000..a7656fc29 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.work.jboss; + +import javax.resource.spi.work.WorkManager; + +import com.fr.third.springframework.jca.work.WorkManagerTaskExecutor; + +/** + * Spring TaskExecutor adapter for the JBoss JCA WorkManager. + * Can be defined in web applications to make a TaskExecutor reference + * available, talking to the JBoss WorkManager (thread pool) underneath. + * + *

This is the JBoss equivalent of the CommonJ + * {@link com.fr.third.springframework.scheduling.commonj.WorkManagerTaskExecutor} + * adapter for WebLogic and WebSphere. + * + *

This class does not work on JBoss 7 or higher. There is no known + * immediate replacement, since JBoss does not want its JCA WorkManager + * to be exposed anymore. As of JBoss/WildFly 8, a + * {@link com.fr.third.springframework.scheduling.concurrent.DefaultManagedTaskExecutor} + * may be used, following JSR-236 support in Java EE 7. + * + * @author Juergen Hoeller + * @since 2.5.2 + * @see org.jboss.resource.work.JBossWorkManagerMBean + * @deprecated as of Spring 4.0, since there are no fully supported versions + * of JBoss that this class works with anymore + */ +@Deprecated +public class JBossWorkManagerTaskExecutor extends WorkManagerTaskExecutor { + + /** + * Identify a specific JBossWorkManagerMBean to talk to, + * through its JMX object name. + *

The default MBean name is "jboss.jca:service=WorkManager". + * @see JBossWorkManagerUtils#getWorkManager(String) + */ + public void setWorkManagerMBeanName(String mbeanName) { + setWorkManager(JBossWorkManagerUtils.getWorkManager(mbeanName)); + } + + /** + * Obtains the default JBoss JCA WorkManager through a JMX lookup + * for the JBossWorkManagerMBean. + * @see JBossWorkManagerUtils#getWorkManager() + */ + @Override + protected WorkManager getDefaultWorkManager() { + return JBossWorkManagerUtils.getWorkManager(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/work/jboss/JBossWorkManagerUtils.java b/fine-spring/src/com/fr/third/springframework/jca/work/jboss/JBossWorkManagerUtils.java new file mode 100644 index 000000000..8cf91aa3e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/work/jboss/JBossWorkManagerUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.jca.work.jboss; + +import java.lang.reflect.Method; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerInvocationHandler; +import javax.management.ObjectName; +import javax.naming.InitialContext; +import javax.resource.spi.work.WorkManager; + +import com.fr.third.springframework.util.Assert; + +/** + * Utility class for obtaining the JBoss JCA WorkManager, + * typically for use in web applications. + * + * @author Juergen Hoeller + * @since 2.5.2 + * @deprecated as of Spring 4.0, since there are no fully supported versions + * of JBoss that this class works with anymore + */ +@Deprecated +public abstract class JBossWorkManagerUtils { + + private static final String JBOSS_WORK_MANAGER_MBEAN_CLASS_NAME = "org.jboss.resource.work.JBossWorkManagerMBean"; + + private static final String MBEAN_SERVER_CONNECTION_JNDI_NAME = "jmx/invoker/RMIAdaptor"; + + private static final String DEFAULT_WORK_MANAGER_MBEAN_NAME = "jboss.jca:service=WorkManager"; + + + /** + * Obtain the default JBoss JCA WorkManager through a JMX lookup + * for the default JBossWorkManagerMBean. + * @see org.jboss.resource.work.JBossWorkManagerMBean + */ + public static WorkManager getWorkManager() { + return getWorkManager(DEFAULT_WORK_MANAGER_MBEAN_NAME); + } + + /** + * Obtain the default JBoss JCA WorkManager through a JMX lookup + * for the JBossWorkManagerMBean. + * @param mbeanName the JMX object name to use + * @see org.jboss.resource.work.JBossWorkManagerMBean + */ + public static WorkManager getWorkManager(String mbeanName) { + Assert.hasLength(mbeanName, "JBossWorkManagerMBean name must not be empty"); + try { + Class mbeanClass = JBossWorkManagerUtils.class.getClassLoader().loadClass(JBOSS_WORK_MANAGER_MBEAN_CLASS_NAME); + InitialContext jndiContext = new InitialContext(); + MBeanServerConnection mconn = (MBeanServerConnection) jndiContext.lookup(MBEAN_SERVER_CONNECTION_JNDI_NAME); + ObjectName objectName = ObjectName.getInstance(mbeanName); + Object workManagerMBean = MBeanServerInvocationHandler.newProxyInstance(mconn, objectName, mbeanClass, false); + Method getInstanceMethod = workManagerMBean.getClass().getMethod("getInstance"); + return (WorkManager) getInstanceMethod.invoke(workManagerMBean); + } + catch (Exception ex) { + throw new IllegalStateException( + "Could not initialize JBossWorkManagerTaskExecutor because JBoss API is not available", ex); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/jca/work/jboss/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/work/jboss/package-info.java new file mode 100644 index 000000000..9b60b975d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/work/jboss/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * Convenience package for obtaining a JBoss JCA WorkManager for use in + * web applications. Provides a Spring TaskExecutor adapter for JBoss. + * + */ +package com.fr.third.springframework.jca.work.jboss; + diff --git a/fine-spring/src/com/fr/third/springframework/jca/work/package-info.java b/fine-spring/src/com/fr/third/springframework/jca/work/package-info.java new file mode 100644 index 000000000..99eac443d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/jca/work/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * Convenience classes for scheduling based on the JCA 1.5 WorkManager facility, + * as supported within JCA 1.5 ResourceAdapters. + * + */ +package com.fr.third.springframework.jca.work; + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/AttributeInUseException.java b/fine-spring/src/com/fr/third/springframework/ldap/AttributeInUseException.java new file mode 100644 index 000000000..d1fbcda38 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/AttributeInUseException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI AttributeInUseException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.AttributeInUseException + */ +public class AttributeInUseException extends NamingException { + + public AttributeInUseException( + javax.naming.directory.AttributeInUseException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/AttributeModificationException.java b/fine-spring/src/com/fr/third/springframework/ldap/AttributeModificationException.java new file mode 100644 index 000000000..9954caffc --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/AttributeModificationException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI AttributeModificationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.AttributeModificationException + */ +public class AttributeModificationException extends NamingException { + + public AttributeModificationException( + javax.naming.directory.AttributeModificationException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/AuthenticationException.java b/fine-spring/src/com/fr/third/springframework/ldap/AuthenticationException.java new file mode 100644 index 000000000..e8ab0299c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/AuthenticationException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI AuthenticationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.AuthenticationException + */ +public class AuthenticationException extends NamingSecurityException { + + public AuthenticationException(javax.naming.AuthenticationException cause) { + super(cause); + } + + public AuthenticationException() { + this(null); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/AuthenticationNotSupportedException.java b/fine-spring/src/com/fr/third/springframework/ldap/AuthenticationNotSupportedException.java new file mode 100644 index 000000000..716a00c07 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/AuthenticationNotSupportedException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI AuthenticationNotSupportedException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.AuthenticationNotSupportedException + */ +public class AuthenticationNotSupportedException extends + NamingSecurityException { + + public AuthenticationNotSupportedException( + javax.naming.AuthenticationNotSupportedException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/BadLdapGrammarException.java b/fine-spring/src/com/fr/third/springframework/ldap/BadLdapGrammarException.java new file mode 100644 index 000000000..9ce98b8ca --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/BadLdapGrammarException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Thrown to indicate that an invalid value has been supplied to an LDAP + * operation. This could be an invalid filter or dn. + * + * @author Mattias Hellborg Arthursson + */ +public class BadLdapGrammarException extends NamingException { + + private static final long serialVersionUID = 961612585331409470L; + + public BadLdapGrammarException(String message) { + super(message); + } + + public BadLdapGrammarException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/CannotProceedException.java b/fine-spring/src/com/fr/third/springframework/ldap/CannotProceedException.java new file mode 100644 index 000000000..fa6a46cc5 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/CannotProceedException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI CannotProceedException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.CannotProceedException + */ +public class CannotProceedException extends NamingException { + + public CannotProceedException(javax.naming.CannotProceedException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/CommunicationException.java b/fine-spring/src/com/fr/third/springframework/ldap/CommunicationException.java new file mode 100644 index 000000000..5f6f52aaf --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/CommunicationException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI CommunicationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.CommunicationException + */ +public class CommunicationException extends NamingException { + + public CommunicationException(javax.naming.CommunicationException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/ConfigurationException.java b/fine-spring/src/com/fr/third/springframework/ldap/ConfigurationException.java new file mode 100644 index 000000000..eb0c5d917 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/ConfigurationException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI ConfigurationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ConfigurationException + */ +public class ConfigurationException extends NamingException { + + public ConfigurationException(javax.naming.ConfigurationException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/ContextNotEmptyException.java b/fine-spring/src/com/fr/third/springframework/ldap/ContextNotEmptyException.java new file mode 100644 index 000000000..b44238602 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/ContextNotEmptyException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI ContextNotEmptyException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ContextNotEmptyException + */ +public class ContextNotEmptyException extends NamingException { + + public ContextNotEmptyException(javax.naming.ContextNotEmptyException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/InsufficientResourcesException.java b/fine-spring/src/com/fr/third/springframework/ldap/InsufficientResourcesException.java new file mode 100644 index 000000000..c1c3a4ae0 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/InsufficientResourcesException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InsufficientResourcesException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.InsufficientResourcesException + */ +public class InsufficientResourcesException extends NamingException { + + public InsufficientResourcesException( + javax.naming.InsufficientResourcesException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/InterruptedNamingException.java b/fine-spring/src/com/fr/third/springframework/ldap/InterruptedNamingException.java new file mode 100644 index 000000000..dc8983ba6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/InterruptedNamingException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InterruptedNamingException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.InterruptedNamingException + */ +public class InterruptedNamingException extends NamingException { + + public InterruptedNamingException( + javax.naming.InterruptedNamingException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/InvalidAttributeIdentifierException.java b/fine-spring/src/com/fr/third/springframework/ldap/InvalidAttributeIdentifierException.java new file mode 100644 index 000000000..8ded76769 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/InvalidAttributeIdentifierException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidAttributeIdentifierException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidAttributeIdentifierException + */ +public class InvalidAttributeIdentifierException extends NamingException { + + public InvalidAttributeIdentifierException( + javax.naming.directory.InvalidAttributeIdentifierException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/InvalidAttributeValueException.java b/fine-spring/src/com/fr/third/springframework/ldap/InvalidAttributeValueException.java new file mode 100644 index 000000000..0d9149db2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/InvalidAttributeValueException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidAttributeValueException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidAttributeValueException + */ +public class InvalidAttributeValueException extends NamingException { + + public InvalidAttributeValueException( + javax.naming.directory.InvalidAttributeValueException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/InvalidAttributesException.java b/fine-spring/src/com/fr/third/springframework/ldap/InvalidAttributesException.java new file mode 100644 index 000000000..dc346086c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/InvalidAttributesException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidAttributesException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidAttributesException + */ +public class InvalidAttributesException extends NamingException { + + public InvalidAttributesException( + javax.naming.directory.InvalidAttributesException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/InvalidNameException.java b/fine-spring/src/com/fr/third/springframework/ldap/InvalidNameException.java new file mode 100644 index 000000000..264098f79 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/InvalidNameException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidNameException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.InvalidNameException + */ +public class InvalidNameException extends NamingException { + + public InvalidNameException(javax.naming.InvalidNameException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/InvalidSearchControlsException.java b/fine-spring/src/com/fr/third/springframework/ldap/InvalidSearchControlsException.java new file mode 100644 index 000000000..90d80eaeb --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/InvalidSearchControlsException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidSearchControlsException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidSearchControlsException + */ +public class InvalidSearchControlsException extends NamingException { + + public InvalidSearchControlsException( + javax.naming.directory.InvalidSearchControlsException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/InvalidSearchFilterException.java b/fine-spring/src/com/fr/third/springframework/ldap/InvalidSearchFilterException.java new file mode 100644 index 000000000..e7c9acca2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/InvalidSearchFilterException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidSearchFilterException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidSearchFilterException + */ +public class InvalidSearchFilterException extends NamingException { + + public InvalidSearchFilterException( + javax.naming.directory.InvalidSearchFilterException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/LdapReferralException.java b/fine-spring/src/com/fr/third/springframework/ldap/LdapReferralException.java new file mode 100644 index 000000000..8c99fa806 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/LdapReferralException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI LdapReferralException. + * + * This class is not abstract. We need to be able to instantiate it, should the + * caught exception be a provider-specific subclass of + * {@link javax.naming.ldap.LdapReferralException}. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ldap.LdapReferralException + */ +public class LdapReferralException extends ReferralException { + + public LdapReferralException(javax.naming.ldap.LdapReferralException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/LimitExceededException.java b/fine-spring/src/com/fr/third/springframework/ldap/LimitExceededException.java new file mode 100644 index 000000000..7ff4d9012 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/LimitExceededException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI LimitExceededException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.LimitExceededException + */ +public class LimitExceededException extends NamingException { + + public LimitExceededException(javax.naming.LimitExceededException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/LinkException.java b/fine-spring/src/com/fr/third/springframework/ldap/LinkException.java new file mode 100644 index 000000000..afd86f1bf --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/LinkException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI LinkException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.LinkException + */ +public class LinkException extends NamingException { + + public LinkException(javax.naming.LinkException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/LinkLoopException.java b/fine-spring/src/com/fr/third/springframework/ldap/LinkLoopException.java new file mode 100644 index 000000000..de2a673a8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/LinkLoopException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI LinkLoopException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.LinkLoopException + */ +public class LinkLoopException extends LinkException { + + public LinkLoopException(javax.naming.LinkLoopException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/MalformedLinkException.java b/fine-spring/src/com/fr/third/springframework/ldap/MalformedLinkException.java new file mode 100644 index 000000000..56159d0a1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/MalformedLinkException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI MalformedLinkException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.MalformedLinkException + */ +public class MalformedLinkException extends LinkException { + + public MalformedLinkException(javax.naming.MalformedLinkException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/NameAlreadyBoundException.java b/fine-spring/src/com/fr/third/springframework/ldap/NameAlreadyBoundException.java new file mode 100644 index 000000000..b1dcae714 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/NameAlreadyBoundException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NameAlreadyBoundException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NameAlreadyBoundException + */ +public class NameAlreadyBoundException extends NamingException { + + public NameAlreadyBoundException( + javax.naming.NameAlreadyBoundException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/NameNotFoundException.java b/fine-spring/src/com/fr/third/springframework/ldap/NameNotFoundException.java new file mode 100644 index 000000000..395fcda3b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/NameNotFoundException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NameNotFoundException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NameNotFoundException + */ +public class NameNotFoundException extends NamingException { + + public NameNotFoundException(String msg) { + super(msg); + } + + public NameNotFoundException(javax.naming.NameNotFoundException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/NamingException.java b/fine-spring/src/com/fr/third/springframework/ldap/NamingException.java new file mode 100644 index 000000000..83844999c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/NamingException.java @@ -0,0 +1,186 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import javax.naming.Name; + +import com.fr.third.springframework.core.NestedRuntimeException; + +/** + * Base class for exception thrown by the framework whenever it encounters a + * problem related to LDAP. + * + * @author Ulrik Sandberg + * @since 1.2 + */ +public abstract class NamingException extends NestedRuntimeException { + + private Throwable cause; + + /** + * Overrides {@link NestedRuntimeException#getCause()} since serialization + * always tries to serialize the base class before the subclass. Our + * cause may have a resolvedObj that is not + * serializable. By storing the cause in this class, we get a chance at + * temporarily nulling the cause before serialization, thus in effect making + * the current instance serializable. + */ + public Throwable getCause() { + // Even if you cannot set the cause of this exception other than through + // the constructor, we check for the cause being "this" here, as the cause + // could still be set to "this" via reflection: for example, by a remoting + // deserializer like Hessian's. + return (this.cause == this ? null : this.cause); + } + + /** + * Constructor that takes a message. + * + * @param msg + * the detail message + */ + public NamingException(String msg) { + super(msg); + } + + /** + * Constructor that allows a message and a root cause. + * + * @param msg + * the detail message + * @param cause + * the cause of the exception. This argument is generally + * expected to be a proper subclass of + * {@link javax.naming.NamingException}. + */ + public NamingException(String msg, Throwable cause) { + super(msg); + this.cause = cause; + } + + /** + * Constructor that allows a plain root cause, intended for subclasses + * mirroring corresponding javax.naming exceptions. + * + * @param cause + * the cause of the exception. This argument is generally + * expected to be a proper subclass of + * {@link javax.naming.NamingException}. + */ + public NamingException(Throwable cause) { + this(cause != null ? cause.getMessage() : null, cause); + } + + /** + * Convenience method to get the explanation associated with this exception, + * if the root cause was an instance of {@link javax.naming.NamingException}. + * + * @return a detail string explaining more about this exception if the root + * cause is an instance of javax.naming.NamingException, or + * null if there is no detail message for this + * exception + */ + public String getExplanation() { + if (getCause() instanceof javax.naming.NamingException) { + return ((javax.naming.NamingException) getCause()).getExplanation(); + } + return null; + } + + /** + * Convenience method to get the unresolved part of the name associated with + * this exception, if the root cause was an instance of + * {@link javax.naming.NamingException}. + * + * @return a composite name describing the part of the name that has not + * been resolved if the root cause is an instance of + * javax.naming.NamingException, or null if the + * remaining name field has not been set + */ + public Name getRemainingName() { + if (getCause() instanceof javax.naming.NamingException) { + return ((javax.naming.NamingException) getCause()) + .getRemainingName(); + } + return null; + } + + /** + * Convenience method to get the leading portion of the resolved name + * associated with this exception, if the root cause was an instance of + * {@link javax.naming.NamingException}. + * + * @return a composite name describing the the leading portion of the name + * that was resolved successfully if the root cause is an instance + * of javax.naming.NamingException, or null if the + * resolved name field has not been set + */ + public Name getResolvedName() { + if (getCause() instanceof javax.naming.NamingException) { + return ((javax.naming.NamingException) getCause()) + .getResolvedName(); + } + return null; + } + + /** + * Convenience method to get the resolved object associated with this + * exception, if the root cause was an instance of + * {@link javax.naming.NamingException}. + * + * @return the object that was resolved so far if the root cause is an + * instance of javax.naming.NamingException, or null + * if the resolved object field has not been set + */ + public Object getResolvedObj() { + if (getCause() instanceof javax.naming.NamingException) { + return ((javax.naming.NamingException) getCause()).getResolvedObj(); + } + return null; + } + + /** + * Checks if the resolvedObj of the causing exception is + * suspected to be non-serializable, and if so temporarily nulls it before + * calling the default serialization mechanism. + * + * @param stream + * the stream onto which this object is serialized + * @throws IOException + * if there is an error writing this object to the stream + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + Object resolvedObj = getResolvedObj(); + boolean serializable = resolvedObj instanceof Serializable; + if (resolvedObj != null && !serializable) { + // the cause is of this type, since resolvedObj is not null + javax.naming.NamingException namingException = (javax.naming.NamingException) getCause(); + namingException.setResolvedObj(null); + try { + stream.defaultWriteObject(); + } finally { + namingException.setResolvedObj(resolvedObj); + } + } else { + stream.defaultWriteObject(); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/NamingSecurityException.java b/fine-spring/src/com/fr/third/springframework/ldap/NamingSecurityException.java new file mode 100644 index 000000000..0b5437471 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/NamingSecurityException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NamingSecurityException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NamingSecurityException + */ +public class NamingSecurityException extends NamingException { + + public NamingSecurityException(javax.naming.NamingSecurityException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/NoInitialContextException.java b/fine-spring/src/com/fr/third/springframework/ldap/NoInitialContextException.java new file mode 100644 index 000000000..e89658981 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/NoInitialContextException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NoInitialContextException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NoInitialContextException + */ +public class NoInitialContextException extends NamingException { + + public NoInitialContextException( + javax.naming.NoInitialContextException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/NoPermissionException.java b/fine-spring/src/com/fr/third/springframework/ldap/NoPermissionException.java new file mode 100644 index 000000000..c8eaebd5b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/NoPermissionException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NoPermissionException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NoPermissionException + */ +public class NoPermissionException extends NamingSecurityException { + + public NoPermissionException(javax.naming.NoPermissionException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/NoSuchAttributeException.java b/fine-spring/src/com/fr/third/springframework/ldap/NoSuchAttributeException.java new file mode 100644 index 000000000..da37dc362 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/NoSuchAttributeException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NoSuchAttributeException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.NoSuchAttributeException + */ +public class NoSuchAttributeException extends NamingException { + + public NoSuchAttributeException(String message) { + super(message); + } + + public NoSuchAttributeException(javax.naming.directory.NoSuchAttributeException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/NotContextException.java b/fine-spring/src/com/fr/third/springframework/ldap/NotContextException.java new file mode 100644 index 000000000..b19556304 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/NotContextException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NotContextException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NotContextException + */ +public class NotContextException extends NamingException { + + public NotContextException(javax.naming.NotContextException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/OperationNotSupportedException.java b/fine-spring/src/com/fr/third/springframework/ldap/OperationNotSupportedException.java new file mode 100644 index 000000000..fee3dbe6f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/OperationNotSupportedException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI OperationNotSupportedException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.OperationNotSupportedException + */ +public class OperationNotSupportedException extends NamingException { + + public OperationNotSupportedException( + javax.naming.OperationNotSupportedException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/PartialResultException.java b/fine-spring/src/com/fr/third/springframework/ldap/PartialResultException.java new file mode 100644 index 000000000..c81f48939 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/PartialResultException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI PartialResultException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.PartialResultException + */ +public class PartialResultException extends NamingException { + + public PartialResultException(javax.naming.PartialResultException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/ReferralException.java b/fine-spring/src/com/fr/third/springframework/ldap/ReferralException.java new file mode 100644 index 000000000..1923e6c0e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/ReferralException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI ReferralException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ReferralException + */ +public class ReferralException extends NamingException { + + public ReferralException(javax.naming.ReferralException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/SchemaViolationException.java b/fine-spring/src/com/fr/third/springframework/ldap/SchemaViolationException.java new file mode 100644 index 000000000..57b577460 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/SchemaViolationException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI SchemaViolationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.SchemaViolationException + */ +public class SchemaViolationException extends NamingException { + + public SchemaViolationException( + javax.naming.directory.SchemaViolationException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/ServiceUnavailableException.java b/fine-spring/src/com/fr/third/springframework/ldap/ServiceUnavailableException.java new file mode 100644 index 000000000..f73008bf2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/ServiceUnavailableException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI ServiceUnavailableException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ServiceUnavailableException + */ +public class ServiceUnavailableException extends NamingException { + + public ServiceUnavailableException( + javax.naming.ServiceUnavailableException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/SizeLimitExceededException.java b/fine-spring/src/com/fr/third/springframework/ldap/SizeLimitExceededException.java new file mode 100644 index 000000000..e0f0dafd8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/SizeLimitExceededException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI SizeLimitExceededException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.SizeLimitExceededException + */ +public class SizeLimitExceededException extends LimitExceededException { + + public SizeLimitExceededException( + javax.naming.SizeLimitExceededException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/TimeLimitExceededException.java b/fine-spring/src/com/fr/third/springframework/ldap/TimeLimitExceededException.java new file mode 100644 index 000000000..200e5294e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/TimeLimitExceededException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI TimeLimitExceededException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.TimeLimitExceededException + */ +public class TimeLimitExceededException extends LimitExceededException { + + public TimeLimitExceededException( + javax.naming.TimeLimitExceededException cause) { + super(cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/UncategorizedLdapException.java b/fine-spring/src/com/fr/third/springframework/ldap/UncategorizedLdapException.java new file mode 100644 index 000000000..3570d7357 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/UncategorizedLdapException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap; + +/** + * NamingException to be thrown when no other matching subclass is found. + * + * @author Ulrik Sandberg + * @since 1.2 + */ +public class UncategorizedLdapException extends NamingException { + + public UncategorizedLdapException(String msg) { + super(msg); + } + + public UncategorizedLdapException(String msg, Throwable cause) { + super(msg, cause); + } + + public UncategorizedLdapException(Throwable cause) { + super("Uncategorized exception occured during LDAP processing", cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecorator.java b/fine-spring/src/com/fr/third/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecorator.java new file mode 100644 index 000000000..91bd149ab --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecorator.java @@ -0,0 +1,156 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.authentication; + +import com.fr.third.springframework.util.StringUtils; +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.ldap.core.AuthenticationSource; + +/** + * Decorator on AuthenticationSource to have default authentication information + * be returned should the target return empty principal and credentials. Useful + * in combination with AcegiAuthenticationSource if users are to be + * allowed to read some information even though they are not logged in. + *

+ * Note: The defaultUser should be an non-privileged + * user. This is important as this is the one that will be used when no user is + * logged in (i.e. empty principal is returned from the target + * AuthenticationSource). + * + * @author Mattias Hellborg Arthursson + * + */ +public class DefaultValuesAuthenticationSourceDecorator implements + AuthenticationSource, InitializingBean { + + private AuthenticationSource target; + + private String defaultUser; + + private String defaultPassword; + + /** + * Constructor for bean usage. + */ + public DefaultValuesAuthenticationSourceDecorator() { + } + + /** + * Constructor to setup instance directly. + * + * @param target + * the target AuthenticationSource. + * @param defaultUser + * dn of the user to use when the target returns an empty + * principal. + * @param defaultPassword + * password of the user to use when the target returns an empty + * principal. + */ + public DefaultValuesAuthenticationSourceDecorator( + AuthenticationSource target, String defaultUser, + String defaultPassword) { + this.target = target; + this.defaultUser = defaultUser; + this.defaultPassword = defaultPassword; + } + + /** + * Checks if the target's principal is not empty; if not, the credentials + * from the target is returned - otherwise return the + * defaultPassword. + * + * @return the target's password if the target's principal is not empty, the + * defaultPassword otherwise. + */ + public String getCredentials() { + if (StringUtils.hasText(target.getPrincipal())) { + return target.getCredentials(); + } else { + return defaultPassword; + } + } + + /** + * Checks if the target's principal is not empty; if not, this is returned - + * otherwise return the defaultPassword. + * + * @return the target's principal if it is not empty, the + * defaultPassword otherwise. + */ + public String getPrincipal() { + String principal = target.getPrincipal(); + if (StringUtils.hasText(principal)) { + return principal; + } else { + return defaultUser; + } + } + + /** + * Set the password of the default user. + * + * @param defaultPassword + * the password of the default user. + */ + public void setDefaultPassword(String defaultPassword) { + this.defaultPassword = defaultPassword; + } + + /** + * Set the default user DN. This should be a non-privileged user, since it + * will be used when no authentication information is returned from the + * target. + * + * @param defaultUser + * DN of the default user. + */ + public void setDefaultUser(String defaultUser) { + this.defaultUser = defaultUser; + } + + /** + * Set the target AuthenticationSource. + * + * @param target + * the target AuthenticationSource. + */ + public void setTarget(AuthenticationSource target) { + this.target = target; + } + + /* + * (non-Javadoc) + * + * @see com.fr.third.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + if (target == null) { + throw new IllegalArgumentException( + "Property 'target' must be set.'"); + } + + if (defaultUser == null) { + throw new IllegalArgumentException( + "Property 'defaultUser' must be set.'"); + } + + if (defaultPassword == null) { + throw new IllegalArgumentException( + "Property 'defaultPassword' must be set.'"); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/authentication/package.html b/fine-spring/src/com/fr/third/springframework/ldap/authentication/package.html new file mode 100644 index 000000000..4e314da9f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/authentication/package.html @@ -0,0 +1,7 @@ + + + +Support classes for custom authentication. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/config/ContextSourceParser.java b/fine-spring/src/com/fr/third/springframework/ldap/config/ContextSourceParser.java new file mode 100644 index 000000000..2a2628ee1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/config/ContextSourceParser.java @@ -0,0 +1,336 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.config; + +import com.fr.third.springframework.beans.factory.config.BeanDefinition; +import com.fr.third.springframework.beans.factory.parsing.BeanComponentDefinition; +import com.fr.third.springframework.beans.factory.support.BeanDefinitionBuilder; +import com.fr.third.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.BeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.ParserContext; +import com.fr.third.springframework.ldap.CommunicationException; +import com.fr.third.springframework.ldap.core.support.LdapContextSource; +import com.fr.third.springframework.ldap.pool.PoolExhaustedAction; +import com.fr.third.springframework.ldap.pool.factory.PoolingContextSource; +import com.fr.third.springframework.ldap.pool.validation.DefaultDirContextValidator; +import com.fr.third.springframework.ldap.pool2.factory.PoolConfig; +import com.fr.third.springframework.ldap.pool2.factory.PooledContextSource; +import com.fr.third.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.ClassUtils; +import com.fr.third.springframework.util.StringUtils; +import com.fr.third.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +import java.util.HashSet; +import java.util.Set; + +import static com.fr.third.springframework.ldap.config.ParserUtils.getBoolean; +import static com.fr.third.springframework.ldap.config.ParserUtils.getInt; +import static com.fr.third.springframework.ldap.config.ParserUtils.getString; + +/** + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + */ +public class ContextSourceParser implements BeanDefinitionParser { + private static final String ATT_ANONYMOUS_READ_ONLY = "anonymous-read-only"; + private static final String ATT_AUTHENTICATION_SOURCE_REF = "authentication-source-ref"; + private static final String ATT_AUTHENTICATION_STRATEGY_REF = "authentication-strategy-ref"; + private static final String ATT_BASE = "base"; + private static final String ATT_PASSWORD = "password"; + private static final String ATT_NATIVE_POOLING = "native-pooling"; + private static final String ATT_REFERRAL = "referral"; + private static final String ATT_URL = "url"; + private static final String ATT_BASE_ENV_PROPS_REF = "base-env-props-ref"; + + // pooling attributes + private static final String ATT_MAX_ACTIVE = "max-active"; + private static final String ATT_MAX_TOTAL = "max-total"; + private static final String ATT_MAX_IDLE = "max-idle"; + private static final String ATT_MIN_IDLE = "min-idle"; + private static final String ATT_MAX_WAIT = "max-wait"; + private static final String ATT_WHEN_EXHAUSTED = "when-exhausted"; + private static final String ATT_TEST_ON_BORROW = "test-on-borrow"; + private static final String ATT_TEST_ON_RETURN = "test-on-return"; + private static final String ATT_TEST_WHILE_IDLE = "test-while-idle"; + private static final String ATT_EVICTION_RUN_MILLIS = "eviction-run-interval-millis"; + private static final String ATT_TESTS_PER_EVICTION_RUN = "tests-per-eviction-run"; + private static final String ATT_EVICTABLE_TIME_MILLIS = "min-evictable-time-millis"; + private static final String ATT_VALIDATION_QUERY_BASE = "validation-query-base"; + private static final String ATT_VALIDATION_QUERY_FILTER = "validation-query-filter"; + private static final String ATT_VALIDATION_QUERY_SEARCH_CONTROLS_REF = "validation-query-search-controls-ref"; + private static final String ATT_NON_TRANSIENT_EXCEPTIONS = "non-transient-exceptions"; + private static final String ATT_MAX_IDLE_PER_KEY = "max-idle-per-key"; + private static final String ATT_MIN_IDLE_PER_KEY = "min-idle-per-key"; + private static final String ATT_MAX_TOTAL_PER_KEY = "max-total-per-key"; + private static final String ATT_EVICTION_POLICY_CLASS = "eviction-policy-class"; + private static final String ATT_FAIRNESS = "fairness"; + private static final String ATT_JMX_ENABLE = "jmx-enable"; + private static final String ATT_JMX_NAME_BASE = "jmx-name-base"; + private static final String ATT_JMX_NAME_PREFIX = "jmx-name-prefix"; + private static final String ATT_LIFO = "lifo"; + private static final String ATT_BLOCK_WHEN_EXHAUSTED = "block-when-exhausted"; + private static final String ATT_TEST_ON_CREATE = "test-on-create"; + private static final String ATT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "soft-min-evictable-idle-time-millis"; + + + private static final String ATT_USERNAME = "username"; + static final String DEFAULT_ID = "contextSource"; + private static final int DEFAULT_MAX_ACTIVE = 8; + private static final int DEFAULT_MAX_TOTAL = -1; + private static final int DEFAULT_MAX_IDLE = 8; + private static final int DEFAULT_MIN_IDLE = 0; + private static final int DEFAULT_MAX_WAIT = -1; + private static final int DEFAULT_EVICTION_RUN_MILLIS = -1; + private static final int DEFAULT_TESTS_PER_EVICTION_RUN = 3; + private static final int DEFAULT_EVICTABLE_MILLIS = 1000 * 60 * 30; + private static final int DEFAULT_MAX_TOTAL_PER_KEY = 8; + private static final int DEFAULT_MAX_IDLE_PER_KEY = 8; + private static final int DEFAULT_MIN_IDLE_PER_KEY = 0; + private static final String DEFAULT_EVICTION_POLICY_CLASS_NAME = + "com.fr.third.org.apache.commons.pool2.impl.DefaultEvictionPolicy"; + private static final boolean DEFAULT_FAIRNESS = false; + private static final boolean DEFAULT_JMX_ENABLE = true; + private static final String DEFAULT_JMX_NAME_BASE = null; + private static final String DEFAULT_JMX_NAME_PREFIX = "ldap-pool"; + private static final boolean DEFAULT_LIFO = true; + private static final int DEFAULT_MAX_WAIT_MILLIS = -1; + private static final boolean DEFAULT_BLOCK_WHEN_EXHAUSTED = true; + private static final int DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = -1; + + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(LdapContextSource.class); + + String username = element.getAttribute(ATT_USERNAME); + String password = element.getAttribute(ATT_PASSWORD); + String url = element.getAttribute(ATT_URL); + Assert.hasText(url, "url attribute must be specified"); + + builder.addPropertyValue("userDn", username); + builder.addPropertyValue("password", password); + + BeanDefinitionBuilder urlsBuilder = BeanDefinitionBuilder + .rootBeanDefinition(UrlsFactory.class) + .setFactoryMethod("urls") + .addConstructorArgValue(url); + + builder.addPropertyValue("urls", urlsBuilder.getBeanDefinition()); + builder.addPropertyValue("base", getString(element, ATT_BASE, "")); + builder.addPropertyValue("referral", getString(element, ATT_REFERRAL, null)); + + boolean anonymousReadOnly = getBoolean(element, ATT_ANONYMOUS_READ_ONLY, false); + builder.addPropertyValue("anonymousReadOnly", anonymousReadOnly); + boolean nativePooling = getBoolean(element, ATT_NATIVE_POOLING, false); + builder.addPropertyValue("pooled", nativePooling); + + String authStrategyRef = element.getAttribute(ATT_AUTHENTICATION_STRATEGY_REF); + if(StringUtils.hasText(authStrategyRef)) { + builder.addPropertyReference("authenticationStrategy", authStrategyRef); + } + + String authSourceRef = element.getAttribute(ATT_AUTHENTICATION_SOURCE_REF); + if(StringUtils.hasText(authSourceRef)) { + builder.addPropertyReference("authenticationSource", authSourceRef); + } else { + Assert.hasText(username, "username attribute must be specified unless an authentication-source-ref explicitly configured"); + Assert.hasText(password, "password attribute must be specified unless an authentication-source-ref explicitly configured"); + } + + String baseEnvPropsRef = element.getAttribute(ATT_BASE_ENV_PROPS_REF); + if(StringUtils.hasText(baseEnvPropsRef)) { + builder.addPropertyReference("baseEnvironmentProperties", baseEnvPropsRef); + } + + BeanDefinition targetContextSourceDefinition = builder.getBeanDefinition(); + targetContextSourceDefinition = applyPoolingIfApplicable(targetContextSourceDefinition, element, nativePooling); + + BeanDefinition actualContextSourceDefinition = targetContextSourceDefinition; + if (!anonymousReadOnly) { + BeanDefinitionBuilder proxyBuilder = BeanDefinitionBuilder.rootBeanDefinition(TransactionAwareContextSourceProxy.class); + proxyBuilder.addConstructorArgValue(targetContextSourceDefinition); + actualContextSourceDefinition = proxyBuilder.getBeanDefinition(); + } + + String id = getString(element, AbstractBeanDefinitionParser.ID_ATTRIBUTE, DEFAULT_ID); + parserContext.registerBeanComponent(new BeanComponentDefinition(actualContextSourceDefinition, id)); + + return actualContextSourceDefinition; + } + + private BeanDefinition applyPoolingIfApplicable( + BeanDefinition targetContextSourceDefinition, + Element element, + boolean nativePooling) { + + Element poolingElement = DomUtils.getChildElementByTagName(element, Elements.POOLING); + Element pooling2Element = DomUtils.getChildElementByTagName(element, Elements.POOLING2); + + if (pooling2Element != null && poolingElement != null) { + throw new IllegalArgumentException( + String.format("%s cannot be enabled together with %s.", Elements.POOLING2, Elements.POOLING)); + } else if (poolingElement == null && pooling2Element == null) { + return targetContextSourceDefinition; + } + + if(nativePooling) { + throw new IllegalArgumentException( + String.format("%s cannot be enabled together with %s", ATT_NATIVE_POOLING, Elements.POOLING)); + } + + if (pooling2Element != null) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PooledContextSource.class); + builder.addPropertyValue("contextSource", targetContextSourceDefinition); + + populatePoolConfigProperties(builder, pooling2Element); + + boolean testOnBorrow = getBoolean(pooling2Element, ATT_TEST_ON_BORROW, false); + boolean testOnReturn = getBoolean(pooling2Element, ATT_TEST_ON_RETURN, false); + boolean testWhileIdle = getBoolean(pooling2Element, ATT_TEST_WHILE_IDLE, false); + boolean testOnCreate = getBoolean(pooling2Element, ATT_TEST_ON_CREATE, false); + + if (testOnBorrow || testOnCreate || testWhileIdle || testOnReturn) { + populatePoolValidationProperties(builder, pooling2Element); + } + + return builder.getBeanDefinition(); + } else { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PoolingContextSource.class); + builder.addPropertyValue("contextSource", targetContextSourceDefinition); + + builder.addPropertyValue("maxActive", getString(poolingElement, ATT_MAX_ACTIVE, String.valueOf(DEFAULT_MAX_ACTIVE))); + builder.addPropertyValue("maxTotal", getString(poolingElement, ATT_MAX_TOTAL, String.valueOf(DEFAULT_MAX_TOTAL))); + builder.addPropertyValue("maxIdle", getString(poolingElement, ATT_MAX_IDLE, String.valueOf(DEFAULT_MAX_IDLE))); + builder.addPropertyValue("minIdle", getString(poolingElement, ATT_MIN_IDLE, String.valueOf(DEFAULT_MIN_IDLE))); + builder.addPropertyValue("maxWait", getString(poolingElement, ATT_MAX_WAIT, String.valueOf(DEFAULT_MAX_WAIT))); + String whenExhausted = getString(poolingElement, ATT_WHEN_EXHAUSTED, PoolExhaustedAction.BLOCK.name()); + builder.addPropertyValue("whenExhaustedAction", PoolExhaustedAction.valueOf(whenExhausted).getValue()); + builder.addPropertyValue("timeBetweenEvictionRunsMillis", getString(poolingElement, ATT_EVICTION_RUN_MILLIS, String.valueOf(DEFAULT_EVICTION_RUN_MILLIS))); + builder.addPropertyValue("minEvictableIdleTimeMillis", getString(poolingElement, ATT_EVICTABLE_TIME_MILLIS, String.valueOf(DEFAULT_EVICTABLE_MILLIS))); + builder.addPropertyValue("numTestsPerEvictionRun", getString(poolingElement, ATT_TESTS_PER_EVICTION_RUN, String.valueOf(DEFAULT_TESTS_PER_EVICTION_RUN))); + + boolean testOnBorrow = getBoolean(poolingElement, ATT_TEST_ON_BORROW, false); + boolean testOnReturn = getBoolean(poolingElement, ATT_TEST_ON_RETURN, false); + boolean testWhileIdle = getBoolean(poolingElement, ATT_TEST_WHILE_IDLE, false); + + if (testOnBorrow || testOnReturn || testWhileIdle) { + populatePoolValidationProperties(builder, poolingElement, testOnBorrow, testOnReturn, testWhileIdle); + } + + return builder.getBeanDefinition(); + } + } + + private void populatePoolValidationProperties(BeanDefinitionBuilder builder, Element element, + boolean testOnBorrow, boolean testOnReturn, boolean testWhileIdle) { + + builder.addPropertyValue("testOnBorrow", testOnBorrow); + builder.addPropertyValue("testOnReturn", testOnReturn); + builder.addPropertyValue("testWhileIdle", testWhileIdle); + + BeanDefinitionBuilder validatorBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultDirContextValidator.class); + validatorBuilder.addPropertyValue("base", getString(element, ATT_VALIDATION_QUERY_BASE, "")); + validatorBuilder.addPropertyValue("filter", + getString(element, ATT_VALIDATION_QUERY_FILTER, DefaultDirContextValidator.DEFAULT_FILTER)); + String searchControlsRef = element.getAttribute(ATT_VALIDATION_QUERY_SEARCH_CONTROLS_REF); + if(StringUtils.hasText(searchControlsRef)) { + validatorBuilder.addPropertyReference("searchControls", searchControlsRef); + } + builder.addPropertyValue("dirContextValidator", validatorBuilder.getBeanDefinition()); + + builder.addPropertyValue("timeBetweenEvictionRunsMillis", getString(element, ATT_EVICTION_RUN_MILLIS, String.valueOf(DEFAULT_EVICTION_RUN_MILLIS))); + builder.addPropertyValue("numTestsPerEvictionRun", getInt(element, ATT_TESTS_PER_EVICTION_RUN, DEFAULT_TESTS_PER_EVICTION_RUN)); + builder.addPropertyValue("minEvictableIdleTimeMillis", getString(element, ATT_EVICTABLE_TIME_MILLIS, String.valueOf(DEFAULT_EVICTABLE_MILLIS))); + + String nonTransientExceptions = getString(element, ATT_NON_TRANSIENT_EXCEPTIONS, CommunicationException.class.getName()); + String[] strings = StringUtils.commaDelimitedListToStringArray(nonTransientExceptions); + Set> nonTransientExceptionClasses = new HashSet>(); + for (String className : strings) { + try { + nonTransientExceptionClasses.add(ClassUtils.getDefaultClassLoader().loadClass(className)); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(String.format("%s is not a valid class name", className), e); + } + } + + builder.addPropertyValue("nonTransientExceptions", nonTransientExceptionClasses); + } + + + private void populatePoolValidationProperties(BeanDefinitionBuilder builder, Element element) { + + BeanDefinitionBuilder validatorBuilder = BeanDefinitionBuilder.rootBeanDefinition( + com.fr.third.springframework.ldap.pool2.validation.DefaultDirContextValidator.class); + validatorBuilder.addPropertyValue("base", getString(element, ATT_VALIDATION_QUERY_BASE, "")); + validatorBuilder.addPropertyValue("filter", + getString(element, ATT_VALIDATION_QUERY_FILTER, + com.fr.third.springframework.ldap.pool2.validation.DefaultDirContextValidator.DEFAULT_FILTER)); + String searchControlsRef = element.getAttribute(ATT_VALIDATION_QUERY_SEARCH_CONTROLS_REF); + if(StringUtils.hasText(searchControlsRef)) { + validatorBuilder.addPropertyReference("searchControls", searchControlsRef); + } + builder.addPropertyValue("dirContextValidator", validatorBuilder.getBeanDefinition()); + + String nonTransientExceptions = getString(element, ATT_NON_TRANSIENT_EXCEPTIONS, CommunicationException.class.getName()); + String[] strings = StringUtils.commaDelimitedListToStringArray(nonTransientExceptions); + Set> nonTransientExceptionClasses = new HashSet>(); + for (String className : strings) { + try { + nonTransientExceptionClasses.add(ClassUtils.getDefaultClassLoader().loadClass(className)); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(String.format("%s is not a valid class name", className), e); + } + } + + builder.addPropertyValue("nonTransientExceptions", nonTransientExceptionClasses); + } + + private void populatePoolConfigProperties(BeanDefinitionBuilder builder, Element element) { + BeanDefinitionBuilder configBuilder = BeanDefinitionBuilder + .rootBeanDefinition(PoolConfig.class); + + configBuilder.addPropertyValue("maxTotal", getString(element, ATT_MAX_TOTAL, String.valueOf(DEFAULT_MAX_TOTAL))); + configBuilder.addPropertyValue("maxTotalPerKey", getString(element, ATT_MAX_TOTAL_PER_KEY, String.valueOf(DEFAULT_MAX_TOTAL_PER_KEY))); + configBuilder.addPropertyValue("maxIdlePerKey", getString(element, ATT_MAX_IDLE_PER_KEY, String.valueOf(DEFAULT_MAX_IDLE_PER_KEY))); + configBuilder.addPropertyValue("minIdlePerKey", getString(element, ATT_MIN_IDLE_PER_KEY, String.valueOf(DEFAULT_MIN_IDLE_PER_KEY))); + configBuilder.addPropertyValue("evictionPolicyClassName", getString(element, ATT_EVICTION_POLICY_CLASS, DEFAULT_EVICTION_POLICY_CLASS_NAME)); + configBuilder.addPropertyValue("fairness", getBoolean(element, ATT_FAIRNESS, DEFAULT_FAIRNESS)); + configBuilder.addPropertyValue("jmxEnabled", getBoolean(element, ATT_JMX_ENABLE, DEFAULT_JMX_ENABLE)); + configBuilder.addPropertyValue("jmxNameBase", getString(element, ATT_JMX_NAME_BASE, DEFAULT_JMX_NAME_BASE)); + configBuilder.addPropertyValue("jmxNamePrefix", getString(element, ATT_JMX_NAME_PREFIX, DEFAULT_JMX_NAME_PREFIX)); + configBuilder.addPropertyValue("lifo", getBoolean(element, ATT_LIFO, DEFAULT_LIFO)); + configBuilder.addPropertyValue("maxWaitMillis", getString(element, ATT_MAX_WAIT, String.valueOf(DEFAULT_MAX_WAIT_MILLIS))); + configBuilder.addPropertyValue("blockWhenExhausted", Boolean.valueOf(getString(element, ATT_BLOCK_WHEN_EXHAUSTED, String.valueOf(DEFAULT_BLOCK_WHEN_EXHAUSTED)))); + configBuilder.addPropertyValue("testOnBorrow", getBoolean(element, ATT_TEST_ON_BORROW, false)); + configBuilder.addPropertyValue("testOnCreate", getBoolean(element, ATT_TEST_ON_CREATE, false)); + configBuilder.addPropertyValue("testOnReturn", getBoolean(element, ATT_TEST_ON_RETURN, false)); + configBuilder.addPropertyValue("testWhileIdle", getBoolean(element, ATT_TEST_WHILE_IDLE, false)); + configBuilder.addPropertyValue("timeBetweenEvictionRunsMillis", getString(element, ATT_EVICTION_RUN_MILLIS, String.valueOf(DEFAULT_EVICTION_RUN_MILLIS))); + configBuilder.addPropertyValue("numTestsPerEvictionRun", getString(element, ATT_TESTS_PER_EVICTION_RUN, String.valueOf(DEFAULT_TESTS_PER_EVICTION_RUN))); + configBuilder.addPropertyValue("minEvictableIdleTimeMillis", getString(element, ATT_EVICTABLE_TIME_MILLIS, String.valueOf(DEFAULT_EVICTABLE_MILLIS))); + configBuilder.addPropertyValue("softMinEvictableIdleTimeMillis", getString(element, ATT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, String.valueOf(DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS))); + + builder.addConstructorArgValue(configBuilder.getBeanDefinition()); + } + + static class UrlsFactory { + public static String[] urls(String value) { + return StringUtils.commaDelimitedListToStringArray(value); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/config/DefaultRenamingStrategyParser.java b/fine-spring/src/com/fr/third/springframework/ldap/config/DefaultRenamingStrategyParser.java new file mode 100644 index 000000000..b2bcd0469 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/config/DefaultRenamingStrategyParser.java @@ -0,0 +1,49 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.config; + +import com.fr.third.springframework.beans.factory.config.BeanDefinition; +import com.fr.third.springframework.beans.factory.support.AbstractBeanDefinition; +import com.fr.third.springframework.beans.factory.support.BeanDefinitionBuilder; +import com.fr.third.springframework.beans.factory.xml.BeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.ParserContext; +import com.fr.third.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy; +import org.w3c.dom.Element; + +import static com.fr.third.springframework.ldap.config.ParserUtils.getString; + +/** + * @author Mattias Hellborg Arthursson + */ +public class DefaultRenamingStrategyParser implements BeanDefinitionParser { + private static final String ATT_TEMP_SUFFIX = "temp-suffix"; + + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DefaultTempEntryRenamingStrategy.class); + + builder.addPropertyValue("tempSuffix", + getString(element, ATT_TEMP_SUFFIX, + DefaultTempEntryRenamingStrategy.DEFAULT_TEMP_SUFFIX)); + + AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); + parserContext.getContainingBeanDefinition().getPropertyValues() + .addPropertyValue("renamingStrategy", beanDefinition); + + return beanDefinition; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/config/Elements.java b/fine-spring/src/com/fr/third/springframework/ldap/config/Elements.java new file mode 100644 index 000000000..1c013dc41 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/config/Elements.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.config; + +/** + * @author Mattias Hellborg Arthursson + * @author Anindya Chatterjee + */ +public abstract class Elements { + public static final String CONTEXT_SOURCE = "context-source"; + public static final String POOLING = "pooling"; + public static final String POOLING2 = "pooling2"; + public static final String LDAP_TEMPLATE = "ldap-template"; + public static final String TRANSACTION_MANAGER = "transaction-manager"; + public static final String REPOSITORIES = "repositories"; + public static final String DEFAULT_RENAMING_STRATEGY = "default-renaming-strategy"; + public static final String DIFFERENT_SUBTREE_RENAMING_STRATEGY = "different-subtree-renaming-strategy"; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/config/LdapNamespaceHandler.java b/fine-spring/src/com/fr/third/springframework/ldap/config/LdapNamespaceHandler.java new file mode 100644 index 000000000..64953eb25 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/config/LdapNamespaceHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.config; + +import com.fr.third.springframework.beans.factory.xml.NamespaceHandlerSupport; +//import com.fr.third.springframework.data.repository.config.RepositoryBeanDefinitionParser; +//import com.fr.third.springframework.ldap.repository.config.LdapRepositoryConfigurationExtension; +import com.fr.third.springframework.util.ClassUtils; + +/** + * @author Mattias Hellborg Arthursson + * @author Rob Winch + */ +public class LdapNamespaceHandler extends NamespaceHandlerSupport { + static final String REPOSITORY_CLASS_NAME = "com.fr.third.springframework.data.repository.config.RepositoryConfigurationExtension"; + + @Override + public void init() { + //LdapRepositoryConfigurationExtension extension = new LdapRepositoryConfigurationExtension(); + + registerBeanDefinitionParser(Elements.CONTEXT_SOURCE, new ContextSourceParser()); + registerBeanDefinitionParser(Elements.LDAP_TEMPLATE, new LdapTemplateParser()); + registerBeanDefinitionParser(Elements.TRANSACTION_MANAGER, new TransactionManagerParser()); + + if (ClassUtils.isPresent(REPOSITORY_CLASS_NAME, getClass().getClassLoader())) { + //RepositoryBeanDefinitionParser repositoryParser = new RepositoryBeanDefinitionParser(extension); + //registerBeanDefinitionParser(Elements.REPOSITORIES, repositoryParser); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/config/LdapTemplateParser.java b/fine-spring/src/com/fr/third/springframework/ldap/config/LdapTemplateParser.java new file mode 100644 index 000000000..801a245a0 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/config/LdapTemplateParser.java @@ -0,0 +1,76 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.config; + +import com.fr.third.springframework.beans.factory.config.BeanDefinition; +import com.fr.third.springframework.beans.factory.parsing.BeanComponentDefinition; +import com.fr.third.springframework.beans.factory.support.BeanDefinitionBuilder; +import com.fr.third.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.BeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.ParserContext; +import com.fr.third.springframework.ldap.core.LdapTemplate; +import com.fr.third.springframework.ldap.query.SearchScope; +import com.fr.third.springframework.util.StringUtils; +import org.w3c.dom.Element; + +import static com.fr.third.springframework.ldap.config.ParserUtils.getBoolean; +import static com.fr.third.springframework.ldap.config.ParserUtils.getInt; +import static com.fr.third.springframework.ldap.config.ParserUtils.getString; + +/** + * @author Mattias Hellborg Arthursson + */ +public class LdapTemplateParser implements BeanDefinitionParser { + private static final String ATT_COUNT_LIMIT = "count-limit"; + private static final String ATT_TIME_LIMIT = "time-limit"; + private static final String ATT_SEARCH_SCOPE = "search-scope"; + private static final String ATT_IGNORE_PARTIAL_RESULT = "ignore-partial-result"; + private static final String ATT_IGNORE_NAME_NOT_FOUND = "ignore-name-not-found"; + private static final String ATT_ODM_REF = "odm-ref"; + private static final String ATT_CONTEXT_SOURCE_REF = "context-source-ref"; + + private static final String DEFAULT_ID = "ldapTemplate"; + private static final int DEFAULT_COUNT_LIMIT = 0; + private static final int DEFAULT_TIME_LIMIT = 0; + + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(LdapTemplate.class); + + String contextSourceRef = getString(element, ATT_CONTEXT_SOURCE_REF, ContextSourceParser.DEFAULT_ID); + builder.addPropertyReference("contextSource", contextSourceRef); + builder.addPropertyValue("defaultCountLimit", getInt(element, ATT_COUNT_LIMIT, DEFAULT_COUNT_LIMIT)); + builder.addPropertyValue("defaultTimeLimit", getInt(element, ATT_TIME_LIMIT, DEFAULT_TIME_LIMIT)); + + String searchScope = getString(element, ATT_SEARCH_SCOPE, SearchScope.SUBTREE.toString()); + builder.addPropertyValue("defaultSearchScope", SearchScope.valueOf(searchScope).getId()); + builder.addPropertyValue("ignorePartialResultException", getBoolean(element, ATT_IGNORE_PARTIAL_RESULT, false)); + builder.addPropertyValue("ignoreNameNotFoundException", getBoolean(element, ATT_IGNORE_NAME_NOT_FOUND, false)); + + String odmRef = element.getAttribute(ATT_ODM_REF); + if(StringUtils.hasText(odmRef)) { + builder.addPropertyReference("objectDirectoryMapper", odmRef); + } + + String id = getString(element, AbstractBeanDefinitionParser.ID_ATTRIBUTE, DEFAULT_ID); + + BeanDefinition beanDefinition = builder.getBeanDefinition(); + parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, id)); + + return beanDefinition; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/config/ParserUtils.java b/fine-spring/src/com/fr/third/springframework/ldap/config/ParserUtils.java new file mode 100644 index 000000000..b250d6819 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/config/ParserUtils.java @@ -0,0 +1,61 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.config; + +import com.fr.third.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * @author Mattias Hellborg Arthursson + */ +final class ParserUtils { + static final String NAMESPACE = "http://www.springframework.org/schema/ldap"; + + /** + * Not to be instantiated + */ + private ParserUtils() { + + } + + static boolean getBoolean(Element element, String attribute, boolean defaultValue) { + String theValue = element.getAttribute(attribute); + if (StringUtils.hasText(theValue)) { + return Boolean.valueOf(theValue); + } + + return defaultValue; + } + + static String getString(Element element, String attribute, String defaultValue) { + String theValue = element.getAttribute(attribute); + if (StringUtils.hasText(theValue)) { + return theValue; + } + + return defaultValue; + } + + static int getInt(Element element, String attribute, int defaultValue) { + String theValue = element.getAttribute(attribute); + if (StringUtils.hasText(theValue)) { + return Integer.parseInt(theValue); + } + + return defaultValue; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/config/TransactionManagerParser.java b/fine-spring/src/com/fr/third/springframework/ldap/config/TransactionManagerParser.java new file mode 100644 index 000000000..a2c0b0813 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/config/TransactionManagerParser.java @@ -0,0 +1,118 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.config; + +import com.fr.third.springframework.beans.factory.config.BeanDefinition; +import com.fr.third.springframework.beans.factory.parsing.BeanComponentDefinition; +import com.fr.third.springframework.beans.factory.support.BeanDefinitionBuilder; +import com.fr.third.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.BeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.ParserContext; +//import com.fr.third.springframework.ldap.transaction.compensating.manager.ContextSourceAndDataSourceTransactionManager; +//import com.fr.third.springframework.ldap.transaction.compensating.manager.ContextSourceAndHibernateTransactionManager; +import com.fr.third.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager; +import com.fr.third.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy; +import com.fr.third.springframework.ldap.transaction.compensating.support.DifferentSubtreeTempEntryRenamingStrategy; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.StringUtils; +import com.fr.third.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +import static com.fr.third.springframework.ldap.config.ParserUtils.getString; + +/** + * @author Mattias Hellborg Arthursson + */ +public class TransactionManagerParser implements BeanDefinitionParser { + private static final String ATT_CONTEXT_SOURCE_REF = "context-source-ref"; + private static final String ATT_DATA_SOURCE_REF = "data-source-ref"; + private static final String ATT_SESSION_FACTORY_REF = "session-factory-ref"; + + private static final String ATT_TEMP_SUFFIX = "temp-suffix"; + private static final String ATT_SUBTREE_NODE = "subtree-node"; + + private static final String DEFAULT_ID = "transactionManager"; + + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + + String contextSourceRef = getString(element, ATT_CONTEXT_SOURCE_REF, ContextSourceParser.DEFAULT_ID); + String dataSourceRef = element.getAttribute(ATT_DATA_SOURCE_REF); + String sessionFactoryRef = element.getAttribute(ATT_SESSION_FACTORY_REF); + + if(StringUtils.hasText(dataSourceRef) && StringUtils.hasText(sessionFactoryRef)) { + throw new IllegalArgumentException( + String.format("Only one of %s and %s can be specified", + ATT_DATA_SOURCE_REF, ATT_SESSION_FACTORY_REF)); + } + + BeanDefinitionBuilder builder; + //if(StringUtils.hasText(dataSourceRef)) { + // builder = BeanDefinitionBuilder.rootBeanDefinition(ContextSourceAndDataSourceTransactionManager.class); + // builder.addPropertyReference("dataSource", dataSourceRef); + //} else if(StringUtils.hasText(sessionFactoryRef)) { + // builder = BeanDefinitionBuilder.rootBeanDefinition(ContextSourceAndHibernateTransactionManager.class); + // builder.addPropertyReference("sessionFactory", sessionFactoryRef); + //} else { + // // Standard transaction manager + // builder = BeanDefinitionBuilder.rootBeanDefinition(ContextSourceTransactionManager.class); + //} + builder = BeanDefinitionBuilder.rootBeanDefinition(ContextSourceTransactionManager.class); + + builder.addPropertyReference("contextSource", contextSourceRef); + + Element defaultStrategyChild = DomUtils.getChildElementByTagName(element, Elements.DEFAULT_RENAMING_STRATEGY); + Element differentSubtreeChild = DomUtils.getChildElementByTagName(element, Elements.DIFFERENT_SUBTREE_RENAMING_STRATEGY); + + if(defaultStrategyChild != null) { + builder.addPropertyValue("renamingStrategy", parseDefaultRenamingStrategy(defaultStrategyChild)); + } + + if(differentSubtreeChild != null) { + builder.addPropertyValue("renamingStrategy", parseDifferentSubtreeRenamingStrategy(differentSubtreeChild)); + } + + String id = getString(element, AbstractBeanDefinitionParser.ID_ATTRIBUTE, DEFAULT_ID); + + BeanDefinition beanDefinition = builder.getBeanDefinition(); + parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, id)); + + return beanDefinition; + } + + private BeanDefinition parseDifferentSubtreeRenamingStrategy(Element element) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DifferentSubtreeTempEntryRenamingStrategy.class); + + String subtreeNode = element.getAttribute(ATT_SUBTREE_NODE); + Assert.hasText(subtreeNode, ATT_SUBTREE_NODE + " must be specified"); + + builder.addConstructorArgValue(subtreeNode); + + return builder.getBeanDefinition(); + } + + public BeanDefinition parseDefaultRenamingStrategy(Element element) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DefaultTempEntryRenamingStrategy.class); + + builder.addPropertyValue("tempSuffix", + getString(element, ATT_TEMP_SUFFIX, + DefaultTempEntryRenamingStrategy.DEFAULT_TEMP_SUFFIX)); + + return builder.getBeanDefinition(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/control/AbstractFallbackRequestAndResponseControlDirContextProcessor.java b/fine-spring/src/com/fr/third/springframework/ldap/control/AbstractFallbackRequestAndResponseControlDirContextProcessor.java new file mode 100644 index 000000000..33be0ba21 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/control/AbstractFallbackRequestAndResponseControlDirContextProcessor.java @@ -0,0 +1,212 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.control; + +import com.fr.third.springframework.ldap.UncategorizedLdapException; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.ClassUtils; +import com.fr.third.springframework.util.ReflectionUtils; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * Convenient base class useful when implementing a standard DirContextProcessor + * which has a request control and a response control. It handles the loading of + * the control classes, using fallback implementations specified by the subclass + * if necessary. It handles the request control constructor invocation; it only + * needs the constructor arguments to be provided. It also handles most of the + * work in the post processing of the response control, only delegating to a + * template method for the actual value retrieval. In short, it makes it easy to + * implement a custom DirContextProcessor.

+ * + *

+ * public class SortControlDirContextProcessor extends AbstractFallbackRequestAndResponseControlDirContextProcessor {
+ * 	String sortKey;
+ * 
+ * 	private boolean sorted = false;
+ * 
+ * 	private int resultCode = -1;
+ * 
+ * 	public SortControlDirContextProcessor(String sortKey) {
+ * 		this.sortKey = sortKey;
+ * 
+ * 		defaultRequestControl = "javax.naming.ldap.SortControl";
+ * 		defaultResponseControl = "com.sun.jndi.ldap.ctl.SortControl";
+ * 		fallbackRequestControl = "javax.naming.ldap.SortResponseControl";
+ * 		fallbackResponseControl = "com.sun.jndi.ldap.ctl.SortResponseControl";
+ * 
+ * 		loadControlClasses();
+ * 	}
+ * 
+ * 	public boolean isSorted() {
+ * 		return sorted;
+ * 	}
+ * 
+ * 	public int getResultCode() {
+ * 		return resultCode;
+ * 	}
+ * 
+ * 	public Control createRequestControl() {
+ * 		return super.createRequestControl(new Class[] { String[].class, boolean.class }, new Object[] {
+ *				new String[] { sortKey }, Boolean.valueOf(critical) });
+ * 	}
+ * 
+ * 	protected void handleResponse(Object control) {
+ * 		Boolean result = (Boolean) invokeMethod("isSorted", responseControlClass, control);
+ * 		this.sorted = result.booleanValue();
+ * 		Integer code = (Integer) invokeMethod("getResultCode", responseControlClass, control);
+ * 		resultCode = code.intValue();
+ * 	}
+ * }
+ * 
+ * + * @author Ulrik Sandberg + */ +public abstract class AbstractFallbackRequestAndResponseControlDirContextProcessor extends + AbstractRequestControlDirContextProcessor { + + private static final boolean CRITICAL_CONTROL = true; + + protected Class responseControlClass; + + protected Class requestControlClass; + + protected boolean critical = CRITICAL_CONTROL; + + protected String defaultRequestControl; + + protected String defaultResponseControl; + + protected String fallbackRequestControl; + + protected String fallbackResponseControl; + + protected void loadControlClasses() { + Assert.notNull(defaultRequestControl, "defaultRequestControl must not be null"); + Assert.notNull(defaultResponseControl, "defaultResponseControl must not be null"); + Assert.notNull(fallbackRequestControl, "fallbackRequestControl must not be null"); + Assert.notNull(fallbackResponseControl, "fallbackReponseControl must not be null"); + try { + requestControlClass = Class.forName(defaultRequestControl); + responseControlClass = Class.forName(defaultResponseControl); + } + catch (ClassNotFoundException e) { + log.debug("Default control classes not found - falling back to LdapBP classes", e); + + try { + requestControlClass = Class.forName(fallbackRequestControl); + responseControlClass = Class.forName(fallbackResponseControl); + } + catch (ClassNotFoundException e1) { + throw new UncategorizedLdapException( + "Neither default nor fallback classes are available - unable to proceed", e); + } + } + } + + /** + * Set the class of the expected ResponseControl for the sorted result + * response. + * + * @param responseControlClass Class of the expected response control. + */ + public void setResponseControlClass(Class responseControlClass) { + this.responseControlClass = responseControlClass; + } + + public void setRequestControlClass(Class requestControlClass) { + this.requestControlClass = requestControlClass; + } + + /** + * Utility method for invoking a method on a Control. + * @param method name of method to invoke + * @param clazz Class of the object that the method should be invoked on + * @param control Instance that the method should be invoked on + * @return the invocation result, if any + */ + protected Object invokeMethod(String method, Class clazz, Object control) { + Method actualMethod = ReflectionUtils.findMethod(clazz, method); + return ReflectionUtils.invokeMethod(actualMethod, control); + } + + /** + * Creates a request control using the constructor parameters given in + * params. + * @param paramTypes Types of the constructor parameters + * @param params Actual constructor parameters + * @return Control to be used by the DirContextProcessor + */ + public Control createRequestControl(Class[] paramTypes, Object[] params) { + Constructor constructor = ClassUtils.getConstructorIfAvailable(requestControlClass, paramTypes); + if (constructor == null) { + throw new IllegalArgumentException("Failed to find an appropriate RequestControl constructor"); + } + + Control result = null; + try { + result = (Control) constructor.newInstance(params); + } + catch (Exception e) { + ReflectionUtils.handleReflectionException(e); + } + + return result; + } + + /* + * @see + * com.fr.third.springframework.ldap.core.DirContextProcessor#postProcess(javax.naming + * .directory.DirContext) + */ + public void postProcess(DirContext ctx) throws NamingException { + + LdapContext ldapContext = (LdapContext) ctx; + Control[] responseControls = ldapContext.getResponseControls(); + if (responseControls == null) { + responseControls = new Control[0]; + } + + // Go through response controls and get info, regardless of class + for (Control responseControl : responseControls) { + // check for match, try fallback otherwise + if (responseControl.getClass().isAssignableFrom(responseControlClass)) { + handleResponse(responseControl); + return; + } + } + + log.info("No matching response control found - looking for '" + responseControlClass); + } + + /** + * Set whether this control should be indicated as critical. + * + * @param critical whether the control is critical. + * @since 2.0 + */ + public void setCritical(boolean critical) { + this.critical = critical; + } + + protected abstract void handleResponse(Object control); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/control/AbstractRequestControlDirContextProcessor.java b/fine-spring/src/com/fr/third/springframework/ldap/control/AbstractRequestControlDirContextProcessor.java new file mode 100644 index 000000000..ae28fc54c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/control/AbstractRequestControlDirContextProcessor.java @@ -0,0 +1,122 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.control; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.DirContextProcessor; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +/** + * Abstract superclass with responsibility to apply a single RequestControl on + * an LdapContext, preserving any existing controls. Subclasses should implement + * {@link DirContextProcessor#postProcess(DirContext)} and template method + * {@link #createRequestControl()}. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public abstract class AbstractRequestControlDirContextProcessor implements DirContextProcessor { + protected Logger log = LoggerFactory.getLogger(AbstractRequestControlDirContextProcessor.class); + + private boolean replaceSameControlEnabled = true; + + /** + * If there already exists a request control of the same class as the one + * created by {@link #createRequestControl()} in the context, the new + * control can either replace the existing one (default behavior) or be + * added. + * + * @return true if an already existing control will be replaced + */ + public boolean isReplaceSameControlEnabled() { + return replaceSameControlEnabled; + } + + /** + * If there already exists a request control of the same class as the one + * created by {@link #createRequestControl()} in the context, the new + * control can either replace the existing one (default behavior) or be + * added. + * + * @param replaceSameControlEnabled true if an already + * existing control should be replaced + */ + public void setReplaceSameControlEnabled(boolean replaceSameControlEnabled) { + this.replaceSameControlEnabled = replaceSameControlEnabled; + } + + /** + * Get the existing RequestControls from the LdapContext, call + * {@link #createRequestControl()} to get a new instance, build a new array + * of Controls and set it on the LdapContext. + *

+ * The {@link Control} feature is specific for LDAP v3 and thus applies only + * to {@link LdapContext}. However, the generic DirContextProcessor + * mechanism used for calling preProcess and + * postProcess uses DirContext, since it also works for LDAP + * v2. This is the reason that DirContext has to be cast to a LdapContext. + * + * @param ctx an LdapContext instance. + * @throws NamingException + * @throws IllegalArgumentException if the supplied DirContext is not an + * LdapContext. + */ + public void preProcess(DirContext ctx) throws NamingException { + LdapContext ldapContext; + if (ctx instanceof LdapContext) { + ldapContext = (LdapContext) ctx; + } + else { + throw new IllegalArgumentException("Request Control operations require LDAPv3 - " + + "Context must be of type LdapContext"); + } + + Control[] requestControls = ldapContext.getRequestControls(); + if (requestControls == null) { + requestControls = new Control[0]; + } + Control newControl = createRequestControl(); + + Control[] newControls = new Control[requestControls.length + 1]; + for (int i = 0; i < requestControls.length; i++) { + if (replaceSameControlEnabled && requestControls[i].getClass() == newControl.getClass()) { + log.debug("Replacing already existing control in context: " + newControl); + requestControls[i] = newControl; + ldapContext.setRequestControls(requestControls); + return; + } + newControls[i] = requestControls[i]; + } + + // Add the new Control at the end of the array. + newControls[newControls.length - 1] = newControl; + + ldapContext.setRequestControls(newControls); + } + + /** + * Create an instance of the appropriate RequestControl. + * + * @return the new instance. + */ + public abstract Control createRequestControl(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/control/CreateControlFailedException.java b/fine-spring/src/com/fr/third/springframework/ldap/control/CreateControlFailedException.java new file mode 100644 index 000000000..5e4258ad6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/control/CreateControlFailedException.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.control; + +import com.fr.third.springframework.ldap.NamingException; + +/** + * Thrown by an AbstractRequestControlDirContextProcessor when it cannot create + * a request control. + * + * @author Ulrik Sandberg + * @since 1.2 + */ +public class CreateControlFailedException extends NamingException { + + /** + * Create a new CreateControlFailedException. + * + * @param msg + * the detail message + */ + public CreateControlFailedException(String msg) { + super(msg); + } + + /** + * Create a new CreateControlFailedException. + * + * @param msg + * the detail message + * @param cause + * the root cause (if any) + */ + public CreateControlFailedException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResult.java b/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResult.java new file mode 100644 index 000000000..399723952 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResult.java @@ -0,0 +1,85 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.control; + +import java.util.List; + +/** + * Bean to encapsulate a result List and a {@link PagedResultsCookie} to use for + * returning the results when using {@link PagedResultsRequestControl}. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + * @deprecated + */ +public class PagedResult { + + private List resultList; + + private PagedResultsCookie cookie; + + /** + * Constructs a PagedResults using the supplied List and + * {@link PagedResultsCookie}. + * + * @param resultList + * the result list. + * @param cookie + * the cookie. + */ + public PagedResult(List resultList, PagedResultsCookie cookie) { + this.resultList = resultList; + this.cookie = cookie; + } + + /** + * Get the cookie. + * + * @return the cookie. + */ + public PagedResultsCookie getCookie() { + return cookie; + } + + /** + * Get the result list. + * + * @return the result list. + */ + public List getResultList() { + return resultList; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PagedResult that = (PagedResult) o; + + if (cookie != null ? !cookie.equals(that.cookie) : that.cookie != null) return false; + if (resultList != null ? !resultList.equals(that.resultList) : that.resultList != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = resultList != null ? resultList.hashCode() : 0; + result = 31 * result + (cookie != null ? cookie.hashCode() : 0); + return result; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResultsCookie.java b/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResultsCookie.java new file mode 100644 index 000000000..9721bbab7 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResultsCookie.java @@ -0,0 +1,76 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.control; + + +import java.util.Arrays; + +/** + * Wrapper class for the cookie returned when using the + * {@link PagedResultsControl}. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class PagedResultsCookie { + + private byte[] cookie; + + /** + * Constructor. + * + * @param cookie + * the cookie returned by a PagedResultsResponseControl. + */ + public PagedResultsCookie(byte[] cookie) { + if (cookie != null) { + this.cookie = Arrays.copyOf(cookie, cookie.length); + } else { + this.cookie = null; + } + } + + /** + * Get the cookie. + * + * @return the cookie. This value may be null, indicating that there are no more requests, + * or that the control wasn't supported by the server. + */ + public byte[] getCookie() { + if (cookie != null) { + return Arrays.copyOf(cookie, cookie.length); + } else { + return null; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PagedResultsCookie that = (PagedResultsCookie) o; + + if (!Arrays.equals(cookie, that.cookie)) return false; + + return true; + } + + @Override + public int hashCode() { + return cookie != null ? Arrays.hashCode(cookie) : 0; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResultsDirContextProcessor.java b/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResultsDirContextProcessor.java new file mode 100644 index 000000000..cf1e420fb --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResultsDirContextProcessor.java @@ -0,0 +1,155 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.control; + +import javax.naming.ldap.Control; + +/** + * DirContextProcessor implementation for managing the paged results control. + * Note that due to the internal workings of LdapTemplate, the + * target connection is closed after each LDAP call. The PagedResults control + * require the same connection be used for each call, which means we need to + * make sure the target connection is never actually closed. There's basically + * two ways of making this happen: use the SingleContextSource + * implementation or make sure all calls happen within a single LDAP transaction + * (using ContextSourceTransactionManager). + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class PagedResultsDirContextProcessor extends AbstractFallbackRequestAndResponseControlDirContextProcessor { + + private static final String DEFAULT_REQUEST_CONTROL = "javax.naming.ldap.PagedResultsControl"; + + private static final String FALLBACK_REQUEST_CONTROL = "com.sun.jndi.ldap.ctl.PagedResultsControl"; + + private static final String DEFAULT_RESPONSE_CONTROL = "javax.naming.ldap.PagedResultsResponseControl"; + + private static final String FALLBACK_RESPONSE_CONTROL = "com.sun.jndi.ldap.ctl.PagedResultsResponseControl"; + + private int pageSize; + + private PagedResultsCookie cookie; + + private int resultSize; + + private boolean more = true; + + /** + * Constructs a new instance. This constructor should be used when + * performing the first paged search operation, when no other results have + * been retrieved. + * + * @param pageSize the page size. + */ + public PagedResultsDirContextProcessor(int pageSize) { + this(pageSize, null); + } + + /** + * Constructs a new instance with the supplied page size and cookie. The + * cookie must be the exact same instance as received from a previous paged + * results search, or null if it is the first in an operation + * sequence. + * + * @param pageSize the page size. + * @param cookie the cookie, as received from a previous search. + */ + public PagedResultsDirContextProcessor(int pageSize, PagedResultsCookie cookie) { + this.pageSize = pageSize; + this.cookie = cookie; + + defaultRequestControl = DEFAULT_REQUEST_CONTROL; + defaultResponseControl = DEFAULT_RESPONSE_CONTROL; + fallbackRequestControl = FALLBACK_REQUEST_CONTROL; + fallbackResponseControl = FALLBACK_RESPONSE_CONTROL; + + loadControlClasses(); + } + + /** + * Get the cookie. + * + * @return the cookie. The cookie will always be set after at leas one query, however the actual cookie content + * can be null, indicating that there are no more results, in which case {@link #hasMore()} will return + * false. + * @see #hasMore() + */ + public PagedResultsCookie getCookie() { + return cookie; + } + + /** + * Get the page size. + * + * @return the page size. + */ + public int getPageSize() { + return pageSize; + } + + /** + * Get the total estimated number of entries that matches the issued search. + * Note that this value is optional for the LDAP server to return, so it + * does not always contain any valid data. + * + * @return the estimated result size, if returned from the server. + */ + public int getResultSize() { + return resultSize; + } + + /* + * @see + * com.fr.third.springframework.ldap.control.AbstractRequestControlDirContextProcessor + * #createRequestControl() + */ + public Control createRequestControl() { + byte[] actualCookie = null; + if (cookie != null) { + actualCookie = cookie.getCookie(); + } + return super.createRequestControl(new Class[] { int.class, byte[].class, boolean.class }, + new Object[] {pageSize, actualCookie, critical}); + } + + /** + * Check whether there are more results to retrieved. When there are no more results to retrieve, + * this is indicated by a null cookie being returned from the server. + * When this happen, the internal status will set to false. + * + * @return true if there are more results to retrieve, false otherwise. + * @since 2.0 + */ + public boolean hasMore() { + return more; + } + + /* + * @seecom.fr.third.springframework.ldap.control. + * AbstractFallbackRequestAndResponseControlDirContextProcessor + * #handleResponse(java.lang.Object) + */ + protected void handleResponse(Object control) { + byte[] result = (byte[]) invokeMethod("getCookie", responseControlClass, control); + if(result == null) { + more = false; + } + this.cookie = new PagedResultsCookie(result); + this.resultSize = (Integer) invokeMethod("getResultSize", responseControlClass, control); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResultsRequestControl.java b/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResultsRequestControl.java new file mode 100644 index 000000000..e6e6727d3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/control/PagedResultsRequestControl.java @@ -0,0 +1,223 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.control; + +import com.fr.third.springframework.ldap.UncategorizedLdapException; +import com.fr.third.springframework.util.ClassUtils; +import com.fr.third.springframework.util.ReflectionUtils; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * DirContextProcessor implementation for managing the paged results control. + * Note that due to the internal workings of LdapTemplate, the + * target connection is closed after each LDAP call. The PagedResults control + * require the same connection be used for each call, which means we need to + * make sure the target connection is never actually closed. There's basically + * two ways of making this happen: use the SingleContextSource + * implementation or make sure all calls happen within a single LDAP transaction + * (using ContextSourceTransactionManager). + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + * @deprecated Use PagedResultsDirContextProcessor instead. + */ +public class PagedResultsRequestControl extends AbstractRequestControlDirContextProcessor { + + private static final boolean CRITICAL_CONTROL = true; + + private static final String DEFAULT_REQUEST_CONTROL = "javax.naming.ldap.PagedResultsControl"; + + private static final String LDAPBP_REQUEST_CONTROL = "com.sun.jndi.ldap.ctl.PagedResultsControl"; + + private static final String DEFAULT_RESPONSE_CONTROL = "javax.naming.ldap.PagedResultsResponseControl"; + + private static final String LDAPBP_RESPONSE_CONTROL = "com.sun.jndi.ldap.ctl.PagedResultsResponseControl"; + + private int pageSize; + + private PagedResultsCookie cookie; + + private int resultSize; + + private boolean critical = CRITICAL_CONTROL; + + private Class responseControlClass; + + private Class requestControlClass; + + /** + * Constructs a new instance. This constructor should be used when + * performing the first paged search operation, when no other results have + * been retrieved. + * + * @param pageSize the page size. + */ + public PagedResultsRequestControl(int pageSize) { + this(pageSize, null); + } + + /** + * Constructs a new instance with the supplied page size and cookie. The + * cookie must be the exact same instance as received from a previous paged + * resullts search, or null if it is the first in an operation + * sequence. + * + * @param pageSize the page size. + * @param cookie the cookie, as received from a previous search. + */ + public PagedResultsRequestControl(int pageSize, PagedResultsCookie cookie) { + this.pageSize = pageSize; + this.cookie = cookie; + + loadControlClasses(); + } + + private void loadControlClasses() { + try { + requestControlClass = Class.forName(DEFAULT_REQUEST_CONTROL); + responseControlClass = Class.forName(DEFAULT_RESPONSE_CONTROL); + } + catch (ClassNotFoundException e) { + log.debug("Default control classes not found - falling back to LdapBP classes", e); + + try { + requestControlClass = Class.forName(LDAPBP_REQUEST_CONTROL); + responseControlClass = Class.forName(LDAPBP_RESPONSE_CONTROL); + } + catch (ClassNotFoundException e1) { + throw new UncategorizedLdapException( + "Neither default nor fallback classes are available - unable to proceed", e); + } + + } + } + + /** + * Get the cookie. + * + * @return the cookie. + */ + public PagedResultsCookie getCookie() { + return cookie; + } + + /** + * Get the page size. + * + * @return the page size. + */ + public int getPageSize() { + return pageSize; + } + + /** + * Get the total estimated number of entries that matches the issued search. + * Note that this value is optional for the LDAP server to return, so it + * does not always contain any valid data. + * + * @return the estimated result size, if returned from the server. + */ + public int getResultSize() { + return resultSize; + } + + /** + * Set the class of the expected ResponseControl for the paged results + * response. + * + * @param responseControlClass Class of the expected response control. + */ + public void setResponseControlClass(Class responseControlClass) { + this.responseControlClass = responseControlClass; + } + + public void setRequestControlClass(Class requestControlClass) { + this.requestControlClass = requestControlClass; + } + + /* + * @see + * com.fr.third.springframework.ldap.control.AbstractRequestControlDirContextProcessor + * #createRequestControl() + */ + + public Control createRequestControl() { + byte[] actualCookie = null; + if (cookie != null) { + actualCookie = cookie.getCookie(); + } + Constructor constructor = ClassUtils.getConstructorIfAvailable(requestControlClass, new Class[] { int.class, + byte[].class, boolean.class }); + if (constructor == null) { + throw new IllegalArgumentException("Failed to find an appropriate RequestControl constructor"); + } + + Control result = null; + try { + result = (Control) constructor.newInstance(pageSize, actualCookie, + critical); + } + catch (Exception e) { + ReflectionUtils.handleReflectionException(e); + } + + return result; + } + + /* + * @see + * com.fr.third.springframework.ldap.core.DirContextProcessor#postProcess(javax.naming + * .directory.DirContext) + */ + + public void postProcess(DirContext ctx) throws NamingException { + + LdapContext ldapContext = (LdapContext) ctx; + Control[] responseControls = ldapContext.getResponseControls(); + if (responseControls == null) { + responseControls = new Control[0]; + } + + // Go through response controls and get info, regardless of class + for (int i = 0; i < responseControls.length; i++) { + Control responseControl = responseControls[i]; + + // check for match, try fallback otherwise + if (responseControl.getClass().isAssignableFrom(responseControlClass)) { + Object control = responseControl; + byte[] result = (byte[]) invokeMethod("getCookie", responseControlClass, control); + this.cookie = new PagedResultsCookie(result); + Integer wrapper = (Integer) invokeMethod("getResultSize", responseControlClass, control); + this.resultSize = wrapper.intValue(); + return; + } + } + + log.error("No matching response control found for paged results - looking for '{}", responseControlClass); + } + + private Object invokeMethod(String method, Class clazz, Object control) { + Method actualMethod = ReflectionUtils.findMethod(clazz, method); + return ReflectionUtils.invokeMethod(actualMethod, control); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/control/SortControlDirContextProcessor.java b/fine-spring/src/com/fr/third/springframework/ldap/control/SortControlDirContextProcessor.java new file mode 100644 index 000000000..fb7e4dc1d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/control/SortControlDirContextProcessor.java @@ -0,0 +1,119 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.control; + +import javax.naming.ldap.Control; + +/** + * DirContextProcessor implementation for managing the SortControl. Note that + * this class is stateful, so a new instance needs to be instantiated for each + * new search. + * + * @author Ulrik Sandberg + */ +public class SortControlDirContextProcessor extends AbstractFallbackRequestAndResponseControlDirContextProcessor { + + private static final String DEFAULT_REQUEST_CONTROL = "javax.naming.ldap.SortControl"; + + private static final String FALLBACK_REQUEST_CONTROL = "com.sun.jndi.ldap.ctl.SortControl"; + + private static final String DEFAULT_RESPONSE_CONTROL = "javax.naming.ldap.SortResponseControl"; + + private static final String FALLBACK_RESPONSE_CONTROL = "com.sun.jndi.ldap.ctl.SortResponseControl"; + + /** + * What key to sort on. + */ + String sortKey; + + /** + * Whether the search result actually was sorted. + */ + private boolean sorted; + + /** + * The result code of the supposedly sorted search. + */ + private int resultCode; + + /** + * Constructs a new instance using the supplied sort key. + * + * @param sortKey the sort key, i.e. the attribute name to sort on. + */ + public SortControlDirContextProcessor(String sortKey) { + this.sortKey = sortKey; + this.sorted = false; + this.resultCode = -1; + + defaultRequestControl = DEFAULT_REQUEST_CONTROL; + defaultResponseControl = DEFAULT_RESPONSE_CONTROL; + + fallbackRequestControl = FALLBACK_REQUEST_CONTROL; + fallbackResponseControl = FALLBACK_RESPONSE_CONTROL; + + loadControlClasses(); + } + + /** + * Check whether the returned values were actually sorted by the server. + * + * @return true if the result was sorted, false + * otherwise. + */ + public boolean isSorted() { + return sorted; + } + + /** + * Get the result code returned by the control. + * + * @return result code. + */ + public int getResultCode() { + return resultCode; + } + + /** + * Get the sort key. + * + * @return the sort key. + */ + public String getSortKey() { + return sortKey; + } + + /* + * @see + * com.fr.third.springframework.ldap.control.AbstractRequestControlDirContextProcessor + * #createRequestControl() + */ + public Control createRequestControl() { + return super.createRequestControl(new Class[] { String[].class, boolean.class }, new Object[] { + new String[] { sortKey }, critical}); + } + + /* + * @see com.fr.third.springframework.ldap.control. + * AbstractFallbackRequestAndResponseControlDirContextProcessor + * #handleResponse(java.lang.Object) + */ + protected void handleResponse(Object control) { + this.sorted = (Boolean) invokeMethod("isSorted", responseControlClass, control); + this.resultCode = (Integer) invokeMethod("getResultCode", responseControlClass, control); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/control/package.html b/fine-spring/src/com/fr/third/springframework/ldap/control/package.html new file mode 100644 index 000000000..af3c0892a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/control/package.html @@ -0,0 +1,7 @@ + + + +Support classes for custom request control context processors. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/AttributeModificationsAware.java b/fine-spring/src/com/fr/third/springframework/ldap/core/AttributeModificationsAware.java new file mode 100644 index 000000000..4b99527c6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/AttributeModificationsAware.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import javax.naming.directory.ModificationItem; + +/** + * Indicates that the implementing class is capable of keeping track of any + * attribute modifications and return them as ModificationItems. + * + * @author Mattias Hellborg Arthursson + * + */ +public interface AttributeModificationsAware { + + /** + * Creates an array of which attributes have been changed, added or removed + * since the initialization of this object. + * + * @return an array of modification items. + */ + ModificationItem[] getModificationItems(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/AttributesMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/core/AttributesMapper.java new file mode 100644 index 000000000..bc4a80ea9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/AttributesMapper.java @@ -0,0 +1,55 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +/** + * An interface used by LdapTemplate for mapping LDAP Attributes to beans. + * Implementions of this interface perform the actual work of extracting + * results, but need not worry about exception handling. NamingExceptions will + * be caught and handled correctly by the {@link LdapTemplate} class. + *

+ * Typically used in search methods of {@link LdapTemplate}. + * AttributeMapper objects are normally stateless and thus + * reusable; they are ideal for implementing attribute-mapping logic in one + * place. + *

+ * Alternatively, consider using a {@link ContextMapper} in stead. + * + * @see LdapTemplate#search(Name, String, AttributesMapper) + * @see LdapTemplate#lookup(Name, AttributesMapper) + * @see ContextMapper + * + * @author Mattias Hellborg Arthursson + */ +public interface AttributesMapper { + /** + * Map Attributes to an object. The supplied attributes are the attributes + * from a single SearchResult. + * + * @param attributes + * attributes from a SearchResult. + * @return an object built from the attributes. + * @throws NamingException + * if any error occurs mapping the attributes + */ + T mapFromAttributes(Attributes attributes) + throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/AttributesMapperCallbackHandler.java b/fine-spring/src/com/fr/third/springframework/ldap/core/AttributesMapperCallbackHandler.java new file mode 100644 index 000000000..d32208a09 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/AttributesMapperCallbackHandler.java @@ -0,0 +1,68 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.ldap.support.LdapUtils; + +import javax.naming.NameClassPair; +import javax.naming.directory.Attributes; +import javax.naming.directory.SearchResult; + +/** + * A CollectingNameClassPairCallbackHandler to wrap an {@link AttributesMapper}. + * That is, the found object is extracted from the {@link Attributes} of each + * {@link SearchResult}, and then passed to the specified + * {@link AttributesMapper} for translation. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + * @since 1.2 + */ +public class AttributesMapperCallbackHandler extends CollectingNameClassPairCallbackHandler { + private AttributesMapper mapper; + + /** + * Constructs a new instance around the specified {@link AttributesMapper}. + * + * @param mapper the target mapper. + */ + public AttributesMapperCallbackHandler(AttributesMapper mapper) { + this.mapper = mapper; + } + + /** + * Cast the NameClassPair to a SearchResult and pass its attributes to the + * {@link AttributesMapper}. + * + * @param nameClassPair a SearchResult instance. + * @return the Object returned from the mapper. + */ + public T getObjectFromNameClassPair(NameClassPair nameClassPair) { + if (!(nameClassPair instanceof SearchResult)) { + throw new IllegalArgumentException("Parameter must be an instance of SearchResult"); + } + + SearchResult searchResult = (SearchResult) nameClassPair; + Attributes attributes = searchResult.getAttributes(); + try { + return mapper.mapFromAttributes(attributes); + } + catch (javax.naming.NamingException e) { + throw LdapUtils.convertLdapException(e); + } + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticatedLdapEntryContextCallback.java b/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticatedLdapEntryContextCallback.java new file mode 100644 index 000000000..9543fd50b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticatedLdapEntryContextCallback.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import javax.naming.directory.DirContext; + +/** + * Callback interface to be used in the authentication methods in + * {@link LdapOperations} for performing operations on individually + * authenticated contexts. + * + * @author Mattias Hellborg Arthursson + * @since 1.3 + */ +public interface AuthenticatedLdapEntryContextCallback { + /** + * Perform some LDAP operation on the supplied authenticated + * DirContext instance. The target context will be + * automatically closed. + * + * @param ctx the DirContext instance to perform an operation + * on. + * @param ldapEntryIdentification the identification of the LDAP entry used + * to authenticate the supplied DirContext. + */ + void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticatedLdapEntryContextMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticatedLdapEntryContextMapper.java new file mode 100644 index 000000000..20c36a569 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticatedLdapEntryContextMapper.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import javax.naming.directory.DirContext; + +/** + * Callback interface to be used in the authentication methods in + * {@link LdapOperations} for performing operations on individually + * authenticated contexts. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public interface AuthenticatedLdapEntryContextMapper { + /** + * Perform some LDAP operation on the supplied authenticated + * DirContext instance. The target context will be + * automatically closed. + * + * @param ctx the DirContext instance to perform an operation + * on. + * @param ldapEntryIdentification the identification of the LDAP entry used + * to authenticate the supplied DirContext. + * @return the result of the operation, if any. + */ + T mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticationErrorCallback.java b/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticationErrorCallback.java new file mode 100644 index 000000000..7625014ca --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticationErrorCallback.java @@ -0,0 +1,20 @@ +package com.fr.third.springframework.ldap.core; + +/** + * Callback interface to be used in the authentication methods in + * {@link LdapOperations} for performing operations when there + * are authentication errors. Can be useful when the cause of the + * authentication failure needs to be retrieved. + * + * @author Ulrik Sandberg + * @since 1.3.1 + */ +public interface AuthenticationErrorCallback { + /** + * This method will be called with the authentication exception in + * case there is a problem with the authentication. + * + * @param e the exception that was caught in the authentication method + */ + void execute(Exception e); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticationSource.java b/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticationSource.java new file mode 100644 index 000000000..badf8a104 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/AuthenticationSource.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +/** + * An AuthenticationSource is responsible for providing the + * principal (user DN) and credentials to be used when creating a new context. + * + * @author Mattias Hellborg Arthursson + * + */ +public interface AuthenticationSource { + /** + * Get the principal to use when creating an authenticated context. + * + * @return the principal (userDn). + */ + String getPrincipal(); + + /** + * Get the credentials to use when creating an authenticated context. + * + * @return the credentials (password). + */ + String getCredentials(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/CollectingAuthenticationErrorCallback.java b/fine-spring/src/com/fr/third/springframework/ldap/core/CollectingAuthenticationErrorCallback.java new file mode 100644 index 000000000..601c6315d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/CollectingAuthenticationErrorCallback.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +/** + * Convenience implementation of AuthenticationErrorCallback that stores the + * given exception and provides a method for retrieving it. The caller of the + * authenticate method can provide an instance of this class as an error + * callback. If the authentication fails, the caller can ask the callback + * instance for the actual authentication exception. + * + * @author Ulrik Sandberg + * @since 1.3.1 + */ +public final class CollectingAuthenticationErrorCallback implements AuthenticationErrorCallback { + private Exception error; + + /* + * (non-Javadoc) + * + * @see + * com.fr.third.springframework.ldap.core.AuthenticationErrorCallback#execute(java + * .lang.Exception) + */ + public void execute(Exception e) { + this.error = e; + } + + /** + * @return the collected exception + */ + public Exception getError() { + return error; + } + + /** + * Check whether this callback has collected an error. + * + * @return true if an error has been collected, false otherwise. + */ + public boolean hasError() { + return error != null; + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/CollectingNameClassPairCallbackHandler.java b/fine-spring/src/com/fr/third/springframework/ldap/core/CollectingNameClassPairCallbackHandler.java new file mode 100644 index 000000000..35127a658 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/CollectingNameClassPairCallbackHandler.java @@ -0,0 +1,64 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import javax.naming.NameClassPair; +import javax.naming.NamingException; +import java.util.LinkedList; +import java.util.List; + +/** + * A NameClassPairCallbackHandler to collect all results in an internal List. + * + * @see LdapTemplate + * + * @author Mattias Hellborg Arthursson + */ +public abstract class CollectingNameClassPairCallbackHandler implements + NameClassPairCallbackHandler { + + private List list = new LinkedList(); + + /** + * Get the assembled list. + * + * @return the list of all assembled objects. + */ + public List getList() { + return list; + } + + /** + * Pass on the supplied NameClassPair to + * {@link #getObjectFromNameClassPair(NameClassPair)} and add the result to + * the internal list. + */ + public final void handleNameClassPair(NameClassPair nameClassPair) throws NamingException { + list.add(getObjectFromNameClassPair(nameClassPair)); + } + + /** + * Handle a NameClassPair and transform it to an Object of the desired type + * and with data from the NameClassPair. + * + * @param nameClassPair + * a NameClassPair from a search operation. + * @return an object constructed from the data in the NameClassPair. + * @throws NamingException if an error occurs. + */ + public abstract T getObjectFromNameClassPair( + NameClassPair nameClassPair) throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/ContextAssembler.java b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextAssembler.java new file mode 100644 index 000000000..01b0fbfac --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextAssembler.java @@ -0,0 +1,36 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +/** + * Helper interface to be used by Dao implementations for assembling to and from + * context. Useful if we have assembler classes responsible for mapping to and + * from a specific entry. + * + * @author Mattias Hellborg Arthursson + */ +public interface ContextAssembler extends ContextMapper { + /** + * Map the supplied object to the specified context. + * + * @param obj + * the object to read data from. + * @param ctx + * the context to map to. + */ + void mapToContext(Object obj, Object ctx); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/ContextExecutor.java b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextExecutor.java new file mode 100644 index 000000000..bb942572a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextExecutor.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +/** + * Interface for delegating an actual operation to be performed on a + * DirContext. For searches, use {@link SearchExecutor} in + * stead. A typical usage of this interface could be e.g.: + * + *

+ * ContextExecutor executor = new ContextExecutor() {
+ *     public Object executeWithContext(DirContext ctx) throws NamingException {
+ *         return ctx.lookup(dn);
+ *     }
+ * };
+ * 
+ * + * @see LdapTemplate#executeReadOnly(ContextExecutor) + * @see LdapTemplate#executeReadWrite(ContextExecutor) + * + * @author Mattias Hellborg Arthursson + */ +public interface ContextExecutor { + /** + * Perform any operation on the context. + * + * @param ctx + * the DirContext to perform the operation on. + * @return any object resulting from the operation - might be null. + * @throws NamingException + * if the operation resulted in one. + */ + T executeWithContext(DirContext ctx) throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/ContextMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextMapper.java new file mode 100644 index 000000000..908716572 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextMapper.java @@ -0,0 +1,67 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.ldap.core.support.AbstractContextMapper; +import com.fr.third.springframework.ldap.core.support.DefaultDirObjectFactory; + +import javax.naming.Binding; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.SearchResult; + +/** + * An interface used by LdapTemplate to map LDAP Contexts to beans. When a + * DirObjectFactory is set on the ContextSource, the objects returned from + * search and listBindings operations are + * automatically transformed to DirContext objects (when using the + * {@link DefaultDirObjectFactory} - which is typically the case, unless + * something else has been explicitly specified - you get a + * {@link DirContextAdapter} object). This object will then be passed to the + * ContextMapper implementation for transformation to the desired bean. + *

+ * ContextMapper implementations are typically stateless and thus reusable; they + * are ideal for implementing mapping logic in one place. + *

+ * Alternatively, consider using an {@link AttributesMapper} in stead. + * + * @see LdapTemplate#search(Name, String, ContextMapper) + * @see LdapTemplate#listBindings(Name, ContextMapper) + * @see LdapTemplate#lookup(Name, ContextMapper) + * @see AttributesMapper + * @see DefaultDirObjectFactory + * @see DirContextAdapter + * @see AbstractContextMapper + * + * @author Mattias Hellborg Arthursson + */ +public interface ContextMapper { + /** + * Map a single LDAP Context to an object. The supplied Object + * ctx is the object from a single {@link SearchResult}, + * {@link Binding}, or a lookup operation. + * + * @param ctx + * the context to map to an object. Typically this will be a + * {@link DirContextAdapter} instance, unless a project specific + * DirObjectFactory has been specified on the + * ContextSource. + * @return an object built from the data in the context. + * @throws NamingException if an error occurs. + */ + T mapFromContext(Object ctx) throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/ContextMapperCallbackHandler.java b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextMapperCallbackHandler.java new file mode 100644 index 000000000..7081ba6f5 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextMapperCallbackHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.util.Assert; + +import javax.naming.Binding; +import javax.naming.NameClassPair; +import javax.naming.NamingException; + +/** + * A CollectingNameClassPairCallbackHandler to wrap a ContextMapper. That is, + * the found object is extracted from each {@link Binding}, and then passed to + * the specified ContextMapper for translation. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + * @since 1.2 + */ +public class ContextMapperCallbackHandler extends + CollectingNameClassPairCallbackHandler { + private ContextMapper mapper; + + /** + * Constructs a new instance wrapping the supplied {@link ContextMapper}. + * + * @param mapper + * the mapper to be called for each entry. + */ + public ContextMapperCallbackHandler(ContextMapper mapper) { + Assert.notNull(mapper, "Mapper must not be empty"); + this.mapper = mapper; + } + + /** + * Cast the NameClassPair to a {@link Binding} and pass its object to + * the ContextMapper. + * + * @param nameClassPair + * a Binding instance. + * @return the Object returned from the mapper. + * @throws NamingException if an error occurs. + * @throws ObjectRetrievalException if the object of the nameClassPair is null. + */ + public T getObjectFromNameClassPair(NameClassPair nameClassPair) throws NamingException { + if (!(nameClassPair instanceof Binding)) { + throw new IllegalArgumentException("Parameter must be an instance of Binding"); + } + + Binding binding = (Binding) nameClassPair; + Object object = binding.getObject(); + if (object == null) { + throw new ObjectRetrievalException( + "Binding did not contain any object."); + } + return mapper.mapFromContext(object); + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/ContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextSource.java new file mode 100644 index 000000000..9692ae99c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/ContextSource.java @@ -0,0 +1,71 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.ldap.NamingException; + +import javax.naming.directory.DirContext; + +/** + * A ContextSource is responsible for configuring and creating + * DirContext instances. It is typically used from + * {@link LdapTemplate} to acquiring contexts for LDAP operations, but may be + * used standalone to perform LDAP authentication. + * + * @see com.fr.third.springframework.ldap.core.LdapTemplate + * + * @author Adam Skogman + * @author Mattias Hellborg Arthursson + */ +public interface ContextSource { + + /** + * Gets a read-only DirContext. The returned + * DirContext must be possible to perform read-only operations + * on. + * + * @return A DirContext instance, never null. + * @throws NamingException if some error occurs creating an DirContext. + */ + DirContext getReadOnlyContext() throws NamingException; + + /** + * Gets a read-write DirContext instance. + * + * @return A DirContext instance, never null. + * @throws NamingException if some error occurs creating an + * DirContext. + */ + DirContext getReadWriteContext() throws NamingException; + + /** + * Gets a DirContext instance authenticated using the supplied + * principal and credentials. Typically to be used for plain authentication + * purposes. Note that this method will never make use + * of native Java LDAP pooling, even though this instance is configured to do so. + * This is to force password changes in the target directory to take effect + * as soon as possible. + * + * @param principal The principal (typically a distinguished name of a user + * in the LDAP tree) to use for authentication. + * @param credentials The credentials to use for authentication. + * @return an authenticated DirContext instance, never + * null. + * @since 1.3 + */ + DirContext getContext(String principal, String credentials) throws NamingException; +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DefaultDnParserFactory.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DefaultDnParserFactory.java new file mode 100644 index 000000000..1526246c4 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DefaultDnParserFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import java.io.StringReader; + +/** + * A factory for creating DnParser instances. The actual implementation of + * DnParser is generated using javacc and should not be constructed directly. + * + * @author Mattias Hellborg Arthursson + * @deprecated {@link DistinguishedName} and associated classes are deprecated as of 2.0. + */ +public final class DefaultDnParserFactory { + /** + * Not to be instantiated. + */ + private DefaultDnParserFactory() { + + } + + /** + * Create a new DnParser instance. + * + * @param string + * the DN String to be parsed. + * @return a new DnParser instance for parsing the supplied DN string. + */ + public static DnParser createDnParser(String string) { + return new DnParserImpl(new StringReader(string)); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DefaultNameClassPairMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DefaultNameClassPairMapper.java new file mode 100644 index 000000000..9a0e32545 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DefaultNameClassPairMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import javax.naming.NameClassPair; +import javax.naming.NamingException; + +/** + * The default NameClassPairMapper implementation. This implementation simply + * takes the Name string from the supplied NameClassPair and returns it as + * result. + * + * @author Mattias Hellborg Arthursson + * + */ +public class DefaultNameClassPairMapper implements NameClassPairMapper { + + /** + * Gets the Name from the supplied NameClassPair and returns it as the + * result. + * + * @param nameClassPair + * the NameClassPair to transform. + * @return the Name string from the NameClassPair. + */ + @Override + public String mapFromNameClassPair(NameClassPair nameClassPair) + throws NamingException { + + return nameClassPair.getName(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextAdapter.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextAdapter.java new file mode 100644 index 000000000..61b79e58f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextAdapter.java @@ -0,0 +1,1465 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.NoSuchAttributeException; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.ObjectUtils; +import com.fr.third.springframework.util.StringUtils; + +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.InvalidNameException; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.LdapName; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Adapter that implements the interesting methods of the DirContext interface. + * In particular it contains utility methods for getting and setting attributes. + * Using the + * {@link com.fr.third.springframework.ldap.core.support.DefaultDirObjectFactory} in your + * ContextSource (which is the default) you will receive instances + * of this class from searches and lookups. This can be particularly useful when + * updating data, since this class implements + * {@link AttributeModificationsAware}, providing a + * {@link #getModificationItems()} method. When in update mode, an object of + * this class keeps track of the changes made to its attributes, making them + * available as an array of ModificationItem objects, suitable as + * input to {@link LdapTemplate#modifyAttributes(DirContextOperations)}. + * + *

+ * This class is aware of the specifics of {@link Name} instances with regards + * to equality when working with attribute values. This comes in very handy + * when working with e.g. security groups and modifications of them. If + * {@link Name} instances are supplied to one of the Attribute manipulation + * methods (e.g. {@link #addAttributeValue(String, Object)}, + * {@link #removeAttributeValue(String, Object)}, {@link #setAttributeValue(String, Object)}, + * or {@link #setAttributeValues(String, Object[])}), the produced modifications + * will be calculated using {@link Name} equality. This means that if an the member + * has a value of "cn=John Doe,ou=People", and we call + * addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe,OU=People"), + * this will not be considered a modification since the two DN + * strings represent the same distinguished name (case and spacing between attributes is + * disregarded). + *

+ *

+ * Note that this is not a complete implementation of DirContext. Several + * methods are not relevant for the intended usage of this class, so they + * throw UnsupportOperationException. + *

+ * + * @see #setAttributeValue(String, Object) + * @see #setAttributeValues(String, Object[]) + * @see #getStringAttribute(String) + * @see #getStringAttributes(String) + * @see #getObjectAttribute(String) + * @see #addAttributeValue(String, Object) + * @see #removeAttributeValue(String, Object) + * @see #setUpdateMode(boolean) + * @see #isUpdateMode() + * + * @author Magnus Robertsson + * @author Andreas Ronge + * @author Adam Skogman + * @author Mattias Hellborg Arthursson + */ +public class DirContextAdapter implements DirContextOperations { + + private static final boolean DONT_ADD_IF_DUPLICATE_EXISTS = false; + + private static final String EMPTY_STRING = ""; + + private static final boolean ORDER_DOESNT_MATTER = false; + private static final String NOT_IMPLEMENTED = "Not implemented."; + + private static Logger log = LoggerFactory.getLogger(DirContextAdapter.class); + + private final NameAwareAttributes originalAttrs; + + private LdapName dn; + + private LdapName base = LdapUtils.emptyLdapName(); + + private boolean updateMode = false; + + private NameAwareAttributes updatedAttrs; + + private String referralUrl; + + /** + * Default constructor. + */ + public DirContextAdapter() { + this(null, null, null); + } + + /** + * Create a new DirContextAdapter from the supplied DN String. + * @param dnString the DN string. Must be syntactically correct, or an + * exception will be thrown. + */ + public DirContextAdapter(String dnString) { + this(LdapUtils.newLdapName(dnString)); + } + + /** + * Create a new adapter from the supplied dn. + * + * @param dn the dn. + */ + public DirContextAdapter(Name dn) { + this(null, dn); + } + + /** + * Create a new adapter from the supplied attributes and dn. + * + * @param attrs the attributes. + * @param dn the dn. + */ + public DirContextAdapter(Attributes attrs, Name dn) { + this(attrs, dn, null); + } + + /** + * Create a new adapter from the supplied attributes, dn, and base. + * + * @param attrs the attributes. + * @param dn the dn. + * @param base the base name. + */ + public DirContextAdapter(Attributes attrs, Name dn, Name base) { + this(attrs, dn, base, null); + } + + /** + * Create a new adapter from the supplied attributes, dn, base, and referral + * url. + * @param attrs the attributes. + * @param dn the dn. + * @param base the base. + * @param referralUrl the referral url (if this instance results from a + * referral). + */ + public DirContextAdapter(Attributes attrs, Name dn, Name base, + String referralUrl) { + if (attrs != null) { + this.originalAttrs = new NameAwareAttributes(attrs); + } + else { + this.originalAttrs = new NameAwareAttributes(); + } + + if (dn != null) { + this.dn = LdapUtils.newLdapName(dn); + } + else { + this.dn = LdapUtils.emptyLdapName(); + } + if (base != null) { + this.base = LdapUtils.newLdapName(base); + } + else { + this.base = LdapUtils.emptyLdapName(); + } + + if (referralUrl != null) { + this.referralUrl = referralUrl; + } + else { + this.referralUrl = EMPTY_STRING; + } + } + + /** + * Constructor for cloning an existing adapter. + * + * @param master The adapter to be copied. + */ + protected DirContextAdapter(DirContextAdapter master) { + this.originalAttrs = (NameAwareAttributes) master.originalAttrs.clone(); + this.dn = master.dn; + this.updatedAttrs = (NameAwareAttributes) master.updatedAttrs.clone(); + this.updateMode = master.updateMode; + } + + /** + * Sets the update mode. The update mode should be false for a + * new entry and true for an existing entry that is being + * updated. + * + * @param mode Update mode. + */ + public void setUpdateMode(boolean mode) { + this.updateMode = mode; + if (updateMode) { + updatedAttrs = new NameAwareAttributes(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isUpdateMode() { + return updateMode; + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getNamesOfModifiedAttributes() { + + List tmpList = new ArrayList(); + + NamingEnumeration attributesEnumeration; + if (isUpdateMode()) { + attributesEnumeration = updatedAttrs.getAll(); + } + else { + attributesEnumeration = originalAttrs.getAll(); + } + + try { + while (attributesEnumeration.hasMore()) { + Attribute oneAttribute = attributesEnumeration + .next(); + tmpList.add(oneAttribute.getID()); + } + } + catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } + finally { + closeNamingEnumeration(attributesEnumeration); + } + + return tmpList.toArray(new String[tmpList.size()]); + } + + private void closeNamingEnumeration(NamingEnumeration enumeration) { + try { + if (enumeration != null) { + enumeration.close(); + } + } + catch (NamingException e) { + // Never mind this + } + } + + /** + * {@inheritDoc} + */ + @Override + public ModificationItem[] getModificationItems() { + if (!updateMode) { + return new ModificationItem[0]; + } + + List tmpList = new LinkedList(); + NamingEnumeration attributesEnumeration = null; + try { + attributesEnumeration = updatedAttrs.getAll(); + + // find attributes that have been changed, removed or added + while (attributesEnumeration.hasMore()) { + NameAwareAttribute oneAttr = (NameAwareAttribute) attributesEnumeration.next(); + + collectModifications(oneAttr, tmpList); + } + } + catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } + finally { + closeNamingEnumeration(attributesEnumeration); + } + + if (log.isDebugEnabled()) { + log.debug("Number of modifications:" + tmpList.size()); + } + + return tmpList.toArray(new ModificationItem[tmpList.size()]); + } + + /** + * Collect all modifications for the changed attribute. If no changes have + * been made, return immediately. If modifications have been made, and the + * original size as well as the updated size of the attribute is 1, replace + * the attribute. If the size of the updated attribute is 0, remove the + * attribute. Otherwise, the attribute is a multi-value attribute; if it's + * an ordered one it should be replaced in its entirety to preserve the new + * ordering, if not all modifications to the original value (removals and + * additions) will be collected individually. + * + * @param changedAttr the value of the changed attribute. + * @param modificationList the list in which to add the modifications. + * @throws NamingException if thrown by called Attribute methods. + */ + private void collectModifications(NameAwareAttribute changedAttr, + List modificationList) throws NamingException { + NameAwareAttribute currentAttribute = originalAttrs.get(changedAttr.getID()); + if(currentAttribute != null && changedAttr.hasValuesAsNames()) { + try { + currentAttribute.initValuesAsNames(); + } catch(IllegalArgumentException e) { + log.warn("Incompatible attributes; changed attribute has Name values but " + + "original cannot be converted to this"); + } + } + + if (changedAttr.equals(currentAttribute)) { + // No changes + return; + } + else if (currentAttribute != null && currentAttribute.size() == 1 + && changedAttr.size() == 1) { + // Replace single-vale attribute. + modificationList.add(new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, changedAttr)); + } + else if (changedAttr.size() == 0 && currentAttribute != null) { + // Attribute has been removed. + modificationList.add(new ModificationItem( + DirContext.REMOVE_ATTRIBUTE, changedAttr)); + } + else if ((currentAttribute == null || currentAttribute.size() == 0) + && changedAttr.size() > 0) { + // Attribute has been added. + modificationList.add(new ModificationItem(DirContext.ADD_ATTRIBUTE, + changedAttr)); + } + else if (changedAttr.size() > 0 && changedAttr.isOrdered()) { + // This is a multivalue attribute and it is ordered - the original + // value should be replaced with the new values so that the ordering + // is preserved. + modificationList.add(new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, changedAttr)); + } + else if (changedAttr.size() > 0) { + // Change of multivalue Attribute. Collect additions and removals + // individually. + List myModifications = new LinkedList(); + collectModifications(currentAttribute, changedAttr, myModifications); + + if (myModifications.isEmpty()) { + // This means that the attributes are not equal, but the + // actual values are the same - thus the order must have + // changed. This should result in a REPLACE_ATTRIBUTE operation. + myModifications.add(new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, changedAttr)); + } + + modificationList.addAll(myModifications); + } + } + + private void collectModifications(Attribute originalAttr, + Attribute changedAttr, List modificationList) + throws NamingException { + + Attribute originalClone = (Attribute) originalAttr.clone(); + Attribute addedValuesAttribute = new NameAwareAttribute(originalAttr + .getID()); + + NamingEnumeration allValues = changedAttr.getAll(); + while(allValues.hasMoreElements()) { + Object attributeValue = allValues.nextElement(); + if (!originalClone.remove(attributeValue)) { + addedValuesAttribute.add(attributeValue); + } + } + + // We have now traversed and removed all values from the original that + // were also present in the new values. The remaining values in the + // original must be the ones that were removed. + if(originalClone.size() > 0 && originalClone.size() == originalAttr.size()) { + // This is actually a complete replacement of the attribute values. + // Fall back to REPLACE + modificationList.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, + addedValuesAttribute)); + } else { + if (originalClone.size() > 0) { + modificationList.add(new ModificationItem( + DirContext.REMOVE_ATTRIBUTE, originalClone)); + } + + if (addedValuesAttribute.size() > 0) { + modificationList.add(new ModificationItem(DirContext.ADD_ATTRIBUTE, + addedValuesAttribute)); + } + } + } + + /** + * returns true if the attribute is empty. It is empty if a == null, size == + * 0 or get() == null or an exception if thrown when accessing the get + * method + */ + private boolean isEmptyAttribute(Attribute a) { + try { + return (a == null || a.size() == 0 || a.get() == null); + } + catch (NamingException e) { + return true; + } + } + + /** + * Compare the existing attribute name with the values on the + * array values. The order of the array must be the same order + * as the existing multivalued attribute. + *

+ * Also handles the case where the values have been reset to the original + * values after a previous change. For example, changing + * [a,b,c] to [a,b] and then back to + * [a,b,c] again must result in this method returning + * true so the first change can be overwritten with the latest + * change. + * + * @param name Name of the original multi-valued attribute. + * @param values Array of values to check if they have been changed. + * @return true if there has been a change compared to original attribute, + * or a previous update + */ + private boolean isChanged(String name, Object[] values, boolean orderMatters) { + + Attribute orig = originalAttrs.get(name); + Attribute prev = updatedAttrs.get(name); + + // values == null and values.length == 0 is treated the same way + boolean emptyNewValue = (values == null || values.length == 0); + + // Setting to empty --------------------- + if (emptyNewValue) { + // FALSE: if both are null, it is not changed (both don't exist) + // TRUE: if new value is null and old value exists (should be + // removed) + // TODO Also include prev in null check + // TODO Also check if there is a single null element + return orig != null; + } + + // NOT setting to empty ------------------- + + // TRUE if existing value is null + if (orig == null) { + return true; + } + + // TRUE if different length compared to original attributes + if (orig.size() != values.length) { + return true; + } + + // TRUE if different length compared to previously updated attributes + if (prev != null && prev.size() != values.length) { + return true; + } + + // Check contents of arrays + + // Order DOES matter, e.g. first names + try { + for (int i = 0; i < orig.size(); i++) { + Object obj = orig.get(i); + // TRUE if one value is not equal + if (!(obj instanceof String)) { + return true; + } + if (orderMatters) { + // check only the string with same index + if (!values[i].equals(obj)) { + return true; + } + } + else { + // check all strings + if (!ObjectUtils.containsElement(values, obj)) { + return true; + } + } + } + + } + catch (NamingException e) { + // TRUE if we can't access the value + return true; + } + + if (prev != null) { + // Also check against updatedAttrs, since there might have been + // a previous update + try { + for (int i = 0; i < prev.size(); i++) { + Object obj = prev.get(i); + // TRUE if one value is not equal + if (!(obj instanceof String)) { + return true; + } + if (orderMatters) { + // check only the string with same index + if (!values[i].equals(obj)) { + return true; + } + } + else { + // check all strings + if (!ObjectUtils.containsElement(values, obj)) { + return true; + } + } + } + + } + catch (NamingException e) { + // TRUE if we can't access the value + return true; + } + } + // FALSE since we have compared all values + return false; + } + + /** + * Checks if an entry has a specific attribute. + * + * This method simply calls exists(String) with the attribute name. + * + * @param attr the attribute to check. + * @return true if attribute exists in entry. + */ + protected final boolean exists(Attribute attr) { + return exists(attr.getID()); + } + + /** + * Checks if the attribute exists in this entry, either it was read or it + * has been added and update() has been called. + * + * @param attrId id of the attribute to check. + * @return true if the attribute exists in the entry. + */ + protected final boolean exists(String attrId) { + return originalAttrs.get(attrId) != null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getStringAttribute(String name) { + return (String) getObjectAttribute(name); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getObjectAttribute(String name) { + Attribute oneAttr = originalAttrs.get(name); + if (oneAttr == null || oneAttr.size() == 0) { // LDAP-215 + return null; + } + try { + return oneAttr.get(); + } + catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + // LDAP-215 + public boolean attributeExists(String name) { + Attribute oneAttr = originalAttrs.get(name); + return oneAttr != null; + } + + /** + * {@inheritDoc} + */ + @Override + public void setAttributeValue(String name, Object value) { + // new entry + if (!updateMode && value != null) { + originalAttrs.put(name, value); + } + + // updating entry + if (updateMode) { + Attribute attribute = new NameAwareAttribute(name); + if (value != null) { + attribute.add(value); + } + updatedAttrs.put(attribute); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addAttributeValue(String name, Object value) { + addAttributeValue(name, value, DONT_ADD_IF_DUPLICATE_EXISTS); + } + + /** + * {@inheritDoc} + */ + @Override + public void addAttributeValue(String name, Object value, + boolean addIfDuplicateExists) { + if (!updateMode && value != null) { + Attribute attr = originalAttrs.get(name); + if (attr == null) { + originalAttrs.put(name, value); + } + else { + attr.add(value); + } + } + else if (updateMode) { + Attribute attr = updatedAttrs.get(name); + if (attr == null) { + if (originalAttrs.get(name) == null) { + // No match in the original attributes - + // add a new Attribute to updatedAttrs + updatedAttrs.put(name, value); + } + else { + // The attribute exists in the original attributes - clone + // that and add the new entry to it + attr = (Attribute) originalAttrs.get(name).clone(); + if (addIfDuplicateExists || !attr.contains(value)) { + attr.add(value); + } + updatedAttrs.put(attr); + } + } + else { + attr.add(value); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeAttributeValue(String name, Object value) { + if (!updateMode && value != null) { + Attribute attr = originalAttrs.get(name); + if (attr != null) { + attr.remove(value); + if (attr.size() == 0) { + originalAttrs.remove(name); + } + } + } + else if (updateMode) { + Attribute attr = updatedAttrs.get(name); + if (attr == null) { + if (originalAttrs.get(name) != null) { + attr = (Attribute) originalAttrs.get(name).clone(); + attr.remove(value); + updatedAttrs.put(attr); + } + } + else { + attr.remove(value); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setAttributeValues(String name, Object[] values) { + setAttributeValues(name, values, ORDER_DOESNT_MATTER); + } + + /** + * {@inheritDoc} + */ + @Override + public void setAttributeValues(String name, Object[] values, + boolean orderMatters) { + Attribute a = new NameAwareAttribute(name, orderMatters); + + for (int i = 0; values != null && i < values.length; i++) { + a.add(values[i]); + } + + // only change the original attribute if not in update mode + if (!updateMode && values != null && values.length > 0) { + // don't save empty arrays + originalAttrs.put(a); + } + + // possible to set an already existing attribute to an empty array + if (updateMode && isChanged(name, values, orderMatters)) { + updatedAttrs.put(a); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void update() { + NamingEnumeration attributesEnumeration = null; + + try { + attributesEnumeration = updatedAttrs.getAll(); + + // find what to update + while (attributesEnumeration.hasMore()) { + Attribute a = attributesEnumeration.next(); + + // if it does not exist it should be added + if (isEmptyAttribute(a)) { + originalAttrs.remove(a.getID()); + } + else { + // Otherwise it should be set. + originalAttrs.put(a); + } + } + } + catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } + finally { + closeNamingEnumeration(attributesEnumeration); + } + + // Reset the attributes to be updated + updatedAttrs = new NameAwareAttributes(); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getStringAttributes(String name) { + try { + List objects = collectAttributeValuesAsList(name, String.class); + return objects.toArray(new String[objects.size()]); + } + catch (NoSuchAttributeException e) { + // The attribute does not exist - contract says to return null. + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getObjectAttributes(String name) { + try { + List list = collectAttributeValuesAsList(name, Object.class); + return list.toArray(new Object[list.size()]); + } + catch (NoSuchAttributeException e) { + // The attribute does not exist - contract says to return null. + return null; + } + } + + private List collectAttributeValuesAsList(String name, Class clazz) { + List list = new LinkedList(); + LdapUtils.collectAttributeValues(originalAttrs, name, list, clazz); + return list; + } + + /** + * {@inheritDoc} + */ + @Override + public SortedSet getAttributeSortedStringSet(String name) { + try { + TreeSet attrSet = new TreeSet(); + LdapUtils.collectAttributeValues(originalAttrs, name, attrSet, String.class); + return attrSet; + } + catch (NoSuchAttributeException e) { + // The attribute does not exist - contract says to return null. + return null; + } + } + + /** + * Set the supplied attribute. + * + * @param attribute the attribute to set. + */ + public void setAttribute(Attribute attribute) { + if (!updateMode) { + originalAttrs.put(attribute); + } + else { + updatedAttrs.put(attribute); + } + } + + /** + * Get all attributes. + * + * @return all attributes. + */ + public Attributes getAttributes() { + return originalAttrs; + } + + /** + * {@inheritDoc} + */ + @Override + public Attributes getAttributes(Name name) throws NamingException { + return getAttributes(name.toString()); + } + + /** + * {@inheritDoc} + */ + @Override + public Attributes getAttributes(String name) throws NamingException { + if (StringUtils.hasLength(name)) { + throw new NameNotFoundException(); + } + return (Attributes) originalAttrs.clone(); + } + + /** + * {@inheritDoc} + */ + @Override + public Attributes getAttributes(Name name, String[] attrIds) + throws NamingException { + return getAttributes(name.toString(), attrIds); + } + + /** + * {@inheritDoc} + */ + @Override + public Attributes getAttributes(String name, String[] attrIds) + throws NamingException { + if (StringUtils.hasLength(name)) { + throw new NameNotFoundException(); + } + + Attributes a = new NameAwareAttributes(); + Attribute target; + for (String attrId : attrIds) { + target = originalAttrs.get(attrId); + if (target != null) { + a.put(target); + } + } + + return a; + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyAttributes(Name name, int modOp, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyAttributes(String name, int modOp, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyAttributes(Name name, ModificationItem[] mods) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyAttributes(String name, ModificationItem[] mods) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void bind(Name name, Object obj, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void bind(String name, Object obj, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void rebind(Name name, Object obj, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void rebind(String name, Object obj, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public DirContext createSubcontext(Name name, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public DirContext createSubcontext(String name, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public DirContext getSchema(Name name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public DirContext getSchema(String name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public DirContext getSchemaClassDefinition(Name name) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public DirContext getSchemaClassDefinition(String name) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration search(Name name, Attributes matchingAttributes, + String[] attributesToReturn) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration search(String name, Attributes matchingAttributes, + String[] attributesToReturn) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration search(Name name, Attributes matchingAttributes) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration search(String name, Attributes matchingAttributes) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration search(Name name, String filter, + SearchControls cons) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration search(String name, String filter, + SearchControls cons) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration search(Name name, String filterExpr, + Object[] filterArgs, SearchControls cons) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration search(String name, String filterExpr, + Object[] filterArgs, SearchControls cons) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Object lookup(Name name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Object lookup(String name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void bind(Name name, Object obj) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void bind(String name, Object obj) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void rebind(Name name, Object obj) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void rebind(String name, Object obj) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void unbind(Name name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void unbind(String name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void rename(Name oldName, Name newName) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void rename(String oldName, String newName) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration list(Name name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration list(String name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration listBindings(Name name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NamingEnumeration listBindings(String name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void destroySubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void destroySubcontext(String name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Context createSubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Context createSubcontext(String name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Object lookupLink(Name name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Object lookupLink(String name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NameParser getNameParser(Name name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public NameParser getNameParser(String name) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Name composeName(Name name, Name prefix) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public String composeName(String name, String prefix) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Object addToEnvironment(String propName, Object propVal) + throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Object removeFromEnvironment(String propName) throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public Hashtable getEnvironment() throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public void close() throws NamingException { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * {@inheritDoc} + */ + @Override + public String getNameInNamespace() { + if(base.size() == 0) { + return dn.toString(); + } + + try { + LdapName result = (LdapName) dn.clone(); + result.addAll(0, base); + return result.toString(); + } catch (InvalidNameException e) { + throw new com.fr.third.springframework.ldap.InvalidNameException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Name getDn() { + return LdapUtils.newLdapName(dn); + } + + /** + * {@inheritDoc} + */ + @Override + public final void setDn(Name dn) { + if (!updateMode) { + this.dn = LdapUtils.newLdapName(dn); + } + else { + throw new IllegalStateException( + "Not possible to call setDn() on a DirContextAdapter in update mode"); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DirContextAdapter that = (DirContextAdapter) o; + + if (updateMode != that.updateMode) return false; + if (base != null ? !base.equals(that.base) : that.base != null) return false; + if (dn != null ? !dn.equals(that.dn) : that.dn != null) return false; + if (originalAttrs != null ? !originalAttrs.equals(that.originalAttrs) : that.originalAttrs != null) + return false; + if (referralUrl != null ? !referralUrl.equals(that.referralUrl) : that.referralUrl != null) return false; + if (updatedAttrs != null ? !updatedAttrs.equals(that.updatedAttrs) : that.updatedAttrs != null) return false; + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int result = originalAttrs != null ? originalAttrs.hashCode() : 0; + result = 31 * result + (dn != null ? dn.hashCode() : 0); + result = 31 * result + (base != null ? base.hashCode() : 0); + result = 31 * result + (updateMode ? 1 : 0); + result = 31 * result + (updatedAttrs != null ? updatedAttrs.hashCode() : 0); + result = 31 * result + (referralUrl != null ? referralUrl.hashCode() : 0); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getName()); + builder.append(":"); + if (dn != null) { + builder.append(" dn=").append(dn); + } + builder.append(" {"); + + try { + for (NamingEnumeration i = originalAttrs.getAll(); i.hasMore();) { + Attribute attribute = i.next(); + if (attribute.size() == 1) { + builder.append(attribute.getID()); + builder.append('='); + builder.append(attribute.get()); + } + else { + for (int j = 0; j < attribute.size(); j++) { + if (j > 0) { + builder.append(", "); + } + builder.append(attribute.getID()); + builder.append('['); + builder.append(j); + builder.append("]="); + builder.append(attribute.get(j)); + } + } + + if (i.hasMore()) { + builder.append(", "); + } + } + } + catch (NamingException e) { + log.warn("Error in toString()"); + } + builder.append('}'); + + return builder.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getReferralUrl() { + return referralUrl; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isReferral() { + return StringUtils.hasLength(referralUrl); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextOperations.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextOperations.java new file mode 100644 index 000000000..ddec421b6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextOperations.java @@ -0,0 +1,97 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.LdapDataEntry; + +import javax.naming.Name; +import javax.naming.directory.DirContext; + +/** + * Interface for DirContextAdapter. + * + * @author Mattias Hellborg Arthursson + * @see DirContextAdapter + */ +public interface DirContextOperations extends DirContext, LdapDataEntry, + AttributeModificationsAware { + + /** + * Gets the update mode. An entry in update mode will keep track of its + * modifications so that they can be retrieved using + * {@link AttributeModificationsAware#getModificationItems()}. The update + * mode should be true for a new entry and true + * for an existing entry that is being updated. + * + * @return update mode. + */ + boolean isUpdateMode(); + + /** + * Creates a String array of the names of the attributes which have been + * changed. + * + * If this is a new entry, all set entries will be in the list. If this is + * an updated entry, only changed and removed entries will be in the array. + * + * @return Array of String + */ + String[] getNamesOfModifiedAttributes(); + + /** + * Update the attributes.This will mean that the getters ( + * getStringAttribute methods) will return the updated values, + * and the modifications will be forgotten (i.e. + * {@link AttributeModificationsAware#getModificationItems()} will return an + * empty array. + */ + void update(); + + /** + * Set the dn of this entry. + * + * @param dn the dn. + */ + void setDn(Name dn); + + /* + * (non-Javadoc) + * + * @see javax.naming.Context#getNameInNamespace() + */ + String getNameInNamespace(); + + /** + * If this instance results from a referral, this method returns the url of + * the referred server. + * + * @return The url of the referred server, e.g. + * ldap://localhost:389, or the empty string if this is not a + * referral. + * @since 1.3 + */ + String getReferralUrl(); + + /** + * Checks whether this instance results from a referral. + * + * @return true if this instance results from a referral, + * false otherwise. + * @since 1.3 + */ + boolean isReferral(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextProcessor.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextProcessor.java new file mode 100644 index 000000000..f35c5b128 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextProcessor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +/** + * Interface to be called in search by {@link LdapTemplate} before and after the + * actual search and enumeration traversal. Implementations may be used to apply + * search controls on the Context and retrieve the results of + * such controls afterwards. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public interface DirContextProcessor { + /** + * Perform pre-processing on the supplied DirContext. + * + * @param ctx + * the DirContext instance. + * @throws NamingException + * if thrown by the underlying operation. + */ + void preProcess(DirContext ctx) throws NamingException; + + /** + * Perform post-processing on the supplied DirContext. + * + * @param ctx + * the DirContext instance. + * @throws NamingException + * if thrown by the underlying operation. + */ + void postProcess(DirContext ctx) throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextProxy.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextProxy.java new file mode 100644 index 000000000..9c64ad3bc --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DirContextProxy.java @@ -0,0 +1,34 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import javax.naming.directory.DirContext; + +/** + * Helper interface to be able to get hold of the target DirContext + * from proxies created by ContextSource proxies. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public interface DirContextProxy { + /** + * Get the target DirContext of the proxy. + * + * @return the target DirContext. + */ + DirContext getTargetContext(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DistinguishedName.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DistinguishedName.java new file mode 100644 index 000000000..f10d87107 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DistinguishedName.java @@ -0,0 +1,861 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.ldap.UncategorizedLdapException; +import com.fr.third.springframework.util.ObjectUtils; +import com.fr.third.springframework.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.BadLdapGrammarException; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.ldap.support.ListComparator; +import com.fr.third.springframework.util.Assert; + +import javax.naming.CompositeName; +import javax.naming.InvalidNameException; +import javax.naming.Name; +import javax.naming.ldap.Rdn; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +/** + * Default implementation of a {@link Name} corresponding to an LDAP path. A + * Distinguished Name manipulation implementation is included in JDK1.5 + * (LdapName), but not in prior releases. + * + * A DistinguishedName is particularly useful when building or + * modifying an LDAP path dynamically, as escaping will be taken care of. + * + * A path is split into several names. The {@link Name} interface specifies that + * the most significant part be in position 0. + *

+ * Example: + * + *

+ *
The path
+ *
uid=adam.skogman, ou=People, ou=EU
+ *
Name[0]
+ *
ou=EU
+ *
Name[1]
+ *
ou=People
+ *
Name[2]
+ *
uid=adam.skogman
+ *
+ *

+ * Name instances, and consequently DistinguishedName + * instances are naturally mutable, which is useful when constructing + * DistinguishedNames. Example: + * + *

+ * DistinguishedName path = new DistinguishedName("dc=jayway,dc=se");
+ * path.add("ou", "People");
+ * path.add("uid", "adam.skogman");
+ * String dn = path.toString();
+ * 
+ * + * will render uid=adam.skogman,ou=People,dc=jayway,dc=se. + *

+ * NOTE: The fact that DistinguishedName instances are mutable needs to + * be taken into careful account, as this means that they may be modified + * involuntarily. This means that whenever a DistinguishedName + * instance is kept for reference (e.g. for identification of a domain entry) or + * as a constant, you should consider getting an immutable copy of the instance + * using {@link #immutableDistinguishedName()} or + * {@link #immutableDistinguishedName(String)}. + *

+ * NB:As of version 1.3 the default toString representation of + * DistinguishedName now defaults to a compact one, without spaces between the + * respective RDNs. For backward compatibility, set the + * {@link #SPACED_DN_FORMAT_PROPERTY} ({@value #SPACED_DN_FORMAT_PROPERTY}) to + * true. + * @author Adam Skogman + * @author Mattias Hellborg Arthursson + * + * @deprecated As of 2.0 it is recommended to use {@link javax.naming.ldap.LdapName} along with + * utility methods in {@link LdapUtils} instead. + * @see javax.naming.ldap.LdapName + * @see LdapUtils#newLdapName(javax.naming.Name) + * @see LdapUtils#newLdapName(String) + * @see com.fr.third.springframework.ldap.support.LdapUtils#emptyLdapName() + */ +public class DistinguishedName implements Name { + /** + * System property that will be inspected to determine whether + * {@link #toString()} will format the DN with spaces after each comma or + * use a more compact representation, i.e.: + * uid=adam.skogman, ou=People, dc=jayway, dc=se rather than + * uid=adam.skogman,ou=People,dc=jayway,dc=se. A value other + * than null or blank will trigger the spaced format. Default is the compact + * representation. + *

+ * Valid values are: + *

    + *
  • blank or null (property not set)
  • + *
  • any non-blank value
  • + *
+ * @since 1.3 + * @see #toCompactString() + */ + public static final String SPACED_DN_FORMAT_PROPERTY = "com.fr.third.springframework.ldap.core.spacedDnFormat"; + + /** + * System property that will be inspected to determine whether creating a + * DistinguishedName will convert the keys to lowercase, convert + * the keys to uppercase, or leave the keys as they were in the + * original String, ie none. Default is to convert the keys to + * lowercase. + *

+ * Valid values are: + *

    + *
  • "lower" or blank or null (property not set)
  • + *
  • "upper"
  • + *
  • "none"
  • + *
+ * @since 1.3.1 + * @see #KEY_CASE_FOLD_LOWER + * @see #KEY_CASE_FOLD_UPPER + * @see #KEY_CASE_FOLD_NONE + */ + public static final String KEY_CASE_FOLD_PROPERTY = "com.fr.third.springframework.ldap.core.keyCaseFold"; + + public static final String KEY_CASE_FOLD_LOWER = "lower"; + + public static final String KEY_CASE_FOLD_UPPER = "upper"; + + public static final String KEY_CASE_FOLD_NONE = "none"; + + private static final String MANGLED_DOUBLE_QUOTES = "\\\\\""; + private static final String PROPER_DOUBLE_QUOTES = "\\\""; + + private static final Logger LOG = LoggerFactory.getLogger(DistinguishedName.class); + + private static final boolean COMPACT = true; + + private static final boolean NON_COMPACT = false; + + private static final long serialVersionUID = 3514344371999042586L; + + /** + * An empty, unmodifiable DistinguishedName. + */ + public static final DistinguishedName EMPTY_PATH = new DistinguishedName(Collections.EMPTY_LIST); + private static final int DEFAULT_BUFFER_SIZE = 256; + + private List names; + + /** + * Construct a new DistinguishedName with no components. + */ + public DistinguishedName() { + names = new LinkedList(); + } + + /** + * Construct a new DistinguishedName from a String. + * + * @param path a String corresponding to a (syntactically) valid LDAP path. + */ + public DistinguishedName(String path) { + if (!StringUtils.hasText(path)) { + names = new LinkedList(); + } + else { + parse(path); + } + } + + /** + * Construct a new DistinguishedName from the supplied + * List of {@link LdapRdn} objects. + * + * @param list the components that this instance will consist of. + */ + public DistinguishedName(List list) { + this.names = list; + } + + /** + * Construct a new DistinguishedName from the supplied + * {@link Name}. The parts of the supplied {@link Name} must be + * syntactically correct {@link LdapRdn}s. + * + * @param name the {@link Name} to construct a new + * DistinguishedName from. + */ + public DistinguishedName(Name name) { + Assert.notNull(name, "name cannot be null"); + if (name instanceof CompositeName) { + parse(LdapUtils.convertCompositeNameToString((CompositeName) name)); + return; + } + names = new LinkedList(); + for (int i = 0; i < name.size(); i++) { + names.add(new LdapRdn(name.get(i))); + } + } + + /** + * Parse the supplied String and make this instance represent the + * corresponding distinguished name. + * + * @param path the LDAP path to parse. + */ + protected final void parse(String path) { + DnParser parser = DefaultDnParserFactory.createDnParser(unmangleCompositeName(path)); + DistinguishedName dn; + try { + dn = parser.dn(); + } + catch (ParseException e) { + throw new BadLdapGrammarException("Failed to parse DN", e); + } + catch (Exception e) { + throw new BadLdapGrammarException("Failed to parse DN", e); + } + this.names = dn.names; + } + + /** + * If path is surrounded by quotes, strip them. JNDI considers forward slash + * ('/') special, but LDAP doesn't. {@link CompositeName#toString()} tends + * to mangle a {@link Name} with a slash by surrounding it with quotes + * ('"'). + * + * @param path Path to check and possibly strip. + * @return A String with the possibly stripped path. + */ + private String unmangleCompositeName(String path) { + String tempPath; + // Check if CompositeName has mangled the name with quotes + if (path.startsWith("\"") && path.endsWith("\"")) { + tempPath = path.substring(1, path.length() - 1); + } + else { + tempPath = path; + } + + tempPath = StringUtils.replace(tempPath, MANGLED_DOUBLE_QUOTES, PROPER_DOUBLE_QUOTES); + return tempPath; + } + + /** + * Get the {@link LdapRdn} at a specified position. + * + * @param index the {@link LdapRdn} to retrieve. + * @return the {@link LdapRdn} at the requested position. + */ + public LdapRdn getLdapRdn(int index) { + return (LdapRdn) names.get(index); + } + + /** + * Get the {@link LdapRdn} with the specified key. If there are several + * {@link Rdn}s with the same key, the first one found (in order of + * significance) will be returned. + * + * @param key Attribute name of the {@link LdapRdn} to retrieve. + * @return the {@link LdapRdn} with the requested key. + * @throws IllegalArgumentException if no Rdn matches the given key. + */ + public LdapRdn getLdapRdn(String key) { + for (Iterator iter = names.iterator(); iter.hasNext();) { + LdapRdn rdn = (LdapRdn) iter.next(); + if (ObjectUtils.nullSafeEquals(rdn.getKey(), key)) { + return rdn; + } + } + + throw new IllegalArgumentException("No Rdn with the requested key: '" + key + "'"); + } + + /** + * Get the value of the {@link LdapRdnComponent} with the specified key + * (Attribute value). If there are several Rdns with the same key, the value + * of the first one found (in order of significance) will be returned. + * + * @param key Attribute name of the {@link LdapRdn} to retrieve. + * @return the value. + * @throws IllegalArgumentException if no Rdn matches the given key. + */ + public String getValue(String key) { + return getLdapRdn(key).getValue(); + } + + /** + * Get the name List. + * + * @return the list of {@link LdapRdn}s that this + * DistinguishedName consists of. + */ + public List getNames() { + return names; + } + + /** + * Get the String representation of this DistinguishedName. + * Depending on the setting of property + * com.fr.third.springframework.ldap.core.spacedDnFormat a space will be + * added after each comma, to make the result more readable. Default is + * compact representation, i.e. without any spaces. + * + * @return a syntactically correct, properly escaped String representation + * of the DistinguishedName. + * @see #SPACED_DN_FORMAT_PROPERTY + */ + public String toString() { + String spacedFormatting = System.getProperty(SPACED_DN_FORMAT_PROPERTY); + if (!StringUtils.hasText(spacedFormatting)) { + return format(COMPACT); + } + else { + return format(NON_COMPACT); + } + } + + /** + * Get the compact String representation of this + * DistinguishedName. Add no space after each comma, to make it + * compact. + * + * @return a syntactically correct, properly escaped String representation + * of the DistinguishedName. + */ + public String toCompactString() { + return format(COMPACT); + } + + /** + * Builds a complete LDAP path, ldap encoded, useful as a DN. + * + * Always uses lowercase, always separates with ", " i.e. comma and a space. + * + * @return the LDAP path. + */ + public String encode() { + return format(NON_COMPACT); + } + + private String format(boolean compact) { + // empty path + if (names.size() == 0) { + return ""; + } + + StringBuffer buffer = new StringBuffer(DEFAULT_BUFFER_SIZE); + + ListIterator i = names.listIterator(names.size()); + while (i.hasPrevious()) { + LdapRdn rdn = (LdapRdn) i.previous(); + buffer.append(rdn.getLdapEncoded()); + + // add comma, except in last iteration + if (i.hasPrevious()) { + if (compact) { + buffer.append(","); + } + else { + buffer.append(", "); + + } + } + } + + return buffer.toString(); + } + + /** + * Builds a complete LDAP path, ldap and url encoded. Separates only with + * ",". + * + * @return the LDAP path, for use in an url. + */ + public String toUrl() { + StringBuffer buffer = new StringBuffer(DEFAULT_BUFFER_SIZE); + + for (int i = names.size() - 1; i >= 0; i--) { + LdapRdn n = (LdapRdn) names.get(i); + buffer.append(n.encodeUrl()); + if (i > 0) { + buffer.append(","); + } + } + return buffer.toString(); + } + + /** + * Determines if this DistinguishedName path contains another + * path. + * + * @param path the path to check. + * @return true if the supplied path is conained in this + * instance, false otherwise. + */ + public boolean contains(DistinguishedName path) { + + List shortlist = path.getNames(); + + // this path must be at least as long + if (getNames().size() < shortlist.size()) { + return false; + } + + // must have names + if (shortlist.size() == 0) { + return false; + } + + Iterator longiter = getNames().iterator(); + Iterator shortiter = shortlist.iterator(); + + LdapRdn longname = (LdapRdn) longiter.next(); + LdapRdn shortname = (LdapRdn) shortiter.next(); + + // find first match + while (!longname.equals(shortname) && longiter.hasNext()) { + longname = (LdapRdn) longiter.next(); + } + + // Done? + if (!shortiter.hasNext() && longname.equals(shortname)) { + return true; + } + if (!longiter.hasNext()) { + return false; + } + + // compare + while (longname.equals(shortname) && longiter.hasNext() && shortiter.hasNext()) { + longname = (LdapRdn) longiter.next(); + shortname = (LdapRdn) shortiter.next(); + } + + // Done + return !shortiter.hasNext() && longname.equals(shortname); + + } + + /** + * Add an LDAP path last in this DistinguishedName. E.g.: + * + *
+	 * DistinguishedName name1 = new DistinguishedName("c=SE, dc=jayway, dc=se");
+	 * DistinguishedName name2 = new DistinguishedName("ou=people");
+	 * name1.append(name2);
+	 * 
+ * + * will result in ou=people, c=SE, dc=jayway, dc=se + * + * @param path the path to append. + * @return this instance. + */ + public DistinguishedName append(DistinguishedName path) { + getNames().addAll(path.getNames()); + return this; + } + + /** + * Append a new {@link LdapRdn} using the supplied key and value. + * + * @param key the key of the {@link LdapRdn}. + * @param value the value of the {@link LdapRdn}. + * @return this instance. + */ + public DistinguishedName append(String key, String value) { + add(key, value); + return this; + } + + /** + * Add an LDAP path first in this DistinguishedName. E.g.: + * + *
+	 * DistinguishedName name1 = new DistinguishedName("ou=people");
+	 * DistinguishedName name2 = new DistinguishedName("c=SE, dc=jayway, dc=se");
+	 * name1.prepend(name2);
+	 * 
+ * + * will result in ou=people, c=SE, dc=jayway, dc=se + * + * @param path the path to prepend. + */ + public void prepend(DistinguishedName path) { + ListIterator i = path.getNames().listIterator(path.getNames().size()); + while (i.hasPrevious()) { + names.add(0, i.previous()); + } + } + + /** + * Remove the first part of this DistinguishedName. + * + * @return the removed entry. + */ + public LdapRdn removeFirst() { + return (LdapRdn) names.remove(0); + } + + /** + * Remove the supplied path from the beginning of this + * DistinguishedName if this instance starts with + * path. Useful for stripping base path suffix from a + * DistinguishedName. + * + * @param path the path to remove from the beginning of this instance. + */ + public void removeFirst(Name path) { + if (path != null && this.startsWith(path)) { + for (int i = 0; i < path.size(); i++) { + this.removeFirst(); + } + } + } + + /** + * @see java.lang.Object#clone() + */ + public Object clone() { + try { + DistinguishedName result = (DistinguishedName) super.clone(); + result.names = new LinkedList(names); + return result; + } + catch (CloneNotSupportedException e) { + LOG.error("CloneNotSupported thrown from superclass - this should not happen"); + throw new UncategorizedLdapException("Fatal error in clone", e); + } + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + // A subclass with identical values should NOT be considered equal. + // EqualsBuilder in commons-lang cannot handle subclasses correctly. + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + + DistinguishedName name = (DistinguishedName) obj; + + // compare the lists + return getNames().equals(name.getNames()); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return this.getClass().hashCode() ^ getNames().hashCode(); + } + + /** + * Compare this instance to another object. Note that the comparison is done + * in order of significance, so the most significant Rdn is compared first, + * then the second and so on. + * + * @see javax.naming.Name#compareTo(java.lang.Object) + */ + public int compareTo(Object obj) { + DistinguishedName that = (DistinguishedName) obj; + ListComparator comparator = new ListComparator(); + return comparator.compare(this.names, that.names); + } + + public int size() { + return names.size(); + } + + public boolean isEmpty() { + return names.size() == 0; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#getAll() + */ + public Enumeration getAll() { + LinkedList strings = new LinkedList(); + for (Iterator iter = names.iterator(); iter.hasNext();) { + LdapRdn rdn = (LdapRdn) iter.next(); + strings.add(rdn.getLdapEncoded()); + } + + return Collections.enumeration(strings); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#get(int) + */ + public String get(int index) { + LdapRdn rdn = (LdapRdn) names.get(index); + return rdn.getLdapEncoded(); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#getPrefix(int) + */ + public Name getPrefix(int index) { + LinkedList newNames = new LinkedList(); + for (int i = 0; i < index; i++) { + newNames.add(names.get(i)); + } + + return new DistinguishedName(newNames); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#getSuffix(int) + */ + public Name getSuffix(int index) { + if (index > names.size()) { + throw new ArrayIndexOutOfBoundsException(); + } + + LinkedList newNames = new LinkedList(); + for (int i = index; i < names.size(); i++) { + newNames.add(names.get(i)); + } + + return new DistinguishedName(newNames); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#startsWith(javax.naming.Name) + */ + public boolean startsWith(Name name) { + if (name.size() == 0) { + return false; + } + + DistinguishedName start = null; + if (name instanceof DistinguishedName) { + start = (DistinguishedName) name; + } + else { + return false; + } + + if (start.size() > this.size()) { + return false; + } + + Iterator longiter = names.iterator(); + Iterator shortiter = start.getNames().iterator(); + + while (shortiter.hasNext()) { + Object longname = longiter.next(); + Object shortname = shortiter.next(); + + if (!longname.equals(shortname)) { + return false; + } + } + + // All names in shortiter matched. + return true; + } + + /** + * Determines if this DistinguishedName ends with a certian + * path. + * + * If the argument path is empty (no names in path) this method will return + * false. + * + * @param name The suffix to check for. + * + */ + public boolean endsWith(Name name) { + DistinguishedName path = null; + if (name instanceof DistinguishedName) { + path = (DistinguishedName) name; + } + else { + return false; + } + + List shortlist = path.getNames(); + + // this path must be at least as long + if (getNames().size() < shortlist.size()) { + return false; + } + + // must have names + if (shortlist.size() == 0) { + return false; + } + + ListIterator longiter = getNames().listIterator(getNames().size()); + ListIterator shortiter = shortlist.listIterator(shortlist.size()); + + while (shortiter.hasPrevious()) { + LdapRdn longname = (LdapRdn) longiter.previous(); + LdapRdn shortname = (LdapRdn) shortiter.previous(); + + if (!longname.equals(shortname)) + return false; + } + + // if short list ended, all were equal + return true; + + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#addAll(javax.naming.Name) + */ + public Name addAll(Name name) throws InvalidNameException { + return addAll(names.size(), name); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#addAll(int, javax.naming.Name) + */ + public Name addAll(int arg0, Name name) throws InvalidNameException { + DistinguishedName distinguishedName = null; + try { + distinguishedName = (DistinguishedName) name; + } + catch (ClassCastException e) { + throw new InvalidNameException("Invalid name type"); + } + + names.addAll(arg0, distinguishedName.getNames()); + return this; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#add(java.lang.String) + */ + public Name add(String string) throws InvalidNameException { + return add(names.size(), string); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#add(int, java.lang.String) + */ + public Name add(int index, String string) throws InvalidNameException { + try { + names.add(index, new LdapRdn(string)); + } + catch (BadLdapGrammarException e) { + throw new InvalidNameException("Failed to parse rdn '" + string + "'"); + } + return this; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#remove(int) + */ + public Object remove(int arg0) throws InvalidNameException { + LdapRdn rdn = (LdapRdn) names.remove(arg0); + return rdn.getLdapEncoded(); + } + + /** + * Remove the last part of this DistinguishedName. + * + * @return the removed {@link LdapRdn}. + */ + public LdapRdn removeLast() { + return (LdapRdn) names.remove(names.size() - 1); + } + + /** + * Add a new {@link LdapRdn} using the supplied key and value. + * + * @param key the key of the {@link LdapRdn}. + * @param value the value of the {@link LdapRdn}. + */ + public void add(String key, String value) { + names.add(new LdapRdn(key, value)); + } + + /** + * Add the supplied {@link LdapRdn} last in the list of Rdns. + * + * @param rdn the {@link LdapRdn} to add. + */ + public void add(LdapRdn rdn) { + names.add(rdn); + } + + /** + * Add the supplied {@link LdapRdn} att the specified index. + * + * @param idx the index at which to add the LdapRdn. + * @param rdn the LdapRdn to add. + */ + public void add(int idx, LdapRdn rdn) { + names.add(idx, rdn); + } + + /** + * Return an immutable copy of this instance. It will not be possible to add + * or remove any Rdns to or from the returned instance, and the respective + * Rdns will also be immutable in turn. + * + * @return a copy of this instance backed by an immutable list. + * @since 1.2 + */ + public DistinguishedName immutableDistinguishedName() { + List listWithImmutableRdns = new ArrayList(names.size()); + for (Iterator iterator = names.iterator(); iterator.hasNext();) { + LdapRdn rdn = (LdapRdn) iterator.next(); + listWithImmutableRdns.add(rdn.immutableLdapRdn()); + } + + return new DistinguishedName(Collections.unmodifiableList(listWithImmutableRdns)); + } + + /** + * Create an immutable DistinguishedName instance, suitable as a constant. + * + * @param dnString the DN string to parse. + * @return an immutable DistinguishedName corresponding to the supplied DN + * string. + * @since 1.3 + */ + public static final DistinguishedName immutableDistinguishedName(String dnString) { + return new DistinguishedName(dnString).immutableDistinguishedName(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DistinguishedNameEditor.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DistinguishedNameEditor.java new file mode 100644 index 000000000..7dafd0040 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DistinguishedNameEditor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import java.beans.PropertyEditorSupport; + +/** + * Property editor for use with {@link DistinguishedName} instances. The + * {@link #setAsText(String)} method sets the value as an immutable + * instance of a DistinguishedName. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + * @deprecated {@link DistinguishedName} and associated classes are deprecated as of 2.0. + */ +public class DistinguishedNameEditor extends PropertyEditorSupport { + + /* + * (non-Javadoc) + * @see java.beans.PropertyEditorSupport#setAsText(java.lang.String) + */ + public void setAsText(String text) throws IllegalArgumentException { + if (text == null) { + setValue(null); + } + else { + setValue(new DistinguishedName(text).immutableDistinguishedName()); + } + } + + /* + * (non-Javadoc) + * @see java.beans.PropertyEditorSupport#getAsText() + */ + public String getAsText() { + Object theValue = getValue(); + if (theValue == null) { + return null; + } + else { + return ((DistinguishedName) theValue).toString(); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DnParser.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DnParser.java new file mode 100644 index 000000000..d1d26b356 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DnParser.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +/** + * A parser for RFC2253-compliant Distinguished Names. + * + * @author Mattias Hellborg Arthursson + * @deprecated {@link DistinguishedName} and associated classes are deprecated as of 2.0. + */ +public interface DnParser { + /** + * Parse a full Distinguished Name. + * + * @return the DistinguishedName corresponding to the parsed + * stream. + */ + public DistinguishedName dn() throws ParseException; + + /** + * Parse a Relative Distinguished Name. + * + * @return the next rdn on the stream. + */ + public LdapRdn rdn() throws ParseException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/DnParserImpl.java b/fine-spring/src/com/fr/third/springframework/ldap/core/DnParserImpl.java new file mode 100644 index 000000000..668944549 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/DnParserImpl.java @@ -0,0 +1,26 @@ +package com.fr.third.springframework.ldap.core; + +import java.io.StringReader; + +/** + * @author andrew_asa + * @date 2019-07-31. + */ +public class DnParserImpl implements DnParser { + + public DnParserImpl(StringReader reader) { + + } + + @Override + public DistinguishedName dn() throws ParseException { + + return null; + } + + @Override + public LdapRdn rdn() throws ParseException { + + return null; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/IncrementalAttributesMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/core/IncrementalAttributesMapper.java new file mode 100644 index 000000000..d18ed309b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/IncrementalAttributesMapper.java @@ -0,0 +1,77 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import java.util.List; + +/** + * Utility that helps with reading all attribute values from Active Directory using Incremental Retrieval of + * Multi-valued Properties. + * + * @author Mattias Hellborg Arthursson + * @since 1.3.2 + * @see Incremental Retrieval of Multi-valued Properties + * @see com.fr.third.springframework.ldap.core.support.DefaultIncrementalAttributesMapper + */ +public interface IncrementalAttributesMapper extends AttributesMapper { + /** + * Get all of the collected values for the specified attribute. + * + * @param attributeName the attribute to get values for. + * @return the collected values for the specified attribute. Will be null + * if the requested attribute has not been returned by the server (attribute did not exist). + */ + List getValues(String attributeName); + + /** + * Get all collected values for all managed attributes as an Attributes instance. + * + * @return an Attributes instance populated with all collected values. + */ + Attributes getCollectedAttributes(); + + /** + * Check whether another query iteration is required to get all values for all attributes. + * + * @return true if there are more values for at least one of the managed attributes, + * false otherwise. + */ + boolean hasMore(); + + /** + * Get properly formatted attributes for use in the next query. The attribute names included will + * include Range specifiers as needed and only the attributes that have not been retrieved in full + * will be included. + * + * @return an array of Strings to be used as input to e.g. + * {@link com.fr.third.springframework.ldap.core.LdapTemplate#lookup(javax.naming.Name, String[], com.fr.third.springframework.ldap.core.AttributesMapper)} + * in the next iteration. + */ + String[] getAttributesForLookup(); + + /** + * Goes through all of the attributes to record their values and figure out whether a new query iteration + * is needed to get more values. + * + * @param attributes attributes from a SearchResult. + * @return this instance. + * @throws javax.naming.NamingException + */ + T mapFromAttributes(Attributes attributes) throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/IterableNamingEnumeration.java b/fine-spring/src/com/fr/third/springframework/ldap/core/IterableNamingEnumeration.java new file mode 100644 index 000000000..edf6a6973 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/IterableNamingEnumeration.java @@ -0,0 +1,40 @@ +package com.fr.third.springframework.ldap.core; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import java.util.Iterator; + +/** +* @author Mattias Hellborg Arthursson +*/ +final class IterableNamingEnumeration implements NamingEnumeration { + private final Iterator iterator; + + IterableNamingEnumeration(Iterable iterable) { + this.iterator = iterable.iterator(); + } + + @Override + public T next() { + return iterator.next(); + } + + @Override + public boolean hasMore() { + return iterator.hasNext(); + } + + @Override + public void close() throws NamingException { + } + + @Override + public boolean hasMoreElements() { + return hasMore(); + } + + @Override + public T nextElement() { + return next(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/LdapEntryIdentification.java b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapEntryIdentification.java new file mode 100644 index 000000000..4067f9a51 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapEntryIdentification.java @@ -0,0 +1,130 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.Assert; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapName; + +/** + * Wrapper class to handle the full identification of an LDAP entry. An LDAP + * entry is identified by its Distinguished Name, in Spring LDAP represented by + * the {@link DistinguishedName} class. A Distinguished Name can be absolute - + * i.e. complete including the very root (base) of the LDAP tree - or relative - + * i.e relative to the base LDAP path of the current LDAP connection (specified + * as base to the {@link ContextSource}). + *

+ * The different representations are needed on different occasions, e.g. the + * relative DN is typically what is needed to perform lookups and searches in + * the LDAP tree, whereas the absolute DN is needed when authenticating and when + * an LDAP entry is referred to in e.g. a group. This wrapper class contains + * both of these representations. + * + * @author Mattias Hellborg Arthursson + */ +public class LdapEntryIdentification { + private final LdapName relativeDn; + + private final LdapName absoluteDn; + + /** + * Construct an LdapEntryIdentification instance. + * @param absoluteDn the absolute DN of the identified entry, e.g. as + * returned by {@link DirContext#getNameInNamespace()}. + * @param relativeDn the DN of the identified entry relative to the base + * LDAP path, e.g. as returned by {@link DirContextOperations#getDn()}. + * @deprecated {@link DistinguishedName} and associated classes and methods are deprecated as of 2.0. + * use {@link #LdapEntryIdentification(javax.naming.ldap.LdapName, javax.naming.ldap.LdapName)} instead. + */ + public LdapEntryIdentification(DistinguishedName absoluteDn, DistinguishedName relativeDn) { + Assert.notNull(absoluteDn, "Absolute DN must not be null"); + Assert.notNull(relativeDn, "Relative DN must not be null"); + this.absoluteDn = LdapUtils.newLdapName(absoluteDn); + this.relativeDn = LdapUtils.newLdapName(relativeDn); + } + + /** + * Construct an LdapEntryIdentification instance. + * @param absoluteDn the absolute DN of the identified entry, e.g. as + * returned by {@link DirContext#getNameInNamespace()}. + * @param relativeDn the DN of the identified entry relative to the base + * LDAP path, e.g. as returned by {@link DirContextOperations#getDn()}. + * @since 2.0 + */ + public LdapEntryIdentification(LdapName absoluteDn, LdapName relativeDn) { + Assert.notNull(absoluteDn, "Absolute DN must not be null"); + Assert.notNull(relativeDn, "Relative DN must not be null"); + this.absoluteDn = LdapUtils.newLdapName(absoluteDn); + this.relativeDn = LdapUtils.newLdapName(relativeDn); + } + + /** + * Get the DN of the identified entry relative to the base LDAP path, e.g. + * as returned by {@link DirContextOperations#getDn()}. + * @return the relative DN. + * @since 2.0 + */ + public LdapName getAbsoluteName() { + return LdapUtils.newLdapName(absoluteDn); + } + + /** + * Get the absolute DN of the identified entry, e.g. as returned by + * {@link DirContext#getNameInNamespace()}. + * @return the absolute DN. + * @since 2.0 + */ + public LdapName getRelativeName() { + return LdapUtils.newLdapName(relativeDn); + } + + /** + * Get the DN of the identified entry relative to the base LDAP path, e.g. + * as returned by {@link DirContextOperations#getDn()}. + * @return the relative DN. + * @deprecated {@link DistinguishedName} and associated classes and methods are deprecated as of 2.0. + * use {@link #getRelativeName()} instead. + */ + public DistinguishedName getRelativeDn() { + return new DistinguishedName(relativeDn); + } + + /** + * Get the absolute DN of the identified entry, e.g. as returned by + * {@link DirContext#getNameInNamespace()}. + * @return the absolute DN. + * @deprecated {@link DistinguishedName} and associated classes and methods are deprecated as of 2.0. + * use {@link #getAbsoluteName()} instead. + */ + public DistinguishedName getAbsoluteDn() { + return new DistinguishedName(absoluteDn); + } + + public boolean equals(Object obj) { + if (obj != null && obj.getClass().equals(this.getClass())) { + LdapEntryIdentification that = (LdapEntryIdentification) obj; + return this.absoluteDn.equals(that.absoluteDn) && this.relativeDn.equals(that.relativeDn); + } + + return false; + } + + public int hashCode() { + return absoluteDn.hashCode() ^ relativeDn.hashCode(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/LdapEntryIdentificationContextMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapEntryIdentificationContextMapper.java new file mode 100644 index 000000000..ffb227a86 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapEntryIdentificationContextMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.ldap.support.LdapUtils; + +/** + * ContextMapper implementation that maps the found entries to the + * {@link LdapEntryIdentification} of each respective entry. + * + * @author Mattias Hellborg Arthursson + * @since 1.3 + */ +public class LdapEntryIdentificationContextMapper implements ContextMapper { + + public LdapEntryIdentification mapFromContext(Object ctx) { + DirContextOperations adapter = (DirContextOperations) ctx; + return new LdapEntryIdentification( + LdapUtils.newLdapName(adapter.getNameInNamespace()), + LdapUtils.newLdapName(adapter.getDn())); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/LdapOperations.java b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapOperations.java new file mode 100644 index 000000000..07980376a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapOperations.java @@ -0,0 +1,1854 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.dao.IncorrectResultSizeDataAccessException; +import com.fr.third.springframework.ldap.ContextNotEmptyException; +import com.fr.third.springframework.ldap.NamingException; +import com.fr.third.springframework.ldap.core.support.AbstractContextSource; +import com.fr.third.springframework.ldap.filter.Filter; +import com.fr.third.springframework.ldap.odm.core.ObjectDirectoryMapper; +import com.fr.third.springframework.ldap.query.LdapQuery; +import com.fr.third.springframework.ldap.support.LdapUtils; + +import javax.naming.Binding; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.directory.Attributes; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import java.util.List; + +/** + * Interface that specifies a basic set of LDAP operations. Implemented by + * LdapTemplate, but it might be a useful option to use this interface in order + * to enhance testability. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public interface LdapOperations { + /** + * Perform a search using a particular {@link SearchExecutor} and context + * processor. Use this method only if especially needed - for the most cases + * there is an overloaded convenience method which calls this one with + * suitable argments. This method handles all the plumbing; getting a + * readonly context; looping through the NamingEnumeration and + * closing the context and enumeration. The actual search is delegated to + * the SearchExecutor and each found NameClassPair is passed to + * the CallbackHandler. Any encountered + * NamingException will be translated using + * {@link LdapUtils#convertLdapException(javax.naming.NamingException)}. + * + * @param se The SearchExecutor to use for performing the + * actual search. + * @param handler The NameClassPairCallbackHandler to which + * each found entry will be passed. + * @param processor DirContextProcessor for custom pre- and + * post-processing. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted as no entries being found. + */ + void search(SearchExecutor se, NameClassPairCallbackHandler handler, DirContextProcessor processor) + throws NamingException; + + /** + * Perform a search using a particular {@link SearchExecutor}. Use this + * method only if especially needed - for the most cases there is an + * overloaded convenience method which calls this one with suitable + * argments. This method handles all the plumbing; getting a readonly + * context; looping through the NamingEnumeration and closing + * the context and enumeration. The actual search is delegated to the + * SearchExecutor and each found NameClassPair is + * passed to the CallbackHandler. Any encountered + * NamingException will be translated using the + * {@link LdapUtils#convertLdapException(javax.naming.NamingException)}. + * + * @param se The SearchExecutor to use for performing the + * actual search. + * @param handler The NameClassPairCallbackHandler to which + * each found entry will be passed. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted as no entries being found. + * @see #search(Name, String, AttributesMapper) + * @see #search(Name, String, ContextMapper) + */ + void search(SearchExecutor se, NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Perform an operation (or series of operations) on a read-only context. + * This method handles the plumbing - getting a DirContext, + * translating any Exceptions and closing the context afterwards. This + * method is not intended for searches; use + * {@link #search(SearchExecutor, NameClassPairCallbackHandler)} or any of + * the overloaded search methods for this. + * + * @param ce The ContextExecutor to which the actual operation + * on the DirContext will be delegated. + * @return the result from the ContextExecutor's operation. + * @throws NamingException if the operation resulted in a + * NamingException. + * + * @see #search(SearchExecutor, NameClassPairCallbackHandler) + * @see #search(Name, String, AttributesMapper) + * @see #search(Name, String, ContextMapper) + */ + T executeReadOnly(ContextExecutor ce) throws NamingException; + + /** + * Perform an operation (or series of operations) on a read-write context. + * This method handles the plumbing - getting a DirContext, + * translating any exceptions and closing the context afterwards. This + * method is intended only for very particular cases, where there is no + * suitable method in this interface to use. + * + * @param ce The ContextExecutor to which the actual operation + * on the DirContext will be delegated. + * @return the result from the ContextExecutor's operation. + * @throws NamingException if the operation resulted in a + * NamingException. + * @see #bind(Name, Object, Attributes) + * @see #unbind(Name) + * @see #rebind(Name, Object, Attributes) + * @see #rename(Name, Name) + * @see #modifyAttributes(Name, ModificationItem[]) + */ + T executeReadWrite(ContextExecutor ce) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. The SearchScope + * specified in the supplied SearchControls will be used in the + * search. Note that if you are using a ContextMapper, the + * returningObjFlag needs to be set to true in the + * SearchControls. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. + * @param handler The NameClassPairCallbackHandler to supply + * the SearchResult to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void search(Name base, String filter, SearchControls controls, NameClassPairCallbackHandler handler) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. See + * {@link #search(Name, String, SearchControls, NameClassPairCallbackHandler)} + * for details. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. + * @param handler The NameClassPairCallbackHandler to supply + * the SearchResult to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void search(String base, String filter, SearchControls controls, NameClassPairCallbackHandler handler) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. The SearchScope + * specified in the supplied SearchControls will be used in the + * search. Note that if you are using a ContextMapper, the + * returningObjFlag needs to be set to true in the + * SearchControls. The given DirContextProcessor + * will be called before and after the search. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. + * @param handler The NameClassPairCallbackHandler to supply + * the SearchResult to. + * @param processor The DirContextProcessor to use before and + * after the search. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void search(Name base, String filter, SearchControls controls, NameClassPairCallbackHandler handler, + DirContextProcessor processor) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. The SearchScope specified in + * the supplied SearchControls will be used in the search. The + * given DirContextProcessor will be called before and after + * the search. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. + * @param mapper The AttributesMapper to use for translating + * each entry. + * @param processor The DirContextProcessor to use before and + * after the search. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, SearchControls controls, AttributesMapper mapper, + DirContextProcessor processor) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. The SearchScope specified in + * the supplied SearchControls will be used in the search. The + * given DirContextProcessor will be called before and after + * the search. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. + * @param mapper The AttributesMapper to use for translating + * each entry. + * @param processor The DirContextProcessor to use before and + * after the search. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, SearchControls controls, AttributesMapper mapper, + DirContextProcessor processor) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Object returned + * in each SearchResult is supplied to the specified + * ContextMapper. The SearchScope specified in the + * supplied SearchControls will be used in the search. The + * given DirContextProcessor will be called before and after + * the search. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. If + * the returnObjFlag is not set in the SearchControls, this + * method will set it automatically, as this is required for the + * ContextMapper to work. + * @param mapper The ContextMapper to use for translating each + * entry. + * @param processor The DirContextProcessor to use before and + * after the search. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, SearchControls controls, ContextMapper mapper, DirContextProcessor processor) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Object returned + * in each SearchResult is supplied to the specified + * ContextMapper. The SearchScope specified in the + * supplied SearchControls will be used in the search. The + * given DirContextProcessor will be called before and after + * the search. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. If + * the returnObjFlag is not set in the SearchControls, this + * method will set it automatically, as this is required for the + * ContextMapper to work. + * @param mapper The ContextMapper to use for translating each + * entry. + * @param processor The DirContextProcessor to use before and + * after the search. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, SearchControls controls, ContextMapper mapper, DirContextProcessor processor) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. See + * {@link #search(Name, String, SearchControls, NameClassPairCallbackHandler, DirContextProcessor)} + * for details. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. + * @param handler The NameClassPairCallbackHandler to supply + * the SearchResults to. + * @param processor The DirContextProcessor to use before and + * after the search. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void search(String base, String filter, SearchControls controls, NameClassPairCallbackHandler handler, + DirContextProcessor processor) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. Use the specified values for + * search scope and return objects flag. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param returningObjFlag Whether the bound object should be returned in + * search results. Must be set to true if a + * ContextMapper is used. + * @param handler The NameClassPairCallbackHandler to supply + * the SearchResults to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void search(Name base, String filter, int searchScope, boolean returningObjFlag, + NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. Use the specified values for + * search scope and return objects flag. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param returningObjFlag Whether the bound object should be returned in + * search results. Must be set to true if a + * ContextMapper is used. + * @param handler The NameClassPairCallbackHandler to supply + * the SearchResults to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void search(String base, String filter, int searchScope, boolean returningObjFlag, + NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. The default Search scope ( + * SearchControls.SUBTREE_SCOPE) will be used and the + * returnObjects flag will be set to false. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param handler The NameClassPairCallbackHandler to supply + * the SearchResults to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void search(Name base, String filter, NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. The default Search scope ( + * SearchControls.SUBTREE_SCOPE) will be used and the + * returnObjects flag will be set to false. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param handler The NameClassPairCallbackHandler to supply + * the SearchResults to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void search(String base, String filter, NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Only return any + * attributes mathing the specified attribute names. The Attributes in each + * SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param attrs The attributes to return, null means returning + * all attributes. + * @param mapper The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, int searchScope, String[] attrs, AttributesMapper mapper) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. Only return any + * attributes mathing the specified attribute names. The Attributes in each + * SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param attrs The attributes to return, null means returning + * all attributes. + * @param mapper The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, int searchScope, String[] attrs, AttributesMapper mapper) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param mapper The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, int searchScope, AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param mapper The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, int searchScope, AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. The default search scope will be used. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param mapper The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. The default search scope will be used. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param mapper The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. Only return the + * supplied attributes. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param attrs The attributes to return, null means all + * attributes. + * @param mapper The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, int searchScope, String[] attrs, ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. Only return the + * supplied attributes. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param attrs The attributes to return, null means all + * attributes. + * @param mapper The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, int searchScope, String[] attrs, ContextMapper mapper) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param mapper The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, int searchScope, ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param searchScope The search scope to set in SearchControls + * . + * @param mapper The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, int searchScope, ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. The default search + * scope (SearchControls.SUBTREE_SCOPE) will be used. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param mapper The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. The default search + * scope (SearchControls.SUBTREE_SCOPE) will be used. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param mapper The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. The default search + * scope (SearchControls.SUBTREE_SCOPE) will be used. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param mapper The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, SearchControls controls, ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Object returned + * in each SearchResult is supplied to the specified + * ContextMapper. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. If + * the returnObjFlag is not set in the SearchControls, this + * method will set it automatically, as this is required for the + * ContextMapper to work. + * @param mapper The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, SearchControls controls, ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes + * returned in each SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. + * @param mapper The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(String base, String filter, SearchControls controls, AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes + * returned in each SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base The base DN where the search should begin. + * @param filter The filter to use in the search. + * @param controls The SearchControls to use in the search. + * @param mapper The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List search(Name base, String filter, SearchControls controls, AttributesMapper mapper) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Each resulting NameClassPair is supplied + * to the specified NameClassPairCallbackHandler. + * + * @param base The base DN where the list should be performed. + * @param handler The NameClassPairCallbackHandler to supply + * each {@link NameClassPair} to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void list(String base, NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Each resulting NameClassPair is supplied + * to the specified NameClassPairCallbackHandler. + * + * @param base The base DN where the list should be performed. + * @param handler The NameClassPairCallbackHandler to supply + * each {@link NameClassPair} to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void list(Name base, NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Pass all the found NameClassPair objects + * to the supplied NameClassPairMapper and return all the + * returned values as a List. + * + * @param base The base DN where the list should be performed. + * @param mapper The NameClassPairMapper to supply each + * {@link NameClassPair} to. + * @return a List containing the Objects returned from the + * Mapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List list(String base, NameClassPairMapper mapper) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Pass all the found NameClassPair objects + * to the supplied NameClassPairMapper and return all the + * returned values as a List. + * + * @param base The base DN where the list should be performed. + * @param mapper The NameClassPairMapper to supply each + * {@link NameClassPair} to. + * @return a List containing the Objects returned from the + * Mapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List list(Name base, NameClassPairMapper mapper) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. + * + * @param base The base DN where the list should be performed. + * @return a List containing the names of all the contexts bound to + * base. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List list(String base) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. + * + * @param base The base DN where the list should be performed. + * @return a List containing the names of all the contexts bound to + * base. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List list(Name base) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Each resulting Binding is supplied to the + * specified NameClassPairCallbackHandler. + * + * @param base The base DN where the list should be performed. + * @param handler The NameClassPairCallbackHandler to supply + * each {@link Binding} to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void listBindings(final String base, NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Each resulting Binding is supplied to the + * specified NameClassPairCallbackHandler. + * + * @param base The base DN where the list should be performed. + * @param handler The NameClassPairCallbackHandler to supply + * each {@link Binding} to. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + void listBindings(final Name base, NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Pass all the found Binding objects to the + * supplied NameClassPairMapper and return all the returned + * values as a List. + * + * @param base The base DN where the list should be performed. + * @param mapper The NameClassPairMapper to supply each + * {@link Binding} to. + * @return a List containing the Objects returned from the + * Mapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List listBindings(String base, NameClassPairMapper mapper) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Pass all the found Binding objects to the + * supplied NameClassPairMapper and return all the returned + * values as a List. + * + * @param base The base DN where the list should be performed. + * @param mapper The NameClassPairMapper to supply each + * {@link Binding} to. + * @return a List containing the Objects returned from the + * Mapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List listBindings(Name base, NameClassPairMapper mapper) throws NamingException; + + /** + * Perform a non-recursive listing of children of the given + * base. + * + * @param base The base DN where the list should be performed. + * @return a List containing the names of all the contexts + * bound to base. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List listBindings(final String base) throws NamingException; + + /** + * Perform a non-recursive listing of children of the given + * base. + * + * @param base The base DN where the list should be performed. + * @return a List containing the names of all the contexts + * bound to base. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List listBindings(final Name base) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. The Object returned in each {@link Binding} is + * supplied to the specified ContextMapper. + * + * @param base The base DN where the list should be performed. + * @param mapper The ContextMapper to use for mapping the found + * object. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List listBindings(String base, ContextMapper mapper) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. The Object returned in each {@link Binding} is + * supplied to the specified ContextMapper. + * + * @param base The base DN where the list should be performed. + * @param mapper The ContextMapper to use for mapping the found + * object. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is + * interpreted that no entries were found. + */ + List listBindings(Name base, ContextMapper mapper) throws NamingException; + + /** + * Lookup the supplied DN and return the found object. This will typically + * be a {@link DirContextAdapter}, unless the DirObjectFactory + * has been modified in the ContextSource. + * + * @param dn The distinguished name of the object to find. + * @return the found object, typically a {@link DirContextAdapter} instance. + * @throws NamingException if any error occurs. + * @see #lookupContext(Name) + * @see AbstractContextSource#setDirObjectFactory(Class) + */ + Object lookup(Name dn) throws NamingException; + + /** + * Lookup the supplied DN and return the found object. This will typically + * be a {@link DirContextAdapter}, unless the DirObjectFactory + * has been modified in the ContextSource. + * + * @param dn The distinguished name of the object to find. + * @return the found object, typically a {@link DirContextAdapter} instance. + * @throws NamingException if any error occurs. + * @see #lookupContext(String) + * @see AbstractContextSource#setDirObjectFactory(Class) + */ + Object lookup(String dn) throws NamingException; + + /** + * Convenience method to get the attributes of a specified DN and + * automatically pass them to an AttributesMapper. + * + * @param dn The distinguished name to find. + * @param mapper The AttributesMapper to use for mapping the + * found object. + * @return the object returned from the mapper. + * @throws NamingException if any error occurs. + */ + T lookup(Name dn, AttributesMapper mapper) throws NamingException; + + /** + * Convenience method to get the attributes of a specified DN and + * automatically pass them to an AttributesMapper. + * + * @param dn The distinguished name to find. + * @param mapper The AttributesMapper to use for mapping the + * found object. + * @return the object returned from the mapper. + * @throws NamingException if any error occurs. + */ + T lookup(String dn, AttributesMapper mapper) throws NamingException; + + /** + * Convenience method to lookup a specified DN and automatically pass the + * found object to a ContextMapper. + * + * @param dn The distinguished name to find. + * @param mapper The ContextMapper to use for mapping the found + * object. + * @return the object returned from the mapper. + * @throws NamingException if any error occurs. + */ + T lookup(Name dn, ContextMapper mapper) throws NamingException; + + /** + * Convenience method to lookup a specified DN and automatically pass the + * found object to a ContextMapper. + * + * @param dn The distinguished name to find. + * @param mapper The ContextMapper to use for mapping the found + * object. + * @return the object returned from the mapper. + * @throws NamingException if any error occurs. + */ + T lookup(String dn, ContextMapper mapper) throws NamingException; + + /** + * Convenience method to get the specified attributes of a specified DN and + * automatically pass them to an AttributesMapper. + * + * @param dn The distinguished name to find. + * @param attributes The names of the attributes to pass to the mapper. + * @param mapper The AttributesMapper to use for mapping the + * found object. + * @return the object returned from the mapper. + * @throws NamingException if any error occurs. + */ + T lookup(Name dn, String[] attributes, AttributesMapper mapper) throws NamingException; + + /** + * Convenience method to get the specified attributes of a specified DN and + * automatically pass them to an AttributesMapper. + * + * @param dn The distinguished name to find. + * @param attributes The names of the attributes to pass to the mapper. + * @param mapper The AttributesMapper to use for mapping the + * found object. + * @return the object returned from the mapper. + * @throws NamingException if any error occurs. + */ + T lookup(String dn, String[] attributes, AttributesMapper mapper) throws NamingException; + + /** + * Convenience method to get the specified attributes of a specified DN and + * automatically pass them to a ContextMapper. + * + * @param dn The distinguished name to find. + * @param attributes The names of the attributes to pass to the mapper. + * @param mapper The ContextMapper to use for mapping the found + * object. + * @return the object returned from the mapper. + * @throws NamingException if any error occurs. + */ + T lookup(Name dn, String[] attributes, ContextMapper mapper) throws NamingException; + + /** + * Convenience method to get the specified attributes of a specified DN and + * automatically pass them to a ContextMapper. + * + * @param dn The distinguished name to find. + * @param attributes The names of the attributes to pass to the mapper. + * @param mapper The ContextMapper to use for mapping the found + * object. + * @return the object returned from the mapper. + * @throws NamingException if any error occurs. + */ + T lookup(String dn, String[] attributes, ContextMapper mapper) throws NamingException; + + /** + * Modify an entry in the LDAP tree using the supplied + * ModificationItems. + * + * @param dn The distinguished name of the node to modify. + * @param mods The modifications to perform. + * @throws NamingException if any error occurs. + * @see #modifyAttributes(DirContextOperations) + */ + void modifyAttributes(Name dn, ModificationItem[] mods) throws NamingException; + + /** + * Modify an entry in the LDAP tree using the supplied + * ModificationItems. + * + * @param dn The distinguished name of the node to modify. + * @param mods The modifications to perform. + * @throws NamingException if any error occurs. + * @see #modifyAttributes(DirContextOperations) + */ + void modifyAttributes(String dn, ModificationItem[] mods) throws NamingException; + + /** + * Create an entry in the LDAP tree. The attributes used to create the entry + * are either retrieved from the obj parameter or the + * attributes parameter (or both). One of these parameters may + * be null but not both. + * + * @param dn The distinguished name to bind the object and attributes to. + * @param obj The object to bind, may be null. Typically a + * DirContext implementation. + * @param attributes The attributes to bind, may be null. + * @throws NamingException if any error occurs. + * @see DirContextAdapter + */ + void bind(Name dn, Object obj, Attributes attributes) throws NamingException; + + /** + * Create an entry in the LDAP tree. The attributes used to create the entry + * are either retrieved from the obj parameter or the + * attributes parameter (or both). One of these parameters may + * be null but not both. + * + * @param dn The distinguished name to bind the object and attributes to. + * @param obj The object to bind, may be null. Typically a + * DirContext implementation. + * @param attributes The attributes to bind, may be null. + * @throws NamingException if any error occurs. + * @see DirContextAdapter + */ + void bind(String dn, Object obj, Attributes attributes) throws NamingException; + + /** + * Remove an entry from the LDAP tree. The entry must not have any children + * - if you suspect that the entry might have descendants, use + * {@link #unbind(Name, boolean)} in stead. + * + * @param dn The distinguished name of the entry to remove. + * @throws NamingException if any error occurs. + */ + void unbind(Name dn) throws NamingException; + + /** + * Remove an entry from the LDAP tree. The entry must not have any children + * - if you suspect that the entry might have descendants, use + * {@link #unbind(Name, boolean)} in stead. + * + * @param dn The distinguished name to unbind. + * @throws NamingException if any error occurs. + */ + void unbind(String dn) throws NamingException; + + /** + * Remove an entry from the LDAP tree, optionally removing all descendants + * in the process. + * + * @param dn The distinguished name to unbind. + * @param recursive Whether to unbind all subcontexts as well. If this + * parameter is false and the entry has children, the operation + * will fail. + * @throws NamingException if any error occurs. + */ + void unbind(Name dn, boolean recursive) throws NamingException; + + /** + * Remove an entry from the LDAP tree, optionally removing all descendants + * in the process. + * + * @param dn The distinguished name to unbind. + * @param recursive Whether to unbind all subcontexts as well. If this + * parameter is false and the entry has children, the operation + * will fail. + * @throws NamingException if any error occurs. + */ + void unbind(String dn, boolean recursive) throws NamingException; + + /** + * Remove an entry and replace it with a new one. The attributes used to + * create the entry are either retrieved from the obj parameter + * or the attributes parameter (or both). One of these + * parameters may be null but not both. This method assumes + * that the specified context already exists - if not it will fail. + * + * @param dn The distinguished name to rebind. + * @param obj The object to bind to the DN, may be null. + * Typically a DirContext implementation. + * @param attributes The attributes to bind, may be null. + * @throws NamingException if any error occurs. + * @see DirContextAdapter + */ + void rebind(Name dn, Object obj, Attributes attributes) throws NamingException; + + /** + * Remove an entry and replace it with a new one. The attributes used to + * create the entry are either retrieved from the obj parameter + * or the attributes parameter (or both). One of these + * parameters may be null but not both. This method assumes + * that the specified context already exists - if not it will fail. + * + * @param dn The distinguished name to rebind. + * @param obj The object to bind to the DN, may be null. + * Typically a DirContext implementation. + * @param attributes The attributes to bind, may be null. + * @throws NamingException if any error occurs. + * @see DirContextAdapter + */ + void rebind(String dn, Object obj, Attributes attributes) throws NamingException; + + /** + * Move an entry in the LDAP tree to a new location. + * + * @param oldDn The distinguished name of the entry to move; may not be + * null or empty. + * @param newDn The distinguished name where the entry should be moved; may + * not be null or empty. + * @throws ContextNotEmptyException if newDn is already bound + * @throws NamingException if any other error occurs. + */ + void rename(final Name oldDn, final Name newDn) throws NamingException; + + /** + * Move an entry in the LDAP tree to a new location. + * + * @param oldDn The distinguished name of the entry to move; may not be + * null or empty. + * @param newDn The distinguished name where the entry should be moved; may + * not be null or empty. + * @throws ContextNotEmptyException if newDn is already bound + * @throws NamingException if any other error occurs. + */ + void rename(final String oldDn, final String newDn) throws NamingException; + + /** + * Convenience method to lookup the supplied DN and automatically cast it to + * {@link DirContextOperations}. + * + * @param dn The distinguished name of the object to find. + * @return The found object, cast to {@link DirContextOperations}. + * @throws ClassCastException if an alternative + * DirObjectFactory has been registered with the + * ContextSource, causing the actual class of the returned + * object to be something else than {@link DirContextOperations}. + * @throws NamingException if any other error occurs. + * @see #lookup(Name) + * @see #modifyAttributes(DirContextOperations) + * @since 1.2 + */ + DirContextOperations lookupContext(Name dn) throws NamingException, ClassCastException; + + /** + * Convenience method to lookup the supplied DN and automatically cast it to + * {@link DirContextOperations}. + * + * @param dn The distinguished name of the object to find. + * @return The found object, cast to {@link DirContextOperations}. + * @throws ClassCastException if an alternative + * DirObjectFactory has been registered with the + * ContextSource, causing the actual class of the returned + * object to be something else than {@link DirContextOperations}. + * @throws NamingException if any other error occurs. + * @see #lookup(String) + * @see #modifyAttributes(DirContextOperations) + * @since 1.2 + */ + DirContextOperations lookupContext(String dn) throws NamingException, ClassCastException; + + /** + * Modify the attributes of the entry referenced by the supplied + * {@link DirContextOperations} instance. The DN to update will be the DN of + * the DirContextOperationsinstance, and the + * ModificationItem array is retrieved from the + * DirContextOperations instance using a call to + * {@link AttributeModificationsAware#getModificationItems()}. NB: + * The supplied instance needs to have been properly initialized; this means + * that if it hasn't been received from a lookup operation, its + * DN needs to be initialized and it must have been put in update mode ( + * {@link DirContextAdapter#setUpdateMode(boolean)}). + *

+ * Typical use of this method would be as follows: + * + *

+	 * public void update(Person person) {
+	 * 	DirContextOperations ctx = ldapOperations.lookupContext(person.getDn());
+	 * 
+	 * 	ctx.setAttributeValue("description", person.getDescription());
+	 * 	ctx.setAttributeValue("telephoneNumber", person.getPhone());
+	 * 	// More modifications here
+	 * 
+	 * 	ldapOperations.modifyAttributes(ctx);
+	 * }
+	 * 
+ * + * @param ctx the DirContextOperations instance to use in the update. + * @throws IllegalStateException if the supplied instance is not in update + * mode or has not been properly initialized. + * @throws NamingException if any other error occurs. + * @since 1.2 + * @see #lookupContext(Name) + * @see DirContextAdapter + */ + void modifyAttributes(DirContextOperations ctx) throws IllegalStateException, NamingException; + + /** + * Bind the data in the supplied context in the tree. All specified + * attributes ctxin will be bound to the DN set on ctx. + *

+ * Example:
+ * + *

+	 * DirContextOperations ctx = new DirContextAdapter(dn);
+	 * ctx.setAttributeValue("cn", "john doe");
+	 * ctx.setAttributeValue("description", "some description");
+	 * //More initialization here.
+	 * 
+	 * ldapTemplate.bind(ctx);
+	 * 
+ * @param ctx the context to bind + * @throws IllegalStateException if no DN is set or if the instance is in + * update mode. + * @since 1.3 + */ + void bind(DirContextOperations ctx); + + /** + * Remove an entry and replace it with a new one. The attributes used to + * create the entry are retrieved from the ctx parameter. This + * method assumes that the specified context already exists - if not it will + * fail. The entry will be bound to the DN set on ctx. + *

+ * Example:
+ * + *

+	 * DirContextOperations ctx = new DirContextAdapter(dn);
+	 * ctx.setAttributeValue("cn", "john doe");
+	 * ctx.setAttributeValue("description", "some description");
+	 * //More initialization here.
+	 * 
+	 * ldapTemplate.rebind(ctx);
+	 * 
+ * @param ctx the context to rebind + * @throws IllegalStateException if no DN is set or if the instance is in + * update mode. + * @since 1.3 + */ + void rebind(DirContextOperations ctx); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. + *

+ * Example:
+ * + *

+	 * AndFilter filter = new AndFilter();
+	 * filter.and("objectclass", "person").and("uid", userId);
+	 * boolean authenticated = ldapTemplate.authenticate(LdapUtils.emptyLdapName(), filter.toString(), password);
+	 * 
+ * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @return true if the authentication was successful, + * false otherwise. + * @since 1.3 + * @deprecated use {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} + */ + boolean authenticate(Name base, String filter, String password); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. + *

+ * Example:
+ * + *

+	 * AndFilter filter = new AndFilter();
+	 * filter.and("objectclass", "person").and("uid", userId);
+	 * boolean authenticated = ldapTemplate.authenticate(LdapUtils.emptyLdapName(), filter.toString(), password);
+	 * 
+ * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @return true if the authentication was successful, + * false otherwise. + * @since 1.3 + * @deprecated use {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} + */ + boolean authenticate(String base, String filter, String password); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. The resulting DirContext instance is then used as input to the + * supplied {@link AuthenticatedLdapEntryContextCallback} to perform any + * additional LDAP operations against the authenticated DirContext. + * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @param callback the callback to that will be called to perform operations + * on the DirContext authenticated with the found user. + * @return true if the authentication was successful, + * false otherwise. + * @see #authenticate(Name, String, String) + * @since 1.3 + * @deprecated use {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} + */ + boolean authenticate(Name base, String filter, String password, AuthenticatedLdapEntryContextCallback callback); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. The resulting DirContext instance is then used as input to the + * supplied {@link AuthenticatedLdapEntryContextCallback} to perform any + * additional LDAP operations against the authenticated DirContext. + * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @param callback the callback to that will be called to perform operations + * on the DirContext authenticated with the found user. + * @return true if the authentication was successful, + * false otherwise. + * @see #authenticate(String, String, String) + * @since 1.3 + * @deprecated use {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} + */ + boolean authenticate(String base, String filter, String password, AuthenticatedLdapEntryContextCallback callback); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. The resulting DirContext instance is then used as input to the + * supplied {@link AuthenticatedLdapEntryContextCallback} to perform any + * additional LDAP operations against the authenticated DirContext. If an + * exception is caught, the same exception is passed on to the given + * {@link AuthenticationErrorCallback}. This enables the caller to provide a + * callback that, for example, collects the exception for later processing. + * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @param callback the callback that will be called to perform operations + * on the DirContext authenticated with the found user. + * @param errorCallback the callback that will be called if an exception is caught. + * @return true if the authentication was successful, + * false otherwise. + * @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback) + * @since 1.3.1 + * @deprecated use {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} + */ + boolean authenticate(Name base, String filter, String password, + AuthenticatedLdapEntryContextCallback callback, + AuthenticationErrorCallback errorCallback); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. The resulting DirContext instance is then used as input to the + * supplied {@link AuthenticatedLdapEntryContextCallback} to perform any + * additional LDAP operations against the authenticated DirContext. If an + * exception is caught, the same exception is passed on to the given + * {@link AuthenticationErrorCallback}. This enables the caller to provide a + * callback that, for example, collects the exception for later processing. + * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @param callback the callback that will be called to perform operations + * on the DirContext authenticated with the found user. + * @param errorCallback the callback that will be called if an exception is caught. + * @return true if the authentication was successful, + * false otherwise. + * @see #authenticate(String, String, String, AuthenticatedLdapEntryContextCallback) + * @since 1.3.1 + * @deprecated use {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} + */ + boolean authenticate(String base, String filter, String password, + AuthenticatedLdapEntryContextCallback callback, + AuthenticationErrorCallback errorCallback); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. If an exception is caught, the same exception is passed on to the given + * {@link AuthenticationErrorCallback}. This enables the caller to provide a + * callback that, for example, collects the exception for later processing. + * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @param errorCallback the callback that will be called if an exception is caught. + * @return true if the authentication was successful, + * false otherwise. + * @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback, AuthenticationErrorCallback) + * @since 1.3.1 + * @deprecated use {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} + */ + boolean authenticate(Name base, String filter, String password, + AuthenticationErrorCallback errorCallback); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. If an exception is caught, the same exception is passed on to the given + * {@link AuthenticationErrorCallback}. This enables the caller to provide a + * callback that, for example, collects the exception for later processing. + * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @param errorCallback the callback that will be called if an exception is caught. + * @return true if the authentication was successful, + * false otherwise. + * @throws IncorrectResultSizeDataAccessException if more than one users were found + * @since 1.3.1 + * @deprecated use {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(com.fr.third.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} + */ + boolean authenticate(String base, String filter, String password, + AuthenticationErrorCallback errorCallback); + + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied LdapQuery; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. + *

+ * Note: This method differs from the older authenticate methods in that encountered + * exceptions are thrown rather than supplied to a callback for handling. + *

+ * + * @param query the LdapQuery specifying the details of the search. + * @param password the password to use for authentication. + * @param mapper the callback that will be called to perform operations + * on the DirContext authenticated with the found user. + * false otherwise. + * @return the result from the callback. + * @throws IncorrectResultSizeDataAccessException if more than one users were found + * @throws com.fr.third.springframework.dao.EmptyResultDataAccessException if only one user was found + * @throws NamingException if something went wrong in authentication. + * + * @since 2.0 + * @see com.fr.third.springframework.ldap.query.LdapQueryBuilder + */ + T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper mapper); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. If an exception is caught, the same exception is passed on to the given + * {@link AuthenticationErrorCallback}. This enables the caller to provide a + * callback that, for example, collects the exception for later processing. + *

+ * Note: This method differs from the older authenticate methods in that encountered + * exceptions are thrown rather than supplied to a callback for handling. + *

+ * + * @param query the LdapQuery specifying the details of the search. + * @param password the password to use for authentication. + * false otherwise. + * @throws IncorrectResultSizeDataAccessException if more than one users were found + * @throws com.fr.third.springframework.dao.EmptyResultDataAccessException if only one user was found + * @throws NamingException if something went wrong in authentication. + * + * @since 2.0 + * @see com.fr.third.springframework.ldap.query.LdapQueryBuilder + */ + void authenticate(LdapQuery query, String password); + + + /** + * Perform a search for a unique entry matching the specified search + * criteria and return the found object. If no entry is found or if there + * are more than one matching entry, an + * {@link IncorrectResultSizeDataAccessException} is thrown. + * @param base the DN to use as the base of the search. + * @param filter the search filter. + * @param mapper the mapper to use for the search. + * @return the single object returned by the mapper that matches the search + * criteria. + * @throws IncorrectResultSizeDataAccessException if the result is not one unique entry + * @since 1.3 + */ + T searchForObject(Name base, String filter, ContextMapper mapper); + + /** + * Perform a search for a unique entry matching the specified search + * criteria and return the found object. If no entry is found or if there + * are more than one matching entry, an + * {@link IncorrectResultSizeDataAccessException} is thrown. + * @param base the DN to use as the base of the search. + * @param filter the search filter. + * @param searchControls the searchControls to use for the search. + * @param mapper the mapper to use for the search. + * @return the single object returned by the mapper that matches the search + * criteria. + * @throws IncorrectResultSizeDataAccessException if the result is not one unique entry + * @since 2.0 + */ + T searchForObject(Name base, String filter, SearchControls searchControls, ContextMapper mapper); + + /** + * Perform a search for a unique entry matching the specified search + * criteria and return the found object. If no entry is found or if there + * are more than one matching entry, an + * {@link IncorrectResultSizeDataAccessException} is thrown. + * @param base the DN to use as the base of the search. + * @param filter the search filter. + * @param searchControls the searchControls to use for the search. + * @param mapper the mapper to use for the search. + * @return the single object returned by the mapper that matches the search + * criteria. + * @throws IncorrectResultSizeDataAccessException if the result is not one unique entry + * @since 2.0 + */ + T searchForObject(String base, String filter, SearchControls searchControls, ContextMapper mapper); + + /** + * Perform a search for a unique entry matching the specified search + * criteria and return the found object. If no entry is found or if there + * are more than one matching entry, an + * {@link IncorrectResultSizeDataAccessException} is thrown. + * @param base the DN to use as the base of the search. + * @param filter the search filter. + * @param mapper the mapper to use for the search. + * @return the single object returned by the mapper that matches the search + * criteria. + * @throws IncorrectResultSizeDataAccessException if the result is not one unique entry + * @since 1.3 + */ + T searchForObject(String base, String filter, ContextMapper mapper); + + /** + * Perform a search with parameters from the specified LdapQuery. All found objects will be supplied to the + * NameClassPairCallbackHandler for processing. + * + * @param query the LDAP query specification. + * @param callbackHandler the NameClassPairCallbackHandler to supply all found entries to. + * + * @throws NamingException if any error occurs. + * @since 2.0 + * @see com.fr.third.springframework.ldap.query.LdapQueryBuilder + * @see com.fr.third.springframework.ldap.core.support.CountNameClassPairCallbackHandler + */ + void search(LdapQuery query, NameClassPairCallbackHandler callbackHandler); + + /** + * Perform a search with parameters from the specified LdapQuery. All found objects will be supplied to the + * ContextMapper for processing, and all returned objects will be collected in a list to be returned. + * + * @param query the LDAP query specification. + * @param mapper the ContextMapper to supply all found entries to. + * @return a List containing all entries received from the + * ContextMapper. + * + * @throws NamingException if any error occurs. + * @since 2.0 + * @see com.fr.third.springframework.ldap.query.LdapQueryBuilder + */ + List search(LdapQuery query, ContextMapper mapper); + + /** + * Perform a search with parameters from the specified LdapQuery. The Attributes of the found entries will be + * supplied to the AttributesMapper for processing, and all + * returned objects will be collected in a list to be returned. + * + * @param query the LDAP query specification. + * @param mapper the Attributes to supply all found Attributes to. + * @return a List containing all entries received from the + * Attributes. + * + * @throws NamingException if any error occurs. + * @since 2.0 + * @see com.fr.third.springframework.ldap.query.LdapQueryBuilder + */ + List search(LdapQuery query, AttributesMapper mapper); + + /** + * Perform a search for a unique entry matching the specified LDAP + * query and return the found entry as a DirContextOperation instance. If no entry is found or if there + * are more than one matching entry, an + * {@link IncorrectResultSizeDataAccessException} is thrown. + * @param query the LDAP query specification. + * @return the single entry matching the query as a DirContextOperations instance. + * @throws IncorrectResultSizeDataAccessException if the result is not one unique entry + * @since 2.0 + * @see com.fr.third.springframework.ldap.query.LdapQueryBuilder + */ + DirContextOperations searchForContext(LdapQuery query); + + /** + * Perform a search for a unique entry matching the specified LDAP + * query and return the found object. If no entry is found or if there + * are more than one matching entry, an + * {@link IncorrectResultSizeDataAccessException} is thrown. + * @param query the LDAP query specification. + * @return the single object returned by the mapper that matches the search + * criteria. + * @throws IncorrectResultSizeDataAccessException if the result is not one unique entry + * @since 2.0 + * @see com.fr.third.springframework.ldap.query.LdapQueryBuilder + */ + T searchForObject(LdapQuery query, ContextMapper mapper); + + /** + * Read a named entry from the LDAP directory. The referenced class must have object-directory mapping metadata + * specified using {@link com.fr.third.springframework.ldap.odm.annotations.Entry} and associated annotations. + * + * @param The Java type to return + * @param dn The distinguished name of the entry to read from the LDAP directory. + * @param clazz The Java type to return + * @return The entry as read from the directory + * + * @throws com.fr.third.springframework.ldap.NamingException on error. + * @since 2.0 + */ + T findByDn(Name dn, Class clazz); + + /** + * Create the given entry in the LDAP directory. The referenced class must have object-directory mapping metadata + * specified using {@link com.fr.third.springframework.ldap.odm.annotations.Entry} and associated annotations. + * If the field annotated with {@link com.fr.third.springframework.ldap.odm.annotations.Id} + * is set in the object, this will be used as the distinguished name of the new entry. If no explicit DN is specified, + * an attempt will be made to calculate the name from fields annotated with {@link com.fr.third.springframework.ldap.odm.annotations.DnAttribute}. + * If an id can be calculated, this will be populated in the supplied object. + * + * @param entry The entry to be create, it must not be null or already exist in the directory. + * + * @throws com.fr.third.springframework.ldap.NamingException on error. + * @throws IllegalArgumentException if the entry is null or on failure to determine the distinguished name. + * @since 2.0 + */ + void create(Object entry); + + /** + * Update the given entry in the LDAP directory. The referenced class must have object-directory mapping metadata + * specified using {@link com.fr.third.springframework.ldap.odm.annotations.Entry} and associated annotations. + * If the distinguished name is not explicitly specified (i.e. if the + * field annotated with {@link com.fr.third.springframework.ldap.odm.annotations.Id} is null), + * an attempt will be made to calculate the name from fields annotated with + * {@link com.fr.third.springframework.ldap.odm.annotations.DnAttribute}. If the {@link com.fr.third.springframework.ldap.odm.annotations.Id} + * field and the calculated DN is different, the entry will be moved (i.e., an {@link #unbind(javax.naming.Name)} + * followed by a {@link #bind(DirContextOperations)}. Otherwise + * the current data of the entry will be read from the directory and a {@link #modifyAttributes(DirContextOperations)} + * operation will be performed using the ModificationItems resulting from the changes of the + * entry compared to its current state in the directory. + * If the id of the entry has changed, i.e. if it wasn't specified from the beginning, or if it is calculated to + * have changed, the new value will be populated in the supplied object. + * + * @param entry The entry to update, it must already exist in the directory. + * + * @throws com.fr.third.springframework.ldap.NamingException on error. + * @throws IllegalArgumentException if the entry is null or on failure to determine the distinguished name. + * @since 2.0 + */ + void update(Object entry); + + /** + * Delete an entry from the LDAP directory. The referenced class must have object-directory mapping metadata + * specified using {@link com.fr.third.springframework.ldap.odm.annotations.Entry} and associated annotations. + * If the field annotated with {@link com.fr.third.springframework.ldap.odm.annotations.Id} + * is set in the object, this will be used as the distinguished name of the new entry. If no explicit DN is specified, + * an attempt will be made to calculate the name from fields annotated with {@link com.fr.third.springframework.ldap.odm.annotations.DnAttribute}. + * + * @param entry The entry to delete, it must already exist in the directory. + * + * @throws com.fr.third.springframework.ldap.NamingException on error. + * @throws IllegalArgumentException if the entry is null or on failure to determine the distinguished name. + * @since 2.0 + */ + void delete(Object entry); + + /** + * Find all entries in the LDAP directory of a given type. The referenced class must have object-directory mapping metadata + * specified using {@link com.fr.third.springframework.ldap.odm.annotations.Entry} and associated annotations. + * + * @param The Java type to return + * @param clazz The Java type to return + * @return All entries that are of the type represented by the given + * Java class + * + * @throws com.fr.third.springframework.ldap.NamingException on error. + * @since 2.0 + */ + List findAll(Class clazz); + + /** + * Find all entries in the LDAP directory of a given type. The referenced class must have object-directory mapping metadata + * specified using {@link com.fr.third.springframework.ldap.odm.annotations.Entry} and associated annotations. + * + * @param The Java type to return + * @param base The root of the sub-tree at which to begin the search. + * @param searchControls The search controls of the search. Note that the 'returned attributes' parameter should + * typically not be tampered with, since that may affect the attributes populated in returned entries. + * @param clazz The Java type to return + * @return All entries that are of the type represented by the given + * Java class + * + * @throws com.fr.third.springframework.ldap.NamingException on error. + * @since 2.0 + */ + List findAll(Name base, SearchControls searchControls, Class clazz); + + /** + * Find all entries in the LDAP directory of a given type that matches the specified filter. + * The referenced class must have object-directory mapping metadata specified using + * {@link com.fr.third.springframework.ldap.odm.annotations.Entry} and associated annotations. + * + * @param The Java type to return + * @param base The root of the sub-tree at which to begin the search. + * @param filter The search filter. + * @param searchControls The search controls of the search. Note that the 'returned attributes' parameter should + * typically not be tampered with, since that may affect the attributes populated in returned entries. + * @param clazz The Java type to return + * @return All entries that are of the type represented by the given + * Java class + * + * @throws com.fr.third.springframework.ldap.NamingException on error. + * @since 2.0 + */ + List find(Name base, Filter filter, SearchControls searchControls, Class clazz); + + /** + * Search for entries in the LDAP directory. The referenced class must have object-directory + * mapping metadata specified using {@link com.fr.third.springframework.ldap.odm.annotations.Entry} and associated annotations. + *

+ * Only those entries that both match the query search filter and + * are represented by the given Java class are returned. + * + * @param The Java type to return + * @param query the LDAP query specification + * @param clazz The Java type to return + * @return All matching entries. + * + * @throws com.fr.third.springframework.ldap.NamingException on error. + * @see com.fr.third.springframework.ldap.query.LdapQueryBuilder + * @since 2.0 + */ + List find(LdapQuery query, Class clazz); + + /** + * Search for objects in the directory tree matching the specified LdapQuery, expecting to find exactly one match. + * The referenced class must have object-directory mapping metadata specified using + * {@link com.fr.third.springframework.ldap.odm.annotations.Entry} and associated annotations. + * @param query the LDAP query specification + * @param clazz The Java type to return + * @param The Java type to return + * @return The single entry matching the search specification. + * @since 2.0 + * @throws com.fr.third.springframework.ldap.NamingException on LDAP error. + * @throws com.fr.third.springframework.dao.EmptyResultDataAccessException if no matching entry can be found + * @throws IncorrectResultSizeDataAccessException if more than one matching entry is found + */ + T findOne(LdapQuery query, Class clazz); + + /** + * Get the configured ObjectDirectoryMapper. For internal use. + * + * @return the configured ObjectDirectoryMapper. + * @since 2.0 + */ + ObjectDirectoryMapper getObjectDirectoryMapper(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/LdapRdn.java b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapRdn.java new file mode 100644 index 000000000..2e7e5d38a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapRdn.java @@ -0,0 +1,308 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.ldap.BadLdapGrammarException; +import com.fr.third.springframework.util.ObjectUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Datatype for a LDAP name, a part of a path. + * + * The name: uid=adam.skogman Key: uid Value: adam.skogman + * + * @author Adam Skogman + * @author Mattias Hellborg Arthursson + * @deprecated {@link DistinguishedName} and associated classes are deprecated as of 2.0. + */ +public class LdapRdn implements Serializable, Comparable { + private static final long serialVersionUID = 5681397547245228750L; + private static final int DEFAULT_BUFFER_SIZE = 100; + + private Map components = new LinkedHashMap(); + + /** + * Default constructor. Create an empty, uninitialized LdapRdn. + */ + public LdapRdn() { + } + + /** + * Parse the supplied string and construct this instance accordingly. + * + * @param string the string to parse. + */ + public LdapRdn(String string) { + DnParser parser = DefaultDnParserFactory.createDnParser(string); + LdapRdn rdn; + try { + rdn = parser.rdn(); + } + catch (ParseException e) { + throw new BadLdapGrammarException("Failed to parse Rdn", e); + } + catch (Exception e) { + throw new BadLdapGrammarException("Failed to parse Rdn", e); + } + this.components = rdn.components; + } + + /** + * Construct an LdapRdn using the supplied key and value. + * + * @param key the attribute name. + * @param value the attribute value. + */ + public LdapRdn(String key, String value) { + components.put(key, new LdapRdnComponent(key, value)); + } + + /** + * Add an LdapRdnComponent to this LdapRdn. + * + * @param rdnComponent the LdapRdnComponent to add.s + */ + public void addComponent(LdapRdnComponent rdnComponent) { + components.put(rdnComponent.getKey(), rdnComponent); + } + + /** + * Gets all components in this LdapRdn. + * + * @return the List of all LdapRdnComponents composing this LdapRdn. + */ + public List getComponents() { + return new ArrayList(components.values()); + } + + /** + * Gets the first LdapRdnComponent of this LdapRdn. + * + * @return The first LdapRdnComponent of this LdapRdn. + * @throws IndexOutOfBoundsException if there are no components in this Rdn. + */ + public LdapRdnComponent getComponent() { + if(components.size() == 0) { + throw new IndexOutOfBoundsException("No components"); + } + + return components.values().iterator().next(); + } + + /** + * Get the LdapRdnComponent at index idx. + * + * @param idx the 0-based index of the component to get. + * @return the LdapRdnComponent at index idx. + * @throws IndexOutOfBoundsException if there are no components in this Rdn. + */ + public LdapRdnComponent getComponent(int idx) { + if(idx >= components.size()) { + throw new IndexOutOfBoundsException(); + } + + return (LdapRdnComponent) new ArrayList(components.values()).get(idx); + } + + /** + * Get a properly rfc2253-encoded String representation of this LdapRdn. + * + * @return an escaped String corresponding to this LdapRdn. + * @throws IndexOutOfBoundsException if there are no components in this Rdn. + */ + public String getLdapEncoded() { + if (components.size() == 0) { + throw new IndexOutOfBoundsException("No components in Rdn."); + } + StringBuffer sb = new StringBuffer(DEFAULT_BUFFER_SIZE); + for (Iterator iter = components.values().iterator(); iter.hasNext();) { + LdapRdnComponent component = (LdapRdnComponent) iter.next(); + sb.append(component.encodeLdap()); + if (iter.hasNext()) { + sb.append("+"); + } + } + + return sb.toString(); + } + + /** + * Get a String representation of this LdapRdn for use in urls. + * + * @return a String representation of this LdapRdn for use in urls. + */ + public String encodeUrl() { + StringBuffer sb = new StringBuffer(DEFAULT_BUFFER_SIZE); + for (Iterator iter = components.values().iterator(); iter.hasNext();) { + LdapRdnComponent component = (LdapRdnComponent) iter.next(); + sb.append(component.encodeUrl()); + if (iter.hasNext()) { + sb.append("+"); + } + } + + return sb.toString(); + } + + /** + * Compare this LdapRdn to another object. + * + * @param obj the object to compare to. + * @throws ClassCastException if the supplied object is not an LdapRdn + * instance. + */ + public int compareTo(Object obj) { + LdapRdn that = (LdapRdn) obj; + + if(this.components.size() != that.components.size()) { + return this.components.size() - that.components.size(); + } + + Set> theseEntries = this.components.entrySet(); + for (Map.Entry oneEntry : theseEntries) { + LdapRdnComponent thatEntry = that.components.get(oneEntry.getKey()); + if(thatEntry == null) { + return -1; + } + + int compared = oneEntry.getValue().compareTo(thatEntry); + if(compared != 0) { + return compared; + } + } + + return 0; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + + LdapRdn that = (LdapRdn) obj; + + if(this.components.size() != that.components.size()) { + return false; + } + + Set> theseEntries = this.components.entrySet(); + for (Map.Entry oneEntry : theseEntries) { + if(!oneEntry.getValue().equals(that.components.get(oneEntry.getKey()))) { + return false; + } + } + + return true; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return this.getClass().hashCode() ^ new HashSet(getComponents()).hashCode(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + public String toString() { + return getLdapEncoded(); + } + + /** + * Get the value of this LdapRdn. Note that if this Rdn is multi-value the + * first value will be returned. E.g. for the Rdn + * cn=john doe+sn=doe, the return value would be + * john doe. + * + * @return the (first) value of this LdapRdn. + * @throws IndexOutOfBoundsException if there are no components in this Rdn. + */ + public String getValue() { + return getComponent().getValue(); + } + + /** + * Get the key of this LdapRdn. Note that if this Rdn is multi-value the + * first key will be returned. E.g. for the Rdn + * cn=john doe+sn=doe, the return value would be + * cn. + * + * @return the (first) key of this LdapRdn. + * @throws IndexOutOfBoundsException if there are no components in this Rdn. + */ + public String getKey() { + return getComponent().getKey(); + } + + /** + * Get the value of the LdapComponent with the specified key (Attribute + * name). + * + * @param key the key + * @return the value. + * @throws IllegalArgumentException if there is no component with the + * specified key. + */ + public String getValue(String key) { + for (Iterator iter = components.values().iterator(); iter.hasNext();) { + LdapRdnComponent component = (LdapRdnComponent) iter.next(); + if (ObjectUtils.nullSafeEquals(component.getKey(), key)) { + return component.getValue(); + } + } + + throw new IllegalArgumentException("No RdnComponent with the key " + key); + } + + /** + * Create an immutable copy of this instance. It will not be possible to add + * or remove components or modify the keys and values of these components. + * + * @return an immutable copy of this instance. + * @since 1.3 + */ + public LdapRdn immutableLdapRdn() { + Map mapWithImmutableRdns = new LinkedHashMap(components.size()); + for (Iterator iterator = components.values().iterator(); iterator.hasNext();) { + LdapRdnComponent rdnComponent = (LdapRdnComponent) iterator.next(); + mapWithImmutableRdns.put(rdnComponent.getKey(), rdnComponent.immutableLdapRdnComponent()); + } + Map unmodifiableMapOfImmutableRdns = Collections.unmodifiableMap(mapWithImmutableRdns); + LdapRdn immutableRdn = new LdapRdn(); + immutableRdn.components = unmodifiableMapOfImmutableRdns; + return immutableRdn; + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/LdapRdnComponent.java b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapRdnComponent.java new file mode 100644 index 000000000..564650883 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapRdnComponent.java @@ -0,0 +1,264 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.support.LdapEncoder; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.StringUtils; + +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Represents part of an LdapRdn. As specified in RFC2253 an LdapRdn may be + * composed of several attributes, separated by "+". An + * LdapRdnComponent represents one of these attributes. + * + * @author Mattias Hellborg Arthursson + * @deprecated {@link DistinguishedName} and associated classes are deprecated as of 2.0. + */ +public class LdapRdnComponent implements Comparable, Serializable { + private static final long serialVersionUID = -3296747972616243038L; + + private static final Logger LOG = LoggerFactory.getLogger(LdapRdnComponent.class); + + public static final boolean DONT_DECODE_VALUE = false; + + private String key; + + private String value; + + /** + * Constructs an LdapRdnComponent without decoding the value. + * + * @param key the Attribute name. + * @param value the Attribute value. + */ + public LdapRdnComponent(String key, String value) { + this(key, value, DONT_DECODE_VALUE); + } + + /** + * Constructs an LdapRdnComponent, optionally decoding the value. + *

+ * Depending on the value of the "key case fold" System property, the keys + * will be lowercased, uppercased, or preserve their original case. Default + * is to convert them to lowercase. + * + * @param key the Attribute name. + * @param value the Attribute value. + * @param decodeValue if true the value is decoded (typically + * used when a DN is parsed from a String), otherwise the value is used as + * specified. + * @see DistinguishedName#KEY_CASE_FOLD_PROPERTY + */ + public LdapRdnComponent(String key, String value, boolean decodeValue) { + Assert.hasText(key, "Key must not be empty"); + Assert.hasText(value, "Value must not be empty"); + + String caseFold = System.getProperty(DistinguishedName.KEY_CASE_FOLD_PROPERTY); + if (!StringUtils.hasText(caseFold) || caseFold.equals(DistinguishedName.KEY_CASE_FOLD_LOWER)) { + this.key = key.toLowerCase(); + } else if (caseFold.equals(DistinguishedName.KEY_CASE_FOLD_UPPER)) { + this.key = key.toUpperCase(); + } else if (caseFold.equals(DistinguishedName.KEY_CASE_FOLD_NONE)) { + this.key = key; + } else { + LOG + .warn("\"" + caseFold + "\" invalid property value for " + DistinguishedName.KEY_CASE_FOLD_PROPERTY + + "; expected \"" + DistinguishedName.KEY_CASE_FOLD_LOWER + "\", \"" + + DistinguishedName.KEY_CASE_FOLD_UPPER + "\", or \"" + + DistinguishedName.KEY_CASE_FOLD_NONE + "\""); + this.key = key.toLowerCase(); + } + if (decodeValue) { + this.value = LdapEncoder.nameDecode(value); + } + else { + this.value = value; + } + } + + /** + * Get the key (Attribute name) of this component. + * + * @return the key. + */ + public String getKey() { + return key; + } + + /** + * Set the key (Attribute name) of this component. + * + * @param key the key. + * @deprecated Using this method changes the internal state of surrounding + * DistinguishedName instance. This should be avoided. + */ + public void setKey(String key) { + Assert.hasText(key, "Key must not be empty"); + this.key = key; + } + + /** + * Get the (Attribute) value of this component. + * + * @return the value. + */ + public String getValue() { + return value; + } + + /** + * Set the (Attribute) value of this component. + * + * @param value the value. + * @deprecated Using this method changes the internal state of surrounding + * DistinguishedName instance. This should be avoided. + */ + public void setValue(String value) { + Assert.hasText(value, "Value must not be empty"); + this.value = value; + } + + /** + * Encode key and value to ldap. + * + * @return Properly ldap escaped rdn. + */ + protected String encodeLdap() { + StringBuffer buff = new StringBuffer(key.length() + value.length() * 2); + + buff.append(key); + buff.append('='); + buff.append(LdapEncoder.nameEncode(value)); + + return buff.toString(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + public String toString() { + return getLdapEncoded(); + } + + /** + * @return The LdapRdn as a string where the value is LDAP-encoded. + */ + public String getLdapEncoded() { + return encodeLdap(); + } + + /** + * Get a String representation of this instance for use in URLs. + * + * @return a properly URL encoded representation of this instancs. + */ + public String encodeUrl() { + // Use the URI class to properly URL encode the value. + try { + URI valueUri = new URI(null, null, value, null); + return key + "=" + valueUri.toString(); + } + catch (URISyntaxException e) { + // This should really never happen... + return key + "=" + "value"; + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return key.toUpperCase().hashCode() ^ value.toUpperCase().hashCode(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + // Slightly more lenient equals comparison here to enable immutable + // instances to equal mutable ones. + if (obj != null && obj instanceof LdapRdnComponent) { + LdapRdnComponent that = (LdapRdnComponent) obj; + // It's safe to compare directly against key and value, + // because they are validated not to be null on instance creation. + return this.key.equalsIgnoreCase(that.key) + && this.value.equalsIgnoreCase(that.value); + + } + else { + return false; + } + } + + /** + * Compare this instance to the supplied object. + * + * @param obj the object to compare to. + * @throws ClassCastException if the object is not possible to cast to an + * LdapRdnComponent. + */ + public int compareTo(Object obj) { + LdapRdnComponent that = (LdapRdnComponent) obj; + + // It's safe to compare directly against key and value, + // because they are validated not to be null on instance creation. + int keyCompare = this.key.toLowerCase().compareTo(that.key.toLowerCase()); + if(keyCompare == 0) { + return this.value.toLowerCase().compareTo(that.value.toLowerCase()); + } else { + return keyCompare; + } + } + + /** + * Create an immutable copy of this instance. It will not be possible to + * modify the key or the value of the returned instance. + * + * @return an immutable copy of this instance. + * @since 1.3 + */ + public LdapRdnComponent immutableLdapRdnComponent() { + return new ImmutableLdapRdnComponent(key, value); + } + + private static class ImmutableLdapRdnComponent extends LdapRdnComponent { + private static final long serialVersionUID = -7099970046426346567L; + + public ImmutableLdapRdnComponent(String key, String value) { + super(key, value); + } + + public void setKey(String key) { + throw new UnsupportedOperationException("SetValue not supported for this immutable LdapRdnComponent"); + } + + public void setValue(String value) { + throw new UnsupportedOperationException("SetKey not supported for this immutable LdapRdnComponent"); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/LdapTemplate.java b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapTemplate.java new file mode 100644 index 000000000..e808a33ec --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/LdapTemplate.java @@ -0,0 +1,1910 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.dao.EmptyResultDataAccessException; +import com.fr.third.springframework.dao.IncorrectResultSizeDataAccessException; +import com.fr.third.springframework.ldap.AuthenticationException; +import com.fr.third.springframework.ldap.NamingException; +import com.fr.third.springframework.ldap.UncategorizedLdapException; +import com.fr.third.springframework.ldap.filter.Filter; +import com.fr.third.springframework.ldap.odm.core.ObjectDirectoryMapper; +import com.fr.third.springframework.ldap.odm.core.OdmException; +import com.fr.third.springframework.ldap.odm.core.impl.DefaultObjectDirectoryMapper; +import com.fr.third.springframework.ldap.query.LdapQuery; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.Assert; + +import javax.naming.Binding; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.PartialResultException; +import javax.naming.SizeLimitExceededException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.ldap.LdapName; +import java.util.List; + +/** + * Executes core LDAP functionality and helps to avoid common errors, relieving + * the user of the burden of looking up contexts, looping through + * NamingEnumerations and closing contexts. + *

+ * Note for Active Directory (AD) users: AD servers are apparently unable + * to handle referrals automatically, which causes a + * PartialResultException to be thrown whenever a referral is + * encountered in a search. To avoid this, set the + * ignorePartialResultException property to true. + * There is currently no way of manually handling these referrals in the form of + * ReferralException, i.e. either you get the exception (and your + * results are lost) or all referrals are ignored (if the server is unable to + * handle them properly. Neither is there any simple way to get notified that a + * PartialResultException has been ignored (other than in the log). + * + * @see com.fr.third.springframework.ldap.core.ContextSource + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class LdapTemplate implements LdapOperations, InitializingBean { + + private static final Logger LOG = LoggerFactory.getLogger(LdapTemplate.class); + + private static final boolean DONT_RETURN_OBJ_FLAG = false; + + private static final boolean RETURN_OBJ_FLAG = true; + + private static final String[] ALL_ATTRIBUTES = null; + + private ContextSource contextSource; + + private boolean ignorePartialResultException = false; + + private boolean ignoreNameNotFoundException = false; + + private boolean ignoreSizeLimitExceededException = true; + + private int defaultSearchScope = SearchControls.SUBTREE_SCOPE; + + private int defaultTimeLimit = 0; + + private int defaultCountLimit = 0; + + private ObjectDirectoryMapper odm = new DefaultObjectDirectoryMapper(); + + /** + * Constructor for bean usage. + */ + public LdapTemplate() { + } + + /** + * Constructor to setup instance directly. + * + * @param contextSource the ContextSource to use. + */ + public LdapTemplate(ContextSource contextSource) { + this.contextSource = contextSource; + } + + /** + * Set the ContextSource. Call this method when the default constructor has + * been used. + * + * @param contextSource the ContextSource. + */ + public void setContextSource(ContextSource contextSource) { + this.contextSource = contextSource; + } + + /** + * {@inheritDoc} + */ + @Override + public ObjectDirectoryMapper getObjectDirectoryMapper() { + return odm; + } + + /** + * Set the ObjectDirectoryMapper instance to use. + * + * @param odm the ObejctDirectoryMapper to use. + * @since 2.0 + */ + public void setObjectDirectoryMapper(ObjectDirectoryMapper odm) { + this.odm = odm; + } + + /** + * Get the ContextSource. + * + * @return the ContextSource. + */ + public ContextSource getContextSource() { + return contextSource; + } + + /** + * Specify whether NameNotFoundException should be ignored in + * searches. In previous version, NameNotFoundException caused + * by the search base not being found was silently ignored. The default + * behavior is now to treat this as an error (as it should), and to convert + * and re-throw the exception. The ability to revert to the previous + * behavior still exists. The only difference is that the incident is in + * that case no longer silently ignored, but logged as a warning. + * + * @param ignore true if NameNotFoundException + * should be ignored in searches, false otherwise. Default is + * false. + * + * @since 1.3 + */ + public void setIgnoreNameNotFoundException(boolean ignore) { + this.ignoreNameNotFoundException = ignore; + } + + /** + * Specify whether PartialResultException should be ignored in + * searches. AD servers typically have a problem with referrals. Normally a + * referral should be followed automatically, but this does not seem to work + * with AD servers. The problem manifests itself with a a + * PartialResultException being thrown when a referral is + * encountered by the server. Setting this property to true + * presents a workaround to this problem by causing + * PartialResultException to be ignored, so that the search + * method returns normally. Default value of this parameter is + * false. + * + * @param ignore true if PartialResultException + * should be ignored in searches, false otherwise. Default is + * false. + */ + public void setIgnorePartialResultException(boolean ignore) { + this.ignorePartialResultException = ignore; + } + + /** + * Specify whether SizeLimitExceededException should be ignored in searches. + * This is typically what you want if you specify count limit in your search controls. + * + * @param ignore true if SizeLimitExceededException + * should be ignored in searches, false otherwise. Default is + * true. + * @since 2.0 + */ + public void setIgnoreSizeLimitExceededException(boolean ignore) { + this.ignoreSizeLimitExceededException = ignore; + } + + /** + * Set the default scope to be used in searches if not explicitly specified. + * Default is {@link javax.naming.directory.SearchControls#SUBTREE_SCOPE}. + * + * @param defaultSearchScope the default search scope to use in searches. + * One of {@link SearchControls#OBJECT_SCOPE}, + * {@link SearchControls#ONELEVEL_SCOPE}, + * or {@link SearchControls#SUBTREE_SCOPE} + * @since 2.0 + */ + public void setDefaultSearchScope(int defaultSearchScope) { + this.defaultSearchScope = defaultSearchScope; + } + + /** + * Set the default time limit be used in searches if not explicitly specified. + * Default is 0, indicating no time limit. + * + * @param defaultTimeLimit the default time limit to use in searches. + * @since 2.0 + */ + public void setDefaultTimeLimit(int defaultTimeLimit) { + this.defaultTimeLimit = defaultTimeLimit; + } + + /** + * Set the default count limit be used in searches if not explicitly specified. + * Default is 0, indicating no count limit. + * + * @param defaultCountLimit the default count limit to use in searches. + * @since 2.0 + */ + public void setDefaultCountLimit(int defaultCountLimit) { + this.defaultCountLimit = defaultCountLimit; + } + + /** + * {@inheritDoc} + */ + @Override + public void search(Name base, String filter, int searchScope, boolean returningObjFlag, + NameClassPairCallbackHandler handler) { + + search(base, filter, getDefaultSearchControls(searchScope, returningObjFlag, ALL_ATTRIBUTES), handler); + } + + /** + * {@inheritDoc} + */ + @Override + public void search(String base, String filter, int searchScope, boolean returningObjFlag, + NameClassPairCallbackHandler handler) { + + search(base, filter, getDefaultSearchControls(searchScope, returningObjFlag, ALL_ATTRIBUTES), handler); + } + + /** + * {@inheritDoc} + */ + @Override + public void search(final Name base, final String filter, final SearchControls controls, + NameClassPairCallbackHandler handler) { + + // Create a SearchExecutor to perform the search. + SearchExecutor se = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { + return ctx.search(base, filter, controls); + } + }; + if (handler instanceof ContextMapperCallbackHandler) { + assureReturnObjFlagSet(controls); + } + search(se, handler); + } + + /** + * {@inheritDoc} + */ + @Override + public void search(final String base, final String filter, final SearchControls controls, + NameClassPairCallbackHandler handler) { + + // Create a SearchExecutor to perform the search. + SearchExecutor se = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { + return ctx.search(base, filter, controls); + } + }; + if (handler instanceof ContextMapperCallbackHandler) { + assureReturnObjFlagSet(controls); + } + search(se, handler); + } + + /** + * {@inheritDoc} + */ + @Override + public void search(final Name base, final String filter, final SearchControls controls, + NameClassPairCallbackHandler handler, DirContextProcessor processor) { + + // Create a SearchExecutor to perform the search. + SearchExecutor se = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { + return ctx.search(base, filter, controls); + } + }; + if (handler instanceof ContextMapperCallbackHandler) { + assureReturnObjFlagSet(controls); + } + search(se, handler, processor); + } + + /** + * {@inheritDoc} + */ + @Override + public void search(final String base, final String filter, final SearchControls controls, + NameClassPairCallbackHandler handler, DirContextProcessor processor) { + + // Create a SearchExecutor to perform the search. + SearchExecutor se = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { + return ctx.search(base, filter, controls); + } + }; + if (handler instanceof ContextMapperCallbackHandler) { + assureReturnObjFlagSet(controls); + } + search(se, handler, processor); + } + + /** + * Perform a search operation, such as a search(), list() or listBindings(). + * This method handles all the plumbing; getting a readonly context; looping + * through the NamingEnumeration and closing the context and enumeration. It + * also calls the supplied DirContextProcessor before and after the search, + * respectively. This enables custom pre-processing and post-processing, + * like for example when handling paged results or other search controls. + *

+ * The actual list is delegated to the {@link SearchExecutor} and each + * {@link NameClassPair} (this might be a NameClassPair or a subclass + * thereof) is passed to the CallbackHandler. Any encountered + * NamingException will be translated using the NamingExceptionTranslator. + * + * @param se the SearchExecutor to use for performing the actual list. + * @param handler the NameClassPairCallbackHandler to which each found entry + * will be passed. + * @param processor DirContextProcessor for custom pre- and post-processing. + * Must not be null. If no custom processing should take place, + * please use e.g. + * {@link #search(SearchExecutor, NameClassPairCallbackHandler)}. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is interpreted that + * no entries were found. + */ + @Override + public void search(SearchExecutor se, NameClassPairCallbackHandler handler, DirContextProcessor processor) { + DirContext ctx = contextSource.getReadOnlyContext(); + + NamingEnumeration results = null; + RuntimeException ex = null; + try { + processor.preProcess(ctx); + results = se.executeSearch(ctx); + + while (results.hasMore()) { + NameClassPair result = (NameClassPair) results.next(); + handler.handleNameClassPair(result); + } + } + catch (NameNotFoundException e) { + // It is possible to ignore errors caused by base not found + if (ignoreNameNotFoundException) { + LOG.warn("Base context not found, ignoring: " + e.getMessage()); + } + else { + ex = LdapUtils.convertLdapException(e); + } + } + catch (PartialResultException e) { + // Workaround for AD servers not handling referrals correctly. + if (ignorePartialResultException) { + LOG.debug("PartialResultException encountered and ignored", e); + } + else { + ex = LdapUtils.convertLdapException(e); + } + } + catch(SizeLimitExceededException e) { + if(ignoreSizeLimitExceededException) { + LOG.debug("SizeLimitExceededException encountered and ignored", e); + } + else { + ex = LdapUtils.convertLdapException(e); + } + } + catch (javax.naming.NamingException e) { + ex = LdapUtils.convertLdapException(e); + } + finally { + try { + processor.postProcess(ctx); + } + catch (javax.naming.NamingException e) { + if (ex == null) { + ex = LdapUtils.convertLdapException(e); + } + else { + // We already had an exception from above and should ignore + // this one. + LOG.debug("Ignoring Exception from postProcess, " + "main exception thrown instead", e); + } + } + closeContextAndNamingEnumeration(ctx, results); + // If we got an exception it should be thrown. + if (ex != null) { + throw ex; + } + } + } + + /** + * Perform a search operation, such as a search(), list() or listBindings(). + * This method handles all the plumbing; getting a readonly context; looping + * through the NamingEnumeration and closing the context and enumeration. + *

+ * The actual list is delegated to the {@link SearchExecutor} and each + * {@link NameClassPair} (this might be a NameClassPair or a subclass + * thereof) is passed to the CallbackHandler. Any encountered + * NamingException will be translated using the NamingExceptionTranslator. + * + * @param se the SearchExecutor to use for performing the actual list. + * @param handler the NameClassPairCallbackHandler to which each found entry + * will be passed. + * @throws NamingException if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead this is interpreted that + * no entries were found. + */ + @Override + public void search(SearchExecutor se, NameClassPairCallbackHandler handler) { + search(se, handler, new NullDirContextProcessor()); + } + + /** + * {@inheritDoc} + */ + @Override + public void search(Name base, String filter, NameClassPairCallbackHandler handler) { + + SearchControls controls = getDefaultSearchControls(defaultSearchScope, DONT_RETURN_OBJ_FLAG, ALL_ATTRIBUTES); + if (handler instanceof ContextMapperCallbackHandler) { + assureReturnObjFlagSet(controls); + } + search(base, filter, controls, handler); + } + + /** + * {@inheritDoc} + */ + @Override + public void search(String base, String filter, NameClassPairCallbackHandler handler) { + + SearchControls controls = getDefaultSearchControls(defaultSearchScope, DONT_RETURN_OBJ_FLAG, ALL_ATTRIBUTES); + if (handler instanceof ContextMapperCallbackHandler) { + assureReturnObjFlagSet(controls); + } + search(base, filter, controls, handler); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, int searchScope, String[] attrs, AttributesMapper mapper) { + return search(base, filter, getDefaultSearchControls(searchScope, DONT_RETURN_OBJ_FLAG, attrs), mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, int searchScope, String[] attrs, AttributesMapper mapper) { + return search(base, filter, getDefaultSearchControls(searchScope, DONT_RETURN_OBJ_FLAG, attrs), mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, int searchScope, AttributesMapper mapper) { + return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, int searchScope, AttributesMapper mapper) { + return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, AttributesMapper mapper) { + return search(base, filter, defaultSearchScope, mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, AttributesMapper mapper) { + return search(base, filter, defaultSearchScope, mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, int searchScope, String[] attrs, ContextMapper mapper) { + return search(base, filter, getDefaultSearchControls(searchScope, RETURN_OBJ_FLAG, attrs), mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, int searchScope, String[] attrs, ContextMapper mapper) { + return search(base, filter, getDefaultSearchControls(searchScope, RETURN_OBJ_FLAG, attrs), mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, int searchScope, ContextMapper mapper) { + return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, int searchScope, ContextMapper mapper) { + return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, ContextMapper mapper) { + return search(base, filter, defaultSearchScope, mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, ContextMapper mapper) { + return search(base, filter, defaultSearchScope, mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, SearchControls controls, ContextMapper mapper) { + return search(base, filter, controls, mapper, new NullDirContextProcessor()); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, SearchControls controls, ContextMapper mapper) { + return search(base, filter, controls, mapper, new NullDirContextProcessor()); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, SearchControls controls, AttributesMapper mapper) { + return search(base, filter, controls, mapper, new NullDirContextProcessor()); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, SearchControls controls, AttributesMapper mapper) { + return search(base, filter, controls, mapper, new NullDirContextProcessor()); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, SearchControls controls, AttributesMapper mapper, + DirContextProcessor processor) { + AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(mapper); + search(base, filter, controls, handler, processor); + + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, SearchControls controls, AttributesMapper mapper, + DirContextProcessor processor) { + AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(mapper); + search(base, filter, controls, handler, processor); + + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(String base, String filter, SearchControls controls, ContextMapper mapper, + DirContextProcessor processor) { + assureReturnObjFlagSet(controls); + ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(mapper); + search(base, filter, controls, handler, processor); + + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(Name base, String filter, SearchControls controls, ContextMapper mapper, + DirContextProcessor processor) { + assureReturnObjFlagSet(controls); + ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(mapper); + search(base, filter, controls, handler, processor); + + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public void list(final String base, NameClassPairCallbackHandler handler) { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { + return ctx.list(base); + } + }; + + search(searchExecutor, handler); + } + + /** + * {@inheritDoc} + */ + @Override + public void list(final Name base, NameClassPairCallbackHandler handler) { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { + return ctx.list(base); + } + }; + + search(searchExecutor, handler); + } + + /** + * {@inheritDoc} + */ + @Override + public List list(String base, NameClassPairMapper mapper) { + CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler(mapper); + list(base, handler); + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List list(Name base, NameClassPairMapper mapper) { + CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler(mapper); + list(base, handler); + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List list(final Name base) { + return list(base, new DefaultNameClassPairMapper()); + } + + /** + * {@inheritDoc} + */ + @Override + public List list(final String base) { + return list(base, new DefaultNameClassPairMapper()); + } + + /** + * {@inheritDoc} + */ + @Override + public void listBindings(final String base, NameClassPairCallbackHandler handler) { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { + return ctx.listBindings(base); + } + }; + + search(searchExecutor, handler); + } + + /** + * {@inheritDoc} + */ + @Override + public void listBindings(final Name base, NameClassPairCallbackHandler handler) { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { + return ctx.listBindings(base); + } + }; + + search(searchExecutor, handler); + } + + /** + * {@inheritDoc} + */ + @Override + public List listBindings(String base, NameClassPairMapper mapper) { + CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler(mapper); + listBindings(base, handler); + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List listBindings(Name base, NameClassPairMapper mapper) { + CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler(mapper); + listBindings(base, handler); + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List listBindings(final String base) { + return listBindings(base, new DefaultNameClassPairMapper()); + } + + /** + * {@inheritDoc} + */ + @Override + public List listBindings(final Name base) { + return listBindings(base, new DefaultNameClassPairMapper()); + } + + /** + * {@inheritDoc} + */ + @Override + public List listBindings(String base, ContextMapper mapper) { + ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(mapper); + listBindings(base, handler); + + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List listBindings(Name base, ContextMapper mapper) { + ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(mapper); + listBindings(base, handler); + + return handler.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public T executeReadOnly(ContextExecutor ce) { + DirContext ctx = contextSource.getReadOnlyContext(); + return executeWithContext(ce, ctx); + } + + /** + * {@inheritDoc} + */ + @Override + public T executeReadWrite(ContextExecutor ce) { + DirContext ctx = contextSource.getReadWriteContext(); + return executeWithContext(ce, ctx); + } + + private T executeWithContext(ContextExecutor ce, DirContext ctx) { + try { + return ce.executeWithContext(ctx); + } + catch (javax.naming.NamingException e) { + throw LdapUtils.convertLdapException(e); + } + finally { + closeContext(ctx); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object lookup(final Name dn) { + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + return ctx.lookup(dn); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public Object lookup(final String dn) { + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + return ctx.lookup(dn); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public T lookup(final Name dn, final AttributesMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { + Attributes attributes = ctx.getAttributes(dn); + return mapper.mapFromAttributes(attributes); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public T lookup(final String dn, final AttributesMapper mapper) { + + return executeReadOnly(new ContextExecutor() { + public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { + Attributes attributes = ctx.getAttributes(dn); + return mapper.mapFromAttributes(attributes); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public T lookup(final Name dn, final ContextMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { + Object object = ctx.lookup(dn); + return mapper.mapFromContext(object); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public T lookup(final String dn, final ContextMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { + Object object = ctx.lookup(dn); + return mapper.mapFromContext(object); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public T lookup(final Name dn, final String[] attributes, final AttributesMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { + Attributes filteredAttributes = ctx.getAttributes(dn, attributes); + return mapper.mapFromAttributes(filteredAttributes); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public T lookup(final String dn, final String[] attributes, final AttributesMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { + Attributes filteredAttributes = ctx.getAttributes(dn, attributes); + return mapper.mapFromAttributes(filteredAttributes); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public T lookup(final Name dn, final String[] attributes, final ContextMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { + Attributes filteredAttributes = ctx.getAttributes(dn, attributes); + DirContextAdapter contextAdapter = new DirContextAdapter(filteredAttributes, dn); + return mapper.mapFromContext(contextAdapter); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public T lookup(final String dn, final String[] attributes, final ContextMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { + Attributes filteredAttributes = ctx.getAttributes(dn, attributes); + LdapName name = LdapUtils.newLdapName(dn); + DirContextAdapter contextAdapter = new DirContextAdapter(filteredAttributes, name); + return mapper.mapFromContext(contextAdapter); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyAttributes(final Name dn, final ModificationItem[] mods) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.modifyAttributes(dn, mods); + return null; + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyAttributes(final String dn, final ModificationItem[] mods) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.modifyAttributes(dn, mods); + return null; + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void bind(final Name dn, final Object obj, final Attributes attributes) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.bind(dn, obj, attributes); + return null; + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void bind(final String dn, final Object obj, final Attributes attributes) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.bind(dn, obj, attributes); + return null; + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void unbind(final Name dn) { + doUnbind(dn); + } + + /** + * {@inheritDoc} + */ + @Override + public void unbind(final String dn) { + doUnbind(dn); + } + + /** + * {@inheritDoc} + */ + @Override + public void unbind(final Name dn, boolean recursive) { + if (!recursive) { + doUnbind(dn); + } + else { + doUnbindRecursively(dn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void unbind(final String dn, boolean recursive) { + if (!recursive) { + doUnbind(dn); + } + else { + doUnbindRecursively(dn); + } + } + + private void doUnbind(final Name dn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.unbind(dn); + return null; + } + }); + } + + private void doUnbind(final String dn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.unbind(dn); + return null; + } + }); + } + + private void doUnbindRecursively(final Name dn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) { + deleteRecursively(ctx, LdapUtils.newLdapName(dn)); + return null; + } + }); + } + + private void doUnbindRecursively(final String dn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + deleteRecursively(ctx, LdapUtils.newLdapName(dn)); + return null; + } + }); + } + + /** + * Delete all subcontexts including the current one recursively. + * + * @param ctx The context to use for deleting. + * @param name The starting point to delete recursively. + * @throws NamingException if any error occurs + */ + protected void deleteRecursively(DirContext ctx, Name name) { + + NamingEnumeration enumeration = null; + try { + enumeration = ctx.listBindings(name); + while (enumeration.hasMore()) { + Binding binding = (Binding) enumeration.next(); + LdapName childName = LdapUtils.newLdapName(binding.getName()); + childName.addAll(0, name); + deleteRecursively(ctx, childName); + } + ctx.unbind(name); + if (LOG.isDebugEnabled()) { + LOG.debug("Entry " + name + " deleted"); + } + } + catch (javax.naming.NamingException e) { + throw LdapUtils.convertLdapException(e); + } + finally { + try { + enumeration.close(); + } + catch (Exception e) { + // Never mind this + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void rebind(final Name dn, final Object obj, final Attributes attributes) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.rebind(dn, obj, attributes); + return null; + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void rebind(final String dn, final Object obj, final Attributes attributes) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.rebind(dn, obj, attributes); + return null; + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void rename(final Name oldDn, final Name newDn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.rename(oldDn, newDn); + return null; + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void rename(final String oldDn, final String newDn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + ctx.rename(oldDn, newDn); + return null; + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void afterPropertiesSet() throws Exception { + Assert.notNull(contextSource, "Property 'contextSource' must be set."); + } + + private void closeContextAndNamingEnumeration(DirContext ctx, NamingEnumeration results) { + + closeNamingEnumeration(results); + closeContext(ctx); + } + + /** + * Close the supplied DirContext if it is not null. Swallow any exceptions, + * as this is only for cleanup. + * + * @param ctx the context to close. + */ + private void closeContext(DirContext ctx) { + if (ctx != null) { + try { + ctx.close(); + } + catch (Exception e) { + // Never mind this. + } + } + } + + /** + * Close the supplied NamingEnumeration if it is not null. Swallow any + * exceptions, as this is only for cleanup. + * + * @param results the NamingEnumeration to close. + */ + private void closeNamingEnumeration(NamingEnumeration results) { + if (results != null) { + try { + results.close(); + } + catch (Exception e) { + // Never mind this. + } + } + } + + private SearchControls getDefaultSearchControls(int searchScope, boolean returningObjFlag, String[] attrs) { + SearchControls controls = new SearchControls(); + controls.setSearchScope(searchScope); + controls.setTimeLimit(defaultTimeLimit); + controls.setCountLimit(defaultCountLimit); + controls.setReturningObjFlag(returningObjFlag); + controls.setReturningAttributes(attrs); + return controls; + } + + /** + * Make sure the returnObjFlag is set in the supplied SearchControls. Set it + * and log if it's not set. + * + * @param controls the SearchControls to check. + */ + private void assureReturnObjFlagSet(SearchControls controls) { + Assert.notNull(controls, "controls must not be null"); + if (!controls.getReturningObjFlag()) { + LOG.debug("The returnObjFlag of supplied SearchControls is not set" + + " but a ContextMapper is used - setting flag to true"); + controls.setReturningObjFlag(true); + } + } + + /** + * Do-nothing implementation of {@link DirContextProcessor}. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ + public static final class NullDirContextProcessor implements DirContextProcessor { + public void postProcess(DirContext ctx) { + // Do nothing + } + + public void preProcess(DirContext ctx) { + // Do nothing + } + } + + /** + * A {@link NameClassPairCallbackHandler} that passes the NameClassPairs + * found to a NameClassPairMapper and collects the results in a list. + * + * @author Mattias Hellborg Arthursson + */ + public final static class MappingCollectingNameClassPairCallbackHandler extends + CollectingNameClassPairCallbackHandler { + + private NameClassPairMapper mapper; + + public MappingCollectingNameClassPairCallbackHandler(NameClassPairMapper mapper) { + this.mapper = mapper; + } + + /** + * {@inheritDoc} + */ + public T getObjectFromNameClassPair(NameClassPair nameClassPair) { + try { + return mapper.mapFromNameClassPair(nameClassPair); + } + catch (javax.naming.NamingException e) { + throw LdapUtils.convertLdapException(e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public DirContextOperations lookupContext(Name dn) { + return (DirContextOperations) lookup(dn); + } + + /** + * {@inheritDoc} + */ + @Override + public DirContextOperations lookupContext(String dn) { + return (DirContextOperations) lookup(dn); + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyAttributes(DirContextOperations ctx) { + Name dn = ctx.getDn(); + if (dn != null && ctx.isUpdateMode()) { + modifyAttributes(dn, ctx.getModificationItems()); + } + else { + throw new IllegalStateException("The DirContextOperations instance needs to be properly initialized."); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void bind(DirContextOperations ctx) { + Name dn = ctx.getDn(); + if (dn != null && !ctx.isUpdateMode()) { + bind(dn, ctx, null); + } + else { + throw new IllegalStateException("The DirContextOperations instance needs to be properly initialized."); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void rebind(DirContextOperations ctx) { + Name dn = ctx.getDn(); + if (dn != null && !ctx.isUpdateMode()) { + rebind(dn, ctx, null); + } + else { + throw new IllegalStateException( + "The DirContextOperations instance needs to be properly initialized."); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean authenticate(Name base, String filter, String password) { + return authenticate(base, filter, password, + new NullAuthenticatedLdapEntryContextCallback(), + new NullAuthenticationErrorCallback()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean authenticate(String base, String filter, String password) { + return authenticate(LdapUtils.newLdapName(base), filter, password, + new NullAuthenticatedLdapEntryContextCallback(), + new NullAuthenticationErrorCallback()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean authenticate(String base, String filter, String password, + AuthenticatedLdapEntryContextCallback callback) { + return authenticate(LdapUtils.newLdapName(base), filter, password, callback, new NullAuthenticationErrorCallback()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean authenticate(Name base, String filter, String password, + final AuthenticatedLdapEntryContextCallback callback) { + return authenticate(base, filter, password, callback, new NullAuthenticationErrorCallback()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean authenticate(String base, String filter, String password, + AuthenticationErrorCallback errorCallback) { + return authenticate(LdapUtils.newLdapName(base), filter, password, new NullAuthenticatedLdapEntryContextCallback(), errorCallback); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean authenticate(Name base, String filter, String password, + final AuthenticationErrorCallback errorCallback) { + return authenticate(base, filter, password, new NullAuthenticatedLdapEntryContextCallback(), errorCallback); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean authenticate(String base, String filter, String password, + final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) { + return authenticate(LdapUtils.newLdapName(base), filter, password, callback, errorCallback); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean authenticate(Name base, String filter, String password, + final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) { + + return authenticate(base, + filter, + password, + getDefaultSearchControls(defaultSearchScope, RETURN_OBJ_FLAG, null), + callback, + errorCallback).isSuccess(); + } + + private AuthenticationStatus authenticate(Name base, + String filter, + String password, + SearchControls searchControls, + final AuthenticatedLdapEntryContextCallback callback, + final AuthenticationErrorCallback errorCallback) { + + List result = search(base, filter, searchControls, new LdapEntryIdentificationContextMapper()); + if (result.size() == 0) { + String msg = "No results found for search, base: '" + base + "'; filter: '" + filter + "'."; + LOG.info(msg); + return AuthenticationStatus.EMPTYRESULT; + } else if (result.size() > 1) { + String msg = "base: '" + base + "'; filter: '" + filter + "'."; + throw new IncorrectResultSizeDataAccessException(msg, 1, result.size()); + } + + final LdapEntryIdentification entryIdentification = result.get(0); + + try { + DirContext ctx = contextSource.getContext(entryIdentification.getAbsoluteName().toString(), password); + executeWithContext(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + callback.executeWithContext(ctx, entryIdentification); + return null; + } + }, ctx); + return AuthenticationStatus.SUCCESS; + } + catch (Exception e) { + LOG.debug("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteName() + "'", e); + errorCallback.execute(e); + return AuthenticationStatus.UNDEFINED_FAILURE; + } + } + + /** + * {@inheritDoc} + */ + @Override + public T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper mapper) { + SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); + ReturningAuthenticatedLdapEntryContext mapperCallback = + new ReturningAuthenticatedLdapEntryContext(mapper); + CollectingAuthenticationErrorCallback errorCallback = + new CollectingAuthenticationErrorCallback(); + + AuthenticationStatus authenticationStatus = authenticate(query.base(), + query.filter().encode(), + password, + searchControls, + mapperCallback, + errorCallback); + + if(errorCallback.hasError()) { + Exception error = errorCallback.getError(); + + if (error instanceof NamingException) { + throw (NamingException) error; + } else { + throw new UncategorizedLdapException(error); + } + } else if(AuthenticationStatus.EMPTYRESULT == authenticationStatus) { + throw new EmptyResultDataAccessException(1); + } else if(!authenticationStatus.isSuccess()) { + throw new AuthenticationException(); + } + + return mapperCallback.collectedObject; + } + + /** + * {@inheritDoc} + */ + @Override + public void authenticate(LdapQuery query, String password) { + authenticate(query, + password, + new NullAuthenticatedLdapEntryContextCallback()); + } + + /** + * {@inheritDoc} + */ + @Override + public T searchForObject(Name base, String filter, ContextMapper mapper) { + return searchForObject(base, + filter, + getDefaultSearchControls(defaultSearchScope, RETURN_OBJ_FLAG, ALL_ATTRIBUTES), + mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public T searchForObject(String base, String filter, ContextMapper mapper) { + return searchForObject(LdapUtils.newLdapName(base), filter, mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public T searchForObject (Name base, String filter, SearchControls searchControls, ContextMapper mapper) { + List result = search(base, filter, searchControls, mapper); + + if (result.size() == 0) { + throw new EmptyResultDataAccessException(1); + } + else if (result.size() != 1) { + throw new IncorrectResultSizeDataAccessException(1, result.size()); + } + + return result.get(0); + } + + /** + * {@inheritDoc} + */ + @Override + public T searchForObject(String base, String filter, SearchControls searchControls, ContextMapper mapper) { + return searchForObject(LdapUtils.newLdapName(base), filter, searchControls, mapper); + } + + private static final class NullAuthenticatedLdapEntryContextCallback + implements AuthenticatedLdapEntryContextCallback, AuthenticatedLdapEntryContextMapper{ + public void executeWithContext(DirContext ctx, + LdapEntryIdentification ldapEntryIdentification) { + // Do nothing + } + + @Override + public Object mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { + return null; + } + } + + private static final class NullAuthenticationErrorCallback + implements AuthenticationErrorCallback { + public void execute(Exception e) { + // Do nothing + } + } + + private static final class ReturningAuthenticatedLdapEntryContext + implements AuthenticatedLdapEntryContextCallback { + private final AuthenticatedLdapEntryContextMapper mapper; + + private T collectedObject; + + private ReturningAuthenticatedLdapEntryContext(AuthenticatedLdapEntryContextMapper mapper) { + this.mapper = mapper; + } + + /** + * {@inheritDoc} + */ + @Override + public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { + collectedObject = mapper.mapWithContext(ctx, ldapEntryIdentification); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void search(LdapQuery query, NameClassPairCallbackHandler callbackHandler) { + SearchControls searchControls = searchControlsForQuery(query, DONT_RETURN_OBJ_FLAG); + search(query.base(), + query.filter().encode(), + searchControls, + callbackHandler); + } + + /** + * {@inheritDoc} + */ + @Override + public List search(LdapQuery query, ContextMapper mapper) { + SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); + + return search(query.base(), + query.filter().encode(), + searchControls, + mapper); + + } + + private SearchControls searchControlsForQuery(LdapQuery query, boolean returnObjFlag) { + SearchControls searchControls = getDefaultSearchControls( + defaultSearchScope, + returnObjFlag, + query.attributes()); + + if(query.searchScope() != null) { + searchControls.setSearchScope(query.searchScope().getId()); + } + + if(query.countLimit() != null) { + searchControls.setCountLimit(query.countLimit()); + } + + if(query.timeLimit() != null) { + searchControls.setTimeLimit(query.timeLimit()); + } + return searchControls; + } + + /** + * {@inheritDoc} + */ + @Override + public List search(LdapQuery query, AttributesMapper mapper) { + SearchControls searchControls = searchControlsForQuery(query, DONT_RETURN_OBJ_FLAG); + + return search(query.base(), + query.filter().encode(), + searchControls, + mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public DirContextOperations searchForContext(LdapQuery query) { + return searchForObject(query, new ContextMapper() { + @Override + public DirContextOperations mapFromContext(Object ctx) throws javax.naming.NamingException { + return (DirContextOperations) ctx; + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public T searchForObject(LdapQuery query, ContextMapper mapper) { + SearchControls searchControls = searchControlsForQuery(query, DONT_RETURN_OBJ_FLAG); + + return searchForObject(query.base(), + query.filter().encode(), + searchControls, + mapper); + } + + /** + * {@inheritDoc} + */ + @Override + public T findByDn(Name dn, final Class clazz) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Reading Entry at - %s$1", dn)); + } + + // Make sure the class is OK before doing the lookup + odm.manageClass(clazz); + + T result = lookup(dn, new ContextMapper() { + @Override + public T mapFromContext(Object ctx) throws javax.naming.NamingException { + return odm.mapFromLdapDataEntry((DirContextOperations) ctx, clazz); + } + }); + + if (result == null) { + throw new OdmException(String.format("Entry %1$s does not have the required objectclasses ", dn)); + } + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Found entry - %s$1", result)); + } + + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void create(Object entry) { + Assert.notNull(entry, "Entry must not be null"); + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Creating entry - %s$1", entry)); + } + + Name id = odm.getId(entry); + if(id == null) { + id = odm.getCalculatedId(entry); + odm.setId(entry, id); + } + + Assert.notNull(id, String.format("Unable to determine id for entry %s", entry.toString())); + + DirContextAdapter context = new DirContextAdapter(id); + odm.mapToLdapDataEntry(entry, context); + + bind(context); + } + + /** + * {@inheritDoc} + */ + @Override + public void update(Object entry) { + Assert.notNull(entry, "Entry must not be null"); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Updating entry - %s$1", entry)); + } + + Name originalId = odm.getId(entry); + Name calculatedId = odm.getCalculatedId(entry); + + if(originalId != null && calculatedId != null && !originalId.equals(calculatedId)) { + // The DN has changed - remove the original entry and bind the new one + // (because other data may have changed as well + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Calculated DN of %s; of entry %s differs from explicitly specified one; %s - moving", + calculatedId, entry, originalId)); + } + + unbind(originalId); + + DirContextAdapter context = new DirContextAdapter(calculatedId); + odm.mapToLdapDataEntry(entry, context); + + bind(context); + odm.setId(entry, calculatedId); + } else { + // DN is the same, just modify the attributes + + Name id = originalId; + if(id == null) { + id = calculatedId; + odm.setId(entry, calculatedId); + } + + Assert.notNull(id, String.format("Unable to determine id for entry %s", entry.toString())); + + DirContextOperations context = lookupContext(id); + odm.mapToLdapDataEntry(entry, context); + modifyAttributes(context); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void delete(Object entry) { + Assert.notNull(entry, "Entry must not be null"); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Deleting %s$1", entry)); + } + + Name id = odm.getId(entry); + if(id == null) { + id = odm.getCalculatedId(entry); + } + + Assert.notNull(id, String.format("Unable to determine id for entry %s", entry.toString())); + unbind(id); + } + + /** + * {@inheritDoc} + */ + @Override + public List findAll(Name base, SearchControls searchControls, final Class clazz) { + return find(base, null, searchControls, clazz); + } + + /** + * {@inheritDoc} + */ + @Override + public List findAll(Class clazz) { + return findAll(LdapUtils.emptyLdapName(), + getDefaultSearchControls(defaultSearchScope, RETURN_OBJ_FLAG, ALL_ATTRIBUTES), + clazz); + } + + /** + * {@inheritDoc} + */ + @Override + public List find(Name base, Filter filter, SearchControls searchControls, final Class clazz) { + Filter finalFilter = odm.filterFor(clazz, filter); + + // Search from the root if we are not told where to search from + Name localBase = base; + if (base == null || base.size() == 0) { + localBase = LdapUtils.emptyLdapName(); + } + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Searching - base=%1$s, finalFilter=%2$s, scope=%3$s", base, finalFilter, searchControls)); + } + + List result = search(localBase, finalFilter.encode(), searchControls, new ContextMapper() { + @Override + public T mapFromContext(Object ctx) throws javax.naming.NamingException { + return odm.mapFromLdapDataEntry((DirContextOperations) ctx, clazz); + } + }); + result.remove(null); + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Found %1$s Entries - %2$s", result.size(), result)); + } + + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public List find(LdapQuery query, Class clazz) { + SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); + return find(query.base(), query.filter(), searchControls, clazz); + } + + /** + * {@inheritDoc} + */ + @Override + public T findOne(LdapQuery query, Class clazz) { + List result = find(query, clazz); + + if (result.size() == 0) { + throw new EmptyResultDataAccessException(1); + } + else if (result.size() != 1) { + throw new IncorrectResultSizeDataAccessException(1, result.size()); + } + + return result.get(0); + } + + /** + * The status of an authentication attempt. + * + * @author Rob Winch + */ + private enum AuthenticationStatus { + /** + * Authentication was successful + */ + SUCCESS(true), + /** + * The user was not found + */ + EMPTYRESULT(false), + /** + * Authentication failed for other reason + */ + UNDEFINED_FAILURE(false); + + private boolean success; + + AuthenticationStatus(boolean success) { + this.success = success; + } + + /** + * Return true if the authentication attempt was successful + * @return + */ + public boolean isSuccess() { + return success; + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/NameAwareAttribute.java b/fine-spring/src/com/fr/third/springframework/ldap/core/NameAwareAttribute.java new file mode 100644 index 000000000..daa8e0c78 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/NameAwareAttribute.java @@ -0,0 +1,351 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.ldap.InvalidNameException; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.CollectionUtils; +import com.fr.third.springframework.util.ObjectUtils; + +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapName; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * Used internally to make DirContextAdapter properly handle Names as values. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public final class NameAwareAttribute implements Attribute { + + private final String id; + private final boolean orderMatters; + private final Set values = new LinkedHashSet(); + private Map valuesAsNames = new HashMap(); + + /** + * Construct a new instance with the specified id and one value. + * @param id the attribute id + * @param value the value to start off with + */ + public NameAwareAttribute(String id, Object value) { + this(id); + values.add(value); + } + + /** + * Construct a new instance from the supplied Attribute. + * + * @param attribute the Attribute to copy. + */ + public NameAwareAttribute(Attribute attribute) { + this(attribute.getID(), attribute.isOrdered()); + try { + NamingEnumeration incomingValues = attribute.getAll(); + while(incomingValues.hasMore()) { + this.add(incomingValues.next()); + } + } catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } + + if (attribute instanceof NameAwareAttribute) { + NameAwareAttribute nameAwareAttribute = (NameAwareAttribute) attribute; + populateValuesAsNames(nameAwareAttribute, this); + } + } + + /** + * Construct a new instance with the specified id and no values. + * @param id the attribute id + */ + public NameAwareAttribute(String id) { + this(id, false); + } + + /** + * Construct a new instance with the specified id, no values and order significance as specified. + * @param id the attribute id + * @param orderMatters whether order has significance in this attribute. + */ + public NameAwareAttribute(String id, boolean orderMatters) { + this.id = id; + this.orderMatters = orderMatters; + } + + @Override + public NamingEnumeration getAll() { + return new IterableNamingEnumeration(values); + } + + @Override + public Object get() { + if(values.isEmpty()) { + return null; + } + + return values.iterator().next(); + } + + @Override + public int size() { + return values.size(); + } + + @Override + public String getID() { + return id; + } + + @Override + public boolean contains(Object attrVal) { + return values.contains(attrVal); + } + + @Override + public boolean add(Object attrVal) { + if (attrVal instanceof Name) { + initValuesAsNames(); + + Name name = LdapUtils.newLdapName((Name) attrVal); + String currentValue = valuesAsNames.get(name); + String nameAsString = name.toString(); + if(currentValue == null) { + valuesAsNames.put(name, name.toString()); + values.add(nameAsString); + return true; + } else { + if(!currentValue.equals(nameAsString)) { + values.remove(currentValue); + values.add(nameAsString); + } + + return false; + } + } + + return values.add(attrVal); + } + + public void initValuesAsNames() { + if(hasValuesAsNames()) { + return; + } + + Map newValuesAsNames = new HashMap(); + for (Object value : values) { + if (value instanceof String) { + String s = (String) value; + try { + newValuesAsNames.put(LdapUtils.newLdapName(s), s); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("This instance has values that are not valid distinguished names; " + + "cannot handle Name values", e); + } + } else if (value instanceof LdapName) { + newValuesAsNames.put((LdapName) value, value.toString()); + } else { + throw new IllegalArgumentException("This instance has non-string attribute values; " + + "cannot handle Name values"); + } + } + + this.valuesAsNames = newValuesAsNames; + } + + public boolean hasValuesAsNames() { + return !valuesAsNames.isEmpty(); + } + + @Override + public boolean remove(Object attrval) { + if (attrval instanceof Name) { + initValuesAsNames(); + + Name name = LdapUtils.newLdapName((Name) attrval); + String removedValue = valuesAsNames.remove(name); + if(removedValue != null) { + values.remove(removedValue); + + return true; + } + + return false; + } + return values.remove(attrval); + } + + @Override + public void clear() { + values.clear(); + } + + @Override + public DirContext getAttributeSyntaxDefinition() throws NamingException { + throw new UnsupportedOperationException(); + } + + @Override + public DirContext getAttributeDefinition() throws NamingException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isOrdered() { + return orderMatters; + } + + @Override + public Object get(int ix) throws NamingException { + Iterator iterator = values.iterator(); + + try { + Object value = iterator.next(); + for(int i = 0; i < ix; i++) { + value = iterator.next(); + } + + return value; + } catch (NoSuchElementException e) { + throw new IndexOutOfBoundsException("No value at index i"); + } + } + + @Override + public Object remove(int ix) { + Iterator iterator = values.iterator(); + + try { + Object value = iterator.next(); + for(int i = 0; i < ix; i++) { + value = iterator.next(); + } + + iterator.remove(); + return value; + } catch (NoSuchElementException e) { + throw new IndexOutOfBoundsException("No value at index i"); + } + } + + @Override + public void add(int ix, Object attrVal) { + throw new UnsupportedOperationException(); + } + + @Override + public Object set(int ix, Object attrVal) { + throw new UnsupportedOperationException(); + } + + @Override + public Object clone() { + return new NameAwareAttribute(this); + } + + private void populateValuesAsNames(NameAwareAttribute from, NameAwareAttribute to) { + Set> entries = from.valuesAsNames.entrySet(); + for (Map.Entry entry : entries) { + to.valuesAsNames.put(LdapUtils.newLdapName(entry.getKey()), entry.getValue()); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + NameAwareAttribute that = (NameAwareAttribute) o; + + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if(this.values.size() != that.values.size()) { + return false; + } + + if(this.orderMatters != that.orderMatters || this.size() != that.size()) { + return false; + } + + if(this.hasValuesAsNames() != that.hasValuesAsNames()) { + return false; + } + + Set myValues = this.values; + Set theirValues = that.values; + if(this.hasValuesAsNames()) { + // We have Name values - compare these to get + // syntactically correct comparison of the values + + myValues = this.valuesAsNames.keySet(); + theirValues = that.valuesAsNames.keySet(); + } + + if(orderMatters) { + Iterator thisIterator = myValues.iterator(); + Iterator thatIterator = theirValues.iterator(); + while(thisIterator.hasNext()) { + if(!ObjectUtils.nullSafeEquals(thisIterator.next(), thatIterator.next())) { + return false; + } + } + + return true; + } else { + for (Object value : myValues) { + if(!CollectionUtils.contains(theirValues.iterator(), value)) { + return false; + } + } + + return true; + } + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + + int valuesHash = 7; + Set myValues = this.values; + if(hasValuesAsNames()) { + myValues = valuesAsNames.keySet(); + } + + for (Object value : myValues) { + result += ObjectUtils.nullSafeHashCode(value); + } + result = 31 * result + valuesHash; + + return result; + } + + @Override + public String toString() { + return String.format("NameAwareAttribute; id: %s; hasValuesAsNames: %s; orderMatters: %s; values: %s", + id, hasValuesAsNames(), orderMatters, values); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/NameAwareAttributes.java b/fine-spring/src/com/fr/third/springframework/ldap/core/NameAwareAttributes.java new file mode 100644 index 000000000..a13699d3a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/NameAwareAttributes.java @@ -0,0 +1,131 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.util.Assert; + +import javax.naming.NamingEnumeration; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import java.util.HashMap; +import java.util.Map; + +/** + * Used internally to help DirContextAdapter properly handle Names as values. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public final class NameAwareAttributes implements Attributes { + private Map attributes = new HashMap(); + + /** + * Create an empty instance + */ + public NameAwareAttributes() { + + } + + /** + * Create a new instance, populated with the data from the supplied instance. + * @param attributes the instance to copy. + */ + public NameAwareAttributes(Attributes attributes) { + NamingEnumeration allAttributes = attributes.getAll(); + while(allAttributes.hasMoreElements()) { + Attribute attribute = allAttributes.nextElement(); + put(new NameAwareAttribute(attribute)); + } + } + + @Override + public boolean isCaseIgnored() { + return true; + } + + @Override + public int size() { + return attributes.size(); + } + + @Override + public NameAwareAttribute get(String attrID) { + Assert.hasLength(attrID, "Attribute ID must not be empty"); + return attributes.get(attrID.toLowerCase()); + } + + @Override + public NamingEnumeration getAll() { + return new IterableNamingEnumeration(attributes.values()); + } + + @Override + public NamingEnumeration getIDs() { + return new IterableNamingEnumeration(attributes.keySet()); + } + + @Override + public Attribute put(String attrID, Object val) { + Assert.hasLength(attrID, "Attribute ID must not be empty"); + NameAwareAttribute newAttribute = new NameAwareAttribute(attrID, val); + attributes.put(attrID.toLowerCase(), newAttribute); + + return newAttribute; + } + + @Override + public Attribute put(Attribute attr) { + Assert.notNull(attr, "Attribute must not be null"); + NameAwareAttribute newAttribute = new NameAwareAttribute(attr); + attributes.put(attr.getID().toLowerCase(), newAttribute); + + return newAttribute; + } + + @Override + public Attribute remove(String attrID) { + Assert.hasLength(attrID, "Attribute ID must not be empty"); + return attributes.remove(attrID); + } + + @Override + public Object clone() { + return new NameAwareAttributes(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + NameAwareAttributes that = (NameAwareAttributes) o; + + if (attributes != null ? !attributes.equals(that.attributes) : that.attributes != null) return false; + + return true; + } + + @Override + public int hashCode() { + return attributes != null ? attributes.hashCode() : 0; + } + + @Override + public String toString() { + return String.format("NameAwareAttribute; attributes: %s", attributes.toString()); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/NameClassPairCallbackHandler.java b/fine-spring/src/com/fr/third/springframework/ldap/core/NameClassPairCallbackHandler.java new file mode 100644 index 000000000..17026891e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/NameClassPairCallbackHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import javax.naming.NameClassPair; +import javax.naming.NamingException; + +/** + * Callback interface used by {@link LdapTemplate} search, list and listBindings + * methods. Implementations of this interface perform the actual work of + * extracting results from a single NameClassPair (a + * NameClassPair, Binding or + * SearchResult depending on the search operation) returned by an + * LDAP seach operation, such as search(), list(), and listBindings(). + * + * @author Mattias Hellborg Arthursson + */ +public interface NameClassPairCallbackHandler { + /** + * Handle one entry. This method will be called once for each entry returned + * by a search or list. + * + * @param nameClassPair + * the NameClassPair returned from the + * NamingEnumeration. + * @throws NamingException if an error occurs. + */ + void handleNameClassPair(NameClassPair nameClassPair) throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/NameClassPairMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/core/NameClassPairMapper.java new file mode 100644 index 000000000..46b04d20a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/NameClassPairMapper.java @@ -0,0 +1,44 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import javax.naming.NameClassPair; +import javax.naming.NamingException; + +/** + * Responsible for mapping NameClassPair objects to beans. + * + * @author Mattias Hellborg Arthursson + */ +public interface NameClassPairMapper { + /** + * Map NameClassPair to an Object. The supplied + * NameClassPair is one of the results from a search + * operation (search, list or listBindings). Depending on which search + * operation is being performed, the NameClassPair might be a + * SearchResult, Binding or + * NameClassPair. + * + * @param nameClassPair + * NameClassPair from a search operation. + * @return and Object built from the NameClassPair. + * @throws NamingException + * if one is encountered in the operation. + */ + T mapFromNameClassPair(NameClassPair nameClassPair) + throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/ObjectRetrievalException.java b/fine-spring/src/com/fr/third/springframework/ldap/core/ObjectRetrievalException.java new file mode 100644 index 000000000..4725b7dd3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/ObjectRetrievalException.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core; + +import com.fr.third.springframework.ldap.NamingException; + +/** + * Thrown by a {@link ContextMapperCallbackHandler} when it cannot retrieve an + * object from the given Binding. + * + * @author Ulrik Sandberg + * @since 1.2 + */ +public class ObjectRetrievalException extends NamingException { + + /** + * Create a new ObjectRetrievalException. + * + * @param msg + * the detail message + * + */ + public ObjectRetrievalException(String msg) { + super(msg); + } + + /** + * Create a new ObjectRetrievalException. + * + * @param msg + * the detail message + * @param cause + * the root cause (if any) + */ + public ObjectRetrievalException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/ParseException.java b/fine-spring/src/com/fr/third/springframework/ldap/core/ParseException.java new file mode 100644 index 000000000..52cb7ee99 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/ParseException.java @@ -0,0 +1,9 @@ +package com.fr.third.springframework.ldap.core; + +/** + * @author andrew_asa + * @date 2019-07-30. + */ +public class ParseException extends Exception{ + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/SearchExecutor.java b/fine-spring/src/com/fr/third/springframework/ldap/core/SearchExecutor.java new file mode 100644 index 000000000..0f9d60abc --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/SearchExecutor.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +/** + * Interface for delegating an actual search operation. The typical + * implementation of executeSearch would be something like: + * + *
+ * SearchExecutor executor = new SearchExecutor(){
+ *   public NamingEnumeration executeSearch(DirContext ctx) throws NamingException{
+ *     return ctx.search(dn, filter, searchControls);
+ *   }
+ * }
+ * 
+ * + * @see com.fr.third.springframework.ldap.core.LdapTemplate#search(SearchExecutor, + * NameClassPairCallbackHandler) + * + * @author Mattias Hellborg Arthursson + */ +public interface SearchExecutor { + /** + * Execute the actual search. + * + * @param ctx + * the DirContext on which to work. + * @return the NamingEnumeration resulting from the search + * operation. + * @throws NamingException + * if the search results in one. + */ + NamingEnumeration executeSearch(DirContext ctx) + throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/TokenMgrError.java b/fine-spring/src/com/fr/third/springframework/ldap/core/TokenMgrError.java new file mode 100644 index 000000000..54db330fa --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/TokenMgrError.java @@ -0,0 +1,9 @@ +package com.fr.third.springframework.ldap.core; + +/** + * @author andrew_asa + * @date 2019-07-30. + */ +public class TokenMgrError extends Exception{ + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/package.html b/fine-spring/src/com/fr/third/springframework/ldap/core/package.html new file mode 100644 index 000000000..2cb85563c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/package.html @@ -0,0 +1,8 @@ + + + +Core package of the JNDI/LDAP support. +Provides a LdapTemplate class and various callback interfaces. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/AbstractContextMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/AbstractContextMapper.java new file mode 100644 index 000000000..c08136691 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/AbstractContextMapper.java @@ -0,0 +1,58 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.ContextMapper; +import com.fr.third.springframework.ldap.core.DirContextOperations; + +/** + * Abstract superclass that may be used instead of implementing + * {@link ContextMapper} directly. Subclassing from this superclass, the + * supplied context will be automatically cast to + * DirContextOperations. Note that if you use your own + * DirObjectFactory, this implementation will fail with a + * ClassCastException. + * + * @author Mattias Hellborg Arthursson + * + */ +public abstract class AbstractContextMapper implements ContextMapper { + + /** + * {@inheritDoc} + * + * @throws ClassCastException + * if a custom DirObjectFactory implementation is + * used, causing the objects passed in be anything else than + * {@link DirContextOperations} instances. + */ + public final T mapFromContext(Object ctx) { + return doMapFromContext((DirContextOperations) ctx); + } + + /** + * Map a single DirContextOperation to an object. The + * supplied instance is the object supplied to + * {@link #mapFromContext(Object)} cast to a + * DirContextOperations. + * + * @param ctx + * the context to map to an object. + * @return an object built from the data in the context. + */ + protected abstract T doMapFromContext(DirContextOperations ctx); + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/AbstractContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/AbstractContextSource.java new file mode 100644 index 000000000..db4461d13 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/AbstractContextSource.java @@ -0,0 +1,698 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.core.JdkVersion; +import com.fr.third.springframework.ldap.UncategorizedLdapException; +import com.fr.third.springframework.ldap.core.AuthenticationSource; +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.DistinguishedName; +import com.fr.third.springframework.ldap.support.LdapEncoder; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.ObjectUtils; +import com.fr.third.springframework.util.StringUtils; + +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Hashtable; +import java.util.ListIterator; +import java.util.Map; + +/** + * Abstract implementation of the {@link ContextSource} interface. By default, + * returns an authenticated + * DirContext implementation for both read-only and + * read-write operations. To have an anonymous environment created for read-only + * operations, set the anonymousReadOnly property to + * true. + *

+ * Implementing classes need to implement + * {@link #getDirContextInstance(Hashtable)} to create a DirContext + * instance of the desired type. + *

+ * If an {@link AuthenticationSource} is set, this will be used for getting user + * principal and password for each new connection, otherwise a default one will + * be created using the specified userDn and password. + *

+ * Note: When using implementations of this class outside of a Spring + * Context it is necessary to call {@link #afterPropertiesSet()} when all + * properties are set, in order to finish up initialization. + * + * @see com.fr.third.springframework.ldap.core.LdapTemplate + * @see com.fr.third.springframework.ldap.core.support.DefaultDirObjectFactory + * @see com.fr.third.springframework.ldap.core.support.LdapContextSource + * @see com.fr.third.springframework.ldap.core.support.DirContextSource + * + * @author Mattias Hellborg Arthursson + * @author Adam Skogman + * @author Ulrik Sandberg + */ +public abstract class AbstractContextSource implements BaseLdapPathContextSource, InitializingBean { + + private static final Class DEFAULT_CONTEXT_FACTORY + = com.sun.jndi.ldap.LdapCtxFactory.class; + + private static final Class DEFAULT_DIR_OBJECT_FACTORY = DefaultDirObjectFactory.class; + private static final boolean DONT_DISABLE_POOLING = false; + private static final boolean EXPLICITLY_DISABLE_POOLING = true; + private static final int DEFAULT_BUFFER_SIZE = 1024; + + private Class dirObjectFactory = DEFAULT_DIR_OBJECT_FACTORY; + + private Class contextFactory = DEFAULT_CONTEXT_FACTORY; + + private LdapName base = LdapUtils.emptyLdapName(); + + /** + * @deprecated use {@link #getUserDn()} and {@link #setUserDn(String)} instead + */ + @Deprecated + protected String userDn = ""; + + /** + * @deprecated use {@link #getPassword()} and {@link #setPassword(String)} instead + */ + @Deprecated + protected String password = ""; + + private String[] urls; + + private boolean pooled = false; + + private Hashtable baseEnv = new Hashtable(); + + private Hashtable anonymousEnv; + + private AuthenticationSource authenticationSource; + + private boolean cacheEnvironmentProperties = true; + + private boolean anonymousReadOnly = false; + + private String referral = null; + + private static final Logger LOG = LoggerFactory.getLogger(AbstractContextSource.class); + + public static final String SUN_LDAP_POOLING_FLAG = "com.sun.jndi.ldap.connect.pool"; + + private static final String JDK_142 = "1.4.2"; + + private DirContextAuthenticationStrategy authenticationStrategy = new SimpleDirContextAuthenticationStrategy(); + + public DirContext getContext(String principal, String credentials) { + // This method is typically called for authentication purposes, which means that we + // should explicitly disable pooling in case passwords are changed (LDAP-183). + return doGetContext(principal, credentials, EXPLICITLY_DISABLE_POOLING); + } + + private DirContext doGetContext(String principal, String credentials, boolean explicitlyDisablePooling) { + Hashtable env = getAuthenticatedEnv(principal, credentials); + if(explicitlyDisablePooling) { + env.remove(SUN_LDAP_POOLING_FLAG); + } + + DirContext ctx = createContext(env); + + try { + authenticationStrategy.processContextAfterCreation(ctx, principal, credentials); + return ctx; + } + catch (NamingException e) { + closeContext(ctx); + throw LdapUtils.convertLdapException(e); + } + } + + /* + * (non-Javadoc) + * + * @see com.fr.third.springframework.ldap.core.ContextSource#getReadOnlyContext() + */ + public DirContext getReadOnlyContext() { + if (!anonymousReadOnly) { + return doGetContext( + authenticationSource.getPrincipal(), + authenticationSource.getCredentials(), + DONT_DISABLE_POOLING); + } + else { + return createContext(getAnonymousEnv()); + } + } + + /* + * (non-Javadoc) + * + * @see com.fr.third.springframework.ldap.core.ContextSource#getReadWriteContext() + */ + public DirContext getReadWriteContext() { + return doGetContext( + authenticationSource.getPrincipal(), + authenticationSource.getCredentials(), + DONT_DISABLE_POOLING); + } + + /** + * Default implementation of setting the environment up to be authenticated. + * This method should typically NOT be overridden; any customization to the + * authentication mechanism should be managed by setting a different + * {@link DirContextAuthenticationStrategy} on this instance. + * + * @param env the environment to modify. + * @param principal the principal to authenticate with. + * @param credentials the credentials to authenticate with. + * @see DirContextAuthenticationStrategy + * @see #setAuthenticationStrategy(DirContextAuthenticationStrategy) + */ + protected void setupAuthenticatedEnvironment(Hashtable env, String principal, String credentials) { + try { + authenticationStrategy.setupEnvironment(env, principal, credentials); + } + catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } + } + + /** + * Close the context and swallow any exceptions. + * + * @param ctx the DirContext to close. + */ + private void closeContext(DirContext ctx) { + if (ctx != null) { + try { + ctx.close(); + } + catch (Exception e) { + LOG.debug("Exception closing context", e); + } + } + } + + /** + * Assemble a valid url String from all registered urls to add as + * PROVIDER_URL to the environment. + * + * @param ldapUrls all individual url Strings. + * @return the full url String + */ + public String assembleProviderUrlString(String[] ldapUrls) { + StringBuilder providerUrlBuffer = new StringBuilder(DEFAULT_BUFFER_SIZE); + for (String ldapUrl : ldapUrls) { + providerUrlBuffer.append(ldapUrl); + if (!base.isEmpty()) { + if (!ldapUrl.endsWith("/")) { + providerUrlBuffer.append("/"); + } + } + providerUrlBuffer.append(formatForUrl(base)); + providerUrlBuffer.append(' '); + } + return providerUrlBuffer.toString().trim(); + } + + static String formatForUrl(LdapName ldapName) { + StringBuilder sb = new StringBuilder(); + ListIterator it = ldapName.getRdns().listIterator(ldapName.size()); + while (it.hasPrevious()) { + Rdn component = it.previous(); + + Attributes attributes = component.toAttributes(); + + // Loop through all attribute of the rdn (usually just one, but more are supported by RFC) + NamingEnumeration allAttributes = attributes.getAll(); + while(allAttributes.hasMoreElements()) { + Attribute oneAttribute = allAttributes.nextElement(); + String encodedAttributeName = nameEncodeForUrl(oneAttribute.getID()); + + // Loop through all values of the attribute (usually just one, but more are supported by RFC) + NamingEnumeration allValues; + try { + allValues = oneAttribute.getAll(); + } catch (NamingException e) { + throw new UncategorizedLdapException("Unexpected error occurred formatting base URL", e); + } + + while(allValues.hasMoreElements()) { + sb.append(encodedAttributeName).append('='); + + Object oneValue = allValues.nextElement(); + if (oneValue instanceof String) { + String oneString = (String) oneValue; + sb.append(nameEncodeForUrl(oneString)); + } else { + throw new IllegalArgumentException("Binary attributes not supported for base URL"); + } + + if(allValues.hasMoreElements()) { + sb.append('+'); + } + } + if(allAttributes.hasMoreElements()) { + sb.append('+'); + } + } + + if(it.hasPrevious()) { + sb.append(','); + } + } + return sb.toString(); + } + + static String nameEncodeForUrl(String value) { + try { + String ldapEncoded = LdapEncoder.nameEncode(value); + URI valueUri = new URI(null, null, ldapEncoded, null); + return valueUri.toString(); + } catch (URISyntaxException e) { + throw new UncategorizedLdapException("This really shouldn't happen - report this", e); + } + } + + /** + * Set the base suffix from which all operations should origin. If a base + * suffix is set, you will not have to (and, indeed, must not) specify the + * full distinguished names in any operations performed. + * + * @param base the base suffix. + */ + public void setBase(String base) { + if (base != null) { + this.base = LdapUtils.newLdapName(base); + } else { + this.base = LdapUtils.emptyLdapName(); + } + } + + /** + * @deprecated {@link DistinguishedName} and associated classes and methods are deprecated as of 2.0. + */ + @Override + public DistinguishedName getBaseLdapPath() { + return new DistinguishedName(base); + } + + @Override + public LdapName getBaseLdapName() { + return (LdapName) base.clone(); + } + + @Override + public String getBaseLdapPathAsString() { + return getBaseLdapName().toString(); + } + + /** + * Create a DirContext using the supplied environment. + * + * @param environment the LDAP environment to use when creating the + * DirContext. + * @return a new DirContext implementation initialized with the supplied + * environment. + */ + protected DirContext createContext(Hashtable environment) { + DirContext ctx = null; + + try { + ctx = getDirContextInstance(environment); + + if (LOG.isInfoEnabled()) { + Hashtable ctxEnv = ctx.getEnvironment(); + String ldapUrl = (String) ctxEnv.get(Context.PROVIDER_URL); + LOG.debug("Got Ldap context on server '" + ldapUrl + "'"); + } + + return ctx; + } + catch (NamingException e) { + closeContext(ctx); + throw LdapUtils.convertLdapException(e); + } + } + + /** + * Set the context factory. Default is com.sun.jndi.ldap.LdapCtxFactory. + * + * @param contextFactory the context factory used when creating Contexts. + */ + public void setContextFactory(Class contextFactory) { + this.contextFactory = contextFactory; + } + + /** + * Get the context factory. + * + * @return the context factory used when creating Contexts. + */ + public Class getContextFactory() { + return contextFactory; + } + + /** + * Set the DirObjectFactory to use. Default is + * {@link DefaultDirObjectFactory}. The specified class needs to be an + * implementation of javax.naming.spi.DirObjectFactory. Note: Setting + * this value to null may have cause connection leaks when using + * ContextMapper methods in LdapTemplate. + * + * @param dirObjectFactory the DirObjectFactory to be used. Null means that + * no DirObjectFactory will be used. + */ + public void setDirObjectFactory(Class dirObjectFactory) { + this.dirObjectFactory = dirObjectFactory; + } + + /** + * Get the DirObjectFactory to use. + * + * @return the DirObjectFactory to be used. null means that no + * DirObjectFactory will be used. + */ + public Class getDirObjectFactory() { + return dirObjectFactory; + } + + /** + * Checks that all necessary data is set and that there is no compatibility + * issues, after which the instance is initialized. Note that you need to + * call this method explicitly after setting all desired properties if using + * the class outside of a Spring Context. + */ + public void afterPropertiesSet() { + if (ObjectUtils.isEmpty(urls)) { + throw new IllegalArgumentException("At least one server url must be set"); + } + + if (!base.isEmpty() && getJdkVersion().compareTo(JDK_142) < 0) { + throw new IllegalArgumentException("Base path is not supported for JDK versions < 1.4.2"); + } + + if (authenticationSource == null) { + LOG.debug("AuthenticationSource not set - " + "using default implementation"); + if (!StringUtils.hasText(userDn)) { + LOG.info("Property 'userDn' not set - " + "anonymous context will be used for read-write operations"); + } + else if (!StringUtils.hasText(password)) { + LOG.info("Property 'password' not set - " + "blank password will be used"); + } + authenticationSource = new SimpleAuthenticationSource(); + } + + if (cacheEnvironmentProperties) { + anonymousEnv = setupAnonymousEnv(); + } + } + + @SuppressWarnings("deprecation") + private Hashtable setupAnonymousEnv() { + if (pooled) { + baseEnv.put(SUN_LDAP_POOLING_FLAG, "true"); + LOG.debug("Using LDAP pooling."); + } + else { + baseEnv.remove(SUN_LDAP_POOLING_FLAG); + LOG.debug("Not using LDAP pooling"); + } + + Hashtable env = new Hashtable(baseEnv); + + env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory.getName()); + env.put(Context.PROVIDER_URL, assembleProviderUrlString(urls)); + + if (dirObjectFactory != null) { + env.put(Context.OBJECT_FACTORIES, dirObjectFactory.getName()); + } + + if (StringUtils.hasText(referral)) { + env.put(Context.REFERRAL, referral); + } + + if (!base.isEmpty()) { + // Save the base path for use in the DefaultDirObjectFactory. + env.put(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY, base); + } + + LOG.debug("Trying provider Urls: " + assembleProviderUrlString(urls)); + + return env; + } + + /** + * Set the password (credentials) to use for getting authenticated contexts. + * + * @param password the password. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Gets the password (credentials) to use for getting authenticated contexts. + * @return the password + */ + public String getPassword() { + return password; + } + + /** + * Set the user distinguished name (principal) to use for getting + * authenticated contexts. + * + * @param userDn the user distinguished name. + */ + public void setUserDn(String userDn) { + this.userDn = userDn; + } + + /** + * Gets the user distinguished name (principal) to use for getting + * authenticated contexts. + * + * @return the user distinguished name. + */ + public String getUserDn() { + return this.userDn; + } + + /** + * Set the urls of the LDAP servers. Use this method if several servers are + * required. + * + * @param urls the urls of all servers. + */ + public void setUrls(String[] urls) { + this.urls = urls.clone(); + } + + /** + * Get the urls of the LDAP servers. + * + * @return the urls of all servers. + */ + public String[] getUrls() { + return urls.clone(); + } + + /** + * Set the url of the LDAP server. Utility method if only one server is + * used. + * + * @param url the url of the LDAP server. + */ + public void setUrl(String url) { + this.urls = new String[] { url }; + } + + /** + * Set whether the pooling flag should be set, enabling the built-in LDAP + * connection pooling. Default is false. The built-in LDAP + * connection pooling suffers from a number of deficiencies, e.g. no + * connection validation. Also, enabling this flag when using TLS + * connections will explicitly not work. Consider using the Spring LDAP + * PoolingContextSource as an alternative instead of enabling + * this flag. + *

+ * Note that since LDAP pooling is system wide, full configuration of this + * needs be done using system parameters as specified in the LDAP/JNDI + * documentation. Also note, that pooling is done on user dn basis, i.e. + * each individually authenticated connection will be pooled separately. + * This means that LDAP pooling will be most efficient using anonymous + * connections or connections authenticated using one single system user. + * + * @param pooled whether Contexts should be pooled. + */ + public void setPooled(boolean pooled) { + this.pooled = pooled; + } + + /** + * Get whether the pooling flag should be set. + * + * @return whether Contexts should be pooled. + */ + public boolean isPooled() { + return pooled; + } + + /** + * If any custom environment properties are needed, these can be set using + * this method. + * + * @param baseEnvironmentProperties the base environment properties that should always be used when + * creating new Context instances. + */ + public void setBaseEnvironmentProperties(Map baseEnvironmentProperties) { + this.baseEnv = new Hashtable(baseEnvironmentProperties); + } + + String getJdkVersion() { + return JdkVersion.getJavaVersion(); + } + + protected Hashtable getAnonymousEnv() { + if (cacheEnvironmentProperties) { + return anonymousEnv; + } + else { + return setupAnonymousEnv(); + } + } + + protected Hashtable getAuthenticatedEnv(String principal, String credentials) { + // The authenticated environment should always be rebuilt. + Hashtable env = new Hashtable(getAnonymousEnv()); + setupAuthenticatedEnvironment(env, principal, credentials); + return env; + } + + /** + * Set the authentication source to use when retrieving user principal and + * credentials. + * + * @param authenticationSource the {@link AuthenticationSource} that will + * provide user info. + */ + public void setAuthenticationSource(AuthenticationSource authenticationSource) { + this.authenticationSource = authenticationSource; + } + + /** + * Get the authentication source. + * + * @return the {@link AuthenticationSource} that will provide user info. + */ + public AuthenticationSource getAuthenticationSource() { + return authenticationSource; + } + + /** + * Set whether environment properties should be cached between requsts for + * anonymous environment. Default is true; setting this + * property to false causes the environment Hashmap to be + * rebuilt from the current property settings of this instance between each + * request for an anonymous environment. + * + * @param cacheEnvironmentProperties true causes that the + * anonymous environment properties should be cached, false + * causes the Hashmap to be rebuilt for each request. + */ + public void setCacheEnvironmentProperties(boolean cacheEnvironmentProperties) { + this.cacheEnvironmentProperties = cacheEnvironmentProperties; + } + + /** + * Set whether an anonymous environment should be used for read-only + * operations. Default is false. + * + * @param anonymousReadOnly true if an anonymous environment + * should be used for read-only operations, false otherwise. + */ + public void setAnonymousReadOnly(boolean anonymousReadOnly) { + this.anonymousReadOnly = anonymousReadOnly; + } + + /** + * Get whether an anonymous environment should be used for read-only + * operations. + * + * @return true if an anonymous environment should be used for + * read-only operations, false otherwise. + */ + public boolean isAnonymousReadOnly() { + return anonymousReadOnly; + } + + /** + * Set the {@link DirContextAuthenticationStrategy} to use for preparing the + * environment and processing the created DirContext instances. + * + * @param authenticationStrategy the + * {@link DirContextAuthenticationStrategy} to use; default is + * {@link SimpleDirContextAuthenticationStrategy}. + */ + public void setAuthenticationStrategy(DirContextAuthenticationStrategy authenticationStrategy) { + this.authenticationStrategy = authenticationStrategy; + } + + /** + * Set the method to handle referrals. Default is 'ignore'; setting this + * flag to 'follow' will enable referrals to be automatically followed. Note + * that this might require particular name server setup in order to work + * (the referred URLs will need to be automatically found using standard DNS + * resolution). + * @param referral the value to set the system property + * Context.REFERRAL to, customizing the way that referrals are + * handled. + */ + public void setReferral(String referral) { + this.referral = referral; + } + + /** + * Implement in subclass to create a DirContext of the desired type (e.g. + * InitialDirContext or InitialLdapContext). + * + * @param environment the environment to use when creating the instance. + * @return a new DirContext instance. + * @throws NamingException if one is encountered when creating the instance. + */ + protected abstract DirContext getDirContextInstance(Hashtable environment) throws NamingException; + + class SimpleAuthenticationSource implements AuthenticationSource { + + public String getPrincipal() { + return userDn; + } + + public String getCredentials() { + return password; + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/AbstractTlsDirContextAuthenticationStrategy.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/AbstractTlsDirContextAuthenticationStrategy.java new file mode 100755 index 000000000..cdd237316 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/AbstractTlsDirContextAuthenticationStrategy.java @@ -0,0 +1,206 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.UncategorizedLdapException; +import com.fr.third.springframework.ldap.core.DirContextProxy; +import com.fr.third.springframework.ldap.support.LdapUtils; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; +import javax.naming.ldap.StartTlsRequest; +import javax.naming.ldap.StartTlsResponse; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Hashtable; + +/** + * Abstract superclass for {@link DirContextAuthenticationStrategy} + * implementations that apply TLS security to the connections. The supported TLS + * behavior differs between servers. E.g., some servers expect the TLS + * connection be shut down gracefully before the actual target context is + * closed, whereas other servers do not support that. The + * shutdownTlsGracefully property controls this behavior; the + * property defaults to false. + *

+ * The SSLSocketFactory used for TLS negotiation can be customized + * using the sslSocketFactory property. This allows for example a + * socket factory that can load the keystore/truststore using the Spring + * Resource abstraction. This provides a much more Spring-like strategy for + * configuring PKI credentials for authentication, in addition to allowing + * application-specific keystores and truststores running in the same JVM. + *

+ * In some rare occasions there is a need to supply a + * HostnameVerifier to the TLS processing instructions in order to + * have the returned certificate properly validated. If a + * HostnameVerifier is supplied to + * {@link #setHostnameVerifier(HostnameVerifier)}, that will be applied to the + * processing. + *

+ * For further information regarding TLS, refer to this + * page. + *

+ * NB: TLS negotiation is an expensive process, which is why you will + * most likely want to use connection pooling, to make sure new connections are + * not created for each individual request. It is imperative however, that the + * built-in LDAP connection pooling is not used in combination with the TLS + * AuthenticationStrategy implementations - this will not work. You should use + * the Spring LDAP PoolingContextSource instead. + * + * @author Mattias Hellborg Arthursson + */ +public abstract class AbstractTlsDirContextAuthenticationStrategy implements DirContextAuthenticationStrategy { + + /** Hostname verifier to use for cert subject validation */ + private HostnameVerifier hostnameVerifier; + + /** Flag to cause graceful shutdown required by some LDAP DSAs */ + private boolean shutdownTlsGracefully = false; + + /** SSL socket factory to use for startTLS negotiation */ + private SSLSocketFactory sslSocketFactory; + + /** + * Specify whether the TLS should be shut down gracefully before the target + * context is closed. Defaults to false. + * + * @param shutdownTlsGracefully true to shut down the TLS + * connection explicitly, false closes the target context + * immediately. + */ + public void setShutdownTlsGracefully(boolean shutdownTlsGracefully) { + this.shutdownTlsGracefully = shutdownTlsGracefully; + } + + /** + * Set the optional + * HostnameVerifier to use for verifying incoming certificates. Defaults to null + * , meaning that the default hostname verification will take place. + * + * @param hostnameVerifier The HostnameVerifier to use, if any. + */ + public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + } + + /** + * Sets the optional SSL socket factory used for startTLS negotiation. + * Defaults to null to indicate that the default socket factory + * provided by the underlying JSSE provider should be used. + * @param sslSocketFactory SSL socket factory to use, if any. + */ + public void setSslSocketFactory(final SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + } + + /* (non-Javadoc) + * @see com.fr.third.springframework.ldap.core.support.DirContextAuthenticationStrategy#setupEnvironment(java.util.Hashtable, java.lang.String, java.lang.String) + */ + public final void setupEnvironment(Hashtable env, String userDn, String password) { + // Nothing to do in this implementation - authentication should take + // place after TLS has been negotiated. + } + + /* (non-Javadoc) + * @see com.fr.third.springframework.ldap.core.support.DirContextAuthenticationStrategy#processContextAfterCreation(javax.naming.directory.DirContext, java.lang.String, java.lang.String) + */ + public final DirContext processContextAfterCreation(DirContext ctx, String userDn, String password) + throws NamingException { + + if (ctx instanceof LdapContext) { + final LdapContext ldapCtx = (LdapContext) ctx; + final StartTlsResponse tlsResponse = (StartTlsResponse) ldapCtx.extendedOperation(new StartTlsRequest()); + try { + if (hostnameVerifier != null) { + tlsResponse.setHostnameVerifier(hostnameVerifier); + } + tlsResponse.negotiate(sslSocketFactory); // If null, the default SSL socket factory is used + applyAuthentication(ldapCtx, userDn, password); + + if (shutdownTlsGracefully) { + // Wrap the target context in a proxy to intercept any calls + // to 'close', so that we can shut down the TLS connection + // gracefully first. + return (DirContext) Proxy.newProxyInstance(DirContextProxy.class.getClassLoader(), new Class[] { + LdapContext.class, DirContextProxy.class }, new TlsAwareDirContextProxy(ldapCtx, + tlsResponse)); + } + else { + return ctx; + } + } + catch (IOException e) { + LdapUtils.closeContext(ctx); + throw new UncategorizedLdapException("Failed to negotiate TLS session", e); + } + } + else { + throw new IllegalArgumentException( + "Processed Context must be an LDAPv3 context, i.e. an LdapContext implementation"); + } + + } + + /** + * Apply the actual authentication to the specified LdapContext + * . Typically, this will involve adding stuff to the environment. + * + * @param ctx the LdapContext instance. + * @param userDn the user dn of the user to authenticate. + * @param password the password of the user to authenticate. + * @throws NamingException if any error occurs. + */ + protected abstract void applyAuthentication(LdapContext ctx, String userDn, String password) throws NamingException; + + private static final class TlsAwareDirContextProxy implements DirContextProxy, InvocationHandler { + + private static final String GET_TARGET_CONTEXT_METHOD_NAME = "getTargetContext"; + + private static final String CLOSE_METHOD_NAME = "close"; + + private final LdapContext target; + + private final StartTlsResponse tlsResponse; + + public TlsAwareDirContextProxy(LdapContext target, StartTlsResponse tlsResponse) { + this.target = target; + this.tlsResponse = tlsResponse; + } + + public DirContext getTargetContext() { + return target; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals(CLOSE_METHOD_NAME)) { + tlsResponse.close(); + return method.invoke(target, args); + } + else if (method.getName().equals(GET_TARGET_CONTEXT_METHOD_NAME)) { + return target; + } + else { + return method.invoke(target, args); + } + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/AggregateDirContextProcessor.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/AggregateDirContextProcessor.java new file mode 100644 index 000000000..5d328b358 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/AggregateDirContextProcessor.java @@ -0,0 +1,85 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.DirContextProcessor; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * Manages a sequence of {@link DirContextProcessor} instances. Applies + * {@link #preProcess(DirContext)} and {@link #postProcess(DirContext)} + * respectively in sequence on the managed objects. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class AggregateDirContextProcessor implements DirContextProcessor { + + private List dirContextProcessors = new LinkedList(); + + /** + * Add the supplied DirContextProcessor to the list of managed objects. + * + * @param processor + * the DirContextpProcessor to add. + */ + public void addDirContextProcessor(DirContextProcessor processor) { + dirContextProcessors.add(processor); + } + + /** + * Get the list of managed {@link DirContextProcessor} instances. + * + * @return the managed list of {@link DirContextProcessor} instances. + */ + public List getDirContextProcessors() { + return dirContextProcessors; + } + + /** + * Set the list of managed {@link DirContextProcessor} instances. + * + * @param dirContextProcessors + * the list of {@link DirContextProcessor} instances to set. + */ + public void setDirContextProcessors(List dirContextProcessors) { + this.dirContextProcessors = new ArrayList(dirContextProcessors); + } + + /* + * @see com.fr.third.springframework.ldap.core.DirContextProcessor#preProcess(javax.naming.directory.DirContext) + */ + public void preProcess(DirContext ctx) throws NamingException { + for (DirContextProcessor processor : dirContextProcessors) { + processor.preProcess(ctx); + } + } + + /* + * @see com.fr.third.springframework.ldap.core.DirContextProcessor#postProcess(javax.naming.directory.DirContext) + */ + public void postProcess(DirContext ctx) throws NamingException { + for (DirContextProcessor processor : dirContextProcessors) { + processor.postProcess(ctx); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapNameAware.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapNameAware.java new file mode 100644 index 000000000..5da224b55 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapNameAware.java @@ -0,0 +1,49 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import javax.naming.ldap.LdapName; + +/** + * Interface to be implemented by classes that want to have access to the base + * context used in the active ContextSource. There are several + * cases in which services may want to have access to the base context, e.g. + * when working with groups (groupOfNames objectclass), in which + * case the full DN of each group member needs to be specified in the attribute + * value. + *

+ * If a class implements this interface and a + * {@link BaseLdapPathBeanPostProcessor} is defined in the + * ApplicationContext, the default base path will automatically + * passed to the {@link #setBaseLdapPath(javax.naming.ldap.LdapName)} method on + * initialization. + *

+ * NB:The ContextSource needs to be a subclass of + * {@link AbstractContextSource} for this mechanism to work. + * + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public interface BaseLdapNameAware { + /** + * Set the base LDAP path specified in the current + * ApplicationContext. + * @param baseLdapPath the base path used in the ContextSource + */ + void setBaseLdapPath(LdapName baseLdapPath); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathAware.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathAware.java new file mode 100644 index 000000000..167f564de --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathAware.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.DistinguishedName; + +/** + * Interface to be implemented by classes that want to have access to the base + * context used in the active ContextSource. There are several + * cases in which services may want to have access to the base context, e.g. + * when working with groups (groupOfNames objectclass), in which + * case the full DN of each group member needs to be specified in the attribute + * value. + *

+ * If a class implements this interface and a + * {@link BaseLdapPathBeanPostProcessor} is defined in the + * ApplicationContext, the default base path will automatically + * passed to the {@link #setBaseLdapPath(DistinguishedName)} method on + * initialization. + *

+ * NB:The ContextSource needs to be a subclass of + * {@link AbstractContextSource} for this mechanism to work. + * + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + * @deprecated {@link DistinguishedName} and associated classes and methods are deprecated as of 2.0. + * Use {@link BaseLdapNameAware} instead. + */ +public interface BaseLdapPathAware { + + /** + * Set the base LDAP path specified in the current + * ApplicationContext. + * @param baseLdapPath the base path used in the ContextSource + */ + void setBaseLdapPath(DistinguishedName baseLdapPath); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathBeanPostProcessor.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathBeanPostProcessor.java new file mode 100644 index 000000000..25493f0c9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathBeanPostProcessor.java @@ -0,0 +1,183 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.beans.factory.NoSuchBeanDefinitionException; +import com.fr.third.springframework.beans.factory.config.BeanPostProcessor; +import com.fr.third.springframework.context.ApplicationContext; +import com.fr.third.springframework.context.ApplicationContextAware; +import com.fr.third.springframework.core.Ordered; +import com.fr.third.springframework.ldap.core.DistinguishedName; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.StringUtils; + +import javax.naming.ldap.LdapName; +import java.util.Collection; + +/** + * This BeanPostProcessor checks each bean if it implements + * {@link BaseLdapNameAware} or {@link BaseLdapPathAware}. + * If it does, the default context base LDAP path will be determined, + * and that value will be injected to the {@link BaseLdapNameAware#setBaseLdapPath(javax.naming.ldap.LdapName)} + * or {@link BaseLdapPathAware#setBaseLdapPath(DistinguishedName)} method of the + * processed bean. + *

+ * If the baseLdapPath property of this + * BeanPostProcessor is set, that value will be used. Otherwise, in + * order to determine which base LDAP path to supply to the instance the + * ApplicationContext is searched for any beans that are + * implementations of {@link BaseLdapPathSource}. If one single occurrence is + * found, that instance is queried for its base path, and that is what will be + * injected. If more than one {@link BaseLdapPathSource} instance is configured + * in the ApplicationContext, the name of the one to use will need + * to be specified to the baseLdapPathSourceName property; + * otherwise the post processing will fail. If no {@link BaseLdapPathSource} + * implementing bean is found in the context and the basePath + * property is not set, post processing will also fail. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class BaseLdapPathBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware, Ordered { + + private ApplicationContext applicationContext; + + private LdapName basePath; + + private String baseLdapPathSourceName; + + private int order = Ordered.LOWEST_PRECEDENCE; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) { + if(bean instanceof BaseLdapNameAware) { + BaseLdapNameAware baseLdapNameAware = (BaseLdapNameAware) bean; + + if (basePath != null) { + baseLdapNameAware.setBaseLdapPath(LdapUtils.newLdapName(basePath)); + } + else { + BaseLdapPathSource ldapPathSource = getBaseLdapPathSourceFromApplicationContext(); + baseLdapNameAware.setBaseLdapPath(LdapUtils.newLdapName(ldapPathSource.getBaseLdapName())); + } + } else if (bean instanceof BaseLdapPathAware) { + BaseLdapPathAware baseLdapPathAware = (BaseLdapPathAware) bean; + + if (basePath != null) { + baseLdapPathAware.setBaseLdapPath(new DistinguishedName(basePath)); + } + else { + BaseLdapPathSource ldapPathSource = getBaseLdapPathSourceFromApplicationContext(); + baseLdapPathAware.setBaseLdapPath(ldapPathSource.getBaseLdapPath().immutableDistinguishedName()); + } + } + return bean; + } + + BaseLdapPathSource getBaseLdapPathSourceFromApplicationContext() { + if (StringUtils.hasLength(baseLdapPathSourceName)) { + return applicationContext.getBean(baseLdapPathSourceName, BaseLdapPathSource.class); + } + + Collection beans = applicationContext.getBeansOfType(BaseLdapPathSource.class).values(); + if (beans.isEmpty()) { + throw new NoSuchBeanDefinitionException("No BaseLdapPathSource implementation definition found"); + } else if (beans.size() == 1) { + return beans.iterator().next(); + } else { + BaseLdapPathSource found = null; + + // Try to find the correct one + for (BaseLdapPathSource bean : beans) { + if(bean instanceof AbstractContextSource) { + if(found != null) { + // More than one found - nothing much to do. + throw new NoSuchBeanDefinitionException( + "More than BaseLdapPathSource implementation definition found in current ApplicationContext; " + + "unable to determine the one to use. Please specify 'baseLdapPathSourceName'"); + } + + found = bean; + } + } + + if(found == null) { + throw new NoSuchBeanDefinitionException( + "More than BaseLdapPathSource implementation definition found in current ApplicationContext; " + + "unable to determine the one to use (one of them should be an AbstractContextSource instance). " + + "Please specify 'baseLdapPathSourceName'"); + } + + return found; + } + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + // Do nothing for this implementation + return bean; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * Set the base path to be injected in all {@link BaseLdapPathAware} beans. + * If this property is not set, the default base path will be determined + * from any defined {@link BaseLdapPathSource} instances available in the + * ApplicationContext. + * + * @param basePath the base path. + * @deprecated {@link DistinguishedName} and associated classes and methods are deprecated as of 2.0. + */ + public void setBasePath(DistinguishedName basePath) { + this.basePath = LdapUtils.newLdapName(basePath); + } + + public void setBasePath(String basePath) { + this.basePath = LdapUtils.newLdapName(basePath); + } + + /** + * Set the name of the ContextSource bean to use for getting + * the base path. This method is typically useful if several ContextSource + * instances have been configured. + * + * @param contextSourceName the name of the ContextSource bean + * to use for determining the base path. + */ + public void setBaseLdapPathSourceName(String contextSourceName) { + this.baseLdapPathSourceName = contextSourceName; + } + + /** + * Set the order value of this object for sorting purposes. + * + * @param order the order of this instance. Defaults to Ordered.LOWEST_PRECEDENCE. + * @see Ordered + * @since 1.3.2 + */ + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return order; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathContextSource.java new file mode 100644 index 000000000..add280c0e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathContextSource.java @@ -0,0 +1,28 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.ContextSource; + +/** + * Interface to be implemented by ContextSources that are capable + * of providing the base LDAP path. + * + * @author Mattias Hellborg Arthursson + */ +public interface BaseLdapPathContextSource extends ContextSource, BaseLdapPathSource { + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathSource.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathSource.java new file mode 100644 index 000000000..3b4e2bc32 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/BaseLdapPathSource.java @@ -0,0 +1,59 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.DistinguishedName; + +import javax.naming.ldap.LdapName; + +/** + * Implementations of this interface are capable of providing a base LDAP path. + * The base LDAP path is the root path to which all LDAP operations performed on + * a particular context are relative. + * + * @see ContextSource + * + * @author Mattias Hellborg Arthursson + */ +public interface BaseLdapPathSource { + /** + * Get the base LDAP path as a {@link DistinguishedName}. + * + * @return the base LDAP path as a {@link DistinguishedName}. The path will + * be empty if no base path is specified. + * @deprecated {@link DistinguishedName} and associated classes and methods are deprecated as of 2.0. + * Use {@link #getBaseLdapName()} instead. + */ + DistinguishedName getBaseLdapPath(); + + /** + * Get the base LDAP path as a {@link LdapName}. + * + * @return the base LDAP path as a {@link LdapName}. The path will + * be empty if no base path is specified. + * @since 2.0 + */ + LdapName getBaseLdapName(); + + /** + * Get the base LDAP path as a String. + * + * @return the base LDAP path as a An empty String will be returned if no + * base path is specified. + */ + String getBaseLdapPathAsString(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/ContextMapperCallbackHandlerWithControls.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/ContextMapperCallbackHandlerWithControls.java new file mode 100644 index 000000000..8e9dcf2fc --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/ContextMapperCallbackHandlerWithControls.java @@ -0,0 +1,71 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.ContextMapperCallbackHandler; +import com.fr.third.springframework.ldap.core.ObjectRetrievalException; + +import javax.naming.Binding; +import javax.naming.NameClassPair; +import javax.naming.NamingException; +import javax.naming.ldap.HasControls; + +/** + * Currently only per request controls can be inspected via the post process + * method on a context processor. If a request control gives a different value + * for each search result, then this cannot be inspected using the existing + * support classes. An example control that requires this feature would be + * 1.3.6.1.4.1.42.2.27.9.5.8 Account usability control, that can be used with + * for example the Sun ONE or the OpenDS directory servers. + * + * The extended callback handler can pass hasControls to mapper. + * + * @author Tim Terry + * @author Ulrik Sandberg + */ +public class ContextMapperCallbackHandlerWithControls extends ContextMapperCallbackHandler { + + private ContextMapperWithControls mapper = null; + + public ContextMapperCallbackHandlerWithControls(final ContextMapperWithControls mapper) { + super(mapper); + this.mapper = mapper; + } + + /* + * @see com.fr.third.springframework.ldap.core.ContextMapperCallbackHandler# + * getObjectFromNameClassPair(javax.naming.NameClassPair) + */ + public T getObjectFromNameClassPair(final NameClassPair nameClassPair) throws NamingException{ + if (!(nameClassPair instanceof Binding)) { + throw new IllegalArgumentException("Parameter must be an instance of Binding"); + } + + Binding binding = (Binding) nameClassPair; + Object object = binding.getObject(); + if (object == null) { + throw new ObjectRetrievalException("Binding did not contain any object."); + } + T result; + if (nameClassPair instanceof HasControls) { + result = mapper.mapFromContextWithControls(object, (HasControls) nameClassPair); + } + else { + result = mapper.mapFromContext(object); + } + return result; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/ContextMapperWithControls.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/ContextMapperWithControls.java new file mode 100644 index 000000000..2d43e62c0 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/ContextMapperWithControls.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.ContextMapper; + +import javax.naming.NamingException; +import javax.naming.ldap.HasControls; + +/** + * Extension of the {@link com.fr.third.springframework.ldap.core.ContextMapper} interface that allows + * controls to be passed to the mapper implementation. Uses Java 5 covariant + * return types to override the return type of the + * {@link #mapFromContextWithControls(Object, javax.naming.ldap.HasControls)} method to be the + * type parameter T. + * + * @author Tim Terry + * @author Ulrik Sandberg + * @param return type of the + * {@link #mapFromContextWithControls(Object, javax.naming.ldap.HasControls)} method + */ +public interface ContextMapperWithControls extends ContextMapper { + T mapFromContextWithControls(final Object ctx, final HasControls hasControls) throws NamingException; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/CountNameClassPairCallbackHandler.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/CountNameClassPairCallbackHandler.java new file mode 100644 index 000000000..baf6254e7 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/CountNameClassPairCallbackHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import javax.naming.NameClassPair; + +import com.fr.third.springframework.ldap.core.NameClassPairCallbackHandler; + +/** + * A {@link NameClassPairCallbackHandler} for counting all returned entries. + * + * @author Mattias Hellborg Arthursson + * + */ +public class CountNameClassPairCallbackHandler implements + NameClassPairCallbackHandler { + + private int noOfRows = 0; + + /** + * Get the number of rows that was returned by the search. + * + * @return the number of entries that have been handled. + */ + public int getNoOfRows() { + return noOfRows; + } + + /* + * (non-Javadoc) + * + * @see com.fr.third.springframework.ldap.SearchResultCallbackHandler#handleSearchResult(javax.naming.directory.SearchResult) + */ + public void handleNameClassPair(NameClassPair nameClassPair) { + noOfRows++; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/DefaultDirObjectFactory.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DefaultDirObjectFactory.java new file mode 100644 index 000000000..3a41caa9c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DefaultDirObjectFactory.java @@ -0,0 +1,173 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.core.JdkVersion; +import com.fr.third.springframework.ldap.core.DirContextAdapter; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.StringUtils; + +import javax.naming.CompositeName; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.directory.Attributes; +import javax.naming.spi.DirObjectFactory; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Hashtable; + +/** + * Default implementation of the DirObjectFactory interface. Creates a + * {@link DirContextAdapter} from the supplied arguments. + * + * @author Mattias Hellborg Arthursson + */ +public class DefaultDirObjectFactory implements DirObjectFactory { + private static final Logger LOG = LoggerFactory.getLogger(DefaultDirObjectFactory.class); + + /** + * Key to use in the ContextSource implementation to store the value of the + * base path suffix, if any, in the Ldap Environment. + * + * @deprecated Use {@link BaseLdapNameAware} and + * {@link BaseLdapPathBeanPostProcessor} instead. + */ + public static final String JNDI_ENV_BASE_PATH_KEY = "com.fr.third.springframework.ldap.base.path"; + + private static final String LDAP_PROTOCOL_PREFIX = "ldap://"; + + private static final String LDAPS_PROTOCOL_PREFIX = "ldaps://"; + + @Override + public final Object getObjectInstance( + Object obj, + Name name, + Context nameCtx, + Hashtable environment, + Attributes attrs) throws Exception { + + try { + String nameInNamespace; + if (nameCtx != null) { + nameInNamespace = nameCtx.getNameInNamespace(); + } + else { + nameInNamespace = ""; + } + + return constructAdapterFromName(attrs, name, nameInNamespace); + } + finally { + // It seems that the object supplied to the obj parameter is a + // DirContext instance with reference to the same Ldap connection as + // the original context. Since it is not the same instance (that's + // the nameCtx parameter) this one really needs to be closed in + // order to correctly clean up and return the connection to the pool + // when we're finished with the surrounding operation. + if (obj instanceof Context) { + + Context ctx = (Context) obj; + try { + ctx.close(); + } + catch (Exception e) { + // Never mind this + } + + } + } + } + + /** + * Construct a DirContextAdapter given the supplied paramters. The + * name is normally a JNDI CompositeName, which + * needs to be handled with particuclar care. Specifically the escaping of a + * CompositeName destroys proper escaping of Distinguished + * Names. Also, the name might contain referral information, in which case + * we need to separate the server information from the actual Distinguished + * Name so that we can create a representing DirContextAdapter. + * + * @param attrs the attributes + * @param name the Name, typically a CompositeName, possibly + * including referral information. + * @param nameInNamespace the Name in namespace. + * @return a {@link DirContextAdapter} representing the specified + * information. + */ + DirContextAdapter constructAdapterFromName(Attributes attrs, Name name, String nameInNamespace) { + String nameString; + String referralUrl = ""; + + if (name instanceof CompositeName) { + // Which it most certainly will be, and therein lies the + // problem. CompositeName.toString() completely screws up the + // formatting + // in some cases, particularly when backslashes are involved. + nameString = LdapUtils + .convertCompositeNameToString((CompositeName) name); + } + else { + LOG + .warn("Expecting a CompositeName as input to getObjectInstance but received a '" + + name.getClass().toString() + + "' - using toString and proceeding with undefined results"); + nameString = name.toString(); + } + + if (nameString.startsWith(LDAP_PROTOCOL_PREFIX) || nameString.startsWith(LDAPS_PROTOCOL_PREFIX)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Received name '" + nameString + "' contains protocol delimiter; indicating a referral." + + "Stripping protocol and address info to enable construction of a proper LdapName"); + } + try { + URI url = new URI(nameString); + String pathString = url.getPath(); + referralUrl = nameString.substring(0, nameString.length() - pathString.length()); + + if (StringUtils.hasLength(pathString) && pathString.startsWith("/")) { + // We don't want any slash in the beginning of the + // Distinguished Name. + pathString = pathString.substring(1); + } + + nameString = pathString; + } + catch (URISyntaxException e) { + throw new IllegalArgumentException( + "Supplied name starts with protocol prefix indicating a referral," + + " but is not possible to parse to an URI", + e); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Resulting name after removal of referral information: '" + nameString + "'"); + } + } + + DirContextAdapter dirContextAdapter = new DirContextAdapter(attrs, LdapUtils.newLdapName(nameString), + LdapUtils.newLdapName(nameInNamespace), referralUrl); + dirContextAdapter.setUpdateMode(true); + return dirContextAdapter; + } + + @Override + public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception { + return null; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/DefaultIncrementalAttributesMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DefaultIncrementalAttributesMapper.java new file mode 100644 index 000000000..df6522bcf --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DefaultIncrementalAttributesMapper.java @@ -0,0 +1,440 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.IncrementalAttributesMapper; +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.ldap.support.LdapUtils; + +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Utility class that helps with reading all attribute values from Active Directory using Incremental Retrieval of + * Multi-valued Properties. + *

Example usage of this attribute mapper: + *

+ *     List values = DefaultIncrementalAttributeMapper.lookupAttributeValues(ldapTemplate, theDn, "oneAttribute");
+ *     Attributes attrs = DefaultIncrementalAttributeMapper.lookupAttributeValues(ldapTemplate, theDn, new Object[]{"oneAttribute", "anotherAttribute"});
+ * 
+ * For greater control, e.g. explicitly specifying the requested page size, create and use an instance yourself: + *
+ *
+ *      IncrementalAttributesMapper incrementalAttributeMapper = new DefaultIncrementalAttributeMapper(10, "someAttribute");
+ *      while (incrementalAttributeMapper.hasMore()) {
+ *          ldap.lookup(entrDn, incrementalAttributeMapper.getAttributesForLookup(), incrementalAttributeMapper);
+ *      }
+ *
+ *      List values = incrementalAttributeMapper.getValues("someAttribute");
+ * 
+ *

+ * NOTE: Instances of this class are highly stateful and must not be reused or shared between threads in any way. + *

+ * NOTE: Instances of this class can only be used with lookups. No support is given for searches. + *

+ * + * @author Marius Scurtescu + * @author Mattias Hellborg Arthursson + * @see Incremental Retrieval of Multi-valued Properties + * @see #lookupAttributes(com.fr.third.springframework.ldap.core.LdapOperations, javax.naming.Name, String[]) + * @see #lookupAttributeValues(com.fr.third.springframework.ldap.core.LdapOperations, javax.naming.Name, String) + * @since 1.3.2 + */ +public class DefaultIncrementalAttributesMapper implements IncrementalAttributesMapper { + private final static Logger LOG = LoggerFactory.getLogger(DefaultIncrementalAttributesMapper.class); + + private Map stateMap = new LinkedHashMap(); + private Set rangedAttributesInNextIteration = new LinkedHashSet(); + + /** + * This guy will be used when an unmapped attribute is encountered. This really should never happen, + * but this saves us a number of null checks. + */ + private static final IncrementalAttributeState NOT_FOUND_ATTRIBUTE_STATE = new IncrementalAttributeState() { + @Override + public String getRequestedAttributeName() { + throw new UnsupportedOperationException("This method should never be called"); + } + + @Override + public boolean hasMore() { + return false; + } + + @Override + public void calculateNextRange(RangeOption responseRange) { + // Nothing to do here + } + + @Override + public String getAttributeNameForQuery() { + throw new UnsupportedOperationException("This method should never be called"); + } + + @Override + public void processValues(Attributes attributes, String attributeName) throws NamingException { + // Nothing to do here + } + + @Override + public List getValues() { + return null; + } + }; + + /** + * Create an instance for the requested attribute. + * + * @param attributeName the name of the attribute that this instance handles. + * This is the attribute name that will be requested, and whose + * values are managed. + */ + public DefaultIncrementalAttributesMapper(String attributeName) { + this(RangeOption.TERMINAL_END_OF_RANGE, attributeName); + } + + /** + * Create an instance for the requested attributes. + * + * @param attributeNames the name of the attributes that this instance handles. + * These are the attribute names that will be requested, and whose + * values are managed. + */ + public DefaultIncrementalAttributesMapper(String[] attributeNames) { + this(RangeOption.TERMINAL_END_OF_RANGE, attributeNames); + } + + /** + * Create an instance for the requested attribute with a specific page size. + * + * @param pageSize the requested page size that will be included in range query attribute names. + * @param attributeName the name of the attribute that this instance handles. + * This is the attribute name that will be requested, and whose + * values are managed. + */ + public DefaultIncrementalAttributesMapper(int pageSize, String attributeName) { + this(pageSize, new String[]{attributeName}); + } + + /** + * Create an instance for the requested attributes with a specific page size. + * + * @param pageSize the requested page size that will be included in range query attribute names. + * @param attributeNames the name of the attributes that this instance handles. + * These are the attribute names that will be requested, and whose + * values are managed. + */ + public DefaultIncrementalAttributesMapper(int pageSize, String[] attributeNames) { + for (String attributeName : attributeNames) { + this.stateMap.put(attributeName, new DefaultIncrementalAttributeState(attributeName, pageSize)); + this.rangedAttributesInNextIteration.add(attributeName); + } + } + + @Override + public final DefaultIncrementalAttributesMapper mapFromAttributes(Attributes attributes) throws NamingException { + if (!hasMore()) { + throw new IllegalStateException("No more attributes!"); + } + + // Reset the affected attributes. + rangedAttributesInNextIteration = new HashSet(); + + NamingEnumeration attributeNameEnum = attributes.getIDs(); + while (attributeNameEnum.hasMore()) { + String attributeName = attributeNameEnum.next(); + + String[] attributeNameSplit = attributeName.split(";"); + IncrementalAttributeState state = getState(attributeNameSplit[0]); + if (attributeNameSplit.length == 1) { + // No range specification for this attribute + state.processValues(attributes, attributeName); + } else { + for (String option : attributeNameSplit) { + RangeOption responseRange = RangeOption.parse(option); + + if (responseRange != null) { + state.processValues(attributes, attributeName); + state.calculateNextRange(responseRange); + if (state.hasMore()) { + rangedAttributesInNextIteration.add(state.getRequestedAttributeName()); + } + } + } + } + } + + return this; + } + + private IncrementalAttributeState getState(String attributeName) { + Object mappedState = stateMap.get(attributeName); + if (mappedState == null) { + LOG.warn("Attribute '" + attributeName + "' is not handled by this instance"); + mappedState = NOT_FOUND_ATTRIBUTE_STATE; + } + + return (IncrementalAttributeState) mappedState; + } + + @Override + public final List getValues(String attributeName) { + return getState(attributeName).getValues(); + } + + @Override + public final Attributes getCollectedAttributes() { + BasicAttributes attributes = new BasicAttributes(); + + Set attributeNames = stateMap.keySet(); + for (String attributeName : attributeNames) { + BasicAttribute oneAttribute = new BasicAttribute(attributeName); + List values = getValues(attributeName); + if (values != null) { + for (Object oneValue : values) { + oneAttribute.add(oneValue); + } + } + + attributes.put(oneAttribute); + } + + return attributes; + } + + @Override + public final boolean hasMore() { + return rangedAttributesInNextIteration.size() > 0; + } + + @Override + public final String[] getAttributesForLookup() { + String[] result = new String[rangedAttributesInNextIteration.size()]; + int index = 0; + for (String next : rangedAttributesInNextIteration) { + IncrementalAttributeState state = stateMap.get(next); + result[index++] = state.getAttributeNameForQuery(); + } + + return result; + } + + /** + * Lookup all values for the specified attribute, looping through the results incrementally if necessary. + * + * @param ldapOperations The instance to use for performing the actual lookup. + * @param dn The distinguished name of the object to find. + * @param attribute name of the attribute to request. + * @return an Attributes instance, populated with all found values for the requested attribute. + * Never null, though the actual attribute may not be set if it was not + * set on the requested object. + */ + public static Attributes lookupAttributes(LdapOperations ldapOperations, String dn, String attribute) { + return lookupAttributes(ldapOperations, LdapUtils.newLdapName(dn), attribute); + } + + /** + * Lookup all values for the specified attributes, looping through the results incrementally if necessary. + * + * @param ldapOperations The instance to use for performing the actual lookup. + * @param dn The distinguished name of the object to find. + * @param attributes names of the attributes to request. + * @return an Attributes instance, populated with all found values for the requested attributes. + * Never null, though the actual attributes may not be set if they was not + * set on the requested object. + */ + public static Attributes lookupAttributes(LdapOperations ldapOperations, String dn, String[] attributes) { + return lookupAttributes(ldapOperations, LdapUtils.newLdapName(dn), attributes); + } + + /** + * Lookup all values for the specified attribute, looping through the results incrementally if necessary. + * + * @param ldapOperations The instance to use for performing the actual lookup. + * @param dn The distinguished name of the object to find. + * @param attribute name of the attribute to request. + * @return an Attributes instance, populated with all found values for the requested attribute. + * Never null, though the actual attribute may not be set if it was not + * set on the requested object. + */ + public static Attributes lookupAttributes(LdapOperations ldapOperations, Name dn, String attribute) { + return lookupAttributes(ldapOperations, dn, new String[]{attribute}); + } + + /** + * Lookup all values for the specified attributes, looping through the results incrementally if necessary. + * + * @param ldapOperations The instance to use for performing the actual lookup. + * @param dn The distinguished name of the object to find. + * @param attributes names of the attributes to request. + * @return an Attributes instance, populated with all found values for the requested attributes. + * Never null, though the actual attributes may not be set if they was not + * set on the requested object. + */ + public static Attributes lookupAttributes(LdapOperations ldapOperations, Name dn, String[] attributes) { + return loopForAllAttributeValues(ldapOperations, dn, attributes).getCollectedAttributes(); + } + + /** + * Lookup all values for the specified attribute, looping through the results incrementally if necessary. + * + * @param ldapOperations The instance to use for performing the actual lookup. + * @param dn The distinguished name of the object to find. + * @param attribute name of the attribute to request. + * @return a list with all attribute values found for the requested attribute. + * Never null, an empty list indicates that the attribute was not set or empty. + */ + public static List lookupAttributeValues(LdapOperations ldapOperations, String dn, String attribute) { + return lookupAttributeValues(ldapOperations, LdapUtils.newLdapName(dn), attribute); + } + + /** + * Lookup all values for the specified attribute, looping through the results incrementally if necessary. + * + * @param ldapOperations The instance to use for performing the actual lookup. + * @param dn The distinguished name of the object to find. + * @param attribute name of the attribute to request. + * @return a list with all attribute values found for the requested attribute. + * Never null, an empty list indicates that the attribute was not set or empty. + */ + public static List lookupAttributeValues(LdapOperations ldapOperations, Name dn, String attribute) { + List values = loopForAllAttributeValues(ldapOperations, dn, new String[]{attribute}).getValues(attribute); + if(values == null) { + values = Collections.emptyList(); + } + + return values; + } + + private static DefaultIncrementalAttributesMapper loopForAllAttributeValues(LdapOperations ldapOperations, Name dn, String[] attributes) { + DefaultIncrementalAttributesMapper mapper = new DefaultIncrementalAttributesMapper(attributes); + while (mapper.hasMore()) { + ldapOperations.lookup(dn, mapper.getAttributesForLookup(), mapper); + } + return mapper; + } + + /** + * This class keeps track of the state of an individual attribute in the process of collecting + * multi-value attributes using ranges. Holds the values collected thus far, the next applicable range, + * and the actual (requested) attribute name. + */ + private static final class DefaultIncrementalAttributeState implements IncrementalAttributeState { + private final String actualAttributeName; + private List values = null; + private final int pageSize; + boolean more = true; + + private RangeOption requestRange; + + private DefaultIncrementalAttributeState(String actualAttributeName, int pageSize) { + this.actualAttributeName = actualAttributeName; + this.pageSize = pageSize; + this.requestRange = new RangeOption(0, pageSize); + } + + @Override + public boolean hasMore() { + return more; + } + + @Override + public String getRequestedAttributeName() { + return actualAttributeName; + } + + @Override + public void calculateNextRange(RangeOption responseRange) { + more = requestRange.compareTo(responseRange) > 0; + + if (more) { + requestRange = responseRange.nextRange(pageSize); + } + } + + @Override + public String getAttributeNameForQuery() { + StringBuilder attributeBuilder = new StringBuilder(actualAttributeName); + + if (!(requestRange.isFullRange())) { + attributeBuilder.append(';'); + requestRange.appendTo(attributeBuilder); + } + + return attributeBuilder.toString(); + } + + @Override + public void processValues(Attributes attributes, String attributeName) throws NamingException { + Attribute attribute = attributes.get(attributeName); + NamingEnumeration valueEnum = attribute.getAll(); + + initValuesIfApplicable(); + while (valueEnum.hasMore()) { + values.add(valueEnum.next()); + } + } + + private void initValuesIfApplicable() { + if (values == null) { + values = new LinkedList(); + } + } + + @Override + public List getValues() { + if (values != null) { + return new ArrayList(values); + } else { + return null; + } + } + } + + /** + * @author Mattias Hellborg Arthursson + */ + private interface IncrementalAttributeState { + boolean hasMore(); + + void calculateNextRange(RangeOption responseRange); + + String getAttributeNameForQuery(); + + String getRequestedAttributeName(); + + void processValues(Attributes attributes, String attributeName) throws NamingException; + + List getValues(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/DefaultTlsDirContextAuthenticationStrategy.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DefaultTlsDirContextAuthenticationStrategy.java new file mode 100755 index 000000000..9cc5d12b1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DefaultTlsDirContextAuthenticationStrategy.java @@ -0,0 +1,41 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.ldap.LdapContext; + +/** + * Default implementation of TLS authentication. Applies SIMPLE + * authentication on top of the negotiated TLS session. Refer to + * {@link AbstractTlsDirContextAuthenticationStrategy} for configuration + * options. + * + * @author Mattias Hellborg Arthursson + * @see AbstractTlsDirContextAuthenticationStrategy + * @see AbstractContextSource + */ +public class DefaultTlsDirContextAuthenticationStrategy extends AbstractTlsDirContextAuthenticationStrategy { + private static final String SIMPLE_AUTHENTICATION = "simple"; + + protected void applyAuthentication(LdapContext ctx, String userDn, String password) throws NamingException { + ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, SIMPLE_AUTHENTICATION); + ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, userDn); + ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/DelegatingBaseLdapPathContextSourceSupport.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DelegatingBaseLdapPathContextSourceSupport.java new file mode 100644 index 000000000..10326f484 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DelegatingBaseLdapPathContextSourceSupport.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.DistinguishedName; + +import javax.naming.ldap.LdapName; + +/** + * Support class to provide {@link BaseLdapPathSource} functionality to ContextSource instances + * that act as proxies. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public abstract class DelegatingBaseLdapPathContextSourceSupport implements BaseLdapPathSource { + + /** + * Get the target ContextSource. + * @return the target ContextSource. + */ + protected abstract ContextSource getTarget(); + + private BaseLdapPathSource getTargetAsBaseLdapPathSource() { + try { + return (BaseLdapPathSource) getTarget(); + } catch (ClassCastException e) { + throw new UnsupportedOperationException("This operation is not supported on a target ContextSource that does not " + + " implement BaseLdapPathContextSource", e); + } + } + + @Override + public final LdapName getBaseLdapName() { + return getTargetAsBaseLdapPathSource().getBaseLdapName(); + } + + @Override + public final DistinguishedName getBaseLdapPath() { + return getTargetAsBaseLdapPathSource().getBaseLdapPath(); + } + + @Override + public final String getBaseLdapPathAsString() { + return getTargetAsBaseLdapPathSource().getBaseLdapPathAsString(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/DigestMd5DirContextAuthenticationStrategy.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DigestMd5DirContextAuthenticationStrategy.java new file mode 100644 index 000000000..ff56ee468 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DigestMd5DirContextAuthenticationStrategy.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import javax.naming.Context; +import javax.naming.directory.DirContext; +import java.util.Hashtable; + +/** + * Authentication strategy for LDAP DIGEST-MD5 SASL mechanism. + * + * @author Marvin S. Addison + * @since 1.3.1 + */ +public class DigestMd5DirContextAuthenticationStrategy implements DirContextAuthenticationStrategy { + + /** Authentication type for DIGEST-MD5 auth */ + private static final String DIGEST_MD5_AUTHENTICATION = "DIGEST-MD5"; + + /* + * (non-Javadoc) + * @see com.fr.third.springframework.ldap.core.support.DirContextAuthenticationStrategy#processContextAfterCreation(javax.naming.directory.DirContext, + * java.lang.String, java.lang.String) + */ + public DirContext processContextAfterCreation(DirContext ctx, String userDn, String password) { + return ctx; + } + + /* + * (non-Javadoc) + * @see com.fr.third.springframework.ldap.core.support.DirContextAuthenticationStrategy#setupEnvironment(java.util.Hashtable, + * java.lang.String, java.lang.String) + */ + public void setupEnvironment(Hashtable env, String userDn, String password) { + env.put(Context.SECURITY_AUTHENTICATION, DIGEST_MD5_AUTHENTICATION); + // userDn should be a bare username for DIGEST-MD5 + env.put(Context.SECURITY_PRINCIPAL, userDn); + env.put(Context.SECURITY_CREDENTIALS, password); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/DirContextAuthenticationStrategy.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DirContextAuthenticationStrategy.java new file mode 100755 index 000000000..194697f36 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DirContextAuthenticationStrategy.java @@ -0,0 +1,85 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.AuthenticationSource; +import com.fr.third.springframework.ldap.core.ContextSource; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import java.util.Hashtable; + +/** + * A strategy to use when authenticating LDAP connections on creation. When + * authenticating LDAP connections different strategies are needed depending on + * the authentication mechanism used. Furthermore, depending on the mechanism + * the work to be done needs to be applied at different stages of the + * DirContext creation process. A + * DirContextAuthenticationStrategy contains the logic to perform a particular + * type of authentication mechanism and will be called by its + * {@link ContextSource} at appropriate stages of the process. + * + * @author Mattias Hellborg Arthursson + */ +public interface DirContextAuthenticationStrategy { + + /** + * This method is responsible for preparing the environment to be used when + * creating the DirContext instance. The base environment + * (including URL, ContextFactory etc. will already be set, + * and this method is called just before the actual Context is to be + * created. + * + * @param env The Hashtable to be sent to the + * DirContext instance on initialization. Pre-configured with + * the basic settings; the implementation of this method is responsible for + * manipulating the environment as appropriate for the particular + * authentication mechanism. + * @param userDn the user DN to authenticate, as received from the + * {@link AuthenticationSource} of the {@link ContextSource}. + * @param password the password to authenticate with, as received from the + * {@link AuthenticationSource} of the {@link ContextSource}. + * @throws NamingException if anything goes wrong. This will cause the + * DirContext creation to be aborted and the exception to be + * translated and rethrown. + */ + void setupEnvironment(Hashtable env, String userDn, String password) throws NamingException; + + /** + * This method is responsible for post-processing the + * DirContext instance after it has been created. It will be + * called immediately after the instance has been created. Some + * authentication mechanisms, e.g. TLS, require particular stuff to happen + * before the actual target Context is closed. This method provides the + * possibility to replace or wrap the actual DirContext with a proxy so that + * any calls on it may be intercepted. + * + * @param ctx the freshly created DirContext instance. The + * actual implementation class (e.g. InitialLdapContext) + * depends on the {@link ContextSource} implementation. + * @param userDn the user DN to authenticate, as received from the + * {@link AuthenticationSource} of the {@link ContextSource}. + * @param password the password to authenticate with, as received from the + * {@link AuthenticationSource} of the {@link ContextSource}. + * @return the DirContext, possibly modified, replaced or wrapped. + * @throws NamingException if anything goes wrong. This will cause the + * DirContext creation to be aborted and the exception to be + * translated and rethrown. + */ + DirContext processContextAfterCreation(DirContext ctx, String userDn, String password) + throws NamingException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/DirContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DirContextSource.java new file mode 100644 index 000000000..0257ec818 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/DirContextSource.java @@ -0,0 +1,48 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import java.util.Hashtable; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + + +/** + * ContextSource implementation which creates InitialDirContext instances, for + * LDAPv2 compatibility. For configuration information, see + * {@link com.fr.third.springframework.ldap.core.support.AbstractContextSource AbstractContextSource}. + * + * @see com.fr.third.springframework.ldap.core.support.AbstractContextSource + * + * @author Mattias Hellborg Arthursson + */ +public class DirContextSource extends AbstractContextSource { + + /** + * Create a new InitialDirContext instance. + * + * @param environment + * the environment to use when creating the context. + * @return a new InitialDirContext implementation. + */ + protected DirContext getDirContextInstance(Hashtable environment) + throws NamingException { + return new InitialDirContext(environment); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/ExternalTlsDirContextAuthenticationStrategy.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/ExternalTlsDirContextAuthenticationStrategy.java new file mode 100755 index 000000000..7f3c0d6d5 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/ExternalTlsDirContextAuthenticationStrategy.java @@ -0,0 +1,43 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.ldap.LdapContext; + +/** + * {@link DirContextAuthenticationStrategy} for using TLS and external (SASL) + * authentication. This implementation requires a client certificate to be + * pointed out using system variables, as described here. Refer to {@link AbstractTlsDirContextAuthenticationStrategy} for + * other configuration options. + * + * @author Mattias Hellborg Arthursson + * @see AbstractTlsDirContextAuthenticationStrategy + * @see AbstractContextSource + */ +public class ExternalTlsDirContextAuthenticationStrategy extends AbstractTlsDirContextAuthenticationStrategy { + + private static final String EXTERNAL_AUTHENTICATION = "EXTERNAL"; + + protected void applyAuthentication(LdapContext ctx, String userDn, String password) throws NamingException { + ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, EXTERNAL_AUTHENTICATION); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/LdapContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/LdapContextSource.java new file mode 100644 index 000000000..881c2ea72 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/LdapContextSource.java @@ -0,0 +1,44 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.InitialLdapContext; +import java.util.Hashtable; + +/** + * ContextSource implementation which creates an InitialLdapContext + * instance. For configuration information, see + * {@link com.fr.third.springframework.ldap.core.support.AbstractContextSource AbstractContextSource}. + * + * @see com.fr.third.springframework.ldap.core.support.AbstractContextSource + * + * @author Mattias Hellborg Arthursson + * @author Adam Skogman + * @author Ulrik Sandberg + */ +public class LdapContextSource extends AbstractContextSource { + + /* + * @see com.fr.third.springframework.ldap.support.AbstractContextSource#getDirContextInstance(java.util.Hashtable) + */ + protected DirContext getDirContextInstance(Hashtable environment) + throws NamingException { + return new InitialLdapContext(environment, null); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/LdapOperationsCallback.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/LdapOperationsCallback.java new file mode 100644 index 000000000..f3f594d4b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/LdapOperationsCallback.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.LdapOperations; + +/** + * Callback interface to be used together with {@link SingleContextSource}. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + * @see SingleContextSource#doWithSingleContext(com.fr.third.springframework.ldap.core.ContextSource, LdapOperationsCallback) + * @see SingleContextSource#doWithSingleContext(com.fr.third.springframework.ldap.core.ContextSource, LdapOperationsCallback, boolean, boolean, boolean) + */ +public interface LdapOperationsCallback { + /** + * Perform a sequence of LDAP operations on the supplied LdapOperations instance. The underlying DirContext + * that the operations will work on is guaranteed to always be exact same instance during the lifetime of this + * method. + * + * @param operations the LdapOperations instance to perform operations on. + * @return The aggregated result of all the performed operations. + */ + T doWithLdapOperations(LdapOperations operations); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/LookupAttemptingCallback.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/LookupAttemptingCallback.java new file mode 100644 index 000000000..daa7260b0 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/LookupAttemptingCallback.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import com.fr.third.springframework.ldap.core.AuthenticatedLdapEntryContextCallback; +import com.fr.third.springframework.ldap.core.AuthenticatedLdapEntryContextMapper; +import com.fr.third.springframework.ldap.core.DirContextOperations; +import com.fr.third.springframework.ldap.core.LdapEntryIdentification; +import com.fr.third.springframework.ldap.support.LdapUtils; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +/** + * Attempts to perform an LDAP operation in the authenticated context, because + * Active Directory might allow bind with incorrect password (specifically empty + * password), and later refuse operations. We want to fail fast when + * authenticating. {@link #mapWithContext(javax.naming.directory.DirContext, com.fr.third.springframework.ldap.core.LdapEntryIdentification)} + * returns the {@link DirContextOperations} instance that results from the lookup operation. This instance + * can be used to obtain information regarding the authenticated user. + * + * @author Hugo Josefson + * @author Mattias Hellborg Arthursson + * @since 1.3.1 + */ +public class LookupAttemptingCallback implements + AuthenticatedLdapEntryContextCallback, AuthenticatedLdapEntryContextMapper { + @Override + public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { + mapWithContext(ctx, ldapEntryIdentification); + } + + @Override + public DirContextOperations mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { + try { + return (DirContextOperations) ctx.lookup(ldapEntryIdentification.getRelativeName()); + } + catch (NamingException e) { + // rethrow, because we aren't allowed to throw checked exceptions. + throw LdapUtils.convertLdapException(e); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/RangeOption.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/RangeOption.java new file mode 100644 index 000000000..2587c1085 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/RangeOption.java @@ -0,0 +1,190 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.core.support; + +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +/** + * Attribute name Range Option used for Incremental Retrieval of + * Multi-valued Properties. + * + * @author Marius Scurtescu + * + * @see DefaultIncrementalAttributesMapper + * @since 1.3.2 + */ +class RangeOption implements Comparable { + public static final int TERMINAL_END_OF_RANGE = -1; + public static final int TERMINAL_MISSING = -2; + + private int initial = 0; + private int terminal = TERMINAL_END_OF_RANGE; + + private static final Pattern RANGE_PATTERN = Pattern.compile("^Range=([0-9]+)(-([0-9]+|\\*))?$", Pattern.CASE_INSENSITIVE); + + public RangeOption(int initial) { + this(initial, TERMINAL_END_OF_RANGE); + } + + public RangeOption(int initial, int terminal) { + if (terminal < 0 && (terminal != TERMINAL_END_OF_RANGE && terminal != TERMINAL_MISSING)) { + throw new IllegalArgumentException("Illegal range-terminal: " + terminal); + } + + if (initial < 0) { + throw new IllegalArgumentException("Illegal range-initial: " + initial); + } + + if (terminal >= 0 && terminal < initial) { + throw new IllegalArgumentException("range-terminal cannot be smaller than range-initial: " + initial + "-" + terminal); + } + + this.initial = initial; + this.terminal = terminal; + } + + public boolean isTerminalEndOfRange() { + return terminal == TERMINAL_END_OF_RANGE; + } + + public boolean isTerminalMissing() { + return terminal == TERMINAL_MISSING; + } + + public int getInitial() { + return initial; + } + + public int getTerminal() { + return terminal; + } + + public boolean isFullRange() { + return getInitial() == 0 && getTerminal() == TERMINAL_END_OF_RANGE; + } + + public String toString() { + StringBuilder rangeBuilder = new StringBuilder(); + appendTo(rangeBuilder); + + return rangeBuilder.toString(); + } + + public void appendTo(StringBuilder rangeBuilder) { + rangeBuilder.append("Range=").append(initial); + + if (!isTerminalMissing()) { + rangeBuilder.append('-'); + + if (isTerminalEndOfRange()) { + rangeBuilder.append('*'); + } else { + rangeBuilder.append(terminal); + } + } + } + + public static RangeOption parse(String option) { + Matcher rangeMatcher = RANGE_PATTERN.matcher(option); + + rangeMatcher.find(); + + if (!rangeMatcher.matches()) { + return null; + } + + String initialStr = rangeMatcher.group(1); + + int initial = Integer.parseInt(initialStr); + int terminal = TERMINAL_MISSING; + + if (rangeMatcher.group(2) != null) { + String terminalStr = rangeMatcher.group(3); + + if ("*".equals(terminalStr)) { + terminal = TERMINAL_END_OF_RANGE; + } else { + terminal = Integer.parseInt(terminalStr); + } + } + + return new RangeOption(initial, terminal); + } + + public int compareTo(RangeOption that) { + if (this.getInitial() != that.getInitial()) + throw new IllegalStateException("Ranges cannot be compared, range-initial not the same: " + this.toString() + " vs " + that.toString()); + + if (this.getTerminal() == that.getTerminal()) { + return 0; + } + + if (that.getTerminal() == TERMINAL_MISSING) { + throw new IllegalStateException("Don't know how to deal with missing range-terminal: " + that.toString()); + } + + if (this.getTerminal() == TERMINAL_MISSING) { + throw new IllegalStateException("Don't know how to deal with missing range-terminal: " + this.toString()); + } + + if (this.getTerminal() == TERMINAL_END_OF_RANGE) { + return 1; + } + + if (that.getTerminal() == TERMINAL_END_OF_RANGE) { + return -1; + } + + return this.getTerminal() > that.getTerminal() ? 1 : -1; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RangeOption that = (RangeOption) o; + + if (initial != that.initial) return false; + if (terminal != that.terminal) return false; + + return true; + } + + @Override + public int hashCode() { + int result = initial; + result = 31 * result + terminal; + return result; + } + + public RangeOption nextRange(int pageSize) { + if (getTerminal() < 0) { + throw new IllegalStateException("Cannot generate next range, range-terminal: " + getTerminal()); + } + + if (pageSize < 0 && pageSize != TERMINAL_END_OF_RANGE) { + throw new IllegalArgumentException("Invalid page size: " + pageSize); + } + + int initial = getTerminal() + 1; + int terminal = pageSize == TERMINAL_END_OF_RANGE ? TERMINAL_END_OF_RANGE : getTerminal() + pageSize; + + return new RangeOption(initial, terminal); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/SimpleDirContextAuthenticationStrategy.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/SimpleDirContextAuthenticationStrategy.java new file mode 100755 index 000000000..48ba8903b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/SimpleDirContextAuthenticationStrategy.java @@ -0,0 +1,55 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import javax.naming.Context; +import javax.naming.directory.DirContext; +import java.util.Hashtable; + +/** + * The default {@link DirContextAuthenticationStrategy} implementation, setting + * the DirContext environment up for 'SIMPLE' authentication, and + * specifying the user DN and password as SECURITY_PRINCIPAL and + * SECURITY_CREDENTIALS respectively in the authenticated environment before the + * context is created. + * + * @author Mattias Hellborg Arthursson + */ +public class SimpleDirContextAuthenticationStrategy implements DirContextAuthenticationStrategy { + + private static final String SIMPLE_AUTHENTICATION = "simple"; + + /* + * (non-Javadoc) + * @see com.fr.third.springframework.ldap.core.support.DirContextAuthenticationStrategy#setupEnvironment(java.util.Hashtable, + * java.lang.String, java.lang.String) + */ + public void setupEnvironment(Hashtable env, String userDn, String password) { + env.put(Context.SECURITY_AUTHENTICATION, SIMPLE_AUTHENTICATION); + env.put(Context.SECURITY_PRINCIPAL, userDn); + env.put(Context.SECURITY_CREDENTIALS, password); + } + + /* + * (non-Javadoc) + * @see com.fr.third.springframework.ldap.core.support.DirContextAuthenticationStrategy#processContextAfterCreation(javax.naming.directory.DirContext, + * java.lang.String, java.lang.String) + */ + public DirContext processContextAfterCreation(DirContext ctx, String userDn, String password) { + return ctx; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/SingleContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/core/support/SingleContextSource.java new file mode 100644 index 000000000..9b1f6ea5b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/SingleContextSource.java @@ -0,0 +1,204 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.core.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.beans.factory.DisposableBean; +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.DirContextProxy; +import com.fr.third.springframework.ldap.core.LdapTemplate; +import com.fr.third.springframework.ldap.support.LdapUtils; + +import javax.naming.directory.DirContext; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * A {@link ContextSource} to be used as a decorator around a target ContextSource + * to make sure the target is never actually closed. Useful when working with e.g. paged results, + * as these require the same target to be used. + * + * @author Mattias Hellborg Arthursson + */ +public class SingleContextSource implements ContextSource, DisposableBean { + + private static final Logger LOG = LoggerFactory.getLogger(SingleContextSource.class); + private static final boolean DONT_USE_READ_ONLY = false; + private static final boolean DONT_IGNORE_PARTIAL_RESULT = false; + private static final boolean DONT_IGNORE_NAME_NOT_FOUND = false; + + private final DirContext ctx; + + /** + * Constructor. + * + * @param ctx the target DirContext. + */ + public SingleContextSource(DirContext ctx) { + this.ctx = ctx; + } + + /* + * @see com.fr.third.springframework.ldap.ContextSource#getReadOnlyContext() + */ + public DirContext getReadOnlyContext() { + return getNonClosingDirContextProxy(ctx); + } + + /* + * @see com.fr.third.springframework.ldap.ContextSource#getReadWriteContext() + */ + public DirContext getReadWriteContext() { + return getNonClosingDirContextProxy(ctx); + } + + private DirContext getNonClosingDirContextProxy(DirContext context) { + return (DirContext) Proxy.newProxyInstance(DirContextProxy.class + .getClassLoader(), new Class[]{ + LdapUtils.getActualTargetClass(context), + DirContextProxy.class}, + new SingleContextSource.NonClosingDirContextInvocationHandler( + context)); + + } + + public DirContext getContext(String principal, String credentials) { + throw new UnsupportedOperationException( + "Not a valid operation for this type of ContextSource"); + } + + /** + * Destroy method that allows the target DirContext to be cleaned up when + * the SingleContextSource is not going to be used any more. + */ + public void destroy() { + try { + ctx.close(); + } + catch (javax.naming.NamingException e) { + LOG.warn("Error when closing", e); + } + } + + /** + * Construct a SingleContextSource and execute the LdapOperationsCallback using the created instance. + * This makes sure the same connection will be used for all operations inside the LdapOperationsCallback, + * which is particularly useful when working with e.g. Paged Results as these typically require the exact + * same connection to be used for all requests involving the same cookie. + * The SingleContextSource instance will be properly disposed of once the operation has been completed. + *

By default, the {@link com.fr.third.springframework.ldap.core.ContextSource#getReadWriteContext()} method + * will be used to create the DirContext instance to operate on.

+ * + * @param contextSource The target ContextSource to retrieve a DirContext from. + * @param callback the callback to perform the Ldap operations. + * @return the result returned from the callback. + * @see #doWithSingleContext(com.fr.third.springframework.ldap.core.ContextSource, LdapOperationsCallback, boolean, boolean, boolean) + * @since 2.0 + */ + public static T doWithSingleContext(ContextSource contextSource, LdapOperationsCallback callback) { + return doWithSingleContext(contextSource, callback, DONT_USE_READ_ONLY, DONT_IGNORE_PARTIAL_RESULT, DONT_IGNORE_NAME_NOT_FOUND); + + } + + /** + * Construct a SingleContextSource and execute the LdapOperationsCallback using the created instance. + * This makes sure the same connection will be used for all operations inside the LdapOperationsCallback, + * which is particularly useful when working with e.g. Paged Results as these typically require the exact + * same connection to be used for all requests involving the same cookie.. + * The SingleContextSource instance will be properly disposed of once the operation has been completed. + * + * @param contextSource The target ContextSource to retrieve a DirContext from + * @param callback the callback to perform the Ldap operations + * @param useReadOnly if true, use the {@link com.fr.third.springframework.ldap.core.ContextSource#getReadOnlyContext()} + * method on the target ContextSource to get the actual DirContext instance, if false, + * use {@link com.fr.third.springframework.ldap.core.ContextSource#getReadWriteContext()}. + * @param ignorePartialResultException Used for populating this property on the created LdapTemplate instance. + * @param ignoreNameNotFoundException Used for populating this property on the created LdapTemplate instance. + * @return the result returned from the callback. + * @since 2.0 + */ + public static T doWithSingleContext(ContextSource contextSource, + LdapOperationsCallback callback, + boolean useReadOnly, + boolean ignorePartialResultException, + boolean ignoreNameNotFoundException) { + SingleContextSource singleContextSource; + if (useReadOnly) { + singleContextSource = new SingleContextSource(contextSource.getReadOnlyContext()); + } else { + singleContextSource = new SingleContextSource(contextSource.getReadWriteContext()); + } + + LdapTemplate ldapTemplate = new LdapTemplate(singleContextSource); + ldapTemplate.setIgnorePartialResultException(ignorePartialResultException); + ldapTemplate.setIgnoreNameNotFoundException(ignoreNameNotFoundException); + + try { + return callback.doWithLdapOperations(ldapTemplate); + } finally { + singleContextSource.destroy(); + } + } + + /** + * A proxy for DirContext forwarding all operation to the target DirContext, + * but making sure that no close operations will be performed. + * + * @author Mattias Hellborg Arthursson + */ + public static class NonClosingDirContextInvocationHandler implements + InvocationHandler { + + private DirContext target; + + public NonClosingDirContextInvocationHandler(DirContext target) { + this.target = target; + } + + /* + * @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 { + + String methodName = method.getName(); + if (methodName.equals("getTargetContext")) { + return target; + } else if (methodName.equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); + } else if (methodName.equals("hashCode")) { + // Use hashCode of Connection proxy. + return proxy.hashCode(); + } else if (methodName.equals("close")) { + // Never close the target context, as this class will only be + // used for operations concerning the compensating transactions. + return null; + } + + try { + return method.invoke(target, args); + } + catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/core/support/package.html b/fine-spring/src/com/fr/third/springframework/ldap/core/support/package.html new file mode 100644 index 000000000..48f06935c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/core/support/package.html @@ -0,0 +1,7 @@ + + + +Support classes for the core Spring LDAP package. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/AbsoluteFalseFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/AbsoluteFalseFilter.java new file mode 100644 index 000000000..32c589744 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/AbsoluteFalseFilter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +/** + * A filter that will always evaluate to false, as specified in RFC4526. + * + * @author Mattias Hellborg Arthursson + * @see RFC4526 + * @since 1.3.2 + */ +public class AbsoluteFalseFilter extends AbstractFilter { + public StringBuffer encode(StringBuffer buff) { + return buff.append("(|)"); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/AbsoluteTrueFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/AbsoluteTrueFilter.java new file mode 100644 index 000000000..0a4806cb9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/AbsoluteTrueFilter.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +/** + * A filter that will always evaluate to true, as specified in RFC4526. + * + * @author Mattias Hellborg Arthursson + * @see RFC4526 + * @since 1.3.2 + */ +public class AbsoluteTrueFilter extends AbstractFilter { + public StringBuffer encode(StringBuffer buff) { + buff.append("(&)"); + return buff; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/AbstractFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/AbstractFilter.java new file mode 100644 index 000000000..ba1f759d2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/AbstractFilter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +/** + * Convenience class that implements most of the methods in the Filter + * interface. + * + * @author Adam Skogman + */ +public abstract class AbstractFilter implements Filter { + + private static final int DEFAULT_BUFFER_SIZE = 256; + + @Override + public String encode() { + StringBuffer buf = new StringBuffer(DEFAULT_BUFFER_SIZE); + buf = encode(buf); + return buf.toString(); + } + + @Override + public String toString() { + return encode(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/AndFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/AndFilter.java new file mode 100644 index 000000000..6bd17a3c2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/AndFilter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +/** + * A filter for a logical AND. Example: + * + *
+ *     AndFilter filter = new AndFilter();
+ *     filter.and(new EqualsFilter("objectclass", "person");
+ *     filter.and(new EqualsFilter("cn", "Some CN");
+ *     System.out.println(filter.encode());    
+ * 
+ * + * would result in: (&(objectclass=person)(cn=Some CN)) + * + * @see com.fr.third.springframework.ldap.filter.EqualsFilter + * @author Adam Skogman + * @author Mattias Hellborg Arthursson + */ +public class AndFilter extends BinaryLogicalFilter { + + private static final String AMPERSAND = "&"; + + /* + * @see com.fr.third.springframework.ldap.filter.BinaryLogicalFilter#getLogicalOperator() + */ + protected String getLogicalOperator() { + return AMPERSAND; + } + + /** + * Add a query to the AND expression. + * + * @param query The expression to AND with the rest of the AND:ed + * expressions. + * @return This LdapAndQuery + */ + public AndFilter and(Filter query) { + append(query); + return this; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/BinaryLogicalFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/BinaryLogicalFilter.java new file mode 100644 index 000000000..1f987ad84 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/BinaryLogicalFilter.java @@ -0,0 +1,99 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.filter; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +/** + * Abstract superclass for binary logical operations, that is "AND" + * and "OR" operations. + * + * @author Mattias Hellborg Arthursson + */ +public abstract class BinaryLogicalFilter extends AbstractFilter { + + private List queryList = new LinkedList(); + + public StringBuffer encode(StringBuffer buff) { + if (queryList.size() <= 0) { + + // only output query if contains anything + return buff; + + } + else if (queryList.size() == 1) { + + // don't add the & + Filter query = queryList.get(0); + return query.encode(buff); + + } + else { + buff.append("(").append(getLogicalOperator()); + + for (Filter query : queryList) { + query.encode(buff); + } + + buff.append(")"); + + return buff; + } + } + + /** + * Implement this in subclass to return the logical operator, for example + * "&". + * + * @return the logical operator. + */ + protected abstract String getLogicalOperator(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BinaryLogicalFilter that = (BinaryLogicalFilter) o; + + if (queryList != null ? !queryList.equals(that.queryList) : that.queryList != null) return false; + + return true; + } + + @Override + public int hashCode() { + return queryList != null ? queryList.hashCode() : 0; + } + + /** + * Add a query to this logical operation. + * + * @param query the query to add. + * @return This instance. + */ + public final BinaryLogicalFilter append(Filter query) { + queryList.add(query); + return this; + } + + public final BinaryLogicalFilter appendAll(Collection subQueries) { + queryList.addAll(subQueries); + return this; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/CompareFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/CompareFilter.java new file mode 100644 index 000000000..483fc4719 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/CompareFilter.java @@ -0,0 +1,110 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.filter; + +import com.fr.third.springframework.ldap.support.LdapEncoder; + +/** + * Abstract superclass for filters that compare values. + * + * @author Mattias Hellborg Arthursson + */ +public abstract class CompareFilter extends AbstractFilter { + + private final String attribute; + + private final String value; + + private final String encodedValue; + + public CompareFilter(String attribute, String value) { + this.attribute = attribute; + this.value = value; + this.encodedValue = encodeValue(value); + } + + /** + * For testing purposes. + * + * @return the encoded value. + */ + String getEncodedValue() { + return encodedValue; + } + + /** + * Override to perform special encoding in subclass. + * + * @param value the value to encode. + * @return properly escaped value. + */ + protected String encodeValue(String value) { + return LdapEncoder.filterEncode(value); + } + + /** + * Convenience constructor for int values. + * + * @param attribute Name of attribute in filter. + * @param value The value of the attribute in the filter. + */ + public CompareFilter(String attribute, int value) { + this.attribute = attribute; + this.value = String.valueOf(value); + this.encodedValue = LdapEncoder.filterEncode(this.value); + } + + /* + * @see com.fr.third.springframework.ldap.filter.AbstractFilter#encode(java.lang.StringBuffer) + */ + public StringBuffer encode(StringBuffer buff) { + buff.append('('); + buff.append(attribute).append(getCompareString()).append(encodedValue); + buff.append(')'); + + return buff; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CompareFilter that = (CompareFilter) o; + + if (attribute != null ? !attribute.equals(that.attribute) : that.attribute != null) return false; + if (value != null ? !value.equals(that.value) : that.value != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = attribute != null ? attribute.hashCode() : 0; + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + + /** + * Implement this method in subclass to return a String representing the + * operator. The {@link EqualsFilter#getCompareString()} would for example + * return an equals sign, "=". + * + * @return the String to use as operator in the comparison for the specific + * subclass. + */ + protected abstract String getCompareString(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/EqualsFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/EqualsFilter.java new file mode 100644 index 000000000..a01eefee1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/EqualsFilter.java @@ -0,0 +1,59 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +/** + * A filter for 'equals'. The following code: + * + *
+ * EqualsFilter filter = new EqualsFilter("cn", "Some CN");
+ * System.out.println(filter.encode());
+ * 
+ * + * would result in: + * + *
+ * (cn=Some CN)
+ * 
+ * + * @author Adam Skogman + */ +public class EqualsFilter extends CompareFilter { + + private static final String EQUALS_SIGN = "="; + + public EqualsFilter(String attribute, String value) { + super(attribute, value); + } + + /** + * Convenience constructor for int values. + * + * @param attribute Name of attribute in filter. + * @param value The value of the attribute in the filter. + */ + public EqualsFilter(String attribute, int value) { + super(attribute, value); + } + + /* + * @see com.fr.third.springframework.ldap.filter.CompareFilter#getCompareString() + */ + protected String getCompareString() { + return EQUALS_SIGN; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/Filter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/Filter.java new file mode 100644 index 000000000..cd2a5ce94 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/Filter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +/** + * Common interface for LDAP filters. + * + * @author Adam Skogman + * @see RFC 1960: A String + * Representation of LDAP Search Filters + */ +public interface Filter { + + /** + * Encodes the filter to a String. + * + * @return The encoded filter in the standard String format + */ + String encode(); + + /** + * Encodes the filter to a StringBuffer. + * + * @param buf The StringBuffer to encode the filter to + * @return The same StringBuffer as was given + */ + StringBuffer encode(StringBuffer buf); + + /** + * All filters must implement equals. + * + * @param o + * @return true if the objects are equal. + */ + boolean equals(Object o); + + /** + * All filters must implement hashCode. + * + * @return the hash code according to the contract in + * {@link Object#hashCode()} + */ + int hashCode(); +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/FilterEditor.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/FilterEditor.java new file mode 100644 index 000000000..5968416f4 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/FilterEditor.java @@ -0,0 +1,30 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.filter; + +import java.beans.PropertyEditorSupport; + +/** + * Property editor for {@link Filter} instances. Creates {@link HardcodedFilter} + * instances. + * + * @author Mathieu Larchet + */ +public class FilterEditor extends PropertyEditorSupport { + public void setAsText(String text) throws IllegalArgumentException { + setValue(new HardcodedFilter(text)); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/GreaterThanOrEqualsFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/GreaterThanOrEqualsFilter.java new file mode 100644 index 000000000..ba0f119f6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/GreaterThanOrEqualsFilter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +/** + * A filter to compare >=. LDAP RFC does not allow > comparison. The following + * code: + * + *
+ * GreaterThanOrEqualsFilter filter = new GreaterThanOrEqualsFilter("cn", "Some CN");
+ * System.out.println(filter.ecode());
+ * 
+ * + * would result in: + * + *
+ * (cn>=Some CN)
+ * 
+ * + * @author Mattias Hellborg Arthursson + */ +public class GreaterThanOrEqualsFilter extends CompareFilter { + + private static final String GREATER_THAN_OR_EQUALS = ">="; + + public GreaterThanOrEqualsFilter(String attribute, String value) { + super(attribute, value); + } + + public GreaterThanOrEqualsFilter(String attribute, int value) { + super(attribute, value); + } + + protected String getCompareString() { + return GREATER_THAN_OR_EQUALS; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/HardcodedFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/HardcodedFilter.java new file mode 100644 index 000000000..b5a824d23 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/HardcodedFilter.java @@ -0,0 +1,85 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.filter; + +import com.fr.third.springframework.util.StringUtils; + +/** + * Allows hard coded parts to be included in a search filter. Particularly useful + * if some filters are specified in configuration files and these should be + * combined with other ones. + * + *
+ * Filter filter = new HardcodedFilter("(&(objectClass=user)(!(objectClass=computer)))");
+ * System.out.println(filter.toString());
+ * 
+ * + * would result in: + * (&(objectClass=user)(!(objectClass=computer))) + *

+ * Note 1: If the definition is in XML you will need to properly encode any special characters so that they are valid in an XML file, + * e.g. "&" needs to be encoded as "&amp;", e.g. + *

+ * <bean class="MyClass">
+ *   <property name="filter" value="(&amp;(objectClass=user)(!(objectClass=computer)))" />
+ * </bean>
+ * 
+ *

+ * Note 2: There will be no validation to ensure that the supplied filter is + * valid. Using this implementation to build filters from user input is strongly + * discouraged. + *

+ * @author Justen Stepka + * @author Mathieu Larchet + */ +public class HardcodedFilter extends AbstractFilter { + + private String filter; + + /** + * The hardcoded string to be used for this filter. + * @param filter the hardcoded filter string. + */ + public HardcodedFilter(String filter) { + this.filter = filter; + } + + public StringBuffer encode(StringBuffer buff) { + if (!StringUtils.hasLength(filter)) { + return buff; + } + + buff.append(filter); + return buff; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + HardcodedFilter that = (HardcodedFilter) o; + + if (filter != null ? !filter.equals(that.filter) : that.filter != null) return false; + + return true; + } + + @Override + public int hashCode() { + return filter != null ? filter.hashCode() : 0; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/LessThanOrEqualsFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/LessThanOrEqualsFilter.java new file mode 100644 index 000000000..6eaf0b66e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/LessThanOrEqualsFilter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +/** + * A filter to compare <=. LDAP RFC does not allow < comparison. The following + * code: + * + *

+ * LessThanOrEqualsFilter filter = new LessThanOrEqualsFilter("cn", "Some CN");
+ * System.out.println(filter.ecode());
+ * 
+ * + * would result in: + * + *
+ * (cn<=Some CN)
+ * 
+ * + * @author Mattias Hellborg Arthursson + */ +public class LessThanOrEqualsFilter extends CompareFilter { + + private static final String LESS_THAN_OR_EQUALS = "<="; + + public LessThanOrEqualsFilter(String attribute, String value) { + super(attribute, value); + } + + public LessThanOrEqualsFilter(String attribute, int value) { + super(attribute, value); + } + + protected String getCompareString() { + return LESS_THAN_OR_EQUALS; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/LikeFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/LikeFilter.java new file mode 100644 index 000000000..82c078b04 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/LikeFilter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +import com.fr.third.springframework.ldap.support.LdapEncoder; + +/** + * This filter allows the user to specify wildcards (*) by not escaping them in + * the filter. The following code: + * + *
+ * LikeFilter filter = new LikeFilter("cn", "foo*");
+ * System.out.println(filter.ecode());
+ * 
+ * + * would result in: + * + *
+ *  (cn=foo*)
+ * 
+ * + * @author Anders Henja + * @author Mattias Hellborg Arthursson + */ +public class LikeFilter extends EqualsFilter { + + public LikeFilter(String attribute, String value) { + super(attribute, value); + } + + protected String encodeValue(String value) { + // just return if blank string + if (value == null) { + return ""; + } + + String[] substrings = value.split("\\*", -2); + + if (substrings.length == 1) { + return LdapEncoder.filterEncode(substrings[0]); + } + + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < substrings.length; i++) { + buff.append(LdapEncoder.filterEncode(substrings[i])); + if (i < substrings.length - 1) { + buff.append("*"); + } + } + + return buff.toString(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/NotFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/NotFilter.java new file mode 100644 index 000000000..a531de453 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/NotFilter.java @@ -0,0 +1,76 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +import com.fr.third.springframework.util.Assert; + +/** + * A filter for 'not'. The following code: + * + *
+ * Filter filter = new NotFilter(new EqualsFilter("cn", "foo");
+ * System.out.println(filter.encode());
+ * 
+ * + * would result in: + * + *
+ * (!(cn = foo))
+ * 
+ * + * @author Adam Skogman + */ +public class NotFilter extends AbstractFilter { + + private final Filter filter; + + /** + * Create a filter that negates the outcome of the given filter. + * + * @param filter The filter that should be negated. + */ + public NotFilter(Filter filter) { + Assert.notNull(filter, "Filter must not be null"); + this.filter = filter; + } + + public StringBuffer encode(StringBuffer buff) { + + buff.append("(!"); + filter.encode(buff); + buff.append(')'); + + return buff; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + NotFilter notFilter = (NotFilter) o; + + if (filter != null ? !filter.equals(notFilter.filter) : notFilter.filter != null) return false; + + return true; + } + + @Override + public int hashCode() { + return filter != null ? filter.hashCode() : 0; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/NotPresentFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/NotPresentFilter.java new file mode 100644 index 000000000..0fe92c7dd --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/NotPresentFilter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.filter; + +/** + * A convenience class that combines {@code NOT} behavior with {@code present} + * behavior to allow the user to check for the non-existence of a attribute. For + * an attribute to be {@code NOT present} it must not have any values set. To + * filter on attributes at are {@code present} use the {@link PresentFilter}. + * + *
+ * NotPresentFilter filter = new NotPresentFilter("foo");
+ * System.out.println(filter.encode());
+ * 
+ * + * would result in: + * + *
+ *  (!(foo=*))
+ * 
+ * @author Jordan Hein + */ +public class NotPresentFilter extends AbstractFilter { + + private String attribute; + + /** + * Creates a new instance of a not present filter for a particular + * attribute. + * + * @param attribute the attribute expected to be not-present (ie, unset, or + * null). + */ + public NotPresentFilter(String attribute) { + this.attribute = attribute; + } + + public StringBuffer encode(StringBuffer buff) { + buff.append("(!("); + buff.append(attribute); + buff.append("=*))"); + return buff; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + NotPresentFilter that = (NotPresentFilter) o; + + if (attribute != null ? !attribute.equals(that.attribute) : that.attribute != null) return false; + + return true; + } + + @Override + public int hashCode() { + return attribute != null ? attribute.hashCode() : 0; + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/OrFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/OrFilter.java new file mode 100644 index 000000000..8f27bca30 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/OrFilter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.filter; + +/** + * Filter for logical OR. + * + *
+ * OrFilter filter = new OrFilter();
+ * filter.or(new EqualsFilter("objectclass", "person");
+ * filter.or(new EqualsFilter("objectclass", "organizationalUnit");
+ * System.out.println(filter.encode());    
+ * 
+ * + * would result in: + * (|(objectclass=person)(objectclass=organizationalUnit)) + * + * @author Adam Skogman + * @author Mattias Hellborg Arthursson + */ +public class OrFilter extends BinaryLogicalFilter { + + private static final String PIPE_SIGN = "|"; + + /** + * Add a query to the OR expression + * + * @param query The query to or with the rest of the or:ed queries. + * @return This LdapOrQuery + */ + public OrFilter or(Filter query) { + append(query); + return this; + } + + protected String getLogicalOperator() { + return PIPE_SIGN; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/PresentFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/PresentFilter.java new file mode 100644 index 000000000..5ba2f96b2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/PresentFilter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.filter; + +/** + * Filter that allows the user to check for the existence of a attribute. For an + * attribute to be {@code 'present'} it must contain a value. Attributes that do + * not contain a value are {@code 'NOT present'}. To filter on attributes that + * are {@code 'NOT present'} use the {@link NotPresentFilter} or use this filter + * in combination with a {@link NotFilter} . + * + *
+ * PresentFilter filter = new PresentFilter("foo");
+ * System.out.println(filter.encode());
+ * 
+ * + * would result in: + * + *
+ *  (foo=*)
+ * 
+ * @author Jordan Hein + */ +public class PresentFilter extends AbstractFilter { + + private String attribute; + + /** + * Creates a new instance of a present filter for a particular attribute. + * + * @param attribute the attribute expected to be present (ie, contains a + * value). + */ + public PresentFilter(String attribute) { + this.attribute = attribute; + } + + public StringBuffer encode(StringBuffer buff) { + buff.append("("); + buff.append(attribute); + buff.append("=*)"); + return buff; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PresentFilter that = (PresentFilter) o; + + if (attribute != null ? !attribute.equals(that.attribute) : that.attribute != null) return false; + + return true; + } + + @Override + public int hashCode() { + return attribute != null ? attribute.hashCode() : 0; + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/WhitespaceWildcardsFilter.java b/fine-spring/src/com/fr/third/springframework/ldap/filter/WhitespaceWildcardsFilter.java new file mode 100644 index 000000000..60501ce5d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/WhitespaceWildcardsFilter.java @@ -0,0 +1,72 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.filter; + +import com.fr.third.springframework.util.StringUtils; +import com.fr.third.springframework.ldap.support.LdapEncoder; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This filter automatically converts all whitespace to wildcards (*). The + * following code: + * + *
+ * WhitespaceWildcardsFilter filter = new WhitespaceWildcardsFilter("cn", "Some CN");
+ * System.out.println(filter.ecode());
+ * 
+ * + * would result in: (cn=*Some*CN*) + * + * @author Adam Skogman + * @author Mattias Hellborg Arthursson + */ +public class WhitespaceWildcardsFilter extends EqualsFilter { + private static Pattern starReplacePattern = Pattern.compile("\\s+"); + + public WhitespaceWildcardsFilter(String attribute, String value) { + super(attribute, value); + } + + protected String encodeValue(String value) { + + // blank string means just ONE star + if (!StringUtils.hasText(value)) { + return "*"; + } + + // filter encode so that any stars etc. are preserved + String filterEncoded = LdapEncoder.filterEncode(value.trim()); + + // Now replace all whitespace with stars + Matcher m = starReplacePattern.matcher(filterEncoded); + + // possibly 2 longer (stars at ends) + StringBuffer buff = new StringBuffer(value.length() + 2); + + buff.append('*'); + + while (m.find()) { + m.appendReplacement(buff, "*"); + } + m.appendTail(buff); + + buff.append('*'); + + return buff.toString(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/filter/package.html b/fine-spring/src/com/fr/third/springframework/ldap/filter/package.html new file mode 100644 index 000000000..5b26891b2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/filter/package.html @@ -0,0 +1,20 @@ + + + +Utility classes for dynamically building LDAP +filters. Filters can be nested and wrapped around each other: +
+AndFilter andFilter = new AndFilter();
+andFilter.and(new EqualsFilter("objectclass", "person");
+andFilter.and(new EqualsFilter("cn", "Some CN");
+OrFilter orFilter = new OrFilter();
+orFilter.or(andFilter);
+orFilter.or(new EqualsFilter("objectclass", "organizationalUnit));
+System.out.println(orFilter.encode());
+
+would result in: + +
(|(&(objectclass=person)(cn=Some CN))(objectclass=organizationalUnit))
+ + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Attribute.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Attribute.java new file mode 100755 index 000000000..1b8dc2f2f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Attribute.java @@ -0,0 +1,65 @@ +package com.fr.third.springframework.ldap.odm.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * This annotation describes the mapping of a Java field to an LDAP attribute. + *

+ * The containing class must be annotated with {@link Entry}. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + * @see Entry + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Attribute { + /** + * The Type attribute indicates whether a field is regarded as binary based + * or string based by the LDAP JNDI provider. + */ + enum Type { + /** + * A string field - returned by the JNDI LDAP provider as a {@link java.lang.String}. + */ + STRING, /** + * A binary field - returned by the JNDI LDAP provider as a byte[]. + */ + BINARY + } + + /** + * The LDAP attribute name that this field represents. + *

+ * Defaults to "" in which case the Java field name is used as the LDAP attribute name. + * + * @return The LDAP attribute name. + * + */ + String name() default ""; + + /** + * Indicates whether this field is returned by the LDAP JNDI provider as a + * String (Type.STRING) or as a + * byte[] (Type.BINARY). + * + * @return Either Type.STRING to indicate a string attribute + * or Type.BINARY to indicate a binary attribute. + */ + Type type() default Type.STRING; + + /** + * The LDAP syntax of the attribute that this field represents. + *

+ * This optional value is typically used to affect the precision of conversion + * of values between LDAP and Java, + * see {@link com.fr.third.springframework.ldap.odm.typeconversion.ConverterManager} + * and {@link com.fr.third.springframework.ldap.odm.typeconversion.impl.ConverterManagerImpl}. + * + * @return The LDAP syntax of this attribute. + */ + String syntax() default ""; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/DnAttribute.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/DnAttribute.java new file mode 100644 index 000000000..0a6a77b62 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/DnAttribute.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a field is to be automatically populated to/from the distinguished name + * of an entry. Fields annotated with this annotation will be automatically populated with values from + * the distinguished names of found entries. + *

+ * For automatic calculation of the DN of an entry to work, the {@link #index()} value + * must be specified on all DnAttribute annotations in that class, and these attribute values, + * prepended with the {@link com.fr.third.springframework.ldap.odm.annotations.Entry#base()} value will be used + * to figure out the distinguished name of entries to create and update. + *

+ * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DnAttribute { + /** + * The name of the distinguished name attribute. + * @return the attribute name. + */ + String value(); + + /** + * The index of this attribute in the calculated distinguished name of an entry. + * @return the 0-based index of this attribute. + */ + int index() default -1; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Entry.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Entry.java new file mode 100755 index 000000000..5cf688a41 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Entry.java @@ -0,0 +1,34 @@ +package com.fr.third.springframework.ldap.odm.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation marks a Java class to be persisted in an LDAP directory. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Entry { + /** + * A list of LDAP object classes that the annotated Java class represents. + *

+ * All fields will be persisted to LDAP unless annotated {@link Transient}. + * + * @return A list of LDAP classes which the annotated Java class represents. + */ + String[] objectClasses(); + + /** + * The base DN of this entry. If specified, this will be prepended to all calculated + * distinguished names for entries of the annotated class. + * + * @return the base DN for entries of this class + */ + String base() default ""; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Id.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Id.java new file mode 100755 index 000000000..fa9aae0ca --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Id.java @@ -0,0 +1,23 @@ +package com.fr.third.springframework.ldap.odm.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * This annotation marks a Java field as containing the Distinguished Name of an LDAP Entry. + *

+ * The marked field must be of type {@link javax.naming.Name} and must not + * be annotated {@link Attribute}. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + * + * @see Attribute + * @see javax.naming.Name + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Id { +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Transient.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Transient.java new file mode 100755 index 000000000..4d0d5045f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/Transient.java @@ -0,0 +1,19 @@ +package com.fr.third.springframework.ldap.odm.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation identifies a field in an {@link Entry} annotated class that + * should not be persisted to LDAP. + * + * @author Paul Harvey <paul@pauls-place.me.uk> + * + * @see Entry + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Transient { +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/package-info.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/package-info.java new file mode 100755 index 000000000..80add03fc --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/annotations/package-info.java @@ -0,0 +1,9 @@ +/** + * Provides a set of annotations to describe the mapping of a Java class to an LDAP entry. + *

+ * These annotations are for use with OdmManager. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ + +package com.fr.third.springframework.ldap.odm.annotations; \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/ObjectDirectoryMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/ObjectDirectoryMapper.java new file mode 100644 index 000000000..0fd01ee4a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/ObjectDirectoryMapper.java @@ -0,0 +1,99 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.core; + +import com.fr.third.springframework.LdapDataEntry; +import com.fr.third.springframework.ldap.filter.Filter; + +import javax.naming.Name; + +/** + * The ObjectDirectoryMapper keeps track of managed class metadata and is used by {@link com.fr.third.springframework.ldap.core.LdapTemplate} + * to map to/from entity objects annotated with the annotations specified in the {@link com.fr.third.springframework.ldap.odm.annotations} + * package. Instances of this class are typically intended for internal use only. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public interface ObjectDirectoryMapper { + + /** + * Used to convert from Java representation of an Ldap Entry when writing to + * the Ldap directory + * + * @param entry - The entry to convert. + * @param context - The LDAP context to store the converted entry + * @throws com.fr.third.springframework.ldap.NamingException on error. + */ + void mapToLdapDataEntry(Object entry, LdapDataEntry context); + + /** + * Used to convert from the JNDI LDAP representation of an Entry to the Java representation when reading from LDAP. + * @throws com.fr.third.springframework.ldap.NamingException on error. + */ + T mapFromLdapDataEntry(LdapDataEntry ctx, Class clazz); + + /** + * Get the distinguished name for the specified object. + * + * @param entry the entry to get distinguished name for. + * @return the distinguished name of the entry. + * @throws com.fr.third.springframework.ldap.NamingException on error. + */ + Name getId(Object entry); + + /** + * Set the distinguished name for the specified object. + * + * @param entry the entry to set the name on + * @param id the name to set + * @throws com.fr.third.springframework.ldap.NamingException on error. + */ + void setId(Object entry, Name id); + + Name getCalculatedId(Object entry); + + /** + * Use the specified search filter and return a new one that only applies to entries of the specified class. + * In effect this means padding the original filter with an objectclass condition. + * + * @param clazz the class. + * @param baseFilter the filter we want to use. + * @return the original filter, modified so that it only applies to entries of the specified class. + * @throws com.fr.third.springframework.ldap.NamingException on error. + */ + Filter filterFor(Class clazz, Filter baseFilter); + + /** + * Get the attribute corresponding to the specified field name. + * @param clazz the clazz. + * @param fieldName the field name. + * @return the attribute name. + * @throws IllegalArgumentException if the fieldName is not present in the class or if + * it is not mapped to an attribute. + */ + String attributeFor(Class clazz, String fieldName); + + /** + * Check if the specified class is already managed by this instance; if not, check the metadata and add the class to the + * managed classes. + * + * @param clazz the class to manage. + * @throws com.fr.third.springframework.ldap.NamingException on error. + */ + void manageClass(Class clazz); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/OdmException.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/OdmException.java new file mode 100755 index 000000000..cd5ce45b0 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/OdmException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.core; + +import com.fr.third.springframework.ldap.NamingException; + +/** + * The root of the Spring LDAP ODM exception hierarchy. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + * + */ +@SuppressWarnings("serial") +public class OdmException extends NamingException { + public OdmException(String message) { + super(message); + } + + public OdmException(String message, Throwable e) { + super(message, e); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/AttributeMetaData.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/AttributeMetaData.java new file mode 100755 index 000000000..f5381dfb1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/AttributeMetaData.java @@ -0,0 +1,314 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.core.impl; + +import com.fr.third.springframework.ldap.UncategorizedLdapException; +import com.fr.third.springframework.ldap.odm.annotations.Attribute; +import com.fr.third.springframework.ldap.odm.annotations.DnAttribute; +import com.fr.third.springframework.ldap.odm.annotations.Id; +import com.fr.third.springframework.ldap.odm.annotations.Transient; + +import javax.naming.Name; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/* + * Extract attribute meta-data from the @Attribute annotation, the @Id annotation + * and via reflection. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +/* package */ final class AttributeMetaData { + private static final CaseIgnoreString OBJECT_CLASS_ATTRIBUTE_CI=new CaseIgnoreString("objectclass"); + + // Name of the LDAP attribute from the @Attribute annotation + private CaseIgnoreString name; + + // Syntax of the LDAP attribute from the @Attribute annotation + private String syntax; + + // Whether this attribute is binary from the @Attribute annotation + private boolean isBinary; + + // The Java field corresponding to this meta-data + private final Field field; + + // The Java class of the field corresponding to this meta data + // This is the actual scalar type meaning that if the field is + // List then the valueClass will be String + private Class valueClass; + + // Is this field annotated @Id + private boolean isId; + + // Is this field multi-valued represented by a List + private boolean isCollection; + + private Class collectionClass; + + // Is this the objectClass attribute + private boolean isObjectClass; + + private boolean isTransient = false; + + private DnAttribute dnAttribute; + + // Extract information from the @Attribute annotation: + // syntax, isBinary, isObjectClass and name. + private boolean processAttributeAnnotation(Field field) { + // Default to no syntax specified + syntax = ""; + + // Default to a String based attribute + isBinary = false; + + // Default name of attribute to the name of the field + name = new CaseIgnoreString(field.getName()); + + // We have not yet found the @Attribute annotation + boolean foundAnnotation=false; + + // Grab the @Attribute annotation + Attribute attribute = field.getAnnotation(Attribute.class); + + // Did we find the annotation? + if (attribute != null) { + // Pull attribute name, syntax and whether attribute is binary + // from the annotation + foundAnnotation=true; + String localAttributeName = attribute.name(); + // Would be more efficient to use !isEmpty - but that then makes us Java 6 dependent + if (localAttributeName != null && localAttributeName.length()>0) { + name = new CaseIgnoreString(localAttributeName); + } + syntax = attribute.syntax(); + isBinary = attribute.type() == Attribute.Type.BINARY; + } + + isObjectClass=name.equals(OBJECT_CLASS_ATTRIBUTE_CI); + + return foundAnnotation; + } + + // Extract reflection information from the field: + // valueClass, isList + private void determineFieldType(Field field) { + // Determine the class of data stored in the field + Class fieldType = field.getType(); + + isCollection = Collection.class.isAssignableFrom(fieldType); + + valueClass=null; + if (!isCollection) { + // It's not a list so assume its single valued - so just take the field type + valueClass = fieldType; + } else { + determineCollectionClass(fieldType); + // It's multi-valued - so we need to look at the signature in + // the class file to find the generic type - this is supported for class file + // format 49 and greater which corresponds to java 5 and later. + ParameterizedType paramType; + try { + paramType = (ParameterizedType)field.getGenericType(); + } catch (ClassCastException e) { + throw new MetaDataException(String.format("Can't determine destination type for field %1$s in Entry class %2$s", + field, field.getDeclaringClass()), e); + } + Type[] actualParamArguments = paramType.getActualTypeArguments(); + if (actualParamArguments.length == 1) { + if (actualParamArguments[0] instanceof Class) { + valueClass = (Class)actualParamArguments[0]; + } else { + if (actualParamArguments[0] instanceof GenericArrayType) { + // Deal with arrays + Type type=((GenericArrayType)actualParamArguments[0]).getGenericComponentType(); + if (type instanceof Class) { + valueClass=Array.newInstance((Class)type, 0).getClass(); + } + } + } + } + } + + // Check we have been able to determine the value class + if (valueClass==null) { + throw new MetaDataException(String.format("Can't determine destination type for field %1$s in class %2$s", + field, field.getDeclaringClass())); + } + } + + @SuppressWarnings("unchecked") + private void determineCollectionClass(Class fieldType) { + if(fieldType.isInterface()) { + if(Collection.class.equals(fieldType) || List.class.equals(fieldType)) { + collectionClass = ArrayList.class; + } else if(SortedSet.class.equals(fieldType)) { + collectionClass = TreeSet.class; + } else if(Set.class.isAssignableFrom(fieldType)) { + collectionClass = LinkedHashSet.class; + } else { + throw new MetaDataException(String.format("Collection class %s is not supported", fieldType)); + } + } else { + collectionClass = (Class) fieldType; + } + } + + @SuppressWarnings("unchecked") + public Collection newCollectionInstance() { + try { + return (Collection) collectionClass.newInstance(); + } catch (Exception e) { + throw new UncategorizedLdapException("Failed to instantiate collection class", e); + } + } + + // Extract information from the @Id annotation: + // isId + private boolean processIdAnnotation(Field field, Class fieldType) { + // Are we dealing with the Id field? + isId=field.getAnnotation(Id.class)!=null; + + if (isId) { + // It must be of type Name or a subclass of that of + if (!Name.class.isAssignableFrom(fieldType)) { + throw new MetaDataException( + String.format("The id field must be of type javax.naming.Name or a subclass that of in Entry class %1$s", + field.getDeclaringClass())); + } + } + + return isId; + } + + // Extract meta-data from the given field + public AttributeMetaData(Field field) { + this.field=field; + + this.dnAttribute = field.getAnnotation(DnAttribute.class); + if(this.dnAttribute != null && !field.getType().equals(String.class)) { + throw new MetaDataException(String.format("%s is of type %s, but only String attributes can be declared as @DnAttributes", + field.toString(), + field.getType().toString())); + } + + Transient transientAnnotation = field.getAnnotation(Transient.class); + if(transientAnnotation != null) { + this.isTransient = true; + return; + } + + // Reflection data + determineFieldType(field); + + + // Data from the @Attribute annotation + boolean foundAttributeAnnotation=processAttributeAnnotation(field); + + // Data from the @Id annotation + boolean foundIdAnnoation=processIdAnnotation(field, valueClass); + + // Check that the field has not been annotated with both @Attribute and with @Id + if (foundAttributeAnnotation && foundIdAnnoation) { + throw new MetaDataException( + String.format("You may not specifiy an %1$s annoation and an %2$s annotation on the same field, error in field %3$s in Entry class %4$s", + Id.class, Attribute.class, field.getName(), field.getDeclaringClass())); + } + + // If this is the objectclass attribute then it must be of type List + if (isObjectClass() && (!isCollection() || valueClass!=String.class)) { + throw new MetaDataException(String.format("The type of the objectclass attribute must be List in classs %1$s", + field.getDeclaringClass())); + } + } + + + public String getSyntax() { + return syntax; + } + + public boolean isBinary() { + return isBinary; + } + + public Field getField() { + return field; + } + + public CaseIgnoreString getName() { + return name; + } + + public boolean isCollection() { + return isCollection; + } + + public boolean isId() { + return isId; + } + + public boolean isTransient() { + return isTransient; + } + + public DnAttribute getDnAttribute() { + return dnAttribute; + } + + public boolean isDnAttribute() { + return dnAttribute != null; + } + + public boolean isObjectClass() { + return isObjectClass; + } + + public Class getValueClass() { + return valueClass; + } + + public Class getJndiClass() { + if(isBinary()) { + return byte[].class; + } else if(Name.class.isAssignableFrom(valueClass)) { + return Name.class; + } else { + return String.class; + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("name=%1$s | field=%2$s | valueClass=%3$s | syntax=%4$s| isBinary=%5$s | isId=%6$s | isList=%7$s | isObjectClass=%8$s", + getName(), getField(), getValueClass(), getSyntax(), isBinary(), isId(), isCollection(), isObjectClass()); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/CaseIgnoreString.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/CaseIgnoreString.java new file mode 100755 index 000000000..c2f70dfa3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/CaseIgnoreString.java @@ -0,0 +1,49 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.core.impl; + +import com.fr.third.springframework.util.Assert; + +// A case independent String wrapper. +/* package */ final class CaseIgnoreString implements Comparable { + private final String string; + private final int hashCode; + + public CaseIgnoreString(String string) { + Assert.notNull(string, "string must not be null"); + this.string = string; + hashCode = string.toUpperCase().hashCode(); + } + + public boolean equals(Object other) { + return other instanceof CaseIgnoreString && + ((CaseIgnoreString)other).string.equalsIgnoreCase(string); + } + + public int hashCode() { + return hashCode; + } + + public int compareTo(CaseIgnoreString other) { + CaseIgnoreString cis = other; + return String.CASE_INSENSITIVE_ORDER.compare(string, cis.string); + } + + public String toString() { + return string; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java new file mode 100644 index 000000000..1fc9020fd --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java @@ -0,0 +1,484 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.core.impl; + +import java.lang.reflect.Field; +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 java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.LdapDataEntry; +import com.fr.third.springframework.core.SpringVersion; +import com.fr.third.springframework.ldap.filter.AndFilter; +import com.fr.third.springframework.ldap.filter.EqualsFilter; +import com.fr.third.springframework.ldap.filter.Filter; +import com.fr.third.springframework.ldap.odm.annotations.DnAttribute; +import com.fr.third.springframework.ldap.odm.core.ObjectDirectoryMapper; +import com.fr.third.springframework.ldap.odm.typeconversion.ConverterManager; +import com.fr.third.springframework.ldap.odm.typeconversion.impl.ConversionServiceConverterManager; +import com.fr.third.springframework.ldap.odm.typeconversion.impl.ConverterManagerImpl; +import com.fr.third.springframework.ldap.support.LdapNameBuilder; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.ReflectionUtils; + +/** + * Default implementation of {@link ObjectDirectoryMapper}. Unless you need to explicitly configure + * converters there is typically no reason to explicitly consider yourself with this class. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { + private static final Logger LOG = LoggerFactory.getLogger(DefaultObjectDirectoryMapper.class); + + // The converter manager to use to translate values between LDAP and Java + private ConverterManager converterManager; + + private static final String OBJECT_CLASS_ATTRIBUTE="objectclass"; + private static final CaseIgnoreString OBJECT_CLASS_ATTRIBUTE_CI=new CaseIgnoreString(OBJECT_CLASS_ATTRIBUTE); + + + public DefaultObjectDirectoryMapper() { + this.converterManager = createDefaultConverterManager(); + } + + private static ConverterManager createDefaultConverterManager() { + String springVersion = SpringVersion.getVersion(); + if(springVersion == null) { + LOG.debug("Could not determine the Spring Version. Guessing > Spring 3.0. If this does not work, please ensure to explicitly set converterManager"); + return new ConversionServiceConverterManager(); + } else if(springVersion.compareTo("3.0") > 0) { + return new ConversionServiceConverterManager(); + } else { + return new ConverterManagerImpl(); + } + } + + public void setConverterManager(ConverterManager converterManager) { + this.converterManager = converterManager; + } + + static final class EntityData { + final ObjectMetaData metaData; + final Filter ocFilter; + + private EntityData(ObjectMetaData metaData, Filter ocFilter) { + this.metaData=metaData; + this.ocFilter=ocFilter; + } + } + + // A map of managed classes to to meta data about those classes + private final ConcurrentMap, EntityData> metaDataMap=new ConcurrentHashMap, EntityData>(); + + private EntityData getEntityData(Class managedClass) { + EntityData result = metaDataMap.get(managedClass); + if (result == null) { + return addManagedClass(managedClass); + } + return result; + } + + @Override + public void manageClass(Class clazz) { + // This throws exception if data is invalid + getEntityData(clazz); + } + + /** + * Adds an {@link com.fr.third.springframework.ldap.odm.annotations} annotated class to the set + * managed by this OdmManager. + * + * @param managedClass The class to add to the managed set. + */ + private EntityData addManagedClass(Class managedClass) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Adding class %1$s to managed set", managedClass)); + } + + // Extract the meta-data from the class + ObjectMetaData metaData=new ObjectMetaData(managedClass); + + // Check we can construct the target type - it must have a zero argument public constructor + try { + managedClass.getConstructor(); + } catch (NoSuchMethodException e) { + throw new InvalidEntryException(String.format( + "The class %1$s must have a zero argument constructor to be an Entry", managedClass), e); + } + + // Check we have all of the necessary converters for the class + for (Field field : metaData) { + AttributeMetaData attributeInfo = metaData.getAttribute(field); + if (!attributeInfo.isTransient() && !attributeInfo.isId() && !(attributeInfo.isObjectClass())) { + verifyConversion(managedClass, field, attributeInfo); + } + } + + // Filter so we only read the object classes supported by the managedClass + AndFilter ocFilter = new AndFilter(); + for (CaseIgnoreString oc : metaData.getObjectClasses()) { + ocFilter.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, oc.toString())); + } + + EntityData newValue = new EntityData(metaData, ocFilter); + EntityData previousValue = metaDataMap.putIfAbsent(managedClass, newValue); + // Just in case someone beat us to it + if(previousValue != null) { + return previousValue; + } + + return newValue; + } + + private void verifyConversion(Class managedClass, Field field, AttributeMetaData attributeInfo) { + Class jndiClass = attributeInfo.getJndiClass(); + Class javaClass = attributeInfo.getValueClass(); + if (!converterManager.canConvert(jndiClass, attributeInfo.getSyntax(), javaClass)) { + throw new InvalidEntryException(String.format( + "Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s", + jndiClass, javaClass, field.getName(), managedClass)); + } + if (!converterManager.canConvert(javaClass, attributeInfo.getSyntax(), jndiClass)) { + throw new InvalidEntryException(String.format( + "Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s", + javaClass, jndiClass, field.getName(), managedClass)); + } + } + + @Override + public void mapToLdapDataEntry(Object entry, LdapDataEntry context) { + ObjectMetaData metaData=getEntityData(entry.getClass()).metaData; + + Attribute objectclassAttribute = context.getAttributes().get(OBJECT_CLASS_ATTRIBUTE); + if(objectclassAttribute == null || objectclassAttribute.size() == 0) { + // Object classes are set from the metadata obtained from the @Entity annotation, + // but only if this is a new entry. + int numOcs=metaData.getObjectClasses().size(); + CaseIgnoreString[] metaDataObjectClasses=metaData.getObjectClasses().toArray(new CaseIgnoreString[numOcs]); + + String[] stringOcs=new String[numOcs]; + for (int ocIndex=0; ocIndex targetClass = attributeInfo.getJndiClass(); + // Multi valued? + if (!attributeInfo.isCollection()) { + populateSingleValueAttribute(entry, context, field, attributeInfo, targetClass); + + } else { + // Multi-valued + populateMultiValueAttribute(entry, context, field, attributeInfo, targetClass); + + } + } catch (IllegalAccessException e) { + throw new InvalidEntryException(String.format("Can't set attribute %1$s", attributeInfo.getName()), + e); + } + } + } + } + + private void populateMultiValueAttribute(Object entry, LdapDataEntry context, Field field, AttributeMetaData attributeInfo, Class targetClass) throws IllegalAccessException { + // We need to build up a list of of the values + List attributeValues = new ArrayList(); + // Get the list of values + Collection fieldValues = (Collection)field.get(entry); + // Ignore null lists + if (fieldValues != null) { + for (final Object o : fieldValues) { + // Ignore null values + if (o != null) { + attributeValues.add(converterManager.convert(o, attributeInfo.getSyntax(), + targetClass)); + } + } + context.setAttributeValues(attributeInfo.getName().toString(), attributeValues.toArray()); + } + } + + private void populateSingleValueAttribute(Object entry, LdapDataEntry context, Field field, AttributeMetaData attributeInfo, Class targetClass) throws IllegalAccessException { + // Single valued - get the value of the field + Object fieldValue = field.get(entry); + // Ignore null field values + if (fieldValue != null) { + // Convert the field value to the required type and write it into the JNDI context + context.setAttributeValue(attributeInfo.getName().toString(), converterManager.convert(fieldValue, + attributeInfo.getSyntax(), targetClass)); + } else { + context.setAttributeValue(attributeInfo.getName().toString(), null); + } + } + + @Override + public T mapFromLdapDataEntry(LdapDataEntry context, Class clazz) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Converting to Java Entry class %1$s from %2$s", clazz, context)); + } + + // The Java representation of the LDAP entry + T result; + + ObjectMetaData metaData=getEntityData(clazz).metaData; + + try { + // The result class must have a zero argument constructor + result = clazz.newInstance(); + + // Build a map of JNDI attribute names to values + Map attributeValueMap = new HashMap(); + // Get a NamingEnumeration to loop through the JNDI attributes in the entry + Attributes attributes = context.getAttributes(); + NamingEnumeration attributesEnumeration = attributes.getAll(); + // Loop through all of the JNDI attributes + while (attributesEnumeration.hasMoreElements()) { + Attribute currentAttribute = attributesEnumeration.nextElement(); + // Add the current attribute to the map keyed on the lowercased (case indep) id of the attribute + attributeValueMap.put(new CaseIgnoreString(currentAttribute.getID()), currentAttribute); + } + + + // If this is the objectclass attribute then check that values correspond to the metadata we have + // for the Java representation + Attribute ocAttribute = attributeValueMap.get(OBJECT_CLASS_ATTRIBUTE_CI); + if (ocAttribute != null) { + // Get all object class values from the JNDI attribute + Set objectClassesFromJndi = new HashSet(); + NamingEnumeration objectClassesFromJndiEnum = ocAttribute.getAll(); + while (objectClassesFromJndiEnum.hasMoreElements()) { + objectClassesFromJndi.add(new CaseIgnoreString((String)objectClassesFromJndiEnum.nextElement())); + } + // OK - checks its the same as the meta-data we have + if(!collectionContainsAll(objectClassesFromJndi, metaData.getObjectClasses())) { + return null; + } + } else { + throw new InvalidEntryException(String.format("No object classes were returned for class %1$s", + clazz.getName())); + } + + // Now loop through all the fields in the Java representation populating it with values from the + // attributeValueMap + for (Field field : metaData) { + // Get the current field + AttributeMetaData attributeInfo = metaData.getAttribute(field); + // We deal with the Id field specially + Name dn = context.getDn(); + if (!attributeInfo.isTransient() && !attributeInfo.isId()) { + // Not the ID - but is is multi valued? + if (!attributeInfo.isCollection()) { + // No - its single valued, grab the JNDI attribute that corresponds to the metadata on the + // current field + populateSingleValueField(result, attributeValueMap, field, attributeInfo); + } else { + // We are dealing with a multi valued attribute + populateMultiValueField(result, attributeValueMap, field, attributeInfo); + } + } else if(attributeInfo.isId()) { // The id field + field.set(result, converterManager.convert(dn, attributeInfo.getSyntax(), + attributeInfo.getValueClass())); + } + + DnAttribute dnAttribute = attributeInfo.getDnAttribute(); + if(dnAttribute != null) { + String dnValue; + int index = dnAttribute.index(); + + if(index != -1) { + dnValue = LdapUtils.getStringValue(dn, index); + } else { + dnValue = LdapUtils.getStringValue(dn, dnAttribute.value()); + } + field.set(result, dnValue); + } + } + } catch (NamingException ne) { + throw new InvalidEntryException(String.format("Problem creating %1$s from LDAP Entry %2$s", + clazz, context), ne); + } catch (IllegalAccessException iae) { + throw new InvalidEntryException(String.format( + "Could not create an instance of %1$s could not access field", clazz.getName()), iae); + } catch (InstantiationException ie) { + throw new InvalidEntryException(String.format("Could not instantiate %1$s", clazz), ie); + } + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Converted object - %1$s", result)); + } + + return result; + } + + private void populateMultiValueField(T result, Map attributeValueMap, Field field, AttributeMetaData attributeInfo) throws NamingException, IllegalAccessException { + // We need to build up a list of values + Collection fieldValues = attributeInfo.newCollectionInstance(); + // Grab the attribute from the JNDI representation + Attribute currentAttribute = attributeValueMap.get(attributeInfo.getName()); + // There is no guarantee that this attribute is present in the directory - so ignore nulls + if (currentAttribute != null) { + // Loop through the values of the JNDI attribute + NamingEnumeration valuesEmumeration = currentAttribute.getAll(); + while (valuesEmumeration.hasMore()) { + // Get the current value + Object value = valuesEmumeration.nextElement(); + // Check the value is not null + if (value != null) { + // Convert the value to its Java representation and add it to our working list + fieldValues.add(converterManager.convert(value, attributeInfo.getSyntax(), + attributeInfo.getValueClass())); + } + } + } + // Now we need to set the List in to a Java object + field.set(result, fieldValues); + } + + private void populateSingleValueField(T result, Map attributeValueMap, Field field, AttributeMetaData attributeInfo) throws NamingException, IllegalAccessException { + Attribute attribute = attributeValueMap.get(attributeInfo.getName()); + // There is no guarantee that this attribute is present in the directory - so ignore nulls + if (attribute != null) { + // Grab the JNDI value + Object value = attribute.get(); + // Check the value is not null + if (value != null) { + // Convert the JNDI value to its Java representation - this will throw if the + // conversion fails + Object convertedValue = converterManager.convert(value, attributeInfo.getSyntax(), + attributeInfo.getValueClass()); + // Set it in the Java version + field.set(result, convertedValue); + } + } + } + + @Override + public Name getId(Object entry) { + try { + return (Name) getIdField(entry).get(entry); + } catch (Exception e) { + throw new InvalidEntryException(String.format("Can't get Id field from Entry %1$s", entry), + e); + } + } + + private Field getIdField(Object entry) { + return getEntityData(entry.getClass()).metaData.getIdAttribute().getField(); + } + + @Override + public void setId(Object entry, Name id) { + try { + getIdField(entry).set(entry, id); + } catch (Exception e) { + throw new InvalidEntryException( + String.format("Can't set Id field on Entry %s to %s", entry, id), e); + } + } + + @Override + public Name getCalculatedId(Object entry) { + Assert.notNull(entry, "Entry must not be null"); + EntityData entityData = getEntityData(entry.getClass()); + if(entityData.metaData.canCalculateDn()) { + Set dnAttributes = entityData.metaData.getDnAttributes(); + LdapNameBuilder ldapNameBuilder = LdapNameBuilder.newInstance(entityData.metaData.getBase()); + + for (AttributeMetaData dnAttribute : dnAttributes) { + Object dnFieldValue = ReflectionUtils.getField(dnAttribute.getField(), entry); + if(dnFieldValue == null) { + throw new IllegalStateException( + String.format("DnAttribute for field %s on class %s is null; cannot build DN", + dnAttribute.getField().getName(), entry.getClass().getName())); + } + + ldapNameBuilder.add(dnAttribute.getDnAttribute().value(), dnFieldValue.toString()); + } + + return ldapNameBuilder.build(); + } + + return null; + } + + @Override + public Filter filterFor(Class clazz, Filter baseFilter) { + Filter ocFilter = getEntityData(clazz).ocFilter; + + if(baseFilter == null) { + return ocFilter; + } + + AndFilter andFilter = new AndFilter(); + return andFilter.append(ocFilter).append(baseFilter); + } + + @Override + public String attributeFor(Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + AttributeMetaData attributeMetaData = + getEntityData(clazz).metaData.getAttribute(field); + return attributeMetaData.getName().toString(); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException( + String.format("Field %s cannot be found in class %s", fieldName, clazz), e); + } + } + + // For testing purposes + ConcurrentMap, EntityData> getMetaDataMap() { + return metaDataMap; + } + + static boolean collectionContainsAll(Collection collection, Set shouldBePresent) { + for (Object o : shouldBePresent) { + if(!collection.contains(o)) { + return false; + } + } + + return true; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/InvalidEntryException.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/InvalidEntryException.java new file mode 100755 index 000000000..883dd1b74 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/InvalidEntryException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.core.impl; + +import com.fr.third.springframework.ldap.odm.core.OdmException; + +/** + * Thrown to indicate that an instance is not suitable for persisting in the LDAP directory. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + * + */ +@SuppressWarnings("serial") +public class InvalidEntryException extends OdmException { + public InvalidEntryException(String message) { + super(message); + } + + public InvalidEntryException(String message, Throwable reason) { + super(message, reason); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/MetaDataException.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/MetaDataException.java new file mode 100755 index 000000000..ebda7f849 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/MetaDataException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.core.impl; + +import com.fr.third.springframework.ldap.odm.core.OdmException; + +/** + * Thrown to indicate an error in the annotated meta-data. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + * + */ +@SuppressWarnings("serial") +public class MetaDataException extends OdmException { + public MetaDataException(String message) { + super(message); + } + + public MetaDataException(String message, Throwable reason) { + super(message, reason); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/ObjectMetaData.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/ObjectMetaData.java new file mode 100755 index 000000000..8c98f7695 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/ObjectMetaData.java @@ -0,0 +1,211 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.core.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.odm.annotations.Entry; +import com.fr.third.springframework.ldap.odm.annotations.Id; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.StringUtils; + +import javax.naming.Name; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/* + * An internal class to process the meta-data and reflection data for an entry. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +/* package */ final class ObjectMetaData implements Iterable { + private static final Logger LOG = LoggerFactory.getLogger(ObjectMetaData.class); + + private AttributeMetaData idAttribute; + + private Map fieldToAttribute = new HashMap(); + + private Set dnAttributes = new TreeSet(new Comparator() { + @Override + public int compare(AttributeMetaData a1, AttributeMetaData a2) { + if(!a1.isDnAttribute() || !a2.isDnAttribute()) { + // Not interesting to compare these. + return 0; + } + + return Integer.valueOf(a1.getDnAttribute().index()).compareTo(a2.getDnAttribute().index()); + } + }); + + private boolean indexedDnAttributes = false; + + private Set objectClasses = new LinkedHashSet(); + + private Name base = LdapUtils.emptyLdapName(); + + public Set getObjectClasses() { + return objectClasses; + } + + public AttributeMetaData getIdAttribute() { + return idAttribute; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return fieldToAttribute.keySet().iterator(); + } + + public AttributeMetaData getAttribute(Field field) { + return fieldToAttribute.get(field); + } + + public ObjectMetaData(Class clazz) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Extracting metadata from %1$s", clazz)); + } + + // Get object class metadata - the @Entity annotation + Entry entity = clazz.getAnnotation(Entry.class); + if (entity != null) { + // Default objectclass name to the class name unless it's specified + // in @Entity(name={objectclass1, objectclass2}); + String[] localObjectClasses = entity.objectClasses(); + if (localObjectClasses != null && localObjectClasses.length > 0 && localObjectClasses[0].length() > 0) { + for (String localObjectClass:localObjectClasses) { + objectClasses.add(new CaseIgnoreString(localObjectClass)); + } + } else { + objectClasses.add(new CaseIgnoreString(clazz.getSimpleName())); + } + + String base = entity.base(); + if(StringUtils.hasText(base)) { + this.base = LdapUtils.newLdapName(base); + } + } else { + throw new MetaDataException(String.format("Class %1$s must have a class level %2$s annotation", clazz, + Entry.class)); + } + + // Check the class is final + if (!Modifier.isFinal(clazz.getModifiers())) { + LOG.warn(String.format("The Entry class %1$s should be declared final", clazz.getSimpleName())); + } + + // Get field meta-data - the @Attribute annotation + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + // So we can write to private fields + field.setAccessible(true); + + // Skip synthetic or static fields + if (Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) { + continue; + } + + AttributeMetaData currentAttributeMetaData=new AttributeMetaData(field); + if (currentAttributeMetaData.isId()) { + if (idAttribute!=null) { + // There can be only one id field + throw new MetaDataException( + String.format("You man have only one field with the %1$s annotation in class %2$s", Id.class, clazz)); + } + idAttribute=currentAttributeMetaData; + } + fieldToAttribute.put(field, currentAttributeMetaData); + + if(currentAttributeMetaData.isDnAttribute()) { + dnAttributes.add(currentAttributeMetaData); + } + } + + if (idAttribute == null) { + throw new MetaDataException( + String.format("All Entry classes must define a field with the %1$s annotation, error in class %2$s", Id.class, + clazz)); + } + + postProcessDnAttributes(clazz); + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Extracted metadata from %1$s as %2$s", clazz, this)); + } + } + + private void postProcessDnAttributes(Class clazz) { + boolean hasIndexed = false; + boolean hasNonIndexed = false; + + for (AttributeMetaData dnAttribute : dnAttributes) { + int declaredIndex = dnAttribute.getDnAttribute().index(); + + if(declaredIndex != -1) { + hasIndexed = true; + } + + if(declaredIndex == -1) { + hasNonIndexed = true; + } + } + + if(hasIndexed && hasNonIndexed) { + throw new MetaDataException(String.format("At least one DnAttribute declared on class %s is indexed, " + + "which means that all DnAttributes must be indexed", clazz.toString())); + } + + indexedDnAttributes = hasIndexed; + } + + int size() { + return fieldToAttribute.size(); + } + + boolean canCalculateDn() { + return dnAttributes.size() > 0 && indexedDnAttributes; + } + + public Set getDnAttributes() { + return dnAttributes; + } + + Name getBase() { + return base; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("objectsClasses=%1$s | idField=%2$s | attributes=%3$s", + objectClasses, idAttribute.getName(), fieldToAttribute); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/UnmanagedClassException.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/UnmanagedClassException.java new file mode 100755 index 000000000..e7755aec1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/UnmanagedClassException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.core.impl; + +import com.fr.third.springframework.ldap.odm.core.OdmException; + +/** + * Thrown when an OdmManager method is called with a class + * which is not being managed by the OdmManager. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + * + */ +@SuppressWarnings("serial") +public class UnmanagedClassException extends OdmException { + public UnmanagedClassException(String message, Throwable reason) { + super(message, reason); + } + + public UnmanagedClassException(String message) { + super(message); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/package-info.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/package-info.java new file mode 100755 index 000000000..1d7b864b2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/impl/package-info.java @@ -0,0 +1,9 @@ +/** + * Provides a single public class which implements OdmManager. + *

+ * The OdmManager implementation works in conjunction with {@link com.fr.third.springframework.ldap.odm.typeconversion} to provide + * conversion between the representation of attributes in LDAP and in Java. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +package com.fr.third.springframework.ldap.odm.core.impl; \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/core/package-info.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/package-info.java new file mode 100755 index 000000000..7e33dbab0 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/core/package-info.java @@ -0,0 +1,10 @@ +/** + * Provides an OdmManager interface for interaction with an LDAP directory. + *

+ * Implementations of this interface are intended to be used in conjunction with classes + * annotated with {@link com.fr.third.springframework.ldap.odm.annotations}. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ + +package com.fr.third.springframework.ldap.odm.core; \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/ConverterException.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/ConverterException.java new file mode 100755 index 000000000..257021762 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/ConverterException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.typeconversion; + +import com.fr.third.springframework.ldap.NamingException; + +/** + * Thrown by the conversion framework to indicate an error condition - typically a failed type conversion. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +@SuppressWarnings("serial") +public final class ConverterException extends NamingException { + public ConverterException(final String message) { + super(message); + } + + public ConverterException(final String message, final Throwable e) { + super(message, e); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/ConverterManager.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/ConverterManager.java new file mode 100755 index 000000000..a349985db --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/ConverterManager.java @@ -0,0 +1,47 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.typeconversion; + +/** + * A simple interface to be implemented to provide type conversion functionality. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +public interface ConverterManager { + /** + * Determine whether this converter manager is able to carry out a specified conversion. + * + * @param fromClass Convert from the fromClass. + * @param syntax Using the LDAP syntax (may be null). + * @param toClass To the toClass. + * @return True if the conversion is supported, false otherwise. + */ + boolean canConvert(Class fromClass, String syntax, Class toClass); + + /** + * Convert a given source object with an optional LDAP syntax to an instance of a given class. + * + * @param The class to convert to. + * @param source The object to convert. + * @param syntax The LDAP syntax to use (may be null). + * @param toClass The class to convert to. + * @return The converted object. + * + * @throws ConverterException If the conversion can not be successfully completed. + */ + T convert(Object source, String syntax, Class toClass); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/ConversionServiceConverterManager.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/ConversionServiceConverterManager.java new file mode 100644 index 000000000..7c22765a6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/ConversionServiceConverterManager.java @@ -0,0 +1,95 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.typeconversion.impl; + +import com.fr.third.springframework.core.convert.support.GenericConversionService; +import com.fr.third.springframework.ldap.odm.typeconversion.ConverterManager; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.ClassUtils; +import com.fr.third.springframework.util.ReflectionUtils; + +import javax.naming.Name; + +/** + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class ConversionServiceConverterManager implements ConverterManager { + private GenericConversionService conversionService; + private static final String DEFAULT_CONVERSION_SERVICE_CLASS = + "com.fr.third.springframework.core.convert.support.DefaultConversionService"; + + public ConversionServiceConverterManager(GenericConversionService conversionService) { + this.conversionService = conversionService; + } + + public ConversionServiceConverterManager() { + ClassLoader defaultClassLoader = ClassUtils.getDefaultClassLoader(); + if(ClassUtils.isPresent(DEFAULT_CONVERSION_SERVICE_CLASS, defaultClassLoader)) { + try { + Class clazz = ClassUtils.forName(DEFAULT_CONVERSION_SERVICE_CLASS, defaultClassLoader); + conversionService = (GenericConversionService) clazz.newInstance(); + } catch (Exception e) { + ReflectionUtils.handleReflectionException(e); + } + } else { + conversionService = new GenericConversionService(); + } + + prePopulateWithNameConverter(); + } + + private void prePopulateWithNameConverter() { + conversionService.addConverter(new StringToNameConverter()); + } + + @Override + public boolean canConvert(Class fromClass, String syntax, Class toClass) { + return conversionService.canConvert(fromClass, toClass); + } + + @Override + public T convert(Object source, String syntax, Class toClass) { + return conversionService.convert(source, toClass); + } + + public final static class NameToStringConverter + implements com.fr.third.springframework.core.convert.converter.Converter { + @Override + public String convert(Name source) { + if(source == null) { + return null; + } + + return source.toString(); + } + } + + public static final class StringToNameConverter + implements com.fr.third.springframework.core.convert.converter.Converter { + + @Override + public Name convert(String source) { + if(source == null) { + return null; + } + + return LdapUtils.newLdapName(source); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/Converter.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/Converter.java new file mode 100755 index 000000000..c6d99a871 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/Converter.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.typeconversion.impl; + +/** + * Interface specifying the conversion between two classes + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +public interface Converter { + /** + * Attempt to convert a given object to a named class. + * + * @param The class to convert to. + * @param source The object to convert. + * @param toClass The class to convert to. + * @return The converted class or null if the conversion was not possible. + * @throws Exception Any exception may be throw by a Converter on error. + */ + T convert(Object source, Class toClass) throws Exception; +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/ConverterManagerFactoryBean.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/ConverterManagerFactoryBean.java new file mode 100755 index 000000000..516b6d4f3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/ConverterManagerFactoryBean.java @@ -0,0 +1,209 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.typeconversion.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.beans.factory.FactoryBean; +import com.fr.third.springframework.beans.factory.FactoryBeanNotInitializedException; + +import java.util.HashSet; +import java.util.Set; + +/** + * A utility class to allow {@link ConverterManagerImpl} instances to be easily configured via spring.xml. + *

+ * The following shows a typical simple example which creates two {@link Converter} instances: + *

    + *
  • fromStringConverter
  • + *
  • toStringConverter
  • + *
+ * Configured in an {@link ConverterManagerImpl} to: + *
    + *
  • Use fromStringConverter to convert from String to Byte, Short, + * Integer, Long, Float, Double, Boolean
  • + *
  • Use toStringConverter to convert from Byte, Short, + * Integer, Long, Float, Double, Boolean to String
  • + *
+ *
+ * <bean id="converterManager" class="com.fr.third.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean">
+ *   <property name="converterConfig">
+ *     <set>
+ *       <bean class="com.fr.third.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
+ *         <property name="fromClasses">
+ *           <set>
+ *             <value>java.lang.String</value>
+ *           </set>
+ *         </property>
+ *         <property name="toClasses">
+ *           <set>
+ *             <value>java.lang.Byte</value>
+ *             <value>java.lang.Short</value>
+ *             <value>java.lang.Integer</value>
+ *             <value>java.lang.Long</value>
+ *             <value>java.lang.Float</value>
+ *             <value>java.lang.Double</value>
+ *             <value>java.lang.Boolean</value>
+ *           </set>
+ *         </property>
+ *         <property name="converter" ref="fromStringConverter"/>
+ *       </bean>
+ *       <bean class="com.fr.third.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
+ *         <property name="fromClasses">
+ *           <set>
+ *             <value>java.lang.Byte</value>
+ *             <value>java.lang.Short</value>
+ *             <value>java.lang.Integer</value>
+ *             <value>java.lang.Long</value>
+ *             <value>java.lang.Float</value>
+ *             <value>java.lang.Double</value>
+ *             <value>java.lang.Boolean</value>
+ *           </set>
+ *         </property>
+ *         <property name="toClasses">
+ *           <set>
+ *             <value>java.lang.String</value>
+ *           </set>
+ *         </property>
+ *         <property name="converter" ref="toStringConverter"/>
+ *       </bean>
+ *     </set>
+ *   </property>
+ * </bean>
+ * 
+ * {@link ConverterConfig} has a second constructor which takes an additional parameter to allow + * an LDAP syntax to be defined. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +public final class ConverterManagerFactoryBean implements FactoryBean { + private static final Logger LOG = LoggerFactory.getLogger(ConverterManagerFactoryBean.class); + + /** + * Configuration information for a single Converter instance. + */ + public static final class ConverterConfig { + // The set of classes the Converter will convert from. + private Set> fromClasses = new HashSet>(); + + // The (optional) LDAP syntax. + private String syntax=null; + + // The set of classes the Converter will convert to. + private Set> toClasses = new HashSet>(); + + // The Converter to use. + private Converter converter=null; + + public ConverterConfig() { + } + + /** + * @param fromClasses Comma separated list of classes the {@link Converter} should can convert from. + */ + public void setFromClasses(Set> fromClasses) { + this.fromClasses=fromClasses; + } + + /** + * @param toClasses Comma separated list of classes the {@link Converter} can convert to. + */ + public void setToClasses(Set> toClasses) { + this.toClasses=toClasses; + + } + + /** + * @param syntax An LDAP syntax supported by the {@link Converter}. + */ + public void setSyntax(String syntax) { + this.syntax=syntax; + } + + /** + * @param converter The {@link Converter} to use. + */ + public void setConverter(Converter converter) { + this.converter=converter; + } + + @Override + public String toString() { + return String.format("fromClasses=%1$s, syntax=%2$s, toClasses=%3$s, converter=%4$s", + fromClasses, syntax, toClasses, converter); + } + } + + private Set converterConfigList=null; + + + /** + * @param converterConfigList + */ + public void setConverterConfig(Set converterConfigList) { + this.converterConfigList=converterConfigList; + } + + /** + * Creates a ConverterManagerImpl populating it with Converter instances from the converterConfigList property. + * + * @return The newly created {@link com.fr.third.springframework.ldap.odm.typeconversion.ConverterManager}. + * @throws ClassNotFoundException Thrown if any of the classes to be converted to or from cannot be found. + * + * @see com.fr.third.springframework.beans.factory.FactoryBean#getObject() + */ + public Object getObject() throws Exception { + if (converterConfigList==null) { + throw new FactoryBeanNotInitializedException("converterConfigList has not been set"); + } + + ConverterManagerImpl result = new ConverterManagerImpl(); + for (ConverterConfig converterConfig : converterConfigList) { + if (converterConfig.fromClasses==null || + converterConfig.toClasses==null || + converterConfig.converter==null) { + + throw new FactoryBeanNotInitializedException( + String.format("All of fromClasses, toClasses and converter must be specified in bean %1$s", + converterConfig.toString())); + } + for (Class fromClass : converterConfig.fromClasses) { + for (Class toClass : converterConfig.toClasses) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Adding converter from %1$s to %2$s", fromClass, toClass)); + } + result.addConverter(fromClass, converterConfig.syntax, toClass, converterConfig.converter); + } + } + } + return result; + } + + /* (non-Javadoc) + * @see com.fr.third.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class getObjectType() { + return ConverterManagerImpl.class; + } + + /* (non-Javadoc) + * @see com.fr.third.springframework.beans.factory.FactoryBean#isSingleton() + */ + public boolean isSingleton() { + return true; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java new file mode 100755 index 000000000..c698bec9f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java @@ -0,0 +1,177 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.typeconversion.impl; + +import com.fr.third.springframework.ldap.odm.typeconversion.ConverterException; +import com.fr.third.springframework.ldap.odm.typeconversion.ConverterManager; + +import java.util.HashMap; +import java.util.Map; + +/** + * An implementation of {@link com.fr.third.springframework.ldap.odm.typeconversion.ConverterManager}. + *

+ * The algorithm used is to: + *

    + *
  1. Try to find and use a {@link Converter} registered for the + * fromClass, syntax and toClass and use it.
  2. + *
  3. If this fails, then if the toClass isAssignableFrom + * the fromClass then just assign it.
  4. + *
  5. If this fails try to find and use a {@link Converter} registered for the fromClass and + * the toClass ignoring the syntax.
  6. + *
  7. If this fails then throw a {@link com.fr.third.springframework.ldap.odm.typeconversion.ConverterException}.
  8. + *
+ * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +public final class ConverterManagerImpl implements ConverterManager { + /** + * Separator used to form keys into the converters Map. + */ + private static final String KEY_SEP = ":"; + + /** + * Map of keys created via makeConverterKey to Converter instances. + */ + private final Map converters = new HashMap(); + + /** + * Make a key into the converters map - the keys is formed from the fromClass, syntax and toClass + * + * @param fromClass The class to convert from. + * @param syntax The LDAP syntax. + * @param toClass The class to convert to. + * @return key + */ + private String makeConverterKey(Class fromClass, String syntax, Class toClass) { + StringBuilder key = new StringBuilder(); + if (syntax==null) { + syntax=""; + } + key.append(fromClass.getName()).append(KEY_SEP).append(syntax).append(KEY_SEP).append(toClass.getName()); + return key.toString(); + } + + /** + * Create an empty ConverterManagerImpl + */ + public ConverterManagerImpl() { + } + + /** + * Used to help in the process of dealing with primitive types by mapping them to + * their equivalent boxed class. + */ + private static Map, Class> primitiveTypeMap = new HashMap, Class>(); + static { + primitiveTypeMap.put(Byte.TYPE, Byte.class); + primitiveTypeMap.put(Short.TYPE, Short.class); + primitiveTypeMap.put(Integer.TYPE, Integer.class); + primitiveTypeMap.put(Long.TYPE, Long.class); + primitiveTypeMap.put(Float.TYPE, Float.class); + primitiveTypeMap.put(Double.TYPE, Double.class); + primitiveTypeMap.put(Boolean.TYPE, Boolean.class); + primitiveTypeMap.put(Character.TYPE, Character.class); + } + + + /* + * (non-Javadoc) + * @see com.fr.third.springframework.ldap.odm.typeconversion.ConverterManager#canConvert(java.lang.Class, java.lang.String, java.lang.Class) + */ + public boolean canConvert(Class fromClass, String syntax, Class toClass) { + Class fixedToClass = toClass; + if (toClass.isPrimitive()) { + fixedToClass = primitiveTypeMap.get(toClass); + } + Class fixedFromClass = fromClass; + if (fromClass.isPrimitive()) { + fixedFromClass = primitiveTypeMap.get(fromClass); + } + return fixedToClass.isAssignableFrom(fixedFromClass) || + (converters.get(makeConverterKey(fixedFromClass, syntax, fixedToClass)) != null) || + (converters.get(makeConverterKey(fixedFromClass, null, fixedToClass)) != null); + } + + + /* + * (non-Javadoc) + * @see com.fr.third.springframework.ldap.odm.typeconversion.ConverterManager#convert(java.lang.Object, java.lang.String, java.lang.Class) + */ + @SuppressWarnings("unchecked") + public T convert(Object source, String syntax, Class toClass) { + Object result = null; + + // What are we converting form + Class fromClass = source.getClass(); + + // Deal with primitives + Class targetClass = toClass; + if (toClass.isPrimitive()) { + targetClass = primitiveTypeMap.get(toClass); + } + + // Try to convert with any syntax we have been given + Converter syntaxConverter = converters.get(makeConverterKey(fromClass, syntax, targetClass)); + if (syntaxConverter != null) { + try { + result = syntaxConverter.convert(source, targetClass); + } catch (Exception e) { + // Ignore as we may still be able to convert successfully + } + } + + // Do we actually need to do any conversion? + if (result == null && targetClass.isAssignableFrom(fromClass)) { + result = source; + } + + // If we were given a syntax and we failed to convert drop back to any mapping + // that will work from class -> to class + if (result == null && syntax != null) { + Converter nullSyntaxConverter = converters.get(makeConverterKey(fromClass, null, targetClass)); + if (nullSyntaxConverter != null) { + try { + result = nullSyntaxConverter.convert(source, targetClass); + } catch (Exception e) { + // Handled at the end of the method + } + } + } + + if (result == null) { + throw new ConverterException(String.format( + "Cannot convert %1$s of class %2$s via syntax %3$s to class %4$s", source, source.getClass(), + syntax, toClass)); + } + + // We cannot do the safe thing of doing a .cast as we need to rely on auto-unboxing to deal with primitives! + return (T)result; + } + + /** + * Add a {@link Converter} to this ConverterManager. + * + * @param fromClass The class the Converter should be used to convert from. + * @param syntax The LDAP syntax that the Converter should be used for. + * @param toClass The class the Converter should be used to convert to. + * @param converter The Converter to add. + */ + public void addConverter(Class fromClass, String syntax, Class toClass, Converter converter) { + converters.put(makeConverterKey(fromClass, syntax, toClass), converter); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/StringConverter.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/StringConverter.java new file mode 100644 index 000000000..069303542 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/StringConverter.java @@ -0,0 +1,7 @@ +package com.fr.third.springframework.ldap.odm.typeconversion.impl; + +/** + * @author Mattias Hellborg Arthursson + */ +public class StringConverter { +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/converters/FromStringConverter.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/converters/FromStringConverter.java new file mode 100755 index 000000000..00503460b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/converters/FromStringConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.typeconversion.impl.converters; + +import com.fr.third.springframework.ldap.odm.typeconversion.impl.Converter; + +import java.lang.reflect.Constructor; + +/** + * A Converter from a {@link java.lang.String} to any class which has a single argument + * public constructor taking a {@link java.lang.String}. + *

+ * This should only be used as a fall-back converter, as a last attempt. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +public final class FromStringConverter implements Converter { + + /* (non-Javadoc) + * @see com.fr.third.springframework.ldap.odm.typeconversion.impl.Converter#convert(java.lang.Object, java.lang.Class) + */ + public T convert(Object source, Class toClass) throws Exception { + Constructor constructor = toClass.getConstructor(java.lang.String.class); + return constructor.newInstance(source); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/converters/ToStringConverter.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/converters/ToStringConverter.java new file mode 100755 index 000000000..aa2b8ec19 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/converters/ToStringConverter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.odm.typeconversion.impl.converters; + +import com.fr.third.springframework.ldap.odm.typeconversion.impl.Converter; + + +/** + * A Converter from any class to a {@link java.lang.String} via the toString method. + *

+ * This should only be used as a fall-back converter, as a last attempt. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ +public final class ToStringConverter implements Converter { + + /* (non-Javadoc) + * @see com.fr.third.springframework.ldap.odm.typeconversion.impl.Converter#convert(java.lang.Object, java.lang.Class) + */ + public T convert(Object source, Class toClass) { + return toClass.cast(source.toString()); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/converters/package-info.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/converters/package-info.java new file mode 100755 index 000000000..77faf2e62 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/converters/package-info.java @@ -0,0 +1,7 @@ +/** + * Provides some basic implementations of the {@link com.fr.third.springframework.ldap.odm.typeconversion.impl.Converter} interface. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ + +package com.fr.third.springframework.ldap.odm.typeconversion.impl.converters; \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/package-info.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/package-info.java new file mode 100755 index 000000000..5eb0f8148 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/impl/package-info.java @@ -0,0 +1,8 @@ +/** + * Provides an implementation of the {@link com.fr.third.springframework.ldap.odm.typeconversion.ConverterManager} interface. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ + +package com.fr.third.springframework.ldap.odm.typeconversion.impl; + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/package-info.java b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/package-info.java new file mode 100755 index 000000000..73155fc6d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/odm/typeconversion/package-info.java @@ -0,0 +1,10 @@ +/** + * Provides an interface to be implemented to create a type conversion framework. + *

+ * This is used to convert between the LDAP and Java representations of attributes. + * + * @author Paul Harvey <paul.at.pauls-place.me.uk> + */ + +package com.fr.third.springframework.ldap.odm.typeconversion; + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/package.html b/fine-spring/src/com/fr/third/springframework/ldap/package.html new file mode 100644 index 000000000..f3059be93 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/package.html @@ -0,0 +1,7 @@ + + + +Base package of Spring LDAP, containing an unchecked mirror of the JNDI NamingException hierarchy. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/DelegatingContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/DelegatingContext.java new file mode 100644 index 000000000..bdd11f7ee --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/DelegatingContext.java @@ -0,0 +1,394 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.pool; + +import com.fr.third.org.apache.commons.pool.KeyedObjectPool; +import com.fr.third.springframework.ldap.pool.factory.PoolingContextSource; +import com.fr.third.springframework.util.Assert; + +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import java.util.Hashtable; + +/** + * Used by {@link PoolingContextSource} to wrap a {@link Context}, delegating most methods + * to the underlying context, retains a reference to the pool the context was checked out + * from and returns itself to the pool when {@link #close()} is called. + * + * @author Eric Dalquist + */ +public class DelegatingContext implements Context { + private KeyedObjectPool keyedObjectPool; + private Context delegateContext; + private final DirContextType dirContextType; + + + /** + * Create a new delegating context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateContext The context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingContext(KeyedObjectPool keyedObjectPool, Context delegateContext, DirContextType dirContextType) { + Assert.notNull(keyedObjectPool, "keyedObjectPool may not be null"); + Assert.notNull(delegateContext, "delegateContext may not be null"); + Assert.notNull(dirContextType, "dirContextType may not be null"); + + this.keyedObjectPool = keyedObjectPool; + this.delegateContext = delegateContext; + this.dirContextType = dirContextType; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this context proxy + */ + public Context getDelegateContext() { + return this.delegateContext; + } + + /** + * Recursivley inspect delegates until a non-delegating context is found. + * + * @return The innermost (real) Context that is being delegated to. + */ + public Context getInnermostDelegateContext() { + final Context delegateContext = this.getDelegateContext(); + + if (delegateContext instanceof DelegatingContext) { + return ((DelegatingContext)delegateContext).getInnermostDelegateContext(); + } + + return delegateContext; + } + + /** + * @throws NamingException If the delegate is null, {@link #close()} has been called. + */ + protected void assertOpen() throws NamingException { + if (this.delegateContext == null) { + throw new NamingException("Context is closed."); + } + } + + + //***** Object methods *****// + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Context)) { + return false; + } + + final Context thisContext = this.getInnermostDelegateContext(); + Context otherContext = (Context)obj; + if (otherContext instanceof DelegatingContext) { + otherContext = ((DelegatingContext)otherContext).getInnermostDelegateContext(); + } + + return thisContext == otherContext || (thisContext != null && thisContext.equals(otherContext)); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final Context context = this.getInnermostDelegateContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final Context context = this.getInnermostDelegateContext(); + return (context != null ? context.toString() : "Context is closed"); + } + + + //***** Context Interface Delegates *****// + + /** + * @see javax.naming.Context#addToEnvironment(java.lang.String, java.lang.Object) + */ + public Object addToEnvironment(String propName, Object propVal) throws NamingException { + throw new UnsupportedOperationException("Cannot call addToEnvironment on a pooled context"); + } + + /** + * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object) + */ + public void bind(Name name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().bind(name, obj); + } + + /** + * @see javax.naming.Context#bind(java.lang.String, java.lang.Object) + */ + public void bind(String name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().bind(name, obj); + } + + /** + * @see javax.naming.Context#close() + */ + public void close() throws NamingException { + final Context context = this.getInnermostDelegateContext(); + if (context == null) { + return; + } + + //Get a local reference so the member can be nulled earlier + this.delegateContext = null; + + //Return the object to the Pool and then null the pool reference + try { + boolean valid = true; + + if (context instanceof FailureAwareContext) { + FailureAwareContext failureAwareContext = (FailureAwareContext) context; + if(failureAwareContext.hasFailed()) { + valid = false; + } + } + + if (valid) { + this.keyedObjectPool.returnObject(this.dirContextType, context); + } else { + this.keyedObjectPool.invalidateObject(this.dirContextType, context); + } + } + catch (Exception e) { + final NamingException namingException = new NamingException("Failed to return delegate Context to pool."); + namingException.setRootCause(e); + throw namingException; + } + finally { + this.keyedObjectPool = null; + } + } + + /** + * @see javax.naming.Context#composeName(javax.naming.Name, javax.naming.Name) + */ + public Name composeName(Name name, Name prefix) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().composeName(name, prefix); + } + + /** + * @see javax.naming.Context#composeName(java.lang.String, java.lang.String) + */ + public String composeName(String name, String prefix) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().composeName(name, prefix); + } + + /** + * @see javax.naming.Context#createSubcontext(javax.naming.Name) + */ + public Context createSubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.Context#createSubcontext(java.lang.String) + */ + public Context createSubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.Context#destroySubcontext(javax.naming.Name) + */ + public void destroySubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call destroySubcontext on a pooled context"); + } + + /** + * @see javax.naming.Context#destroySubcontext(java.lang.String) + */ + public void destroySubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call destroySubcontext on a pooled context"); + } + + /** + * @see javax.naming.Context#getEnvironment() + */ + public Hashtable getEnvironment() throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getEnvironment(); + } + + /** + * @see javax.naming.Context#getNameInNamespace() + */ + public String getNameInNamespace() throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameInNamespace(); + } + + /** + * @see javax.naming.Context#getNameParser(javax.naming.Name) + */ + public NameParser getNameParser(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameParser(name); + } + + /** + * @see javax.naming.Context#getNameParser(java.lang.String) + */ + public NameParser getNameParser(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameParser(name); + } + + /** + * @see javax.naming.Context#list(javax.naming.Name) + */ + public NamingEnumeration list(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().list(name); + } + + /** + * @see javax.naming.Context#list(java.lang.String) + */ + public NamingEnumeration list(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().list(name); + } + + /** + * @see javax.naming.Context#listBindings(javax.naming.Name) + */ + public NamingEnumeration listBindings(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().listBindings(name); + } + + /** + * @see javax.naming.Context#listBindings(java.lang.String) + */ + public NamingEnumeration listBindings(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().listBindings(name); + } + + /** + * @see javax.naming.Context#lookup(javax.naming.Name) + */ + public Object lookup(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookup(name); + } + + /** + * @see javax.naming.Context#lookup(java.lang.String) + */ + public Object lookup(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookup(name); + } + + /** + * @see javax.naming.Context#lookupLink(javax.naming.Name) + */ + public Object lookupLink(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookupLink(name); + } + + /** + * @see javax.naming.Context#lookupLink(java.lang.String) + */ + public Object lookupLink(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookupLink(name); + } + + /** + * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object) + */ + public void rebind(Name name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rebind(name, obj); + } + + /** + * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object) + */ + public void rebind(String name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rebind(name, obj); + } + + /** + * @see javax.naming.Context#removeFromEnvironment(java.lang.String) + */ + public Object removeFromEnvironment(String propName) throws NamingException { + throw new UnsupportedOperationException("Cannot call removeFromEnvironment on a pooled context"); + } + + /** + * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name) + */ + public void rename(Name oldName, Name newName) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rename(oldName, newName); + } + + /** + * @see javax.naming.Context#rename(java.lang.String, java.lang.String) + */ + public void rename(String oldName, String newName) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rename(oldName, newName); + } + + /** + * @see javax.naming.Context#unbind(javax.naming.Name) + */ + public void unbind(Name name) throws NamingException { + this.assertOpen(); + this.getDelegateContext().unbind(name); + } + + /** + * @see javax.naming.Context#unbind(java.lang.String) + */ + public void unbind(String name) throws NamingException { + this.assertOpen(); + this.getDelegateContext().unbind(name); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/DelegatingDirContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/DelegatingDirContext.java new file mode 100644 index 000000000..b3ea7fd39 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/DelegatingDirContext.java @@ -0,0 +1,361 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.pool; + +import com.fr.third.org.apache.commons.pool.KeyedObjectPool; +import com.fr.third.springframework.ldap.core.DirContextProxy; +import com.fr.third.springframework.ldap.pool.factory.PoolingContextSource; +import com.fr.third.springframework.util.Assert; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + + +/** + * Used by {@link PoolingContextSource} to wrap a {@link DirContext}, delegating most methods + * to the underlying context. This class extends {@link DelegatingContext} which handles returning + * the context to the pool on a call to {@link #close()} + * + * @author Eric Dalquist + */ +public class DelegatingDirContext extends DelegatingContext implements DirContext, DirContextProxy { + private DirContext delegateDirContext; + + /** + * Create a new delegating dir context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateDirContext The dir context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingDirContext(KeyedObjectPool keyedObjectPool, DirContext delegateDirContext, DirContextType dirContextType) { + super(keyedObjectPool, delegateDirContext, dirContextType); + Assert.notNull(delegateDirContext, "delegateDirContext may not be null"); + + this.delegateDirContext = delegateDirContext; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this dir context proxy + */ + public DirContext getDelegateDirContext() { + return this.delegateDirContext; + } + + public Context getDelegateContext() { + return this.getDelegateDirContext(); + } + + /** + * Recursivley inspect delegates until a non-delegating dir context is found. + * + * @return The innermost (real) DirContext that is being delegated to. + */ + public DirContext getInnermostDelegateDirContext() { + final DirContext delegateDirContext = this.getDelegateDirContext(); + + if (delegateDirContext instanceof DelegatingDirContext) { + return ((DelegatingDirContext)delegateDirContext).getInnermostDelegateDirContext(); + } + + return delegateDirContext; + } + + protected void assertOpen() throws NamingException { + if (this.delegateDirContext == null) { + throw new NamingException("DirContext is closed."); + } + + super.assertOpen(); + } + + + //***** Object methods *****// + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DirContext)) { + return false; + } + + final DirContext thisDirContext = this.getInnermostDelegateDirContext(); + DirContext otherDirContext = (DirContext)obj; + if (otherDirContext instanceof DelegatingDirContext) { + otherDirContext = ((DelegatingDirContext)otherDirContext).getInnermostDelegateDirContext(); + } + + return thisDirContext == otherDirContext || (thisDirContext != null && thisDirContext.equals(otherDirContext)); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final DirContext context = this.getInnermostDelegateDirContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final DirContext context = this.getInnermostDelegateDirContext(); + return (context != null ? context.toString() : "DirContext is closed"); + } + + + //***** DirContextProxy Interface Methods *****// + + /* (non-Javadoc) + * @see com.fr.third.springframework.ldap.core.DirContextProxy#getTargetContext() + */ + public DirContext getTargetContext() { + return this.getInnermostDelegateDirContext(); + } + + + //***** DirContext Interface Delegates *****// + + /** + * @see javax.naming.directory.DirContext#bind(javax.naming.Name, java.lang.Object, javax.naming.directory.Attributes) + */ + public void bind(Name name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().bind(name, obj, attrs); + } + + /** + * @see javax.naming.directory.DirContext#bind(java.lang.String, java.lang.Object, javax.naming.directory.Attributes) + */ + public void bind(String name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().bind(name, obj, attrs); + } + + /** + * @see javax.naming.directory.DirContext#createSubcontext(javax.naming.Name, javax.naming.directory.Attributes) + */ + public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#createSubcontext(java.lang.String, javax.naming.directory.Attributes) + */ + public DirContext createSubcontext(String name, Attributes attrs) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(javax.naming.Name, java.lang.String[]) + */ + public Attributes getAttributes(Name name, String[] attrIds) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name, attrIds); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(javax.naming.Name) + */ + public Attributes getAttributes(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(java.lang.String, java.lang.String[]) + */ + public Attributes getAttributes(String name, String[] attrIds) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name, attrIds); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(java.lang.String) + */ + public Attributes getAttributes(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name); + } + + /** + * @see javax.naming.directory.DirContext#getSchema(javax.naming.Name) + */ + public DirContext getSchema(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchema on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#getSchema(java.lang.String) + */ + public DirContext getSchema(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchema on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#getSchemaClassDefinition(javax.naming.Name) + */ + public DirContext getSchemaClassDefinition(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchemaClassDefinition on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#getSchemaClassDefinition(java.lang.String) + */ + public DirContext getSchemaClassDefinition(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchemaClassDefinition on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(javax.naming.Name, int, javax.naming.directory.Attributes) + */ + public void modifyAttributes(Name name, int modOp, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, modOp, attrs); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(javax.naming.Name, javax.naming.directory.ModificationItem[]) + */ + public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mods); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(java.lang.String, int, javax.naming.directory.Attributes) + */ + public void modifyAttributes(String name, int modOp, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, modOp, attrs); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(java.lang.String, javax.naming.directory.ModificationItem[]) + */ + public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mods); + } + + /** + * @see javax.naming.directory.DirContext#rebind(javax.naming.Name, java.lang.Object, javax.naming.directory.Attributes) + */ + public void rebind(Name name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().rebind(name, obj, attrs); + } + + /** + * @see javax.naming.directory.DirContext#rebind(java.lang.String, java.lang.Object, javax.naming.directory.Attributes) + */ + public void rebind(String name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().rebind(name, obj, attrs); + } + + /** + * @see javax.naming.directory.DirContext#search(javax.naming.Name, javax.naming.directory.Attributes, java.lang.String[]) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes, attributesToReturn); + } + + /** + * @see javax.naming.directory.DirContext#search(javax.naming.Name, javax.naming.directory.Attributes) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes); + } + + /** + * @see javax.naming.directory.DirContext#search(javax.naming.Name, java.lang.String, java.lang.Object[], javax.naming.directory.SearchControls) + */ + public NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filterExpr, filterArgs, cons); + } + + /** + * @see javax.naming.directory.DirContext#search(javax.naming.Name, java.lang.String, javax.naming.directory.SearchControls) + */ + public NamingEnumeration search(Name name, String filter, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filter, cons); + } + + /** + * @see javax.naming.directory.DirContext#search(java.lang.String, javax.naming.directory.Attributes, java.lang.String[]) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes, attributesToReturn); + } + + /** + * @see javax.naming.directory.DirContext#search(java.lang.String, javax.naming.directory.Attributes) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes); + } + + /** + * @see javax.naming.directory.DirContext#search(java.lang.String, java.lang.String, java.lang.Object[], javax.naming.directory.SearchControls) + */ + public NamingEnumeration search(String name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filterExpr, filterArgs, cons); + } + + /** + * @see javax.naming.directory.DirContext#search(java.lang.String, java.lang.String, javax.naming.directory.SearchControls) + */ + public NamingEnumeration search(String name, String filter, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filter, cons); + } + + /** + * @see DelegatingContext#close() + */ + public void close() throws NamingException { + if (this.delegateDirContext == null) { + return; + } + + super.close(); + this.delegateDirContext = null; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/DelegatingLdapContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/DelegatingLdapContext.java new file mode 100644 index 000000000..325ad4afa --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/DelegatingLdapContext.java @@ -0,0 +1,198 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.pool; + +import com.fr.third.org.apache.commons.pool.KeyedObjectPool; +import com.fr.third.springframework.ldap.pool.factory.PoolingContextSource; +import com.fr.third.springframework.util.Assert; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.ExtendedRequest; +import javax.naming.ldap.ExtendedResponse; +import javax.naming.ldap.LdapContext; + +/** + * Used by {@link PoolingContextSource} to wrap a {@link LdapContext}, delegating most methods + * to the underlying context. This class extends {@link DelegatingDirContext} which handles returning + * the context to the pool on a call to {@link #close()} + * + * @author Eric Dalquist + */ +public class DelegatingLdapContext extends DelegatingDirContext implements LdapContext { + private LdapContext delegateLdapContext; + + /** + * Create a new delegating ldap context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateLdapContext The ldap context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingLdapContext(KeyedObjectPool keyedObjectPool, LdapContext delegateLdapContext, DirContextType dirContextType) { + super(keyedObjectPool, delegateLdapContext, dirContextType); + Assert.notNull(delegateLdapContext, "delegateLdapContext may not be null"); + + this.delegateLdapContext = delegateLdapContext; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this ldap context proxy + */ + public LdapContext getDelegateLdapContext() { + return this.delegateLdapContext; + } + + // cannot return subtype in overridden method unless Java5 + public DirContext getDelegateDirContext() { + return this.getDelegateLdapContext(); + } + + /** + * Recursivley inspect delegates until a non-delegating ldap context is found. + * + * @return The innermost (real) DirContext that is being delegated to. + */ + public LdapContext getInnermostDelegateLdapContext() { + final LdapContext delegateLdapContext = this.getDelegateLdapContext(); + + if (delegateLdapContext instanceof DelegatingLdapContext) { + return ((DelegatingLdapContext)delegateLdapContext).getInnermostDelegateLdapContext(); + } + + return delegateLdapContext; + } + + protected void assertOpen() throws NamingException { + if (this.delegateLdapContext == null) { + throw new NamingException("LdapContext is closed."); + } + + super.assertOpen(); + } + + + //***** Object methods *****// + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LdapContext)) { + return false; + } + + final LdapContext thisLdapContext = this.getInnermostDelegateLdapContext(); + LdapContext otherLdapContext = (LdapContext)obj; + if (otherLdapContext instanceof DelegatingLdapContext) { + otherLdapContext = ((DelegatingLdapContext)otherLdapContext).getInnermostDelegateLdapContext(); + } + + return thisLdapContext == otherLdapContext || (thisLdapContext != null && thisLdapContext.equals(otherLdapContext)); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final LdapContext context = this.getInnermostDelegateLdapContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final LdapContext context = this.getInnermostDelegateLdapContext(); + return (context != null ? context.toString() : "LdapContext is closed"); + } + + + //***** LdapContext Interface Delegates *****// + + /** + * @see javax.naming.ldap.LdapContext#extendedOperation(javax.naming.ldap.ExtendedRequest) + */ + public ExtendedResponse extendedOperation(ExtendedRequest request) throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().extendedOperation(request); + } + + /** + * @see javax.naming.ldap.LdapContext#getConnectControls() + */ + public Control[] getConnectControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getConnectControls(); + } + + /** + * @see javax.naming.ldap.LdapContext#getRequestControls() + */ + public Control[] getRequestControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getRequestControls(); + } + + /** + * @see javax.naming.ldap.LdapContext#getResponseControls() + */ + public Control[] getResponseControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getResponseControls(); + } + + /** + * @see javax.naming.ldap.LdapContext#newInstance(javax.naming.ldap.Control[]) + */ + public LdapContext newInstance(Control[] requestControls) throws NamingException { + throw new UnsupportedOperationException("Cannot call newInstance on a pooled context"); + } + + /** + * @see javax.naming.ldap.LdapContext#reconnect(javax.naming.ldap.Control[]) + */ + public void reconnect(Control[] connCtls) throws NamingException { + throw new UnsupportedOperationException("Cannot call reconnect on a pooled context"); + } + + /** + * @see javax.naming.ldap.LdapContext#setRequestControls(javax.naming.ldap.Control[]) + */ + public void setRequestControls(Control[] requestControls) throws NamingException { + throw new UnsupportedOperationException("Cannot call setRequestControls on a pooled context"); + } + + /** + * @see DelegatingDirContext#close() + */ + public void close() throws NamingException { + if (this.delegateLdapContext == null) { + return; + } + + super.close(); + this.delegateLdapContext = null; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/DirContextType.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/DirContextType.java new file mode 100644 index 000000000..909a04e3a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/DirContextType.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool; + +import com.fr.third.springframework.ldap.core.ContextSource; + +import javax.naming.directory.DirContext; + + +/** + * An enum representing the two types of {@link DirContext}s that can be returned by a + * {@link ContextSource}. + * + * @author Eric Dalquist + */ +public final class DirContextType { + private String name; + + private DirContextType(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + /** + * The type of {@link DirContext} returned by {@link ContextSource#getReadOnlyContext()} + */ + public static final DirContextType READ_ONLY = new DirContextType("READ_ONLY"); + + /** + * The type of {@link DirContext} returned by {@link ContextSource#getReadWriteContext()} + */ + public static final DirContextType READ_WRITE = new DirContextType("READ_WRITE"); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/FailureAwareContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/FailureAwareContext.java new file mode 100644 index 000000000..54747fb0c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/FailureAwareContext.java @@ -0,0 +1,24 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool; + +/** + * @author Mattias Hellborg Arthursson + */ +public interface FailureAwareContext { + boolean hasFailed(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/MutableDelegatingLdapContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/MutableDelegatingLdapContext.java new file mode 100644 index 000000000..cb872247a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/MutableDelegatingLdapContext.java @@ -0,0 +1,56 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool; + +import com.fr.third.org.apache.commons.pool.KeyedObjectPool; +import com.fr.third.springframework.ldap.pool.factory.MutablePoolingContextSource; + +import javax.naming.NamingException; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +/** + * Used by {@link MutablePoolingContextSource} to wrap a {@link LdapContext}, + * delegating most methods to the underlying context. This class extends + * {@link DelegatingLdapContext}, allowing request controls to be set on the + * wrapped ldap context. This enables the Spring LDAP pooling to be used for + * scenarios such as paged results. + * + * @author Ulrik Sandberg + */ +public class MutableDelegatingLdapContext extends DelegatingLdapContext { + + /** + * Create a new mutable delegating ldap context for the specified pool, + * context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out + * from. + * @param delegateLdapContext The ldap context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public MutableDelegatingLdapContext(KeyedObjectPool keyedObjectPool, LdapContext delegateLdapContext, + DirContextType dirContextType) { + super(keyedObjectPool, delegateLdapContext, dirContextType); + } + + public void setRequestControls(Control[] requestControls) throws NamingException { + assertOpen(); + getDelegateLdapContext().setRequestControls(requestControls); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/PoolExhaustedAction.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/PoolExhaustedAction.java new file mode 100644 index 000000000..8806dc0e5 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/PoolExhaustedAction.java @@ -0,0 +1,20 @@ +package com.fr.third.springframework.ldap.pool; + +/** + * @author Mattias Hellborg Arthursson + */ +public enum PoolExhaustedAction { + FAIL((byte)0), + BLOCK((byte)1), + GROW((byte)2); + + private final byte value; + + private PoolExhaustedAction(byte value) { + this.value = value; + } + + public byte getValue() { + return value; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/DirContextPoolableObjectFactory.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/DirContextPoolableObjectFactory.java new file mode 100644 index 000000000..429ff676a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/DirContextPoolableObjectFactory.java @@ -0,0 +1,302 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.pool.factory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.org.apache.commons.pool.BaseKeyedPoolableObjectFactory; +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.DirContextProxy; +import com.fr.third.springframework.ldap.pool.DirContextType; +import com.fr.third.springframework.ldap.pool.FailureAwareContext; +import com.fr.third.springframework.ldap.pool.validation.DirContextValidator; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.Assert; + +import javax.naming.CommunicationException; +import javax.naming.directory.DirContext; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Factory that creates {@link DirContext} instances for pooling via a + * configured {@link ContextSource}. The {@link DirContext}s are keyed based + * on if they are read only or read/write. The expected key type is the + * {@link DirContextType} enum. + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyDescriptionRequiredDefault
contextSource The {@link ContextSource} to get {@link DirContext}s from + * for adding to the pool. Yesnull
dirContextValidator The {@link DirContextValidator} to use to validate + * {@link DirContext}s. This is only required if the pool has validation of any + * kind turned on. Nonull
+ * + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + * @author Mattias Hellborg Arthursson + */ +class DirContextPoolableObjectFactory extends BaseKeyedPoolableObjectFactory { + /** + * Logger for this class and subclasses + */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private static final Set> DEFAULT_NONTRANSIENT_EXCEPTIONS + = new HashSet>(){{ + add(CommunicationException.class); + }}; + + private ContextSource contextSource; + + private DirContextValidator dirContextValidator; + + private Set> nonTransientExceptions = DEFAULT_NONTRANSIENT_EXCEPTIONS; + + void setNonTransientExceptions(Collection> nonTransientExceptions) { + this.nonTransientExceptions = new HashSet>(nonTransientExceptions); + } + + /** + * @return the contextSource + */ + public ContextSource getContextSource() { + return this.contextSource; + } + + /** + * @param contextSource + * the contextSource to set + */ + public void setContextSource(ContextSource contextSource) { + if (contextSource == null) { + throw new IllegalArgumentException("contextSource may not be null"); + } + + this.contextSource = contextSource; + } + + /** + * @return the dirContextValidator + */ + public DirContextValidator getDirContextValidator() { + return this.dirContextValidator; + } + + /** + * @param dirContextValidator + * the dirContextValidator to set + */ + public void setDirContextValidator(DirContextValidator dirContextValidator) { + if (dirContextValidator == null) { + throw new IllegalArgumentException( + "dirContextValidator may not be null"); + } + + this.dirContextValidator = dirContextValidator; + } + + /** + * @see com.fr.third.org.apache.commons.pool.BaseKeyedPoolableObjectFactory#makeObject(java.lang.Object) + */ + public Object makeObject(Object key) throws Exception { + Assert.notNull(this.contextSource, "ContextSource may not be null"); + Assert.isTrue(key instanceof DirContextType, + "key must be a DirContextType"); + + final DirContextType contextType = (DirContextType) key; + if (this.logger.isDebugEnabled()) { + this.logger.debug("Creating a new " + contextType + " DirContext"); + } + + if (contextType == DirContextType.READ_WRITE) { + final DirContext readWriteContext = this.contextSource + .getReadWriteContext(); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Created new " + DirContextType.READ_WRITE + + " DirContext='" + readWriteContext + "'"); + } + + return makeFailureAwareProxy(readWriteContext); + } else if (contextType == DirContextType.READ_ONLY) { + + final DirContext readOnlyContext = this.contextSource + .getReadOnlyContext(); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Created new " + DirContextType.READ_ONLY + + " DirContext='" + readOnlyContext + "'"); + } + + return makeFailureAwareProxy(readOnlyContext); + } else { + throw new IllegalArgumentException("Unrecognized ContextType: " + + contextType); + } + } + + private Object makeFailureAwareProxy(DirContext readOnlyContext) { + return Proxy.newProxyInstance(DirContextProxy.class + .getClassLoader(), + new Class[]{ + LdapUtils.getActualTargetClass(readOnlyContext), + DirContextProxy.class, + FailureAwareContext.class}, + new FailureAwareContextProxy(readOnlyContext)); + } + + /** + * @see com.fr.third.org.apache.commons.pool.BaseKeyedPoolableObjectFactory#validateObject(java.lang.Object, + * java.lang.Object) + */ + public boolean validateObject(Object key, Object obj) { + Assert.notNull(this.dirContextValidator, + "DirContextValidator may not be null"); + Assert.isTrue(key instanceof DirContextType, + "key must be a DirContextType"); + Assert.isTrue(obj instanceof DirContext, + "The Object to validate must be of type '" + DirContext.class + + "'"); + + try { + final DirContextType contextType = (DirContextType) key; + final DirContext dirContext = (DirContext) obj; + return this.dirContextValidator.validateDirContext(contextType, + dirContext); + } catch (Exception e) { + this.logger.warn("Failed to validate '" + obj + + "' due to an unexpected exception.", e); + return false; + } + } + + + /** + * @see com.fr.third.org.apache.commons.pool.BaseKeyedPoolableObjectFactory#destroyObject(java.lang.Object, + * java.lang.Object) + */ + public void destroyObject(Object key, Object obj) throws Exception { + Assert.isTrue(obj instanceof DirContext, + "The Object to validate must be of type '" + DirContext.class + + "'"); + + try { + final DirContext dirContext = (DirContext) obj; + if (this.logger.isDebugEnabled()) { + this.logger.debug("Closing " + key + " DirContext='" + + dirContext + "'"); + } + dirContext.close(); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Closed " + key + " DirContext='" + + dirContext + "'"); + } + } catch (Exception e) { + this.logger.warn( + "An exception occured while closing '" + obj + "'", e); + } + } + + /** + * Invocation handler that checks thrown exceptions against the configured {@link #nonTransientExceptions}, + * marking the Context as invalid on match. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ + private class FailureAwareContextProxy implements + InvocationHandler { + + private DirContext target; + + private boolean hasFailed = false; + + public FailureAwareContextProxy(DirContext target) { + Assert.notNull(target, "Target must not be null"); + this.target = target; + } + + /* + * @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 { + + String methodName = method.getName(); + if (methodName.equals("getTargetContext")) { + return target; + } else if (methodName.equals("hasFailed")) { + return hasFailed; + } + + try { + return method.invoke(target, args); + } + catch (InvocationTargetException e) { + Throwable targetException = e.getTargetException(); + Class targetExceptionClass = targetException.getClass(); + + boolean nonTransientEncountered = false; + for (Class clazz : nonTransientExceptions) { + if(clazz.isAssignableFrom(targetExceptionClass)) { + logger.info( + String.format("An %s - explicitly configured to be a non-transient exception - encountered; eagerly invalidating the target context.", + targetExceptionClass)); + nonTransientEncountered = true; + break; + } + } + + if(nonTransientEncountered) { + hasFailed = true; + } else { + if (logger.isDebugEnabled()) { + logger.debug(String.format("An %s - not explicitly configured to be a non-transient exception - encountered; ignoring.", + targetExceptionClass)); + } + } + + throw targetException; + } + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/MutablePoolingContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/MutablePoolingContextSource.java new file mode 100644 index 000000000..e5834ff8e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/MutablePoolingContextSource.java @@ -0,0 +1,49 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool.factory; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import com.fr.third.springframework.dao.DataAccessResourceFailureException; +import com.fr.third.springframework.ldap.pool.DelegatingDirContext; +import com.fr.third.springframework.ldap.pool.DirContextType; +import com.fr.third.springframework.ldap.pool.MutableDelegatingLdapContext; + +/** + * A {@link PoolingContextSource} subclass that creates + * {@link MutableDelegatingLdapContext} instances. This enables the Spring LDAP + * pooling to be used in scenarios that require request controls to be set, such + * as paged results. + */ +public class MutablePoolingContextSource extends PoolingContextSource { + protected DirContext getContext(DirContextType dirContextType) { + final DirContext dirContext; + try { + dirContext = (DirContext) this.keyedObjectPool.borrowObject(dirContextType); + } + catch (Exception e) { + throw new DataAccessResourceFailureException("Failed to borrow DirContext from pool.", e); + } + + if (dirContext instanceof LdapContext) { + return new MutableDelegatingLdapContext(this.keyedObjectPool, (LdapContext) dirContext, dirContextType); + } + + return new DelegatingDirContext(this.keyedObjectPool, dirContext, dirContextType); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/PoolingContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/PoolingContextSource.java new file mode 100644 index 000000000..3ad89044e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/PoolingContextSource.java @@ -0,0 +1,460 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool.factory; + +import com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.beans.factory.DisposableBean; +import com.fr.third.springframework.dao.DataAccessResourceFailureException; +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.support.DelegatingBaseLdapPathContextSourceSupport; +import com.fr.third.springframework.ldap.pool.DelegatingDirContext; +import com.fr.third.springframework.ldap.pool.DelegatingLdapContext; +import com.fr.third.springframework.ldap.pool.DirContextType; +import com.fr.third.springframework.ldap.pool.validation.DirContextValidator; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; +import java.util.Collection; + +/** + * A {@link ContextSource} implementation that wraps an object pool and another + * {@link ContextSource}. {@link DirContext}s are retrieved from the pool which + * maintains them. + * + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Property Description Required Default
contextSource + * The {@link ContextSource} to get {@link DirContext}s from for adding to the + * pool.Yesnull
dirContextValidator + * The {@link DirContextValidator} to use for validating {@link DirContext}s. + * Required if any of the test/validate options are enabled.Nonull
minIdle{@link GenericKeyedObjectPool#setMinIdle(int)}No0
maxIdle{@link GenericKeyedObjectPool#setMaxIdle(int)}No8
maxActive{@link GenericKeyedObjectPool#setMaxActive(int)}No8
maxTotal{@link GenericKeyedObjectPool#setMaxTotal(int)}No-1
maxWait{@link GenericKeyedObjectPool#setMaxWait(long)}No-1L
whenExhaustedAction{@link GenericKeyedObjectPool#setWhenExhaustedAction(byte)}No{@link GenericKeyedObjectPool#WHEN_EXHAUSTED_BLOCK}
testOnBorrow{@link GenericKeyedObjectPool#setTestOnBorrow(boolean)}Nofalse
testOnReturn{@link GenericKeyedObjectPool#setTestOnReturn(boolean)}Nofalse
testWhileIdle{@link GenericKeyedObjectPool#setTestWhileIdle(boolean)}Nofalse
timeBetweenEvictionRunsMillis + * {@link GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis(long)}No-1L
minEvictableIdleTimeMillis + * {@link GenericKeyedObjectPool#setMinEvictableIdleTimeMillis(long)}No1000L * 60L * 30L
numTestsPerEvictionRun + * {@link GenericKeyedObjectPool#setNumTestsPerEvictionRun(int)}No3
+ * + * @author Eric Dalquist + */ +public class PoolingContextSource + extends DelegatingBaseLdapPathContextSourceSupport + implements ContextSource, DisposableBean { + /** + * The logger for this class and sub-classes + */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected final GenericKeyedObjectPool keyedObjectPool; + + private final DirContextPoolableObjectFactory dirContextPoolableObjectFactory; + + /** + * Creates a new pooling context source, setting up the DirContext object + * factory and generic keyed object pool. + */ + public PoolingContextSource() { + this.dirContextPoolableObjectFactory = new DirContextPoolableObjectFactory(); + this.keyedObjectPool = new GenericKeyedObjectPool(); + this.keyedObjectPool.setFactory(this.dirContextPoolableObjectFactory); + } + + // ***** Pool Property Configuration *****// + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getMaxActive() + */ + public int getMaxActive() { + return this.keyedObjectPool.getMaxActive(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getMaxIdle() + */ + public int getMaxIdle() { + return this.keyedObjectPool.getMaxIdle(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getMaxTotal() + */ + public int getMaxTotal() { + return this.keyedObjectPool.getMaxTotal(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getMaxWait() + */ + public long getMaxWait() { + return this.keyedObjectPool.getMaxWait(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getMinEvictableIdleTimeMillis() + */ + public long getMinEvictableIdleTimeMillis() { + return this.keyedObjectPool.getMinEvictableIdleTimeMillis(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getMinIdle() + */ + public int getMinIdle() { + return this.keyedObjectPool.getMinIdle(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getNumActive() + */ + public int getNumActive() { + return this.keyedObjectPool.getNumActive(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getNumIdle() + */ + public int getNumIdle() { + return this.keyedObjectPool.getNumIdle(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getNumTestsPerEvictionRun() + */ + public int getNumTestsPerEvictionRun() { + return this.keyedObjectPool.getNumTestsPerEvictionRun(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getTestOnBorrow() + */ + public boolean getTestOnBorrow() { + return this.keyedObjectPool.getTestOnBorrow(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getTestOnReturn() + */ + public boolean getTestOnReturn() { + return this.keyedObjectPool.getTestOnReturn(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getTestWhileIdle() + */ + public boolean getTestWhileIdle() { + return this.keyedObjectPool.getTestWhileIdle(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getTimeBetweenEvictionRunsMillis() + */ + public long getTimeBetweenEvictionRunsMillis() { + return this.keyedObjectPool.getTimeBetweenEvictionRunsMillis(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#getWhenExhaustedAction() + */ + public byte getWhenExhaustedAction() { + return this.keyedObjectPool.getWhenExhaustedAction(); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setMaxActive(int) + */ + public void setMaxActive(int maxActive) { + this.keyedObjectPool.setMaxActive(maxActive); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setMaxIdle(int) + */ + public void setMaxIdle(int maxIdle) { + this.keyedObjectPool.setMaxIdle(maxIdle); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setMaxTotal(int) + */ + public void setMaxTotal(int maxTotal) { + this.keyedObjectPool.setMaxTotal(maxTotal); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setMaxWait(long) + */ + public void setMaxWait(long maxWait) { + this.keyedObjectPool.setMaxWait(maxWait); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setMinEvictableIdleTimeMillis(long) + */ + public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { + this.keyedObjectPool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setMinIdle(int) + */ + public void setMinIdle(int poolSize) { + this.keyedObjectPool.setMinIdle(poolSize); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setNumTestsPerEvictionRun(int) + */ + public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { + this.keyedObjectPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setTestOnBorrow(boolean) + */ + public void setTestOnBorrow(boolean testOnBorrow) { + this.keyedObjectPool.setTestOnBorrow(testOnBorrow); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setTestOnReturn(boolean) + */ + public void setTestOnReturn(boolean testOnReturn) { + this.keyedObjectPool.setTestOnReturn(testOnReturn); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setTestWhileIdle(boolean) + */ + public void setTestWhileIdle(boolean testWhileIdle) { + this.keyedObjectPool.setTestWhileIdle(testWhileIdle); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis(long) + */ + public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { + this.keyedObjectPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + } + + /** + * @see com.fr.third.org.apache.commons.pool.impl.GenericKeyedObjectPool#setWhenExhaustedAction(byte) + */ + public void setWhenExhaustedAction(byte whenExhaustedAction) { + this.keyedObjectPool.setWhenExhaustedAction(whenExhaustedAction); + } + + // ***** Object Factory Property Configuration *****// + + /** + * @return the contextSource + */ + public ContextSource getContextSource() { + return this.dirContextPoolableObjectFactory.getContextSource(); + } + + /** + * @return the dirContextValidator + */ + public DirContextValidator getDirContextValidator() { + return this.dirContextPoolableObjectFactory.getDirContextValidator(); + } + + /** + * @param contextSource the contextSource to set + * Required + */ + public void setContextSource(ContextSource contextSource) { + this.dirContextPoolableObjectFactory.setContextSource(contextSource); + } + + /** + * @param dirContextValidator the dirContextValidator to set + * Required + */ + public void setDirContextValidator(DirContextValidator dirContextValidator) { + this.dirContextPoolableObjectFactory.setDirContextValidator(dirContextValidator); + } + + /** + * Configure the exception classes that are to be interpreted as no-transient with regards to eager + * context invalidation. If one of the configured exceptions (or subclasses of them) + * is thrown by any method on a pooled DirContext, that instance will immediately be marked + * as invalid without any additional testing (i.e. testOnReturn). + * This allows for more efficient management of dead connections. + * Default is {@link javax.naming.CommunicationException}. + * + * @param nonTransientExceptions the exception classes that should be interpreted as non-transient + * with regards to eager invalidation. + * @since 2.0 + */ + public void setNonTransientExceptions(Collection> nonTransientExceptions) { + this.dirContextPoolableObjectFactory.setNonTransientExceptions(nonTransientExceptions); + } + + + // ***** DisposableBean interface methods *****// + + /* + * (non-Javadoc) + * + * @see com.fr.third.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + try { + this.keyedObjectPool.close(); + } + catch (Exception e) { + this.logger.warn("An exception occured while closing the underlying pool.", e); + } + } + + @Override + protected ContextSource getTarget() { + return getContextSource(); + } + + // ***** ContextSource interface methods *****// + + @Override + public DirContext getReadOnlyContext() { + return this.getContext(DirContextType.READ_ONLY); + } + + @Override + public DirContext getReadWriteContext() { + return this.getContext(DirContextType.READ_WRITE); + } + + /** + * Gets a DirContext of the specified type from the keyed object pool. + * + * @param dirContextType The type of context to return. + * @return A wrapped DirContext of the specified type. + * @throws DataAccessResourceFailureException If retrieving the object from + * the pool throws an exception + */ + protected DirContext getContext(DirContextType dirContextType) { + final DirContext dirContext; + try { + dirContext = (DirContext) this.keyedObjectPool.borrowObject(dirContextType); + } + catch (Exception e) { + throw new DataAccessResourceFailureException("Failed to borrow DirContext from pool.", e); + } + + if (dirContext instanceof LdapContext) { + return new DelegatingLdapContext(this.keyedObjectPool, (LdapContext) dirContext, dirContextType); + } + + return new DelegatingDirContext(this.keyedObjectPool, dirContext, dirContextType); + } + + @Override + public DirContext getContext(String principal, String credentials) { + throw new UnsupportedOperationException("Not supported for this implementation"); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/package.html b/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/package.html new file mode 100644 index 000000000..bcf1cd833 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/factory/package.html @@ -0,0 +1,7 @@ + + + +Core classes for the pooling library. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/package.html b/fine-spring/src/com/fr/third/springframework/ldap/pool/package.html new file mode 100644 index 000000000..85bee7239 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/package.html @@ -0,0 +1,7 @@ + + + +Base classes for the pooling library. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/validation/DefaultDirContextValidator.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/validation/DefaultDirContextValidator.java new file mode 100644 index 000000000..0ae3d913f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/validation/DefaultDirContextValidator.java @@ -0,0 +1,191 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool.validation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.pool.DirContextType; +import com.fr.third.springframework.util.Assert; + +import javax.naming.NamingEnumeration; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +/** + * Default {@link DirContext} validator that executes {@link DirContext#search(String, String, SearchControls)}. The + * name, filter and {@link SearchControls} are all configurable. There is no special handling for read only versus + * read write {@link DirContext}s. + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyDescriptionRequiredDefault
base + * The name parameter to the search method. + * No""
filter + * The filter parameter to the search method. + * No"objectclass=*"
searchControls + * The {@link SearchControls} parameter to the search method. + * No + * {@link SearchControls#setCountLimit(long)} = 1
+ * {@link SearchControls#setReturningAttributes(String[])} = new String[] { "objectclass" }
+ * {@link SearchControls#setTimeLimit(int)} = 500 + *
+ * + * @author Eric Dalquist + */ +public class DefaultDirContextValidator implements DirContextValidator { + public static final String DEFAULT_FILTER = "objectclass=*"; + private static final int DEFAULT_TIME_LIMIT = 500; + + /** + * Logger for this class and sub-classes + */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private String base; + private String filter; + private SearchControls searchControls; + + /** + * Create the default validator, creates {@link SearchControls} with search scope OBJECT_SCOPE, + * a countLimit of 1, returningAttributes of objectclass and timeLimit of 500. + * The default base is an empty string and the default filter is objectclass=* + */ + public DefaultDirContextValidator() { + this(SearchControls.OBJECT_SCOPE); + } + + /** + * Create a validator with all the defaults of the default constructor, but with the search scope set to the + * referred value. + * + * @param searchScope The searchScope to be set in the default SearchControls + */ + public DefaultDirContextValidator(int searchScope) { + this.searchControls = new SearchControls(); + this.searchControls.setSearchScope(searchScope); + this.searchControls.setCountLimit(1); + this.searchControls.setReturningAttributes(new String[] { "objectclass" }); + this.searchControls.setTimeLimit(DEFAULT_TIME_LIMIT); + + this.base = ""; + + this.filter = DEFAULT_FILTER; + } + + /** + * @return the baseName + */ + public String getBase() { + return this.base; + } + /** + * @param base the baseName to set + */ + public void setBase(String base) { + this.base = base; + } + /** + * @return the filter + */ + public String getFilter() { + return this.filter; + } + /** + * @param filter the filter to set + */ + public void setFilter(String filter) { + if (filter == null) { + throw new IllegalArgumentException("filter may not be null"); + } + + this.filter = filter; + } + /** + * @return the searchControls + */ + public SearchControls getSearchControls() { + return this.searchControls; + } + /** + * @param searchControls the searchControls to set + */ + public void setSearchControls(SearchControls searchControls) { + if (searchControls == null) { + throw new IllegalArgumentException("searchControls may not be null"); + } + + this.searchControls = searchControls; + } + + + /** + * @see DirContextValidator#validateDirContext(DirContextType, javax.naming.directory.DirContext) + */ + public boolean validateDirContext(DirContextType contextType, DirContext dirContext) { + Assert.notNull(contextType, "contextType may not be null"); + Assert.notNull(dirContext, "dirContext may not be null"); + + try { + final NamingEnumeration searchResults = dirContext.search(this.base, this.filter, this.searchControls); + + if (searchResults.hasMore()) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("DirContext '" + dirContext + "' passed validation."); + } + + return true; + } + } + catch (Exception e) { + if(this.logger.isDebugEnabled()) { + this.logger.debug("DirContext '" + dirContext + "' failed validation with an exception.", e); + } + } + + if (this.logger.isInfoEnabled()) { + this.logger.info("DirContext '" + dirContext + "' failed validation."); + } + return false; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/validation/DirContextValidator.java b/fine-spring/src/com/fr/third/springframework/ldap/pool/validation/DirContextValidator.java new file mode 100644 index 000000000..18f85bb50 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/validation/DirContextValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool.validation; + +import javax.naming.directory.DirContext; + +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.pool.DirContextType; + +/** + * A validator for {@link DirContext}s. + * + * @author Eric Dalquist + */ +public interface DirContextValidator { + /** + * Validates the {@link DirContext}. A valid {@link DirContext} should be able + * to answer queries and if applicable write to the directory. + * + * @param contextType The type of the {@link DirContext}, refers to if {@link ContextSource#getReadOnlyContext()} or {@link ContextSource#getReadWriteContext()} was called to create the {@link DirContext} + * @param dirContext The {@link DirContext} to validate. + * @return true if the {@link DirContext} operated correctly during validation. + */ + boolean validateDirContext(DirContextType contextType, DirContext dirContext); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool/validation/package.html b/fine-spring/src/com/fr/third/springframework/ldap/pool/validation/package.html new file mode 100644 index 000000000..788f99a59 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool/validation/package.html @@ -0,0 +1,7 @@ + + + +Connection validation support for the pooling library. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/DelegatingContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/DelegatingContext.java new file mode 100644 index 000000000..24101cd25 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/DelegatingContext.java @@ -0,0 +1,390 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.pool2; + + +import com.fr.third.org.apache.commons.pool2.KeyedObjectPool; +import com.fr.third.springframework.ldap.pool2.factory.PooledContextSource; +import com.fr.third.springframework.util.Assert; + +import javax.naming.*; +import java.util.Hashtable; + +/** + * Used by {@link PooledContextSource} to wrap a {@link Context}, delegating most methods + * to the underlying context, retains a reference to the pool the context was checked out + * from and returns itself to the pool when {@link #close()} is called. + * + * @since 2.0 + * @author Eric Dalquist + */ +public class DelegatingContext implements Context { + private KeyedObjectPool keyedObjectPool; + private Context delegateContext; + private final DirContextType dirContextType; + + + /** + * Create a new delegating context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateContext The context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingContext(KeyedObjectPool keyedObjectPool, Context delegateContext, DirContextType dirContextType) { + Assert.notNull(keyedObjectPool, "keyedObjectPool may not be null"); + Assert.notNull(delegateContext, "delegateContext may not be null"); + Assert.notNull(dirContextType, "dirContextType may not be null"); + + this.keyedObjectPool = keyedObjectPool; + this.delegateContext = delegateContext; + this.dirContextType = dirContextType; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this context proxy + */ + public Context getDelegateContext() { + return this.delegateContext; + } + + /** + * Recursivley inspect delegates until a non-delegating context is found. + * + * @return The innermost (real) Context that is being delegated to. + */ + public Context getInnermostDelegateContext() { + final Context delegateContext = this.getDelegateContext(); + + if (delegateContext instanceof DelegatingContext) { + return ((DelegatingContext)delegateContext).getInnermostDelegateContext(); + } + + return delegateContext; + } + + /** + * @throws NamingException If the delegate is null, {@link #close()} has been called. + */ + protected void assertOpen() throws NamingException { + if (this.delegateContext == null) { + throw new NamingException("Context is closed."); + } + } + + + //***** Object methods *****// + + /** + * @see Object#equals(Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Context)) { + return false; + } + + final Context thisContext = this.getInnermostDelegateContext(); + Context otherContext = (Context)obj; + if (otherContext instanceof DelegatingContext) { + otherContext = ((DelegatingContext)otherContext).getInnermostDelegateContext(); + } + + return thisContext == otherContext || (thisContext != null && thisContext.equals(otherContext)); + } + + /** + * @see Object#hashCode() + */ + public int hashCode() { + final Context context = this.getInnermostDelegateContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see Object#toString() + */ + public String toString() { + final Context context = this.getInnermostDelegateContext(); + return (context != null ? context.toString() : "Context is closed"); + } + + + //***** Context Interface Delegates *****// + + /** + * @see Context#addToEnvironment(String, Object) + */ + public Object addToEnvironment(String propName, Object propVal) throws NamingException { + throw new UnsupportedOperationException("Cannot call addToEnvironment on a pooled context"); + } + + /** + * @see Context#bind(Name, Object) + */ + public void bind(Name name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().bind(name, obj); + } + + /** + * @see Context#bind(String, Object) + */ + public void bind(String name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().bind(name, obj); + } + + /** + * @see Context#close() + */ + public void close() throws NamingException { + final Context context = this.getInnermostDelegateContext(); + if (context == null) { + return; + } + + //Get a local reference so the member can be nulled earlier + this.delegateContext = null; + + //Return the object to the Pool and then null the pool reference + try { + boolean valid = true; + + if (context instanceof FailureAwareContext) { + FailureAwareContext failureAwareContext = (FailureAwareContext) context; + if(failureAwareContext.hasFailed()) { + valid = false; + } + } + + if (valid) { + this.keyedObjectPool.returnObject(this.dirContextType, context); + } else { + this.keyedObjectPool.invalidateObject(this.dirContextType, context); + } + } + catch (Exception e) { + final NamingException namingException = new NamingException("Failed to return delegate Context to pool."); + namingException.setRootCause(e); + throw namingException; + } + finally { + this.keyedObjectPool = null; + } + } + + /** + * @see Context#composeName(Name, Name) + */ + public Name composeName(Name name, Name prefix) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().composeName(name, prefix); + } + + /** + * @see Context#composeName(String, String) + */ + public String composeName(String name, String prefix) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().composeName(name, prefix); + } + + /** + * @see Context#createSubcontext(Name) + */ + public Context createSubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see Context#createSubcontext(String) + */ + public Context createSubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see Context#destroySubcontext(Name) + */ + public void destroySubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call destroySubcontext on a pooled context"); + } + + /** + * @see Context#destroySubcontext(String) + */ + public void destroySubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call destroySubcontext on a pooled context"); + } + + /** + * @see Context#getEnvironment() + */ + public Hashtable getEnvironment() throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getEnvironment(); + } + + /** + * @see Context#getNameInNamespace() + */ + public String getNameInNamespace() throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameInNamespace(); + } + + /** + * @see Context#getNameParser(Name) + */ + public NameParser getNameParser(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameParser(name); + } + + /** + * @see Context#getNameParser(String) + */ + public NameParser getNameParser(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameParser(name); + } + + /** + * @see Context#list(Name) + */ + public NamingEnumeration list(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().list(name); + } + + /** + * @see Context#list(String) + */ + public NamingEnumeration list(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().list(name); + } + + /** + * @see Context#listBindings(Name) + */ + public NamingEnumeration listBindings(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().listBindings(name); + } + + /** + * @see Context#listBindings(String) + */ + public NamingEnumeration listBindings(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().listBindings(name); + } + + /** + * @see Context#lookup(Name) + */ + public Object lookup(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookup(name); + } + + /** + * @see Context#lookup(String) + */ + public Object lookup(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookup(name); + } + + /** + * @see Context#lookupLink(Name) + */ + public Object lookupLink(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookupLink(name); + } + + /** + * @see Context#lookupLink(String) + */ + public Object lookupLink(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookupLink(name); + } + + /** + * @see Context#rebind(Name, Object) + */ + public void rebind(Name name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rebind(name, obj); + } + + /** + * @see Context#rebind(String, Object) + */ + public void rebind(String name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rebind(name, obj); + } + + /** + * @see Context#removeFromEnvironment(String) + */ + public Object removeFromEnvironment(String propName) throws NamingException { + throw new UnsupportedOperationException("Cannot call removeFromEnvironment on a pooled context"); + } + + /** + * @see Context#rename(Name, Name) + */ + public void rename(Name oldName, Name newName) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rename(oldName, newName); + } + + /** + * @see Context#rename(String, String) + */ + public void rename(String oldName, String newName) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rename(oldName, newName); + } + + /** + * @see Context#unbind(Name) + */ + public void unbind(Name name) throws NamingException { + this.assertOpen(); + this.getDelegateContext().unbind(name); + } + + /** + * @see Context#unbind(String) + */ + public void unbind(String name) throws NamingException { + this.assertOpen(); + this.getDelegateContext().unbind(name); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/DelegatingDirContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/DelegatingDirContext.java new file mode 100644 index 000000000..3d408ba78 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/DelegatingDirContext.java @@ -0,0 +1,361 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.pool2; + +import com.fr.third.org.apache.commons.pool2.KeyedObjectPool; +import com.fr.third.springframework.ldap.core.DirContextProxy; +import com.fr.third.springframework.ldap.pool2.DirContextType; +import com.fr.third.springframework.ldap.pool2.factory.PooledContextSource; +import com.fr.third.springframework.util.Assert; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.*; + + +/** + * Used by {@link PooledContextSource} to wrap a {@link DirContext}, delegating most methods + * to the underlying context. This class extends {@link DelegatingContext} which handles returning + * the context to the pool on a call to {@link #close()} + * + * @since 2.0 + * @author Eric Dalquist + * @author Anindya Chatterjee + */ +public class DelegatingDirContext extends DelegatingContext implements DirContext, DirContextProxy { + private DirContext delegateDirContext; + + /** + * Create a new delegating dir context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateDirContext The dir context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingDirContext(KeyedObjectPool keyedObjectPool, + DirContext delegateDirContext, DirContextType dirContextType) { + super(keyedObjectPool, delegateDirContext, dirContextType); + Assert.notNull(delegateDirContext, "delegateDirContext may not be null"); + + this.delegateDirContext = delegateDirContext; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this dir context proxy + */ + public DirContext getDelegateDirContext() { + return this.delegateDirContext; + } + + public Context getDelegateContext() { + return this.getDelegateDirContext(); + } + + /** + * Recursivley inspect delegates until a non-delegating dir context is found. + * + * @return The innermost (real) DirContext that is being delegated to. + */ + public DirContext getInnermostDelegateDirContext() { + final DirContext delegateDirContext = this.getDelegateDirContext(); + + if (delegateDirContext instanceof DelegatingDirContext) { + return ((DelegatingDirContext)delegateDirContext).getInnermostDelegateDirContext(); + } + + return delegateDirContext; + } + + protected void assertOpen() throws NamingException { + if (this.delegateDirContext == null) { + throw new NamingException("DirContext is closed."); + } + + super.assertOpen(); + } + + + //***** Object methods *****// + + /** + * @see Object#equals(Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DirContext)) { + return false; + } + + final DirContext thisDirContext = this.getInnermostDelegateDirContext(); + DirContext otherDirContext = (DirContext)obj; + if (otherDirContext instanceof DelegatingDirContext) { + otherDirContext = ((DelegatingDirContext)otherDirContext).getInnermostDelegateDirContext(); + } + + return thisDirContext == otherDirContext || (thisDirContext != null && thisDirContext.equals(otherDirContext)); + } + + /** + * @see Object#hashCode() + */ + public int hashCode() { + final DirContext context = this.getInnermostDelegateDirContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see Object#toString() + */ + public String toString() { + final DirContext context = this.getInnermostDelegateDirContext(); + return (context != null ? context.toString() : "DirContext is closed"); + } + + + //***** DirContextProxy Interface Methods *****// + + /* (non-Javadoc) + * @see com.fr.third.springframework.ldap.core.DirContextProxy#getTargetContext() + */ + public DirContext getTargetContext() { + return this.getInnermostDelegateDirContext(); + } + + + //***** DirContext Interface Delegates *****// + + /** + * @see DirContext#bind(Name, Object, Attributes) + */ + public void bind(Name name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().bind(name, obj, attrs); + } + + /** + * @see DirContext#bind(String, Object, Attributes) + */ + public void bind(String name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().bind(name, obj, attrs); + } + + /** + * @see DirContext#createSubcontext(Name, Attributes) + */ + public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see DirContext#createSubcontext(String, Attributes) + */ + public DirContext createSubcontext(String name, Attributes attrs) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see DirContext#getAttributes(Name, String[]) + */ + public Attributes getAttributes(Name name, String[] attrIds) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name, attrIds); + } + + /** + * @see DirContext#getAttributes(Name) + */ + public Attributes getAttributes(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name); + } + + /** + * @see DirContext#getAttributes(String, String[]) + */ + public Attributes getAttributes(String name, String[] attrIds) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name, attrIds); + } + + /** + * @see DirContext#getAttributes(String) + */ + public Attributes getAttributes(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name); + } + + /** + * @see DirContext#getSchema(Name) + */ + public DirContext getSchema(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchema on a pooled context"); + } + + /** + * @see DirContext#getSchema(String) + */ + public DirContext getSchema(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchema on a pooled context"); + } + + /** + * @see DirContext#getSchemaClassDefinition(Name) + */ + public DirContext getSchemaClassDefinition(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchemaClassDefinition on a pooled context"); + } + + /** + * @see DirContext#getSchemaClassDefinition(String) + */ + public DirContext getSchemaClassDefinition(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchemaClassDefinition on a pooled context"); + } + + /** + * @see DirContext#modifyAttributes(Name, int, Attributes) + */ + public void modifyAttributes(Name name, int modOp, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, modOp, attrs); + } + + /** + * @see DirContext#modifyAttributes(Name, ModificationItem[]) + */ + public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mods); + } + + /** + * @see DirContext#modifyAttributes(String, int, Attributes) + */ + public void modifyAttributes(String name, int modOp, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, modOp, attrs); + } + + /** + * @see DirContext#modifyAttributes(String, ModificationItem[]) + */ + public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mods); + } + + /** + * @see DirContext#rebind(Name, Object, Attributes) + */ + public void rebind(Name name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().rebind(name, obj, attrs); + } + + /** + * @see DirContext#rebind(String, Object, Attributes) + */ + public void rebind(String name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().rebind(name, obj, attrs); + } + + /** + * @see DirContext#search(Name, Attributes, String[]) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes, attributesToReturn); + } + + /** + * @see DirContext#search(Name, Attributes) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes); + } + + /** + * @see DirContext#search(Name, String, Object[], SearchControls) + */ + public NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filterExpr, filterArgs, cons); + } + + /** + * @see DirContext#search(Name, String, SearchControls) + */ + public NamingEnumeration search(Name name, String filter, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filter, cons); + } + + /** + * @see DirContext#search(String, Attributes, String[]) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes, attributesToReturn); + } + + /** + * @see DirContext#search(String, Attributes) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes); + } + + /** + * @see DirContext#search(String, String, Object[], SearchControls) + */ + public NamingEnumeration search(String name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filterExpr, filterArgs, cons); + } + + /** + * @see DirContext#search(String, String, SearchControls) + */ + public NamingEnumeration search(String name, String filter, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filter, cons); + } + + /** + * @see DelegatingContext#close() + */ + public void close() throws NamingException { + if (this.delegateDirContext == null) { + return; + } + + super.close(); + this.delegateDirContext = null; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/DelegatingLdapContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/DelegatingLdapContext.java new file mode 100644 index 000000000..b1084253f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/DelegatingLdapContext.java @@ -0,0 +1,202 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.pool2; + +import com.fr.third.org.apache.commons.pool2.KeyedObjectPool; +import com.fr.third.springframework.ldap.pool2.DirContextType; +import com.fr.third.springframework.ldap.pool2.factory.PooledContextSource; +import com.fr.third.springframework.util.Assert; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.ExtendedRequest; +import javax.naming.ldap.ExtendedResponse; +import javax.naming.ldap.LdapContext; + +/** + * Used by {@link PooledContextSource} to wrap a {@link LdapContext}, delegating most methods + * to the underlying context. This class extends {@link DelegatingDirContext} which handles returning + * the context to the pool on a call to {@link #close()} + * + * @since 2.0 + * @author Eric Dalquist + * @author Anindya Chatterjee + */ +public class DelegatingLdapContext extends DelegatingDirContext implements LdapContext { + private LdapContext delegateLdapContext; + + /** + * Create a new delegating ldap context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateLdapContext The ldap context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingLdapContext(KeyedObjectPool keyedObjectPool, + LdapContext delegateLdapContext, DirContextType dirContextType) { + super(keyedObjectPool, delegateLdapContext, dirContextType); + Assert.notNull(delegateLdapContext, "delegateLdapContext may not be null"); + + this.delegateLdapContext = delegateLdapContext; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this ldap context proxy + */ + public LdapContext getDelegateLdapContext() { + return this.delegateLdapContext; + } + + // cannot return subtype in overridden method unless Java5 + public DirContext getDelegateDirContext() { + return this.getDelegateLdapContext(); + } + + /** + * Recursivley inspect delegates until a non-delegating ldap context is found. + * + * @return The innermost (real) DirContext that is being delegated to. + */ + public LdapContext getInnermostDelegateLdapContext() { + final LdapContext delegateLdapContext = this.getDelegateLdapContext(); + + if (delegateLdapContext instanceof DelegatingLdapContext) { + return ((DelegatingLdapContext)delegateLdapContext).getInnermostDelegateLdapContext(); + } + + return delegateLdapContext; + } + + protected void assertOpen() throws NamingException { + if (this.delegateLdapContext == null) { + throw new NamingException("LdapContext is closed."); + } + + super.assertOpen(); + } + + + //***** Object methods *****// + + /** + * @see Object#equals(Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LdapContext)) { + return false; + } + + final LdapContext thisLdapContext = this.getInnermostDelegateLdapContext(); + LdapContext otherLdapContext = (LdapContext)obj; + if (otherLdapContext instanceof DelegatingLdapContext) { + otherLdapContext = ((DelegatingLdapContext)otherLdapContext).getInnermostDelegateLdapContext(); + } + + return thisLdapContext == otherLdapContext || (thisLdapContext != null && thisLdapContext.equals(otherLdapContext)); + } + + /** + * @see Object#hashCode() + */ + public int hashCode() { + final LdapContext context = this.getInnermostDelegateLdapContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see Object#toString() + */ + public String toString() { + final LdapContext context = this.getInnermostDelegateLdapContext(); + return (context != null ? context.toString() : "LdapContext is closed"); + } + + + //***** LdapContext Interface Delegates *****// + + /** + * @see LdapContext#extendedOperation(ExtendedRequest) + */ + public ExtendedResponse extendedOperation(ExtendedRequest request) throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().extendedOperation(request); + } + + /** + * @see LdapContext#getConnectControls() + */ + public Control[] getConnectControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getConnectControls(); + } + + /** + * @see LdapContext#getRequestControls() + */ + public Control[] getRequestControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getRequestControls(); + } + + /** + * @see LdapContext#getResponseControls() + */ + public Control[] getResponseControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getResponseControls(); + } + + /** + * @see LdapContext#newInstance(Control[]) + */ + public LdapContext newInstance(Control[] requestControls) throws NamingException { + throw new UnsupportedOperationException("Cannot call newInstance on a pooled context"); + } + + /** + * @see LdapContext#reconnect(Control[]) + */ + public void reconnect(Control[] connCtls) throws NamingException { + throw new UnsupportedOperationException("Cannot call reconnect on a pooled context"); + } + + /** + * @see LdapContext#setRequestControls(Control[]) + */ + public void setRequestControls(Control[] requestControls) throws NamingException { + throw new UnsupportedOperationException("Cannot call setRequestControls on a pooled context"); + } + + /** + * @see DelegatingDirContext#close() + */ + public void close() throws NamingException { + if (this.delegateLdapContext == null) { + return; + } + + super.close(); + this.delegateLdapContext = null; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/DirContextType.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/DirContextType.java new file mode 100644 index 000000000..b81d62d8e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/DirContextType.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool2; + +import com.fr.third.springframework.ldap.core.ContextSource; + +import javax.naming.directory.DirContext; + + +/** + * An enum representing the two types of {@link DirContext}s that can be returned by a + * {@link ContextSource}. + * + * @author Eric Dalquist + */ +public final class DirContextType { + private String name; + + private DirContextType(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + /** + * The type of {@link DirContext} returned by {@link ContextSource#getReadOnlyContext()} + */ + public static final DirContextType READ_ONLY = new DirContextType("READ_ONLY"); + + /** + * The type of {@link DirContext} returned by {@link ContextSource#getReadWriteContext()} + */ + public static final DirContextType READ_WRITE = new DirContextType("READ_WRITE"); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/FailureAwareContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/FailureAwareContext.java new file mode 100644 index 000000000..15ded8ab6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/FailureAwareContext.java @@ -0,0 +1,24 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool2; + +/** + * @author Mattias Hellborg Arthursson + */ +public interface FailureAwareContext { + boolean hasFailed(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/MutableDelegatingLdapContext.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/MutableDelegatingLdapContext.java new file mode 100644 index 000000000..927c83bf2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/MutableDelegatingLdapContext.java @@ -0,0 +1,58 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool2; + +import com.fr.third.org.apache.commons.pool2.KeyedObjectPool; +import com.fr.third.springframework.ldap.pool2.factory.MutablePooledContextSource; + +import javax.naming.NamingException; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +/** + * Used by {@link MutablePooledContextSource} to wrap a {@link LdapContext}, + * delegating most methods to the underlying context. This class extends + * {@link DelegatingLdapContext}, allowing request controls to be set on the + * wrapped ldap context. This enables the Spring LDAP pooling to be used for + * scenarios such as paged results. + * + * @since 2.0 + * @author Ulrik Sandberg + * @author Anindya Chatterjee + */ +public class MutableDelegatingLdapContext extends DelegatingLdapContext { + + /** + * Create a new mutable delegating ldap context for the specified pool, + * context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out + * from. + * @param delegateLdapContext The ldap context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public MutableDelegatingLdapContext(KeyedObjectPool keyedObjectPool, LdapContext delegateLdapContext, + DirContextType dirContextType) { + super(keyedObjectPool, delegateLdapContext, dirContextType); + } + + public void setRequestControls(Control[] requestControls) throws NamingException { + assertOpen(); + getDelegateLdapContext().setRequestControls(requestControls); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/DirContextPoolableObjectFactory.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/DirContextPoolableObjectFactory.java new file mode 100644 index 000000000..6106f2f8b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/DirContextPoolableObjectFactory.java @@ -0,0 +1,323 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.pool2.factory; + +import com.fr.third.org.apache.commons.pool2.BaseKeyedPooledObjectFactory; +import com.fr.third.org.apache.commons.pool2.PooledObject; +import com.fr.third.org.apache.commons.pool2.impl.DefaultPooledObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.DirContextProxy; +import com.fr.third.springframework.ldap.pool2.DirContextType; +import com.fr.third.springframework.ldap.pool2.FailureAwareContext; +import com.fr.third.springframework.ldap.pool2.validation.DirContextValidator; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.Assert; + +import javax.naming.CommunicationException; +import javax.naming.directory.DirContext; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Factory that creates {@link DirContext} instances for pooling via a + * configured {@link ContextSource}. The {@link DirContext}s are keyed based + * on if they are read only or read/write. The expected key type is the + * {@link com.fr.third.springframework.ldap.pool2.DirContextType} enum. + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyDescriptionRequiredDefault
contextSource The {@link ContextSource} to get {@link DirContext}s from + * for adding to the pool. Yesnull
dirContextValidator The {@link DirContextValidator} to use to validate + * {@link DirContext}s. This is only required if the pool has validation of any + * kind turned on. Nonull
+ * + * @since 2.0 + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + * @author Mattias Hellborg Arthursson + * @author Anindya Chatterjee + */ +class DirContextPooledObjectFactory extends BaseKeyedPooledObjectFactory { + /** + * Logger for this class and subclasses + */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private static final Set> DEFAULT_NONTRANSIENT_EXCEPTIONS + = new HashSet>(); + + static { + DEFAULT_NONTRANSIENT_EXCEPTIONS.add(CommunicationException.class); + }; + + private ContextSource contextSource; + + private DirContextValidator dirContextValidator; + + private Set> nonTransientExceptions = DEFAULT_NONTRANSIENT_EXCEPTIONS; + + void setNonTransientExceptions(Collection> nonTransientExceptions) { + this.nonTransientExceptions = new HashSet>(nonTransientExceptions); + } + + /** + * @return the contextSource + */ + public ContextSource getContextSource() { + return this.contextSource; + } + + /** + * @param contextSource + * the contextSource to set + */ + public void setContextSource(ContextSource contextSource) { + if (contextSource == null) { + throw new IllegalArgumentException("contextSource may not be null"); + } + + this.contextSource = contextSource; + } + + /** + * @return the dirContextValidator + */ + public DirContextValidator getDirContextValidator() { + return this.dirContextValidator; + } + + /** + * @param dirContextValidator + * the dirContextValidator to set + */ + public void setDirContextValidator(DirContextValidator dirContextValidator) { + if (dirContextValidator == null) { + throw new IllegalArgumentException( + "dirContextValidator may not be null"); + } + + this.dirContextValidator = dirContextValidator; + } + + private Object makeFailureAwareProxy(DirContext readOnlyContext) { + return Proxy.newProxyInstance(DirContextProxy.class + .getClassLoader(), + new Class[]{ + LdapUtils.getActualTargetClass(readOnlyContext), + DirContextProxy.class, + FailureAwareContext.class}, + new FailureAwareContextProxy(readOnlyContext)); + } + + /** + * @see BaseKeyedPooledObjectFactory#validateObject(Object, PooledObject) + * + * */ + @Override + public boolean validateObject(Object key, PooledObject pooledObject) { + Assert.notNull(this.dirContextValidator, + "DirContextValidator may not be null"); + Assert.isTrue(key instanceof DirContextType, + "key must be a DirContextType"); + Assert.notNull(pooledObject, + "The Object to validate must not be null"); + Assert.isTrue(pooledObject.getObject() instanceof DirContext, + "The Object to validate must be of type '" + DirContext.class + + "'"); + + try { + final DirContextType contextType = (DirContextType) key; + final DirContext dirContext = (DirContext) pooledObject.getObject(); + return this.dirContextValidator.validateDirContext(contextType, + dirContext); + } catch (Exception e) { + this.logger.warn("Failed to validate '" + pooledObject.getObject() + + "' due to an unexpected exception.", e); + return false; + } + } + + /** + * @see BaseKeyedPooledObjectFactory#destroyObject(Object, PooledObject) + * + * */ + @Override + public void destroyObject(Object key, PooledObject pooledObject) throws Exception { + Assert.notNull(pooledObject, + "The Object to destroy must not be null"); + Assert.isTrue(pooledObject.getObject() instanceof DirContext, + "The Object to destroy must be of type '" + DirContext.class + + "'"); + + try { + final DirContext dirContext = (DirContext) pooledObject.getObject(); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Closing " + key + " DirContext='" + + dirContext + "'"); + } + dirContext.close(); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Closed " + key + " DirContext='" + + dirContext + "'"); + } + } catch (Exception e) { + this.logger.warn( + "An exception occured while closing '" + pooledObject.getObject() + "'", e); + } + } + + /** + * @see BaseKeyedPooledObjectFactory#create(Object) + * + * */ + @Override + public Object create(Object key) throws Exception { + Assert.notNull(this.contextSource, "ContextSource may not be null"); + Assert.isTrue(key instanceof DirContextType, + "key must be a DirContextType"); + + final DirContextType contextType = (DirContextType) key; + if (this.logger.isDebugEnabled()) { + this.logger.debug("Creating a new " + contextType + " DirContext"); + } + + if (contextType == DirContextType.READ_WRITE) { + final DirContext readWriteContext = this.contextSource + .getReadWriteContext(); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Created new " + DirContextType.READ_WRITE + + " DirContext='" + readWriteContext + "'"); + } + + return makeFailureAwareProxy(readWriteContext); + } else if (contextType == DirContextType.READ_ONLY) { + + final DirContext readOnlyContext = this.contextSource + .getReadOnlyContext(); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Created new " + DirContextType.READ_ONLY + + " DirContext='" + readOnlyContext + "'"); + } + + return makeFailureAwareProxy(readOnlyContext); + } else { + throw new IllegalArgumentException("Unrecognized ContextType: " + + contextType); + } + } + + /** + * @see BaseKeyedPooledObjectFactory#wrap(Object) + * + * */ + @Override + public PooledObject wrap(Object value) { + return new DefaultPooledObject(value); + } + + /** + * Invocation handler that checks thrown exceptions against the configured {@link #nonTransientExceptions}, + * marking the Context as invalid on match. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ + private class FailureAwareContextProxy implements + InvocationHandler { + + private DirContext target; + + private boolean hasFailed = false; + + public FailureAwareContextProxy(DirContext target) { + Assert.notNull(target, "Target must not be null"); + this.target = target; + } + + /* + * @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 { + + String methodName = method.getName(); + if (methodName.equals("getTargetContext")) { + return target; + } else if (methodName.equals("hasFailed")) { + return hasFailed; + } + + try { + return method.invoke(target, args); + } + catch (InvocationTargetException e) { + Throwable targetException = e.getTargetException(); + Class targetExceptionClass = targetException.getClass(); + + boolean nonTransientEncountered = false; + for (Class clazz : nonTransientExceptions) { + if(clazz.isAssignableFrom(targetExceptionClass)) { + logger.info( + String.format("An %s - explicitly configured to be a non-transient exception - encountered; eagerly invalidating the target context.", + targetExceptionClass)); + nonTransientEncountered = true; + break; + } + } + + if(nonTransientEncountered) { + hasFailed = true; + } else { + if (logger.isDebugEnabled()) { + logger.debug(String.format("An %s - not explicitly configured to be a non-transient exception - encountered; ignoring.", + targetExceptionClass)); + } + } + + throw targetException; + } + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/MutablePooledContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/MutablePooledContextSource.java new file mode 100644 index 000000000..79d4d74e9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/MutablePooledContextSource.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool2.factory; + +import com.fr.third.springframework.dao.DataAccessResourceFailureException; +import com.fr.third.springframework.ldap.pool2.DirContextType; +import com.fr.third.springframework.ldap.pool2.DelegatingDirContext; +import com.fr.third.springframework.ldap.pool2.MutableDelegatingLdapContext; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +/** + * A {@link PooledContextSource} subclass that creates + * {@link MutableDelegatingLdapContext} instances. This enables the Spring LDAP + * pooling to be used in scenarios that require request controls to be set, such + * as paged results. + * + * @since 2.0 + * @author Anindya Chatterjee + */ +public class MutablePooledContextSource extends PooledContextSource { + /** + * Creates a new pooling context source, setting up the DirContext object + * factory and generic keyed object pool. + * + * @param poolConfig pool configurations to set. + */ + public MutablePooledContextSource(PoolConfig poolConfig) { + super(poolConfig); + } + + protected DirContext getContext(DirContextType dirContextType) { + final DirContext dirContext; + try { + dirContext = (DirContext) this.keyedObjectPool.borrowObject(dirContextType); + } + catch (Exception e) { + throw new DataAccessResourceFailureException("Failed to borrow DirContext from pool.", e); + } + + if (dirContext instanceof LdapContext) { + return new MutableDelegatingLdapContext(this.keyedObjectPool, (LdapContext) dirContext, dirContextType); + } + + return new DelegatingDirContext(this.keyedObjectPool, dirContext, dirContextType); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/PoolConfig.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/PoolConfig.java new file mode 100644 index 000000000..823354080 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/PoolConfig.java @@ -0,0 +1,332 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.pool2.factory; + + +import com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; + +/** + * A wrapper class for the pool configuration. It helps to create an instance of + * {@link GenericKeyedObjectPoolConfig}. + * + * @author Anindya Chatterjee + * @since 2.0 + */ +public class PoolConfig { + private int maxIdlePerKey = 8; + private int maxTotal = -1; + private int maxTotalPerKey = 8; + private int minIdlePerKey = 0; + private boolean blockWhenExhausted = true; + private String evictionPolicyClassName = "com.fr.third.org.apache.commons.pool2.impl.DefaultEvictionPolicy"; + private boolean fairness = false; + private boolean jmxEnabled = true; + private String jmxNameBase = null; + private String jmxNamePrefix = "ldap-pool"; + private boolean lifo = true; + private long maxWaitMillis = -1L; + private long minEvictableIdleTimeMillis = 1000L * 60L * 30L; + private int numTestsPerEvictionRun = 3; + private long softMinEvictableIdleTimeMillis = -1L; + private boolean testOnBorrow = false; + private boolean testOnCreate = false; + private boolean testOnReturn = false; + private boolean testWhileIdle = false; + private long timeBetweenEvictionRunsMillis = -1L; + + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMaxIdlePerKey(int) + * + */ + public void setMaxIdlePerKey(int maxIdlePerKey) { + this.maxIdlePerKey = maxIdlePerKey; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMaxTotal(int) + * + */ + public void setMaxTotal(int maxTotal) { + this.maxTotal = maxTotal; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMaxTotalPerKey(int) + */ + public void setMaxTotalPerKey(int maxTotalPerKey) { + this.maxTotalPerKey = maxTotalPerKey; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMinIdlePerKey(int) + */ + public void setMinIdlePerKey(int minIdlePerKey) { + this.minIdlePerKey = minIdlePerKey; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setBlockWhenExhausted(boolean) + */ + public void setBlockWhenExhausted(boolean blockWhenExhausted) { + this.blockWhenExhausted = blockWhenExhausted; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setEvictionPolicyClassName(String) + */ + public void setEvictionPolicyClassName(String evictionPolicyClassName) { + this.evictionPolicyClassName = evictionPolicyClassName; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setFairness(boolean) + */ + public void setFairness(boolean fairness) { + this.fairness = fairness; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setJmxEnabled(boolean) + */ + public void setJmxEnabled(boolean jmxEnabled) { + this.jmxEnabled = jmxEnabled; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setJmxNameBase(String) + */ + public void setJmxNameBase(String jmxNameBase) { + this.jmxNameBase = jmxNameBase; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setJmxNamePrefix(String) + */ + public void setJmxNamePrefix(String jmxNamePrefix) { + this.jmxNamePrefix = jmxNamePrefix; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setLifo(boolean) + */ + public void setLifo(boolean lifo) { + this.lifo = lifo; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMaxWaitMillis(long) + */ + public void setMaxWaitMillis(long maxWaitMillis) { + this.maxWaitMillis = maxWaitMillis; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMinEvictableIdleTimeMillis(long) + */ + public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setNumTestsPerEvictionRun(int) + */ + public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { + this.numTestsPerEvictionRun = numTestsPerEvictionRun; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setSoftMinEvictableIdleTimeMillis(long) + */ + public void setSoftMinEvictableIdleTimeMillis(long softMinEvictableIdleTimeMillis) { + this.softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTestOnBorrow(boolean) + */ + public void setTestOnBorrow(boolean testOnBorrow) { + this.testOnBorrow = testOnBorrow; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTestOnCreate(boolean) + */ + public void setTestOnCreate(boolean testOnCreate) { + this.testOnCreate = testOnCreate; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTestOnReturn(boolean) + */ + public void setTestOnReturn(boolean testOnReturn) { + this.testOnReturn = testOnReturn; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTestWhileIdle(boolean) + */ + public void setTestWhileIdle(boolean testWhileIdle) { + this.testWhileIdle = testWhileIdle; + } + + /** + * @see com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTimeBetweenEvictionRunsMillis(long) + */ + public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMaxIdlePerKey() + */ + public int getMaxIdlePerKey() { + return maxIdlePerKey; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMaxTotal() + */ + public int getMaxTotal() { + return maxTotal; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMaxIdlePerKey() + */ + public int getMaxTotalPerKey() { + return maxTotalPerKey; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMinIdlePerKey() + */ + public int getMinIdlePerKey() { + return minIdlePerKey; + } + + /** + * @see GenericKeyedObjectPoolConfig#getBlockWhenExhausted() + */ + public boolean isBlockWhenExhausted() { + return blockWhenExhausted; + } + + /** + * @see GenericKeyedObjectPoolConfig#getEvictionPolicyClassName() + */ + public String getEvictionPolicyClassName() { + return evictionPolicyClassName; + } + + /** + * @see GenericKeyedObjectPoolConfig#getFairness() + */ + public boolean isFairness() { + return fairness; + } + + /** + * @see GenericKeyedObjectPoolConfig#getJmxEnabled() + */ + public boolean isJmxEnabled() { + return jmxEnabled; + } + + /** + * @see GenericKeyedObjectPoolConfig#getJmxNameBase() + */ + public String getJmxNameBase() { + return jmxNameBase; + } + + /** + * @see GenericKeyedObjectPoolConfig#getJmxNamePrefix() + */ + public String getJmxNamePrefix() { + return jmxNamePrefix; + } + + /** + * @see GenericKeyedObjectPoolConfig#getLifo() + */ + public boolean isLifo() { + return lifo; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMaxWaitMillis() + */ + public long getMaxWaitMillis() { + return maxWaitMillis; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMinEvictableIdleTimeMillis() + */ + public long getMinEvictableIdleTimeMillis() { + return minEvictableIdleTimeMillis; + } + + /** + * @see GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun() + */ + public int getNumTestsPerEvictionRun() { + return numTestsPerEvictionRun; + } + + /** + * @see GenericKeyedObjectPoolConfig#getSoftMinEvictableIdleTimeMillis() + */ + public long getSoftMinEvictableIdleTimeMillis() { + return softMinEvictableIdleTimeMillis; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTestOnBorrow() + */ + public boolean isTestOnBorrow() { + return testOnBorrow; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTestOnCreate() + */ + public boolean isTestOnCreate() { + return testOnCreate; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTestOnReturn() + */ + public boolean isTestOnReturn() { + return testOnReturn; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTestWhileIdle() + */ + public boolean isTestWhileIdle() { + return testWhileIdle; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTimeBetweenEvictionRunsMillis() + */ + public long getTimeBetweenEvictionRunsMillis() { + return timeBetweenEvictionRunsMillis; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/PooledContextSource.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/PooledContextSource.java new file mode 100644 index 000000000..0074f3985 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/PooledContextSource.java @@ -0,0 +1,310 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool2.factory; + +import com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import com.fr.third.org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.beans.factory.DisposableBean; +import com.fr.third.springframework.dao.DataAccessResourceFailureException; +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.support.DelegatingBaseLdapPathContextSourceSupport; +import com.fr.third.springframework.ldap.pool2.DirContextType; +import com.fr.third.springframework.ldap.pool2.validation.DirContextValidator; +import com.fr.third.springframework.ldap.pool2.DelegatingDirContext; +import com.fr.third.springframework.ldap.pool2.DelegatingLdapContext; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; +import java.util.Collection; + +/** + * A {@link ContextSource} implementation that wraps an object pool and another + * {@link ContextSource}. {@link DirContext}s are retrieved from the pool which + * maintains them. + * + * NOTE: This implementation is based on apache commons-pool2. + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Property Description Required Default
contextSource + * The {@link ContextSource} to get {@link DirContext}s from for adding to the + * pool.Yesnull
dirContextValidator + * The {@link com.fr.third.springframework.ldap.pool2.validation.DirContextValidator} to use for validating {@link DirContext}s. + * Required if any of the test/validate options are enabled.Nonull
poolConfigThe {@link PoolConfig} to configure the pool.Nonull
+ * + * @since 2.0 + * @author Eric Dalquist + * @author Anindya Chatterjee + */ +public class PooledContextSource + extends DelegatingBaseLdapPathContextSourceSupport + implements ContextSource, DisposableBean { + /** + * The logger for this class and sub-classes + */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected final GenericKeyedObjectPool keyedObjectPool; + + private final DirContextPooledObjectFactory dirContextPooledObjectFactory; + + private PoolConfig poolConfig; + + /** + * Creates a new pooling context source, setting up the DirContext object + * factory and generic keyed object pool. + */ + public PooledContextSource(PoolConfig poolConfig) { + this.dirContextPooledObjectFactory = new DirContextPooledObjectFactory(); + if (poolConfig != null) { + this.poolConfig = poolConfig; + GenericKeyedObjectPoolConfig objectPoolConfig = getConfig(poolConfig); + this.keyedObjectPool = + new GenericKeyedObjectPool(this.dirContextPooledObjectFactory, objectPoolConfig); + } else { + this.keyedObjectPool = + new GenericKeyedObjectPool(this.dirContextPooledObjectFactory); + } + } + + // ***** Pool Property Configuration *****// + + /** + * @return the poolConfig + * */ + public PoolConfig getPoolConfig() { + return poolConfig; + } + + /** + * @see GenericKeyedObjectPool#getNumIdle() + * */ + public int getNumIdle() { + return this.keyedObjectPool.getNumIdle(); + } + + /** + * @see GenericKeyedObjectPool#getNumIdle(Object) + * */ + public int getNumIdleRead() { + return this.keyedObjectPool.getNumIdle(DirContextType.READ_ONLY); + } + + /** + * @see GenericKeyedObjectPool#getNumIdle(Object) + * */ + public int getNumIdleWrite() { + return this.keyedObjectPool.getNumIdle(DirContextType.READ_WRITE); + } + + /** + * @see GenericKeyedObjectPool#getNumActive() + * */ + public int getNumActive() { + return this.keyedObjectPool.getNumActive(); + } + + /** + * @see GenericKeyedObjectPool#getNumActive(Object) + * */ + public int getNumActiveRead() { + return this.keyedObjectPool.getNumActive(DirContextType.READ_ONLY); + } + + /** + * @see GenericKeyedObjectPool#getNumActive(Object) + * */ + public int getNumActiveWrite() { + return this.keyedObjectPool.getNumActive(DirContextType.READ_WRITE); + } + + /** + * @see GenericKeyedObjectPool#getNumWaiters() + * */ + public int getNumWaiters() { + return this.keyedObjectPool.getNumWaiters(); + } + + // ***** Object Factory Property Configuration *****// + + /** + * @return the contextSource + */ + public ContextSource getContextSource() { + return this.dirContextPooledObjectFactory.getContextSource(); + } + + /** + * @return the dirContextValidator + */ + public DirContextValidator getDirContextValidator() { + return this.dirContextPooledObjectFactory.getDirContextValidator(); + } + + /** + * @param contextSource the contextSource to set + * Required + */ + public void setContextSource(ContextSource contextSource) { + this.dirContextPooledObjectFactory.setContextSource(contextSource); + } + + /** + * @param dirContextValidator the dirContextValidator to set + * Required + */ + public void setDirContextValidator(DirContextValidator dirContextValidator) { + this.dirContextPooledObjectFactory.setDirContextValidator(dirContextValidator); + } + + /** + * Configure the exception classes that are to be interpreted as no-transient with regards to eager + * context invalidation. If one of the configured exceptions (or subclasses of them) + * is thrown by any method on a pooled DirContext, that instance will immediately be marked + * as invalid without any additional testing (i.e. testOnReturn). + * This allows for more efficient management of dead connections. + * Default is {@link javax.naming.CommunicationException}. + * + * @param nonTransientExceptions the exception classes that should be interpreted as non-transient + * with regards to eager invalidation. + * @since 2.0 + */ + public void setNonTransientExceptions(Collection> nonTransientExceptions) { + this.dirContextPooledObjectFactory.setNonTransientExceptions(nonTransientExceptions); + } + + + // ***** DisposableBean interface methods *****// + + /* + * (non-Javadoc) + * + * @see com.fr.third.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + try { + this.keyedObjectPool.close(); + } + catch (Exception e) { + this.logger.warn("An exception occurred while closing the underlying pool.", e); + } + } + + @Override + protected ContextSource getTarget() { + return getContextSource(); + } + + // ***** ContextSource interface methods *****// + + @Override + public DirContext getReadOnlyContext() { + return this.getContext(DirContextType.READ_ONLY); + } + + @Override + public DirContext getReadWriteContext() { + return this.getContext(DirContextType.READ_WRITE); + } + + /** + * Gets a DirContext of the specified type from the keyed object pool. + * + * @param dirContextType The type of context to return. + * @return A wrapped DirContext of the specified type. + * @throws DataAccessResourceFailureException If retrieving the object from + * the pool throws an exception + */ + protected DirContext getContext(DirContextType dirContextType) { + final DirContext dirContext; + try { + dirContext = (DirContext) this.keyedObjectPool.borrowObject(dirContextType); + } + catch (Exception e) { + throw new DataAccessResourceFailureException("Failed to borrow DirContext from pool.", e); + } + + if (dirContext instanceof LdapContext) { + return new DelegatingLdapContext(this.keyedObjectPool, (LdapContext) dirContext, dirContextType); + } + + return new DelegatingDirContext(this.keyedObjectPool, dirContext, dirContextType); + } + + @Override + public DirContext getContext(String principal, String credentials) { + throw new UnsupportedOperationException("Not supported for this implementation"); + } + + private GenericKeyedObjectPoolConfig getConfig(PoolConfig poolConfig) { + GenericKeyedObjectPoolConfig objectPoolConfig = new GenericKeyedObjectPoolConfig(); + + objectPoolConfig.setMaxTotalPerKey(poolConfig.getMaxTotalPerKey()); + objectPoolConfig.setMaxTotal(poolConfig.getMaxTotal()); + + objectPoolConfig.setMaxIdlePerKey(poolConfig.getMaxIdlePerKey()); + objectPoolConfig.setMinIdlePerKey(poolConfig.getMinIdlePerKey()); + + objectPoolConfig.setTestWhileIdle(poolConfig.isTestWhileIdle()); + objectPoolConfig.setTestOnReturn(poolConfig.isTestOnReturn()); + objectPoolConfig.setTestOnCreate(poolConfig.isTestOnCreate()); + objectPoolConfig.setTestOnBorrow(poolConfig.isTestOnBorrow()); + + objectPoolConfig.setTimeBetweenEvictionRunsMillis(poolConfig.getTimeBetweenEvictionRunsMillis()); + objectPoolConfig.setEvictionPolicyClassName(poolConfig.getEvictionPolicyClassName()); + objectPoolConfig.setMinEvictableIdleTimeMillis(poolConfig.getMinEvictableIdleTimeMillis()); + objectPoolConfig.setNumTestsPerEvictionRun(poolConfig.getNumTestsPerEvictionRun()); + objectPoolConfig.setSoftMinEvictableIdleTimeMillis(poolConfig.getSoftMinEvictableIdleTimeMillis()); + + objectPoolConfig.setJmxEnabled(poolConfig.isJmxEnabled()); + objectPoolConfig.setJmxNameBase(poolConfig.getJmxNameBase()); + objectPoolConfig.setJmxNamePrefix(poolConfig.getJmxNamePrefix()); + + objectPoolConfig.setMaxWaitMillis(poolConfig.getMaxWaitMillis()); + + objectPoolConfig.setFairness(poolConfig.isFairness()); + objectPoolConfig.setBlockWhenExhausted(poolConfig.isBlockWhenExhausted()); + objectPoolConfig.setLifo(poolConfig.isLifo()); + + return objectPoolConfig; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/package.html b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/package.html new file mode 100644 index 000000000..176375cb2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/factory/package.html @@ -0,0 +1,7 @@ + + + +Core classes for the pooling library based on commons-pool2 library. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/package.html b/fine-spring/src/com/fr/third/springframework/ldap/pool2/package.html new file mode 100644 index 000000000..3231c7931 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/package.html @@ -0,0 +1,7 @@ + + + +Base classes for the pooling library based on commons-pool2 library. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/validation/DefaultDirContextValidator.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/validation/DefaultDirContextValidator.java new file mode 100644 index 000000000..d82115a81 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/validation/DefaultDirContextValidator.java @@ -0,0 +1,191 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool2.validation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.pool2.DirContextType; +import com.fr.third.springframework.util.Assert; + +import javax.naming.NamingEnumeration; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +/** + * Default {@link DirContext} validator that executes {@link DirContext#search(String, String, SearchControls)}. The + * name, filter and {@link SearchControls} are all configurable. There is no special handling for read only versus + * read write {@link DirContext}s. + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyDescriptionRequiredDefault
base + * The name parameter to the search method. + * No""
filter + * The filter parameter to the search method. + * No"objectclass=*"
searchControls + * The {@link SearchControls} parameter to the search method. + * No + * {@link SearchControls#setCountLimit(long)} = 1
+ * {@link SearchControls#setReturningAttributes(String[])} = new String[] { "objectclass" }
+ * {@link SearchControls#setTimeLimit(int)} = 500 + *
+ * + * @author Eric Dalquist + */ +public class DefaultDirContextValidator implements DirContextValidator { + public static final String DEFAULT_FILTER = "objectclass=*"; + private static final int DEFAULT_TIME_LIMIT = 500; + + /** + * Logger for this class and sub-classes + */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private String base; + private String filter; + private SearchControls searchControls; + + /** + * Create the default validator, creates {@link SearchControls} with search scope OBJECT_SCOPE, + * a countLimit of 1, returningAttributes of objectclass and timeLimit of 500. + * The default base is an empty string and the default filter is objectclass=* + */ + public DefaultDirContextValidator() { + this(SearchControls.OBJECT_SCOPE); + } + + /** + * Create a validator with all the defaults of the default constructor, but with the search scope set to the + * referred value. + * + * @param searchScope The searchScope to be set in the default SearchControls + */ + public DefaultDirContextValidator(int searchScope) { + this.searchControls = new SearchControls(); + this.searchControls.setSearchScope(searchScope); + this.searchControls.setCountLimit(1); + this.searchControls.setReturningAttributes(new String[] { "objectclass" }); + this.searchControls.setTimeLimit(DEFAULT_TIME_LIMIT); + + this.base = ""; + + this.filter = DEFAULT_FILTER; + } + + /** + * @return the baseName + */ + public String getBase() { + return this.base; + } + /** + * @param base the baseName to set + */ + public void setBase(String base) { + this.base = base; + } + /** + * @return the filter + */ + public String getFilter() { + return this.filter; + } + /** + * @param filter the filter to set + */ + public void setFilter(String filter) { + if (filter == null) { + throw new IllegalArgumentException("filter may not be null"); + } + + this.filter = filter; + } + /** + * @return the searchControls + */ + public SearchControls getSearchControls() { + return this.searchControls; + } + /** + * @param searchControls the searchControls to set + */ + public void setSearchControls(SearchControls searchControls) { + if (searchControls == null) { + throw new IllegalArgumentException("searchControls may not be null"); + } + + this.searchControls = searchControls; + } + + + /** + * @see DirContextValidator#validateDirContext(DirContextType, DirContext) + */ + public boolean validateDirContext(DirContextType contextType, DirContext dirContext) { + Assert.notNull(contextType, "contextType may not be null"); + Assert.notNull(dirContext, "dirContext may not be null"); + + try { + final NamingEnumeration searchResults = dirContext.search(this.base, this.filter, this.searchControls); + + if (searchResults.hasMore()) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("DirContext '" + dirContext + "' passed validation."); + } + + return true; + } + } + catch (Exception e) { + if(this.logger.isDebugEnabled()) { + this.logger.debug("DirContext '" + dirContext + "' failed validation with an exception.", e); + } + } + + if (this.logger.isInfoEnabled()) { + this.logger.info("DirContext '" + dirContext + "' failed validation."); + } + return false; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/validation/DirContextValidator.java b/fine-spring/src/com/fr/third/springframework/ldap/pool2/validation/DirContextValidator.java new file mode 100644 index 000000000..d150dc022 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/validation/DirContextValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.pool2.validation; + +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.pool2.DirContextType; + +import javax.naming.directory.DirContext; + +/** + * A validator for {@link DirContext}s. + * + * @author Eric Dalquist + */ +public interface DirContextValidator { + /** + * Validates the {@link DirContext}. A valid {@link DirContext} should be able + * to answer queries and if applicable write to the directory. + * + * @param contextType The type of the {@link DirContext}, refers to if {@link ContextSource#getReadOnlyContext()} or {@link ContextSource#getReadWriteContext()} was called to create the {@link DirContext} + * @param dirContext The {@link DirContext} to validate. + * @return true if the {@link DirContext} operated correctly during validation. + */ + boolean validateDirContext(DirContextType contextType, DirContext dirContext); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/pool2/validation/package.html b/fine-spring/src/com/fr/third/springframework/ldap/pool2/validation/package.html new file mode 100644 index 000000000..45aeeac46 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/pool2/validation/package.html @@ -0,0 +1,7 @@ + + + +Connection validation support for the pooling library. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/query/AppendableContainerCriteria.java b/fine-spring/src/com/fr/third/springframework/ldap/query/AppendableContainerCriteria.java new file mode 100644 index 000000000..0e68b08b4 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/query/AppendableContainerCriteria.java @@ -0,0 +1,26 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.query; + +import com.fr.third.springframework.ldap.filter.Filter; + +/** + * @author Mattias Hellborg Arthursson + */ +interface AppendableContainerCriteria extends ContainerCriteria { + ContainerCriteria append(Filter filter); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/query/ConditionCriteria.java b/fine-spring/src/com/fr/third/springframework/ldap/query/ConditionCriteria.java new file mode 100644 index 000000000..44c9656d3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/query/ConditionCriteria.java @@ -0,0 +1,113 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.query; + +/** + * Constructs a conditional LDAP filter based on the attribute specified in the previous builder step. + * + * @author Mattias Hellborg Arthursson + * + * @see LdapQueryBuilder#where(String) + * @see ContainerCriteria#and(String) + * @see ContainerCriteria#or(String) + * @since 2.0 + */ +public interface ConditionCriteria { + + /** + * Appends an {@link com.fr.third.springframework.ldap.filter.EqualsFilter}. + * + * @param value the value to compare with. + * @return an ContainerCriteria instance that can be used to continue append more criteria + * or as the LdapQuery instance to be used as instance to e.g. + * {@link com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper)}. + * + * @see com.fr.third.springframework.ldap.filter.EqualsFilter + */ + ContainerCriteria is(String value); + + /** + * Appends an {@link com.fr.third.springframework.ldap.filter.GreaterThanOrEqualsFilter}. + * + * @param value the value to compare with. + * @return an ContainerCriteria instance that can be used to continue append more criteria + * or as the LdapQuery instance to be used as instance to e.g. + * {@link com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper)}. + * + * @see com.fr.third.springframework.ldap.filter.GreaterThanOrEqualsFilter + */ + ContainerCriteria gte(String value); + + /** + * Appends a {@link com.fr.third.springframework.ldap.filter.LessThanOrEqualsFilter}. + * + * @param value the value to compare with. + * @return an ContainerCriteria instance that can be used to continue append more criteria + * or as the LdapQuery instance to be used as instance to e.g. + * {@link com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper)}. + * + * @see com.fr.third.springframework.ldap.filter.LessThanOrEqualsFilter + */ + ContainerCriteria lte(String value); + + /** + * Appends a {@link com.fr.third.springframework.ldap.filter.LikeFilter}. + * + * @param value the value to compare with. + * @return an ContainerCriteria instance that can be used to continue append more criteria + * or as the LdapQuery instance to be used as instance to e.g. + * {@link com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper)}. + * + * @see com.fr.third.springframework.ldap.filter.LikeFilter + */ + ContainerCriteria like(String value); + + /** + * Appends a {@link com.fr.third.springframework.ldap.filter.WhitespaceWildcardsFilter}. + * + * @param value the value to compare with. + * @return an ContainerCriteria instance that can be used to continue append more criteria + * or as the LdapQuery instance to be used as instance to e.g. + * {@link com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper)}. + * + * @see com.fr.third.springframework.ldap.filter.WhitespaceWildcardsFilter + */ + ContainerCriteria whitespaceWildcardsLike(String value); + + /** + * Appends a {@link com.fr.third.springframework.ldap.filter.PresentFilter}. + * + * @return an ContainerCriteria instance that can be used to continue append more criteria + * or as the LdapQuery instance to be used as instance to e.g. + * {@link com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper)}. + * + * @see com.fr.third.springframework.ldap.filter.PresentFilter + */ + ContainerCriteria isPresent(); + + /** + * Negates the currently constructed operation. In effect this means that the resulting filter will be + * wrapped in a {@link com.fr.third.springframework.ldap.filter.NotFilter}. + * + * @return an ContainerCriteria instance that can be used to continue append more criteria + * or as the LdapQuery instance to be used as instance to e.g. + * {@link com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper)}. + * + * @see com.fr.third.springframework.ldap.filter.NotFilter + */ + ConditionCriteria not(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/query/ContainerCriteria.java b/fine-spring/src/com/fr/third/springframework/ldap/query/ContainerCriteria.java new file mode 100644 index 000000000..7199c4425 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/query/ContainerCriteria.java @@ -0,0 +1,63 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.query; + +/** + * And/or filter builder support for LdapQuery. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public interface ContainerCriteria extends LdapQuery { + /** + * Append a logical And condition to the currently built filter. + * + * @param attribute Name of the attribute to specify a condition for. + * @return A ConditionCriteria instance for specifying the compare operation. + * @throws IllegalStateException if {@link #or(String)} has previously been called on this instance. + */ + ConditionCriteria and(String attribute); + + /** + * Append a logical Or condition to the currently built filter. + * + * @param attribute Name of the attribute to specify a condition for. + * @return A ConditionCriteria instance for specifying the compare operation. + * @throws IllegalStateException if {@link #and(String)} has previously been called on this instance. + */ + ConditionCriteria or(String attribute); + + /** + * Append an And condition for a nested criterion. Use {@link com.fr.third.springframework.ldap.query.LdapQueryBuilder#query()} + * to start the nested condition. Any base query information on the nested builder instance will not be considered. + * + * @param nested the nested criterion. + * + * @return A ConditionCriteria instance for specifying the compare operation. + */ + ContainerCriteria and(ContainerCriteria nested); + + /** + * Append an Or condition for a nested criterion. Use {@link com.fr.third.springframework.ldap.query.LdapQueryBuilder#query()} + * to start the nested condition. Any base query information on the nested builder instance will not be considered. + * + * @param nested the nested criterion. + * + * @return A ConditionCriteria instance for specifying the compare operation. + */ + ContainerCriteria or(ContainerCriteria nested); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/query/CriteriaContainerType.java b/fine-spring/src/com/fr/third/springframework/ldap/query/CriteriaContainerType.java new file mode 100644 index 000000000..30d4368d8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/query/CriteriaContainerType.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.query; + +import com.fr.third.springframework.ldap.filter.AndFilter; +import com.fr.third.springframework.ldap.filter.BinaryLogicalFilter; +import com.fr.third.springframework.ldap.filter.OrFilter; + +/** + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +enum CriteriaContainerType { + AND { + @Override + public BinaryLogicalFilter constructFilter() { + return new AndFilter(); + } + }, OR { + @Override + public BinaryLogicalFilter constructFilter() { + return new OrFilter(); + } + }; + + public void validateSameType(CriteriaContainerType oldType) { + if (oldType != null && oldType != this) { + throw new IllegalStateException( + String.format("Container type has already been specified as %s, cannot change it to %s", + oldType.toString(), + this.toString())); + } + + } + + public abstract BinaryLogicalFilter constructFilter(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/query/DefaultConditionCriteria.java b/fine-spring/src/com/fr/third/springframework/ldap/query/DefaultConditionCriteria.java new file mode 100644 index 000000000..f59464f88 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/query/DefaultConditionCriteria.java @@ -0,0 +1,89 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.query; + +import com.fr.third.springframework.ldap.filter.EqualsFilter; +import com.fr.third.springframework.ldap.filter.Filter; +import com.fr.third.springframework.ldap.filter.GreaterThanOrEqualsFilter; +import com.fr.third.springframework.ldap.filter.LessThanOrEqualsFilter; +import com.fr.third.springframework.ldap.filter.LikeFilter; +import com.fr.third.springframework.ldap.filter.NotFilter; +import com.fr.third.springframework.ldap.filter.PresentFilter; +import com.fr.third.springframework.ldap.filter.WhitespaceWildcardsFilter; + +/** + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +class DefaultConditionCriteria implements ConditionCriteria { + private final AppendableContainerCriteria parent; + private final String attribute; + private boolean negated = false; + + DefaultConditionCriteria(AppendableContainerCriteria parent, String attribute) { + this.parent = parent; + this.attribute = attribute; + } + + @Override + public ContainerCriteria is(String value) { + return appendToParent(new EqualsFilter(attribute, value)); + } + + @Override + public ContainerCriteria gte(String value) { + return appendToParent(new GreaterThanOrEqualsFilter(attribute, value)); + } + + @Override + public ContainerCriteria lte(String value) { + return appendToParent(new LessThanOrEqualsFilter(attribute, value)); + } + + @Override + public ContainerCriteria like(String value) { + return appendToParent(new LikeFilter(attribute, value)); + } + + @Override + public ContainerCriteria whitespaceWildcardsLike(String value) { + return appendToParent(new WhitespaceWildcardsFilter(attribute, value)); + } + + @Override + public ContainerCriteria isPresent() { + return appendToParent(new PresentFilter(attribute)); + } + + private ContainerCriteria appendToParent(Filter filter) { + return parent.append(negateIfApplicable(filter)); + } + + private Filter negateIfApplicable(Filter myFilter) { + if (negated) { + return new NotFilter(myFilter); + } + + return myFilter; + } + + @Override + public DefaultConditionCriteria not() { + negated = !negated; + return this; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/query/DefaultContainerCriteria.java b/fine-spring/src/com/fr/third/springframework/ldap/query/DefaultContainerCriteria.java new file mode 100644 index 000000000..d8232dc8b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/query/DefaultContainerCriteria.java @@ -0,0 +1,130 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.query; + +import com.fr.third.springframework.ldap.filter.Filter; + +import javax.naming.Name; +import java.util.LinkedHashSet; +import java.util.Set; + +import static com.fr.third.springframework.ldap.query.CriteriaContainerType.AND; +import static com.fr.third.springframework.ldap.query.CriteriaContainerType.OR; + +/** + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +class DefaultContainerCriteria implements AppendableContainerCriteria { + private final Set filters = new LinkedHashSet(); + private final LdapQuery topQuery; + private CriteriaContainerType type; + + DefaultContainerCriteria(LdapQuery topQuery) { + this.topQuery = topQuery; + } + + @Override + public DefaultContainerCriteria append(Filter filter) { + this.filters.add(filter); + return this; + } + + DefaultContainerCriteria withType(CriteriaContainerType newType) { + this.type = newType; + return this; + } + + @Override + public ConditionCriteria and(String attribute) { + AND.validateSameType(type); + type = AND; + + return new DefaultConditionCriteria(this, attribute); + } + + @Override + public ConditionCriteria or(String attribute) { + OR.validateSameType(type); + type = OR; + + return new DefaultConditionCriteria(this, attribute); + } + + @Override + public ContainerCriteria and(ContainerCriteria nested) { + if(type == OR) { + return new DefaultContainerCriteria(topQuery) + .withType(AND) + .append(this.filter()) + .append(nested.filter()); + } else { + type = AND; + this.filters.add(nested.filter()); + return this; + } + } + + @Override + public ContainerCriteria or(ContainerCriteria nested) { + if (type == AND) { + return new DefaultContainerCriteria(topQuery) + .withType(OR) + .append(this.filter()) + .append(nested.filter()); + } else { + type = OR; + this.filters.add(nested.filter()); + return this; + } + } + + @Override + public Filter filter() { + if(filters.size() == 1) { + // No need to wrap in And/OrFilter if there's just one condition. + return filters.iterator().next(); + } + + return type.constructFilter().appendAll(filters); + } + + @Override + public Name base() { + return topQuery.base(); + } + + @Override + public SearchScope searchScope() { + return topQuery.searchScope(); + } + + @Override + public Integer timeLimit() { + return topQuery.timeLimit(); + } + + @Override + public Integer countLimit() { + return topQuery.countLimit(); + } + + @Override + public String[] attributes() { + return topQuery.attributes(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/query/LdapQuery.java b/fine-spring/src/com/fr/third/springframework/ldap/query/LdapQuery.java new file mode 100644 index 000000000..9ed207e87 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/query/LdapQuery.java @@ -0,0 +1,77 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.query; + +import com.fr.third.springframework.ldap.filter.Filter; + +import javax.naming.Name; + +/** + * Holds all information regarding a Ldap query to be performed. Contains information regarding search base, + * search scope, time and count limits, and search filter. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + * @see LdapQueryBuilder + * + * @see com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.AttributesMapper) + * @see com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper) + * @see com.fr.third.springframework.ldap.core.LdapOperations#searchForObject(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper) + * @see com.fr.third.springframework.ldap.core.LdapOperations#searchForContext(LdapQuery) + */ +public interface LdapQuery { + /** + * Get the search base. Default is {@link com.fr.third.springframework.ldap.support.LdapUtils#emptyLdapName()}. + * + * @return the search base. + */ + Name base(); + + /** + * Get the search scope. Default is null, indicating that the LdapTemplate default should be used. + * + * @return the search scope. + */ + SearchScope searchScope(); + + /** + * Get the time limit. Default is null, indicating that the LdapTemplate default should be used. + * + * @return the time limit. + */ + Integer timeLimit(); + + /** + * Get the count limit. Default is null, indicating that the LdapTemplate default should be used. + * @return the count limit. + */ + Integer countLimit(); + + /** + * Get the attributes to return. Default is null, indicating that all attributes should be returned. + * + * @return the attributes to return. + */ + String[] attributes(); + + /** + * Get the filter. + * + * @return the filter. + */ + Filter filter(); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/query/LdapQueryBuilder.java b/fine-spring/src/com/fr/third/springframework/ldap/query/LdapQueryBuilder.java new file mode 100644 index 000000000..943b68550 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/query/LdapQueryBuilder.java @@ -0,0 +1,251 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.query; + +import com.fr.third.springframework.ldap.filter.Filter; +import com.fr.third.springframework.ldap.filter.HardcodedFilter; +import com.fr.third.springframework.ldap.support.LdapEncoder; +import com.fr.third.springframework.ldap.support.LdapUtils; + +import javax.naming.Name; +import java.text.MessageFormat; + +/** + * Builder of LdapQueries. Start with a call to {@link #query()}, proceed with specifying the + * basic search configuration (e.g. search base, time limit, etc.), finally specify the actual query. + * Example: + *
+ * import static com.fr.third.springframework.ldap.query.LdapQueryBuilder.query;
+ * ...
+ *
+ * LdapQuery query = query()
+ *  .base("dc=261consulting, dc=com")
+ *  .searchScope(SearchScope.ONELEVEL)
+ *  .timeLimit(200)
+ *  .countLimit(221)
+ *  .where("objectclass").is("person").and("cn").is("John Doe");
+ * 
+ *

+ * Default configuration is that base path is {@link com.fr.third.springframework.ldap.support.LdapUtils#emptyLdapName()}. + * All other parameters are undefined, meaning that (in the case of base search parameters), the LdapTemplate + * defaults will be used. Filter conditions must always be specified. + *

+ * @author Mattias Hellborg Arthursson + * @since 2.0 + * + * @see javax.naming.directory.SearchControls + * @see com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.AttributesMapper) + * @see com.fr.third.springframework.ldap.core.LdapOperations#search(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper) + * @see com.fr.third.springframework.ldap.core.LdapOperations#searchForObject(LdapQuery, com.fr.third.springframework.ldap.core.ContextMapper) + * @see com.fr.third.springframework.ldap.core.LdapOperations#searchForContext(LdapQuery) + */ +public final class LdapQueryBuilder implements LdapQuery { + private Name base = LdapUtils.emptyLdapName(); + private SearchScope searchScope = null; + private Integer countLimit = null; + private Integer timeLimit = null; + private String[] attributes = null; + + private DefaultContainerCriteria rootContainer = null; + + /** + * Not to be instantiated directly - use static query() method. + */ + private LdapQueryBuilder() { + + } + + /** + * Construct a new LdapQueryBuilder. + * + * @return a new instance. + */ + public static LdapQueryBuilder query() { + return new LdapQueryBuilder(); + } + + /** + * Set the base search path for the query. + * Default is {@link com.fr.third.springframework.ldap.support.LdapUtils#emptyLdapName()}. + * + * @param baseDn the base search path. + * @return this instance. + */ + public LdapQueryBuilder base(String baseDn) { + assertFilterNotStarted(); + this.base = LdapUtils.newLdapName(baseDn); + return this; + } + + /** + * Set the base search path for the query. + * Default is {@link com.fr.third.springframework.ldap.support.LdapUtils#emptyLdapName()}. + * + * @param baseDn the base search path. + * @return this instance. + */ + public LdapQueryBuilder base(Name baseDn) { + assertFilterNotStarted(); + this.base = LdapUtils.newLdapName(baseDn); + return this; + } + + /** + * Set the search scope for the query. + * Default is {@link SearchScope#SUBTREE}. + * + * @param searchScope the search scope. + * @return this instance. + */ + public LdapQueryBuilder searchScope(SearchScope searchScope) { + assertFilterNotStarted(); + this.searchScope = searchScope; + return this; + } + + /** + * Set the count limit for the query. + * Default is 0 (no limit). + * + * @param countLimit the count limit. + * @return this instance. + */ + public LdapQueryBuilder countLimit(int countLimit) { + assertFilterNotStarted(); + this.countLimit = countLimit; + return this; + } + + public LdapQueryBuilder attributes(String... attributesToReturn) { + assertFilterNotStarted(); + this.attributes = attributesToReturn; + return this; + } + + /** + * Set the time limit for the query. + * Default is 0 (no limit). + * + * @param timeLimit the time limit. + * @return this instance. + */ + public LdapQueryBuilder timeLimit(int timeLimit) { + assertFilterNotStarted(); + this.timeLimit = timeLimit; + return this; + } + + /** + * Start specifying the filter conditions in this query. + * + * @param attribute The attribute that the first part of the filter should test against. + * @return A ConditionCriteria instance for specifying the compare operation. + * @throws IllegalStateException if a filter has already been specified. + */ + public ConditionCriteria where(String attribute) { + initRootContainer(); + return new DefaultConditionCriteria(rootContainer, attribute); + } + + private void initRootContainer() { + assertFilterNotStarted(); + rootContainer = new DefaultContainerCriteria(this); + } + + /** + * Specify a hardcoded filter. Please note that using this method, the filter string will not be + * validated or escaped in any way. Never use direct user input and use it concatenating strings + * to use as LDAP filters. Doing so opens up for "LDAP injection", where malicious user + * may inject specifically constructed data to form filters at their convenience. When user input is used + * consider using {@link #where(String)} or {@link #filter(String, Object...)} instead. + * + * @param hardcodedFilter The hardcoded filter string to use in the search. + * @return this instance. + * @throws IllegalStateException if a filter has already been specified. + */ + public LdapQuery filter(String hardcodedFilter) { + initRootContainer(); + rootContainer.append(new HardcodedFilter(hardcodedFilter)); + return this; + } + + public LdapQuery filter(Filter filter) { + initRootContainer(); + rootContainer.append(filter); + return this; + } + + /** + * Specify a hardcoded filter using the specified parameters. The parameters will be properly encoded using + * {@link LdapEncoder#filterEncode(String)} to make sure no malicious data gets through. The filterFormat + * String should be formatted for input to {@link MessageFormat#format(String, Object...)}. + * + * @param filterFormat the filter format string, formatted for input to {@link MessageFormat#format(String, Object...)}. + * @param params the parameters that will be used for building the final filter. All parameters will be properly encoded. + * @return this instance. + * @throws IllegalStateException if a filter has already been specified. + */ + public LdapQuery filter(String filterFormat, Object... params) { + Object[] encodedParams = new String[params.length]; + + for (int i=0; i < params.length; i++) { + encodedParams[i] = LdapEncoder.filterEncode(params[i].toString()); + } + + return filter(MessageFormat.format(filterFormat, encodedParams)); + } + + private void assertFilterNotStarted() { + if(rootContainer != null) { + throw new IllegalStateException("Invalid operation - filter condition specification already started"); + } + } + + @Override + public Name base() { + return base; + } + + @Override + public SearchScope searchScope() { + return searchScope; + } + + @Override + public Integer countLimit() { + return countLimit; + } + + @Override + public Integer timeLimit() { + return timeLimit; + } + + @Override + public String[] attributes() { + return attributes; + } + + + @Override + public Filter filter() { + if(rootContainer == null) { + throw new IllegalStateException("No filter conditions have been specified specified"); + } + return rootContainer.filter(); + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/query/SearchScope.java b/fine-spring/src/com/fr/third/springframework/ldap/query/SearchScope.java new file mode 100644 index 000000000..cff8c6d12 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/query/SearchScope.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.query; + +import javax.naming.directory.SearchControls; + +/** + * Type safe definitions of search scopes. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public enum SearchScope { + /** + * Corresponds to {@link SearchControls#OBJECT_SCOPE} + */ + OBJECT(SearchControls.OBJECT_SCOPE), + /** + * Corresponds to {@link SearchControls#ONELEVEL_SCOPE} + */ + ONELEVEL(SearchControls.ONELEVEL_SCOPE), + /** + * Corresponds to {@link SearchControls#SUBTREE_SCOPE} + */ + SUBTREE(SearchControls.SUBTREE_SCOPE); + + private final int id; + + private SearchScope(int id) { + this.id = id; + } + + public int getId() { + return id; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/support/AttributeValueCallbackHandler.java b/fine-spring/src/com/fr/third/springframework/ldap/support/AttributeValueCallbackHandler.java new file mode 100644 index 000000000..a267efa08 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/support/AttributeValueCallbackHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.support; + +/** + * Callback interface for use when looping through Attribute values. + * + * @author Mattias Hellborg Arthursson + * @since 1.3 + */ +public interface AttributeValueCallbackHandler { + /** + * Implement to take handle one of the Attribute values. + * + * @param attributeName the name of the Attribute. + * @param attributeValue the value. + * @param index the index of the value within the Attribute. + */ + void handleAttributeValue(String attributeName, Object attributeValue, int index); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/support/LdapEncoder.java b/fine-spring/src/com/fr/third/springframework/ldap/support/LdapEncoder.java new file mode 100644 index 000000000..1e4589bd9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/support/LdapEncoder.java @@ -0,0 +1,294 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.support; + +import com.fr.third.springframework.ldap.BadLdapGrammarException; +import com.fr.third.springframework.util.Assert; + +import javax.xml.bind.DatatypeConverter; + +/** + * Helper class to encode and decode ldap names and values. + * + * @author Adam Skogman + * @author Mattias Hellborg Arthursson + * @author Thomas Darimont + */ +public final class LdapEncoder { + + private static final int HEX = 16; + private static String[] NAME_ESCAPE_TABLE = new String[96]; + + private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1]; + + private static final int RFC2849_MAX_BASE64_CHARS_PER_LINE = 76; + + static { + + // Name encoding table ------------------------------------- + + // all below 0x20 (control chars) + for (char c = 0; c < ' '; c++) { + NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c); + } + + NAME_ESCAPE_TABLE['#'] = "\\#"; + NAME_ESCAPE_TABLE[','] = "\\,"; + NAME_ESCAPE_TABLE[';'] = "\\;"; + NAME_ESCAPE_TABLE['='] = "\\="; + NAME_ESCAPE_TABLE['+'] = "\\+"; + NAME_ESCAPE_TABLE['<'] = "\\<"; + NAME_ESCAPE_TABLE['>'] = "\\>"; + NAME_ESCAPE_TABLE['\"'] = "\\\""; + NAME_ESCAPE_TABLE['\\'] = "\\\\"; + + // Filter encoding table ------------------------------------- + + // fill with char itself + for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) { + FILTER_ESCAPE_TABLE[c] = String.valueOf(c); + } + + // escapes (RFC2254) + FILTER_ESCAPE_TABLE['*'] = "\\2a"; + FILTER_ESCAPE_TABLE['('] = "\\28"; + FILTER_ESCAPE_TABLE[')'] = "\\29"; + FILTER_ESCAPE_TABLE['\\'] = "\\5c"; + FILTER_ESCAPE_TABLE[0] = "\\00"; + + } + + /** + * All static methods - not to be instantiated. + */ + private LdapEncoder() { + } + + protected static String toTwoCharHex(char c) { + + String raw = Integer.toHexString(c).toUpperCase(); + + if (raw.length() > 1) { + return raw; + } else { + return "0" + raw; + } + } + + /** + * Escape a value for use in a filter. + * + * @param value + * the value to escape. + * @return a properly escaped representation of the supplied value. + */ + public static String filterEncode(String value) { + + if (value == null) + return null; + + // make buffer roomy + StringBuilder encodedValue = new StringBuilder(value.length() * 2); + + int length = value.length(); + + for (int i = 0; i < length; i++) { + + char c = value.charAt(i); + + if (c < FILTER_ESCAPE_TABLE.length) { + encodedValue.append(FILTER_ESCAPE_TABLE[c]); + } else { + // default: add the char + encodedValue.append(c); + } + } + + return encodedValue.toString(); + } + + /** + * LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI! + * + *
Escapes:
' ' [space] - "\ " [if first or last]
'#' + * [hash] - "\#"
',' [comma] - "\,"
';' [semicolon] - "\;"
'= + * [equals] - "\="
'+' [plus] - "\+"
'<' [less than] - + * "\<"
'>' [greater than] - "\>"
'"' [double quote] - + * "\""
'\' [backslash] - "\\"
+ * + * @param value + * the value to escape. + * @return The escaped value. + */ + public static String nameEncode(String value) { + + if (value == null) + return null; + + // make buffer roomy + StringBuilder encodedValue = new StringBuilder(value.length() * 2); + + int length = value.length(); + int last = length - 1; + + for (int i = 0; i < length; i++) { + + char c = value.charAt(i); + + // space first or last + if (c == ' ' && (i == 0 || i == last)) { + encodedValue.append("\\ "); + continue; + } + + if (c < NAME_ESCAPE_TABLE.length) { + // check in table for escapes + String esc = NAME_ESCAPE_TABLE[c]; + + if (esc != null) { + encodedValue.append(esc); + continue; + } + } + + // default: add the char + encodedValue.append(c); + } + + return encodedValue.toString(); + + } + + /** + * Decodes a value. Converts escaped chars to ordinary chars. + * + * @param value + * Trimmed value, so no leading an trailing blanks, except an + * escaped space last. + * @return The decoded value as a string. + * @throws BadLdapGrammarException + */ + static public String nameDecode(String value) + throws BadLdapGrammarException { + + if (value == null) + return null; + + // make buffer same size + StringBuilder decoded = new StringBuilder(value.length()); + + int i = 0; + while (i < value.length()) { + char currentChar = value.charAt(i); + if (currentChar == '\\') { + if (value.length() <= i + 1) { + // Ending with a single backslash is not allowed + throw new BadLdapGrammarException( + "Unexpected end of value " + "unterminated '\\'"); + } else { + char nextChar = value.charAt(i + 1); + if (nextChar == ',' || nextChar == '=' || nextChar == '+' + || nextChar == '<' || nextChar == '>' + || nextChar == '#' || nextChar == ';' + || nextChar == '\\' || nextChar == '\"' + || nextChar == ' ') { + // Normal backslash escape + decoded.append(nextChar); + i += 2; + } else { + if (value.length() <= i + 2) { + throw new BadLdapGrammarException( + "Unexpected end of value " + + "expected special or hex, found '" + + nextChar + "'"); + } else { + // This should be a hex value + String hexString = "" + nextChar + + value.charAt(i + 2); + decoded.append((char) Integer.parseInt(hexString, + HEX)); + i += 3; + } + } + } + } else { + // This character wasn't escaped - just append it + decoded.append(currentChar); + i++; + } + } + + return decoded.toString(); + + } + + /** + * Converts an array of bytes into a Base64 encoded string according to the rules for converting LDAP Attributes in RFC2849. + * + * @param val + * @return + * A string containing a lexical representation of base64Binary wrapped around 76 characters. + * @throws IllegalArgumentException if val is null. + */ + public static String printBase64Binary(byte[] val) { + + Assert.notNull(val, "val must not be null!"); + + String encoded = DatatypeConverter.printBase64Binary(val); + + int length = encoded.length(); + StringBuilder sb = new StringBuilder(length + length / RFC2849_MAX_BASE64_CHARS_PER_LINE); + + for (int i = 0, len = length; i < len; i++) { + sb.append(encoded.charAt(i)); + + if ((i + 1) % RFC2849_MAX_BASE64_CHARS_PER_LINE == 0) { + sb.append('\n'); + } + } + + return sb.toString(); + } + + /** + * Converts the Base64 encoded string argument into an array of bytes. + * + * @param val + * @return + * An array of bytes represented by the string argument. + * @throws IllegalArgumentException if val is null or does not conform to lexical value space defined in XML Schema Part 2: Datatypes for xsd:base64Binary. + */ + public static byte[] parseBase64Binary(String val) { + + Assert.notNull(val, "val must not be null!"); + + int length = val.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0, len = length; i < len; i++) { + + char c = val.charAt(i); + + if(c == '\n'){ + continue; + } + + sb.append(c); + } + + return DatatypeConverter.parseBase64Binary(sb.toString()); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/support/LdapNameBuilder.java b/fine-spring/src/com/fr/third/springframework/ldap/support/LdapNameBuilder.java new file mode 100644 index 000000000..5033a4a79 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/support/LdapNameBuilder.java @@ -0,0 +1,132 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.support; + +import com.fr.third.springframework.util.Assert; + +import javax.naming.InvalidNameException; +import javax.naming.Name; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +/** + * Helper class for building {@link javax.naming.ldap.LdapName} instances. + * + * Note that the first part of a Distinguished Name is the least significant, which means that when adding components, + * they will be added to the beginning of the resulting string, e.g. + *
+ *     LdapNameBuilder.newInstance("dc=261consulting,dc=com").add("ou=people").build().toString();
+ * 
+ * will result in ou=people,dc=261consulting,dc=com. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public final class LdapNameBuilder { + + private final LdapName ldapName; + + private LdapNameBuilder(LdapName ldapName) { + this.ldapName = ldapName; + } + + /** + * Construct a new instance, starting with a blank LdapName. + * + * @return a new instance. + */ + public static LdapNameBuilder newInstance() { + return new LdapNameBuilder(LdapUtils.emptyLdapName()); + } + + /** + * Construct a new instance, starting with a copy of the supplied LdapName. + * @param name the starting point of the LdapName to be built. + * + * @return a new instance. + */ + public static LdapNameBuilder newInstance(Name name) { + return new LdapNameBuilder(LdapUtils.newLdapName(name)); + } + + /** + * Construct a new instance, starting with an LdapName constructed from the supplied string. + * @param name the starting point of the LdapName to be built. + * + * @return a new instance. + */ + public static LdapNameBuilder newInstance(String name) { + return new LdapNameBuilder(LdapUtils.newLdapName(name)); + } + + /** + * Add a Rdn to the built LdapName. + * @param key the rdn attribute key. + * @param value the rdn value. + * + * @return this builder. + */ + public LdapNameBuilder add(String key, Object value) { + Assert.hasText(key, "key must not be blank"); + Assert.notNull(key, "value must not be null"); + + try { + ldapName.add(new Rdn(key, value)); + return this; + } catch (InvalidNameException e) { + throw new com.fr.third.springframework.ldap.InvalidNameException(e); + } + } + + /** + * Append the specified name to the currently built LdapName. + * + * @param name the name to add. + * @return this builder. + */ + public LdapNameBuilder add(Name name) { + Assert.notNull(name, "name must not be null"); + + try { + ldapName.addAll(ldapName.size(), name); + return this; + } catch (InvalidNameException e) { + throw new com.fr.third.springframework.ldap.InvalidNameException(e); + } + } + + /** + * Append the LdapName represented by the specified string to the currently built LdapName. + * + * @param name the name to add. + * @return this builder. + */ + public LdapNameBuilder add(String name) { + Assert.notNull(name, "name must not be null"); + + return add(LdapUtils.newLdapName(name)); + } + + /** + * Build the LdapName instance. + * + * @return the LdapName instance that has been built. + */ + public LdapName build() { + return LdapUtils.newLdapName(ldapName); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/support/LdapUtils.java b/fine-spring/src/com/fr/third/springframework/ldap/support/LdapUtils.java new file mode 100644 index 000000000..b08873a33 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/support/LdapUtils.java @@ -0,0 +1,776 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.NamingException; +import com.fr.third.springframework.ldap.NoSuchAttributeException; +import com.fr.third.springframework.util.Assert; + +import javax.naming.CompositeName; +import javax.naming.InvalidNameException; +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Generic utility methods for working with LDAP. Mainly for internal use within + * the framework, but also useful for custom code. + * + * @author Ulrik Sandberg + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public final class LdapUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(LdapUtils.class); + private static final int HEX = 16; + + /** + * Not to be instantiated. + */ + private LdapUtils() { + + } + + /** + * Close the given JNDI Context and ignore any thrown exception. This is + * useful for typical finally blocks in JNDI code. + * + * @param context the JNDI Context to close (may be null) + */ + public static void closeContext(DirContext context) { + if (context != null) { + try { + context.close(); + } + catch (NamingException ex) { + LOGGER.debug("Could not close JNDI DirContext", ex); + } + catch (Throwable ex) { + // We don't trust the JNDI provider: It might throw + // RuntimeException or Error. + LOGGER.debug("Unexpected exception on closing JNDI DirContext", ex); + } + } + } + + /** + * Convert the specified checked {@link javax.naming.NamingException + * NamingException} to a Spring LDAP runtime + * {@link com.fr.third.springframework.ldap.NamingException NamingException} + * equivalent. + * + * @param ex the original checked NamingException to convert + * @return the Spring LDAP runtime NamingException wrapping the given + * exception + */ + public static NamingException convertLdapException(javax.naming.NamingException ex) { + Assert.notNull(ex, "NamingException must not be null"); + + if (javax.naming.directory.AttributeInUseException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.AttributeInUseException( + (javax.naming.directory.AttributeInUseException) ex); + } + if (javax.naming.directory.AttributeModificationException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.AttributeModificationException( + (javax.naming.directory.AttributeModificationException) ex); + } + if (javax.naming.CannotProceedException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.CannotProceedException((javax.naming.CannotProceedException) ex); + } + if (javax.naming.CommunicationException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.CommunicationException((javax.naming.CommunicationException) ex); + } + if (javax.naming.ConfigurationException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.ConfigurationException((javax.naming.ConfigurationException) ex); + } + if (javax.naming.ContextNotEmptyException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.ContextNotEmptyException((javax.naming.ContextNotEmptyException) ex); + } + if (javax.naming.InsufficientResourcesException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.InsufficientResourcesException( + (javax.naming.InsufficientResourcesException) ex); + } + if (javax.naming.InterruptedNamingException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.InterruptedNamingException((javax.naming.InterruptedNamingException) ex); + } + if (javax.naming.directory.InvalidAttributeIdentifierException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.InvalidAttributeIdentifierException( + (javax.naming.directory.InvalidAttributeIdentifierException) ex); + } + if (javax.naming.directory.InvalidAttributesException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.InvalidAttributesException( + (javax.naming.directory.InvalidAttributesException) ex); + } + if (javax.naming.directory.InvalidAttributeValueException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.InvalidAttributeValueException( + (javax.naming.directory.InvalidAttributeValueException) ex); + } + if (javax.naming.InvalidNameException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.InvalidNameException((javax.naming.InvalidNameException) ex); + } + if (javax.naming.directory.InvalidSearchControlsException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.InvalidSearchControlsException( + (javax.naming.directory.InvalidSearchControlsException) ex); + } + if (javax.naming.directory.InvalidSearchFilterException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.InvalidSearchFilterException( + (javax.naming.directory.InvalidSearchFilterException) ex); + } + + if (javax.naming.ldap.LdapReferralException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.LdapReferralException((javax.naming.ldap.LdapReferralException) ex); + } + + if (javax.naming.ReferralException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.ReferralException((javax.naming.ReferralException) ex); + } + + // LimitExceededException hierarchy + if (javax.naming.SizeLimitExceededException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.SizeLimitExceededException((javax.naming.SizeLimitExceededException) ex); + } + if (javax.naming.TimeLimitExceededException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.TimeLimitExceededException((javax.naming.TimeLimitExceededException) ex); + } + // this class is the superclass of the two above + if (javax.naming.LimitExceededException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.LimitExceededException((javax.naming.LimitExceededException) ex); + } + + // LinkException hierarchy + if (javax.naming.LinkLoopException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.LinkLoopException((javax.naming.LinkLoopException) ex); + } + if (javax.naming.MalformedLinkException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.MalformedLinkException((javax.naming.MalformedLinkException) ex); + } + // this class is the superclass of the two above + if (javax.naming.LinkException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.LinkException((javax.naming.LinkException) ex); + } + + if (javax.naming.NameAlreadyBoundException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.NameAlreadyBoundException((javax.naming.NameAlreadyBoundException) ex); + } + if (javax.naming.NameNotFoundException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.NameNotFoundException((javax.naming.NameNotFoundException) ex); + } + + // NamingSecurityException hierarchy + if (javax.naming.NoPermissionException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.NoPermissionException((javax.naming.NoPermissionException) ex); + } + if (javax.naming.AuthenticationException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.AuthenticationException((javax.naming.AuthenticationException) ex); + } + if (javax.naming.AuthenticationNotSupportedException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.AuthenticationNotSupportedException( + (javax.naming.AuthenticationNotSupportedException) ex); + } + if (javax.naming.NamingSecurityException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.NamingSecurityException((javax.naming.NamingSecurityException) ex); + } + + if (javax.naming.NoInitialContextException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.NoInitialContextException((javax.naming.NoInitialContextException) ex); + } + if (javax.naming.directory.NoSuchAttributeException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.NoSuchAttributeException( + (javax.naming.directory.NoSuchAttributeException) ex); + } + if (javax.naming.NotContextException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.NotContextException((javax.naming.NotContextException) ex); + } + if (javax.naming.OperationNotSupportedException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.OperationNotSupportedException( + (javax.naming.OperationNotSupportedException) ex); + } + if (javax.naming.PartialResultException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.PartialResultException((javax.naming.PartialResultException) ex); + } + if (javax.naming.directory.SchemaViolationException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.SchemaViolationException( + (javax.naming.directory.SchemaViolationException) ex); + } + if (javax.naming.ServiceUnavailableException.class.isAssignableFrom(ex.getClass())) { + return new com.fr.third.springframework.ldap.ServiceUnavailableException( + (javax.naming.ServiceUnavailableException) ex); + } + + // fallback + return new com.fr.third.springframework.ldap.UncategorizedLdapException(ex); + } + + /** + * Get the actual class of the supplied DirContext instance; LdapContext or + * DirContext. + * + * @param context the DirContext instance to check. + * @return LdapContext.class if context is an LdapContext, DirContext.class + * otherwise. + */ + public static Class getActualTargetClass(DirContext context) { + if (context instanceof LdapContext) { + return LdapContext.class; + } + + return DirContext.class; + } + + /** + * Collect all the values of a the specified attribute from the supplied + * Attributes. + * + * @param attributes The Attributes; not null. + * @param name The name of the Attribute to get values for. + * @param collection the collection to collect the values in. + * @throws NoSuchAttributeException if no attribute with the specified name + * exists. + * @since 1.3 + */ + public static void collectAttributeValues(Attributes attributes, String name, Collection collection) { + collectAttributeValues(attributes, name, collection, Object.class); + } + + /** + * Collect all the values of a the specified attribute from the supplied + * Attributes as the specified class. + * + * @param attributes The Attributes; not null. + * @param name The name of the Attribute to get values for. + * @param collection the collection to collect the values in. + * @param clazz the class of the collected attribute values + * @throws NoSuchAttributeException if no attribute with the specified name + * exists. + * @throws IllegalArgumentException if an attribute value cannot be cast to the specified class. + * @since 2.0 + */ + public static void collectAttributeValues( + Attributes attributes, String name, Collection collection, Class clazz) { + + Assert.notNull(attributes, "Attributes must not be null"); + Assert.hasText(name, "Name must not be empty"); + Assert.notNull(collection, "Collection must not be null"); + + Attribute attribute = attributes.get(name); + if (attribute == null) { + throw new NoSuchAttributeException("No attribute with name '" + name + "'"); + } + + iterateAttributeValues(attribute, new CollectingAttributeValueCallbackHandler(collection, clazz)); + + } + + /** + * Iterate through all the values of the specified Attribute calling back to + * the specified callbackHandler. + * @param attribute the Attribute to work with; not null. + * @param callbackHandler the callbackHandler; not null. + * @since 1.3 + */ + public static void iterateAttributeValues(Attribute attribute, AttributeValueCallbackHandler callbackHandler) { + Assert.notNull(attribute, "Attribute must not be null"); + Assert.notNull(callbackHandler, "callbackHandler must not be null"); + + for (int i = 0; i < attribute.size(); i++) { + try { + callbackHandler.handleAttributeValue(attribute.getID(), attribute.get(i), i); + } + catch (javax.naming.NamingException e) { + throw convertLdapException(e); + } + } + } + + /** + * An {@link AttributeValueCallbackHandler} to collect values in a supplied + * collection. + * + * @author Mattias Hellborg Arthursson + */ + private static final class CollectingAttributeValueCallbackHandler implements AttributeValueCallbackHandler { + private final Collection collection; + private final Class clazz; + + public CollectingAttributeValueCallbackHandler(Collection collection, Class clazz) { + Assert.notNull(collection, "Collection must not be null"); + Assert.notNull(clazz, "Clazz parameter must not be null"); + + this.collection = collection; + this.clazz = clazz; + } + + public void handleAttributeValue(String attributeName, Object attributeValue, int index) { + Assert.isTrue(attributeName == null || clazz.isAssignableFrom(attributeValue.getClass())); + collection.add(clazz.cast(attributeValue)); + } + } + + /** + * Converts a CompositeName to a String in a way that avoids escaping + * problems, such as the dreaded "triple backslash" problem. + * + * @param compositeName The CompositeName to convert + * @return String containing the String representation of name + */ + public static String convertCompositeNameToString( + CompositeName compositeName) { + if (compositeName.size() > 0) { + // A lookup with an empty String seems to produce an empty + // compositeName here; need to take this into account. + return compositeName.get(0); + } + else { + return ""; + } + } + + /** + * Construct a new LdapName instance from the supplied Name instance. + * LdapName instances will be cloned, CompositeName tweaks will be managed using + * {@link #convertCompositeNameToString(javax.naming.CompositeName)}; for all other Name + * implementations, new LdapName instances are constructed using {@link LdapName#addAll(int, javax.naming.Name)}. + * + * @param name the Name instance to convert to LdapName, not null. + * @return a new LdapName representing the same Distinguished Name as the supplied instance. + * @throws com.fr.third.springframework.ldap.InvalidNameException to wrap any InvalidNameExceptions thrown by LdapName. + * @since 2.0 + */ + public static LdapName newLdapName(Name name) { + Assert.notNull(name, "name must not be null"); + if(name instanceof LdapName) { + return (LdapName) name.clone(); + } else if (name instanceof CompositeName) { + CompositeName compositeName = (CompositeName) name; + + try { + return new LdapName(convertCompositeNameToString(compositeName)); + } catch (InvalidNameException e) { + throw convertLdapException(e); + } + } else { + LdapName result = emptyLdapName(); + try { + result.addAll(0, name); + } catch (InvalidNameException e) { + throw convertLdapException(e); + } + + return result; + } + } + + /** + * Construct a new LdapName instance from the supplied distinguished name string. + * + * @param distinguishedName the string to parse for constructing an LdapName instance. + * @return a new LdapName instance. + * @throws com.fr.third.springframework.ldap.InvalidNameException to wrap any InvalidNameExceptions thrown by LdapName. + * @since 2.0 + */ + public static LdapName newLdapName(String distinguishedName) { + Assert.notNull(distinguishedName, "distinguishedName must not be null"); + + try { + return new LdapName(distinguishedName); + } catch (InvalidNameException e) { + throw convertLdapException(e); + } + } + + + private static LdapName returnOrConstructLdapNameFromName(Name name) { + if (name instanceof LdapName) { + return (LdapName) name; + } else { + return newLdapName(name); + } + } + + /** + * Remove the supplied path from the beginning the specified + * Name if the name instance starts with + * path. Useful for stripping base path suffix from a + * Name. The original Name will not be affected. + * + * @param dn the dn to strip from. + * @param pathToRemove the path to remove from the beginning the dn instance. + * @return an LdapName instance that is a copy of the original name with the + * specified path stripped from its beginning. + * @since 2.0 + */ + public static LdapName removeFirst(Name dn, Name pathToRemove) { + Assert.notNull(dn, "dn must not be null"); + Assert.notNull(pathToRemove, "pathToRemove must not be null"); + + LdapName result = newLdapName(dn); + LdapName path = returnOrConstructLdapNameFromName(pathToRemove); + + if(path.size() == 0 || !dn.startsWith(path)) { + return result; + } + + for(int i = 0; i < path.size(); i++) { + try { + result.remove(0); + } catch (InvalidNameException e) { + throw convertLdapException(e); + } + } + + return result; + } + + /** + * Prepend the supplied path in the beginning the specified + * Name if the name instance starts with + * path. The original Name will not be affected. + * + * @param dn the dn to strip from. + * @param pathToPrepend the path to prepend in the beginning of the dn. + * @return an LdapName instance that is a copy of the original name with the + * specified path inserted at its beginning. + * @since 2.0 + */ + public static LdapName prepend(Name dn, Name pathToPrepend) { + Assert.notNull(dn, "dn must not be null"); + Assert.notNull(pathToPrepend, "pathToRemove must not be null"); + + LdapName result = newLdapName(dn); + try { + result.addAll(0, pathToPrepend); + } catch (InvalidNameException e) { + throw convertLdapException(e); + } + + return result; + } + + /** + * Construct a new, empty LdapName instance. + * @return a new LdapName instance representing the empty path (""). + * @since 2.0 + */ + public static LdapName emptyLdapName() { + return newLdapName(""); + } + + /** + * Find the Rdn with the requested key in the supplied Name. + * + * @param name the Name in which to search for the key. + * @param key the attribute key to search for. + * @return the rdn corresponding to the first occurrence of the requested key. + * @throws NoSuchElementException if no corresponding entry is found. + * @since 2.0 + */ + public static Rdn getRdn(Name name, String key) { + Assert.notNull(name, "name must not be null"); + Assert.hasText(key, "key must not be blank"); + + LdapName ldapName = returnOrConstructLdapNameFromName(name); + + List rdns = ldapName.getRdns(); + for (Rdn rdn : rdns) { + NamingEnumeration ids = rdn.toAttributes().getIDs(); + while (ids.hasMoreElements()) { + String id = ids.nextElement(); + if(key.equalsIgnoreCase(id)) { + return rdn; + } + } + } + + throw new NoSuchElementException("No Rdn with the requested key: '" + key + "'"); + } + + /** + * Get the value of the Rdn with the requested key in the supplied Name. + * + * @param name the Name in which to search for the key. + * @param key the attribute key to search for. + * @return the value of the rdn corresponding to the first occurrence of the requested key. + * @throws NoSuchElementException if no corresponding entry is found. + * @since 2.0 + */ + public static Object getValue(Name name, String key) { + NamingEnumeration allAttributes = getRdn(name, key).toAttributes().getAll(); + while (allAttributes.hasMoreElements()) { + Attribute oneAttribute = allAttributes.nextElement(); + if(key.equalsIgnoreCase(oneAttribute.getID())) { + try { + return oneAttribute.get(); + } catch (javax.naming.NamingException e) { + throw convertLdapException(e); + } + } + } + + // This really shouldn't happen + throw new NoSuchElementException("No Rdn with the requested key: '" + key + "'"); + } + + /** + * Get the value of the Rdn at the requested index in the supplied Name. + * + * @param name the Name to work on. + * @param index The 0-based index of the rdn value to retrieve. Must be in the range [0,size()). + * @return the value of the rdn at the requested index. + * @throws IndexOutOfBoundsException if index is outside the specified range. + * @since 2.0 + */ + public static Object getValue(Name name, int index) { + Assert.notNull(name, "name must not be null"); + + LdapName ldapName = returnOrConstructLdapNameFromName(name); + Rdn rdn = ldapName.getRdn(index); + if(rdn.size() > 1) { + LOGGER.warn("Rdn at position " + index + " of dn '" + name + + "' is multi-value - returned value is not to be trusted. " + + "Consider using name-based getValue method instead"); + } + return rdn.getValue(); + } + + /** + * Get the value of the Rdn at the requested index in the supplied Name as a String. + * + * @param name the Name to work on. + * @param index The 0-based index of the rdn value to retrieve. Must be in the range [0,size()). + * @return the value of the rdn at the requested index as a String. + * @throws IndexOutOfBoundsException if index is outside the specified range. + * @throws ClassCastException if the value of the requested component is not a String. + * @since 2.0 + */ + public static String getStringValue(Name name, int index) { + return (String) getValue(name, index); + } + + /** + * Get the value of the Rdn with the requested key in the supplied Name as a String. + * + * @param name the Name in which to search for the key. + * @param key the attribute key to search for. + * @return the String value of the rdn corresponding to the first occurrence of the requested key. + * @throws NoSuchElementException if no corresponding entry is found. + * @throws ClassCastException if the value of the requested component is not a String. + * @since 2.0 + */ + public static String getStringValue(Name name, String key) { + return (String) getValue(name, key); + } + + /** + * Converts a binary SID to its String representation, according to the + * algorithm described here. Thanks to Eyal + * Lupu for algorithmic inspiration. + * + *
+	 * If you have a SID like S-a-b-c-d-e-f-g-...
+	 * 
+	 * Then the bytes are
+	 * a	(revision)
+	 * N	(number of dashes minus two)
+	 * bbbbbb	(six bytes of "b" treated as a 48-bit number in big-endian format)
+	 * cccc	(four bytes of "c" treated as a 32-bit number in little-endian format)
+	 * dddd	(four bytes of "d" treated as a 32-bit number in little-endian format)
+	 * eeee	(four bytes of "e" treated as a 32-bit number in little-endian format)
+	 * ffff	(four bytes of "f" treated as a 32-bit number in little-endian format)
+	 * etc.	
+	 * 
+	 * So for example, if your SID is S-1-5-21-2127521184-1604012920-1887927527-72713, then your raw hex SID is
+	 * 
+	 * 010500000000000515000000A065CF7E784B9B5FE77C8770091C0100
+	 * 
+	 * This breaks down as follows:
+	 * 01	S-1
+	 * 05	(seven dashes, seven minus two = 5)
+	 * 000000000005	(5 = 0x000000000005, big-endian)
+	 * 15000000	(21 = 0x00000015, little-endian)
+	 * A065CF7E	(2127521184 = 0x7ECF65A0, little-endian)
+	 * 784B9B5F	(1604012920 = 0x5F9B4B78, little-endian)
+	 * E77C8770	(1887927527 = 0X70877CE7, little-endian)
+	 * 091C0100	(72713 = 0x00011c09, little-endian)
+	 * 
+	 * S-1-	version number (SID_REVISION)
+	 * -5-	SECURITY_NT_AUTHORITY
+	 * -21-	SECURITY_NT_NON_UNIQUE
+	 * -...-...-...-	these identify the machine that issued the SID
+	 * 72713	unique user id on the machine
+	 * 
+ * + * @param sid binary SID in byte array format + * @return String version of the given sid + * @since 1.3.1 + */ + public static String convertBinarySidToString(byte[] sid) { + // Add the 'S' prefix + StringBuffer sidAsString = new StringBuffer("S-"); + + // bytes[0] : in the array is the version (must be 1 but might + // change in the future) + sidAsString.append(sid[0]).append('-'); + + // bytes[2..7] : the Authority + StringBuffer sb = new StringBuffer(); + for (int t = 2; t <= 7; t++) { + String hexString = Integer.toHexString(sid[t] & 0xFF); + sb.append(hexString); + } + sidAsString.append(Long.parseLong(sb.toString(), HEX)); + + // bytes[1] : the sub authorities count + int count = sid[1]; + + // bytes[8..end] : the sub authorities (these are Integers - notice + // the endian) + for (int i = 0; i < count; i++) { + int currSubAuthOffset = i * 4; + sb.setLength(0); + sb.append(toHexString((byte) (sid[11 + currSubAuthOffset] & 0xFF))); + sb.append(toHexString((byte) (sid[10 + currSubAuthOffset] & 0xFF))); + sb.append(toHexString((byte) (sid[9 + currSubAuthOffset] & 0xFF))); + sb.append(toHexString((byte) (sid[8 + currSubAuthOffset] & 0xFF))); + + sidAsString.append('-').append(Long.parseLong(sb.toString(), HEX)); + } + + // That's it - we have the SID + return sidAsString.toString(); + } + + /** + * Converts a String SID to its binary representation, according to the + * algorithm described here. + * + * @param string SID in readable format + * @return Binary version of the given sid + * @see LdapUtils#convertBinarySidToString(byte[]) + * @since 1.3.1 + */ + public static byte[] convertStringSidToBinary(String string) { + String[] parts = string.split("-"); + byte sidRevision = (byte) Integer.parseInt(parts[1]); + int subAuthCount = parts.length - 3; + + byte[] sid = new byte[] {sidRevision, (byte) subAuthCount}; + sid = addAll(sid, numberToBytes(parts[2], 6, true)); + for (int i = 0; i < subAuthCount; i++) { + sid = addAll(sid, numberToBytes(parts[3 + i], 4, false)); + } + return sid; + } + + private static byte[] addAll(byte[] array1, byte[] array2) { + byte[] joinedArray = new byte[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + /** + * Converts the given number to a binary representation of the specified + * length and "endian-ness". + * + * @param number String with number to convert + * @param length How long the resulting binary array should be + * @param bigEndian true if big endian (5=0005), or + * false if little endian (5=5000) + * @return byte array containing the binary result in the given order + */ + static byte[] numberToBytes(String number, int length, boolean bigEndian) { + BigInteger bi = new BigInteger(number); + byte[] bytes = bi.toByteArray(); + int remaining = length - bytes.length; + if (remaining < 0) { + bytes = Arrays.copyOfRange(bytes, -remaining, bytes.length); + } else { + byte[] fill = new byte[remaining]; + bytes = addAll(fill, bytes); + } + if (!bigEndian) { + reverse(bytes); + } + return bytes; + } + + private static void reverse(byte[] array) { + int i = 0; + int j = array.length - 1; + byte tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + * Converts a byte into its hexadecimal representation, padding with a + * leading zero to get an even number of characters. + * + * @param b value to convert + * @return hex string, possibly padded with a zero + */ + static String toHexString(final byte b) { + String hexString = Integer.toHexString(b & 0xFF); + if (hexString.length() % 2 != 0) { + // Pad with 0 + hexString = "0" + hexString; + } + return hexString; + } + + /** + * Converts a byte array into its hexadecimal representation, padding each + * with a leading zero to get an even number of characters. + * + * @param b values to convert + * @return hex string, possibly with elements padded with a zero + */ + static String toHexString(final byte[] b) { + StringBuffer sb = new StringBuffer("{"); + for (int i = 0; i < b.length; i++) { + sb.append(toHexString(b[i])); + if (i < b.length - 1) { + sb.append(","); + } + } + sb.append("}"); + return sb.toString(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/support/ListComparator.java b/fine-spring/src/com/fr/third/springframework/ldap/support/ListComparator.java new file mode 100644 index 000000000..974eb4677 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/support/ListComparator.java @@ -0,0 +1,67 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.support; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; + +/** + * Comparator for comparing lists of Comparable objects. + * + * @author Mattias Hellborg Arthursson + */ +public class ListComparator implements Comparator, Serializable { + private static final long serialVersionUID = -3068381879731157178L; + + /** + * Compare two lists of Comparable objects. + * + * @param o1 the first object to be compared. + * @param o2 the second object to be compared. + * @throws ClassCastException if any of the lists contains an object that + * is not Comparable. + */ + public int compare(Object o1, Object o2) { + List list1 = (List) o1; + List list2 = (List) o2; + + for (int i = 0; i < list1.size(); i++) { + if (list2.size() > i) { + Comparable component1 = (Comparable) list1.get(i); + Comparable component2 = (Comparable) list2.get(i); + int componentsCompared = component1.compareTo(component2); + if (componentsCompared != 0) { + return componentsCompared; + } + } + else { + // First instance has more components, so that instance is + // greater. + return 1; + } + } + + // All components so far are equal - if the other instance has + // more components it is greater otherwise they are equal. + if (list2.size() > list1.size()) { + return -1; + } + else { + return 0; + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/support/package.html b/fine-spring/src/com/fr/third/springframework/ldap/support/package.html new file mode 100644 index 000000000..5eabecc8e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/support/package.html @@ -0,0 +1,7 @@ + + + +Support classes for Spring-LDAP. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/BindOperationExecutor.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/BindOperationExecutor.java new file mode 100644 index 000000000..f6c57074b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/BindOperationExecutor.java @@ -0,0 +1,127 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +import javax.naming.Name; +import javax.naming.directory.Attributes; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage a bind + * operation. Performs a bind in {@link #performOperation()}, a corresponding + * unbind in {@link #rollback()}, and nothing in {@link #commit()}. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class BindOperationExecutor implements + CompensatingTransactionOperationExecutor { + private static Logger log = LoggerFactory.getLogger(BindOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name dn; + + private Object originalObject; + + private Attributes originalAttributes; + + /** + * Constructor. + * + * @param ldapOperations + * {@link LdapOperations} to use for performing the rollback + * operation. + * @param dn + * DN of the entry to be unbound. + * @param originalObject + * original value sent to the 'object' parameter of the bind + * operation. + * @param originalAttributes + * original value sent to the 'attributes' parameter of the bind + * operation. + */ + public BindOperationExecutor(LdapOperations ldapOperations, Name dn, + Object originalObject, Attributes originalAttributes) { + this.ldapOperations = ldapOperations; + this.dn = dn; + this.originalObject = originalObject; + this.originalAttributes = originalAttributes; + } + + /* + * (non-Javadoc) + * + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + try { + ldapOperations.unbind(dn); + } catch (Exception e) { + log.warn("Failed to rollback, dn:" + dn.toString(), e); + } + } + + /* + * (non-Javadoc) + * + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Nothing to do in commit for bind operation"); + } + + /* + * (non-Javadoc) + * + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing bind operation"); + ldapOperations.bind(dn, originalObject, originalAttributes); + } + + /** + * Get the DN. Package private for testing purposes. + * + * @return the target DN. + */ + Name getDn() { + return dn; + } + + /** + * Get the LdapOperations. Package private for testing purposes. + * + * @return the LdapOperations. + */ + LdapOperations getLdapOperations() { + return ldapOperations; + } + + Attributes getOriginalAttributes() { + return originalAttributes; + } + + Object getOriginalObject() { + return originalObject; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/BindOperationRecorder.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/BindOperationRecorder.java new file mode 100644 index 000000000..9fbc0810a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/BindOperationRecorder.java @@ -0,0 +1,79 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import javax.naming.Name; +import javax.naming.directory.Attributes; + +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * A {@link CompensatingTransactionOperationRecorder} keeping track of bind + * operations. Creates {@link BindOperationExecutor} objects in + * {@link #recordOperation(Object[])}. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class BindOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private LdapOperations ldapOperations; + + /** + * Constructor. + * + * @param ldapOperations + * {@link LdapOperations} to use for supplying to the + * corresponding rollback operation. + */ + public BindOperationRecorder(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + if (args == null || args.length != 3) { + throw new IllegalArgumentException( + "Invalid arguments for bind operation"); + } + Name dn = LdapTransactionUtils.getFirstArgumentAsName(args); + Object object = args[1]; + Attributes attributes = null; + if (args[2] != null && !(args[2] instanceof Attributes)) { + throw new IllegalArgumentException( + "Invalid third argument to bind operation"); + } else if (args[2] != null) { + attributes = (Attributes) args[2]; + } + + return new BindOperationExecutor(ldapOperations, dn, object, attributes); + } + + /** + * Get the LdapOperations. For testing purposes.s + * + * @return the LdapOperations. + */ + LdapOperations getLdapOperations() { + return ldapOperations; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactory.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactory.java new file mode 100644 index 000000000..f6368c134 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.ldap.core.LdapTemplate; +import com.fr.third.springframework.ldap.core.support.SingleContextSource; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationFactory; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; +import com.fr.third.springframework.util.ObjectUtils; + +import javax.naming.directory.DirContext; + +/** + * {@link CompensatingTransactionOperationRecorder} implementation for LDAP + * operations. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class LdapCompensatingTransactionOperationFactory implements CompensatingTransactionOperationFactory { + private static Logger log = LoggerFactory.getLogger(LdapCompensatingTransactionOperationFactory.class); + + private TempEntryRenamingStrategy renamingStrategy; + + /** + * Constructor. + * + * @param renamingStrategy the {@link TempEntryRenamingStrategy} to supply + * to relevant operations. + */ + public LdapCompensatingTransactionOperationFactory(TempEntryRenamingStrategy renamingStrategy) { + this.renamingStrategy = renamingStrategy; + } + + /* + * @see com.fr.third.springframework.transaction.compensating. + * CompensatingTransactionOperationFactory + * #createRecordingOperation(java.lang.Object, java.lang.String) + */ + public CompensatingTransactionOperationRecorder createRecordingOperation(Object resource, String operation) { + if (ObjectUtils.nullSafeEquals(operation, LdapTransactionUtils.BIND_METHOD_NAME)) { + log.debug("Bind operation recorded"); + return new BindOperationRecorder(createLdapOperationsInstance((DirContext) resource)); + } + else if (ObjectUtils.nullSafeEquals(operation, LdapTransactionUtils.REBIND_METHOD_NAME)) { + log.debug("Rebind operation recorded"); + return new RebindOperationRecorder(createLdapOperationsInstance((DirContext) resource), renamingStrategy); + } + else if (ObjectUtils.nullSafeEquals(operation, LdapTransactionUtils.RENAME_METHOD_NAME)) { + log.debug("Rename operation recorded"); + return new RenameOperationRecorder(createLdapOperationsInstance((DirContext) resource)); + } + else if (ObjectUtils.nullSafeEquals(operation, LdapTransactionUtils.MODIFY_ATTRIBUTES_METHOD_NAME)) { + return new ModifyAttributesOperationRecorder(createLdapOperationsInstance((DirContext) resource)); + } + else if (ObjectUtils.nullSafeEquals(operation, LdapTransactionUtils.UNBIND_METHOD_NAME)) { + return new UnbindOperationRecorder(createLdapOperationsInstance((DirContext) resource), renamingStrategy); + } + + log.warn("No suitable CompensatingTransactionOperationRecorder found for method " + operation + + ". Operation will not be transacted."); + return new NullOperationRecorder(); + } + + LdapOperations createLdapOperationsInstance(DirContext ctx) { + return new LdapTemplate(new SingleContextSource(ctx)); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/LdapTransactionUtils.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/LdapTransactionUtils.java new file mode 100644 index 000000000..faa5b6849 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/LdapTransactionUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.ObjectUtils; + +import javax.naming.Name; + +/** + * Utility methods for working with LDAP transactions. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public final class LdapTransactionUtils { + + public static final String REBIND_METHOD_NAME = "rebind"; + + public static final String BIND_METHOD_NAME = "bind"; + + public static final String RENAME_METHOD_NAME = "rename"; + + public static final String UNBIND_METHOD_NAME = "unbind"; + + public static final String MODIFY_ATTRIBUTES_METHOD_NAME = "modifyAttributes"; + + /** + * Not to be instantiated. + */ + private LdapTransactionUtils() { + + } + + /** + * Get the first parameter in the argument list as a Name. + * + * @param args + * arguments supplied to a ldap operation. + * @return a Name representation of the first argument, or the Name itself + * if it is a name. + */ + public static Name getFirstArgumentAsName(Object[] args) { + Assert.notEmpty(args); + + Object firstArg = args[0]; + return getArgumentAsName(firstArg); + } + + /** + * Get the argument as a Name. + * + * @param arg + * an argument supplied to an Ldap operation. + * @return a Name representation of the argument, or the Name itself if it + * is a Name. + */ + public static Name getArgumentAsName(Object arg) { + if (arg instanceof String) { + return LdapUtils.newLdapName((String) arg); + } else if (arg instanceof Name) { + return (Name) arg; + } else { + throw new IllegalArgumentException( + "First argument needs to be a Name or a String representation thereof"); + } + } + + /** + * Check whether the supplied method is a method for which transactions is + * supported (and which should be recorded for possible rollback later). + * + * @param methodName + * name of the method to check. + * @return true if this is a supported transaction operation, + * false otherwise. + */ + public static boolean isSupportedWriteTransactionOperation(String methodName) { + return (ObjectUtils.nullSafeEquals(methodName, BIND_METHOD_NAME) + || ObjectUtils.nullSafeEquals(methodName, REBIND_METHOD_NAME) + || ObjectUtils.nullSafeEquals(methodName, RENAME_METHOD_NAME) + || ObjectUtils.nullSafeEquals(methodName, MODIFY_ATTRIBUTES_METHOD_NAME) + || ObjectUtils.nullSafeEquals(methodName, UNBIND_METHOD_NAME)); + + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutor.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutor.java new file mode 100644 index 000000000..a62411938 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutor.java @@ -0,0 +1,115 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +import javax.naming.Name; +import javax.naming.directory.ModificationItem; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage a + * modifyAttributes operation. Performs a + * modifyAttributes in {@link #performOperation()}, a negating + * modifyAttributes in {@link #rollback()}, and nothing in {@link #commit()}. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class ModifyAttributesOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Logger log = LoggerFactory.getLogger(ModifyAttributesOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name dn; + + private ModificationItem[] compensatingModifications; + + private ModificationItem[] actualModifications; + + /** + * Constructor. + * + * @param ldapOperations + * The {@link LdapOperations} to use to perform the rollback + * operation. + * @param dn + * the DN of the target entry. + * @param actualModifications + * the actual modificationItems that were sent to the + * modifyAttributes operation. + * @param compensatingModifications + * the ModificationItems to undo the recorded operation. + */ + public ModifyAttributesOperationExecutor(LdapOperations ldapOperations, + Name dn, ModificationItem[] actualModifications, + ModificationItem[] compensatingModifications) { + this.ldapOperations = ldapOperations; + this.dn = dn; + this.actualModifications = actualModifications.clone(); + this.compensatingModifications = compensatingModifications.clone(); + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + try { + log.debug("Rolling back modifyAttributes operation"); + ldapOperations.modifyAttributes(dn, compensatingModifications); + } catch (Exception e) { + log + .warn("Failed to rollback ModifyAttributes operation, dn: " + + dn); + } + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Nothing to do in commit for modifyAttributes"); + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing modifyAttributes operation"); + ldapOperations.modifyAttributes(dn, actualModifications); + } + + Name getDn() { + return dn; + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } + + ModificationItem[] getActualModifications() { + return actualModifications; + } + + ModificationItem[] getCompensatingModifications() { + return compensatingModifications; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorder.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorder.java new file mode 100644 index 000000000..e4550c631 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorder.java @@ -0,0 +1,168 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.transaction.compensating; + +import com.fr.third.springframework.ldap.core.AttributesMapper; +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.ldap.core.support.DefaultIncrementalAttributesMapper; +import com.fr.third.springframework.ldap.core.IncrementalAttributesMapper; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; +import com.fr.third.springframework.util.Assert; + +import javax.naming.Name; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import java.util.HashSet; +import java.util.Set; + +/** + * A {@link CompensatingTransactionOperationRecorder} keeping track of + * modifyAttributes operations, creating corresponding + * {@link ModifyAttributesOperationExecutor} instances for rollback. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class ModifyAttributesOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private LdapOperations ldapOperations; + + public ModifyAttributesOperationRecorder(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + Assert.notNull(args); + Name dn = LdapTransactionUtils.getFirstArgumentAsName(args); + if (args.length != 2 || !(args[1] instanceof ModificationItem[])) { + throw new IllegalArgumentException( + "Unexpected arguments to ModifyAttributes operation"); + } + + ModificationItem[] incomingModifications = (ModificationItem[]) args[1]; + + Set set = new HashSet(); + for (ModificationItem incomingModification : incomingModifications) { + set.add(incomingModification.getAttribute().getID()); + } + + // Get the current values of all referred Attributes. + String[] attributeNameArray = set.toArray(new String[set.size()]); + + // LDAP-234: We need to explicitly an IncrementalAttributesMapper in + // case we're working against AD and there are too many attribute values to be returned + // by one query. + IncrementalAttributesMapper attributesMapper = getAttributesMapper(attributeNameArray); + while (attributesMapper.hasMore()) { + ldapOperations.lookup(dn, attributesMapper.getAttributesForLookup(), attributesMapper); + } + + Attributes currentAttributes = attributesMapper.getCollectedAttributes(); + + // Get a compensating ModificationItem for each of the incoming + // modification. + ModificationItem[] rollbackItems = new ModificationItem[incomingModifications.length]; + for (int i = 0; i < incomingModifications.length; i++) { + rollbackItems[i] = getCompensatingModificationItem( + currentAttributes, incomingModifications[i]); + } + + return new ModifyAttributesOperationExecutor(ldapOperations, dn, + incomingModifications, rollbackItems); + } + + /** + * Get an {@link AttributesMapper} that just returns the supplied + * Attributes. + * + * @return the {@link AttributesMapper} to use for getting the current + * Attributes of the target DN. + */ + IncrementalAttributesMapper getAttributesMapper(String[] attributeNames) { + return new DefaultIncrementalAttributesMapper(attributeNames); + } + + /** + * Get a ModificationItem to use for rollback of the supplied modification. + * + * @param originalAttributes + * All Attributes of the target DN that are affected of any of + * the ModificationItems. + * @param modificationItem + * the ModificationItem to create a rollback item for. + * @return A ModificationItem to use for rollback of the supplied + * ModificationItem. + */ + protected ModificationItem getCompensatingModificationItem( + Attributes originalAttributes, ModificationItem modificationItem) { + Attribute modificationAttribute = modificationItem.getAttribute(); + Attribute originalAttribute = originalAttributes + .get(modificationAttribute.getID()); + + if (modificationItem.getModificationOp() == DirContext.REMOVE_ATTRIBUTE) { + if (modificationAttribute.size() == 0) { + // If the modification attribute size it means that the + // Attribute should be removed entirely - we should store a + // ModificationItem to restore all present values for rollback. + return new ModificationItem(DirContext.ADD_ATTRIBUTE, + (Attribute) originalAttribute.clone()); + } else { + // The rollback modification will be to re-add the removed + // attribute values. + return new ModificationItem(DirContext.ADD_ATTRIBUTE, + (Attribute) modificationAttribute.clone()); + } + } else if (modificationItem.getModificationOp() == DirContext.REPLACE_ATTRIBUTE) { + if (originalAttribute != null) { + return new ModificationItem(DirContext.REPLACE_ATTRIBUTE, + (Attribute) originalAttribute.clone()); + } else { + // The attribute doesn't previously exist - the rollback + // operation will be to remove the attribute. + return new ModificationItem(DirContext.REMOVE_ATTRIBUTE, + new BasicAttribute(modificationAttribute.getID())); + } + } else { + // An ADD_ATTRIBUTE operation + if (originalAttribute == null) { + // The attribute doesn't previously exist - the rollback + // operation will be to remove the attribute. + return new ModificationItem(DirContext.REMOVE_ATTRIBUTE, + new BasicAttribute(modificationAttribute.getID())); + } else { + // The attribute does exist before - we should store the + // previous value and it should be used for replacing in + // rollback. + return new ModificationItem(DirContext.REPLACE_ATTRIBUTE, + (Attribute) originalAttribute.clone()); + } + } + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/NullOperationExecutor.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/NullOperationExecutor.java new file mode 100644 index 000000000..6645e3059 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/NullOperationExecutor.java @@ -0,0 +1,47 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +/** + * A {@link CompensatingTransactionOperationExecutor} that performs nothing. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class NullOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Logger log = LoggerFactory.getLogger(NullOperationExecutor.class); + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + log.info("Rolling back null operation"); + } + + public void commit() { + log.info("Committing back null operation"); + } + + public void performOperation() { + log.info("Performing null operation"); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/NullOperationRecorder.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/NullOperationRecorder.java new file mode 100644 index 000000000..bdd0fca50 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/NullOperationRecorder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * A {@link CompensatingTransactionOperationRecorder} performing nothing, + * returning a {@link NullOperationExecutor} regardless of the input. Instances + * of this class will be created if the + * {@link CompensatingTransactionOperationManager} cannot determine any + * appropriate {@link CompensatingTransactionOperationRecorder} for the current + * operation. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class NullOperationRecorder implements + CompensatingTransactionOperationRecorder { + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + return new NullOperationExecutor(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RebindOperationExecutor.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RebindOperationExecutor.java new file mode 100644 index 000000000..6ecdda6aa --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RebindOperationExecutor.java @@ -0,0 +1,135 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +import javax.naming.Name; +import javax.naming.directory.Attributes; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage a rebind + * operation. The methods in this class do not behave as expected, since it + * might be impossible to retrieve all the original attributes from the entry. + * Instead this class performs a rename in {@link #performOperation()}, + * a negating rename in {@link #rollback()}, and the {@link #commit()} + * operation unbinds the original entry from its temporary location and binds a + * new entry to the original location using the attributes supplied to the + * original rebind opertaion. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class RebindOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Logger log = LoggerFactory.getLogger(RebindOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name originalDn; + + private Name temporaryDn; + + private Object originalObject; + + private Attributes originalAttributes; + + /** + * Constructor. + * + * @param ldapOperations + * the {@link LdapOperations} to use to perform the rollback. + * @param originalDn + * The original DN of the entry to bind. + * @param temporaryDn + * The temporary DN of the entry. + * @param originalObject + * Original 'object' parameter sent to the rebind operation. + * @param originalAttributes + * Original 'attributes' parameter sent to the rebind operation + */ + public RebindOperationExecutor(LdapOperations ldapOperations, + Name originalDn, Name temporaryDn, Object originalObject, + Attributes originalAttributes) { + this.ldapOperations = ldapOperations; + this.originalDn = originalDn; + this.temporaryDn = temporaryDn; + this.originalObject = originalObject; + this.originalAttributes = originalAttributes; + } + + /** + * Get the LdapOperations. Package private for testing purposes. + * + * @return the LdapOperations. + */ + LdapOperations getLdapOperations() { + return ldapOperations; + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + log.debug("Rolling back rebind operation"); + try { + ldapOperations.unbind(originalDn); + ldapOperations.rename(temporaryDn, originalDn); + } catch (Exception e) { + log.warn("Failed to rollback operation, dn: " + originalDn + + "; temporary DN: " + temporaryDn, e); + } + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Committing rebind operation"); + ldapOperations.unbind(temporaryDn); + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing rebind operation - " + + "renaming original entry and " + + "binding new contents to entry."); + ldapOperations.rename(originalDn, temporaryDn); + ldapOperations.bind(originalDn, originalObject, originalAttributes); + } + + Attributes getOriginalAttributes() { + return originalAttributes; + } + + Name getOriginalDn() { + return originalDn; + } + + Object getOriginalObject() { + return originalObject; + } + + Name getTemporaryDn() { + return temporaryDn; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RebindOperationRecorder.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RebindOperationRecorder.java new file mode 100644 index 000000000..88223b485 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RebindOperationRecorder.java @@ -0,0 +1,93 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import javax.naming.Name; +import javax.naming.directory.Attributes; + +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * A {@link CompensatingTransactionOperationRecorder} keeping track of a rebind + * operation. Creates {@link RebindOperationExecutor} objects in + * {@link #recordOperation(Object[])}. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class RebindOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private LdapOperations ldapOperations; + + private TempEntryRenamingStrategy renamingStrategy; + + /** + * Constructor. + * + * @param ldapOperations + * {@link LdapOperations} to use for getting the rollback + * information and supply to the {@link RebindOperationExecutor}. + * @param renamingStrategy + * {@link TempEntryRenamingStrategy} to use for generating temp + * DNs. + */ + public RebindOperationRecorder(LdapOperations ldapOperations, + TempEntryRenamingStrategy renamingStrategy) { + this.ldapOperations = ldapOperations; + this.renamingStrategy = renamingStrategy; + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + if (args == null || args.length != 3) { + throw new IllegalArgumentException( + "Invalid arguments for bind operation"); + } + Name dn = LdapTransactionUtils.getFirstArgumentAsName(args); + Object object = args[1]; + Attributes attributes = null; + if (args[2] != null && !(args[2] instanceof Attributes)) { + throw new IllegalArgumentException( + "Invalid third argument to bind operation"); + } else if (args[2] != null) { + attributes = (Attributes) args[2]; + } + + Name temporaryName = renamingStrategy.getTemporaryName(dn); + + return new RebindOperationExecutor(ldapOperations, dn, temporaryName, + object, attributes); + } + + /** + * Get the LdapOperations. For testing purposes. + * + * @return the LdapOperations. + */ + LdapOperations getLdapOperations() { + return ldapOperations; + } + + public TempEntryRenamingStrategy getRenamingStrategy() { + return renamingStrategy; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RenameOperationExecutor.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RenameOperationExecutor.java new file mode 100644 index 000000000..1be577388 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RenameOperationExecutor.java @@ -0,0 +1,101 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +import javax.naming.Name; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage a rename + * operation. Performs a rename operation in {@link #performOperation()}, a + * negating rename in {@link #rollback()}, and nothing in {@link #commit()}. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class RenameOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Logger log = LoggerFactory.getLogger(RenameOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name newDn; + + private Name originalDn; + + /** + * Constructor. + * + * @param ldapOperations + * The {@link LdapOperations} to use for performing the rollback + * operation. + * @param originalDn + * DN that the entry was moved from in the recorded operation. + * @param newDn + * DN that the entry has been moved to in the recorded operation. + */ + public RenameOperationExecutor(LdapOperations ldapOperations, + Name originalDn, Name newDn) { + this.ldapOperations = ldapOperations; + this.originalDn = originalDn; + this.newDn = newDn; + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + log.debug("Rolling back rename operation"); + try { + ldapOperations.rename(newDn, originalDn); + } catch (Exception e) { + log.warn("Unable to rollback rename operation. " + "originalDn: " + + newDn + "; newDn: " + originalDn); + } + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Nothing to do in commit for rename operation"); + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing rename operation"); + ldapOperations.rename(originalDn, newDn); + } + + Name getNewDn() { + return newDn; + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } + + Name getOriginalDn() { + return originalDn; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RenameOperationRecorder.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RenameOperationRecorder.java new file mode 100644 index 000000000..0e093009c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/RenameOperationRecorder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; +import com.fr.third.springframework.util.Assert; + +import javax.naming.Name; + +/** + * A {@link CompensatingTransactionOperationRecorder} for keeping track of + * rename operations. Creates {@link RenameOperationExecutor} objects for + * rolling back. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class RenameOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private static Logger log = LoggerFactory.getLogger(RenameOperationRecorder.class); + + private LdapOperations ldapOperations; + + /** + * Constructor. + * + * @param ldapOperations + * The {@link LdapOperations} to supply to the created + * {@link RebindOperationExecutor} objects. + */ + public RenameOperationRecorder(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + log.debug("Storing rollback information for rename operation"); + Assert.notEmpty(args); + if (args.length != 2) { + // This really shouldn't happen. + throw new IllegalArgumentException("Illegal argument length"); + } + Name oldDn = LdapTransactionUtils.getArgumentAsName(args[0]); + Name newDn = LdapTransactionUtils.getArgumentAsName(args[1]); + return new RenameOperationExecutor(ldapOperations, oldDn, newDn); + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/TempEntryRenamingStrategy.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/TempEntryRenamingStrategy.java new file mode 100644 index 000000000..3d2be7a41 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/TempEntryRenamingStrategy.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import javax.naming.Name; + +/** + * Interface for different strategies to rename temporary entries for unbind and + * rebind operations. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public interface TempEntryRenamingStrategy { + + /** + * Get a temporary name for the current entry to be renamed to. + * + * @param originalName + * The original name of the entry. + * @return The name to which the entry should be temporarily renamed + * according to this strategy. + */ + Name getTemporaryName(Name originalName); +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/UnbindOperationExecutor.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/UnbindOperationExecutor.java new file mode 100644 index 000000000..2f64b2ae8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/UnbindOperationExecutor.java @@ -0,0 +1,106 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +import javax.naming.Name; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage an unbind + * operation. The methods in this class do not behave as expected, since it + * might be impossible to retrieve all the original attributes from the entry. + * Instead this class performs a rename in {@link #performOperation()}, + * a negating rename in {@link #rollback()}, and {@link #commit()} unbinds the + * entry from its temporary location. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class UnbindOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Logger log = LoggerFactory.getLogger(UnbindOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name originalDn; + + private Name temporaryDn; + + /** + * Constructor. + * + * @param ldapOperations + * The {@link LdapOperations} to use for performing the rollback + * operation. + * @param originalDn + * The original DN of the entry to be removed. + * @param temporaryDn + * Temporary DN of the entry to be removed; this is where the + * entry is temporarily stored during the transaction. + */ + public UnbindOperationExecutor(LdapOperations ldapOperations, + Name originalDn, Name temporaryDn) { + this.ldapOperations = ldapOperations; + this.originalDn = originalDn; + this.temporaryDn = temporaryDn; + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + try { + ldapOperations.rename(temporaryDn, originalDn); + } catch (Exception e) { + log.warn("Filed to rollback unbind operation, temporaryDn: " + + temporaryDn + "; originalDn: " + originalDn); + } + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Committing unbind operation - unbinding temporary entry"); + ldapOperations.unbind(temporaryDn); + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing operation for unbind -" + + " renaming to temporary entry."); + ldapOperations.rename(originalDn, temporaryDn); + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } + + Name getOriginalDn() { + return originalDn; + } + + Name getTemporaryDn() { + return temporaryDn; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/UnbindOperationRecorder.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/UnbindOperationRecorder.java new file mode 100644 index 000000000..f35b2d71f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/UnbindOperationRecorder.java @@ -0,0 +1,74 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating; + +import javax.naming.Name; + +import com.fr.third.springframework.ldap.core.LdapOperations; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * {@link CompensatingTransactionOperationRecorder} to keep track of unbind + * operations. This class creates {@link UnbindOperationExecutor} objects for + * rollback. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class UnbindOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private LdapOperations ldapOperations; + + private TempEntryRenamingStrategy renamingStrategy; + + /** + * Constructor. + * + * @param ldapOperations + * {@link LdapOperations} to use for getting the data prior to + * unbinding the entry and to supply to the + * {@link UnbindOperationExecutor} for rollback. + * @param renamingStrategy + * the {@link TempEntryRenamingStrategy} to use when generating + * DNs for temporary entries. + */ + public UnbindOperationRecorder(LdapOperations ldapOperations, + TempEntryRenamingStrategy renamingStrategy) { + this.ldapOperations = ldapOperations; + this.renamingStrategy = renamingStrategy; + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + Name dn = LdapTransactionUtils.getFirstArgumentAsName(args); + Name temporaryDn = renamingStrategy.getTemporaryName(dn); + + return new UnbindOperationExecutor(ldapOperations, dn, temporaryDn); + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } + + public TempEntryRenamingStrategy getRenamingStrategy() { + return renamingStrategy; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManager.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManager.java new file mode 100644 index 000000000..48e1efe00 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManager.java @@ -0,0 +1,212 @@ +///* +// * Copyright 2005-2013 the original author or authors. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package com.fr.third.springframework.ldap.transaction.compensating.manager; +// +//import com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager; +//import com.fr.third.springframework.ldap.core.ContextSource; +//import com.fr.third.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +//import com.fr.third.springframework.transaction.TransactionDefinition; +//import com.fr.third.springframework.transaction.TransactionException; +//import com.fr.third.springframework.transaction.TransactionSuspensionNotSupportedException; +//import com.fr.third.springframework.transaction.support.DefaultTransactionStatus; +// +///** +// * A Transaction Manager to manage LDAP and JDBC operations within the same +// * transaction. Note that even though the same logical transaction is used, this +// * is not a JTA XA transaction; no two-phase commit will be performed, +// * and thus commit and rollback may yield unexpected results. +// * +// * Note that nested transactions are not supported. +// * +// * @author Mattias Hellborg Arthursson +// * @since 1.2 +// * @deprecated The idea of wrapping two transaction managers without actual XA support is probably not such a good idea +// * after all. AbstractPlatformTransactionManager is not designed for this usage. +// */ +//public class ContextSourceAndDataSourceTransactionManager extends +// DataSourceTransactionManager { +// +// private static final long serialVersionUID = 6832868697460384648L; +// +// private ContextSourceTransactionManagerDelegate ldapManagerDelegate = new ContextSourceTransactionManagerDelegate(); +// +// public ContextSourceAndDataSourceTransactionManager() { +// super(); +// // Override the default behaviour. +// setNestedTransactionAllowed(false); +// } +// +// /* +// * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#isExistingTransaction(java.lang.Object) +// */ +// protected boolean isExistingTransaction(Object transaction) { +// // We don't support nested transactions here +// return false; +// } +// +// /* +// * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction() +// */ +// protected Object doGetTransaction() { +// Object dataSourceTransactionObject = super.doGetTransaction(); +// Object contextSourceTransactionObject = ldapManagerDelegate +// .doGetTransaction(); +// +// return new ContextSourceAndDataSourceTransactionObject( +// contextSourceTransactionObject, dataSourceTransactionObject); +// } +// +// /* +// * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin(java.lang.Object, +// * com.fr.third.springframework.transaction.TransactionDefinition) +// */ +// protected void doBegin(Object transaction, TransactionDefinition definition) { +// ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) transaction; +// +// super.doBegin(actualTransactionObject.getDataSourceTransactionObject(), +// definition); +// try { +// ldapManagerDelegate.doBegin(actualTransactionObject +// .getLdapTransactionObject(), definition); +// } catch (TransactionException e) { +// // Failed to start LDAP transaction - make sure we clean up properly +// super.doCleanupAfterCompletion(actualTransactionObject.getDataSourceTransactionObject()); +// throw e; +// } +// } +// +// /* +// * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doCleanupAfterCompletion(java.lang.Object) +// */ +// protected void doCleanupAfterCompletion(Object transaction) { +// ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) transaction; +// +// super.doCleanupAfterCompletion(actualTransactionObject +// .getDataSourceTransactionObject()); +// ldapManagerDelegate.doCleanupAfterCompletion(actualTransactionObject +// .getLdapTransactionObject()); +// } +// +// /* +// * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit(com.fr.third.springframework.transaction.support.DefaultTransactionStatus) +// */ +// protected void doCommit(DefaultTransactionStatus status) { +// +// ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) status +// .getTransaction(); +// +// try { +// super.doCommit(new DefaultTransactionStatus(actualTransactionObject +// .getDataSourceTransactionObject(), status +// .isNewTransaction(), status.isNewSynchronization(), status +// .isReadOnly(), status.isDebug(), status +// .getSuspendedResources())); +// } catch (TransactionException ex) { +// if (isRollbackOnCommitFailure()) { +// logger.debug("Failed to commit db resource, rethrowing", ex); +// // If we are to rollback on commit failure, just rethrow the +// // exception - this will cause a rollback to be performed on +// // both resources. +// throw ex; +// } else { +// logger +// .warn("Failed to commit and resource is rollbackOnCommit not set -" +// + " proceeding to commit ldap resource."); +// } +// } +// ldapManagerDelegate.doCommit(new DefaultTransactionStatus( +// actualTransactionObject.getLdapTransactionObject(), status +// .isNewTransaction(), status.isNewSynchronization(), +// status.isReadOnly(), status.isDebug(), status +// .getSuspendedResources())); +// } +// +// /* +// * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback(com.fr.third.springframework.transaction.support.DefaultTransactionStatus) +// */ +// protected void doRollback(DefaultTransactionStatus status) { +// ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) status +// .getTransaction(); +// +// super.doRollback(new DefaultTransactionStatus(actualTransactionObject +// .getDataSourceTransactionObject(), status.isNewTransaction(), +// status.isNewSynchronization(), status.isReadOnly(), status +// .isDebug(), status.getSuspendedResources())); +// ldapManagerDelegate.doRollback(new DefaultTransactionStatus( +// actualTransactionObject.getLdapTransactionObject(), status +// .isNewTransaction(), status.isNewSynchronization(), +// status.isReadOnly(), status.isDebug(), status +// .getSuspendedResources())); +// } +// +// public ContextSource getContextSource() { +// return ldapManagerDelegate.getContextSource(); +// } +// +// public void setContextSource(ContextSource contextSource) { +// ldapManagerDelegate.setContextSource(contextSource); +// } +// +// public void setRenamingStrategy( +// TempEntryRenamingStrategy renamingStrategy) { +// ldapManagerDelegate.setRenamingStrategy(renamingStrategy); +// } +// +// private final static class ContextSourceAndDataSourceTransactionObject { +// private Object ldapTransactionObject; +// +// private Object dataSourceTransactionObject; +// +// public ContextSourceAndDataSourceTransactionObject( +// Object ldapTransactionObject, Object dataSourceTransactionObject) { +// this.ldapTransactionObject = ldapTransactionObject; +// this.dataSourceTransactionObject = dataSourceTransactionObject; +// } +// +// public Object getDataSourceTransactionObject() { +// return dataSourceTransactionObject; +// } +// +// public Object getLdapTransactionObject() { +// return ldapTransactionObject; +// } +// } +// +// /* +// * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doSuspend(java.lang.Object) +// */ +// protected Object doSuspend(Object transaction) { +// throw new TransactionSuspensionNotSupportedException( +// "Transaction manager [" + getClass().getName() +// + "] does not support transaction suspension"); +// } +// +// /* +// * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doResume(java.lang.Object, +// * java.lang.Object) +// */ +// protected void doResume(Object transaction, Object suspendedResources) { +// throw new TransactionSuspensionNotSupportedException( +// "Transaction manager [" + getClass().getName() +// + "] does not support transaction suspension"); +// } +// +// public void afterPropertiesSet() { +// super.afterPropertiesSet(); +// ldapManagerDelegate.checkRenamingStrategy(); +// } +//} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceAndHibernateTransactionManager.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceAndHibernateTransactionManager.java new file mode 100755 index 000000000..aaa8dff5f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceAndHibernateTransactionManager.java @@ -0,0 +1,211 @@ +///* +// * Copyright 2005-2013 the original author or authors. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package com.fr.third.springframework.ldap.transaction.compensating.manager; +// +//import com.fr.third.springframework.ldap.core.ContextSource; +//import com.fr.third.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +//import com.fr.third.springframework.orm.hibernate3.HibernateTransactionManager; +//import com.fr.third.springframework.transaction.TransactionDefinition; +//import com.fr.third.springframework.transaction.TransactionException; +//import com.fr.third.springframework.transaction.TransactionSuspensionNotSupportedException; +//import com.fr.third.springframework.transaction.support.DefaultTransactionStatus; +///** +// * A Transaction Manager to manage LDAP and Hibernate 3 operations within the same +// * transaction. Note that even though the same logical transaction is used, this +// * is not a JTA XA transaction; no two-phase commit will be performed, +// * and thus commit and rollback may yield unexpected results.
+// * This Transaction Manager is as good as it gets when you are using in LDAP in +// * combination with a Hibernate 3 and unable to use XA transactions because LDAP +// * is not transactional by design to begin with.
+// * +// * Furthermore, this manager does not support nested transactions +// * @author Hans Westerbeek +// * @since 1.2.2 +// * @deprecated The idea of wrapping two transaction managers without actual XA support is probably not such a good idea +// * after all. AbstractPlatformTransactionManager is not designed for this usage. +// */ +//public class ContextSourceAndHibernateTransactionManager extends HibernateTransactionManager { +// +// /** +// * +// */ +// private static final long serialVersionUID = 1L; +// +// private ContextSourceTransactionManagerDelegate ldapManagerDelegate = new ContextSourceTransactionManagerDelegate(); +// +// /* +// * @see com.fr.third.springframework.orm.hibernate3.HibernateTransactionManager#isExistingTransaction(java.lang.Object) +// */ +// protected boolean isExistingTransaction(Object transaction) { +// ContextSourceAndHibernateTransactionObject actualTransactionObject = (ContextSourceAndHibernateTransactionObject) transaction; +// +// return super.isExistingTransaction(actualTransactionObject +// .getHibernateTransactionObject()); +// } +// +// /* +// * @see com.fr.third.springframework.orm.hibernate3.HibernateTransactionManager#doGetTransaction() +// */ +// protected Object doGetTransaction() { +// Object dataSourceTransactionObject = super.doGetTransaction(); +// Object contextSourceTransactionObject = ldapManagerDelegate +// .doGetTransaction(); +// +// return new ContextSourceAndHibernateTransactionObject( +// contextSourceTransactionObject, dataSourceTransactionObject); +// } +// +// /* +// * @see com.fr.third.springframework.orm.hibernate3.HibernateTransactionManager#doBegin(java.lang.Object, +// * com.fr.third.springframework.transaction.TransactionDefinition) +// */ +// protected void doBegin(Object transaction, TransactionDefinition definition) { +// ContextSourceAndHibernateTransactionObject actualTransactionObject = (ContextSourceAndHibernateTransactionObject) transaction; +// +// super.doBegin(actualTransactionObject.getHibernateTransactionObject(), +// definition); +// try { +// ldapManagerDelegate.doBegin(actualTransactionObject +// .getLdapTransactionObject(), definition); +// } catch (TransactionException e) { +// // Failed to start LDAP transaction - make sure we clean up properly +// super.doCleanupAfterCompletion(actualTransactionObject.getHibernateTransactionObject()); +// throw e; +// } +// } +// +// /* +// * @see com.fr.third.springframework.orm.hibernate3.HibernateTransactionManager#doCleanupAfterCompletion(java.lang.Object) +// */ +// protected void doCleanupAfterCompletion(Object transaction) { +// ContextSourceAndHibernateTransactionObject actualTransactionObject = (ContextSourceAndHibernateTransactionObject) transaction; +// +// super.doCleanupAfterCompletion(actualTransactionObject +// .getHibernateTransactionObject()); +// ldapManagerDelegate.doCleanupAfterCompletion(actualTransactionObject +// .getLdapTransactionObject()); +// } +// +// /* +// * @see com.fr.third.springframework.orm.hibernate3.HibernateTransactionManager#doCommit(com.fr.third.springframework.transaction.support.DefaultTransactionStatus) +// */ +// protected void doCommit(DefaultTransactionStatus status) { +// +// ContextSourceAndHibernateTransactionObject actualTransactionObject = (ContextSourceAndHibernateTransactionObject) status +// .getTransaction(); +// +// try { +// super.doCommit(new DefaultTransactionStatus(actualTransactionObject +// .getHibernateTransactionObject(), status +// .isNewTransaction(), status.isNewSynchronization(), status +// .isReadOnly(), status.isDebug(), status +// .getSuspendedResources())); +// } catch (TransactionException ex) { +// if (isRollbackOnCommitFailure()) { +// logger.debug("Failed to commit db resource, rethrowing", ex); +// // If we are to rollback on commit failure, just rethrow the +// // exception - this will cause a rollback to be performed on +// // both resources. +// throw ex; +// } else { +// logger +// .warn("Failed to commit and resource is rollbackOnCommit not set -" +// + " proceeding to commit ldap resource."); +// } +// } +// ldapManagerDelegate.doCommit(new DefaultTransactionStatus( +// actualTransactionObject.getLdapTransactionObject(), status +// .isNewTransaction(), status.isNewSynchronization(), +// status.isReadOnly(), status.isDebug(), status +// .getSuspendedResources())); +// } +// +// /* +// * @see com.fr.third.springframework.orm.hibernate3.HibernateTransactionManager#doRollback(com.fr.third.springframework.transaction.support.DefaultTransactionStatus) +// */ +// protected void doRollback(DefaultTransactionStatus status) { +// ContextSourceAndHibernateTransactionObject actualTransactionObject = (ContextSourceAndHibernateTransactionObject) status +// .getTransaction(); +// +// super.doRollback(new DefaultTransactionStatus(actualTransactionObject +// .getHibernateTransactionObject(), status.isNewTransaction(), +// status.isNewSynchronization(), status.isReadOnly(), status +// .isDebug(), status.getSuspendedResources())); +// ldapManagerDelegate.doRollback(new DefaultTransactionStatus( +// actualTransactionObject.getLdapTransactionObject(), status +// .isNewTransaction(), status.isNewSynchronization(), +// status.isReadOnly(), status.isDebug(), status +// .getSuspendedResources())); +// } +// +// public ContextSource getContextSource() { +// return ldapManagerDelegate.getContextSource(); +// } +// +// public void setContextSource(ContextSource contextSource) { +// ldapManagerDelegate.setContextSource(contextSource); +// } +// +// public void setRenamingStrategy( +// TempEntryRenamingStrategy renamingStrategy) { +// ldapManagerDelegate.setRenamingStrategy(renamingStrategy); +// } +// +// private static final class ContextSourceAndHibernateTransactionObject { +// private Object ldapTransactionObject; +// +// private Object hibernateTransactionObject; +// +// public ContextSourceAndHibernateTransactionObject( +// Object ldapTransactionObject, Object hibernateTransactionObject) { +// this.ldapTransactionObject = ldapTransactionObject; +// this.hibernateTransactionObject = hibernateTransactionObject; +// } +// +// public Object getHibernateTransactionObject() { +// return hibernateTransactionObject; +// } +// +// public Object getLdapTransactionObject() { +// return ldapTransactionObject; +// } +// } +// +// /* +// * @see com.fr.third.springframework.orm.hibernate3.HibernateTransactionManager#doSuspend(java.lang.Object) +// */ +// protected Object doSuspend(Object transaction) { +// throw new TransactionSuspensionNotSupportedException( +// "Transaction manager [" + getClass().getName() +// + "] does not support transaction suspension"); +// } +// +// /* +// * @see com.fr.third.springframework.orm.hibernate3.HibernateTransactionManager#doResume(java.lang.Object, +// * java.lang.Object) +// */ +// protected void doResume(Object transaction, Object suspendedResources) { +// throw new TransactionSuspensionNotSupportedException( +// "Transaction manager [" + getClass().getName() +// + "] does not support transaction suspension"); +// } +// +// public void afterPropertiesSet() { +// super.afterPropertiesSet(); +// ldapManagerDelegate.checkRenamingStrategy(); +// } +//} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManager.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManager.java new file mode 100644 index 000000000..1b139d83d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManager.java @@ -0,0 +1,196 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.transaction.compensating.manager; + +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +import com.fr.third.springframework.ldap.transaction.compensating.UnbindOperationExecutor; +import com.fr.third.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy; +import com.fr.third.springframework.transaction.TransactionDefinition; +import com.fr.third.springframework.transaction.TransactionException; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; +import com.fr.third.springframework.transaction.compensating.support.CompensatingTransactionObject; +import com.fr.third.springframework.transaction.compensating.support.DefaultCompensatingTransactionOperationManager; +import com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager; +import com.fr.third.springframework.transaction.support.DefaultTransactionStatus; + +/** + * TransactionManager for managing LDAP transactions. Since transactions are not + * supported in the LDAP protocol, this class and its collaborators aim to + * provide compensating transactions instead. Should a transaction + * need to be rolled back, this TransactionManager will try to restore the + * original state using information recorded prior to each operation. The + * operation where the original state is restored is called a compensating + * operation. + *

+ * NOTE: The transactions provided by this TransactionManager are all + * client side and are by no means 'real' transactions, in the sense + * that we know them in the ordinary database world, e.g.: + *

    + *
  • Should the transaction failure be caused by a network failure, there is + * no way whatsoever that this TransactionManager can restore the database + * state. In this case, all possibilities for rollback will be utterly lost.
  • + *
  • Transaction isolation is not provided, i.e. entries participating in a + * transaction for one client may very well participate in another transaction + * for another client at the same time. Should one of these transactions be + * rolled back, the outcome of this is undetermined, and may in the worst case + * result in total failure.
  • + *
+ *

+ * While the points above should be noted and considered, the compensating + * transaction approach will be perfectly sufficient for all but the most + * unfortunate of circumstances. Considering that there currently is a total + * absence of server-side transaction support in the LDAP world, being able to + * mark operations as transactional in the same way as for relational database + * operations is surely a step forward. + *

+ * An LDAP transaction is tied to a {@link ContextSource}, to be supplied to + * the {@link #setContextSource(ContextSource)} method. While the actual + * ContextSource used by the target LdapTemplate instance needs to be of the + * type {@link TransactionAwareContextSourceProxy}, the ContextSource supplied + * to this class should be the actual target ContextSource. + *

+ * Using this TransactionManager along with + * {@link TransactionAwareContextSourceProxy}, all modifying operations (bind, + * unbind, rebind, rename, modifyAttributes) in a transaction will be + * intercepted. Each modification has its corresponding + * {@link CompensatingTransactionOperationRecorder}, which collects the + * information necessary to perform a rollback and produces a + * {@link CompensatingTransactionOperationExecutor} which is then used to + * execute the actual operation and is later called for performing the commit or + * rollback. + *

+ * For several of the operations, performing a rollback is pretty + * straightforward. For example, in order to roll back a rename operation, it + * will only be required to rename the entry back to its original position. For + * other operations, however, it's a bit more complicated. An unbind operation + * is not possible to roll back by simply binding the entry back with the + * attributes retrieved from the original entry. It might not be possible to get + * all the information from the original entry. Consequently, the + * {@link UnbindOperationExecutor} will move the original entry to a temporary + * location in its performOperation() method. The commit() method will know that + * everything went well, so it will be OK to unbind the entry. The rollback + * operation will be to rename the entry back to its original location. The same + * behaviour is used for rebind() operations. The operation of calculating a + * temporary location for an entry is delegated to a + * {@link TempEntryRenamingStrategy} (default + * {@link DefaultTempEntryRenamingStrategy}), specified in + * {@link #setRenamingStrategy(TempEntryRenamingStrategy)}. + *

+ * The actual work of this Transaction Manager is delegated to a + * {@link ContextSourceTransactionManagerDelegate}. This is because the exact + * same logic needs to be used if we want to wrap a JDBC and LDAP transaction in + * the same logical transaction. + *

+ * + * @author Mattias Hellborg Arthursson + * + * @see ContextSourceAndDataSourceTransactionManager + * @see ContextSourceTransactionManagerDelegate + * @see DefaultCompensatingTransactionOperationManager + * @see TempEntryRenamingStrategy + * @see TransactionAwareContextSourceProxy + * @since 1.2 + */ +public class ContextSourceTransactionManager extends + AbstractPlatformTransactionManager implements InitializingBean { + + private static final long serialVersionUID = 7138208218687237856L; + + private ContextSourceTransactionManagerDelegate delegate = new ContextSourceTransactionManagerDelegate(); + + /* + * @see com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager#doBegin(java.lang.Object, + * com.fr.third.springframework.transaction.TransactionDefinition) + */ + protected void doBegin(Object transaction, TransactionDefinition definition) { + delegate.doBegin(transaction, definition); + } + + /* + * @see com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager#doCleanupAfterCompletion(java.lang.Object) + */ + protected void doCleanupAfterCompletion(Object transaction) { + delegate.doCleanupAfterCompletion(transaction); + } + + /* + * @see com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager#doCommit(com.fr.third.springframework.transaction.support.DefaultTransactionStatus) + */ + protected void doCommit(DefaultTransactionStatus status) { + delegate.doCommit(status); + } + + /* + * @see com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager#doGetTransaction() + */ + protected Object doGetTransaction() { + return delegate.doGetTransaction(); + } + + /* + * @see com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager#doRollback(com.fr.third.springframework.transaction.support.DefaultTransactionStatus) + */ + protected void doRollback(DefaultTransactionStatus status) { + delegate.doRollback(status); + } + + /** + * Get the ContextSource. + * + * @return the contextSource. + * @see ContextSourceTransactionManagerDelegate#getContextSource() + */ + public ContextSource getContextSource() { + return delegate.getContextSource(); + } + + /** + * Set the ContextSource. + * + * @param contextSource + * the ContextSource. + * @see ContextSourceTransactionManagerDelegate#setContextSource(ContextSource) + */ + public void setContextSource(ContextSource contextSource) { + delegate.setContextSource(contextSource); + } + + /** + * Set the {@link TempEntryRenamingStrategy}. + * + * @param renamingStrategy + * the Renaming Strategy. + * @see ContextSourceTransactionManagerDelegate#setRenamingStrategy(TempEntryRenamingStrategy) + */ + public void setRenamingStrategy(TempEntryRenamingStrategy renamingStrategy) { + delegate.setRenamingStrategy(renamingStrategy); + } + + public void afterPropertiesSet() throws Exception { + delegate.checkRenamingStrategy(); + } + + @Override + protected boolean isExistingTransaction(Object transaction) + throws TransactionException { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction; + return (txObject.getHolder() != null); + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java new file mode 100644 index 000000000..cec0b8da2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java @@ -0,0 +1,134 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.transaction.compensating.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.support.AbstractContextSource; +import com.fr.third.springframework.ldap.transaction.compensating.LdapCompensatingTransactionOperationFactory; +import com.fr.third.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +import com.fr.third.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy; +import com.fr.third.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate; +import com.fr.third.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport; +import com.fr.third.springframework.transaction.compensating.support.DefaultCompensatingTransactionOperationManager; +import com.fr.third.springframework.util.Assert; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +/** + * This delegate performs all the work for the + * {@link ContextSourceTransactionManager}. The work is delegated in order to + * be able to perform the exact same work for the LDAP part in + * {@link ContextSourceAndDataSourceTransactionManager}. + * + * @author Mattias Hellborg Arthursson + * @see ContextSourceTransactionManager + * @see ContextSourceAndDataSourceTransactionManager + * @since 1.2 + */ +public class ContextSourceTransactionManagerDelegate extends + AbstractCompensatingTransactionManagerDelegate { + + private static final Logger LOG = LoggerFactory.getLogger(ContextSourceTransactionManagerDelegate.class); + + private ContextSource contextSource; + + private TempEntryRenamingStrategy renamingStrategy; + + /** + * Set the ContextSource to work on. Even though the actual ContextSource + * sent to the LdapTemplate instance should be a + * {@link TransactionAwareContextSourceProxy}, the one sent to this method + * should be the target of that proxy. If it is not, the target will be + * extracted and used instead. + * + * @param contextSource + * the ContextSource to work on. + */ + public void setContextSource(ContextSource contextSource) { + if (contextSource instanceof TransactionAwareContextSourceProxy) { + TransactionAwareContextSourceProxy proxy = (TransactionAwareContextSourceProxy) contextSource; + this.contextSource = proxy.getTarget(); + } else { + this.contextSource = contextSource; + } + + if (contextSource instanceof AbstractContextSource) { + AbstractContextSource abstractContextSource = (AbstractContextSource) contextSource; + if(abstractContextSource.isAnonymousReadOnly()) { + throw new IllegalArgumentException( + "Compensating LDAP transactions cannot be used when context-source is anonymous-read-only"); + } + } + } + + public ContextSource getContextSource() { + return contextSource; + } + + /* + * @see com.fr.third.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate#getTransactionSynchronizationKey() + */ + protected Object getTransactionSynchronizationKey() { + return getContextSource(); + } + + /* + * @see com.fr.third.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate#getNewHolder() + */ + protected CompensatingTransactionHolderSupport getNewHolder() { + DirContext newCtx = getContextSource().getReadWriteContext(); + return new DirContextHolder( + new DefaultCompensatingTransactionOperationManager( + new LdapCompensatingTransactionOperationFactory( + renamingStrategy)), newCtx); + } + + /* + * @see com.fr.third.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate#closeTargetResource(com.fr.third.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport) + */ + protected void closeTargetResource( + CompensatingTransactionHolderSupport transactionHolderSupport) { + DirContextHolder contextHolder = (DirContextHolder) transactionHolderSupport; + DirContext ctx = contextHolder.getCtx(); + + try { + LOG.debug("Closing target context"); + ctx.close(); + } catch (NamingException e) { + LOG.warn("Failed to close target context", e); + } + } + + /** + * Set the {@link TempEntryRenamingStrategy} to be used when renaming + * temporary entries in unbind and rebind operations. Default value is a + * {@link DefaultTempEntryRenamingStrategy}. + * + * @param renamingStrategy + * the {@link TempEntryRenamingStrategy} to use. + */ + public void setRenamingStrategy(TempEntryRenamingStrategy renamingStrategy) { + this.renamingStrategy = renamingStrategy; + } + + void checkRenamingStrategy() { + Assert.notNull(renamingStrategy, "RenamingStrategy must be specified"); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/DirContextHolder.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/DirContextHolder.java new file mode 100644 index 000000000..02b1395e8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/DirContextHolder.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating.manager; + +import javax.naming.directory.DirContext; + +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import com.fr.third.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport; + +/** + * Keeps track of the transaction DirContext. The same DirContext instance will + * be reused throughout a transaction. Also keeps a + * {@link CompensatingTransactionOperationManager}, responsible for performing + * operations and keeping track of all changes and storing information necessary + * for commit or rollback. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class DirContextHolder extends CompensatingTransactionHolderSupport { + private DirContext ctx; + + /** + * Constructor. + * + * @param manager + * The {@link CompensatingTransactionOperationManager}. + * @param ctx + * The DirContext associated with the current transaction. + */ + public DirContextHolder(CompensatingTransactionOperationManager manager, + DirContext ctx) { + super(manager); + this.ctx = ctx; + } + + /** + * Set the DirContext associated with the current transaction. + * + * @param ctx + * The DirContext associated with the current transaction. + */ + public void setCtx(DirContext ctx) { + this.ctx = ctx; + } + + /** + * Return the DirContext associated with the current transaction. + */ + public DirContext getCtx() { + return ctx; + } + + /* + * @see com.fr.third.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport#getTransactedResource() + */ + protected Object getTransactedResource() { + return ctx; + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxy.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxy.java new file mode 100644 index 000000000..d43e30da3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxy.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating.manager; + +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.core.DirContextProxy; +import com.fr.third.springframework.ldap.core.support.DelegatingBaseLdapPathContextSourceSupport; +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.transaction.support.TransactionSynchronizationManager; + +import javax.naming.directory.DirContext; +import java.lang.reflect.Proxy; + +/** + * A proxy for ContextSource to make sure that the returned DirContext objects + * are aware of the surrounding transactions. This makes sure that the + * DirContext is not closed during the transaction and that all modifying + * operations are recorded, keeping track of the corresponding rollback + * operations. All returned DirContext instances will be of the type + * {@link TransactionAwareDirContextInvocationHandler}. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class TransactionAwareContextSourceProxy + extends DelegatingBaseLdapPathContextSourceSupport + implements ContextSource { + + private ContextSource target; + + /** + * Constructor. + * + * @param target + * the target ContextSource. + */ + public TransactionAwareContextSourceProxy(ContextSource target) { + this.target = target; + } + + @Override + public ContextSource getTarget() { + return target; + } + + @Override + public DirContext getReadOnlyContext() { + return getReadWriteContext(); + } + + private DirContext getTransactionAwareDirContextProxy(DirContext context, + ContextSource target) { + return (DirContext) Proxy + .newProxyInstance(DirContextProxy.class.getClassLoader(), + new Class[] { + LdapUtils + .getActualTargetClass(context), + DirContextProxy.class }, + new TransactionAwareDirContextInvocationHandler( + context, target)); + + } + + @Override + public DirContext getReadWriteContext() { + DirContextHolder contextHolder = (DirContextHolder) TransactionSynchronizationManager + .getResource(target); + DirContext ctx = null; + + if (contextHolder != null) { + ctx = contextHolder.getCtx(); + } + + if (ctx == null) { + ctx = target.getReadWriteContext(); + if (contextHolder != null) { + contextHolder.setCtx(ctx); + } + } + return getTransactionAwareDirContextProxy(ctx, target); + } + + @Override + public DirContext getContext(String principal, String credentials) { + return target.getContext(principal, credentials); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandler.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandler.java new file mode 100644 index 000000000..378b87fce --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandler.java @@ -0,0 +1,121 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.ldap.NamingException; +import com.fr.third.springframework.ldap.core.ContextSource; +import com.fr.third.springframework.ldap.transaction.compensating.LdapTransactionUtils; +import com.fr.third.springframework.transaction.compensating.support.CompensatingTransactionUtils; +import com.fr.third.springframework.transaction.support.TransactionSynchronizationManager; + +import javax.naming.directory.DirContext; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Proxy implementation for DirContext, making sure that the instance is not + * closed during a transaction, and that all modifying operations are recorded, + * storing compensating rollback operations for them. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class TransactionAwareDirContextInvocationHandler implements + InvocationHandler { + + private static Logger log = LoggerFactory.getLogger(TransactionAwareDirContextInvocationHandler.class); + + private DirContext target; + + private ContextSource contextSource; + + /** + * Constructor. + * + * @param target + * The target DirContext. + * @param contextSource + * The transactional ContextSource, needed to get hold of the + * current transaction's {@link DirContextHolder}. + */ + public TransactionAwareDirContextInvocationHandler(DirContext target, + ContextSource contextSource) { + this.target = target; + this.contextSource = contextSource; + } + + /* + * @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 { + + String methodName = method.getName(); + if (methodName.equals("getTargetContext")) { + return target; + } else if (methodName.equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); + } else if (methodName.equals("hashCode")) { + // Use hashCode of Connection proxy. + return hashCode(); + } else if (methodName.equals("close")) { + doCloseConnection(target, contextSource); + return null; + } else if (LdapTransactionUtils + .isSupportedWriteTransactionOperation(methodName)) { + // Store transaction data and allow operation to proceed. + CompensatingTransactionUtils.performOperation(contextSource, + target, method, args); + return null; + } else { + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + } + + /** + * Close the supplied context, but only if it is not associated with the + * current transaction. + * + * @param context + * the DirContext to close. + * @param contextSource + * the ContextSource bound to the transaction. + * @throws NamingException + */ + void doCloseConnection(DirContext context, ContextSource contextSource) + throws javax.naming.NamingException { + DirContextHolder transactionContextHolder = (DirContextHolder) TransactionSynchronizationManager + .getResource(contextSource); + if (transactionContextHolder == null + || transactionContextHolder.getCtx() != context) { + log.debug("Closing context"); + // This is not the transactional context or the transaction is + // no longer active - we should close it. + context.close(); + } else { + log.debug("Leaving transactional context open"); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/package.html b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/package.html new file mode 100644 index 000000000..2cac0e878 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/manager/package.html @@ -0,0 +1,7 @@ + + + +The core implementation classes for client-side LDAP transactions. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/package.html b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/package.html new file mode 100644 index 000000000..a5d44d78d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/package.html @@ -0,0 +1,7 @@ + + + +LDAP specific implementations of the Compensating Transaction interfaces. + + + diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategy.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategy.java new file mode 100644 index 000000000..4e895b33e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategy.java @@ -0,0 +1,96 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.ldap.transaction.compensating.support; + +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; + +import javax.naming.InvalidNameException; +import javax.naming.Name; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +/** + * Default implementation of {@link TempEntryRenamingStrategy}. This + * implementation simply adds "_temp" to the leftmost (least significant part) + * of the name. For example: + * + *

+ * cn=john doe, ou=company1, c=SE
+ * 
+ * + * becomes: + * + *
+ * cn=john doe_temp, ou=company1, c=SE
+ * 
+ *

+ * Note that using this strategy means that the entry remains in virtually the + * same location as where it originally resided. This means that searches later + * in the same transaction might return references to the temporary entry even + * though it should have been removed or rebound. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class DefaultTempEntryRenamingStrategy implements + TempEntryRenamingStrategy { + + /** + * The default temp entry suffix, "_temp". + */ + public static final String DEFAULT_TEMP_SUFFIX = "_temp"; + + private String tempSuffix = DEFAULT_TEMP_SUFFIX; + + /* + * @see com.fr.third.springframework.ldap.support.transaction.TempEntryRenamingStrategy#getTemporaryName(javax.naming.Name) + */ + public Name getTemporaryName(Name originalName) { + LdapName temporaryName = LdapUtils.newLdapName(originalName); + + // Add tempSuffix to the leaf node name. + try { + String leafNode = (String) temporaryName.remove(temporaryName.size() - 1); + temporaryName.add(new Rdn(leafNode + tempSuffix)); + } catch (InvalidNameException e) { + throw new com.fr.third.springframework.ldap.InvalidNameException(e); + } + + return temporaryName; + } + + /** + * Get the suffix that will be used for renaming temporary entries. + * + * @return the suffix. + */ + public String getTempSuffix() { + return tempSuffix; + } + + /** + * Set the suffix to use for renaming temporary entries. Default value is + * {@link #DEFAULT_TEMP_SUFFIX}. + * + * @param tempSuffix + * the suffix. + */ + public void setTempSuffix(String tempSuffix) { + this.tempSuffix = tempSuffix; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategy.java b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategy.java new file mode 100644 index 000000000..d746c3d2e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategy.java @@ -0,0 +1,91 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.ldap.transaction.compensating.support; + +import com.fr.third.springframework.ldap.support.LdapUtils; +import com.fr.third.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; + +import javax.naming.InvalidNameException; +import javax.naming.Name; +import javax.naming.ldap.LdapName; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A {@link TempEntryRenamingStrategy} that moves the entry to a different + * subtree than the original entry. The specified subtree needs to be present in + * the LDAP tree; it will not be created and operations using this strategy will + * fail if the destination is not in place. However, this strategy is preferable + * to {@link DefaultTempEntryRenamingStrategy}, as it makes searches have the + * expected result even though the temporary entry still exists during the + * transaction. + *

+ * Example: If the specified subtreeNode is + * ou=tempEntries and the originalName is + * cn=john doe, ou=company1, c=SE, the result of + * {@link #getTemporaryName(Name)} will be + * cn=john doe1, ou=tempEntries. The "1" suffix is a + * sequence number needed to prevent potential collisions in the temporary + * storage. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class DifferentSubtreeTempEntryRenamingStrategy implements + TempEntryRenamingStrategy { + + private Name subtreeNode; + + private static final AtomicInteger NEXT_SEQUENCE_NO = new AtomicInteger(1); + + public DifferentSubtreeTempEntryRenamingStrategy(Name subtreeNode) { + this.subtreeNode = subtreeNode; + } + + public DifferentSubtreeTempEntryRenamingStrategy(String subtreeNode) { + this(LdapUtils.newLdapName(subtreeNode)); + } + + public Name getSubtreeNode() { + return subtreeNode; + } + + public void setSubtreeNode(Name subtreeNode) { + this.subtreeNode = subtreeNode; + } + + int getNextSequenceNo() { + return NEXT_SEQUENCE_NO.get(); + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.TempEntryRenamingStrategy#getTemporaryName(javax.naming.Name) + */ + public Name getTemporaryName(Name originalName) { + int thisSequenceNo = NEXT_SEQUENCE_NO.getAndIncrement(); + + LdapName tempName = LdapUtils.newLdapName(originalName); + try { + String leafNode = tempName.get(tempName.size() - 1) + thisSequenceNo; + LdapName newName = LdapUtils.newLdapName(subtreeNode); + newName.add(leafNode); + + return newName; + } catch (InvalidNameException e) { + throw new com.fr.third.springframework.ldap.InvalidNameException(e); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/support/package.html b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/support/package.html new file mode 100644 index 000000000..b62d994f0 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/ldap/transaction/compensating/support/package.html @@ -0,0 +1,7 @@ + + + +Useful helper implementations for client side Compensating LDAP Transactions. + + + diff --git a/fine-spring/src/com/fr/third/springframework/transaction/CannotCreateTransactionException.java b/fine-spring/src/com/fr/third/springframework/transaction/CannotCreateTransactionException.java new file mode 100644 index 000000000..53f4b8dae --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/CannotCreateTransactionException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Exception thrown when a transaction can't be created using an + * underlying transaction API such as JTA. + * + * @author Rod Johnson + * @since 17.03.2003 + */ +@SuppressWarnings("serial") +public class CannotCreateTransactionException extends TransactionException { + + /** + * Constructor for CannotCreateTransactionException. + * @param msg the detail message + */ + public CannotCreateTransactionException(String msg) { + super(msg); + } + + /** + * Constructor for CannotCreateTransactionException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public CannotCreateTransactionException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/HeuristicCompletionException.java b/fine-spring/src/com/fr/third/springframework/transaction/HeuristicCompletionException.java new file mode 100644 index 000000000..fbf900458 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/HeuristicCompletionException.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Exception that represents a transaction failure caused by a heuristic + * decision on the side of the transaction coordinator. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 17.03.2003 + */ +@SuppressWarnings("serial") +public class HeuristicCompletionException extends TransactionException { + + /** + * Values for the outcome state of a heuristically completed transaction. + */ + public static final int STATE_UNKNOWN = 0; + public static final int STATE_COMMITTED = 1; + public static final int STATE_ROLLED_BACK = 2; + public static final int STATE_MIXED = 3; + + + public static String getStateString(int state) { + switch (state) { + case STATE_COMMITTED: + return "committed"; + case STATE_ROLLED_BACK: + return "rolled back"; + case STATE_MIXED: + return "mixed"; + default: + return "unknown"; + } + } + + + /** + * The outcome state of the transaction: have some or all resources been committed? + */ + private int outcomeState = STATE_UNKNOWN; + + + /** + * Constructor for HeuristicCompletionException. + * @param outcomeState the outcome state of the transaction + * @param cause the root cause from the transaction API in use + */ + public HeuristicCompletionException(int outcomeState, Throwable cause) { + super("Heuristic completion: outcome state is " + getStateString(outcomeState), cause); + this.outcomeState = outcomeState; + } + + /** + * Return the outcome state of the transaction state, + * as one of the constants in this class. + * @see #STATE_UNKNOWN + * @see #STATE_COMMITTED + * @see #STATE_ROLLED_BACK + * @see #STATE_MIXED + */ + public int getOutcomeState() { + return outcomeState; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/IllegalTransactionStateException.java b/fine-spring/src/com/fr/third/springframework/transaction/IllegalTransactionStateException.java new file mode 100644 index 000000000..5d1e41d3f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/IllegalTransactionStateException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Exception thrown when the existence or non-existence of a transaction + * amounts to an illegal state according to the transaction propagation + * behavior that applies. + * + * @author Juergen Hoeller + * @since 21.01.2004 + */ +@SuppressWarnings("serial") +public class IllegalTransactionStateException extends TransactionUsageException { + + /** + * Constructor for IllegalTransactionStateException. + * @param msg the detail message + */ + public IllegalTransactionStateException(String msg) { + super(msg); + } + + /** + * Constructor for IllegalTransactionStateException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public IllegalTransactionStateException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/InvalidIsolationLevelException.java b/fine-spring/src/com/fr/third/springframework/transaction/InvalidIsolationLevelException.java new file mode 100644 index 000000000..0c68a0985 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/InvalidIsolationLevelException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Exception that gets thrown when an invalid isolation level is specified, + * i.e. an isolation level that the transaction manager implementation + * doesn't support. + * + * @author Juergen Hoeller + * @since 12.05.2003 + */ +@SuppressWarnings("serial") +public class InvalidIsolationLevelException extends TransactionUsageException { + + /** + * Constructor for InvalidIsolationLevelException. + * @param msg the detail message + */ + public InvalidIsolationLevelException(String msg) { + super(msg); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/InvalidTimeoutException.java b/fine-spring/src/com/fr/third/springframework/transaction/InvalidTimeoutException.java new file mode 100644 index 000000000..526ab6758 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/InvalidTimeoutException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Exception that gets thrown when an invalid timeout is specified, + * that is, the specified timeout valid is out of range or the + * transaction manager implementation doesn't support timeouts. + * + * @author Juergen Hoeller + * @since 12.05.2003 + */ +@SuppressWarnings("serial") +public class InvalidTimeoutException extends TransactionUsageException { + + private int timeout; + + + /** + * Constructor for InvalidTimeoutException. + * @param msg the detail message + * @param timeout the invalid timeout value + */ + public InvalidTimeoutException(String msg, int timeout) { + super(msg); + this.timeout = timeout; + } + + /** + * Return the invalid timeout value. + */ + public int getTimeout() { + return timeout; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/NestedTransactionNotSupportedException.java b/fine-spring/src/com/fr/third/springframework/transaction/NestedTransactionNotSupportedException.java new file mode 100644 index 000000000..8482e5e36 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/NestedTransactionNotSupportedException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Exception thrown when attempting to work with a nested transaction + * but nested transactions are not supported by the underlying backend. + * + * @author Juergen Hoeller + * @since 1.1 + */ +@SuppressWarnings("serial") +public class NestedTransactionNotSupportedException extends CannotCreateTransactionException { + + /** + * Constructor for NestedTransactionNotSupportedException. + * @param msg the detail message + */ + public NestedTransactionNotSupportedException(String msg) { + super(msg); + } + + /** + * Constructor for NestedTransactionNotSupportedException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public NestedTransactionNotSupportedException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/NoTransactionException.java b/fine-spring/src/com/fr/third/springframework/transaction/NoTransactionException.java new file mode 100644 index 000000000..592ef8379 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/NoTransactionException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Exception thrown when an operation is attempted that + * relies on an existing transaction (such as setting + * rollback status) and there is no existing transaction. + * This represents an illegal usage of the transaction API. + * + * @author Rod Johnson + * @since 17.03.2003 + */ +@SuppressWarnings("serial") +public class NoTransactionException extends TransactionUsageException { + + /** + * Constructor for NoTransactionException. + * @param msg the detail message + */ + public NoTransactionException(String msg) { + super(msg); + } + + /** + * Constructor for NoTransactionException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public NoTransactionException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/PlatformTransactionManager.java b/fine-spring/src/com/fr/third/springframework/transaction/PlatformTransactionManager.java new file mode 100644 index 000000000..c07d1c889 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/PlatformTransactionManager.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * This is the central interface in Spring's transaction infrastructure. + * Applications can use this directly, but it is not primarily meant as API: + * Typically, applications will work with either TransactionTemplate or + * declarative transaction demarcation through AOP. + * + *

For implementors, it is recommended to derive from the provided + * {@link com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager} + * class, which pre-implements the defined propagation behavior and takes care + * of transaction synchronization handling. Subclasses have to implement + * template methods for specific states of the underlying transaction, + * for example: begin, suspend, resume, commit. + * + *

The default implementations of this strategy interface are + * {@link com.fr.third.springframework.transaction.jta.JtaTransactionManager} and + * {@link com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager}, + * which can serve as an implementation guide for other transaction strategies. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 16.05.2003 + * @see com.fr.third.springframework.transaction.support.TransactionTemplate + * @see com.fr.third.springframework.transaction.interceptor.TransactionInterceptor + * @see com.fr.third.springframework.transaction.interceptor.TransactionProxyFactoryBean + */ +public interface PlatformTransactionManager { + + /** + * Return a currently active transaction or create a new one, according to + * the specified propagation behavior. + *

Note that parameters like isolation level or timeout will only be applied + * to new transactions, and thus be ignored when participating in active ones. + *

Furthermore, not all transaction definition settings will be supported + * by every transaction manager: A proper transaction manager implementation + * should throw an exception when unsupported settings are encountered. + *

An exception to the above rule is the read-only flag, which should be + * ignored if no explicit read-only mode is supported. Essentially, the + * read-only flag is just a hint for potential optimization. + * @param definition TransactionDefinition instance (can be {@code null} for defaults), + * describing propagation behavior, isolation level, timeout etc. + * @return transaction status object representing the new or current transaction + * @throws TransactionException in case of lookup, creation, or system errors + * @throws IllegalTransactionStateException if the given transaction definition + * cannot be executed (for example, if a currently active transaction is in + * conflict with the specified propagation behavior) + * @see TransactionDefinition#getPropagationBehavior + * @see TransactionDefinition#getIsolationLevel + * @see TransactionDefinition#getTimeout + * @see TransactionDefinition#isReadOnly + */ + TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; + + /** + * Commit the given transaction, with regard to its status. If the transaction + * has been marked rollback-only programmatically, perform a rollback. + *

If the transaction wasn't a new one, omit the commit for proper + * participation in the surrounding transaction. If a previous transaction + * has been suspended to be able to create a new one, resume the previous + * transaction after committing the new one. + *

Note that when the commit call completes, no matter if normally or + * throwing an exception, the transaction must be fully completed and + * cleaned up. No rollback call should be expected in such a case. + *

If this method throws an exception other than a TransactionException, + * then some before-commit error caused the commit attempt to fail. For + * example, an O/R Mapping tool might have tried to flush changes to the + * database right before commit, with the resulting DataAccessException + * causing the transaction to fail. The original exception will be + * propagated to the caller of this commit method in such a case. + * @param status object returned by the {@code getTransaction} method + * @throws UnexpectedRollbackException in case of an unexpected rollback + * that the transaction coordinator initiated + * @throws HeuristicCompletionException in case of a transaction failure + * caused by a heuristic decision on the side of the transaction coordinator + * @throws TransactionSystemException in case of commit or system errors + * (typically caused by fundamental resource failures) + * @throws IllegalTransactionStateException if the given transaction + * is already completed (that is, committed or rolled back) + * @see TransactionStatus#setRollbackOnly + */ + void commit(TransactionStatus status) throws TransactionException; + + /** + * Perform a rollback of the given transaction. + *

If the transaction wasn't a new one, just set it rollback-only for proper + * participation in the surrounding transaction. If a previous transaction + * has been suspended to be able to create a new one, resume the previous + * transaction after rolling back the new one. + *

Do not call rollback on a transaction if commit threw an exception. + * The transaction will already have been completed and cleaned up when commit + * returns, even in case of a commit exception. Consequently, a rollback call + * after commit failure will lead to an IllegalTransactionStateException. + * @param status object returned by the {@code getTransaction} method + * @throws TransactionSystemException in case of rollback or system errors + * (typically caused by fundamental resource failures) + * @throws IllegalTransactionStateException if the given transaction + * is already completed (that is, committed or rolled back) + */ + void rollback(TransactionStatus status) throws TransactionException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/SavepointManager.java b/fine-spring/src/com/fr/third/springframework/transaction/SavepointManager.java new file mode 100644 index 000000000..d5495727f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/SavepointManager.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Interface that specifies an API to programmatically manage transaction + * savepoints in a generic fashion. Extended by TransactionStatus to + * expose savepoint management functionality for a specific transaction. + * + *

Note that savepoints can only work within an active transaction. + * Just use this programmatic savepoint handling for advanced needs; + * else, a subtransaction with PROPAGATION_NESTED is preferable. + * + *

This interface is inspired by JDBC 3.0's Savepoint mechanism + * but is independent from any specific persistence technology. + * + * @author Juergen Hoeller + * @since 1.1 + * @see TransactionStatus + * @see TransactionDefinition#PROPAGATION_NESTED + * @see java.sql.Savepoint + */ +public interface SavepointManager { + + /** + * Create a new savepoint. You can roll back to a specific savepoint + * via {@code rollbackToSavepoint}, and explicitly release a + * savepoint that you don't need anymore via {@code releaseSavepoint}. + *

Note that most transaction managers will automatically release + * savepoints at transaction completion. + * @return a savepoint object, to be passed into rollbackToSavepoint + * or releaseSavepoint + * @throws NestedTransactionNotSupportedException if the underlying + * transaction does not support savepoints + * @throws TransactionException if the savepoint could not be created, + * for example because the transaction is not in an appropriate state + * @see java.sql.Connection#setSavepoint + */ + Object createSavepoint() throws TransactionException; + + /** + * Roll back to the given savepoint. The savepoint will be + * automatically released afterwards. + * @param savepoint the savepoint to roll back to + * @throws NestedTransactionNotSupportedException if the underlying + * transaction does not support savepoints + * @throws TransactionException if the rollback failed + * @see java.sql.Connection#rollback(java.sql.Savepoint) + */ + void rollbackToSavepoint(Object savepoint) throws TransactionException; + + /** + * Explicitly release the given savepoint. + *

Note that most transaction managers will automatically release + * savepoints at transaction completion. + *

Implementations should fail as silently as possible if + * proper resource cleanup will still happen at transaction completion. + * @param savepoint the savepoint to release + * @throws NestedTransactionNotSupportedException if the underlying + * transaction does not support savepoints + * @throws TransactionException if the release failed + * @see java.sql.Connection#releaseSavepoint + */ + void releaseSavepoint(Object savepoint) throws TransactionException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/TransactionDefinition.java b/fine-spring/src/com/fr/third/springframework/transaction/TransactionDefinition.java new file mode 100644 index 000000000..e034de26d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/TransactionDefinition.java @@ -0,0 +1,258 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +import java.sql.Connection; + +/** + * Interface that defines Spring-compliant transaction properties. + * Based on the propagation behavior definitions analogous to EJB CMT attributes. + * + *

Note that isolation level and timeout settings will not get applied unless + * an actual new transaction gets started. As only {@link #PROPAGATION_REQUIRED}, + * {@link #PROPAGATION_REQUIRES_NEW} and {@link #PROPAGATION_NESTED} can cause + * that, it usually doesn't make sense to specify those settings in other cases. + * Furthermore, be aware that not all transaction managers will support those + * advanced features and thus might throw corresponding exceptions when given + * non-default values. + * + *

The {@link #isReadOnly() read-only flag} applies to any transaction context, + * whether backed by an actual resource transaction or operating non-transactionally + * at the resource level. In the latter case, the flag will only apply to managed + * resources within the application, such as a Hibernate {@code Session}. + * + * @author Juergen Hoeller + * @since 08.05.2003 + * @see PlatformTransactionManager#getTransaction(TransactionDefinition) + * @see com.fr.third.springframework.transaction.support.DefaultTransactionDefinition + * @see com.fr.third.springframework.transaction.interceptor.TransactionAttribute + */ +public interface TransactionDefinition { + + /** + * Support a current transaction; create a new one if none exists. + * Analogous to the EJB transaction attribute of the same name. + *

This is typically the default setting of a transaction definition, + * and typically defines a transaction synchronization scope. + */ + int PROPAGATION_REQUIRED = 0; + + /** + * Support a current transaction; execute non-transactionally if none exists. + * Analogous to the EJB transaction attribute of the same name. + *

NOTE: For transaction managers with transaction synchronization, + * {@code PROPAGATION_SUPPORTS} is slightly different from no transaction + * at all, as it defines a transaction scope that synchronization might apply to. + * As a consequence, the same resources (a JDBC {@code Connection}, a + * Hibernate {@code Session}, etc) will be shared for the entire specified + * scope. Note that the exact behavior depends on the actual synchronization + * configuration of the transaction manager! + *

In general, use {@code PROPAGATION_SUPPORTS} with care! In particular, do + * not rely on {@code PROPAGATION_REQUIRED} or {@code PROPAGATION_REQUIRES_NEW} + * within a {@code PROPAGATION_SUPPORTS} scope (which may lead to + * synchronization conflicts at runtime). If such nesting is unavoidable, make sure + * to configure your transaction manager appropriately (typically switching to + * "synchronization on actual transaction"). + * @see com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization + * @see com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager#SYNCHRONIZATION_ON_ACTUAL_TRANSACTION + */ + int PROPAGATION_SUPPORTS = 1; + + /** + * Support a current transaction; throw an exception if no current transaction + * exists. Analogous to the EJB transaction attribute of the same name. + *

Note that transaction synchronization within a {@code PROPAGATION_MANDATORY} + * scope will always be driven by the surrounding transaction. + */ + int PROPAGATION_MANDATORY = 2; + + /** + * Create a new transaction, suspending the current transaction if one exists. + * Analogous to the EJB transaction attribute of the same name. + *

NOTE: Actual transaction suspension will not work out-of-the-box + * on all transaction managers. This in particular applies to + * {@link com.fr.third.springframework.transaction.jta.JtaTransactionManager}, + * which requires the {@code javax.transaction.TransactionManager} + * to be made available it to it (which is server-specific in standard J2EE). + *

A {@code PROPAGATION_REQUIRES_NEW} scope always defines its own + * transaction synchronizations. Existing synchronizations will be suspended + * and resumed appropriately. + * @see com.fr.third.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + int PROPAGATION_REQUIRES_NEW = 3; + + /** + * Do not support a current transaction; rather always execute non-transactionally. + * Analogous to the EJB transaction attribute of the same name. + *

NOTE: Actual transaction suspension will not work out-of-the-box + * on all transaction managers. This in particular applies to + * {@link com.fr.third.springframework.transaction.jta.JtaTransactionManager}, + * which requires the {@code javax.transaction.TransactionManager} + * to be made available it to it (which is server-specific in standard J2EE). + *

Note that transaction synchronization is not available within a + * {@code PROPAGATION_NOT_SUPPORTED} scope. Existing synchronizations + * will be suspended and resumed appropriately. + * @see com.fr.third.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + int PROPAGATION_NOT_SUPPORTED = 4; + + /** + * Do not support a current transaction; throw an exception if a current transaction + * exists. Analogous to the EJB transaction attribute of the same name. + *

Note that transaction synchronization is not available within a + * {@code PROPAGATION_NEVER} scope. + */ + int PROPAGATION_NEVER = 5; + + /** + * Execute within a nested transaction if a current transaction exists, + * behave like {@link #PROPAGATION_REQUIRED} else. There is no analogous + * feature in EJB. + *

NOTE: Actual creation of a nested transaction will only work on + * specific transaction managers. Out of the box, this only applies to the JDBC + * {@link com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager} + * when working on a JDBC 3.0 driver. Some JTA providers might support + * nested transactions as well. + * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager + */ + int PROPAGATION_NESTED = 6; + + + /** + * Use the default isolation level of the underlying datastore. + * All other levels correspond to the JDBC isolation levels. + * @see java.sql.Connection + */ + int ISOLATION_DEFAULT = -1; + + /** + * Indicates that dirty reads, non-repeatable reads and phantom reads + * can occur. + *

This level allows a row changed by one transaction to be read by another + * transaction before any changes in that row have been committed (a "dirty read"). + * If any of the changes are rolled back, the second transaction will have + * retrieved an invalid row. + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + */ + int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; + + /** + * Indicates that dirty reads are prevented; non-repeatable reads and + * phantom reads can occur. + *

This level only prohibits a transaction from reading a row + * with uncommitted changes in it. + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + */ + int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; + + /** + * Indicates that dirty reads and non-repeatable reads are prevented; + * phantom reads can occur. + *

This level prohibits a transaction from reading a row with uncommitted changes + * in it, and it also prohibits the situation where one transaction reads a row, + * a second transaction alters the row, and the first transaction re-reads the row, + * getting different values the second time (a "non-repeatable read"). + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + */ + int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; + + /** + * Indicates that dirty reads, non-repeatable reads and phantom reads + * are prevented. + *

This level includes the prohibitions in {@link #ISOLATION_REPEATABLE_READ} + * and further prohibits the situation where one transaction reads all rows that + * satisfy a {@code WHERE} condition, a second transaction inserts a row + * that satisfies that {@code WHERE} condition, and the first transaction + * re-reads for the same condition, retrieving the additional "phantom" row + * in the second read. + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + */ + int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; + + + /** + * Use the default timeout of the underlying transaction system, + * or none if timeouts are not supported. + */ + int TIMEOUT_DEFAULT = -1; + + + /** + * Return the propagation behavior. + *

Must return one of the {@code PROPAGATION_XXX} constants + * defined on {@link TransactionDefinition this interface}. + * @return the propagation behavior + * @see #PROPAGATION_REQUIRED + * @see com.fr.third.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive() + */ + int getPropagationBehavior(); + + /** + * Return the isolation level. + *

Must return one of the {@code ISOLATION_XXX} constants + * defined on {@link TransactionDefinition this interface}. + *

Only makes sense in combination with {@link #PROPAGATION_REQUIRED} + * or {@link #PROPAGATION_REQUIRES_NEW}. + *

Note that a transaction manager that does not support custom isolation levels + * will throw an exception when given any other level than {@link #ISOLATION_DEFAULT}. + * @return the isolation level + */ + int getIsolationLevel(); + + /** + * Return the transaction timeout. + *

Must return a number of seconds, or {@link #TIMEOUT_DEFAULT}. + *

Only makes sense in combination with {@link #PROPAGATION_REQUIRED} + * or {@link #PROPAGATION_REQUIRES_NEW}. + *

Note that a transaction manager that does not support timeouts will throw + * an exception when given any other timeout than {@link #TIMEOUT_DEFAULT}. + * @return the transaction timeout + */ + int getTimeout(); + + /** + * Return whether to optimize as a read-only transaction. + *

The read-only flag applies to any transaction context, whether + * backed by an actual resource transaction + * ({@link #PROPAGATION_REQUIRED}/{@link #PROPAGATION_REQUIRES_NEW}) or + * operating non-transactionally at the resource level + * ({@link #PROPAGATION_SUPPORTS}). In the latter case, the flag will + * only apply to managed resources within the application, such as a + * Hibernate {@code Session}. + << *

This just serves as a hint for the actual transaction subsystem; + * it will not necessarily cause failure of write access attempts. + * A transaction manager which cannot interpret the read-only hint will + * not throw an exception when asked for a read-only transaction. + * @return {@code true} if the transaction is to be optimized as read-only + * @see com.fr.third.springframework.transaction.support.TransactionSynchronization#beforeCommit(boolean) + * @see com.fr.third.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly() + */ + boolean isReadOnly(); + + /** + * Return the name of this transaction. Can be {@code null}. + *

This will be used as the transaction name to be shown in a + * transaction monitor, if applicable (for example, WebLogic's). + *

In case of Spring's declarative transactions, the exposed name will be + * the {@code fully-qualified class name + "." + method name} (by default). + * @return the name of this transaction + * @see com.fr.third.springframework.transaction.interceptor.TransactionAspectSupport + * @see com.fr.third.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionName() + */ + String getName(); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/TransactionException.java b/fine-spring/src/com/fr/third/springframework/transaction/TransactionException.java new file mode 100644 index 000000000..3bbeb1ab4 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/TransactionException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +import com.fr.third.springframework.core.NestedRuntimeException; + +/** + * Superclass for all transaction exceptions. + * + * @author Rod Johnson + * @since 17.03.2003 + */ +@SuppressWarnings("serial") +public abstract class TransactionException extends NestedRuntimeException { + + /** + * Constructor for TransactionException. + * @param msg the detail message + */ + public TransactionException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/TransactionStatus.java b/fine-spring/src/com/fr/third/springframework/transaction/TransactionStatus.java new file mode 100644 index 000000000..0d1c28370 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/TransactionStatus.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +import java.io.Flushable; + +/** + * Representation of the status of a transaction. + * + *

Transactional code can use this to retrieve status information, + * and to programmatically request a rollback (instead of throwing + * an exception that causes an implicit rollback). + * + *

Derives from the SavepointManager interface to provide access + * to savepoint management facilities. Note that savepoint management + * is only available if supported by the underlying transaction manager. + * + * @author Juergen Hoeller + * @since 27.03.2003 + * @see #setRollbackOnly() + * @see PlatformTransactionManager#getTransaction + * @see com.fr.third.springframework.transaction.support.TransactionCallback#doInTransaction + * @see com.fr.third.springframework.transaction.interceptor.TransactionInterceptor#currentTransactionStatus() + */ +public interface TransactionStatus extends SavepointManager, Flushable { + + /** + * Return whether the present transaction is new (else participating + * in an existing transaction, or potentially not running in an + * actual transaction in the first place). + */ + boolean isNewTransaction(); + + /** + * Return whether this transaction internally carries a savepoint, + * that is, has been created as nested transaction based on a savepoint. + *

This method is mainly here for diagnostic purposes, alongside + * {@link #isNewTransaction()}. For programmatic handling of custom + * savepoints, use SavepointManager's operations. + * @see #isNewTransaction() + * @see #createSavepoint + * @see #rollbackToSavepoint(Object) + * @see #releaseSavepoint(Object) + */ + boolean hasSavepoint(); + + /** + * Set the transaction rollback-only. This instructs the transaction manager + * that the only possible outcome of the transaction may be a rollback, as + * alternative to throwing an exception which would in turn trigger a rollback. + *

This is mainly intended for transactions managed by + * {@link com.fr.third.springframework.transaction.support.TransactionTemplate} or + * {@link com.fr.third.springframework.transaction.interceptor.TransactionInterceptor}, + * where the actual commit/rollback decision is made by the container. + * @see com.fr.third.springframework.transaction.support.TransactionCallback#doInTransaction + * @see com.fr.third.springframework.transaction.interceptor.TransactionAttribute#rollbackOn + */ + void setRollbackOnly(); + + /** + * Return whether the transaction has been marked as rollback-only + * (either by the application or by the transaction infrastructure). + */ + boolean isRollbackOnly(); + + /** + * Flush the underlying session to the datastore, if applicable: + * for example, all affected Hibernate/JPA sessions. + */ + @Override + void flush(); + + /** + * Return whether this transaction is completed, that is, + * whether it has already been committed or rolled back. + * @see PlatformTransactionManager#commit + * @see PlatformTransactionManager#rollback + */ + boolean isCompleted(); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/TransactionSuspensionNotSupportedException.java b/fine-spring/src/com/fr/third/springframework/transaction/TransactionSuspensionNotSupportedException.java new file mode 100644 index 000000000..f711d87a2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/TransactionSuspensionNotSupportedException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Exception thrown when attempting to suspend an existing transaction + * but transaction suspension is not supported by the underlying backend. + * + * @author Juergen Hoeller + * @since 1.1 + */ +@SuppressWarnings("serial") +public class TransactionSuspensionNotSupportedException extends CannotCreateTransactionException { + + /** + * Constructor for TransactionSuspensionNotSupportedException. + * @param msg the detail message + */ + public TransactionSuspensionNotSupportedException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionSuspensionNotSupportedException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionSuspensionNotSupportedException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/TransactionSystemException.java b/fine-spring/src/com/fr/third/springframework/transaction/TransactionSystemException.java new file mode 100644 index 000000000..09957bc5d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/TransactionSystemException.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +import com.fr.third.springframework.util.Assert; + +/** + * Exception thrown when a general transaction system error is encountered, + * like on commit or rollback. + * + * @author Juergen Hoeller + * @since 24.03.2003 + */ +@SuppressWarnings("serial") +public class TransactionSystemException extends TransactionException { + + private Throwable applicationException; + + + /** + * Constructor for TransactionSystemException. + * @param msg the detail message + */ + public TransactionSystemException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionSystemException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionSystemException(String msg, Throwable cause) { + super(msg, cause); + } + + + /** + * Set an application exception that was thrown before this transaction exception, + * preserving the original exception despite the overriding TransactionSystemException. + * @param ex the application exception + * @throws IllegalStateException if this TransactionSystemException already holds an + * application exception + */ + public void initApplicationException(Throwable ex) { + Assert.notNull(ex, "Application exception must not be null"); + if (this.applicationException != null) { + throw new IllegalStateException("Already holding an application exception: " + this.applicationException); + } + this.applicationException = ex; + } + + /** + * Return the application exception that was thrown before this transaction exception, + * if any. + * @return the application exception, or {@code null} if none set + */ + public final Throwable getApplicationException() { + return this.applicationException; + } + + /** + * Return the exception that was the first to be thrown within the failed transaction: + * i.e. the application exception, if any, or the TransactionSystemException's own cause. + * @return the original exception, or {@code null} if there was none + */ + public Throwable getOriginalException() { + return (this.applicationException != null ? this.applicationException : getCause()); + } + + @Override + public boolean contains(Class exType) { + return super.contains(exType) || (exType != null && exType.isInstance(this.applicationException)); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/TransactionTimedOutException.java b/fine-spring/src/com/fr/third/springframework/transaction/TransactionTimedOutException.java new file mode 100644 index 000000000..f30324180 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/TransactionTimedOutException.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Exception to be thrown when a transaction has timed out. + * + *

Thrown by Spring's local transaction strategies if the deadline + * for a transaction has been reached when an operation is attempted, + * according to the timeout specified for the given transaction. + * + *

Beyond such checks before each transactional operation, Spring's + * local transaction strategies will also pass appropriate timeout values + * to resource operations (for example to JDBC Statements, letting the JDBC + * driver respect the timeout). Such operations will usually throw native + * resource exceptions (for example, JDBC SQLExceptions) if their operation + * timeout has been exceeded, to be converted to Spring's DataAccessException + * in the respective DAO (which might use Spring's JdbcTemplate, for example). + * + *

In a JTA environment, it is up to the JTA transaction coordinator + * to apply transaction timeouts. Usually, the corresponding JTA-aware + * connection pool will perform timeout checks and throw corresponding + * native resource exceptions (for example, JDBC SQLExceptions). + * + * @author Juergen Hoeller + * @since 1.1.5 + * @see com.fr.third.springframework.transaction.support.ResourceHolderSupport#getTimeToLiveInMillis + * @see java.sql.Statement#setQueryTimeout + * @see java.sql.SQLException + */ +@SuppressWarnings("serial") +public class TransactionTimedOutException extends TransactionException { + + /** + * Constructor for TransactionTimedOutException. + * @param msg the detail message + */ + public TransactionTimedOutException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionTimedOutException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionTimedOutException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/TransactionUsageException.java b/fine-spring/src/com/fr/third/springframework/transaction/TransactionUsageException.java new file mode 100644 index 000000000..84a25d9dd --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/TransactionUsageException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Superclass for exceptions caused by inappropriate usage of + * a Spring transaction API. + * + * @author Rod Johnson + * @since 22.03.2003 + */ +@SuppressWarnings("serial") +public class TransactionUsageException extends TransactionException { + + /** + * Constructor for TransactionUsageException. + * @param msg the detail message + */ + public TransactionUsageException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionUsageException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionUsageException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/UnexpectedRollbackException.java b/fine-spring/src/com/fr/third/springframework/transaction/UnexpectedRollbackException.java new file mode 100644 index 000000000..355e5e8e6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/UnexpectedRollbackException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction; + +/** + * Thrown when an attempt to commit a transaction resulted + * in an unexpected rollback. + * + * @author Rod Johnson + * @since 17.03.2003 + */ +@SuppressWarnings("serial") +public class UnexpectedRollbackException extends TransactionException { + + /** + * Constructor for UnexpectedRollbackException. + * @param msg the detail message + */ + public UnexpectedRollbackException(String msg) { + super(msg); + } + + /** + * Constructor for UnexpectedRollbackException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public UnexpectedRollbackException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java new file mode 100644 index 000000000..42e770fb8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import java.util.Collection; + +import com.fr.third.springframework.beans.factory.annotation.Autowired; +import com.fr.third.springframework.context.annotation.Configuration; +import com.fr.third.springframework.context.annotation.ImportAware; +import com.fr.third.springframework.core.annotation.AnnotationAttributes; +import com.fr.third.springframework.core.type.AnnotationMetadata; +import com.fr.third.springframework.transaction.PlatformTransactionManager; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.CollectionUtils; + +/** + * Abstract base {@code @Configuration} class providing common structure for enabling + * Spring's annotation-driven transaction management capability. + * + * @author Chris Beams + * @since 3.1 + * @see EnableTransactionManagement + */ +@Configuration +public abstract class AbstractTransactionManagementConfiguration implements ImportAware { + + protected AnnotationAttributes enableTx; + + protected PlatformTransactionManager txManager; + + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + this.enableTx = AnnotationAttributes.fromMap( + importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false)); + Assert.notNull(this.enableTx, + "@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName()); + } + + @Autowired(required=false) + void setConfigurers(Collection configurers) { + if (CollectionUtils.isEmpty(configurers)) { + return; + } + if (configurers.size() > 1) { + throw new IllegalStateException("Only one TransactionManagementConfigurer may exist"); + } + TransactionManagementConfigurer configurer = configurers.iterator().next(); + this.txManager = configurer.annotationDrivenTransactionManager(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java new file mode 100644 index 000000000..0f2327779 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java @@ -0,0 +1,189 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import java.io.Serializable; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import com.fr.third.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource; +import com.fr.third.springframework.transaction.interceptor.TransactionAttribute; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.ClassUtils; + +/** + * Implementation of the + * {@link com.fr.third.springframework.transaction.interceptor.TransactionAttributeSource} + * interface for working with transaction metadata in JDK 1.5+ annotation format. + * + *

This class reads Spring's JDK 1.5+ {@link Transactional} annotation and + * exposes corresponding transaction attributes to Spring's transaction infrastructure. + * Also supports JTA 1.2's {@link javax.transaction.Transactional} and EJB3's + * {@link javax.ejb.TransactionAttribute} annotation (if present). + * This class may also serve as base class for a custom TransactionAttributeSource, + * or get customized through {@link TransactionAnnotationParser} strategies. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.2 + * @see Transactional + * @see TransactionAnnotationParser + * @see SpringTransactionAnnotationParser + * @see Ejb3TransactionAnnotationParser + * @see com.fr.third.springframework.transaction.interceptor.TransactionInterceptor#setTransactionAttributeSource + * @see com.fr.third.springframework.transaction.interceptor.TransactionProxyFactoryBean#setTransactionAttributeSource + */ +@SuppressWarnings("serial") +public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource + implements Serializable { + + private static final boolean jta12Present = ClassUtils.isPresent( + "javax.transaction.Transactional", AnnotationTransactionAttributeSource.class.getClassLoader()); + + private static final boolean ejb3Present = ClassUtils.isPresent( + "javax.ejb.TransactionAttribute", AnnotationTransactionAttributeSource.class.getClassLoader()); + + private final boolean publicMethodsOnly; + + private final Set annotationParsers; + + + /** + * Create a default AnnotationTransactionAttributeSource, supporting + * public methods that carry the {@code Transactional} annotation + * or the EJB3 {@link javax.ejb.TransactionAttribute} annotation. + */ + public AnnotationTransactionAttributeSource() { + this(true); + } + + /** + * Create a custom AnnotationTransactionAttributeSource, supporting + * public methods that carry the {@code Transactional} annotation + * or the EJB3 {@link javax.ejb.TransactionAttribute} annotation. + * @param publicMethodsOnly whether to support public methods that carry + * the {@code Transactional} annotation only (typically for use + * with proxy-based AOP), or protected/private methods as well + * (typically used with AspectJ class weaving) + */ + public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) { + this.publicMethodsOnly = publicMethodsOnly; + this.annotationParsers = new LinkedHashSet(2); + this.annotationParsers.add(new SpringTransactionAnnotationParser()); + if (jta12Present) { + this.annotationParsers.add(new JtaTransactionAnnotationParser()); + } + if (ejb3Present) { + this.annotationParsers.add(new Ejb3TransactionAnnotationParser()); + } + } + + /** + * Create a custom AnnotationTransactionAttributeSource. + * @param annotationParser the TransactionAnnotationParser to use + */ + public AnnotationTransactionAttributeSource(TransactionAnnotationParser annotationParser) { + this.publicMethodsOnly = true; + Assert.notNull(annotationParser, "TransactionAnnotationParser must not be null"); + this.annotationParsers = Collections.singleton(annotationParser); + } + + /** + * Create a custom AnnotationTransactionAttributeSource. + * @param annotationParsers the TransactionAnnotationParsers to use + */ + public AnnotationTransactionAttributeSource(TransactionAnnotationParser... annotationParsers) { + this.publicMethodsOnly = true; + Assert.notEmpty(annotationParsers, "At least one TransactionAnnotationParser needs to be specified"); + Set parsers = new LinkedHashSet(annotationParsers.length); + Collections.addAll(parsers, annotationParsers); + this.annotationParsers = parsers; + } + + /** + * Create a custom AnnotationTransactionAttributeSource. + * @param annotationParsers the TransactionAnnotationParsers to use + */ + public AnnotationTransactionAttributeSource(Set annotationParsers) { + this.publicMethodsOnly = true; + Assert.notEmpty(annotationParsers, "At least one TransactionAnnotationParser needs to be specified"); + this.annotationParsers = annotationParsers; + } + + + @Override + protected TransactionAttribute findTransactionAttribute(Method method) { + return determineTransactionAttribute(method); + } + + @Override + protected TransactionAttribute findTransactionAttribute(Class clazz) { + return determineTransactionAttribute(clazz); + } + + /** + * Determine the transaction attribute for the given method or class. + *

This implementation delegates to configured + * {@link TransactionAnnotationParser TransactionAnnotationParsers} + * for parsing known annotations into Spring's metadata attribute class. + * Returns {@code null} if it's not transactional. + *

Can be overridden to support custom annotations that carry transaction metadata. + * @param ae the annotated method or class + * @return TransactionAttribute the configured transaction attribute, + * or {@code null} if none was found + */ + protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) { + for (TransactionAnnotationParser annotationParser : this.annotationParsers) { + TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae); + if (attr != null) { + return attr; + } + } + return null; + } + + /** + * By default, only public methods can be made transactional. + */ + @Override + protected boolean allowPublicMethodsOnly() { + return this.publicMethodsOnly; + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AnnotationTransactionAttributeSource)) { + return false; + } + AnnotationTransactionAttributeSource otherTas = (AnnotationTransactionAttributeSource) other; + return (this.annotationParsers.equals(otherTas.annotationParsers) && + this.publicMethodsOnly == otherTas.publicMethodsOnly); + } + + @Override + public int hashCode() { + return this.annotationParsers.hashCode(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java new file mode 100644 index 000000000..f74a70c3b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import java.io.Serializable; +import java.lang.reflect.AnnotatedElement; +import javax.ejb.ApplicationException; +import javax.ejb.TransactionAttributeType; + +import com.fr.third.springframework.transaction.interceptor.DefaultTransactionAttribute; +import com.fr.third.springframework.transaction.interceptor.TransactionAttribute; + +/** + * Strategy implementation for parsing EJB3's {@link javax.ejb.TransactionAttribute} + * annotation. + * + * @author Juergen Hoeller + * @since 2.5 + */ +@SuppressWarnings("serial") +public class Ejb3TransactionAnnotationParser implements TransactionAnnotationParser, Serializable { + + @Override + public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { + javax.ejb.TransactionAttribute ann = ae.getAnnotation(javax.ejb.TransactionAttribute.class); + if (ann != null) { + return parseTransactionAnnotation(ann); + } + else { + return null; + } + } + + public TransactionAttribute parseTransactionAnnotation(javax.ejb.TransactionAttribute ann) { + return new Ejb3TransactionAttribute(ann.value()); + } + + @Override + public boolean equals(Object other) { + return (this == other || other instanceof Ejb3TransactionAnnotationParser); + } + + @Override + public int hashCode() { + return Ejb3TransactionAnnotationParser.class.hashCode(); + } + + + /** + * EJB3-specific TransactionAttribute, implementing EJB3's rollback rules + * which are based on annotated exceptions. + */ + private static class Ejb3TransactionAttribute extends DefaultTransactionAttribute { + + public Ejb3TransactionAttribute(TransactionAttributeType type) { + setPropagationBehaviorName(PREFIX_PROPAGATION + type.name()); + } + + @Override + public boolean rollbackOn(Throwable ex) { + ApplicationException ann = ex.getClass().getAnnotation(ApplicationException.class); + return (ann != null ? ann.rollback() : super.rollbackOn(ex)); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/EnableTransactionManagement.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/EnableTransactionManagement.java new file mode 100644 index 000000000..7494a3f7d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/EnableTransactionManagement.java @@ -0,0 +1,171 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.fr.third.springframework.context.annotation.AdviceMode; +import com.fr.third.springframework.context.annotation.Import; +import com.fr.third.springframework.core.Ordered; + +/** + * Enables Spring's annotation-driven transaction management capability, similar to + * the support found in Spring's {@code } XML namespace. To be used + * on @{@link com.fr.third.springframework.context.annotation.Configuration Configuration} classes + * as follows: + *

+ * @Configuration
+ * @EnableTransactionManagement
+ * public class AppConfig {
+ *     @Bean
+ *     public FooRepository fooRepository() {
+ *         // configure and return a class having @Transactional methods
+ *         return new JdbcFooRepository(dataSource());
+ *     }
+ *
+ *     @Bean
+ *     public DataSource dataSource() {
+ *         // configure and return the necessary JDBC DataSource
+ *     }
+ *
+ *     @Bean
+ *     public PlatformTransactionManager txManager() {
+ *         return new DataSourceTransactionManager(dataSource());
+ *     }
+ * }
+ * + *

For reference, the example above can be compared to the following Spring XML + * configuration: + *

+ * {@code
+ * 
+ *     
+ *     
+ *         
+ *     
+ *     
+ *     
+ *         
+ *     
+ * 
+ * }
+ * In both of the scenarios above, {@code @EnableTransactionManagement} and {@code + * } are responsible for registering the necessary Spring + * components that power annotation-driven transaction management, such as the + * TransactionInterceptor and the proxy- or AspectJ-based advice that weave the + * interceptor into the call stack when {@code JdbcFooRepository}'s {@code @Transactional} + * methods are invoked. + * + *

A minor difference between the two examples lies in the naming of the {@code + * PlatformTransactionManager} bean: In the {@code @Bean} case, the name is + * "txManager" (per the name of the method); in the XML case, the name is + * "transactionManager". The {@code } is hard-wired to + * look for a bean named "transactionManager" by default, however + * {@code @EnableTransactionManagement} is more flexible; it will fall back to a by-type + * lookup for any {@code PlatformTransactionManager} bean in the container. Thus the name + * can be "txManager", "transactionManager", or "tm": it simply does not matter. + * + *

For those that wish to establish a more direct relationship between + * {@code @EnableTransactionManagement} and the exact transaction manager bean to be used, + * the {@link TransactionManagementConfigurer} callback interface may be implemented - + * notice the {@code implements} clause and the {@code @Override}-annotated method below: + *

+ * @Configuration
+ * @EnableTransactionManagement
+ * public class AppConfig implements TransactionManagementConfigurer {
+ *     @Bean
+ *     public FooRepository fooRepository() {
+ *         // configure and return a class having @Transactional methods
+ *         return new JdbcFooRepository(dataSource());
+ *     }
+ *
+ *     @Bean
+ *     public DataSource dataSource() {
+ *         // configure and return the necessary JDBC DataSource
+ *     }
+ *
+ *     @Bean
+ *     public PlatformTransactionManager txManager() {
+ *         return new DataSourceTransactionManager(dataSource());
+ *     }
+ *
+ *     @Override
+ *     public PlatformTransactionManager annotationDrivenTransactionManager() {
+ *         return txManager();
+ *     }
+ * }
+ * This approach may be desirable simply because it is more explicit, or it may be + * necessary in order to distinguish between two {@code PlatformTransactionManager} beans + * present in the same container. As the name suggests, the + * {@code annotationDrivenTransactionManager()} will be the one used for processing + * {@code @Transactional} methods. See {@link TransactionManagementConfigurer} Javadoc + * for further details. + * + *

The {@link #mode()} attribute controls how advice is applied; if the mode is + * {@link AdviceMode#PROXY} (the default), then the other attributes control the behavior + * of the proxying. + * + *

If the {@linkplain #mode} is set to {@link AdviceMode#ASPECTJ}, then the + * {@link #proxyTargetClass()} attribute is obsolete. Note also that in this case the + * {@code spring-aspects} module JAR must be present on the classpath. + * + * @author Chris Beams + * @since 3.1 + * @see TransactionManagementConfigurer + * @see TransactionManagementConfigurationSelector + * @see ProxyTransactionManagementConfiguration + * @see com.fr.third.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(TransactionManagementConfigurationSelector.class) +public @interface EnableTransactionManagement { + + /** + * Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true}) as + * opposed to standard Java interface-based proxies ({@code false}). The default is + * {@code false}. Applicable only if {@link #mode()} is set to + * {@link AdviceMode#PROXY}. + *

Note that setting this attribute to {@code true} will affect all + * Spring-managed beans requiring proxying, not just those marked with + * {@code @Transactional}. For example, other beans marked with Spring's + * {@code @Async} annotation will be upgraded to subclass proxying at the same + * time. This approach has no negative impact in practice unless one is explicitly + * expecting one type of proxy vs another, e.g. in tests. + */ + boolean proxyTargetClass() default false; + + /** + * Indicate how transactional advice should be applied. The default is + * {@link AdviceMode#PROXY}. + * @see AdviceMode + */ + AdviceMode mode() default AdviceMode.PROXY; + + /** + * Indicate the ordering of the execution of the transaction advisor + * when multiple advices are applied at a specific joinpoint. + * The default is {@link Ordered#LOWEST_PRECEDENCE}. + */ + int order() default Ordered.LOWEST_PRECEDENCE; + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/Isolation.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/Isolation.java new file mode 100644 index 000000000..73c23498b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/Isolation.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import com.fr.third.springframework.transaction.TransactionDefinition; + +/** + * Enumeration that represents transaction isolation levels for use + * with the {@link Transactional} annotation, corresponding to the + * {@link TransactionDefinition} interface. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.2 + */ +public enum Isolation { + + /** + * Use the default isolation level of the underlying datastore. + * All other levels correspond to the JDBC isolation levels. + * @see java.sql.Connection + */ + DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), + + /** + * A constant indicating that dirty reads, non-repeatable reads and phantom reads + * can occur. This level allows a row changed by one transaction to be read by + * another transaction before any changes in that row have been committed + * (a "dirty read"). If any of the changes are rolled back, the second + * transaction will have retrieved an invalid row. + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + */ + READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), + + /** + * A constant indicating that dirty reads are prevented; non-repeatable reads + * and phantom reads can occur. This level only prohibits a transaction + * from reading a row with uncommitted changes in it. + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + */ + READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), + + /** + * A constant indicating that dirty reads and non-repeatable reads are + * prevented; phantom reads can occur. This level prohibits a transaction + * from reading a row with uncommitted changes in it, and it also prohibits + * the situation where one transaction reads a row, a second transaction + * alters the row, and the first transaction rereads the row, getting + * different values the second time (a "non-repeatable read"). + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + */ + REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), + + /** + * A constant indicating that dirty reads, non-repeatable reads and phantom + * reads are prevented. This level includes the prohibitions in + * {@code ISOLATION_REPEATABLE_READ} and further prohibits the situation + * where one transaction reads all rows that satisfy a {@code WHERE} + * condition, a second transaction inserts a row that satisfies that + * {@code WHERE} condition, and the first transaction rereads for the + * same condition, retrieving the additional "phantom" row in the second read. + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + */ + SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); + + + private final int value; + + + Isolation(int value) { this.value = value; } + + public int value() { return this.value; } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/JtaTransactionAnnotationParser.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/JtaTransactionAnnotationParser.java new file mode 100644 index 000000000..adf766f1a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/JtaTransactionAnnotationParser.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import java.io.Serializable; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; + +import com.fr.third.springframework.core.annotation.AnnotationAttributes; +import com.fr.third.springframework.core.annotation.AnnotationUtils; +import com.fr.third.springframework.core.annotation.AnnotatedElementUtils; +import com.fr.third.springframework.transaction.interceptor.NoRollbackRuleAttribute; +import com.fr.third.springframework.transaction.interceptor.RollbackRuleAttribute; +import com.fr.third.springframework.transaction.interceptor.RuleBasedTransactionAttribute; +import com.fr.third.springframework.transaction.interceptor.TransactionAttribute; + +/** + * Strategy implementation for parsing JTA 1.2's {@link javax.transaction.Transactional} annotation. + * + * @author Juergen Hoeller + * @since 4.0 + */ +@SuppressWarnings("serial") +public class JtaTransactionAnnotationParser implements TransactionAnnotationParser, Serializable { + + @Override + public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { + AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, javax.transaction.Transactional.class.getName()); + if (ann != null) { + return parseTransactionAnnotation(ann); + } + else { + return null; + } + } + + public TransactionAttribute parseTransactionAnnotation(javax.transaction.Transactional ann) { + return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false)); + } + + protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) { + RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); + rbta.setPropagationBehaviorName( + RuleBasedTransactionAttribute.PREFIX_PROPAGATION + attributes.getEnum("value").toString()); + ArrayList rollBackRules = new ArrayList(); + Class[] rbf = attributes.getClassArray("rollbackOn"); + for (Class rbRule : rbf) { + RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule); + rollBackRules.add(rule); + } + Class[] nrbf = attributes.getClassArray("dontRollbackOn"); + for (Class rbRule : nrbf) { + NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule); + rollBackRules.add(rule); + } + rbta.getRollbackRules().addAll(rollBackRules); + return rbta; + } + + @Override + public boolean equals(Object other) { + return (this == other || other instanceof JtaTransactionAnnotationParser); + } + + @Override + public int hashCode() { + return JtaTransactionAnnotationParser.class.hashCode(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/Propagation.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/Propagation.java new file mode 100644 index 000000000..52cd4f312 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/Propagation.java @@ -0,0 +1,105 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import com.fr.third.springframework.transaction.TransactionDefinition; + +/** + * Enumeration that represents transaction propagation behaviors for use + * with the {@link Transactional} annotation, corresponding to the + * {@link TransactionDefinition} interface. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.2 + */ +public enum Propagation { + + /** + * Support a current transaction, create a new one if none exists. + * Analogous to EJB transaction attribute of the same name. + *

This is the default setting of a transaction annotation. + */ + REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), + + /** + * Support a current transaction, execute non-transactionally if none exists. + * Analogous to EJB transaction attribute of the same name. + *

Note: For transaction managers with transaction synchronization, + * PROPAGATION_SUPPORTS is slightly different from no transaction at all, + * as it defines a transaction scope that synchronization will apply for. + * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) + * will be shared for the entire specified scope. Note that this depends on + * the actual synchronization configuration of the transaction manager. + * @see com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization + */ + SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), + + /** + * Support a current transaction, throw an exception if none exists. + * Analogous to EJB transaction attribute of the same name. + */ + MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), + + /** + * Create a new transaction, suspend the current transaction if one exists. + * Analogous to EJB transaction attribute of the same name. + *

Note: Actual transaction suspension will not work on out-of-the-box + * on all transaction managers. This in particular applies to JtaTransactionManager, + * which requires the {@code javax.transaction.TransactionManager} to be + * made available it to it (which is server-specific in standard J2EE). + * @see com.fr.third.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), + + /** + * Execute non-transactionally, suspend the current transaction if one exists. + * Analogous to EJB transaction attribute of the same name. + *

Note: Actual transaction suspension will not work on out-of-the-box + * on all transaction managers. This in particular applies to JtaTransactionManager, + * which requires the {@code javax.transaction.TransactionManager} to be + * made available it to it (which is server-specific in standard J2EE). + * @see com.fr.third.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), + + /** + * Execute non-transactionally, throw an exception if a transaction exists. + * Analogous to EJB transaction attribute of the same name. + */ + NEVER(TransactionDefinition.PROPAGATION_NEVER), + + /** + * Execute within a nested transaction if a current transaction exists, + * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB. + *

Note: Actual creation of a nested transaction will only work on specific + * transaction managers. Out of the box, this only applies to the JDBC + * DataSourceTransactionManager when working on a JDBC 3.0 driver. + * Some JTA providers might support nested transactions as well. + * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager + */ + NESTED(TransactionDefinition.PROPAGATION_NESTED); + + + private final int value; + + + Propagation(int value) { this.value = value; } + + public int value() { return this.value; } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java new file mode 100644 index 000000000..74fe8c1ec --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import com.fr.third.springframework.beans.factory.config.BeanDefinition; +import com.fr.third.springframework.context.annotation.Bean; +import com.fr.third.springframework.context.annotation.Configuration; +import com.fr.third.springframework.context.annotation.Role; +import com.fr.third.springframework.transaction.config.TransactionManagementConfigUtils; +import com.fr.third.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor; +import com.fr.third.springframework.transaction.interceptor.TransactionAttributeSource; +import com.fr.third.springframework.transaction.interceptor.TransactionInterceptor; + +/** + * {@code @Configuration} class that registers the Spring infrastructure beans necessary + * to enable proxy-based annotation-driven transaction management. + * + * @author Chris Beams + * @since 3.1 + * @see EnableTransactionManagement + * @see TransactionManagementConfigurationSelector + */ +@Configuration +public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { + + @Bean(name=TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() { + BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); + advisor.setTransactionAttributeSource(transactionAttributeSource()); + advisor.setAdvice(transactionInterceptor()); + advisor.setOrder(this.enableTx.getNumber("order")); + return advisor; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionAttributeSource transactionAttributeSource() { + return new AnnotationTransactionAttributeSource(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionInterceptor transactionInterceptor() { + TransactionInterceptor interceptor = new TransactionInterceptor(); + interceptor.setTransactionAttributeSource(transactionAttributeSource()); + if (this.txManager != null) { + interceptor.setTransactionManager(this.txManager); + } + return interceptor; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/SpringTransactionAnnotationParser.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/SpringTransactionAnnotationParser.java new file mode 100644 index 000000000..f596cbc27 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/SpringTransactionAnnotationParser.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import java.io.Serializable; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; + +import com.fr.third.springframework.core.annotation.AnnotationAttributes; +import com.fr.third.springframework.core.annotation.AnnotationUtils; +import com.fr.third.springframework.core.annotation.AnnotatedElementUtils; +import com.fr.third.springframework.transaction.interceptor.NoRollbackRuleAttribute; +import com.fr.third.springframework.transaction.interceptor.RollbackRuleAttribute; +import com.fr.third.springframework.transaction.interceptor.RuleBasedTransactionAttribute; +import com.fr.third.springframework.transaction.interceptor.TransactionAttribute; + +/** + * Strategy implementation for parsing Spring's {@link Transactional} annotation. + * + * @author Juergen Hoeller + * @since 2.5 + */ +@SuppressWarnings("serial") +public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable { + + @Override + public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { + AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName()); + if (ann != null) { + return parseTransactionAnnotation(ann); + } + else { + return null; + } + } + + public TransactionAttribute parseTransactionAnnotation(Transactional ann) { + return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false)); + } + + protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) { + RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); + Propagation propagation = attributes.getEnum("propagation"); + rbta.setPropagationBehavior(propagation.value()); + Isolation isolation = attributes.getEnum("isolation"); + rbta.setIsolationLevel(isolation.value()); + rbta.setTimeout(attributes.getNumber("timeout").intValue()); + rbta.setReadOnly(attributes.getBoolean("readOnly")); + rbta.setQualifier(attributes.getString("value")); + ArrayList rollBackRules = new ArrayList(); + Class[] rbf = attributes.getClassArray("rollbackFor"); + for (Class rbRule : rbf) { + RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule); + rollBackRules.add(rule); + } + String[] rbfc = attributes.getStringArray("rollbackForClassName"); + for (String rbRule : rbfc) { + RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule); + rollBackRules.add(rule); + } + Class[] nrbf = attributes.getClassArray("noRollbackFor"); + for (Class rbRule : nrbf) { + NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule); + rollBackRules.add(rule); + } + String[] nrbfc = attributes.getStringArray("noRollbackForClassName"); + for (String rbRule : nrbfc) { + NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule); + rollBackRules.add(rule); + } + rbta.getRollbackRules().addAll(rollBackRules); + return rbta; + } + + @Override + public boolean equals(Object other) { + return (this == other || other instanceof SpringTransactionAnnotationParser); + } + + @Override + public int hashCode() { + return SpringTransactionAnnotationParser.class.hashCode(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/TransactionAnnotationParser.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/TransactionAnnotationParser.java new file mode 100644 index 000000000..09a6e8aaf --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/TransactionAnnotationParser.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import java.lang.reflect.AnnotatedElement; + +import com.fr.third.springframework.transaction.interceptor.TransactionAttribute; + +/** + * Strategy interface for parsing known transaction annotation types. + * {@link AnnotationTransactionAttributeSource} delegates to such + * parsers for supporting specific annotation types such as Spring's own + * {@link Transactional}, JTA 1.2's {@link javax.transaction.Transactional} + * or EJB3's {@link javax.ejb.TransactionAttribute}. + * + * @author Juergen Hoeller + * @since 2.5 + * @see AnnotationTransactionAttributeSource + * @see SpringTransactionAnnotationParser + * @see Ejb3TransactionAnnotationParser + * @see JtaTransactionAnnotationParser + */ +public interface TransactionAnnotationParser { + + /** + * Parse the transaction attribute for the given method or class, + * based on a known annotation type. + *

This essentially parses a known transaction annotation into Spring's + * metadata attribute class. Returns {@code null} if the method/class + * is not transactional. + * @param ae the annotated method or class + * @return TransactionAttribute the configured transaction attribute, + * or {@code null} if none was found + * @see AnnotationTransactionAttributeSource#determineTransactionAttribute + */ + TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java new file mode 100644 index 000000000..063476d26 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import com.fr.third.springframework.context.annotation.AdviceMode; +import com.fr.third.springframework.context.annotation.AdviceModeImportSelector; +import com.fr.third.springframework.context.annotation.AutoProxyRegistrar; +import com.fr.third.springframework.transaction.config.TransactionManagementConfigUtils; + +/** + * Selects which implementation of {@link AbstractTransactionManagementConfiguration} + * should be used based on the value of {@link EnableTransactionManagement#mode} on the + * importing {@code @Configuration} class. + * + * @author Chris Beams + * @since 3.1 + * @see EnableTransactionManagement + * @see ProxyTransactionManagementConfiguration + * @see TransactionManagementConfigUtils#TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME + */ +public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector { + + /** + * {@inheritDoc} + * @return {@link ProxyTransactionManagementConfiguration} or + * {@code AspectJTransactionManagementConfiguration} for {@code PROXY} and + * {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()}, respectively + */ + @Override + protected String[] selectImports(AdviceMode adviceMode) { + switch (adviceMode) { + case PROXY: + return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; + case ASPECTJ: + return new String[] {TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME}; + default: + return null; + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/TransactionManagementConfigurer.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/TransactionManagementConfigurer.java new file mode 100644 index 000000000..a25b61878 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/TransactionManagementConfigurer.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import com.fr.third.springframework.transaction.PlatformTransactionManager; + +/** + * Interface to be implemented by @{@link com.fr.third.springframework.context.annotation.Configuration + * Configuration} classes annotated with @{@link EnableTransactionManagement} that wish + * or need to specify explicitly the {@link PlatformTransactionManager} bean to be used + * for annotation-driven transaction management, as opposed to the default approach of a + * by-type lookup. One reason this might be necessary is if there are two + * {@code PlatformTransactionManager} beans present in the container. + * + *

See @{@link EnableTransactionManagement} for general examples and context; see + * {@link #annotationDrivenTransactionManager()} for detailed instructions. + * + *

Note that in by-type lookup disambiguation cases, an alternative approach to + * implementing this interface is to simply mark one of the offending {@code + * PlatformTransactionManager} {@code @Bean} methods as @{@link + * com.fr.third.springframework.context.annotation.Primary Primary}. + * + * @author Chris Beams + * @since 3.1 + * @see EnableTransactionManagement + * @see com.fr.third.springframework.context.annotation.Primary + */ +public interface TransactionManagementConfigurer { + + /** + * Return the transaction manager bean to use for annotation-driven database + * transaction management, i.e. when processing {@code @Transactional} methods. + * + *

There are two basic approaches to implementing this method: + *

1. Implement the method and annotate it with {@code @Bean}

+ * In this case, the implementing {@code @Configuration} class implements this method, + * marks it with {@code @Bean} and configures and returns the transaction manager + * directly within the method body: + *
+	 * @Bean
+	 * @Override
+	 * public PlatformTransactionManager annotationDrivenTransactionManager() {
+	 *     return new DataSourceTransactionManager(dataSource());
+	 * }
+ *

2. Implement the method without {@code @Bean} and delegate to another existing + * {@code @Bean} method

+ *
+	 * @Bean
+	 * public PlatformTransactionManager txManager() {
+	 *     return new DataSourceTransactionManager(dataSource());
+	 * }
+	 *
+	 * @Override
+	 * public PlatformTransactionManager annotationDrivenTransactionManager() {
+	 *     return txManager(); // reference the existing {@code @Bean} method above
+	 * }
+ * + * If taking approach #2, be sure that only one of the methods is marked with + * {@code @Bean}! + * + *

In either scenario #1 or #2, it is important that the + * {@code PlatformTransactionManager} instance is managed as a Spring bean within the + * container as all {@code PlatformTransactionManager} implementations take + * advantage of Spring lifecycle callbacks such as {@code InitializingBean} and {@code + * BeanFactoryAware}. + */ + PlatformTransactionManager annotationDrivenTransactionManager(); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/Transactional.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/Transactional.java new file mode 100644 index 000000000..72c3434e5 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/Transactional.java @@ -0,0 +1,145 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.fr.third.springframework.transaction.TransactionDefinition; + +/** + * Describes transaction attributes on a method or class. + * + *

This annotation type is generally directly comparable to Spring's + * {@link com.fr.third.springframework.transaction.interceptor.RuleBasedTransactionAttribute} + * class, and in fact {@link AnnotationTransactionAttributeSource} will directly + * convert the data to the latter class, so that Spring's transaction support code + * does not have to know about annotations. If no rules are relevant to the exception, + * it will be treated like + * {@link com.fr.third.springframework.transaction.interceptor.DefaultTransactionAttribute} + * (rolling back on runtime exceptions). + * + *

For specific information about the semantics of this annotation's attributes, + * consider the {@link com.fr.third.springframework.transaction.TransactionDefinition} and + * {@link com.fr.third.springframework.transaction.interceptor.TransactionAttribute} javadocs. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.2 + * @see com.fr.third.springframework.transaction.interceptor.TransactionAttribute + * @see com.fr.third.springframework.transaction.interceptor.DefaultTransactionAttribute + * @see com.fr.third.springframework.transaction.interceptor.RuleBasedTransactionAttribute + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Transactional { + + /** + * A qualifier value for the specified transaction. + *

May be used to determine the target transaction manager, + * matching the qualifier value (or the bean name) of a specific + * {@link com.fr.third.springframework.transaction.PlatformTransactionManager} + * bean definition. + */ + String value() default ""; + + /** + * The transaction propagation type. + * Defaults to {@link Propagation#REQUIRED}. + * @see com.fr.third.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior() + */ + Propagation propagation() default Propagation.REQUIRED; + + /** + * The transaction isolation level. + * Defaults to {@link Isolation#DEFAULT}. + * @see com.fr.third.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel() + */ + Isolation isolation() default Isolation.DEFAULT; + + /** + * The timeout for this transaction. + * Defaults to the default timeout of the underlying transaction system. + * @see com.fr.third.springframework.transaction.interceptor.TransactionAttribute#getTimeout() + */ + int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; + + /** + * {@code true} if the transaction is read-only. + * Defaults to {@code false}. + *

This just serves as a hint for the actual transaction subsystem; + * it will not necessarily cause failure of write access attempts. + * A transaction manager which cannot interpret the read-only hint will + * not throw an exception when asked for a read-only transaction. + * @see com.fr.third.springframework.transaction.interceptor.TransactionAttribute#isReadOnly() + */ + boolean readOnly() default false; + + /** + * Defines zero (0) or more exception {@link Class classes}, which must be a + * subclass of {@link Throwable}, indicating which exception types must cause + * a transaction rollback. + *

This is the preferred way to construct a rollback rule, matching the + * exception class and subclasses. + *

Similar to {@link com.fr.third.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)} + */ + Class[] rollbackFor() default {}; + + /** + * Defines zero (0) or more exception names (for exceptions which must be a + * subclass of {@link Throwable}), indicating which exception types must cause + * a transaction rollback. + *

This can be a substring, with no wildcard support at present. + * A value of "ServletException" would match + * {@link javax.servlet.ServletException} and subclasses, for example. + *

NB: Consider carefully how specific the pattern is, and whether + * to include package information (which isn't mandatory). For example, + * "Exception" will match nearly anything, and will probably hide other rules. + * "java.lang.Exception" would be correct if "Exception" was meant to define + * a rule for all checked exceptions. With more unusual {@link Exception} + * names such as "BaseBusinessException" there is no need to use a FQN. + *

Similar to {@link com.fr.third.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)} + */ + String[] rollbackForClassName() default {}; + + /** + * Defines zero (0) or more exception {@link Class Classes}, which must be a + * subclass of {@link Throwable}, indicating which exception types must not + * cause a transaction rollback. + *

This is the preferred way to construct a rollback rule, matching the + * exception class and subclasses. + *

Similar to {@link com.fr.third.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)} + */ + Class[] noRollbackFor() default {}; + + /** + * Defines zero (0) or more exception names (for exceptions which must be a + * subclass of {@link Throwable}) indicating which exception types must not + * cause a transaction rollback. + *

See the description of {@link #rollbackForClassName()} for more info on how + * the specified names are treated. + *

Similar to {@link com.fr.third.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)} + */ + String[] noRollbackForClassName() default {}; + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/annotation/package-info.java b/fine-spring/src/com/fr/third/springframework/transaction/annotation/package-info.java new file mode 100644 index 000000000..59cff339b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/annotation/package-info.java @@ -0,0 +1,10 @@ + +/** + * + * JDK 1.5+ annotation for transaction demarcation. + * Hooked into Spring's transaction interception infrastructure + * via a special TransactionAttributeSource implementation. + * + */ +package com.fr.third.springframework.transaction.annotation; + diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationExecutor.java b/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationExecutor.java new file mode 100644 index 000000000..fede2cf8c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationExecutor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.transaction.compensating; + +/** + * Responsible for executing a single recorded operation as well as committing + * or rolling it back, depending on the transaction outcome. Instances of this + * interface are constructed by {@link CompensatingTransactionOperationRecorder} + * objects, supplying them with the information necessary for the respective + * operations. + *

+ * The actual operations performed by the respective methods of this class might + * not be what would originally be expected. E.g. one would expect that the + * {@link #performOperation()} method of a + * CompensatingTransactionOperationExecutor implementation would actually delete + * the entry, leaving it for the {@link #rollback()} method to recreate it using + * data from the original entry. However, this will not always be possible. In + * an LDAP system, for instance, it might not be possible to retrieve all the + * stored data from the original entry. In that case, the + * {@link #performOperation()} method will instead move the entry to a temporary + * location and leave it for the {@link #commit()} method to actually remove the + * entry. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public interface CompensatingTransactionOperationExecutor { + /** + * Rollback the operation, restoring state of the target as it was before + * the operation was performed using the information supplied on creation of + * this instance. + */ + void rollback(); + + /** + * Commit the operation. In many cases, this will not require any work at + * all to be performed. However, in some cases there will be interesting + * stuff to do. See class description for elaboration on this. + */ + void commit(); + + /** + * Perform the operation. This will most often require performing the + * recorded operation, but in some cases the actual operation performed by + * this method might be something else. See class description for + * elaboration on this. + */ + void performOperation(); +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationFactory.java b/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationFactory.java new file mode 100644 index 000000000..d3b68ad56 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.transaction.compensating; + +import com.fr.third.springframework.transaction.compensating.support.DefaultCompensatingTransactionOperationManager; + +/** + * Factory interface for creating + * {@link CompensatingTransactionOperationRecorder} objects based on operation + * method names. + * + * @author Mattias Hellborg Arthursson + * @see DefaultCompensatingTransactionOperationManager + * @since 1.2 + */ +public interface CompensatingTransactionOperationFactory { + /** + * Create an appropriate {@link CompensatingTransactionOperationRecorder} + * instance corresponding to the supplied method name. + * + * @param resource + * The target transaction resource. + * @param method + * the method name to create a + * {@link CompensatingTransactionOperationRecorder} for. + * + * @return a new {@link CompensatingTransactionOperationRecorder} instance. + */ + CompensatingTransactionOperationRecorder createRecordingOperation( + Object resource, String method); +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationManager.java b/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationManager.java new file mode 100644 index 000000000..c2ee307b2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationManager.java @@ -0,0 +1,55 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.transaction.compensating; + +/** + * A CompensatingTransactionOperationManager implementation records and performs + * operations that are to be performed within a compensating transaction. It + * keeps track of compensating actions necessary for rolling back each + * individual operation. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public interface CompensatingTransactionOperationManager { + /** + * Indicates that the supplied operation (method name) is to be performed. + * This method is responsible for recording the current state (prior to the + * operation), performing the operation, and storing the necessary + * information to roll back or commit the performed operation. + * + * @param resource + * the target resource to perform the operation on. + * @param operation + * The method to be invoked. + * @param args + * Arguments supplied to the method. + */ + void performOperation(Object resource, String operation, + Object[] args); + + /** + * Rollback all recorded operations by performing each of the recorded + * rollback operations. + */ + void rollback(); + + /** + * Commit all recorded operations. In many cases this means doing nothing, + * but in some cases some temporary data will need to be removed. + */ + void commit(); +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationRecorder.java b/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationRecorder.java new file mode 100644 index 000000000..9e7917b54 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/CompensatingTransactionOperationRecorder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.transaction.compensating; + +/** + * An implementation of this interface is responsible for recording data and + * supplying a {@link CompensatingTransactionOperationExecutor} to be invoked + * for execution and compensating transaction management of the operation. + * Recording of an operation should not fail (throwing an Exception), but + * instead log the result. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public interface CompensatingTransactionOperationRecorder { + /** + * Record information about the operation performed and return a + * corresponding {@link CompensatingTransactionOperationExecutor} to be used + * if the operation would need to be rolled back. + * + * @param args + * The arguments that have been sent to the operation. + * @return A {@link CompensatingTransactionOperationExecutor} to be used if + * the recorded operation should need to be rolled back. + */ + CompensatingTransactionOperationExecutor recordOperation( + Object[] args); +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/package.html b/fine-spring/src/com/fr/third/springframework/transaction/compensating/package.html new file mode 100644 index 000000000..6d166abe4 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/package.html @@ -0,0 +1,8 @@ + + + +Interface definitions for a general Compensating Transaction framework +based on PlatformTransactionManager. + + + diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java new file mode 100644 index 000000000..7c4008b85 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java @@ -0,0 +1,130 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.transaction.compensating.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.transaction.CannotCreateTransactionException; +import com.fr.third.springframework.transaction.TransactionDefinition; +import com.fr.third.springframework.transaction.TransactionException; +import com.fr.third.springframework.transaction.support.DefaultTransactionStatus; +import com.fr.third.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Abstract superclass for Compensating TransactionManager delegates. The actual + * transaction work is extracted to a delegate to enable composite Transaction + * Managers. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public abstract class AbstractCompensatingTransactionManagerDelegate { + + private static Logger log = LoggerFactory.getLogger(AbstractCompensatingTransactionManagerDelegate.class); + + /** + * Close the target resource - the implementation specific resource held in + * the specified {@link CompensatingTransactionHolderSupport}. + * + * @param transactionHolderSupport the + * {@link CompensatingTransactionHolderSupport} that holds the transaction + * specific target resource. + */ + protected abstract void closeTargetResource(CompensatingTransactionHolderSupport transactionHolderSupport); + + /** + * Get a new implementation specific + * {@link CompensatingTransactionHolderSupport} instance. + * + * @return a new {@link CompensatingTransactionHolderSupport} instance. + */ + protected abstract CompensatingTransactionHolderSupport getNewHolder(); + + /** + * Get the key (normally, a DataSource or similar) that should be used for + * transaction synchronization. + * + * @return the transaction synchronization key + */ + protected abstract Object getTransactionSynchronizationKey(); + + /* + * @seecom.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager# + * doGetTransaction() + */ + public Object doGetTransaction() throws TransactionException { + CompensatingTransactionHolderSupport holder = (CompensatingTransactionHolderSupport) TransactionSynchronizationManager + .getResource(getTransactionSynchronizationKey()); + return new CompensatingTransactionObject(holder); + } + + /* + * @see + * com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin + * (java.lang.Object, com.fr.third.springframework.transaction.TransactionDefinition) + */ + public void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { + try { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction; + if (txObject.getHolder() == null) { + CompensatingTransactionHolderSupport contextHolder = getNewHolder(); + txObject.setHolder(contextHolder); + + TransactionSynchronizationManager.bindResource(getTransactionSynchronizationKey(), contextHolder); + } + } + catch (Exception e) { + throw new CannotCreateTransactionException("Could not create DirContext instance for transaction", e); + } + } + + /* + * @see + * com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit + * (com.fr.third.springframework.transaction.support.DefaultTransactionStatus) + */ + public void doCommit(DefaultTransactionStatus status) throws TransactionException { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) status.getTransaction(); + txObject.getHolder().getTransactionOperationManager().commit(); + + } + + /* + * @see + * com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback + * (com.fr.third.springframework.transaction.support.DefaultTransactionStatus) + */ + public void doRollback(DefaultTransactionStatus status) throws TransactionException { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) status.getTransaction(); + txObject.getHolder().getTransactionOperationManager().rollback(); + } + + /* + * @seecom.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager# + * doCleanupAfterCompletion(java.lang.Object) + */ + public void doCleanupAfterCompletion(Object transaction) { + log.debug("Cleaning stored transaction synchronization"); + TransactionSynchronizationManager.unbindResource(getTransactionSynchronizationKey()); + + CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction; + CompensatingTransactionHolderSupport transactionHolderSupport = txObject.getHolder(); + + closeTargetResource(transactionHolderSupport); + + txObject.getHolder().clear(); + } +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/CompensatingTransactionHolderSupport.java b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/CompensatingTransactionHolderSupport.java new file mode 100644 index 000000000..ae14f9c72 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/CompensatingTransactionHolderSupport.java @@ -0,0 +1,80 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.transaction.compensating.support; + +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import com.fr.third.springframework.transaction.support.ResourceHolderSupport; + +/** + * Base class for compensating transaction resource holders. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public abstract class CompensatingTransactionHolderSupport extends + ResourceHolderSupport { + + private CompensatingTransactionOperationManager transactionOperationManager; + + /** + * Constructor. + * + * @param manager + * The {@link CompensatingTransactionOperationManager} to use for + * creating Compensating operations. + */ + public CompensatingTransactionHolderSupport( + CompensatingTransactionOperationManager manager) { + this.transactionOperationManager = manager; + } + + /** + * Get the actual transacted resource. + * + * @return the transaction's target resource + */ + protected abstract Object getTransactedResource(); + + /* + * @see com.fr.third.springframework.transaction.support.ResourceHolderSupport#clear() + */ + public void clear() { + super.clear(); + transactionOperationManager = null; + } + + /** + * Get the CompensatingTransactionOperationManager to handle the data for + * the current transaction. + * + * @return the CompensatingTransactionOperationManager. + */ + public CompensatingTransactionOperationManager getTransactionOperationManager() { + return transactionOperationManager; + } + + /** + * Set the CompensatingTransactionOperationManager. For testing purposes + * only. + * + * @param transactionOperationManager + * the CompensatingTransactionOperationManager to use. + */ + public void setTransactionOperationManager( + CompensatingTransactionOperationManager transactionOperationManager) { + this.transactionOperationManager = transactionOperationManager; + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/CompensatingTransactionObject.java b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/CompensatingTransactionObject.java new file mode 100644 index 000000000..002063422 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/CompensatingTransactionObject.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.transaction.compensating.support; + +/** + * Transaction object used by + * {@link AbstractCompensatingTransactionManagerDelegate}. Keeps a reference to + * the {@link CompensatingTransactionHolderSupport} associated with the current + * transaction. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class CompensatingTransactionObject { + private CompensatingTransactionHolderSupport holder; + + /** + * Constructor. + * + * @param holder + * the {@link CompensatingTransactionHolderSupport} associated + * with the current transaction. + */ + public CompensatingTransactionObject( + CompensatingTransactionHolderSupport holder) { + this.holder = holder; + } + + /** + * Get the DirContextHolder. + * + * @return the DirContextHolder. + */ + public CompensatingTransactionHolderSupport getHolder() { + return holder; + } + + /** + * Set the {@link CompensatingTransactionHolderSupport} associated with the + * current transaction. + * + * @param holder + * the {@link CompensatingTransactionHolderSupport} associated + * with the current transaction. + */ + public void setHolder(CompensatingTransactionHolderSupport holder) { + this.holder = holder; + } +} \ No newline at end of file diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/CompensatingTransactionUtils.java b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/CompensatingTransactionUtils.java new file mode 100644 index 000000000..19e95e4ab --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/CompensatingTransactionUtils.java @@ -0,0 +1,76 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.compensating.support; + +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import com.fr.third.springframework.transaction.support.TransactionSynchronizationManager; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Common methods for use with compensating transactions. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public final class CompensatingTransactionUtils { + + /** + * Not to be instantiated. + */ + private CompensatingTransactionUtils() { + } + + /** + * Perform the specified operation, storing the state prior to the operation + * in order to enable commit/rollback later. If no transaction is currently + * active, proceed with the original call on the target. + * + * @param synchronizationKey + * the transaction synchronization key we are operating on + * (typically something similar to a DataSource). + * @param target + * the actual target resource that should be used for invoking + * the operation on should no transaction be active. + * @param method + * name of the method to be invoked. + * @param args + * arguments with which the operation is invoked. + */ + public static void performOperation(Object synchronizationKey, + Object target, Method method, Object[] args) throws Throwable { + CompensatingTransactionHolderSupport transactionResourceHolder = (CompensatingTransactionHolderSupport) TransactionSynchronizationManager + .getResource(synchronizationKey); + if (transactionResourceHolder != null) { + + CompensatingTransactionOperationManager transactionOperationManager = transactionResourceHolder + .getTransactionOperationManager(); + transactionOperationManager.performOperation( + transactionResourceHolder.getTransactedResource(), method + .getName(), args); + } else { + // Perform the target operation + try { + method.invoke(target, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManager.java b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManager.java new file mode 100644 index 000000000..1f143320c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManager.java @@ -0,0 +1,124 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.springframework.transaction.compensating.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fr.third.springframework.transaction.TransactionSystemException; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationFactory; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +import java.util.Stack; + +/** + * Default implementation of {@link CompensatingTransactionOperationManager}. + * Manages a stack of {@link CompensatingTransactionOperationExecutor} objects + * and performs rollback of these in the reverse order. + * + * @author Mattias Hellborg Arthursson + * @since 1.2 + */ +public class DefaultCompensatingTransactionOperationManager implements + CompensatingTransactionOperationManager { + + private static Logger log = LoggerFactory.getLogger(DefaultCompensatingTransactionOperationManager.class); + + private Stack operationExecutors = + new Stack(); + + private CompensatingTransactionOperationFactory operationFactory; + + /** + * Set the {@link CompensatingTransactionOperationFactory} to use. + * + * @param operationFactory + * the {@link CompensatingTransactionOperationFactory}. + */ + public DefaultCompensatingTransactionOperationManager( + CompensatingTransactionOperationFactory operationFactory) { + this.operationFactory = operationFactory; + } + + /* + * @see com.fr.third.springframework.transaction.compensating.CompensatingTransactionOperationManager#performOperation(java.lang.Object, + * java.lang.String, java.lang.Object[]) + */ + public void performOperation(Object resource, String operation, + Object[] args) { + CompensatingTransactionOperationRecorder recorder = operationFactory + .createRecordingOperation(resource, operation); + CompensatingTransactionOperationExecutor executor = recorder + .recordOperation(args); + + executor.performOperation(); + + // Don't push the executor until the actual operation passed. + operationExecutors.push(executor); + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationManager#rollback() + */ + public void rollback() { + log.debug("Performing rollback"); + while (!operationExecutors.isEmpty()) { + CompensatingTransactionOperationExecutor rollbackOperation = operationExecutors.pop(); + try { + rollbackOperation.rollback(); + } catch (Exception e) { + throw new TransactionSystemException( + "Error occurred during rollback", e); + } + } + } + + /** + * Get the rollback operations. Used for testing purposes. + * + * @return the rollback operations. + */ + protected Stack getOperationExecutors() { + return operationExecutors; + } + + /** + * Set the rollback operations. Package protected - for testing purposes + * only. + * + * @param operationExecutors + * the rollback operations. + */ + void setOperationExecutors(Stack operationExecutors) { + this.operationExecutors = operationExecutors; + } + + /* + * @see com.fr.third.springframework.ldap.support.transaction.CompensatingTransactionOperationManager#commit() + */ + public void commit() { + log.debug("Performing commit"); + for (CompensatingTransactionOperationExecutor operationExecutor : operationExecutors) { + try { + operationExecutor.commit(); + } catch (Exception e) { + throw new TransactionSystemException( + "Error occurred during commit", e); + } + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/package.html b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/package.html new file mode 100644 index 000000000..383254c71 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/compensating/support/package.html @@ -0,0 +1,9 @@ + + + +Support package for general Compensating Transaction Framework. +Contains default implementations of core interfaces as well as useful +helper classes. + + + diff --git a/fine-spring/src/com/fr/third/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java b/fine-spring/src/com/fr/third/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java new file mode 100644 index 000000000..d68e74031 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java @@ -0,0 +1,154 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.config; + +import org.w3c.dom.Element; + +import com.fr.third.springframework.aop.config.AopNamespaceUtils; +import com.fr.third.springframework.beans.factory.config.BeanDefinition; +import com.fr.third.springframework.beans.factory.config.RuntimeBeanReference; +import com.fr.third.springframework.beans.factory.parsing.BeanComponentDefinition; +import com.fr.third.springframework.beans.factory.parsing.CompositeComponentDefinition; +import com.fr.third.springframework.beans.factory.support.RootBeanDefinition; +import com.fr.third.springframework.beans.factory.xml.BeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.ParserContext; +import com.fr.third.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor; +import com.fr.third.springframework.transaction.interceptor.TransactionInterceptor; + +/** + * {@link com.fr.third.springframework.beans.factory.xml.BeanDefinitionParser + * BeanDefinitionParser} implementation that allows users to easily configure + * all the infrastructure beans required to enable annotation-driven transaction + * demarcation. + * + *

By default, all proxies are created as JDK proxies. This may cause some + * problems if you are injecting objects as concrete classes rather than + * interfaces. To overcome this restriction you can set the + * '{@code proxy-target-class}' attribute to '{@code true}', which + * will result in class-based proxies being created. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @author Chris Beams + * @since 2.0 + */ +class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { + + /** + * The bean name of the internally managed transaction advisor (mode="proxy"). + * @deprecated as of Spring 3.1 in favor of + * {@link TransactionManagementConfigUtils#TRANSACTION_ADVISOR_BEAN_NAME} + */ + @Deprecated + public static final String TRANSACTION_ADVISOR_BEAN_NAME = + TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME; + + /** + * The bean name of the internally managed transaction aspect (mode="aspectj"). + * @deprecated as of Spring 3.1 in favor of + * {@link TransactionManagementConfigUtils#TRANSACTION_ASPECT_BEAN_NAME} + */ + @Deprecated + public static final String TRANSACTION_ASPECT_BEAN_NAME = + TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME; + + + /** + * Parses the {@code } tag. Will + * {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator} + * with the container as necessary. + */ + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + String mode = element.getAttribute("mode"); + if ("aspectj".equals(mode)) { + // mode="aspectj" + registerTransactionAspect(element, parserContext); + } + else { + // mode="proxy" + AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext); + } + return null; + } + + private void registerTransactionAspect(Element element, ParserContext parserContext) { + String txAspectBeanName = TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME; + String txAspectClassName = TransactionManagementConfigUtils.TRANSACTION_ASPECT_CLASS_NAME; + if (!parserContext.getRegistry().containsBeanDefinition(txAspectBeanName)) { + RootBeanDefinition def = new RootBeanDefinition(); + def.setBeanClassName(txAspectClassName); + def.setFactoryMethodName("aspectOf"); + registerTransactionManager(element, def); + parserContext.registerBeanComponent(new BeanComponentDefinition(def, txAspectBeanName)); + } + } + + private static void registerTransactionManager(Element element, BeanDefinition def) { + def.getPropertyValues().add("transactionManagerBeanName", + TxNamespaceHandler.getTransactionManagerName(element)); + } + + + /** + * Inner class to just introduce an AOP framework dependency when actually in proxy mode. + */ + private static class AopAutoProxyConfigurer { + + public static void configureAutoProxyCreator(Element element, ParserContext parserContext) { + AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element); + + String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME; + if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) { + Object eleSource = parserContext.extractSource(element); + + // Create the TransactionAttributeSource definition. + RootBeanDefinition sourceDef = new RootBeanDefinition( + "com.fr.third.springframework.transaction.annotation.AnnotationTransactionAttributeSource"); + sourceDef.setSource(eleSource); + sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef); + + // Create the TransactionInterceptor definition. + RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class); + interceptorDef.setSource(eleSource); + interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registerTransactionManager(element, interceptorDef); + interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName)); + String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef); + + // Create the TransactionAttributeSourceAdvisor definition. + RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class); + advisorDef.setSource(eleSource); + advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName)); + advisorDef.getPropertyValues().add("adviceBeanName", interceptorName); + if (element.hasAttribute("order")) { + advisorDef.getPropertyValues().add("order", element.getAttribute("order")); + } + parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef); + + CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource); + compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName)); + compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName)); + compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName)); + parserContext.registerComponent(compositeDef); + } + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java b/fine-spring/src/com/fr/third/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java new file mode 100644 index 000000000..9cf80d8b0 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.config; + +import org.w3c.dom.Element; + +import com.fr.third.springframework.beans.factory.support.AbstractBeanDefinition; +import com.fr.third.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.ParserContext; +import com.fr.third.springframework.util.ClassUtils; + +/** + * Parser for the <tx:jta-transaction-manager/> element, + * autodetecting BEA WebLogic and IBM WebSphere. + * + * @author Juergen Hoeller + * @author Christian Dupuis + * @since 2.5 + */ +public class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + private static final String WEBLOGIC_JTA_TRANSACTION_MANAGER_CLASS_NAME = + "com.fr.third.springframework.transaction.jta.WebLogicJtaTransactionManager"; + + private static final String WEBSPHERE_TRANSACTION_MANAGER_CLASS_NAME = + "com.fr.third.springframework.transaction.jta.WebSphereUowTransactionManager"; + + private static final String JTA_TRANSACTION_MANAGER_CLASS_NAME = + "com.fr.third.springframework.transaction.jta.JtaTransactionManager"; + + + private static final boolean weblogicPresent = ClassUtils.isPresent( + "weblogic.transaction.UserTransaction", JtaTransactionManagerBeanDefinitionParser.class.getClassLoader()); + + private static final boolean webspherePresent = ClassUtils.isPresent( + "com.ibm.wsspi.uow.UOWManager", JtaTransactionManagerBeanDefinitionParser.class.getClassLoader()); + + + @Override + protected String getBeanClassName(Element element) { + if (weblogicPresent) { + return WEBLOGIC_JTA_TRANSACTION_MANAGER_CLASS_NAME; + } + else if (webspherePresent) { + return WEBSPHERE_TRANSACTION_MANAGER_CLASS_NAME; + } + else { + return JTA_TRANSACTION_MANAGER_CLASS_NAME; + } + } + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) { + return TxNamespaceHandler.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/config/TransactionManagementConfigUtils.java b/fine-spring/src/com/fr/third/springframework/transaction/config/TransactionManagementConfigUtils.java new file mode 100644 index 000000000..4a3cd4be9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/config/TransactionManagementConfigUtils.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.config; + +/** + * Configuration constants for internal sharing across subpackages. + * + * @author Chris Beams + * @since 3.1 + */ +public abstract class TransactionManagementConfigUtils { + + /** + * The bean name of the internally managed transaction advisor (used when mode == PROXY). + */ + public static final String TRANSACTION_ADVISOR_BEAN_NAME = + "com.fr.third.springframework.transaction.config.internalTransactionAdvisor"; + + /** + * The bean name of the internally managed transaction aspect (used when mode == ASPECTJ). + */ + public static final String TRANSACTION_ASPECT_BEAN_NAME = + "com.fr.third.springframework.transaction.config.internalTransactionAspect"; + + /** + * The class name of the AspectJ transaction management aspect. + */ + public static final String TRANSACTION_ASPECT_CLASS_NAME = + "com.fr.third.springframework.transaction.aspectj.AnnotationTransactionAspect"; + + /** + * The name of the AspectJ transaction management @{@code Configuration} class. + */ + public static final String TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME = + "com.fr.third.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration"; + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/config/TxAdviceBeanDefinitionParser.java b/fine-spring/src/com/fr/third/springframework/transaction/config/TxAdviceBeanDefinitionParser.java new file mode 100644 index 000000000..7e55ad0a6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/config/TxAdviceBeanDefinitionParser.java @@ -0,0 +1,164 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.config; + +import java.util.LinkedList; +import java.util.List; + +import org.w3c.dom.Element; + +import com.fr.third.springframework.beans.factory.config.TypedStringValue; +import com.fr.third.springframework.beans.factory.support.BeanDefinitionBuilder; +import com.fr.third.springframework.beans.factory.support.ManagedMap; +import com.fr.third.springframework.beans.factory.support.RootBeanDefinition; +import com.fr.third.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import com.fr.third.springframework.beans.factory.xml.ParserContext; +import com.fr.third.springframework.transaction.interceptor.NameMatchTransactionAttributeSource; +import com.fr.third.springframework.transaction.interceptor.NoRollbackRuleAttribute; +import com.fr.third.springframework.transaction.interceptor.RollbackRuleAttribute; +import com.fr.third.springframework.transaction.interceptor.RuleBasedTransactionAttribute; +import com.fr.third.springframework.transaction.interceptor.TransactionInterceptor; +import com.fr.third.springframework.util.StringUtils; +import com.fr.third.springframework.util.xml.DomUtils; + +/** + * {@link com.fr.third.springframework.beans.factory.xml.BeanDefinitionParser + * BeanDefinitionParser} for the {@code } tag. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Adrian Colyer + * @author Chris Beams + * @since 2.0 + */ +class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + private static final String METHOD_ELEMENT = "method"; + + private static final String METHOD_NAME_ATTRIBUTE = "name"; + + private static final String ATTRIBUTES_ELEMENT = "attributes"; + + private static final String TIMEOUT_ATTRIBUTE = "timeout"; + + private static final String READ_ONLY_ATTRIBUTE = "read-only"; + + private static final String PROPAGATION_ATTRIBUTE = "propagation"; + + private static final String ISOLATION_ATTRIBUTE = "isolation"; + + private static final String ROLLBACK_FOR_ATTRIBUTE = "rollback-for"; + + private static final String NO_ROLLBACK_FOR_ATTRIBUTE = "no-rollback-for"; + + + @Override + protected Class getBeanClass(Element element) { + return TransactionInterceptor.class; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element)); + + List txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT); + if (txAttributes.size() > 1) { + parserContext.getReaderContext().error( + "Element is allowed at most once inside element ", element); + } + else if (txAttributes.size() == 1) { + // Using attributes source. + Element attributeSourceElement = txAttributes.get(0); + RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext); + builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition); + } + else { + // Assume annotations source. + builder.addPropertyValue("transactionAttributeSource", + new RootBeanDefinition("com.fr.third.springframework.transaction.annotation.AnnotationTransactionAttributeSource")); + } + } + + private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) { + List methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT); + ManagedMap transactionAttributeMap = + new ManagedMap(methods.size()); + transactionAttributeMap.setSource(parserContext.extractSource(attrEle)); + + for (Element methodEle : methods) { + String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE); + TypedStringValue nameHolder = new TypedStringValue(name); + nameHolder.setSource(parserContext.extractSource(methodEle)); + + RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute(); + String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE); + String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE); + String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE); + String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE); + if (StringUtils.hasText(propagation)) { + attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation); + } + if (StringUtils.hasText(isolation)) { + attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation); + } + if (StringUtils.hasText(timeout)) { + try { + attribute.setTimeout(Integer.parseInt(timeout)); + } + catch (NumberFormatException ex) { + parserContext.getReaderContext().error("Timeout must be an integer value: [" + timeout + "]", methodEle); + } + } + if (StringUtils.hasText(readOnly)) { + attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY_ATTRIBUTE))); + } + + List rollbackRules = new LinkedList(); + if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) { + String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE); + addRollbackRuleAttributesTo(rollbackRules,rollbackForValue); + } + if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) { + String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE); + addNoRollbackRuleAttributesTo(rollbackRules,noRollbackForValue); + } + attribute.setRollbackRules(rollbackRules); + + transactionAttributeMap.put(nameHolder, attribute); + } + + RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class); + attributeSourceDefinition.setSource(parserContext.extractSource(attrEle)); + attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap); + return attributeSourceDefinition; + } + + private void addRollbackRuleAttributesTo(List rollbackRules, String rollbackForValue) { + String[] exceptionTypeNames = StringUtils.commaDelimitedListToStringArray(rollbackForValue); + for (String typeName : exceptionTypeNames) { + rollbackRules.add(new RollbackRuleAttribute(StringUtils.trimWhitespace(typeName))); + } + } + + private void addNoRollbackRuleAttributesTo(List rollbackRules, String noRollbackForValue) { + String[] exceptionTypeNames = StringUtils.commaDelimitedListToStringArray(noRollbackForValue); + for (String typeName : exceptionTypeNames) { + rollbackRules.add(new NoRollbackRuleAttribute(StringUtils.trimWhitespace(typeName))); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/config/TxNamespaceHandler.java b/fine-spring/src/com/fr/third/springframework/transaction/config/TxNamespaceHandler.java new file mode 100644 index 000000000..f80b0fd6f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/config/TxNamespaceHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.config; + +import org.w3c.dom.Element; + +import com.fr.third.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * {@code NamespaceHandler} allowing for the configuration of + * declarative transaction management using either XML or using annotations. + * + *

This namespace handler is the central piece of functionality in the + * Spring transaction management facilities and offers two approaches + * to declaratively manage transactions. + * + *

One approach uses transaction semantics defined in XML using the + * {@code <tx:advice>} elements, the other uses annotations + * in combination with the {@code <tx:annotation-driven>} element. + * Both approached are detailed to great extent in the Spring reference manual. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class TxNamespaceHandler extends NamespaceHandlerSupport { + + static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager"; + + static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager"; + + + static String getTransactionManagerName(Element element) { + return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ? + element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME); + } + + + @Override + public void init() { + registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser()); + registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); + registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser()); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/config/package-info.java b/fine-spring/src/com/fr/third/springframework/transaction/config/package-info.java new file mode 100644 index 000000000..d7d15deec --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/config/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * Support package for declarative transaction configuration, + * with XML schema being the primary configuration format. + * + */ +package com.fr.third.springframework.transaction.config; + diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java new file mode 100644 index 000000000..15f18c24c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java @@ -0,0 +1,234 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.core.BridgeMethodResolver; +import com.fr.third.springframework.util.ClassUtils; +import com.fr.third.springframework.util.ObjectUtils; + +/** + * Abstract implementation of {@link TransactionAttributeSource} that caches + * attributes for methods and implements a fallback policy: 1. specific target + * method; 2. target class; 3. declaring method; 4. declaring class/interface. + * + *

Defaults to using the target class's transaction attribute if none is + * associated with the target method. Any transaction attribute associated with + * the target method completely overrides a class transaction attribute. + * If none found on the target class, the interface that the invoked method + * has been called through (in case of a JDK proxy) will be checked. + * + *

This implementation caches attributes by method after they are first used. + * If it is ever desirable to allow dynamic changing of transaction attributes + * (which is very unlikely), caching could be made configurable. Caching is + * desirable because of the cost of evaluating rollback rules. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + */ +public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource { + + /** + * Canonical value held in cache to indicate no transaction attribute was + * found for this method, and we don't need to look again. + */ + private final static TransactionAttribute NULL_TRANSACTION_ATTRIBUTE = new DefaultTransactionAttribute(); + + + /** + * Logger available to subclasses. + *

As this base class is not marked Serializable, the logger will be recreated + * after serialization - provided that the concrete subclass is Serializable. + */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** + * Cache of TransactionAttributes, keyed by DefaultCacheKey (Method + target Class). + *

As this base class is not marked Serializable, the cache will be recreated + * after serialization - provided that the concrete subclass is Serializable. + */ + final Map attributeCache = new ConcurrentHashMap(1024); + + + /** + * Determine the transaction attribute for this method invocation. + *

Defaults to the class's transaction attribute if no method attribute is found. + * @param method the method for the current invocation (never {@code null}) + * @param targetClass the target class for this invocation (may be {@code null}) + * @return TransactionAttribute for this method, or {@code null} if the method + * is not transactional + */ + @Override + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + // First, see if we have a cached value. + Object cacheKey = getCacheKey(method, targetClass); + Object cached = this.attributeCache.get(cacheKey); + if (cached != null) { + // Value will either be canonical value indicating there is no transaction attribute, + // or an actual transaction attribute. + if (cached == NULL_TRANSACTION_ATTRIBUTE) { + return null; + } + else { + return (TransactionAttribute) cached; + } + } + else { + // We need to work it out. + TransactionAttribute txAtt = computeTransactionAttribute(method, targetClass); + // Put it in the cache. + if (txAtt == null) { + this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE); + } + else { + if (logger.isDebugEnabled()) { + Class classToLog = (targetClass != null ? targetClass : method.getDeclaringClass()); + logger.debug("Adding transactional method '" + classToLog.getSimpleName() + "." + + method.getName() + "' with attribute: " + txAtt); + } + this.attributeCache.put(cacheKey, txAtt); + } + return txAtt; + } + } + + /** + * Determine a cache key for the given method and target class. + *

Must not produce same key for overloaded methods. + * Must produce same key for different instances of the same method. + * @param method the method (never {@code null}) + * @param targetClass the target class (may be {@code null}) + * @return the cache key (never {@code null}) + */ + protected Object getCacheKey(Method method, Class targetClass) { + return new DefaultCacheKey(method, targetClass); + } + + /** + * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result. + * {@link #getTransactionAttribute} is effectively a caching decorator for this method. + * @see #getTransactionAttribute + */ + private TransactionAttribute computeTransactionAttribute(Method method, Class targetClass) { + // Don't allow no-public methods as required. + if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { + return null; + } + + // Ignore CGLIB subclasses - introspect the actual user class. + Class userClass = ClassUtils.getUserClass(targetClass); + // The method may be on an interface, but we need attributes from the target class. + // If the target class is null, the method will be unchanged. + Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass); + // If we are dealing with method with generic parameters, find the original method. + specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); + + // First try is the method in the target class. + TransactionAttribute txAtt = findTransactionAttribute(specificMethod); + if (txAtt != null) { + return txAtt; + } + + // Second try is the transaction attribute on the target class. + txAtt = findTransactionAttribute(specificMethod.getDeclaringClass()); + if (txAtt != null) { + return txAtt; + } + + if (specificMethod != method) { + // Fallback is to look at the original method. + txAtt = findTransactionAttribute(method); + if (txAtt != null) { + return txAtt; + } + // Last fallback is the class of the original method. + return findTransactionAttribute(method.getDeclaringClass()); + } + return null; + } + + + /** + * Subclasses need to implement this to return the transaction attribute + * for the given method, if any. + * @param method the method to retrieve the attribute for + * @return all transaction attribute associated with this method + * (or {@code null} if none) + */ + protected abstract TransactionAttribute findTransactionAttribute(Method method); + + /** + * Subclasses need to implement this to return the transaction attribute + * for the given class, if any. + * @param clazz the class to retrieve the attribute for + * @return all transaction attribute associated with this class + * (or {@code null} if none) + */ + protected abstract TransactionAttribute findTransactionAttribute(Class clazz); + + + /** + * Should only public methods be allowed to have transactional semantics? + *

The default implementation returns {@code false}. + */ + protected boolean allowPublicMethodsOnly() { + return false; + } + + + /** + * Default cache key for the TransactionAttribute cache. + */ + private static class DefaultCacheKey { + + private final Method method; + + private final Class targetClass; + + public DefaultCacheKey(Method method, Class targetClass) { + this.method = method; + this.targetClass = targetClass; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof DefaultCacheKey)) { + return false; + } + DefaultCacheKey otherKey = (DefaultCacheKey) other; + return (this.method.equals(otherKey.method) && + ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass)); + } + + @Override + public int hashCode() { + return this.method.hashCode() + (this.targetClass != null ? this.targetClass.hashCode() * 29 : 0); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/BeanFactoryTransactionAttributeSourceAdvisor.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/BeanFactoryTransactionAttributeSourceAdvisor.java new file mode 100644 index 000000000..2a645b1a1 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/BeanFactoryTransactionAttributeSourceAdvisor.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import com.fr.third.springframework.aop.ClassFilter; +import com.fr.third.springframework.aop.Pointcut; +import com.fr.third.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor; + +/** + * Advisor driven by a {@link TransactionAttributeSource}, used to include + * a transaction advice bean for methods that are transactional. + * + * @author Juergen Hoeller + * @since 2.5.5 + * @see #setAdviceBeanName + * @see TransactionInterceptor + * @see TransactionAttributeSourceAdvisor + */ +@SuppressWarnings("serial") +public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { + + private TransactionAttributeSource transactionAttributeSource; + + private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() { + @Override + protected TransactionAttributeSource getTransactionAttributeSource() { + return transactionAttributeSource; + } + }; + + + /** + * Set the transaction attribute source which is used to find transaction + * attributes. This should usually be identical to the source reference + * set on the transaction interceptor itself. + * @see TransactionInterceptor#setTransactionAttributeSource + */ + public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) { + this.transactionAttributeSource = transactionAttributeSource; + } + + /** + * Set the {@link ClassFilter} to use for this pointcut. + * Default is {@link ClassFilter#TRUE}. + */ + public void setClassFilter(ClassFilter classFilter) { + this.pointcut.setClassFilter(classFilter); + } + + @Override + public Pointcut getPointcut() { + return this.pointcut; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java new file mode 100644 index 000000000..61777e69b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import com.fr.third.springframework.util.Assert; + +/** + * Composite {@link TransactionAttributeSource} implementation that iterates + * over a given array of {@link TransactionAttributeSource} instances. + * + * @author Juergen Hoeller + * @since 2.0 + */ +@SuppressWarnings("serial") +public class CompositeTransactionAttributeSource implements TransactionAttributeSource, Serializable { + + private final TransactionAttributeSource[] transactionAttributeSources; + + + /** + * Create a new CompositeTransactionAttributeSource for the given sources. + * @param transactionAttributeSources the TransactionAttributeSource instances to combine + */ + public CompositeTransactionAttributeSource(TransactionAttributeSource[] transactionAttributeSources) { + Assert.notNull(transactionAttributeSources, "TransactionAttributeSource array must not be null"); + this.transactionAttributeSources = transactionAttributeSources; + } + + /** + * Return the TransactionAttributeSource instances that this + * CompositeTransactionAttributeSource combines. + */ + public final TransactionAttributeSource[] getTransactionAttributeSources() { + return this.transactionAttributeSources; + } + + + @Override + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + for (TransactionAttributeSource tas : this.transactionAttributeSources) { + TransactionAttribute ta = tas.getTransactionAttribute(method, targetClass); + if (ta != null) { + return ta; + } + } + return null; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/DefaultTransactionAttribute.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/DefaultTransactionAttribute.java new file mode 100644 index 000000000..aea47a1d7 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/DefaultTransactionAttribute.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import com.fr.third.springframework.transaction.support.DefaultTransactionDefinition; + +/** + * Transaction attribute that takes the EJB approach to rolling + * back on runtime, but not checked, exceptions. + * + * @author Rod Johnson + * @since 16.03.2003 + */ +@SuppressWarnings("serial") +public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute { + + private String qualifier; + + + /** + * Create a new DefaultTransactionAttribute, with default settings. + * Can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + */ + public DefaultTransactionAttribute() { + super(); + } + + /** + * Copy constructor. Definition can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + */ + public DefaultTransactionAttribute(TransactionAttribute other) { + super(other); + } + + /** + * Create a new DefaultTransactionAttribute with the the given + * propagation behavior. Can be modified through bean property setters. + * @param propagationBehavior one of the propagation constants in the + * TransactionDefinition interface + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + */ + public DefaultTransactionAttribute(int propagationBehavior) { + super(propagationBehavior); + } + + + /** + * Associate a qualifier value with this transaction attribute. + *

This may be used for choosing a corresponding transaction manager + * to process this specific transaction. + */ + public void setQualifier(String qualifier) { + this.qualifier = qualifier; + } + + /** + * Return a qualifier value associated with this transaction attribute. + */ + @Override + public String getQualifier() { + return this.qualifier; + } + + /** + * The default behavior is as with EJB: rollback on unchecked exception. + * Additionally attempt to rollback on Error. + *

This is consistent with TransactionTemplate's default behavior. + */ + @Override + public boolean rollbackOn(Throwable ex) { + return (ex instanceof RuntimeException || ex instanceof Error); + } + + + /** + * Return an identifying description for this transaction attribute. + *

Available to subclasses, for inclusion in their {@code toString()} result. + */ + protected final StringBuilder getAttributeDescription() { + StringBuilder result = getDefinitionDescription(); + if (this.qualifier != null) { + result.append("; '").append(this.qualifier).append("'"); + } + return result; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/DelegatingTransactionAttribute.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/DelegatingTransactionAttribute.java new file mode 100644 index 000000000..66ad54655 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/DelegatingTransactionAttribute.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.io.Serializable; + +import com.fr.third.springframework.transaction.support.DelegatingTransactionDefinition; + +/** + * {@link TransactionAttribute} implementation that delegates all calls to a given target + * {@link TransactionAttribute} instance. Abstract because it is meant to be subclassed, + * with subclasses overriding specific methods that are not supposed to simply delegate + * to the target instance. + * + * @author Juergen Hoeller + * @since 1.2 + */ +@SuppressWarnings("serial") +public abstract class DelegatingTransactionAttribute extends DelegatingTransactionDefinition + implements TransactionAttribute, Serializable { + + private final TransactionAttribute targetAttribute; + + + /** + * Create a DelegatingTransactionAttribute for the given target attribute. + * @param targetAttribute the target TransactionAttribute to delegate to + */ + public DelegatingTransactionAttribute(TransactionAttribute targetAttribute) { + super(targetAttribute); + this.targetAttribute = targetAttribute; + } + + + @Override + public String getQualifier() { + return this.targetAttribute.getQualifier(); + } + + @Override + public boolean rollbackOn(Throwable ex) { + return this.targetAttribute.rollbackOn(ex); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java new file mode 100644 index 000000000..e5df8dc5f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import com.fr.third.springframework.util.ClassUtils; +import com.fr.third.springframework.util.ObjectUtils; + +/** + * Very simple implementation of TransactionAttributeSource which will always return + * the same TransactionAttribute for all methods fed to it. The TransactionAttribute + * may be specified, but will otherwise default to PROPAGATION_REQUIRED. This may be + * used in the cases where you want to use the same transaction attribute with all + * methods being handled by a transaction interceptor. + * + * @author Colin Sampaleanu + * @since 15.10.2003 + * @see com.fr.third.springframework.transaction.interceptor.TransactionProxyFactoryBean + * @see com.fr.third.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator + */ +@SuppressWarnings("serial") +public class MatchAlwaysTransactionAttributeSource implements TransactionAttributeSource, Serializable { + + private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); + + + /** + * Allows a transaction attribute to be specified, using the String form, for + * example, "PROPAGATION_REQUIRED". + * @param transactionAttribute The String form of the transactionAttribute to use. + * @see com.fr.third.springframework.transaction.interceptor.TransactionAttributeEditor + */ + public void setTransactionAttribute(TransactionAttribute transactionAttribute) { + this.transactionAttribute = transactionAttribute; + } + + + @Override + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + return (method == null || ClassUtils.isUserLevelMethod(method) ? this.transactionAttribute : null); + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MatchAlwaysTransactionAttributeSource)) { + return false; + } + MatchAlwaysTransactionAttributeSource otherTas = (MatchAlwaysTransactionAttributeSource) other; + return ObjectUtils.nullSafeEquals(this.transactionAttribute, otherTas.transactionAttribute); + } + + @Override + public int hashCode() { + return MatchAlwaysTransactionAttributeSource.class.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + this.transactionAttribute; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java new file mode 100644 index 000000000..c50d59af7 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java @@ -0,0 +1,248 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.beans.factory.BeanClassLoaderAware; +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.ClassUtils; +import com.fr.third.springframework.util.ObjectUtils; +import com.fr.third.springframework.util.PatternMatchUtils; + +/** + * Simple {@link TransactionAttributeSource} implementation that + * allows attributes to be stored per method in a {@link Map}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 24.04.2003 + * @see #isMatch + * @see NameMatchTransactionAttributeSource + */ +public class MethodMapTransactionAttributeSource + implements TransactionAttributeSource, BeanClassLoaderAware, InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** Map from method name to attribute value */ + private Map methodMap; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + private boolean eagerlyInitialized = false; + + private boolean initialized = false; + + /** Map from Method to TransactionAttribute */ + private final Map transactionAttributeMap = + new HashMap(); + + /** Map from Method to name pattern used for registration */ + private final Map methodNameMap = new HashMap(); + + + /** + * Set a name/attribute map, consisting of "FQCN.method" method names + * (e.g. "com.mycompany.mycode.MyClass.myMethod") and + * {@link TransactionAttribute} instances (or Strings to be converted + * to {@code TransactionAttribute} instances). + *

Intended for configuration via setter injection, typically within + * a Spring bean factory. Relies on {@link #afterPropertiesSet()} + * being called afterwards. + * @param methodMap said {@link Map} from method name to attribute value + * @see TransactionAttribute + * @see TransactionAttributeEditor + */ + public void setMethodMap(Map methodMap) { + this.methodMap = methodMap; + } + + @Override + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + + /** + * Eagerly initializes the specified + * {@link #setMethodMap(java.util.Map) "methodMap"}, if any. + * @see #initMethodMap(java.util.Map) + */ + @Override + public void afterPropertiesSet() { + initMethodMap(this.methodMap); + this.eagerlyInitialized = true; + this.initialized = true; + } + + /** + * Initialize the specified {@link #setMethodMap(java.util.Map) "methodMap"}, if any. + * @param methodMap Map from method names to {@code TransactionAttribute} instances + * @see #setMethodMap + */ + protected void initMethodMap(Map methodMap) { + if (methodMap != null) { + for (Map.Entry entry : methodMap.entrySet()) { + addTransactionalMethod(entry.getKey(), entry.getValue()); + } + } + } + + + /** + * Add an attribute for a transactional method. + *

Method names can end or start with "*" for matching multiple methods. + * @param name class and method name, separated by a dot + * @param attr attribute associated with the method + * @throws IllegalArgumentException in case of an invalid name + */ + public void addTransactionalMethod(String name, TransactionAttribute attr) { + Assert.notNull(name, "Name must not be null"); + int lastDotIndex = name.lastIndexOf("."); + if (lastDotIndex == -1) { + throw new IllegalArgumentException("'" + name + "' is not a valid method name: format is FQN.methodName"); + } + String className = name.substring(0, lastDotIndex); + String methodName = name.substring(lastDotIndex + 1); + Class clazz = ClassUtils.resolveClassName(className, this.beanClassLoader); + addTransactionalMethod(clazz, methodName, attr); + } + + /** + * Add an attribute for a transactional method. + * Method names can end or start with "*" for matching multiple methods. + * @param clazz target interface or class + * @param mappedName mapped method name + * @param attr attribute associated with the method + */ + public void addTransactionalMethod(Class clazz, String mappedName, TransactionAttribute attr) { + Assert.notNull(clazz, "Class must not be null"); + Assert.notNull(mappedName, "Mapped name must not be null"); + String name = clazz.getName() + '.' + mappedName; + + Method[] methods = clazz.getDeclaredMethods(); + List matchingMethods = new ArrayList(); + for (Method method : methods) { + if (isMatch(method.getName(), mappedName)) { + matchingMethods.add(method); + } + } + if (matchingMethods.isEmpty()) { + throw new IllegalArgumentException( + "Couldn't find method '" + mappedName + "' on class [" + clazz.getName() + "]"); + } + + // register all matching methods + for (Method method : matchingMethods) { + String regMethodName = this.methodNameMap.get(method); + if (regMethodName == null || (!regMethodName.equals(name) && regMethodName.length() <= name.length())) { + // No already registered method name, or more specific + // method name specification now -> (re-)register method. + if (logger.isDebugEnabled() && regMethodName != null) { + logger.debug("Replacing attribute for transactional method [" + method + "]: current name '" + + name + "' is more specific than '" + regMethodName + "'"); + } + this.methodNameMap.put(method, name); + addTransactionalMethod(method, attr); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Keeping attribute for transactional method [" + method + "]: current name '" + + name + "' is not more specific than '" + regMethodName + "'"); + } + } + } + } + + /** + * Add an attribute for a transactional method. + * @param method the method + * @param attr attribute associated with the method + */ + public void addTransactionalMethod(Method method, TransactionAttribute attr) { + Assert.notNull(method, "Method must not be null"); + Assert.notNull(attr, "TransactionAttribute must not be null"); + if (logger.isDebugEnabled()) { + logger.debug("Adding transactional method [" + method + "] with attribute [" + attr + "]"); + } + this.transactionAttributeMap.put(method, attr); + } + + /** + * Return if the given method name matches the mapped name. + *

The default implementation checks for "xxx*", "*xxx" and "*xxx*" + * matches, as well as direct equality. + * @param methodName the method name of the class + * @param mappedName the name in the descriptor + * @return if the names match + * @see com.fr.third.springframework.util.PatternMatchUtils#simpleMatch(String, String) + */ + protected boolean isMatch(String methodName, String mappedName) { + return PatternMatchUtils.simpleMatch(mappedName, methodName); + } + + + @Override + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + if (this.eagerlyInitialized) { + return this.transactionAttributeMap.get(method); + } + else { + synchronized (this.transactionAttributeMap) { + if (!this.initialized) { + initMethodMap(this.methodMap); + this.initialized = true; + } + return this.transactionAttributeMap.get(method); + } + } + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MethodMapTransactionAttributeSource)) { + return false; + } + MethodMapTransactionAttributeSource otherTas = (MethodMapTransactionAttributeSource) other; + return ObjectUtils.nullSafeEquals(this.methodMap, otherTas.methodMap); + } + + @Override + public int hashCode() { + return MethodMapTransactionAttributeSource.class.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + this.methodMap; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java new file mode 100644 index 000000000..9d14aa9fe --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.util.ClassUtils; +import com.fr.third.springframework.util.ObjectUtils; +import com.fr.third.springframework.util.PatternMatchUtils; + +/** + * Simple {@link TransactionAttributeSource} implementation that + * allows attributes to be matched by registered name. + * + * @author Juergen Hoeller + * @since 21.08.2003 + * @see #isMatch + * @see MethodMapTransactionAttributeSource + */ +@SuppressWarnings("serial") +public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable { + + /** + * Logger available to subclasses. + *

Static for optimal serialization. + */ + protected static final Log logger = LogFactory.getLog(NameMatchTransactionAttributeSource.class); + + /** Keys are method names; values are TransactionAttributes */ + private Map nameMap = new HashMap(); + + + /** + * Set a name/attribute map, consisting of method names + * (e.g. "myMethod") and TransactionAttribute instances + * (or Strings to be converted to TransactionAttribute instances). + * @see TransactionAttribute + * @see TransactionAttributeEditor + */ + public void setNameMap(Map nameMap) { + for (Map.Entry entry : nameMap.entrySet()) { + addTransactionalMethod(entry.getKey(), entry.getValue()); + } + } + + /** + * Parses the given properties into a name/attribute map. + * Expects method names as keys and String attributes definitions as values, + * parsable into TransactionAttribute instances via TransactionAttributeEditor. + * @see #setNameMap + * @see TransactionAttributeEditor + */ + public void setProperties(Properties transactionAttributes) { + TransactionAttributeEditor tae = new TransactionAttributeEditor(); + Enumeration propNames = transactionAttributes.propertyNames(); + while (propNames.hasMoreElements()) { + String methodName = (String) propNames.nextElement(); + String value = transactionAttributes.getProperty(methodName); + tae.setAsText(value); + TransactionAttribute attr = (TransactionAttribute) tae.getValue(); + addTransactionalMethod(methodName, attr); + } + } + + /** + * Add an attribute for a transactional method. + *

Method names can be exact matches, or of the pattern "xxx*", + * "*xxx" or "*xxx*" for matching multiple methods. + * @param methodName the name of the method + * @param attr attribute associated with the method + */ + public void addTransactionalMethod(String methodName, TransactionAttribute attr) { + if (logger.isDebugEnabled()) { + logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]"); + } + this.nameMap.put(methodName, attr); + } + + + @Override + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + if (!ClassUtils.isUserLevelMethod(method)) { + return null; + } + + // Look for direct name match. + String methodName = method.getName(); + TransactionAttribute attr = this.nameMap.get(methodName); + + if (attr == null) { + // Look for most specific name match. + String bestNameMatch = null; + for (String mappedName : this.nameMap.keySet()) { + if (isMatch(methodName, mappedName) && + (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) { + attr = this.nameMap.get(mappedName); + bestNameMatch = mappedName; + } + } + } + + return attr; + } + + /** + * Return if the given method name matches the mapped name. + *

The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, + * as well as direct equality. Can be overridden in subclasses. + * @param methodName the method name of the class + * @param mappedName the name in the descriptor + * @return if the names match + * @see com.fr.third.springframework.util.PatternMatchUtils#simpleMatch(String, String) + */ + protected boolean isMatch(String methodName, String mappedName) { + return PatternMatchUtils.simpleMatch(mappedName, methodName); + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof NameMatchTransactionAttributeSource)) { + return false; + } + NameMatchTransactionAttributeSource otherTas = (NameMatchTransactionAttributeSource) other; + return ObjectUtils.nullSafeEquals(this.nameMap, otherTas.nameMap); + } + + @Override + public int hashCode() { + return NameMatchTransactionAttributeSource.class.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + this.nameMap; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/NoRollbackRuleAttribute.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/NoRollbackRuleAttribute.java new file mode 100644 index 000000000..c52bde0d2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/NoRollbackRuleAttribute.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +/** + * Tag subclass of {@link RollbackRuleAttribute} that has the opposite behavior + * to the {@code RollbackRuleAttribute} superclass. + * + * @author Rod Johnson + * @since 09.04.2003 + */ +@SuppressWarnings("serial") +public class NoRollbackRuleAttribute extends RollbackRuleAttribute { + + /** + * Create a new instance of the {@code NoRollbackRuleAttribute} class + * for the supplied {@link Throwable} class. + * @param clazz the {@code Throwable} class + * @see RollbackRuleAttribute#RollbackRuleAttribute(Class) + */ + public NoRollbackRuleAttribute(Class clazz) { + super(clazz); + } + + /** + * Create a new instance of the {@code NoRollbackRuleAttribute} class + * for the supplied {@code exceptionName}. + * @param exceptionName the exception name pattern + * @see RollbackRuleAttribute#RollbackRuleAttribute(String) + */ + public NoRollbackRuleAttribute(String exceptionName) { + super(exceptionName); + } + + @Override + public String toString() { + return "No" + super.toString(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/RollbackRuleAttribute.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/RollbackRuleAttribute.java new file mode 100644 index 000000000..aea8bf273 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/RollbackRuleAttribute.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.io.Serializable; + +import com.fr.third.springframework.util.Assert; + +/** + * Rule determining whether or not a given exception (and any subclasses) + * should cause a rollback. + * + *

Multiple such rules can be applied to determine whether a transaction + * should commit or rollback after an exception has been thrown. + * + * @author Rod Johnson + * @since 09.04.2003 + * @see NoRollbackRuleAttribute + */ +@SuppressWarnings("serial") +public class RollbackRuleAttribute implements Serializable{ + + /** + * The {@link RollbackRuleAttribute rollback rule} for + * {@link RuntimeException RuntimeExceptions}. + */ + public static final RollbackRuleAttribute ROLLBACK_ON_RUNTIME_EXCEPTIONS = + new RollbackRuleAttribute(RuntimeException.class); + + + /** + * Could hold exception, resolving class name but would always require FQN. + * This way does multiple string comparisons, but how often do we decide + * whether to roll back a transaction following an exception? + */ + private final String exceptionName; + + + /** + * Create a new instance of the {@code RollbackRuleAttribute} class. + *

This is the preferred way to construct a rollback rule that matches + * the supplied {@link Exception} class (and subclasses). + * @param clazz throwable class; must be {@link Throwable} or a subclass + * of {@code Throwable} + * @throws IllegalArgumentException if the supplied {@code clazz} is + * not a {@code Throwable} type or is {@code null} + */ + public RollbackRuleAttribute(Class clazz) { + Assert.notNull(clazz, "'clazz' cannot be null"); + if (!Throwable.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException( + "Cannot construct rollback rule from [" + clazz.getName() + "]: it's not a Throwable"); + } + this.exceptionName = clazz.getName(); + } + + /** + * Create a new instance of the {@code RollbackRuleAttribute} class + * for the given {@code exceptionName}. + *

This can be a substring, with no wildcard support at present. A value + * of "ServletException" would match + * {@code javax.servlet.ServletException} and subclasses, for example. + *

NB: Consider carefully how specific the pattern is, and + * whether to include package information (which is not mandatory). For + * example, "Exception" will match nearly anything, and will probably hide + * other rules. "java.lang.Exception" would be correct if "Exception" was + * meant to define a rule for all checked exceptions. With more unusual + * exception names such as "BaseBusinessException" there's no need to use a + * fully package-qualified name. + * @param exceptionName the exception name pattern; can also be a fully + * package-qualified class name + * @throws IllegalArgumentException if the supplied + * {@code exceptionName} is {@code null} or empty + */ + public RollbackRuleAttribute(String exceptionName) { + Assert.hasText(exceptionName, "'exceptionName' cannot be null or empty"); + this.exceptionName = exceptionName; + } + + + /** + * Return the pattern for the exception name. + */ + public String getExceptionName() { + return exceptionName; + } + + /** + * Return the depth of the superclass matching. + *

{@code 0} means {@code ex} matches exactly. Returns + * {@code -1} if there is no match. Otherwise, returns depth with the + * lowest depth winning. + */ + public int getDepth(Throwable ex) { + return getDepth(ex.getClass(), 0); + } + + + private int getDepth(Class exceptionClass, int depth) { + if (exceptionClass.getName().contains(this.exceptionName)) { + // Found it! + return depth; + } + // If we've gone as far as we can go and haven't found it... + if (exceptionClass.equals(Throwable.class)) { + return -1; + } + return getDepth(exceptionClass.getSuperclass(), depth + 1); + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof RollbackRuleAttribute)) { + return false; + } + RollbackRuleAttribute rhs = (RollbackRuleAttribute) other; + return this.exceptionName.equals(rhs.exceptionName); + } + + @Override + public int hashCode() { + return this.exceptionName.hashCode(); + } + + @Override + public String toString() { + return "RollbackRuleAttribute with pattern [" + this.exceptionName + "]"; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java new file mode 100644 index 000000000..cc831e4fd --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java @@ -0,0 +1,173 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * TransactionAttribute implementation that works out whether a given exception + * should cause transaction rollback by applying a number of rollback rules, + * both positive and negative. If no rules are relevant to the exception, it + * behaves like DefaultTransactionAttribute (rolling back on runtime exceptions). + * + *

{@link TransactionAttributeEditor} creates objects of this class. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 09.04.2003 + * @see TransactionAttributeEditor + */ +@SuppressWarnings("serial") +public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable { + + /** Prefix for rollback-on-exception rules in description strings */ + public static final String PREFIX_ROLLBACK_RULE = "-"; + + /** Prefix for commit-on-exception rules in description strings */ + public static final String PREFIX_COMMIT_RULE = "+"; + + + /** Static for optimal serializability */ + private static final Log logger = LogFactory.getLog(RuleBasedTransactionAttribute.class); + + private List rollbackRules; + + + /** + * Create a new RuleBasedTransactionAttribute, with default settings. + * Can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + * @see #setRollbackRules + */ + public RuleBasedTransactionAttribute() { + super(); + } + + /** + * Copy constructor. Definition can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + * @see #setRollbackRules + */ + public RuleBasedTransactionAttribute(RuleBasedTransactionAttribute other) { + super(other); + this.rollbackRules = new ArrayList(other.rollbackRules); + } + + /** + * Create a new DefaultTransactionAttribute with the the given + * propagation behavior. Can be modified through bean property setters. + * @param propagationBehavior one of the propagation constants in the + * TransactionDefinition interface + * @param rollbackRules the list of RollbackRuleAttributes to apply + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + */ + public RuleBasedTransactionAttribute(int propagationBehavior, List rollbackRules) { + super(propagationBehavior); + this.rollbackRules = rollbackRules; + } + + + /** + * Set the list of {@code RollbackRuleAttribute} objects + * (and/or {@code NoRollbackRuleAttribute} objects) to apply. + * @see RollbackRuleAttribute + * @see NoRollbackRuleAttribute + */ + public void setRollbackRules(List rollbackRules) { + this.rollbackRules = rollbackRules; + } + + /** + * Return the list of {@code RollbackRuleAttribute} objects + * (never {@code null}). + */ + public List getRollbackRules() { + if (this.rollbackRules == null) { + this.rollbackRules = new LinkedList(); + } + return this.rollbackRules; + } + + + /** + * Winning rule is the shallowest rule (that is, the closest in the + * inheritance hierarchy to the exception). If no rule applies (-1), + * return false. + * @see TransactionAttribute#rollbackOn(java.lang.Throwable) + */ + @Override + public boolean rollbackOn(Throwable ex) { + if (logger.isTraceEnabled()) { + logger.trace("Applying rules to determine whether transaction should rollback on " + ex); + } + + RollbackRuleAttribute winner = null; + int deepest = Integer.MAX_VALUE; + + if (this.rollbackRules != null) { + for (RollbackRuleAttribute rule : this.rollbackRules) { + int depth = rule.getDepth(ex); + if (depth >= 0 && depth < deepest) { + deepest = depth; + winner = rule; + } + } + } + + if (logger.isTraceEnabled()) { + logger.trace("Winning rollback rule is: " + winner); + } + + // User superclass behavior (rollback on unchecked) if no rule matches. + if (winner == null) { + logger.trace("No relevant rollback rule found: applying default rules"); + return super.rollbackOn(ex); + } + + return !(winner instanceof NoRollbackRuleAttribute); + } + + + @Override + public String toString() { + StringBuilder result = getAttributeDescription(); + if (this.rollbackRules != null) { + for (RollbackRuleAttribute rule : this.rollbackRules) { + String sign = (rule instanceof NoRollbackRuleAttribute ? PREFIX_COMMIT_RULE : PREFIX_ROLLBACK_RULE); + result.append(',').append(sign).append(rule.getExceptionName()); + } + } + return result.toString(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAspectSupport.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAspectSupport.java new file mode 100644 index 000000000..63cfdfee3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAspectSupport.java @@ -0,0 +1,667 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.springframework.beans.factory.BeanFactory; +import com.fr.third.springframework.beans.factory.BeanFactoryAware; +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; +import com.fr.third.springframework.core.NamedThreadLocal; +import com.fr.third.springframework.transaction.NoTransactionException; +import com.fr.third.springframework.transaction.PlatformTransactionManager; +import com.fr.third.springframework.transaction.TransactionStatus; +import com.fr.third.springframework.transaction.TransactionSystemException; +import com.fr.third.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; +import com.fr.third.springframework.transaction.support.TransactionCallback; +import com.fr.third.springframework.util.StringUtils; + +import java.lang.reflect.Method; +import java.util.Properties; + +/** + * Base class for transactional aspects, such as the {@link TransactionInterceptor} + * or an AspectJ aspect. + * + *

This enables the underlying Spring transaction infrastructure to be used easily + * to implement an aspect for any aspect system. + * + *

Subclasses are responsible for calling methods in this class in the correct order. + * + *

If no transaction name has been specified in the {@code TransactionAttribute}, + * the exposed name will be the {@code fully-qualified class name + "." + method name} + * (by default). + * + *

Uses the Strategy design pattern. A {@code PlatformTransactionManager} + * implementation will perform the actual transaction management, and a + * {@code TransactionAttributeSource} is used for determining transaction definitions. + * + *

A transaction aspect is serializable if its {@code PlatformTransactionManager} + * and {@code TransactionAttributeSource} are serializable. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + * @see #setTransactionManager + * @see #setTransactionAttributes + * @see #setTransactionAttributeSource + */ +public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { + + // NOTE: This class must not implement Serializable because it serves as base + // class for AspectJ aspects (which are not allowed to implement Serializable)! + + /** + * Holder to support the {@code currentTransactionStatus()} method, + * and to support communication between different cooperating advices + * (e.g. before and after advice) if the aspect involves more than a + * single method (as will be the case for around advice). + */ + private static final ThreadLocal transactionInfoHolder = + new NamedThreadLocal("Current aspect-driven transaction"); + + + /** + * Subclasses can use this to return the current TransactionInfo. + * Only subclasses that cannot handle all operations in one method, + * such as an AspectJ aspect involving distinct before and after advice, + * need to use this mechanism to get at the current TransactionInfo. + * An around advice such as an AOP Alliance MethodInterceptor can hold a + * reference to the TransactionInfo throughout the aspect method. + *

A TransactionInfo will be returned even if no transaction was created. + * The {@code TransactionInfo.hasTransaction()} method can be used to query this. + *

To find out about specific transaction characteristics, consider using + * TransactionSynchronizationManager's {@code isSynchronizationActive()} + * and/or {@code isActualTransactionActive()} methods. + * @return TransactionInfo bound to this thread, or {@code null} if none + * @see TransactionInfo#hasTransaction() + * @see com.fr.third.springframework.transaction.support.TransactionSynchronizationManager#isSynchronizationActive() + * @see com.fr.third.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive() + */ + protected static TransactionInfo currentTransactionInfo() throws NoTransactionException { + return transactionInfoHolder.get(); + } + + /** + * Return the transaction status of the current method invocation. + * Mainly intended for code that wants to set the current transaction + * rollback-only but not throw an application exception. + * @throws NoTransactionException if the transaction info cannot be found, + * because the method was invoked outside an AOP invocation context + */ + public static TransactionStatus currentTransactionStatus() throws NoTransactionException { + TransactionInfo info = currentTransactionInfo(); + if (info == null) { + throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope"); + } + return currentTransactionInfo().transactionStatus; + } + + + protected final Log logger = LogFactory.getLog(getClass()); + + private String transactionManagerBeanName; + + private PlatformTransactionManager transactionManager; + + private TransactionAttributeSource transactionAttributeSource; + + private BeanFactory beanFactory; + + + /** + * Specify the name of the default transaction manager bean. + */ + public void setTransactionManagerBeanName(String transactionManagerBeanName) { + this.transactionManagerBeanName = transactionManagerBeanName; + } + + /** + * Return the name of the default transaction manager bean. + */ + protected final String getTransactionManagerBeanName() { + return this.transactionManagerBeanName; + } + + /** + * Specify the target transaction manager. + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Return the transaction manager, if specified. + */ + public PlatformTransactionManager getTransactionManager() { + return this.transactionManager; + } + + /** + * Set properties with method names as keys and transaction attribute + * descriptors (parsed via TransactionAttributeEditor) as values: + * e.g. key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly". + *

Note: Method names are always applied to the target class, + * no matter if defined in an interface or the class itself. + *

Internally, a NameMatchTransactionAttributeSource will be + * created from the given properties. + * @see #setTransactionAttributeSource + * @see TransactionAttributeEditor + * @see NameMatchTransactionAttributeSource + */ + public void setTransactionAttributes(Properties transactionAttributes) { + NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource(); + tas.setProperties(transactionAttributes); + this.transactionAttributeSource = tas; + } + + /** + * Set multiple transaction attribute sources which are used to find transaction + * attributes. Will build a CompositeTransactionAttributeSource for the given sources. + * @see CompositeTransactionAttributeSource + * @see MethodMapTransactionAttributeSource + * @see NameMatchTransactionAttributeSource + * @see com.fr.third.springframework.transaction.annotation.AnnotationTransactionAttributeSource + */ + public void setTransactionAttributeSources(TransactionAttributeSource[] transactionAttributeSources) { + this.transactionAttributeSource = new CompositeTransactionAttributeSource(transactionAttributeSources); + } + + /** + * Set the transaction attribute source which is used to find transaction + * attributes. If specifying a String property value, a PropertyEditor + * will create a MethodMapTransactionAttributeSource from the value. + * @see TransactionAttributeSourceEditor + * @see MethodMapTransactionAttributeSource + * @see NameMatchTransactionAttributeSource + * @see com.fr.third.springframework.transaction.annotation.AnnotationTransactionAttributeSource + */ + public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) { + this.transactionAttributeSource = transactionAttributeSource; + } + + /** + * Return the transaction attribute source. + */ + public TransactionAttributeSource getTransactionAttributeSource() { + return this.transactionAttributeSource; + } + + /** + * Set the BeanFactory to use for retrieving PlatformTransactionManager beans. + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + /** + * Return the BeanFactory to use for retrieving PlatformTransactionManager beans. + */ + protected final BeanFactory getBeanFactory() { + return this.beanFactory; + } + + /** + * Check that required properties were set. + */ + @Override + public void afterPropertiesSet() { + if (this.transactionManager == null && this.beanFactory == null) { + throw new IllegalStateException( + "Setting the property 'transactionManager' or running in a ListableBeanFactory is required"); + } + if (this.transactionAttributeSource == null) { + throw new IllegalStateException( + "Either 'transactionAttributeSource' or 'transactionAttributes' is required: " + + "If there are no transactional methods, then don't use a transaction aspect."); + } + } + + + /** + * General delegate for around-advice-based subclasses, delegating to several other template + * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager} + * as well as regular {@link PlatformTransactionManager} implementations. + * @param method the Method being invoked + * @param targetClass the target class that we're invoking the method on + * @param invocation the callback to use for proceeding with the target invocation + * @return the return value of the method, if any + * @throws Throwable propagated from the target invocation + */ + protected Object invokeWithinTransaction(Method method, Class targetClass, final InvocationCallback invocation) + throws Throwable { + + // If the transaction attribute is null, the method is non-transactional. + final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); + final PlatformTransactionManager tm = determineTransactionManager(txAttr); + final String joinpointIdentification = methodIdentification(method, targetClass); + + if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { + // Standard transaction demarcation with getTransaction and commit/rollback calls. + TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); + Object retVal = null; + try { + // This is an around advice: Invoke the next interceptor in the chain. + // This will normally result in a target object being invoked. + retVal = invocation.proceedWithInvocation(); + } + catch (Throwable ex) { + // target invocation exception + completeTransactionAfterThrowing(txInfo, ex); + throw ex; + } + finally { + cleanupTransactionInfo(txInfo); + } + commitTransactionAfterReturning(txInfo); + return retVal; + } + + else { + // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. + try { + Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, + new TransactionCallback() { + @Override + public Object doInTransaction(TransactionStatus status) { + TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); + try { + return invocation.proceedWithInvocation(); + } + catch (Throwable ex) { + if (txAttr.rollbackOn(ex)) { + // A RuntimeException: will lead to a rollback. + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + else { + throw new ThrowableHolderException(ex); + } + } + else { + // A normal return value: will lead to a commit. + return new ThrowableHolder(ex); + } + } + finally { + cleanupTransactionInfo(txInfo); + } + } + }); + + // Check result: It might indicate a Throwable to rethrow. + if (result instanceof ThrowableHolder) { + throw ((ThrowableHolder) result).getThrowable(); + } + else { + return result; + } + } + catch (ThrowableHolderException ex) { + throw ex.getCause(); + } + } + } + + /** + * Determine the specific transaction manager to use for the given transaction. + */ + protected PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr) { + if (this.transactionManager != null || this.beanFactory == null || txAttr == null) { + return this.transactionManager; + } + String qualifier = txAttr.getQualifier(); + if (StringUtils.hasLength(qualifier)) { + return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, PlatformTransactionManager.class, qualifier); + } + else if (this.transactionManagerBeanName != null) { + return this.beanFactory.getBean(this.transactionManagerBeanName, PlatformTransactionManager.class); + } + else { + return this.beanFactory.getBean(PlatformTransactionManager.class); + } + } + + /** + * Convenience method to return a String representation of this Method + * for use in logging. Can be overridden in subclasses to provide a + * different identifier for the given method. + * @param method the method we're interested in + * @param targetClass the class that the method is being invoked on + * @return a String representation identifying this method + * @see com.fr.third.springframework.util.ClassUtils#getQualifiedMethodName + */ + protected String methodIdentification(Method method, Class targetClass) { + String simpleMethodId = methodIdentification(method); + if (simpleMethodId != null) { + return simpleMethodId; + } + return (targetClass != null ? targetClass : method.getDeclaringClass()).getName() + "." + method.getName(); + } + + /** + * Convenience method to return a String representation of this Method + * for use in logging. Can be overridden in subclasses to provide a + * different identifier for the given method. + * @param method the method we're interested in + * @return a String representation identifying this method + * @deprecated in favor of {@link #methodIdentification(Method, Class)} + */ + @Deprecated + protected String methodIdentification(Method method) { + return null; + } + + /** + * Create a transaction if necessary, based on the given method and class. + *

Performs a default TransactionAttribute lookup for the given method. + * @param method the method about to execute + * @param targetClass the class that the method is being invoked on + * @return a TransactionInfo object, whether or not a transaction was created. + * The {@code hasTransaction()} method on TransactionInfo can be used to + * tell if there was a transaction created. + * @see #getTransactionAttributeSource() + * @deprecated in favor of + * {@link #createTransactionIfNecessary(PlatformTransactionManager, TransactionAttribute, String)} + */ + @Deprecated + protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) { + // If the transaction attribute is null, the method is non-transactional. + TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); + PlatformTransactionManager tm = determineTransactionManager(txAttr); + return createTransactionIfNecessary(tm, txAttr, methodIdentification(method, targetClass)); + } + + /** + * Create a transaction if necessary based on the given TransactionAttribute. + *

Allows callers to perform custom TransactionAttribute lookups through + * the TransactionAttributeSource. + * @param txAttr the TransactionAttribute (may be {@code null}) + * @param joinpointIdentification the fully qualified method name + * (used for monitoring and logging purposes) + * @return a TransactionInfo object, whether or not a transaction was created. + * The {@code hasTransaction()} method on TransactionInfo can be used to + * tell if there was a transaction created. + * @see #getTransactionAttributeSource() + */ + @SuppressWarnings("serial") + protected TransactionInfo createTransactionIfNecessary( + PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) { + + // If no name specified, apply method identification as transaction name. + if (txAttr != null && txAttr.getName() == null) { + txAttr = new DelegatingTransactionAttribute(txAttr) { + @Override + public String getName() { + return joinpointIdentification; + } + }; + } + + TransactionStatus status = null; + if (txAttr != null) { + if (tm != null) { + status = tm.getTransaction(txAttr); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + + "] because no transaction manager has been configured"); + } + } + } + return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); + } + + /** + * Prepare a TransactionInfo for the given attribute and status object. + * @param txAttr the TransactionAttribute (may be {@code null}) + * @param joinpointIdentification the fully qualified method name + * (used for monitoring and logging purposes) + * @param status the TransactionStatus for the current transaction + * @return the prepared TransactionInfo object + */ + protected TransactionInfo prepareTransactionInfo(PlatformTransactionManager tm, + TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) { + + TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification); + if (txAttr != null) { + // We need a transaction for this method + if (logger.isTraceEnabled()) { + logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]"); + } + // The transaction manager will flag an error if an incompatible tx already exists + txInfo.newTransactionStatus(status); + } + else { + // The TransactionInfo.hasTransaction() method will return + // false. We created it only to preserve the integrity of + // the ThreadLocal stack maintained in this class. + if (logger.isTraceEnabled()) + logger.trace("Don't need to create transaction for [" + joinpointIdentification + + "]: This method isn't transactional."); + } + + // We always bind the TransactionInfo to the thread, even if we didn't create + // a new transaction here. This guarantees that the TransactionInfo stack + // will be managed correctly even if no transaction was created by this aspect. + txInfo.bindToThread(); + return txInfo; + } + + /** + * Execute after successful completion of call, but not after an exception was handled. + * Do nothing if we didn't create a transaction. + * @param txInfo information about the current transaction + */ + protected void commitTransactionAfterReturning(TransactionInfo txInfo) { + if (txInfo != null && txInfo.hasTransaction()) { + if (logger.isTraceEnabled()) { + logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); + } + txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); + } + } + + /** + * Handle a throwable, completing the transaction. + * We may commit or roll back, depending on the configuration. + * @param txInfo information about the current transaction + * @param ex throwable encountered + */ + protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) { + if (txInfo != null && txInfo.hasTransaction()) { + if (logger.isTraceEnabled()) { + logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + + "] after exception: " + ex); + } + if (txInfo.transactionAttribute.rollbackOn(ex)) { + try { + txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); + } + catch (TransactionSystemException ex2) { + logger.error("Application exception overridden by rollback exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } + catch (RuntimeException ex2) { + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } + catch (Error err) { + logger.error("Application exception overridden by rollback error", ex); + throw err; + } + } + else { + // We don't roll back on this exception. + // Will still roll back if TransactionStatus.isRollbackOnly() is true. + try { + txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); + } + catch (TransactionSystemException ex2) { + logger.error("Application exception overridden by commit exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } + catch (RuntimeException ex2) { + logger.error("Application exception overridden by commit exception", ex); + throw ex2; + } + catch (Error err) { + logger.error("Application exception overridden by commit error", ex); + throw err; + } + } + } + } + + /** + * Reset the TransactionInfo ThreadLocal. + *

Call this in all cases: exception or normal return! + * @param txInfo information about the current transaction (may be {@code null}) + */ + protected void cleanupTransactionInfo(TransactionInfo txInfo) { + if (txInfo != null) { + txInfo.restoreThreadLocalStatus(); + } + } + + + /** + * Opaque object used to hold Transaction information. Subclasses + * must pass it back to methods on this class, but not see its internals. + */ + protected final class TransactionInfo { + + private final PlatformTransactionManager transactionManager; + + private final TransactionAttribute transactionAttribute; + + private final String joinpointIdentification; + + private TransactionStatus transactionStatus; + + private TransactionInfo oldTransactionInfo; + + public TransactionInfo(PlatformTransactionManager transactionManager, + TransactionAttribute transactionAttribute, String joinpointIdentification) { + this.transactionManager = transactionManager; + this.transactionAttribute = transactionAttribute; + this.joinpointIdentification = joinpointIdentification; + } + + public PlatformTransactionManager getTransactionManager() { + return this.transactionManager; + } + + public TransactionAttribute getTransactionAttribute() { + return this.transactionAttribute; + } + + /** + * Return a String representation of this joinpoint (usually a Method call) + * for use in logging. + */ + public String getJoinpointIdentification() { + return this.joinpointIdentification; + } + + public void newTransactionStatus(TransactionStatus status) { + this.transactionStatus = status; + } + + public TransactionStatus getTransactionStatus() { + return this.transactionStatus; + } + + /** + * Return whether a transaction was created by this aspect, + * or whether we just have a placeholder to keep ThreadLocal stack integrity. + */ + public boolean hasTransaction() { + return (this.transactionStatus != null); + } + + private void bindToThread() { + // Expose current TransactionStatus, preserving any existing TransactionStatus + // for restoration after this transaction is complete. + this.oldTransactionInfo = transactionInfoHolder.get(); + transactionInfoHolder.set(this); + } + + private void restoreThreadLocalStatus() { + // Use stack to restore old transaction TransactionInfo. + // Will be null if none was set. + transactionInfoHolder.set(this.oldTransactionInfo); + } + + @Override + public String toString() { + return this.transactionAttribute.toString(); + } + } + + + /** + * Simple callback interface for proceeding with the target invocation. + * Concrete interceptors/aspects adapt this to their invocation mechanism. + */ + protected interface InvocationCallback { + + Object proceedWithInvocation() throws Throwable; + } + + + /** + * Internal holder class for a Throwable, used as a return value + * from a TransactionCallback (to be subsequently unwrapped again). + */ + private static class ThrowableHolder { + + private final Throwable throwable; + + public ThrowableHolder(Throwable throwable) { + this.throwable = throwable; + } + + public final Throwable getThrowable() { + return this.throwable; + } + } + + + /** + * Internal holder class for a Throwable, used as a RuntimeException to be + * thrown from a TransactionCallback (and subsequently unwrapped again). + */ + @SuppressWarnings("serial") + private static class ThrowableHolderException extends RuntimeException { + + public ThrowableHolderException(Throwable throwable) { + super(throwable); + } + + @Override + public String toString() { + return getCause().toString(); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttribute.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttribute.java new file mode 100644 index 000000000..da1a7b186 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttribute.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import com.fr.third.springframework.transaction.TransactionDefinition; + +/** + * This interface adds a {@code rollbackOn} specification to {@link TransactionDefinition}. + * As custom {@code rollbackOn} is only possible with AOP, this class resides + * in the AOP transaction package. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 16.03.2003 + * @see DefaultTransactionAttribute + * @see RuleBasedTransactionAttribute + */ +public interface TransactionAttribute extends TransactionDefinition { + + /** + * Return a qualifier value associated with this transaction attribute. + *

This may be used for choosing a corresponding transaction manager + * to process this specific transaction. + */ + String getQualifier(); + + /** + * Should we roll back on the given exception? + * @param ex the exception to evaluate + * @return whether to perform a rollback or not + */ + boolean rollbackOn(Throwable ex); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeEditor.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeEditor.java new file mode 100644 index 000000000..9e333188a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeEditor.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.beans.PropertyEditorSupport; + +import com.fr.third.springframework.util.StringUtils; + +/** + * PropertyEditor for {@link TransactionAttribute} objects. Accepts a String of form + *

{@code PROPAGATION_NAME, ISOLATION_NAME, readOnly, timeout_NNNN,+Exception1,-Exception2} + *

where only propagation code is required. For example: + *

{@code PROPAGATION_MANDATORY, ISOLATION_DEFAULT} + * + *

The tokens can be in any order. Propagation and isolation codes + * must use the names of the constants in the TransactionDefinition class. Timeout values + * are in seconds. If no timeout is specified, the transaction manager will apply a default + * timeout specific to the particular transaction manager. + * + *

A "+" before an exception name substring indicates that transactions should commit + * even if this exception is thrown; a "-" that they should roll back. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 24.04.2003 + * @see com.fr.third.springframework.transaction.TransactionDefinition + * @see com.fr.third.springframework.core.Constants + */ +public class TransactionAttributeEditor extends PropertyEditorSupport { + + /** + * Format is PROPAGATION_NAME,ISOLATION_NAME,readOnly,timeout_NNNN,+Exception1,-Exception2. + * Null or the empty string means that the method is non transactional. + * @see java.beans.PropertyEditor#setAsText(java.lang.String) + */ + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (StringUtils.hasLength(text)) { + // tokenize it with "," + String[] tokens = StringUtils.commaDelimitedListToStringArray(text); + RuleBasedTransactionAttribute attr = new RuleBasedTransactionAttribute(); + for (int i = 0; i < tokens.length; i++) { + // Trim leading and trailing whitespace. + String token = StringUtils.trimWhitespace(tokens[i].trim()); + // Check whether token contains illegal whitespace within text. + if (StringUtils.containsWhitespace(token)) { + throw new IllegalArgumentException( + "Transaction attribute token contains illegal whitespace: [" + token + "]"); + } + // Check token type. + if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_PROPAGATION)) { + attr.setPropagationBehaviorName(token); + } + else if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_ISOLATION)) { + attr.setIsolationLevelName(token); + } + else if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_TIMEOUT)) { + String value = token.substring(DefaultTransactionAttribute.PREFIX_TIMEOUT.length()); + attr.setTimeout(Integer.parseInt(value)); + } + else if (token.equals(RuleBasedTransactionAttribute.READ_ONLY_MARKER)) { + attr.setReadOnly(true); + } + else if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_COMMIT_RULE)) { + attr.getRollbackRules().add(new NoRollbackRuleAttribute(token.substring(1))); + } + else if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_ROLLBACK_RULE)) { + attr.getRollbackRules().add(new RollbackRuleAttribute(token.substring(1))); + } + else { + throw new IllegalArgumentException("Invalid transaction attribute token: [" + token + "]"); + } + } + setValue(attr); + } + else { + setValue(null); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSource.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSource.java new file mode 100644 index 000000000..f7dd2021c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSource.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.lang.reflect.Method; + +/** + * Strategy interface used by {@link TransactionInterceptor} for metadata retrieval. + * + *

Implementations know how to source transaction attributes, whether from configuration, + * metadata attributes at source level (such as Java 5 annotations), or anywhere else. + * + * @author Rod Johnson + * @since 15.04.2003 + * @see TransactionInterceptor#setTransactionAttributeSource + * @see TransactionProxyFactoryBean#setTransactionAttributeSource + * @see com.fr.third.springframework.transaction.annotation.AnnotationTransactionAttributeSource + */ +public interface TransactionAttributeSource { + + /** + * Return the transaction attribute for the given method, + * or {@code null} if the method is non-transactional. + * @param method the method to introspect + * @param targetClass the target class. May be {@code null}, + * in which case the declaring class of the method must be used. + * @return TransactionAttribute the matching transaction attribute, + * or {@code null} if none found + */ + TransactionAttribute getTransactionAttribute(Method method, Class targetClass); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSourceAdvisor.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSourceAdvisor.java new file mode 100644 index 000000000..54238d000 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSourceAdvisor.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import org.aopalliance.aop.Advice; + +import com.fr.third.springframework.aop.ClassFilter; +import com.fr.third.springframework.aop.Pointcut; +import com.fr.third.springframework.aop.support.AbstractPointcutAdvisor; + +/** + * Advisor driven by a {@link TransactionAttributeSource}, used to include + * a {@link TransactionInterceptor} only for methods that are transactional. + * + *

Because the AOP framework caches advice calculations, this is normally + * faster than just letting the TransactionInterceptor run and find out + * itself that it has no work to do. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #setTransactionInterceptor + * @see TransactionProxyFactoryBean + */ +@SuppressWarnings("serial") +public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor { + + private TransactionInterceptor transactionInterceptor; + + private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() { + @Override + protected TransactionAttributeSource getTransactionAttributeSource() { + return (transactionInterceptor != null ? transactionInterceptor.getTransactionAttributeSource() : null); + } + }; + + + /** + * Create a new TransactionAttributeSourceAdvisor. + */ + public TransactionAttributeSourceAdvisor() { + } + + /** + * Create a new TransactionAttributeSourceAdvisor. + * @param interceptor the transaction interceptor to use for this advisor + */ + public TransactionAttributeSourceAdvisor(TransactionInterceptor interceptor) { + setTransactionInterceptor(interceptor); + } + + + /** + * Set the transaction interceptor to use for this advisor. + */ + public void setTransactionInterceptor(TransactionInterceptor interceptor) { + this.transactionInterceptor = interceptor; + } + + /** + * Set the {@link ClassFilter} to use for this pointcut. + * Default is {@link ClassFilter#TRUE}. + */ + public void setClassFilter(ClassFilter classFilter) { + this.pointcut.setClassFilter(classFilter); + } + + + @Override + public Advice getAdvice() { + return this.transactionInterceptor; + } + + @Override + public Pointcut getPointcut() { + return this.pointcut; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java new file mode 100644 index 000000000..2764b8c81 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.beans.PropertyEditorSupport; +import java.util.Enumeration; +import java.util.Properties; + +import com.fr.third.springframework.beans.propertyeditors.PropertiesEditor; +import com.fr.third.springframework.util.StringUtils; + +/** + * Property editor that converts a String into a {@link TransactionAttributeSource}. + * The transaction attribute string must be parseable by the + * {@link TransactionAttributeEditor} in this package. + * + *

Strings are in property syntax, with the form:
+ * {@code FQCN.methodName=<transaction attribute string>} + * + *

For example:
+ * {@code com.mycompany.mycode.MyClass.myMethod=PROPAGATION_MANDATORY,ISOLATION_DEFAULT} + * + *

NOTE: The specified class must be the one where the methods are + * defined; in case of implementing an interface, the interface class name. + * + *

Note: Will register all overloaded methods for a given name. + * Does not support explicit registration of certain overloaded methods. + * Supports "xxx*" mappings, e.g. "notify*" for "notify" and "notifyAll". + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 26.04.2003 + * @see TransactionAttributeEditor + */ +public class TransactionAttributeSourceEditor extends PropertyEditorSupport { + + @Override + public void setAsText(String text) throws IllegalArgumentException { + MethodMapTransactionAttributeSource source = new MethodMapTransactionAttributeSource(); + if (StringUtils.hasLength(text)) { + // Use properties editor to tokenize the hold string. + PropertiesEditor propertiesEditor = new PropertiesEditor(); + propertiesEditor.setAsText(text); + Properties props = (Properties) propertiesEditor.getValue(); + + // Now we have properties, process each one individually. + TransactionAttributeEditor tae = new TransactionAttributeEditor(); + Enumeration propNames = props.propertyNames(); + while (propNames.hasMoreElements()) { + String name = (String) propNames.nextElement(); + String value = props.getProperty(name); + // Convert value to a transaction attribute. + tae.setAsText(value); + TransactionAttribute attr = (TransactionAttribute) tae.getValue(); + // Register name and attribute. + source.addTransactionalMethod(name, attr); + } + } + setValue(source); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java new file mode 100644 index 000000000..49c50ca3e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import com.fr.third.springframework.aop.support.StaticMethodMatcherPointcut; +import com.fr.third.springframework.util.ObjectUtils; + +/** + * Inner class that implements a Pointcut that matches if the underlying + * {@link TransactionAttributeSource} has an attribute for a given method. + * + * @author Juergen Hoeller + * @since 2.5.5 + */ +@SuppressWarnings("serial") +abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { + + @Override + public boolean matches(Method method, Class targetClass) { + TransactionAttributeSource tas = getTransactionAttributeSource(); + return (tas == null || tas.getTransactionAttribute(method, targetClass) != null); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof TransactionAttributeSourcePointcut)) { + return false; + } + TransactionAttributeSourcePointcut otherPc = (TransactionAttributeSourcePointcut) other; + return ObjectUtils.nullSafeEquals(getTransactionAttributeSource(), otherPc.getTransactionAttributeSource()); + } + + @Override + public int hashCode() { + return TransactionAttributeSourcePointcut.class.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + getTransactionAttributeSource(); + } + + + /** + * Obtain the underlying TransactionAttributeSource (may be {@code null}). + * To be implemented by subclasses. + */ + protected abstract TransactionAttributeSource getTransactionAttributeSource(); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionInterceptor.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionInterceptor.java new file mode 100644 index 000000000..1374aae89 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionInterceptor.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import com.fr.third.springframework.aop.support.AopUtils; +import com.fr.third.springframework.beans.factory.BeanFactory; +import com.fr.third.springframework.transaction.PlatformTransactionManager; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Properties; + +/** + * AOP Alliance MethodInterceptor for declarative transaction + * management using the common Spring transaction infrastructure + * ({@link com.fr.third.springframework.transaction.PlatformTransactionManager}). + * + *

Derives from the {@link TransactionAspectSupport} class which + * contains the integration with Spring's underlying transaction API. + * TransactionInterceptor simply calls the relevant superclass methods + * such as {@link #invokeWithinTransaction} in the correct order. + * + *

TransactionInterceptors are thread-safe. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see TransactionProxyFactoryBean + * @see com.fr.third.springframework.aop.framework.ProxyFactoryBean + * @see com.fr.third.springframework.aop.framework.ProxyFactory + */ +@SuppressWarnings("serial") +public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable { + + /** + * Create a new TransactionInterceptor. + *

Transaction manager and transaction attributes still need to be set. + * @see #setTransactionManager + * @see #setTransactionAttributes(java.util.Properties) + * @see #setTransactionAttributeSource(TransactionAttributeSource) + */ + public TransactionInterceptor() { + } + + /** + * Create a new TransactionInterceptor. + * @param ptm the transaction manager to perform the actual transaction management + * @param attributes the transaction attributes in properties format + * @see #setTransactionManager + * @see #setTransactionAttributes(java.util.Properties) + */ + public TransactionInterceptor(PlatformTransactionManager ptm, Properties attributes) { + setTransactionManager(ptm); + setTransactionAttributes(attributes); + } + + /** + * Create a new TransactionInterceptor. + * @param ptm the transaction manager to perform the actual transaction management + * @param tas the attribute source to be used to find transaction attributes + * @see #setTransactionManager + * @see #setTransactionAttributeSource(TransactionAttributeSource) + */ + public TransactionInterceptor(PlatformTransactionManager ptm, TransactionAttributeSource tas) { + setTransactionManager(ptm); + setTransactionAttributeSource(tas); + } + + + @Override + public Object invoke(final MethodInvocation invocation) throws Throwable { + // Work out the target class: may be {@code null}. + // The TransactionAttributeSource should be passed the target class + // as well as the method, which may be from an interface. + Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); + + // Adapt to TransactionAspectSupport's invokeWithinTransaction... + return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() { + @Override + public Object proceedWithInvocation() throws Throwable { + return invocation.proceed(); + } + }); + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void writeObject(ObjectOutputStream oos) throws IOException { + // Rely on default serialization, although this class itself doesn't carry state anyway... + oos.defaultWriteObject(); + + // Deserialize superclass fields. + oos.writeObject(getTransactionManagerBeanName()); + oos.writeObject(getTransactionManager()); + oos.writeObject(getTransactionAttributeSource()); + oos.writeObject(getBeanFactory()); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization, although this class itself doesn't carry state anyway... + ois.defaultReadObject(); + + // Serialize all relevant superclass fields. + // Superclass can't implement Serializable because it also serves as base class + // for AspectJ aspects (which are not allowed to implement Serializable)! + setTransactionManagerBeanName((String) ois.readObject()); + setTransactionManager((PlatformTransactionManager) ois.readObject()); + setTransactionAttributeSource((TransactionAttributeSource) ois.readObject()); + setBeanFactory((BeanFactory) ois.readObject()); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionProxyFactoryBean.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionProxyFactoryBean.java new file mode 100644 index 000000000..60fb4f857 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/TransactionProxyFactoryBean.java @@ -0,0 +1,202 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.interceptor; + +import java.util.Properties; + +import com.fr.third.springframework.aop.Pointcut; +import com.fr.third.springframework.aop.framework.AbstractSingletonProxyFactoryBean; +import com.fr.third.springframework.aop.support.DefaultPointcutAdvisor; +import com.fr.third.springframework.beans.factory.BeanFactory; +import com.fr.third.springframework.beans.factory.BeanFactoryAware; +import com.fr.third.springframework.beans.factory.FactoryBean; +import com.fr.third.springframework.beans.factory.ListableBeanFactory; +import com.fr.third.springframework.transaction.PlatformTransactionManager; + +/** + * Proxy factory bean for simplified declarative transaction handling. + * This is a convenient alternative to a standard AOP + * {@link com.fr.third.springframework.aop.framework.ProxyFactoryBean} + * with a separate {@link TransactionInterceptor} definition. + * + *

HISTORICAL NOTE: This class was originally designed to cover the + * typical case of declarative transaction demarcation: namely, wrapping a singleton + * target object with a transactional proxy, proxying all the interfaces that the target + * implements. However, in Spring versions 2.0 and beyond, the functionality provided here + * is superseded by the more convenient {@code tx:} XML namespace. See the declarative transaction management section of the + * Spring reference documentation to understand the modern options for managing + * transactions in Spring applications. For these reasons, users should favor of + * the {@code tx:} XML namespace as well as + * the @{@link com.fr.third.springframework.transaction.annotation.Transactional Transactional} + * and @{@link com.fr.third.springframework.transaction.annotation.EnableTransactionManagement + * EnableTransactionManagement} annotations. + * + *

There are three main properties that need to be specified: + *

    + *
  • "transactionManager": the {@link PlatformTransactionManager} implementation to use + * (for example, a {@link com.fr.third.springframework.transaction.jta.JtaTransactionManager} instance) + *
  • "target": the target object that a transactional proxy should be created for + *
  • "transactionAttributes": the transaction attributes (for example, propagation + * behavior and "readOnly" flag) per target method name (or method name pattern) + *
+ * + *

If the "transactionManager" property is not set explicitly and this {@link FactoryBean} + * is running in a {@link ListableBeanFactory}, a single matching bean of type + * {@link PlatformTransactionManager} will be fetched from the {@link BeanFactory}. + * + *

In contrast to {@link TransactionInterceptor}, the transaction attributes are + * specified as properties, with method names as keys and transaction attribute + * descriptors as values. Method names are always applied to the target class. + * + *

Internally, a {@link TransactionInterceptor} instance is used, but the user of this + * class does not have to care. Optionally, a method pointcut can be specified + * to cause conditional invocation of the underlying {@link TransactionInterceptor}. + * + *

The "preInterceptors" and "postInterceptors" properties can be set to add + * additional interceptors to the mix, like + * {@link com.fr.third.springframework.aop.interceptor.PerformanceMonitorInterceptor}. + * + *

HINT: This class is often used with parent / child bean definitions. + * Typically, you will define the transaction manager and default transaction + * attributes (for method name patterns) in an abstract parent bean definition, + * deriving concrete child bean definitions for specific target objects. + * This reduces the per-bean definition effort to a minimum. + * + *

+ * {@code
+ * 
+ *   
+ *   
+ *     
+ *       PROPAGATION_REQUIRED
+ *       PROPAGATION_REQUIRED
+ *       PROPAGATION_REQUIRED,readOnly
+ *     
+ *   
+ * 
+ *
+ * 
+ *   
+ * 
+ *
+ * 
+ *   
+ * }
+ * + * @author Juergen Hoeller + * @author Dmitriy Kopylenko + * @author Rod Johnson + * @author Chris Beams + * @since 21.08.2003 + * @see #setTransactionManager + * @see #setTarget + * @see #setTransactionAttributes + * @see TransactionInterceptor + * @see com.fr.third.springframework.aop.framework.ProxyFactoryBean + */ +@SuppressWarnings("serial") +public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBean + implements BeanFactoryAware { + + private final TransactionInterceptor transactionInterceptor = new TransactionInterceptor(); + + private Pointcut pointcut; + + + /** + * Set the transaction manager. This will perform actual + * transaction management: This class is just a way of invoking it. + * @see TransactionInterceptor#setTransactionManager + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionInterceptor.setTransactionManager(transactionManager); + } + + /** + * Set properties with method names as keys and transaction attribute + * descriptors (parsed via TransactionAttributeEditor) as values: + * e.g. key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly". + *

Note: Method names are always applied to the target class, + * no matter if defined in an interface or the class itself. + *

Internally, a NameMatchTransactionAttributeSource will be + * created from the given properties. + * @see #setTransactionAttributeSource + * @see TransactionInterceptor#setTransactionAttributes + * @see TransactionAttributeEditor + * @see NameMatchTransactionAttributeSource + */ + public void setTransactionAttributes(Properties transactionAttributes) { + this.transactionInterceptor.setTransactionAttributes(transactionAttributes); + } + + /** + * Set the transaction attribute source which is used to find transaction + * attributes. If specifying a String property value, a PropertyEditor + * will create a MethodMapTransactionAttributeSource from the value. + * @see #setTransactionAttributes + * @see TransactionInterceptor#setTransactionAttributeSource + * @see TransactionAttributeSourceEditor + * @see MethodMapTransactionAttributeSource + * @see NameMatchTransactionAttributeSource + * @see com.fr.third.springframework.transaction.annotation.AnnotationTransactionAttributeSource + */ + public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) { + this.transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource); + } + + /** + * Set a pointcut, i.e a bean that can cause conditional invocation + * of the TransactionInterceptor depending on method and attributes passed. + * Note: Additional interceptors are always invoked. + * @see #setPreInterceptors + * @see #setPostInterceptors + */ + public void setPointcut(Pointcut pointcut) { + this.pointcut = pointcut; + } + + /** + * This callback is optional: If running in a BeanFactory and no transaction + * manager has been set explicitly, a single matching bean of type + * {@link PlatformTransactionManager} will be fetched from the BeanFactory. + * @see com.fr.third.springframework.beans.factory.BeanFactory#getBean(Class) + * @see com.fr.third.springframework.transaction.PlatformTransactionManager + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.transactionInterceptor.setBeanFactory(beanFactory); + } + + + /** + * Creates an advisor for this FactoryBean's TransactionInterceptor. + */ + @Override + protected Object createMainInterceptor() { + this.transactionInterceptor.afterPropertiesSet(); + if (this.pointcut != null) { + return new DefaultPointcutAdvisor(this.pointcut, this.transactionInterceptor); + } + else { + // Rely on default pointcut. + return new TransactionAttributeSourceAdvisor(this.transactionInterceptor); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/interceptor/package-info.java b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/package-info.java new file mode 100644 index 000000000..3406126ab --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/interceptor/package-info.java @@ -0,0 +1,18 @@ + +/** + * + * AOP-based solution for declarative transaction demarcation. + * Builds on the AOP infrastructure in com.fr.third.springframework.aop.framework. + * Any POJO can be transactionally advised with Spring. + * + *

The TransactionFactoryProxyBean can be used to create transactional + * AOP proxies transparently to code that uses them. + * + *

The TransactionInterceptor is the AOP Alliance MethodInterceptor that + * delivers transactional advice, based on the Spring transaction abstraction. + * This allows declarative transaction management in any environment, + * even without JTA if an application uses only a single database. + * + */ +package com.fr.third.springframework.transaction.interceptor; + diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/JtaAfterCompletionSynchronization.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/JtaAfterCompletionSynchronization.java new file mode 100644 index 000000000..a1f5b2974 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/JtaAfterCompletionSynchronization.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.jta; + +import java.util.List; +import javax.transaction.Status; +import javax.transaction.Synchronization; + +import com.fr.third.springframework.transaction.support.TransactionSynchronization; +import com.fr.third.springframework.transaction.support.TransactionSynchronizationUtils; + +/** + * Adapter for a JTA Synchronization, invoking the {@code afterCommit} / + * {@code afterCompletion} callbacks of Spring {@link TransactionSynchronization} + * objects callbacks after the outer JTA transaction has completed. + * Applied when participating in an existing (non-Spring) JTA transaction. + * + * @author Juergen Hoeller + * @since 2.0 + * @see TransactionSynchronization#afterCommit + * @see TransactionSynchronization#afterCompletion + */ +public class JtaAfterCompletionSynchronization implements Synchronization { + + private final List synchronizations; + + + /** + * Create a new JtaAfterCompletionSynchronization for the given synchronization objects. + * @param synchronizations the List of TransactionSynchronization objects + * @see com.fr.third.springframework.transaction.support.TransactionSynchronization + */ + public JtaAfterCompletionSynchronization(List synchronizations) { + this.synchronizations = synchronizations; + } + + + @Override + public void beforeCompletion() { + } + + @Override + public void afterCompletion(int status) { + switch (status) { + case Status.STATUS_COMMITTED: + try { + TransactionSynchronizationUtils.invokeAfterCommit(this.synchronizations); + } + finally { + TransactionSynchronizationUtils.invokeAfterCompletion( + this.synchronizations, TransactionSynchronization.STATUS_COMMITTED); + } + break; + case Status.STATUS_ROLLEDBACK: + TransactionSynchronizationUtils.invokeAfterCompletion( + this.synchronizations, TransactionSynchronization.STATUS_ROLLED_BACK); + break; + default: + TransactionSynchronizationUtils.invokeAfterCompletion( + this.synchronizations, TransactionSynchronization.STATUS_UNKNOWN); + } + } +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/JtaTransactionManager.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/JtaTransactionManager.java new file mode 100644 index 000000000..b0c63884c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/JtaTransactionManager.java @@ -0,0 +1,1210 @@ +///* +// * Copyright 2002-2013 the original author or authors. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package com.fr.third.springframework.transaction.jta; +// +//import java.io.IOException; +//import java.io.ObjectInputStream; +//import java.io.Serializable; +//import java.util.List; +//import java.util.Properties; +//import javax.naming.NamingException; +//import javax.transaction.HeuristicMixedException; +//import javax.transaction.HeuristicRollbackException; +//import javax.transaction.InvalidTransactionException; +//import javax.transaction.NotSupportedException; +//import javax.transaction.RollbackException; +//import javax.transaction.Status; +//import javax.transaction.SystemException; +//import javax.transaction.Transaction; +//import javax.transaction.TransactionManager; +//import javax.transaction.TransactionSynchronizationRegistry; +//import javax.transaction.UserTransaction; +// +//import com.fr.third.springframework.beans.factory.InitializingBean; +//import com.fr.third.springframework.jndi.JndiTemplate; +//import com.fr.third.springframework.transaction.CannotCreateTransactionException; +//import com.fr.third.springframework.transaction.HeuristicCompletionException; +//import com.fr.third.springframework.transaction.IllegalTransactionStateException; +//import com.fr.third.springframework.transaction.InvalidIsolationLevelException; +//import com.fr.third.springframework.transaction.NestedTransactionNotSupportedException; +//import com.fr.third.springframework.transaction.TransactionDefinition; +//import com.fr.third.springframework.transaction.TransactionSuspensionNotSupportedException; +//import com.fr.third.springframework.transaction.TransactionSystemException; +//import com.fr.third.springframework.transaction.UnexpectedRollbackException; +//import com.fr.third.springframework.transaction.support.AbstractPlatformTransactionManager; +//import com.fr.third.springframework.transaction.support.DefaultTransactionStatus; +//import com.fr.third.springframework.transaction.support.TransactionSynchronization; +//import com.fr.third.springframework.util.Assert; +//import com.fr.third.springframework.util.StringUtils; +// +///** +// * {@link com.fr.third.springframework.transaction.PlatformTransactionManager} implementation +// * for JTA, delegating to a backend JTA provider. This is typically used to delegate +// * to a Java EE server's transaction coordinator, but may also be configured with a +// * local JTA provider which is embedded within the application. +// * +// *

This transaction manager is appropriate for handling distributed transactions, +// * i.e. transactions that span multiple resources, and for controlling transactions on +// * application server resources (e.g. JDBC DataSources available in JNDI) in general. +// * For a single JDBC DataSource, DataSourceTransactionManager is perfectly sufficient, +// * and for accessing a single resource with Hibernate (including transactional cache), +// * HibernateTransactionManager is appropriate, for example. +// * +// *

For typical JTA transactions (REQUIRED, SUPPORTS, MANDATORY, NEVER), a plain +// * JtaTransactionManager definition is all you need, portable across all Java EE servers. +// * This corresponds to the functionality of the JTA UserTransaction, for which Java EE +// * specifies a standard JNDI name ("java:comp/UserTransaction"). There is no need to +// * configure a server-specific TransactionManager lookup for this kind of JTA usage. +// * +// *

Transaction suspension (REQUIRES_NEW, NOT_SUPPORTED) is just available with a +// * JTA TransactionManager being registered. Common TransactionManager locations are +// * autodetected by JtaTransactionManager, provided that the "autodetectTransactionManager" +// * flag is set to "true" (which it is by default). +// * +// *

Note: Support for the JTA TransactionManager interface is not required by Java EE. +// * Almost all Java EE servers expose it, but do so as extension to EE. There might be some +// * issues with compatibility, despite the TransactionManager interface being part of JTA. +// * As a consequence, Spring provides various vendor-specific PlatformTransactionManagers, +// * which are recommended to be used if appropriate: {@link WebLogicJtaTransactionManager} +// * and {@link WebSphereUowTransactionManager}. For all other Java EE servers, the +// * standard JtaTransactionManager is sufficient. +// * +// *

This pure JtaTransactionManager class supports timeouts but not per-transaction +// * isolation levels. Custom subclasses may override the {@link #doJtaBegin} method for +// * specific JTA extensions in order to provide this functionality; Spring includes a +// * corresponding {@link WebLogicJtaTransactionManager} class for WebLogic Server. Such +// * adapters for specific Java EE transaction coordinators may also expose transaction +// * names for monitoring; with standard JTA, transaction names will simply be ignored. +// * +// *

Consider using Spring's {@code tx:jta-transaction-manager} configuration +// * element for automatically picking the appropriate JTA platform transaction manager +// * (automatically detecting WebLogic and WebSphere). +// * +// *

JTA 1.1 adds the TransactionSynchronizationRegistry facility, as public Java EE 5 +// * API in addition to the standard JTA UserTransaction handle. As of Spring 2.5, this +// * JtaTransactionManager autodetects the TransactionSynchronizationRegistry and uses +// * it for registering Spring-managed synchronizations when participating in an existing +// * JTA transaction (e.g. controlled by EJB CMT). If no TransactionSynchronizationRegistry +// * is available, then such synchronizations will be registered via the (non-EE) JTA +// * TransactionManager handle. +// * +// *

This class is serializable. However, active synchronizations do not survive serialization. +// * +// * @author Juergen Hoeller +// * @since 24.03.2003 +// * @see javax.transaction.UserTransaction +// * @see javax.transaction.TransactionManager +// * @see javax.transaction.TransactionSynchronizationRegistry +// * @see #setUserTransactionName +// * @see #setUserTransaction +// * @see #setTransactionManagerName +// * @see #setTransactionManager +// * @see WebLogicJtaTransactionManager +// */ +//@SuppressWarnings("serial") +//public class JtaTransactionManager extends AbstractPlatformTransactionManager +// implements TransactionFactory, InitializingBean, Serializable { +// +// /** +// * Default JNDI location for the JTA UserTransaction. Many Java EE servers +// * also provide support for the JTA TransactionManager interface there. +// * @see #setUserTransactionName +// * @see #setAutodetectTransactionManager +// */ +// public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction"; +// +// /** +// * Fallback JNDI locations for the JTA TransactionManager. Applied if +// * the JTA UserTransaction does not implement the JTA TransactionManager +// * interface, provided that the "autodetectTransactionManager" flag is "true". +// * @see #setTransactionManagerName +// * @see #setAutodetectTransactionManager +// */ +// public static final String[] FALLBACK_TRANSACTION_MANAGER_NAMES = +// new String[] {"java:comp/TransactionManager", "java:appserver/TransactionManager", +// "java:pm/TransactionManager", "java:/TransactionManager"}; +// +// /** +// * Standard Java EE 5 JNDI location for the JTA TransactionSynchronizationRegistry. +// * Autodetected when available. +// */ +// public static final String DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME = +// "java:comp/TransactionSynchronizationRegistry"; +// +// +// private transient JndiTemplate jndiTemplate = new JndiTemplate(); +// +// private transient UserTransaction userTransaction; +// +// private String userTransactionName; +// +// private boolean autodetectUserTransaction = true; +// +// private boolean cacheUserTransaction = true; +// +// private boolean userTransactionObtainedFromJndi = false; +// +// private transient TransactionManager transactionManager; +// +// private String transactionManagerName; +// +// private boolean autodetectTransactionManager = true; +// +// private transient TransactionSynchronizationRegistry transactionSynchronizationRegistry; +// +// private String transactionSynchronizationRegistryName; +// +// private boolean autodetectTransactionSynchronizationRegistry = true; +// +// private boolean allowCustomIsolationLevels = false; +// +// +// /** +// * Create a new JtaTransactionManager instance, to be configured as bean. +// * Invoke {@code afterPropertiesSet} to activate the configuration. +// * @see #setUserTransactionName +// * @see #setUserTransaction +// * @see #setTransactionManagerName +// * @see #setTransactionManager +// * @see #afterPropertiesSet() +// */ +// public JtaTransactionManager() { +// setNestedTransactionAllowed(true); +// } +// +// /** +// * Create a new JtaTransactionManager instance. +// * @param userTransaction the JTA UserTransaction to use as direct reference +// */ +// public JtaTransactionManager(UserTransaction userTransaction) { +// this(); +// Assert.notNull(userTransaction, "UserTransaction must not be null"); +// this.userTransaction = userTransaction; +// } +// +// /** +// * Create a new JtaTransactionManager instance. +// * @param userTransaction the JTA UserTransaction to use as direct reference +// * @param transactionManager the JTA TransactionManager to use as direct reference +// */ +// public JtaTransactionManager(UserTransaction userTransaction, TransactionManager transactionManager) { +// this(); +// Assert.notNull(userTransaction, "UserTransaction must not be null"); +// Assert.notNull(transactionManager, "TransactionManager must not be null"); +// this.userTransaction = userTransaction; +// this.transactionManager = transactionManager; +// } +// +// /** +// * Create a new JtaTransactionManager instance. +// * @param transactionManager the JTA TransactionManager to use as direct reference +// */ +// public JtaTransactionManager(TransactionManager transactionManager) { +// this(); +// Assert.notNull(transactionManager, "TransactionManager must not be null"); +// this.transactionManager = transactionManager; +// this.userTransaction = buildUserTransaction(transactionManager); +// } +// +// +// /** +// * Set the JndiTemplate to use for JNDI lookups. +// * A default one is used if not set. +// */ +// public void setJndiTemplate(JndiTemplate jndiTemplate) { +// Assert.notNull(jndiTemplate, "JndiTemplate must not be null"); +// this.jndiTemplate = jndiTemplate; +// } +// +// /** +// * Return the JndiTemplate used for JNDI lookups. +// */ +// public JndiTemplate getJndiTemplate() { +// return this.jndiTemplate; +// } +// +// /** +// * Set the JNDI environment to use for JNDI lookups. +// * Creates a JndiTemplate with the given environment settings. +// * @see #setJndiTemplate +// */ +// public void setJndiEnvironment(Properties jndiEnvironment) { +// this.jndiTemplate = new JndiTemplate(jndiEnvironment); +// } +// +// /** +// * Return the JNDI environment to use for JNDI lookups. +// */ +// public Properties getJndiEnvironment() { +// return this.jndiTemplate.getEnvironment(); +// } +// +// +// /** +// * Set the JTA UserTransaction to use as direct reference. +// *

Typically just used for local JTA setups; in a Java EE environment, +// * the UserTransaction will always be fetched from JNDI. +// * @see #setUserTransactionName +// * @see #setAutodetectUserTransaction +// */ +// public void setUserTransaction(UserTransaction userTransaction) { +// this.userTransaction = userTransaction; +// } +// +// /** +// * Return the JTA UserTransaction that this transaction manager uses. +// */ +// public UserTransaction getUserTransaction() { +// return this.userTransaction; +// } +// +// /** +// * Set the JNDI name of the JTA UserTransaction. +// *

Note that the UserTransaction will be autodetected at the Java EE +// * default location "java:comp/UserTransaction" if not specified explicitly. +// * @see #DEFAULT_USER_TRANSACTION_NAME +// * @see #setUserTransaction +// * @see #setAutodetectUserTransaction +// */ +// public void setUserTransactionName(String userTransactionName) { +// this.userTransactionName = userTransactionName; +// } +// +// /** +// * Set whether to autodetect the JTA UserTransaction at its default +// * JNDI location "java:comp/UserTransaction", as specified by Java EE. +// * Will proceed without UserTransaction if none found. +// *

Default is "true", autodetecting the UserTransaction unless +// * it has been specified explicitly. Turn this flag off to allow for +// * JtaTransactionManager operating against the TransactionManager only, +// * despite a default UserTransaction being available. +// * @see #DEFAULT_USER_TRANSACTION_NAME +// */ +// public void setAutodetectUserTransaction(boolean autodetectUserTransaction) { +// this.autodetectUserTransaction = autodetectUserTransaction; +// } +// +// /** +// * Set whether to cache the JTA UserTransaction object fetched from JNDI. +// *

Default is "true": UserTransaction lookup will only happen at startup, +// * reusing the same UserTransaction handle for all transactions of all threads. +// * This is the most efficient choice for all application servers that provide +// * a shared UserTransaction object (the typical case). +// *

Turn this flag off to enforce a fresh lookup of the UserTransaction +// * for every transaction. This is only necessary for application servers +// * that return a new UserTransaction for every transaction, keeping state +// * tied to the UserTransaction object itself rather than the current thread. +// * @see #setUserTransactionName +// */ +// public void setCacheUserTransaction(boolean cacheUserTransaction) { +// this.cacheUserTransaction = cacheUserTransaction; +// } +// +// /** +// * Set the JTA TransactionManager to use as direct reference. +// *

A TransactionManager is necessary for suspending and resuming transactions, +// * as this not supported by the UserTransaction interface. +// *

Note that the TransactionManager will be autodetected if the JTA +// * UserTransaction object implements the JTA TransactionManager interface too, +// * as well as autodetected at various well-known fallback JNDI locations. +// * @see #setTransactionManagerName +// * @see #setAutodetectTransactionManager +// */ +// public void setTransactionManager(TransactionManager transactionManager) { +// this.transactionManager = transactionManager; +// } +// +// /** +// * Return the JTA TransactionManager that this transaction manager uses, if any. +// */ +// public TransactionManager getTransactionManager() { +// return this.transactionManager; +// } +// +// /** +// * Set the JNDI name of the JTA TransactionManager. +// *

A TransactionManager is necessary for suspending and resuming transactions, +// * as this not supported by the UserTransaction interface. +// *

Note that the TransactionManager will be autodetected if the JTA +// * UserTransaction object implements the JTA TransactionManager interface too, +// * as well as autodetected at various well-known fallback JNDI locations. +// * @see #setTransactionManager +// * @see #setAutodetectTransactionManager +// */ +// public void setTransactionManagerName(String transactionManagerName) { +// this.transactionManagerName = transactionManagerName; +// } +// +// /** +// * Set whether to autodetect a JTA UserTransaction object that implements +// * the JTA TransactionManager interface too (i.e. the JNDI location for the +// * TransactionManager is "java:comp/UserTransaction", same as for the UserTransaction). +// * Also checks the fallback JNDI locations "java:comp/TransactionManager" and +// * "java:/TransactionManager". Will proceed without TransactionManager if none found. +// *

Default is "true", autodetecting the TransactionManager unless it has been +// * specified explicitly. Can be turned off to deliberately ignore an available +// * TransactionManager, for example when there are known issues with suspend/resume +// * and any attempt to use REQUIRES_NEW or NOT_SUPPORTED should fail fast. +// * @see #FALLBACK_TRANSACTION_MANAGER_NAMES +// */ +// public void setAutodetectTransactionManager(boolean autodetectTransactionManager) { +// this.autodetectTransactionManager = autodetectTransactionManager; +// } +// +// /** +// * Set the JTA 1.1 TransactionSynchronizationRegistry to use as direct reference. +// *

A TransactionSynchronizationRegistry allows for interposed registration +// * of transaction synchronizations, as an alternative to the regular registration +// * methods on the JTA TransactionManager API. Also, it is an official part of the +// * Java EE 5 platform, in contrast to the JTA TransactionManager itself. +// *

Note that the TransactionSynchronizationRegistry will be autodetected in JNDI and +// * also from the UserTransaction/TransactionManager object if implemented there as well. +// * @see #setTransactionSynchronizationRegistryName +// * @see #setAutodetectTransactionSynchronizationRegistry +// */ +// public void setTransactionSynchronizationRegistry(TransactionSynchronizationRegistry transactionSynchronizationRegistry) { +// this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; +// } +// +// /** +// * Return the JTA 1.1 TransactionSynchronizationRegistry that this transaction manager uses, if any. +// */ +// public TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() { +// return this.transactionSynchronizationRegistry; +// } +// +// /** +// * Set the JNDI name of the JTA 1.1 TransactionSynchronizationRegistry. +// *

Note that the TransactionSynchronizationRegistry will be autodetected +// * at the Java EE 5 default location "java:comp/TransactionSynchronizationRegistry" +// * if not specified explicitly. +// * @see #DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME +// */ +// public void setTransactionSynchronizationRegistryName(String transactionSynchronizationRegistryName) { +// this.transactionSynchronizationRegistryName = transactionSynchronizationRegistryName; +// } +// +// /** +// * Set whether to autodetect a JTA 1.1 TransactionSynchronizationRegistry object +// * at its default JDNI location ("java:comp/TransactionSynchronizationRegistry") +// * if the UserTransaction has also been obtained from JNDI, and also whether +// * to fall back to checking whether the JTA UserTransaction/TransactionManager +// * object implements the JTA TransactionSynchronizationRegistry interface too. +// *

Default is "true", autodetecting the TransactionSynchronizationRegistry +// * unless it has been specified explicitly. Can be turned off to delegate +// * synchronization registration to the regular JTA TransactionManager API. +// */ +// public void setAutodetectTransactionSynchronizationRegistry(boolean autodetectTransactionSynchronizationRegistry) { +// this.autodetectTransactionSynchronizationRegistry = autodetectTransactionSynchronizationRegistry; +// } +// +// /** +// * Set whether to allow custom isolation levels to be specified. +// *

Default is "false", throwing an exception if a non-default isolation level +// * is specified for a transaction. Turn this flag on if affected resource adapters +// * check the thread-bound transaction context and apply the specified isolation +// * levels individually (e.g. through an IsolationLevelDataSourceAdapter). +// * @see com.fr.third.springframework.jdbc.datasource.IsolationLevelDataSourceAdapter +// * @see com.fr.third.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter +// */ +// public void setAllowCustomIsolationLevels(boolean allowCustomIsolationLevels) { +// this.allowCustomIsolationLevels = allowCustomIsolationLevels; +// } +// +// +// /** +// * Initialize the UserTransaction as well as the TransactionManager handle. +// * @see #initUserTransactionAndTransactionManager() +// */ +// @Override +// public void afterPropertiesSet() throws TransactionSystemException { +// initUserTransactionAndTransactionManager(); +// checkUserTransactionAndTransactionManager(); +// initTransactionSynchronizationRegistry(); +// } +// +// /** +// * Initialize the UserTransaction as well as the TransactionManager handle. +// * @throws TransactionSystemException if initialization failed +// */ +// protected void initUserTransactionAndTransactionManager() throws TransactionSystemException { +// if (this.userTransaction == null) { +// // Fetch JTA UserTransaction from JNDI, if necessary. +// if (StringUtils.hasLength(this.userTransactionName)) { +// this.userTransaction = lookupUserTransaction(this.userTransactionName); +// this.userTransactionObtainedFromJndi = true; +// } +// else { +// this.userTransaction = retrieveUserTransaction(); +// if (this.userTransaction == null && this.autodetectUserTransaction) { +// // Autodetect UserTransaction at its default JNDI location. +// this.userTransaction = findUserTransaction(); +// } +// } +// } +// +// if (this.transactionManager == null) { +// // Fetch JTA TransactionManager from JNDI, if necessary. +// if (StringUtils.hasLength(this.transactionManagerName)) { +// this.transactionManager = lookupTransactionManager(this.transactionManagerName); +// } +// else { +// this.transactionManager = retrieveTransactionManager(); +// if (this.transactionManager == null && this.autodetectTransactionManager) { +// // Autodetect UserTransaction object that implements TransactionManager, +// // and check fallback JNDI locations otherwise. +// this.transactionManager = findTransactionManager(this.userTransaction); +// } +// } +// } +// +// // If only JTA TransactionManager specified, create UserTransaction handle for it. +// if (this.userTransaction == null && this.transactionManager != null) { +// this.userTransaction = buildUserTransaction(this.transactionManager); +// } +// } +// +// /** +// * Check the UserTransaction as well as the TransactionManager handle, +// * assuming standard JTA requirements. +// * @throws IllegalStateException if no sufficient handles are available +// */ +// protected void checkUserTransactionAndTransactionManager() throws IllegalStateException { +// // We at least need the JTA UserTransaction. +// if (this.userTransaction != null) { +// if (logger.isInfoEnabled()) { +// logger.info("Using JTA UserTransaction: " + this.userTransaction); +// } +// } +// else { +// throw new IllegalStateException("No JTA UserTransaction available - specify either " + +// "'userTransaction' or 'userTransactionName' or 'transactionManager' or 'transactionManagerName'"); +// } +// +// // For transaction suspension, the JTA TransactionManager is necessary too. +// if (this.transactionManager != null) { +// if (logger.isInfoEnabled()) { +// logger.info("Using JTA TransactionManager: " + this.transactionManager); +// } +// } +// else { +// logger.warn("No JTA TransactionManager found: transaction suspension not available"); +// } +// } +// +// /** +// * Initialize the JTA 1.1 TransactionSynchronizationRegistry, if available. +// *

To be called after {@link #initUserTransactionAndTransactionManager()}, +// * since it may check the UserTransaction and TransactionManager handles. +// * @throws TransactionSystemException if initialization failed +// */ +// protected void initTransactionSynchronizationRegistry() { +// if (this.transactionSynchronizationRegistry == null) { +// // Fetch JTA TransactionSynchronizationRegistry from JNDI, if necessary. +// if (StringUtils.hasLength(this.transactionSynchronizationRegistryName)) { +// this.transactionSynchronizationRegistry = +// lookupTransactionSynchronizationRegistry(this.transactionSynchronizationRegistryName); +// } +// else { +// this.transactionSynchronizationRegistry = retrieveTransactionSynchronizationRegistry(); +// if (this.transactionSynchronizationRegistry == null && this.autodetectTransactionSynchronizationRegistry) { +// // Autodetect in JNDI if applicable, and check UserTransaction/TransactionManager +// // object that implements TransactionSynchronizationRegistry otherwise. +// this.transactionSynchronizationRegistry = +// findTransactionSynchronizationRegistry(this.userTransaction, this.transactionManager); +// } +// } +// } +// +// if (this.transactionSynchronizationRegistry != null) { +// if (logger.isInfoEnabled()) { +// logger.info("Using JTA TransactionSynchronizationRegistry: " + this.transactionSynchronizationRegistry); +// } +// } +// } +// +// +// /** +// * Build a UserTransaction handle based on the given TransactionManager. +// * @param transactionManager the TransactionManager +// * @return a corresponding UserTransaction handle +// */ +// protected UserTransaction buildUserTransaction(TransactionManager transactionManager) { +// if (transactionManager instanceof UserTransaction) { +// return (UserTransaction) transactionManager; +// } +// else { +// return new UserTransactionAdapter(transactionManager); +// } +// } +// +// /** +// * Look up the JTA UserTransaction in JNDI via the configured name. +// *

Called by {@code afterPropertiesSet} if no direct UserTransaction reference was set. +// * Can be overridden in subclasses to provide a different UserTransaction object. +// * @param userTransactionName the JNDI name of the UserTransaction +// * @return the UserTransaction object +// * @throws TransactionSystemException if the JNDI lookup failed +// * @see #setJndiTemplate +// * @see #setUserTransactionName +// */ +// protected UserTransaction lookupUserTransaction(String userTransactionName) +// throws TransactionSystemException { +// try { +// if (logger.isDebugEnabled()) { +// logger.debug("Retrieving JTA UserTransaction from JNDI location [" + userTransactionName + "]"); +// } +// return getJndiTemplate().lookup(userTransactionName, UserTransaction.class); +// } +// catch (NamingException ex) { +// throw new TransactionSystemException( +// "JTA UserTransaction is not available at JNDI location [" + userTransactionName + "]", ex); +// } +// } +// +// /** +// * Look up the JTA TransactionManager in JNDI via the configured name. +// *

Called by {@code afterPropertiesSet} if no direct TransactionManager reference was set. +// * Can be overridden in subclasses to provide a different TransactionManager object. +// * @param transactionManagerName the JNDI name of the TransactionManager +// * @return the UserTransaction object +// * @throws TransactionSystemException if the JNDI lookup failed +// * @see #setJndiTemplate +// * @see #setTransactionManagerName +// */ +// protected TransactionManager lookupTransactionManager(String transactionManagerName) +// throws TransactionSystemException { +// try { +// if (logger.isDebugEnabled()) { +// logger.debug("Retrieving JTA TransactionManager from JNDI location [" + transactionManagerName + "]"); +// } +// return getJndiTemplate().lookup(transactionManagerName, TransactionManager.class); +// } +// catch (NamingException ex) { +// throw new TransactionSystemException( +// "JTA TransactionManager is not available at JNDI location [" + transactionManagerName + "]", ex); +// } +// } +// +// /** +// * Look up the JTA 1.1 TransactionSynchronizationRegistry in JNDI via the configured name. +// *

Can be overridden in subclasses to provide a different TransactionManager object. +// * @param registryName the JNDI name of the +// * TransactionSynchronizationRegistry +// * @return the TransactionSynchronizationRegistry object +// * @throws TransactionSystemException if the JNDI lookup failed +// * @see #setJndiTemplate +// * @see #setTransactionSynchronizationRegistryName +// */ +// protected TransactionSynchronizationRegistry lookupTransactionSynchronizationRegistry(String registryName) throws TransactionSystemException { +// try { +// if (logger.isDebugEnabled()) { +// logger.debug("Retrieving JTA TransactionSynchronizationRegistry from JNDI location [" + registryName + "]"); +// } +// return getJndiTemplate().lookup(registryName, TransactionSynchronizationRegistry.class); +// } +// catch (NamingException ex) { +// throw new TransactionSystemException( +// "JTA TransactionSynchronizationRegistry is not available at JNDI location [" + registryName + "]", ex); +// } +// } +// +// /** +// * Allows subclasses to retrieve the JTA UserTransaction in a vendor-specific manner. +// * Only called if no "userTransaction" or "userTransactionName" specified. +// *

The default implementation simply returns {@code null}. +// * @return the JTA UserTransaction handle to use, or {@code null} if none found +// * @throws TransactionSystemException in case of errors +// * @see #setUserTransaction +// * @see #setUserTransactionName +// */ +// protected UserTransaction retrieveUserTransaction() throws TransactionSystemException { +// return null; +// } +// +// /** +// * Allows subclasses to retrieve the JTA TransactionManager in a vendor-specific manner. +// * Only called if no "transactionManager" or "transactionManagerName" specified. +// *

The default implementation simply returns {@code null}. +// * @return the JTA TransactionManager handle to use, or {@code null} if none found +// * @throws TransactionSystemException in case of errors +// * @see #setTransactionManager +// * @see #setTransactionManagerName +// */ +// protected TransactionManager retrieveTransactionManager() throws TransactionSystemException { +// return null; +// } +// +// /** +// * Allows subclasses to retrieve the JTA 1.1 TransactionSynchronizationRegistry +// * in a vendor-specific manner. +// *

The default implementation simply returns {@code null}. +// * @return the JTA TransactionSynchronizationRegistry handle to use, +// * or {@code null} if none found +// * @throws TransactionSystemException in case of errors +// */ +// protected TransactionSynchronizationRegistry retrieveTransactionSynchronizationRegistry() throws TransactionSystemException { +// return null; +// } +// +// /** +// * Find the JTA UserTransaction through a default JNDI lookup: +// * "java:comp/UserTransaction". +// * @return the JTA UserTransaction reference, or {@code null} if not found +// * @see #DEFAULT_USER_TRANSACTION_NAME +// */ +// protected UserTransaction findUserTransaction() { +// String jndiName = DEFAULT_USER_TRANSACTION_NAME; +// try { +// UserTransaction ut = getJndiTemplate().lookup(jndiName, UserTransaction.class); +// if (logger.isDebugEnabled()) { +// logger.debug("JTA UserTransaction found at default JNDI location [" + jndiName + "]"); +// } +// this.userTransactionObtainedFromJndi = true; +// return ut; +// } +// catch (NamingException ex) { +// if (logger.isDebugEnabled()) { +// logger.debug("No JTA UserTransaction found at default JNDI location [" + jndiName + "]", ex); +// } +// return null; +// } +// } +// +// /** +// * Find the JTA TransactionManager through autodetection: checking whether the +// * UserTransaction object implements the TransactionManager, and checking the +// * fallback JNDI locations. +// * @param ut the JTA UserTransaction object +// * @return the JTA TransactionManager reference, or {@code null} if not found +// * @see #FALLBACK_TRANSACTION_MANAGER_NAMES +// */ +// protected TransactionManager findTransactionManager(UserTransaction ut) { +// if (ut instanceof TransactionManager) { +// if (logger.isDebugEnabled()) { +// logger.debug("JTA UserTransaction object [" + ut + "] implements TransactionManager"); +// } +// return (TransactionManager) ut; +// } +// +// // Check fallback JNDI locations. +// for (String jndiName : FALLBACK_TRANSACTION_MANAGER_NAMES) { +// try { +// TransactionManager tm = getJndiTemplate().lookup(jndiName, TransactionManager.class); +// if (logger.isDebugEnabled()) { +// logger.debug("JTA TransactionManager found at fallback JNDI location [" + jndiName + "]"); +// } +// return tm; +// } +// catch (NamingException ex) { +// if (logger.isDebugEnabled()) { +// logger.debug("No JTA TransactionManager found at fallback JNDI location [" + jndiName + "]", ex); +// } +// } +// } +// +// // OK, so no JTA TransactionManager is available... +// return null; +// } +// +// /** +// * Find the JTA 1.1 TransactionSynchronizationRegistry through autodetection: +// * checking whether the UserTransaction object or TransactionManager object +// * implements it, and checking Java EE 5's standard JNDI location. +// *

The default implementation simply returns {@code null}. +// * @param ut the JTA UserTransaction object +// * @param tm the JTA TransactionManager object +// * @return the JTA TransactionSynchronizationRegistry handle to use, +// * or {@code null} if none found +// * @throws TransactionSystemException in case of errors +// */ +// protected TransactionSynchronizationRegistry findTransactionSynchronizationRegistry(UserTransaction ut, TransactionManager tm) +// throws TransactionSystemException { +// +// if (this.userTransactionObtainedFromJndi) { +// // UserTransaction has already been obtained from JNDI, so the +// // TransactionSynchronizationRegistry probably sits there as well. +// String jndiName = DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME; +// try { +// TransactionSynchronizationRegistry tsr = getJndiTemplate().lookup(jndiName, TransactionSynchronizationRegistry.class); +// if (logger.isDebugEnabled()) { +// logger.debug("JTA TransactionSynchronizationRegistry found at default JNDI location [" + jndiName + "]"); +// } +// return tsr; +// } +// catch (NamingException ex) { +// if (logger.isDebugEnabled()) { +// logger.debug( +// "No JTA TransactionSynchronizationRegistry found at default JNDI location [" + jndiName + "]", ex); +// } +// } +// } +// // Check whether the UserTransaction or TransactionManager implements it... +// if (ut instanceof TransactionSynchronizationRegistry) { +// return (TransactionSynchronizationRegistry) ut; +// } +// if (tm instanceof TransactionSynchronizationRegistry) { +// return (TransactionSynchronizationRegistry) tm; +// } +// // OK, so no JTA 1.1 TransactionSynchronizationRegistry is available... +// return null; +// } +// +// +// /** +// * This implementation returns a JtaTransactionObject instance for the +// * JTA UserTransaction. +// *

The UserTransaction object will either be looked up freshly for the +// * current transaction, or the cached one looked up at startup will be used. +// * The latter is the default: Most application servers use a shared singleton +// * UserTransaction that can be cached. Turn off the "cacheUserTransaction" +// * flag to enforce a fresh lookup for every transaction. +// * @see #setCacheUserTransaction +// */ +// @Override +// protected Object doGetTransaction() { +// UserTransaction ut = getUserTransaction(); +// if (ut == null) { +// throw new CannotCreateTransactionException("No JTA UserTransaction available - " + +// "programmatic PlatformTransactionManager.getTransaction usage not supported"); +// } +// if (!this.cacheUserTransaction) { +// ut = lookupUserTransaction( +// this.userTransactionName != null ? this.userTransactionName : DEFAULT_USER_TRANSACTION_NAME); +// } +// return doGetJtaTransaction(ut); +// } +// +// /** +// * Get a JTA transaction object for the given current UserTransaction. +// *

Subclasses can override this to provide a JtaTransactionObject +// * subclass, for example holding some additional JTA handle needed. +// * @param ut the UserTransaction handle to use for the current transaction +// * @return the JtaTransactionObject holding the UserTransaction +// */ +// protected JtaTransactionObject doGetJtaTransaction(UserTransaction ut) { +// return new JtaTransactionObject(ut); +// } +// +// @Override +// protected boolean isExistingTransaction(Object transaction) { +// JtaTransactionObject txObject = (JtaTransactionObject) transaction; +// try { +// return (txObject.getUserTransaction().getStatus() != Status.STATUS_NO_TRANSACTION); +// } +// catch (SystemException ex) { +// throw new TransactionSystemException("JTA failure on getStatus", ex); +// } +// } +// +// /** +// * This implementation returns false to cause a further invocation +// * of doBegin despite an already existing transaction. +// *

JTA implementations might support nested transactions via further +// * {@code UserTransaction.begin()} invocations, but never support savepoints. +// * @see #doBegin +// * @see javax.transaction.UserTransaction#begin() +// */ +// @Override +// protected boolean useSavepointForNestedTransaction() { +// return false; +// } +// +// +// @Override +// protected void doBegin(Object transaction, TransactionDefinition definition) { +// JtaTransactionObject txObject = (JtaTransactionObject) transaction; +// try { +// doJtaBegin(txObject, definition); +// } +// catch (NotSupportedException ex) { +// // assume nested transaction not supported +// throw new NestedTransactionNotSupportedException( +// "JTA implementation does not support nested transactions", ex); +// } +// catch (UnsupportedOperationException ex) { +// // assume nested transaction not supported +// throw new NestedTransactionNotSupportedException( +// "JTA implementation does not support nested transactions", ex); +// } +// catch (SystemException ex) { +// throw new CannotCreateTransactionException("JTA failure on begin", ex); +// } +// } +// +// /** +// * Perform a JTA begin on the JTA UserTransaction or TransactionManager. +// *

This implementation only supports standard JTA functionality: +// * that is, no per-transaction isolation levels and no transaction names. +// * Can be overridden in subclasses, for specific JTA implementations. +// *

Calls {@code applyIsolationLevel} and {@code applyTimeout} +// * before invoking the UserTransaction's {@code begin} method. +// * @param txObject the JtaTransactionObject containing the UserTransaction +// * @param definition TransactionDefinition instance, describing propagation +// * behavior, isolation level, read-only flag, timeout, and transaction name +// * @throws NotSupportedException if thrown by JTA methods +// * @throws SystemException if thrown by JTA methods +// * @see #getUserTransaction +// * @see #getTransactionManager +// * @see #applyIsolationLevel +// * @see #applyTimeout +// * @see JtaTransactionObject#getUserTransaction() +// * @see javax.transaction.UserTransaction#setTransactionTimeout +// * @see javax.transaction.UserTransaction#begin +// */ +// protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition) +// throws NotSupportedException, SystemException { +// +// applyIsolationLevel(txObject, definition.getIsolationLevel()); +// int timeout = determineTimeout(definition); +// applyTimeout(txObject, timeout); +// txObject.getUserTransaction().begin(); +// } +// +// /** +// * Apply the given transaction isolation level. The default implementation +// * will throw an exception for any level other than ISOLATION_DEFAULT. +// *

To be overridden in subclasses for specific JTA implementations, +// * as alternative to overriding the full {@link #doJtaBegin} method. +// * @param txObject the JtaTransactionObject containing the UserTransaction +// * @param isolationLevel isolation level taken from transaction definition +// * @throws InvalidIsolationLevelException if the given isolation level +// * cannot be applied +// * @throws SystemException if thrown by the JTA implementation +// * @see #doJtaBegin +// * @see JtaTransactionObject#getUserTransaction() +// * @see #getTransactionManager() +// */ +// protected void applyIsolationLevel(JtaTransactionObject txObject, int isolationLevel) +// throws InvalidIsolationLevelException, SystemException { +// +// if (!this.allowCustomIsolationLevels && isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) { +// throw new InvalidIsolationLevelException( +// "JtaTransactionManager does not support custom isolation levels by default - " + +// "switch 'allowCustomIsolationLevels' to 'true'"); +// } +// } +// +// /** +// * Apply the given transaction timeout. The default implementation will call +// * {@code UserTransaction.setTransactionTimeout} for a non-default timeout value. +// * @param txObject the JtaTransactionObject containing the UserTransaction +// * @param timeout timeout value taken from transaction definition +// * @throws SystemException if thrown by the JTA implementation +// * @see #doJtaBegin +// * @see JtaTransactionObject#getUserTransaction() +// * @see javax.transaction.UserTransaction#setTransactionTimeout(int) +// */ +// protected void applyTimeout(JtaTransactionObject txObject, int timeout) throws SystemException { +// if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) { +// txObject.getUserTransaction().setTransactionTimeout(timeout); +// } +// } +// +// +// @Override +// protected Object doSuspend(Object transaction) { +// JtaTransactionObject txObject = (JtaTransactionObject) transaction; +// try { +// return doJtaSuspend(txObject); +// } +// catch (SystemException ex) { +// throw new TransactionSystemException("JTA failure on suspend", ex); +// } +// } +// +// /** +// * Perform a JTA suspend on the JTA TransactionManager. +// *

Can be overridden in subclasses, for specific JTA implementations. +// * @param txObject the JtaTransactionObject containing the UserTransaction +// * @return the suspended JTA Transaction object +// * @throws SystemException if thrown by JTA methods +// * @see #getTransactionManager() +// * @see javax.transaction.TransactionManager#suspend() +// */ +// protected Object doJtaSuspend(JtaTransactionObject txObject) throws SystemException { +// if (getTransactionManager() == null) { +// throw new TransactionSuspensionNotSupportedException( +// "JtaTransactionManager needs a JTA TransactionManager for suspending a transaction: " + +// "specify the 'transactionManager' or 'transactionManagerName' property"); +// } +// return getTransactionManager().suspend(); +// } +// +// @Override +// protected void doResume(Object transaction, Object suspendedResources) { +// JtaTransactionObject txObject = (JtaTransactionObject) transaction; +// try { +// doJtaResume(txObject, suspendedResources); +// } +// catch (InvalidTransactionException ex) { +// throw new IllegalTransactionStateException("Tried to resume invalid JTA transaction", ex); +// } +// catch (IllegalStateException ex) { +// throw new TransactionSystemException("Unexpected internal transaction state", ex); +// } +// catch (SystemException ex) { +// throw new TransactionSystemException("JTA failure on resume", ex); +// } +// } +// +// /** +// * Perform a JTA resume on the JTA TransactionManager. +// *

Can be overridden in subclasses, for specific JTA implementations. +// * @param txObject the JtaTransactionObject containing the UserTransaction +// * @param suspendedTransaction the suspended JTA Transaction object +// * @throws InvalidTransactionException if thrown by JTA methods +// * @throws SystemException if thrown by JTA methods +// * @see #getTransactionManager() +// * @see javax.transaction.TransactionManager#resume(javax.transaction.Transaction) +// */ +// protected void doJtaResume(JtaTransactionObject txObject, Object suspendedTransaction) +// throws InvalidTransactionException, SystemException { +// +// if (getTransactionManager() == null) { +// throw new TransactionSuspensionNotSupportedException( +// "JtaTransactionManager needs a JTA TransactionManager for suspending a transaction: " + +// "specify the 'transactionManager' or 'transactionManagerName' property"); +// } +// getTransactionManager().resume((Transaction) suspendedTransaction); +// } +// +// +// /** +// * This implementation returns "true": a JTA commit will properly handle +// * transactions that have been marked rollback-only at a global level. +// */ +// @Override +// protected boolean shouldCommitOnGlobalRollbackOnly() { +// return true; +// } +// +// @Override +// protected void doCommit(DefaultTransactionStatus status) { +// JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); +// try { +// int jtaStatus = txObject.getUserTransaction().getStatus(); +// if (jtaStatus == Status.STATUS_NO_TRANSACTION) { +// // Should never happen... would have thrown an exception before +// // and as a consequence led to a rollback, not to a commit call. +// // In any case, the transaction is already fully cleaned up. +// throw new UnexpectedRollbackException("JTA transaction already completed - probably rolled back"); +// } +// if (jtaStatus == Status.STATUS_ROLLEDBACK) { +// // Only really happens on JBoss 4.2 in case of an early timeout... +// // Explicit rollback call necessary to clean up the transaction. +// // IllegalStateException expected on JBoss; call still necessary. +// try { +// txObject.getUserTransaction().rollback(); +// } +// catch (IllegalStateException ex) { +// if (logger.isDebugEnabled()) { +// logger.debug("Rollback failure with transaction already marked as rolled back: " + ex); +// } +// } +// throw new UnexpectedRollbackException("JTA transaction already rolled back (probably due to a timeout)"); +// } +// txObject.getUserTransaction().commit(); +// } +// catch (RollbackException ex) { +// throw new UnexpectedRollbackException( +// "JTA transaction unexpectedly rolled back (maybe due to a timeout)", ex); +// } +// catch (HeuristicMixedException ex) { +// throw new HeuristicCompletionException(HeuristicCompletionException.STATE_MIXED, ex); +// } +// catch (HeuristicRollbackException ex) { +// throw new HeuristicCompletionException(HeuristicCompletionException.STATE_ROLLED_BACK, ex); +// } +// catch (IllegalStateException ex) { +// throw new TransactionSystemException("Unexpected internal transaction state", ex); +// } +// catch (SystemException ex) { +// throw new TransactionSystemException("JTA failure on commit", ex); +// } +// } +// +// @Override +// protected void doRollback(DefaultTransactionStatus status) { +// JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); +// try { +// int jtaStatus = txObject.getUserTransaction().getStatus(); +// if (jtaStatus != Status.STATUS_NO_TRANSACTION) { +// try { +// txObject.getUserTransaction().rollback(); +// } +// catch (IllegalStateException ex) { +// if (jtaStatus == Status.STATUS_ROLLEDBACK) { +// // Only really happens on JBoss 4.2 in case of an early timeout... +// if (logger.isDebugEnabled()) { +// logger.debug("Rollback failure with transaction already marked as rolled back: " + ex); +// } +// } +// else { +// throw new TransactionSystemException("Unexpected internal transaction state", ex); +// } +// } +// } +// } +// catch (SystemException ex) { +// throw new TransactionSystemException("JTA failure on rollback", ex); +// } +// } +// +// @Override +// protected void doSetRollbackOnly(DefaultTransactionStatus status) { +// JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); +// if (status.isDebug()) { +// logger.debug("Setting JTA transaction rollback-only"); +// } +// try { +// int jtaStatus = txObject.getUserTransaction().getStatus(); +// if (jtaStatus != Status.STATUS_NO_TRANSACTION && jtaStatus != Status.STATUS_ROLLEDBACK) { +// txObject.getUserTransaction().setRollbackOnly(); +// } +// } +// catch (IllegalStateException ex) { +// throw new TransactionSystemException("Unexpected internal transaction state", ex); +// } +// catch (SystemException ex) { +// throw new TransactionSystemException("JTA failure on setRollbackOnly", ex); +// } +// } +// +// +// @Override +// protected void registerAfterCompletionWithExistingTransaction( +// Object transaction, List synchronizations) { +// +// JtaTransactionObject txObject = (JtaTransactionObject) transaction; +// logger.debug("Registering after-completion synchronization with existing JTA transaction"); +// try { +// doRegisterAfterCompletionWithJtaTransaction(txObject, synchronizations); +// } +// catch (SystemException ex) { +// throw new TransactionSystemException("JTA failure on registerSynchronization", ex); +// } +// catch (Exception ex) { +// // Note: JBoss throws plain RuntimeException with RollbackException as cause. +// if (ex instanceof RollbackException || ex.getCause() instanceof RollbackException) { +// logger.debug("Participating in existing JTA transaction that has been marked for rollback: " + +// "cannot register Spring after-completion callbacks with outer JTA transaction - " + +// "immediately performing Spring after-completion callbacks with outcome status 'rollback'. " + +// "Original exception: " + ex); +// invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_ROLLED_BACK); +// } +// else { +// logger.debug("Participating in existing JTA transaction, but unexpected internal transaction " + +// "state encountered: cannot register Spring after-completion callbacks with outer JTA " + +// "transaction - processing Spring after-completion callbacks with outcome status 'unknown'" + +// "Original exception: " + ex); +// invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); +// } +// } +// } +// +// /** +// * Register a JTA synchronization on the JTA TransactionManager, for calling +// * {@code afterCompletion} on the given Spring TransactionSynchronizations. +// *

The default implementation registers the synchronizations on the +// * JTA 1.1 TransactionSynchronizationRegistry, if available, or on the +// * JTA TransactionManager's current Transaction - again, if available. +// * If none of the two is available, a warning will be logged. +// *

Can be overridden in subclasses, for specific JTA implementations. +// * @param txObject the current transaction object +// * @param synchronizations List of TransactionSynchronization objects +// * @throws RollbackException if thrown by JTA methods +// * @throws SystemException if thrown by JTA methods +// * @see #getTransactionManager() +// * @see javax.transaction.Transaction#registerSynchronization +// * @see javax.transaction.TransactionSynchronizationRegistry#registerInterposedSynchronization +// */ +// protected void doRegisterAfterCompletionWithJtaTransaction( +// JtaTransactionObject txObject, List synchronizations) +// throws RollbackException, SystemException { +// +// int jtaStatus = txObject.getUserTransaction().getStatus(); +// if (jtaStatus == Status.STATUS_NO_TRANSACTION) { +// throw new RollbackException("JTA transaction already completed - probably rolled back"); +// } +// if (jtaStatus == Status.STATUS_ROLLEDBACK) { +// throw new RollbackException("JTA transaction already rolled back (probably due to a timeout)"); +// } +// +// if (this.transactionSynchronizationRegistry != null) { +// // JTA 1.1 TransactionSynchronizationRegistry available - use it. +// this.transactionSynchronizationRegistry.registerInterposedSynchronization( +// new JtaAfterCompletionSynchronization(synchronizations)); +// } +// +// else if (getTransactionManager() != null) { +// // At least the JTA TransactionManager available - use that one. +// Transaction transaction = getTransactionManager().getTransaction(); +// if (transaction == null) { +// throw new IllegalStateException("No JTA Transaction available"); +// } +// transaction.registerSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); +// } +// +// else { +// // No JTA TransactionManager available - log a warning. +// logger.warn("Participating in existing JTA transaction, but no JTA TransactionManager available: " + +// "cannot register Spring after-completion callbacks with outer JTA transaction - " + +// "processing Spring after-completion callbacks with outcome status 'unknown'"); +// invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); +// } +// } +// +// +// //--------------------------------------------------------------------- +// // Implementation of TransactionFactory interface +// //--------------------------------------------------------------------- +// +// @Override +// public Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException { +// TransactionManager tm = getTransactionManager(); +// Assert.state(tm != null, "No JTA TransactionManager available"); +// if (timeout >= 0) { +// tm.setTransactionTimeout(timeout); +// } +// tm.begin(); +// return new ManagedTransactionAdapter(tm); +// } +// +// @Override +// public boolean supportsResourceAdapterManagedTransactions() { +// return false; +// } +// +// +// //--------------------------------------------------------------------- +// // Serialization support +// //--------------------------------------------------------------------- +// +// private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { +// // Rely on default serialization; just initialize state after deserialization. +// ois.defaultReadObject(); +// +// // Create template for client-side JNDI lookup. +// this.jndiTemplate = new JndiTemplate(); +// +// // Perform a fresh lookup for JTA handles. +// initUserTransactionAndTransactionManager(); +// initTransactionSynchronizationRegistry(); +// } +// +//} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/JtaTransactionObject.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/JtaTransactionObject.java new file mode 100644 index 000000000..355bad642 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/JtaTransactionObject.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.jta; + +import javax.transaction.Status; +import javax.transaction.SystemException; +import javax.transaction.UserTransaction; + +import com.fr.third.springframework.transaction.TransactionSystemException; +import com.fr.third.springframework.transaction.support.SmartTransactionObject; +import com.fr.third.springframework.transaction.support.TransactionSynchronizationUtils; + +/** + * JTA transaction object, representing a {@link javax.transaction.UserTransaction}. + * Used as transaction object by Spring's {@link JtaTransactionManager}. + * + *

Note: This is an SPI class, not intended to be used by applications. + * + * @author Juergen Hoeller + * @since 1.1 + * @see JtaTransactionManager + * @see javax.transaction.UserTransaction + */ +public class JtaTransactionObject implements SmartTransactionObject { + + private final UserTransaction userTransaction; + + + /** + * Create a new JtaTransactionObject for the given JTA UserTransaction. + * @param userTransaction the JTA UserTransaction for the current transaction + * (either a shared object or retrieved through a fresh per-transaction lookuip) + */ + public JtaTransactionObject(UserTransaction userTransaction) { + this.userTransaction = userTransaction; + } + + /** + * Return the JTA UserTransaction object for the current transaction. + */ + public final UserTransaction getUserTransaction() { + return this.userTransaction; + } + + + /** + * This implementation checks the UserTransaction's rollback-only flag. + */ + @Override + public boolean isRollbackOnly() { + if (this.userTransaction == null) { + return false; + } + try { + int jtaStatus = this.userTransaction.getStatus(); + return (jtaStatus == Status.STATUS_MARKED_ROLLBACK || jtaStatus == Status.STATUS_ROLLEDBACK); + } + catch (SystemException ex) { + throw new TransactionSystemException("JTA failure on getStatus", ex); + } + } + + /** + * This implementation triggers flush callbacks, + * assuming that they will flush all affected ORM sessions. + * @see com.fr.third.springframework.transaction.support.TransactionSynchronization#flush() + */ + @Override + public void flush() { + TransactionSynchronizationUtils.triggerFlush(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/ManagedTransactionAdapter.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/ManagedTransactionAdapter.java new file mode 100644 index 000000000..c06c40472 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/ManagedTransactionAdapter.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.jta; + +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.RollbackException; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.xa.XAResource; + +import com.fr.third.springframework.util.Assert; + +/** + * Adapter for a managed JTA Transaction handle, taking a JTA + * {@link javax.transaction.TransactionManager} reference and creating + * a JTA {@link javax.transaction.Transaction} handle for it. + * + * @author Juergen Hoeller + * @since 3.0.2 + */ +public class ManagedTransactionAdapter implements Transaction { + + private final TransactionManager transactionManager; + + + /** + * Create a new ManagedTransactionAdapter for the given TransactionManager. + * @param transactionManager the JTA TransactionManager to wrap + */ + public ManagedTransactionAdapter(TransactionManager transactionManager) throws SystemException { + Assert.notNull(transactionManager, "TransactionManager must not be null"); + this.transactionManager = transactionManager; + } + + /** + * Return the JTA TransactionManager that this adapter delegates to. + */ + public final TransactionManager getTransactionManager() { + return this.transactionManager; + } + + + @Override + public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, + SecurityException, SystemException { + this.transactionManager.commit(); + } + + @Override + public void rollback() throws SystemException { + this.transactionManager.rollback(); + } + + @Override + public void setRollbackOnly() throws SystemException { + this.transactionManager.setRollbackOnly(); + } + + @Override + public int getStatus() throws SystemException { + return this.transactionManager.getStatus(); + } + + @Override + public boolean enlistResource(XAResource xaRes) throws RollbackException, SystemException { + return this.transactionManager.getTransaction().enlistResource(xaRes); + } + + @Override + public boolean delistResource(XAResource xaRes, int flag) throws SystemException { + return this.transactionManager.getTransaction().delistResource(xaRes, flag); + } + + @Override + public void registerSynchronization(Synchronization sync) throws RollbackException, SystemException { + this.transactionManager.getTransaction().registerSynchronization(sync); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/SimpleTransactionFactory.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/SimpleTransactionFactory.java new file mode 100644 index 000000000..e619d4c43 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/SimpleTransactionFactory.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.jta; + +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; + +import com.fr.third.springframework.util.Assert; + +/** + * Default implementation of the {@link TransactionFactory} strategy interface, + * simply wrapping a standard JTA {@link javax.transaction.TransactionManager}. + * + *

Does not support transaction names; simply ignores any specified name. + * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.transaction.TransactionManager#setTransactionTimeout(int) + * @see javax.transaction.TransactionManager#begin() + * @see javax.transaction.TransactionManager#getTransaction() + */ +public class SimpleTransactionFactory implements TransactionFactory { + + private final TransactionManager transactionManager; + + + /** + * Create a new SimpleTransactionFactory for the given TransactionManager + * @param transactionManager the JTA TransactionManager to wrap + */ + public SimpleTransactionFactory(TransactionManager transactionManager) { + Assert.notNull(transactionManager, "TransactionManager must not be null"); + this.transactionManager = transactionManager; + } + + + @Override + public Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException { + if (timeout >= 0) { + this.transactionManager.setTransactionTimeout(timeout); + } + this.transactionManager.begin(); + return new ManagedTransactionAdapter(this.transactionManager); + } + + @Override + public boolean supportsResourceAdapterManagedTransactions() { + return false; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/SpringJtaSynchronizationAdapter.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/SpringJtaSynchronizationAdapter.java new file mode 100644 index 000000000..47f06cfd8 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/SpringJtaSynchronizationAdapter.java @@ -0,0 +1,192 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.jta; + +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.transaction.support.TransactionSynchronization; +import com.fr.third.springframework.transaction.support.TransactionSynchronizationManager; +import com.fr.third.springframework.util.Assert; + +/** + * Adapter that implements the JTA {@link javax.transaction.Synchronization} + * interface delegating to an underlying Spring + * {@link com.fr.third.springframework.transaction.support.TransactionSynchronization}. + * + *

Useful for synchronizing Spring resource management code with plain + * JTA / EJB CMT transactions, despite the original code being built for + * Spring transaction synchronization. + * + * @author Juergen Hoeller + * @since 2.0 + * @see javax.transaction.Transaction#registerSynchronization + * @see com.fr.third.springframework.transaction.support.TransactionSynchronization + */ +public class SpringJtaSynchronizationAdapter implements Synchronization { + + protected static final Log logger = LogFactory.getLog(SpringJtaSynchronizationAdapter.class); + + private final TransactionSynchronization springSynchronization; + + private UserTransaction jtaTransaction; + + private boolean beforeCompletionCalled = false; + + + /** + * Create a new SpringJtaSynchronizationAdapter for the given Spring + * TransactionSynchronization and JTA TransactionManager. + * @param springSynchronization the Spring TransactionSynchronization to delegate to + */ + public SpringJtaSynchronizationAdapter(TransactionSynchronization springSynchronization) { + Assert.notNull(springSynchronization, "TransactionSynchronization must not be null"); + this.springSynchronization = springSynchronization; + } + + /** + * Create a new SpringJtaSynchronizationAdapter for the given Spring + * TransactionSynchronization and JTA TransactionManager. + *

Note that this adapter will never perform a rollback-only call on WebLogic, + * since WebLogic Server is known to automatically mark the transaction as + * rollback-only in case of a {@code beforeCompletion} exception. Hence, + * on WLS, this constructor is equivalent to the single-arg constructor. + * @param springSynchronization the Spring TransactionSynchronization to delegate to + * @param jtaUserTransaction the JTA UserTransaction to use for rollback-only + * setting in case of an exception thrown in {@code beforeCompletion} + * (can be omitted if the JTA provider itself marks the transaction rollback-only + * in such a scenario, which is required by the JTA specification as of JTA 1.1). + */ + public SpringJtaSynchronizationAdapter( + TransactionSynchronization springSynchronization, UserTransaction jtaUserTransaction) { + + this(springSynchronization); + if (jtaUserTransaction != null && !jtaUserTransaction.getClass().getName().startsWith("weblogic.")) { + this.jtaTransaction = jtaUserTransaction; + } + } + + /** + * Create a new SpringJtaSynchronizationAdapter for the given Spring + * TransactionSynchronization and JTA TransactionManager. + *

Note that this adapter will never perform a rollback-only call on WebLogic, + * since WebLogic Server is known to automatically mark the transaction as + * rollback-only in case of a {@code beforeCompletion} exception. Hence, + * on WLS, this constructor is equivalent to the single-arg constructor. + * @param springSynchronization the Spring TransactionSynchronization to delegate to + * @param jtaTransactionManager the JTA TransactionManager to use for rollback-only + * setting in case of an exception thrown in {@code beforeCompletion} + * (can be omitted if the JTA provider itself marks the transaction rollback-only + * in such a scenario, which is required by the JTA specification as of JTA 1.1) + */ + public SpringJtaSynchronizationAdapter( + TransactionSynchronization springSynchronization, TransactionManager jtaTransactionManager) { + + this(springSynchronization); + if (jtaTransactionManager != null && !jtaTransactionManager.getClass().getName().startsWith("weblogic.")) { + this.jtaTransaction = new UserTransactionAdapter(jtaTransactionManager); + } + } + + + /** + * JTA {@code beforeCompletion} callback: just invoked before commit. + *

In case of an exception, the JTA transaction will be marked as rollback-only. + * @see com.fr.third.springframework.transaction.support.TransactionSynchronization#beforeCommit + */ + @Override + public void beforeCompletion() { + try { + boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); + this.springSynchronization.beforeCommit(readOnly); + } + catch (RuntimeException ex) { + setRollbackOnlyIfPossible(); + throw ex; + } + catch (Error err) { + setRollbackOnlyIfPossible(); + throw err; + } + finally { + // Process Spring's beforeCompletion early, in order to avoid issues + // with strict JTA implementations that issue warnings when doing JDBC + // operations after transaction completion (e.g. Connection.getWarnings). + this.beforeCompletionCalled = true; + this.springSynchronization.beforeCompletion(); + } + } + + /** + * Set the underlying JTA transaction to rollback-only. + */ + private void setRollbackOnlyIfPossible() { + if (this.jtaTransaction != null) { + try { + this.jtaTransaction.setRollbackOnly(); + } + catch (UnsupportedOperationException ex) { + // Probably Hibernate's WebSphereExtendedJTATransactionLookup pseudo JTA stuff... + logger.debug("JTA transaction handle does not support setRollbackOnly method - " + + "relying on JTA provider to mark the transaction as rollback-only based on " + + "the exception thrown from beforeCompletion", ex); + } + catch (Throwable ex) { + logger.error("Could not set JTA transaction rollback-only", ex); + } + } + else { + logger.debug("No JTA transaction handle available and/or running on WebLogic - " + + "relying on JTA provider to mark the transaction as rollback-only based on " + + "the exception thrown from beforeCompletion"); + } + } + + /** + * JTA {@code afterCompletion} callback: invoked after commit/rollback. + *

Needs to invoke the Spring synchronization's {@code beforeCompletion} + * at this late stage in case of a rollback, since there is no corresponding + * callback with JTA. + * @see com.fr.third.springframework.transaction.support.TransactionSynchronization#beforeCompletion + * @see com.fr.third.springframework.transaction.support.TransactionSynchronization#afterCompletion + */ + @Override + public void afterCompletion(int status) { + if (!this.beforeCompletionCalled) { + // beforeCompletion not called before (probably because of JTA rollback). + // Perform the cleanup here. + this.springSynchronization.beforeCompletion(); + } + // Call afterCompletion with the appropriate status indication. + switch (status) { + case Status.STATUS_COMMITTED: + this.springSynchronization.afterCompletion(TransactionSynchronization.STATUS_COMMITTED); + break; + case Status.STATUS_ROLLEDBACK: + this.springSynchronization.afterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK); + break; + default: + this.springSynchronization.afterCompletion(TransactionSynchronization.STATUS_UNKNOWN); + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/TransactionFactory.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/TransactionFactory.java new file mode 100644 index 000000000..d6b04d424 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/TransactionFactory.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.jta; + +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.Transaction; + +/** + * Strategy interface for creating JTA {@link javax.transaction.Transaction} + * objects based on specified transactional characteristics. + * + *

The default implementation, {@link SimpleTransactionFactory}, simply + * wraps a standard JTA {@link javax.transaction.TransactionManager}. + * This strategy interface allows for more sophisticated implementations + * that adapt to vendor-specific JTA extensions. + * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.transaction.TransactionManager#getTransaction() + * @see SimpleTransactionFactory + * @see JtaTransactionManager + */ +public interface TransactionFactory { + + /** + * Create an active Transaction object based on the given name and timeout. + * @param name the transaction name (may be {@code null}) + * @param timeout the transaction timeout (may be -1 for the default timeout) + * @return the active Transaction object (never {@code null}) + * @throws NotSupportedException if the transaction manager does not support + * a transaction of the specified type + * @throws SystemException if the transaction manager failed to create the + * transaction + */ + Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException; + + /** + * Determine whether the underlying transaction manager supports XA transactions + * managed by a resource adapter (i.e. without explicit XA resource enlistment). + *

Typically {@code false}. Checked by + * {@link com.fr.third.springframework.jca.endpoint.AbstractMessageEndpointFactory} + * in order to differentiate between invalid configuration and valid + * ResourceAdapter-managed transactions. + * @see javax.resource.spi.ResourceAdapter#endpointActivation + * @see javax.resource.spi.endpoint.MessageEndpointFactory#isDeliveryTransacted + */ + boolean supportsResourceAdapterManagedTransactions(); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/UserTransactionAdapter.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/UserTransactionAdapter.java new file mode 100644 index 000000000..0bc5f20b6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/UserTransactionAdapter.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.jta; + +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.SystemException; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +import com.fr.third.springframework.util.Assert; + +/** + * Adapter for a JTA UserTransaction handle, taking a JTA + * {@link javax.transaction.TransactionManager} reference and creating + * a JTA {@link javax.transaction.UserTransaction} handle for it. + * + *

The JTA UserTransaction interface is an exact subset of the JTA + * TransactionManager interface. Unfortunately, it does not serve as + * super-interface of TransactionManager, though, which requires an + * adapter such as this class to be used when intending to talk to + * a TransactionManager handle through the UserTransaction interface. + * + *

Used internally by Spring's {@link JtaTransactionManager} for certain + * scenarios. Not intended for direct use in application code. + * + * @author Juergen Hoeller + * @since 1.1.5 + */ +public class UserTransactionAdapter implements UserTransaction { + + private final TransactionManager transactionManager; + + + /** + * Create a new UserTransactionAdapter for the given TransactionManager. + * @param transactionManager the JTA TransactionManager to wrap + */ + public UserTransactionAdapter(TransactionManager transactionManager) { + Assert.notNull(transactionManager, "TransactionManager must not be null"); + this.transactionManager = transactionManager; + } + + /** + * Return the JTA TransactionManager that this adapter delegates to. + */ + public final TransactionManager getTransactionManager() { + return this.transactionManager; + } + + + @Override + public void setTransactionTimeout(int timeout) throws SystemException { + this.transactionManager.setTransactionTimeout(timeout); + } + + @Override + public void begin() throws NotSupportedException, SystemException { + this.transactionManager.begin(); + } + + @Override + public void commit() + throws RollbackException, HeuristicMixedException, HeuristicRollbackException, + SecurityException, SystemException { + this.transactionManager.commit(); + } + + @Override + public void rollback() throws SecurityException, SystemException { + this.transactionManager.rollback(); + } + + @Override + public void setRollbackOnly() throws SystemException { + this.transactionManager.setRollbackOnly(); + } + + @Override + public int getStatus() throws SystemException { + return this.transactionManager.getStatus(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/WebLogicJtaTransactionManager.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/WebLogicJtaTransactionManager.java new file mode 100644 index 000000000..a000191fa --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/WebLogicJtaTransactionManager.java @@ -0,0 +1,341 @@ +///* +// * Copyright 2002-2013 the original author or authors. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package com.fr.third.springframework.transaction.jta; +// +//import java.io.Serializable; +//import java.lang.reflect.InvocationTargetException; +//import java.lang.reflect.Method; +//import javax.transaction.InvalidTransactionException; +//import javax.transaction.NotSupportedException; +//import javax.transaction.SystemException; +//import javax.transaction.Transaction; +//import javax.transaction.TransactionManager; +//import javax.transaction.UserTransaction; +// +//import com.fr.third.springframework.transaction.TransactionDefinition; +//import com.fr.third.springframework.transaction.TransactionSystemException; +// +///** +// * Special {@link JtaTransactionManager} variant for BEA WebLogic (9.0 and higher). +// * Supports the full power of Spring's transaction definitions on WebLogic's +// * transaction coordinator, beyond standard JTA: transaction names, +// * per-transaction isolation levels, and proper resuming of transactions in all cases. +// * +// *

Uses WebLogic's special {@code begin(name)} method to start a JTA transaction, +// * in order to make Spring-driven transactions visible in WebLogic's transaction +// * monitor. In case of Spring's declarative transactions, the exposed name will +// * (by default) be the fully-qualified class name + "." + method name. +// * +// *

Supports a per-transaction isolation level through WebLogic's corresponding +// * JTA transaction property "ISOLATION LEVEL". This will apply the specified isolation +// * level (e.g. ISOLATION_SERIALIZABLE) to all JDBC Connections that participate in the +// * given transaction. +// * +// *

Invokes WebLogic's special {@code forceResume} method if standard JTA resume +// * failed, to also resume if the target transaction was marked rollback-only. +// * If you're not relying on this feature of transaction suspension in the first +// * place, Spring's standard JtaTransactionManager will behave properly too. +// * +// *

By default, the JTA UserTransaction and TransactionManager handles are +// * fetched directly from WebLogic's {@code TransactionHelper}. This can be +// * overridden by specifying "userTransaction"/"userTransactionName" and +// * "transactionManager"/"transactionManagerName", passing in existing handles +// * or specifying corresponding JNDI locations to look up. +// * +// *

NOTE: This JtaTransactionManager is intended to refine specific transaction +// * demarcation behavior on Spring's side. It will happily co-exist with independently +// * configured WebLogic transaction strategies in your persistence provider, with no +// * need to specifically connect those setups in any way. +// * +// * @author Juergen Hoeller +// * @since 1.1 +// * @see com.fr.third.springframework.transaction.TransactionDefinition#getName +// * @see com.fr.third.springframework.transaction.TransactionDefinition#getIsolationLevel +// * @see weblogic.transaction.UserTransaction#begin(String) +// * @see weblogic.transaction.Transaction#setProperty +// * @see weblogic.transaction.TransactionManager#forceResume +// * @see weblogic.transaction.TransactionHelper +// */ +//@SuppressWarnings("serial") +//public class WebLogicJtaTransactionManager extends JtaTransactionManager { +// +// private static final String USER_TRANSACTION_CLASS_NAME = "weblogic.transaction.UserTransaction"; +// +// private static final String CLIENT_TRANSACTION_MANAGER_CLASS_NAME = "weblogic.transaction.ClientTransactionManager"; +// +// private static final String TRANSACTION_CLASS_NAME = "weblogic.transaction.Transaction"; +// +// private static final String TRANSACTION_HELPER_CLASS_NAME = "weblogic.transaction.TransactionHelper"; +// +// private static final String ISOLATION_LEVEL_KEY = "ISOLATION LEVEL"; +// +// +// private boolean weblogicUserTransactionAvailable; +// +// private Method beginWithNameMethod; +// +// private Method beginWithNameAndTimeoutMethod; +// +// private boolean weblogicTransactionManagerAvailable; +// +// private Method forceResumeMethod; +// +// private Method setPropertyMethod; +// +// private Object transactionHelper; +// +// +// @Override +// public void afterPropertiesSet() throws TransactionSystemException { +// super.afterPropertiesSet(); +// loadWebLogicTransactionClasses(); +// } +// +// @Override +// protected UserTransaction retrieveUserTransaction() throws TransactionSystemException { +// loadWebLogicTransactionHelper(); +// try { +// logger.debug("Retrieving JTA UserTransaction from WebLogic TransactionHelper"); +// Method getUserTransactionMethod = this.transactionHelper.getClass().getMethod("getUserTransaction"); +// return (UserTransaction) getUserTransactionMethod.invoke(this.transactionHelper); +// } +// catch (InvocationTargetException ex) { +// throw new TransactionSystemException( +// "WebLogic's TransactionHelper.getUserTransaction() method failed", ex.getTargetException()); +// } +// catch (Exception ex) { +// throw new TransactionSystemException( +// "Could not invoke WebLogic's TransactionHelper.getUserTransaction() method", ex); +// } +// } +// +// @Override +// protected TransactionManager retrieveTransactionManager() throws TransactionSystemException { +// loadWebLogicTransactionHelper(); +// try { +// logger.debug("Retrieving JTA TransactionManager from WebLogic TransactionHelper"); +// Method getTransactionManagerMethod = this.transactionHelper.getClass().getMethod("getTransactionManager"); +// return (TransactionManager) getTransactionManagerMethod.invoke(this.transactionHelper); +// } +// catch (InvocationTargetException ex) { +// throw new TransactionSystemException( +// "WebLogic's TransactionHelper.getTransactionManager() method failed", ex.getTargetException()); +// } +// catch (Exception ex) { +// throw new TransactionSystemException( +// "Could not invoke WebLogic's TransactionHelper.getTransactionManager() method", ex); +// } +// } +// +// private void loadWebLogicTransactionHelper() throws TransactionSystemException { +// if (this.transactionHelper == null) { +// try { +// Class transactionHelperClass = getClass().getClassLoader().loadClass(TRANSACTION_HELPER_CLASS_NAME); +// Method getTransactionHelperMethod = transactionHelperClass.getMethod("getTransactionHelper"); +// this.transactionHelper = getTransactionHelperMethod.invoke(null); +// logger.debug("WebLogic TransactionHelper found"); +// } +// catch (InvocationTargetException ex) { +// throw new TransactionSystemException( +// "WebLogic's TransactionHelper.getTransactionHelper() method failed", ex.getTargetException()); +// } +// catch (Exception ex) { +// throw new TransactionSystemException( +// "Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available", +// ex); +// } +// } +// } +// +// private void loadWebLogicTransactionClasses() throws TransactionSystemException { +// try { +// Class userTransactionClass = getClass().getClassLoader().loadClass(USER_TRANSACTION_CLASS_NAME); +// this.weblogicUserTransactionAvailable = userTransactionClass.isInstance(getUserTransaction()); +// if (this.weblogicUserTransactionAvailable) { +// this.beginWithNameMethod = userTransactionClass.getMethod("begin", String.class); +// this.beginWithNameAndTimeoutMethod = userTransactionClass.getMethod("begin", String.class, int.class); +// logger.info("Support for WebLogic transaction names available"); +// } +// else { +// logger.info("Support for WebLogic transaction names not available"); +// } +// +// // Obtain WebLogic ClientTransactionManager interface. +// Class transactionManagerClass = +// getClass().getClassLoader().loadClass(CLIENT_TRANSACTION_MANAGER_CLASS_NAME); +// logger.debug("WebLogic ClientTransactionManager found"); +// +// this.weblogicTransactionManagerAvailable = transactionManagerClass.isInstance(getTransactionManager()); +// if (this.weblogicTransactionManagerAvailable) { +// Class transactionClass = getClass().getClassLoader().loadClass(TRANSACTION_CLASS_NAME); +// this.forceResumeMethod = transactionManagerClass.getMethod("forceResume", Transaction.class); +// this.setPropertyMethod = transactionClass.getMethod("setProperty", String.class, Serializable.class); +// logger.debug("Support for WebLogic forceResume available"); +// } +// else { +// logger.warn("Support for WebLogic forceResume not available"); +// } +// } +// catch (Exception ex) { +// throw new TransactionSystemException( +// "Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available", +// ex); +// } +// } +// +// +// @Override +// protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition) +// throws NotSupportedException, SystemException { +// +// int timeout = determineTimeout(definition); +// +// // Apply transaction name (if any) to WebLogic transaction. +// if (this.weblogicUserTransactionAvailable && definition.getName() != null) { +// try { +// if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) { +// /* +// weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut; +// wut.begin(definition.getName(), timeout); +// */ +// this.beginWithNameAndTimeoutMethod.invoke(txObject.getUserTransaction(), definition.getName(), timeout); +// } +// else { +// /* +// weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut; +// wut.begin(definition.getName()); +// */ +// this.beginWithNameMethod.invoke(txObject.getUserTransaction(), definition.getName()); +// } +// } +// catch (InvocationTargetException ex) { +// throw new TransactionSystemException( +// "WebLogic's UserTransaction.begin() method failed", ex.getTargetException()); +// } +// catch (Exception ex) { +// throw new TransactionSystemException( +// "Could not invoke WebLogic's UserTransaction.begin() method", ex); +// } +// } +// else { +// // No WebLogic UserTransaction available or no transaction name specified +// // -> standard JTA begin call. +// applyTimeout(txObject, timeout); +// txObject.getUserTransaction().begin(); +// } +// +// // Specify isolation level, if any, through corresponding WebLogic transaction property. +// if (this.weblogicTransactionManagerAvailable) { +// if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { +// try { +// Transaction tx = getTransactionManager().getTransaction(); +// Integer isolationLevel = definition.getIsolationLevel(); +// /* +// weblogic.transaction.Transaction wtx = (weblogic.transaction.Transaction) tx; +// wtx.setProperty(ISOLATION_LEVEL_KEY, isolationLevel); +// */ +// this.setPropertyMethod.invoke(tx, ISOLATION_LEVEL_KEY, isolationLevel); +// } +// catch (InvocationTargetException ex) { +// throw new TransactionSystemException( +// "WebLogic's Transaction.setProperty(String, Serializable) method failed", ex.getTargetException()); +// } +// catch (Exception ex) { +// throw new TransactionSystemException( +// "Could not invoke WebLogic's Transaction.setProperty(String, Serializable) method", ex); +// } +// } +// } +// else { +// applyIsolationLevel(txObject, definition.getIsolationLevel()); +// } +// } +// +// @Override +// protected void doJtaResume(JtaTransactionObject txObject, Object suspendedTransaction) +// throws InvalidTransactionException, SystemException { +// +// try { +// getTransactionManager().resume((Transaction) suspendedTransaction); +// } +// catch (InvalidTransactionException ex) { +// if (!this.weblogicTransactionManagerAvailable) { +// throw ex; +// } +// +// if (logger.isDebugEnabled()) { +// logger.debug("Standard JTA resume threw InvalidTransactionException: " + ex.getMessage() + +// " - trying WebLogic JTA forceResume"); +// } +// /* +// weblogic.transaction.TransactionManager wtm = +// (weblogic.transaction.TransactionManager) getTransactionManager(); +// wtm.forceResume(suspendedTransaction); +// */ +// try { +// this.forceResumeMethod.invoke(getTransactionManager(), suspendedTransaction); +// } +// catch (InvocationTargetException ex2) { +// throw new TransactionSystemException( +// "WebLogic's TransactionManager.forceResume(Transaction) method failed", ex2.getTargetException()); +// } +// catch (Exception ex2) { +// throw new TransactionSystemException( +// "Could not access WebLogic's TransactionManager.forceResume(Transaction) method", ex2); +// } +// } +// } +// +// @Override +// public Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException { +// if (this.weblogicUserTransactionAvailable && name != null) { +// try { +// if (timeout >= 0) { +// this.beginWithNameAndTimeoutMethod.invoke(getUserTransaction(), name, timeout); +// } +// else { +// this.beginWithNameMethod.invoke(getUserTransaction(), name); +// } +// } +// catch (InvocationTargetException ex) { +// if (ex.getTargetException() instanceof NotSupportedException) { +// throw (NotSupportedException) ex.getTargetException(); +// } +// else if (ex.getTargetException() instanceof SystemException) { +// throw (SystemException) ex.getTargetException(); +// } +// else if (ex.getTargetException() instanceof RuntimeException) { +// throw (RuntimeException) ex.getTargetException(); +// } +// else { +// throw new SystemException( +// "WebLogic's begin() method failed with an unexpected error: " + ex.getTargetException()); +// } +// } +// catch (Exception ex) { +// throw new SystemException("Could not invoke WebLogic's UserTransaction.begin() method: " + ex); +// } +// return new ManagedTransactionAdapter(getTransactionManager()); +// } +// +// else { +// // No name specified - standard JTA is sufficient. +// return super.createTransaction(name, timeout); +// } +// } +// +//} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/WebSphereUowTransactionManager.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/WebSphereUowTransactionManager.java new file mode 100644 index 000000000..74d07acb2 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/WebSphereUowTransactionManager.java @@ -0,0 +1,390 @@ +///* +// * Copyright 2002-2014 the original author or authors. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package com.fr.third.springframework.transaction.jta; +// +//import java.util.List; +//import javax.naming.NamingException; +// +//import com.ibm.websphere.uow.UOWSynchronizationRegistry; +//import com.ibm.wsspi.uow.UOWAction; +//import com.ibm.wsspi.uow.UOWActionException; +//import com.ibm.wsspi.uow.UOWException; +//import com.ibm.wsspi.uow.UOWManager; +//import com.ibm.wsspi.uow.UOWManagerFactory; +// +//import com.fr.third.springframework.transaction.IllegalTransactionStateException; +//import com.fr.third.springframework.transaction.InvalidTimeoutException; +//import com.fr.third.springframework.transaction.NestedTransactionNotSupportedException; +//import com.fr.third.springframework.transaction.TransactionDefinition; +//import com.fr.third.springframework.transaction.TransactionException; +//import com.fr.third.springframework.transaction.TransactionSystemException; +//import com.fr.third.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; +//import com.fr.third.springframework.transaction.support.DefaultTransactionDefinition; +//import com.fr.third.springframework.transaction.support.DefaultTransactionStatus; +//import com.fr.third.springframework.transaction.support.SmartTransactionObject; +//import com.fr.third.springframework.transaction.support.TransactionCallback; +//import com.fr.third.springframework.transaction.support.TransactionSynchronization; +//import com.fr.third.springframework.transaction.support.TransactionSynchronizationManager; +//import com.fr.third.springframework.transaction.support.TransactionSynchronizationUtils; +//import com.fr.third.springframework.util.ReflectionUtils; +// +///** +// * WebSphere-specific PlatformTransactionManager implementation that delegates +// * to a {@link com.ibm.wsspi.uow.UOWManager} instance, obtained from WebSphere's +// * JNDI environment. This allows Spring to leverage the full power of the WebSphere +// * transaction coordinator, including transaction suspension, in a manner that is +// * perfectly compliant with officially supported WebSphere API. +// * +// *

The {@link CallbackPreferringPlatformTransactionManager} interface +// * implemented by this class indicates that callers should preferably pass in +// * a {@link TransactionCallback} through the {@link #execute} method, which +// * will be handled through the callback-based WebSphere UOWManager API instead +// * of through standard JTA API (UserTransaction / TransactionManager). This avoids +// * the use of the non-public {@code javax.transaction.TransactionManager} +// * API on WebSphere, staying within supported WebSphere API boundaries. +// * +// *

This transaction manager implementation derives from Spring's standard +// * {@link JtaTransactionManager}, inheriting the capability to support programmatic +// * transaction demarcation via {@code getTransaction} / {@code commit} / +// * {@code rollback} calls through a JTA UserTransaction handle, for callers +// * that do not use the TransactionCallback-based {@link #execute} method. However, +// * transaction suspension is not supported in this {@code getTransaction} +// * style (unless you explicitly specify a {@link #setTransactionManager} reference, +// * despite the official WebSphere recommendations). Use the {@link #execute} style +// * for any code that might require transaction suspension. +// * +// *

This transaction manager is compatible with WebSphere 6.1.0.9 and above. +// * The default JNDI location for the UOWManager is "java:comp/websphere/UOWManager". +// * If the location happens to differ according to your WebSphere documentation, +// * simply specify the actual location through this transaction manager's +// * "uowManagerName" bean property. +// * +// *

NOTE: This JtaTransactionManager is intended to refine specific transaction +// * demarcation behavior on Spring's side. It will happily co-exist with independently +// * configured WebSphere transaction strategies in your persistence provider, with no +// * need to specifically connect those setups in any way. +// * +// * @author Juergen Hoeller +// * @since 2.5 +// * @see #setUowManager +// * @see #setUowManagerName +// * @see com.ibm.wsspi.uow.UOWManager +// */ +//@SuppressWarnings("serial") +//public class WebSphereUowTransactionManager extends JtaTransactionManager +// implements CallbackPreferringPlatformTransactionManager { +// +// /** +// * Default JNDI location for the WebSphere UOWManager. +// * @see #setUowManagerName +// */ +// public static final String DEFAULT_UOW_MANAGER_NAME = "java:comp/websphere/UOWManager"; +// +// +// private UOWManager uowManager; +// +// private String uowManagerName; +// +// +// /** +// * Create a new WebSphereUowTransactionManager. +// */ +// public WebSphereUowTransactionManager() { +// setAutodetectTransactionManager(false); +// } +// +// /** +// * Create a new WebSphereUowTransactionManager for the given UOWManager. +// * @param uowManager the WebSphere UOWManager to use as direct reference +// */ +// public WebSphereUowTransactionManager(UOWManager uowManager) { +// this(); +// this.uowManager = uowManager; +// } +// +// +// /** +// * Set the WebSphere UOWManager to use as direct reference. +// *

Typically just used for test setups; in a J2EE environment, +// * the UOWManager will always be fetched from JNDI. +// * @see #setUserTransactionName +// */ +// public void setUowManager(UOWManager uowManager) { +// this.uowManager = uowManager; +// } +// +// /** +// * Set the JNDI name of the WebSphere UOWManager. +// * The default "java:comp/websphere/UOWManager" is used if not set. +// * @see #DEFAULT_USER_TRANSACTION_NAME +// * @see #setUowManager +// */ +// public void setUowManagerName(String uowManagerName) { +// this.uowManagerName = uowManagerName; +// } +// +// +// @Override +// public void afterPropertiesSet() throws TransactionSystemException { +// initUserTransactionAndTransactionManager(); +// +// // Fetch UOWManager handle from JNDI, if necessary. +// if (this.uowManager == null) { +// if (this.uowManagerName != null) { +// this.uowManager = lookupUowManager(this.uowManagerName); +// } +// else { +// this.uowManager = lookupDefaultUowManager(); +// } +// } +// } +// +// /** +// * Look up the WebSphere UOWManager in JNDI via the configured name. +// * @param uowManagerName the JNDI name of the UOWManager +// * @return the UOWManager object +// * @throws TransactionSystemException if the JNDI lookup failed +// * @see #setJndiTemplate +// * @see #setUowManagerName +// */ +// protected UOWManager lookupUowManager(String uowManagerName) throws TransactionSystemException { +// try { +// if (logger.isDebugEnabled()) { +// logger.debug("Retrieving WebSphere UOWManager from JNDI location [" + uowManagerName + "]"); +// } +// return getJndiTemplate().lookup(uowManagerName, UOWManager.class); +// } +// catch (NamingException ex) { +// throw new TransactionSystemException( +// "WebSphere UOWManager is not available at JNDI location [" + uowManagerName + "]", ex); +// } +// } +// +// /** +// * Obtain the WebSphere UOWManager from the default JNDI location +// * "java:comp/websphere/UOWManager". +// * @return the UOWManager object +// * @throws TransactionSystemException if the JNDI lookup failed +// * @see #setJndiTemplate +// */ +// protected UOWManager lookupDefaultUowManager() throws TransactionSystemException { +// try { +// logger.debug("Retrieving WebSphere UOWManager from default JNDI location [" + DEFAULT_UOW_MANAGER_NAME + "]"); +// return getJndiTemplate().lookup(DEFAULT_UOW_MANAGER_NAME, UOWManager.class); +// } +// catch (NamingException ex) { +// logger.debug("WebSphere UOWManager is not available at default JNDI location [" + +// DEFAULT_UOW_MANAGER_NAME + "] - falling back to UOWManagerFactory lookup"); +// return UOWManagerFactory.getUOWManager(); +// } +// } +// +// /** +// * Registers the synchronizations as interposed JTA Synchronization on the UOWManager. +// */ +// @Override +// protected void doRegisterAfterCompletionWithJtaTransaction( +// JtaTransactionObject txObject, List synchronizations) { +// +// this.uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); +// } +// +// /** +// * Returns {@code true} since WebSphere ResourceAdapters (as exposed in JNDI) +// * implicitly perform transaction enlistment if the MessageEndpointFactory's +// * {@code isDeliveryTransacted} method returns {@code true}. +// * In that case we'll simply skip the {@link #createTransaction} call. +// * @see javax.resource.spi.endpoint.MessageEndpointFactory#isDeliveryTransacted +// * @see com.fr.third.springframework.jca.endpoint.AbstractMessageEndpointFactory +// * @see TransactionFactory#createTransaction +// */ +// @Override +// public boolean supportsResourceAdapterManagedTransactions() { +// return true; +// } +// +// +// @Override +// public T execute(TransactionDefinition definition, TransactionCallback callback) throws TransactionException { +// if (definition == null) { +// // Use defaults if no transaction definition given. +// definition = new DefaultTransactionDefinition(); +// } +// +// if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { +// throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); +// } +// int pb = definition.getPropagationBehavior(); +// boolean existingTx = (this.uowManager.getUOWStatus() != UOWSynchronizationRegistry.UOW_STATUS_NONE && +// this.uowManager.getUOWType() != UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION); +// +// int uowType = UOWSynchronizationRegistry.UOW_TYPE_GLOBAL_TRANSACTION; +// boolean joinTx = false; +// boolean newSynch = false; +// +// if (existingTx) { +// if (pb == TransactionDefinition.PROPAGATION_NEVER) { +// throw new IllegalTransactionStateException( +// "Transaction propagation 'never' but existing transaction found"); +// } +// if (pb == TransactionDefinition.PROPAGATION_NESTED) { +// throw new NestedTransactionNotSupportedException( +// "Transaction propagation 'nested' not supported for WebSphere UOW transactions"); +// } +// if (pb == TransactionDefinition.PROPAGATION_SUPPORTS || +// pb == TransactionDefinition.PROPAGATION_REQUIRED || pb == TransactionDefinition.PROPAGATION_MANDATORY) { +// joinTx = true; +// newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); +// } +// else if (pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { +// uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION; +// newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); +// } +// else { +// newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); +// } +// } +// else { +// if (pb == TransactionDefinition.PROPAGATION_MANDATORY) { +// throw new IllegalTransactionStateException( +// "Transaction propagation 'mandatory' but no existing transaction found"); +// } +// if (pb == TransactionDefinition.PROPAGATION_SUPPORTS || +// pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED || pb == TransactionDefinition.PROPAGATION_NEVER) { +// uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION; +// newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); +// } +// else { +// newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); +// } +// } +// +// boolean debug = logger.isDebugEnabled(); +// if (debug) { +// logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); +// } +// SuspendedResourcesHolder suspendedResources = (!joinTx ? suspend(null) : null); +// try { +// if (definition.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) { +// this.uowManager.setUOWTimeout(uowType, definition.getTimeout()); +// } +// if (debug) { +// logger.debug("Invoking WebSphere UOW action: type=" + uowType + ", join=" + joinTx); +// } +// UOWActionAdapter action = new UOWActionAdapter( +// definition, callback, (uowType == UOWManager.UOW_TYPE_GLOBAL_TRANSACTION), !joinTx, newSynch, debug); +// this.uowManager.runUnderUOW(uowType, joinTx, action); +// if (debug) { +// logger.debug("Returned from WebSphere UOW action: type=" + uowType + ", join=" + joinTx); +// } +// return action.getResult(); +// } +// catch (UOWException ex) { +// throw new TransactionSystemException("UOWManager transaction processing failed", ex); +// } +// catch (UOWActionException ex) { +// throw new TransactionSystemException("UOWManager threw unexpected UOWActionException", ex); +// } +// finally { +// if (suspendedResources != null) { +// resume(null, suspendedResources); +// } +// } +// } +// +// +// /** +// * Adapter that executes the given Spring transaction within the WebSphere UOWAction shape. +// */ +// private class UOWActionAdapter implements UOWAction, SmartTransactionObject { +// +// private final TransactionDefinition definition; +// +// private final TransactionCallback callback; +// +// private final boolean actualTransaction; +// +// private final boolean newTransaction; +// +// private final boolean newSynchronization; +// +// private boolean debug; +// +// private T result; +// +// private Throwable exception; +// +// public UOWActionAdapter(TransactionDefinition definition, TransactionCallback callback, +// boolean actualTransaction, boolean newTransaction, boolean newSynchronization, boolean debug) { +// this.definition = definition; +// this.callback = callback; +// this.actualTransaction = actualTransaction; +// this.newTransaction = newTransaction; +// this.newSynchronization = newSynchronization; +// this.debug = debug; +// } +// +// @Override +// public void run() { +// DefaultTransactionStatus status = prepareTransactionStatus( +// this.definition, (this.actualTransaction ? this : null), +// this.newTransaction, this.newSynchronization, this.debug, null); +// try { +// this.result = this.callback.doInTransaction(status); +// triggerBeforeCommit(status); +// } +// catch (Throwable ex) { +// this.exception = ex; +// uowManager.setRollbackOnly(); +// } +// finally { +// if (status.isLocalRollbackOnly()) { +// if (status.isDebug()) { +// logger.debug("Transactional code has requested rollback"); +// } +// uowManager.setRollbackOnly(); +// } +// triggerBeforeCompletion(status); +// if (status.isNewSynchronization()) { +// List synchronizations = TransactionSynchronizationManager.getSynchronizations(); +// TransactionSynchronizationManager.clear(); +// if (!synchronizations.isEmpty()) { +// uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); +// } +// } +// } +// } +// +// public T getResult() { +// if (this.exception != null) { +// ReflectionUtils.rethrowRuntimeException(this.exception); +// } +// return this.result; +// } +// +// @Override +// public boolean isRollbackOnly() { +// return uowManager.getRollbackOnly(); +// } +// +// @Override +// public void flush() { +// TransactionSynchronizationUtils.triggerFlush(); +// } +// } +// +//} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/jta/package-info.java b/fine-spring/src/com/fr/third/springframework/transaction/jta/package-info.java new file mode 100644 index 000000000..69663c340 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/jta/package-info.java @@ -0,0 +1,8 @@ + +/** + * + * Transaction SPI implementation for JTA. + * + */ +package com.fr.third.springframework.transaction.jta; + diff --git a/fine-spring/src/com/fr/third/springframework/transaction/package-info.java b/fine-spring/src/com/fr/third/springframework/transaction/package-info.java new file mode 100644 index 000000000..52cc6887e --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/package-info.java @@ -0,0 +1,10 @@ + +/** + * + * Exception hierarchy for Spring's transaction infrastructure, + * independent of any specific transaction management system. + * Contains transaction manager, definition, and status interfaces. + * + */ +package com.fr.third.springframework.transaction; + diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/AbstractPlatformTransactionManager.java b/fine-spring/src/com/fr/third/springframework/transaction/support/AbstractPlatformTransactionManager.java new file mode 100644 index 000000000..83ce5812c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/AbstractPlatformTransactionManager.java @@ -0,0 +1,1310 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.core.Constants; +import com.fr.third.springframework.transaction.IllegalTransactionStateException; +import com.fr.third.springframework.transaction.InvalidTimeoutException; +import com.fr.third.springframework.transaction.NestedTransactionNotSupportedException; +import com.fr.third.springframework.transaction.PlatformTransactionManager; +import com.fr.third.springframework.transaction.TransactionDefinition; +import com.fr.third.springframework.transaction.TransactionException; +import com.fr.third.springframework.transaction.TransactionStatus; +import com.fr.third.springframework.transaction.TransactionSuspensionNotSupportedException; +import com.fr.third.springframework.transaction.UnexpectedRollbackException; + +/** + * Abstract base class that implements Spring's standard transaction workflow, + * serving as basis for concrete platform transaction managers like + * {@link com.fr.third.springframework.transaction.jta.JtaTransactionManager}. + * + *

This base class provides the following workflow handling: + *

    + *
  • determines if there is an existing transaction; + *
  • applies the appropriate propagation behavior; + *
  • suspends and resumes transactions if necessary; + *
  • checks the rollback-only flag on commit; + *
  • applies the appropriate modification on rollback + * (actual rollback or setting rollback-only); + *
  • triggers registered synchronization callbacks + * (if transaction synchronization is active). + *
+ * + *

Subclasses have to implement specific template methods for specific + * states of a transaction, e.g.: begin, suspend, resume, commit, rollback. + * The most important of them are abstract and must be provided by a concrete + * implementation; for the rest, defaults are provided, so overriding is optional. + * + *

Transaction synchronization is a generic mechanism for registering callbacks + * that get invoked at transaction completion time. This is mainly used internally + * by the data access support classes for JDBC, Hibernate, JPA, etc when running + * within a JTA transaction: They register resources that are opened within the + * transaction for closing at transaction completion time, allowing e.g. for reuse + * of the same Hibernate Session within the transaction. The same mechanism can + * also be leveraged for custom synchronization needs in an application. + * + *

The state of this class is serializable, to allow for serializing the + * transaction strategy along with proxies that carry a transaction interceptor. + * It is up to subclasses if they wish to make their state to be serializable too. + * They should implement the {@code java.io.Serializable} marker interface in + * that case, and potentially a private {@code readObject()} method (according + * to Java serialization rules) if they need to restore any transient state. + * + * @author Juergen Hoeller + * @since 28.03.2003 + * @see #setTransactionSynchronization + * @see TransactionSynchronizationManager + * @see com.fr.third.springframework.transaction.jta.JtaTransactionManager + */ +@SuppressWarnings("serial") +public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { + + /** + * Always activate transaction synchronization, even for "empty" transactions + * that result from PROPAGATION_SUPPORTS with no existing backend transaction. + * @see com.fr.third.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS + * @see com.fr.third.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED + * @see com.fr.third.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER + */ + public static final int SYNCHRONIZATION_ALWAYS = 0; + + /** + * Activate transaction synchronization only for actual transactions, + * that is, not for empty ones that result from PROPAGATION_SUPPORTS with + * no existing backend transaction. + * @see com.fr.third.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED + * @see com.fr.third.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY + * @see com.fr.third.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW + */ + public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1; + + /** + * Never active transaction synchronization, not even for actual transactions. + */ + public static final int SYNCHRONIZATION_NEVER = 2; + + + /** Constants instance for AbstractPlatformTransactionManager */ + private static final Constants constants = new Constants(AbstractPlatformTransactionManager.class); + + + protected transient Log logger = LogFactory.getLog(getClass()); + + private int transactionSynchronization = SYNCHRONIZATION_ALWAYS; + + private int defaultTimeout = TransactionDefinition.TIMEOUT_DEFAULT; + + private boolean nestedTransactionAllowed = false; + + private boolean validateExistingTransaction = false; + + private boolean globalRollbackOnParticipationFailure = true; + + private boolean failEarlyOnGlobalRollbackOnly = false; + + private boolean rollbackOnCommitFailure = false; + + + /** + * Set the transaction synchronization by the name of the corresponding constant + * in this class, e.g. "SYNCHRONIZATION_ALWAYS". + * @param constantName name of the constant + * @see #SYNCHRONIZATION_ALWAYS + */ + public final void setTransactionSynchronizationName(String constantName) { + setTransactionSynchronization(constants.asNumber(constantName).intValue()); + } + + /** + * Set when this transaction manager should activate the thread-bound + * transaction synchronization support. Default is "always". + *

Note that transaction synchronization isn't supported for + * multiple concurrent transactions by different transaction managers. + * Only one transaction manager is allowed to activate it at any time. + * @see #SYNCHRONIZATION_ALWAYS + * @see #SYNCHRONIZATION_ON_ACTUAL_TRANSACTION + * @see #SYNCHRONIZATION_NEVER + * @see TransactionSynchronizationManager + * @see TransactionSynchronization + */ + public final void setTransactionSynchronization(int transactionSynchronization) { + this.transactionSynchronization = transactionSynchronization; + } + + /** + * Return if this transaction manager should activate the thread-bound + * transaction synchronization support. + */ + public final int getTransactionSynchronization() { + return this.transactionSynchronization; + } + + /** + * Specify the default timeout that this transaction manager should apply + * if there is no timeout specified at the transaction level, in seconds. + *

Default is the underlying transaction infrastructure's default timeout, + * e.g. typically 30 seconds in case of a JTA provider, indicated by the + * {@code TransactionDefinition.TIMEOUT_DEFAULT} value. + * @see com.fr.third.springframework.transaction.TransactionDefinition#TIMEOUT_DEFAULT + */ + public final void setDefaultTimeout(int defaultTimeout) { + if (defaultTimeout < TransactionDefinition.TIMEOUT_DEFAULT) { + throw new InvalidTimeoutException("Invalid default timeout", defaultTimeout); + } + this.defaultTimeout = defaultTimeout; + } + + /** + * Return the default timeout that this transaction manager should apply + * if there is no timeout specified at the transaction level, in seconds. + *

Returns {@code TransactionDefinition.TIMEOUT_DEFAULT} to indicate + * the underlying transaction infrastructure's default timeout. + */ + public final int getDefaultTimeout() { + return this.defaultTimeout; + } + + /** + * Set whether nested transactions are allowed. Default is "false". + *

Typically initialized with an appropriate default by the + * concrete transaction manager subclass. + */ + public final void setNestedTransactionAllowed(boolean nestedTransactionAllowed) { + this.nestedTransactionAllowed = nestedTransactionAllowed; + } + + /** + * Return whether nested transactions are allowed. + */ + public final boolean isNestedTransactionAllowed() { + return this.nestedTransactionAllowed; + } + + /** + * Set whether existing transactions should be validated before participating + * in them. + *

When participating in an existing transaction (e.g. with + * PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing + * transaction), this outer transaction's characteristics will apply even + * to the inner transaction scope. Validation will detect incompatible + * isolation level and read-only settings on the inner transaction definition + * and reject participation accordingly through throwing a corresponding exception. + *

Default is "false", leniently ignoring inner transaction settings, + * simply overriding them with the outer transaction's characteristics. + * Switch this flag to "true" in order to enforce strict validation. + */ + public final void setValidateExistingTransaction(boolean validateExistingTransaction) { + this.validateExistingTransaction = validateExistingTransaction; + } + + /** + * Return whether existing transactions should be validated before participating + * in them. + */ + public final boolean isValidateExistingTransaction() { + return this.validateExistingTransaction; + } + + /** + * Set whether to globally mark an existing transaction as rollback-only + * after a participating transaction failed. + *

Default is "true": If a participating transaction (e.g. with + * PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing + * transaction) fails, the transaction will be globally marked as rollback-only. + * The only possible outcome of such a transaction is a rollback: The + * transaction originator cannot make the transaction commit anymore. + *

Switch this to "false" to let the transaction originator make the rollback + * decision. If a participating transaction fails with an exception, the caller + * can still decide to continue with a different path within the transaction. + * However, note that this will only work as long as all participating resources + * are capable of continuing towards a transaction commit even after a data access + * failure: This is generally not the case for a Hibernate Session, for example; + * neither is it for a sequence of JDBC insert/update/delete operations. + *

Note:This flag only applies to an explicit rollback attempt for a + * subtransaction, typically caused by an exception thrown by a data access operation + * (where TransactionInterceptor will trigger a {@code PlatformTransactionManager.rollback()} + * call according to a rollback rule). If the flag is off, the caller can handle the exception + * and decide on a rollback, independent of the rollback rules of the subtransaction. + * This flag does, however, not apply to explicit {@code setRollbackOnly} + * calls on a {@code TransactionStatus}, which will always cause an eventual + * global rollback (as it might not throw an exception after the rollback-only call). + *

The recommended solution for handling failure of a subtransaction + * is a "nested transaction", where the global transaction can be rolled + * back to a savepoint taken at the beginning of the subtransaction. + * PROPAGATION_NESTED provides exactly those semantics; however, it will + * only work when nested transaction support is available. This is the case + * with DataSourceTransactionManager, but not with JtaTransactionManager. + * @see #setNestedTransactionAllowed + * @see com.fr.third.springframework.transaction.jta.JtaTransactionManager + */ + public final void setGlobalRollbackOnParticipationFailure(boolean globalRollbackOnParticipationFailure) { + this.globalRollbackOnParticipationFailure = globalRollbackOnParticipationFailure; + } + + /** + * Return whether to globally mark an existing transaction as rollback-only + * after a participating transaction failed. + */ + public final boolean isGlobalRollbackOnParticipationFailure() { + return this.globalRollbackOnParticipationFailure; + } + + /** + * Set whether to fail early in case of the transaction being globally marked + * as rollback-only. + *

Default is "false", only causing an UnexpectedRollbackException at the + * outermost transaction boundary. Switch this flag on to cause an + * UnexpectedRollbackException as early as the global rollback-only marker + * has been first detected, even from within an inner transaction boundary. + *

Note that, as of Spring 2.0, the fail-early behavior for global + * rollback-only markers has been unified: All transaction managers will by + * default only cause UnexpectedRollbackException at the outermost transaction + * boundary. This allows, for example, to continue unit tests even after an + * operation failed and the transaction will never be completed. All transaction + * managers will only fail earlier if this flag has explicitly been set to "true". + * @see com.fr.third.springframework.transaction.UnexpectedRollbackException + */ + public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRollbackOnly) { + this.failEarlyOnGlobalRollbackOnly = failEarlyOnGlobalRollbackOnly; + } + + /** + * Return whether to fail early in case of the transaction being globally marked + * as rollback-only. + */ + public final boolean isFailEarlyOnGlobalRollbackOnly() { + return this.failEarlyOnGlobalRollbackOnly; + } + + /** + * Set whether {@code doRollback} should be performed on failure of the + * {@code doCommit} call. Typically not necessary and thus to be avoided, + * as it can potentially override the commit exception with a subsequent + * rollback exception. + *

Default is "false". + * @see #doCommit + * @see #doRollback + */ + public final void setRollbackOnCommitFailure(boolean rollbackOnCommitFailure) { + this.rollbackOnCommitFailure = rollbackOnCommitFailure; + } + + /** + * Return whether {@code doRollback} should be performed on failure of the + * {@code doCommit} call. + */ + public final boolean isRollbackOnCommitFailure() { + return this.rollbackOnCommitFailure; + } + + + //--------------------------------------------------------------------- + // Implementation of PlatformTransactionManager + //--------------------------------------------------------------------- + + /** + * This implementation handles propagation behavior. Delegates to + * {@code doGetTransaction}, {@code isExistingTransaction} + * and {@code doBegin}. + * @see #doGetTransaction + * @see #isExistingTransaction + * @see #doBegin + */ + @Override + public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { + Object transaction = doGetTransaction(); + + // Cache debug flag to avoid repeated checks. + boolean debugEnabled = logger.isDebugEnabled(); + + if (definition == null) { + // Use defaults if no transaction definition given. + definition = new DefaultTransactionDefinition(); + } + + if (isExistingTransaction(transaction)) { + // Existing transaction found -> check propagation behavior to find out how to behave. + return handleExistingTransaction(definition, transaction, debugEnabled); + } + + // Check definition settings for new transaction. + if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { + throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); + } + + // No existing transaction found -> check propagation behavior to find out how to proceed. + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { + throw new IllegalTransactionStateException( + "No existing transaction found for transaction marked with propagation 'mandatory'"); + } + else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || + definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || + definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + SuspendedResourcesHolder suspendedResources = suspend(null); + if (debugEnabled) { + logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); + } + try { + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + DefaultTransactionStatus status = newTransactionStatus( + definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); + doBegin(transaction, definition); + prepareSynchronization(status, definition); + return status; + } + catch (RuntimeException ex) { + resume(null, suspendedResources); + throw ex; + } + catch (Error err) { + resume(null, suspendedResources); + throw err; + } + } + else { + // Create "empty" transaction: no actual transaction, but potentially synchronization. + boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); + } + } + + /** + * Create a TransactionStatus for an existing transaction. + */ + private TransactionStatus handleExistingTransaction( + TransactionDefinition definition, Object transaction, boolean debugEnabled) + throws TransactionException { + + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { + throw new IllegalTransactionStateException( + "Existing transaction found for transaction marked with propagation 'never'"); + } + + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { + if (debugEnabled) { + logger.debug("Suspending current transaction"); + } + Object suspendedResources = suspend(transaction); + boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + return prepareTransactionStatus( + definition, null, false, newSynchronization, debugEnabled, suspendedResources); + } + + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { + if (debugEnabled) { + logger.debug("Suspending current transaction, creating new transaction with name [" + + definition.getName() + "]"); + } + SuspendedResourcesHolder suspendedResources = suspend(transaction); + try { + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + DefaultTransactionStatus status = newTransactionStatus( + definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); + doBegin(transaction, definition); + prepareSynchronization(status, definition); + return status; + } + catch (RuntimeException beginEx) { + resumeAfterBeginException(transaction, suspendedResources, beginEx); + throw beginEx; + } + catch (Error beginErr) { + resumeAfterBeginException(transaction, suspendedResources, beginErr); + throw beginErr; + } + } + + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + if (!isNestedTransactionAllowed()) { + throw new NestedTransactionNotSupportedException( + "Transaction manager does not allow nested transactions by default - " + + "specify 'nestedTransactionAllowed' property with value 'true'"); + } + if (debugEnabled) { + logger.debug("Creating nested transaction with name [" + definition.getName() + "]"); + } + if (useSavepointForNestedTransaction()) { + // Create savepoint within existing Spring-managed transaction, + // through the SavepointManager API implemented by TransactionStatus. + // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization. + DefaultTransactionStatus status = + prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); + status.createAndHoldSavepoint(); + return status; + } + else { + // Nested transaction through nested begin and commit/rollback calls. + // Usually only for JTA: Spring synchronization might get activated here + // in case of a pre-existing JTA transaction. + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + DefaultTransactionStatus status = newTransactionStatus( + definition, transaction, true, newSynchronization, debugEnabled, null); + doBegin(transaction, definition); + prepareSynchronization(status, definition); + return status; + } + } + + // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED. + if (debugEnabled) { + logger.debug("Participating in existing transaction"); + } + if (isValidateExistingTransaction()) { + if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { + Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); + if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) { + Constants isoConstants = DefaultTransactionDefinition.constants; + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] specifies isolation level which is incompatible with existing transaction: " + + (currentIsolationLevel != null ? + isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : + "(unknown)")); + } + } + if (!definition.isReadOnly()) { + if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] is not marked as read-only but existing transaction is"); + } + } + } + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); + } + + /** + * Create a new TransactionStatus for the given arguments, + * also initializing transaction synchronization as appropriate. + * @see #newTransactionStatus + * @see #prepareTransactionStatus + */ + protected final DefaultTransactionStatus prepareTransactionStatus( + TransactionDefinition definition, Object transaction, boolean newTransaction, + boolean newSynchronization, boolean debug, Object suspendedResources) { + + DefaultTransactionStatus status = newTransactionStatus( + definition, transaction, newTransaction, newSynchronization, debug, suspendedResources); + prepareSynchronization(status, definition); + return status; + } + + /** + * Create a rae TransactionStatus instance for the given arguments. + */ + protected DefaultTransactionStatus newTransactionStatus( + TransactionDefinition definition, Object transaction, boolean newTransaction, + boolean newSynchronization, boolean debug, Object suspendedResources) { + + boolean actualNewSynchronization = newSynchronization && + !TransactionSynchronizationManager.isSynchronizationActive(); + return new DefaultTransactionStatus( + transaction, newTransaction, actualNewSynchronization, + definition.isReadOnly(), debug, suspendedResources); + } + + /** + * Initialize transaction synchronization as appropriate. + */ + protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) { + if (status.isNewSynchronization()) { + TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction()); + TransactionSynchronizationManager.setCurrentTransactionIsolationLevel( + definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ? + definition.getIsolationLevel() : null); + TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly()); + TransactionSynchronizationManager.setCurrentTransactionName(definition.getName()); + TransactionSynchronizationManager.initSynchronization(); + } + } + + /** + * Determine the actual timeout to use for the given definition. + * Will fall back to this manager's default timeout if the + * transaction definition doesn't specify a non-default value. + * @param definition the transaction definition + * @return the actual timeout to use + * @see com.fr.third.springframework.transaction.TransactionDefinition#getTimeout() + * @see #setDefaultTimeout + */ + protected int determineTimeout(TransactionDefinition definition) { + if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) { + return definition.getTimeout(); + } + return this.defaultTimeout; + } + + + /** + * Suspend the given transaction. Suspends transaction synchronization first, + * then delegates to the {@code doSuspend} template method. + * @param transaction the current transaction object + * (or {@code null} to just suspend active synchronizations, if any) + * @return an object that holds suspended resources + * (or {@code null} if neither transaction nor synchronization active) + * @see #doSuspend + * @see #resume + */ + protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + List suspendedSynchronizations = doSuspendSynchronization(); + try { + Object suspendedResources = null; + if (transaction != null) { + suspendedResources = doSuspend(transaction); + } + String name = TransactionSynchronizationManager.getCurrentTransactionName(); + TransactionSynchronizationManager.setCurrentTransactionName(null); + boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); + TransactionSynchronizationManager.setCurrentTransactionReadOnly(false); + Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); + TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null); + boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive(); + TransactionSynchronizationManager.setActualTransactionActive(false); + return new SuspendedResourcesHolder( + suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive); + } + catch (RuntimeException ex) { + // doSuspend failed - original transaction is still active... + doResumeSynchronization(suspendedSynchronizations); + throw ex; + } + catch (Error err) { + // doSuspend failed - original transaction is still active... + doResumeSynchronization(suspendedSynchronizations); + throw err; + } + } + else if (transaction != null) { + // Transaction active but no synchronization active. + Object suspendedResources = doSuspend(transaction); + return new SuspendedResourcesHolder(suspendedResources); + } + else { + // Neither transaction nor synchronization active. + return null; + } + } + + /** + * Resume the given transaction. Delegates to the {@code doResume} + * template method first, then resuming transaction synchronization. + * @param transaction the current transaction object + * @param resourcesHolder the object that holds suspended resources, + * as returned by {@code suspend} (or {@code null} to just + * resume synchronizations, if any) + * @see #doResume + * @see #suspend + */ + protected final void resume(Object transaction, SuspendedResourcesHolder resourcesHolder) + throws TransactionException { + + if (resourcesHolder != null) { + Object suspendedResources = resourcesHolder.suspendedResources; + if (suspendedResources != null) { + doResume(transaction, suspendedResources); + } + List suspendedSynchronizations = resourcesHolder.suspendedSynchronizations; + if (suspendedSynchronizations != null) { + TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive); + TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel); + TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly); + TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name); + doResumeSynchronization(suspendedSynchronizations); + } + } + } + + /** + * Resume outer transaction after inner transaction begin failed. + */ + private void resumeAfterBeginException( + Object transaction, SuspendedResourcesHolder suspendedResources, Throwable beginEx) { + + String exMessage = "Inner transaction begin exception overridden by outer transaction resume exception"; + try { + resume(transaction, suspendedResources); + } + catch (RuntimeException resumeEx) { + logger.error(exMessage, beginEx); + throw resumeEx; + } + catch (Error resumeErr) { + logger.error(exMessage, beginEx); + throw resumeErr; + } + } + + /** + * Suspend all current synchronizations and deactivate transaction + * synchronization for the current thread. + * @return the List of suspended TransactionSynchronization objects + */ + private List doSuspendSynchronization() { + List suspendedSynchronizations = + TransactionSynchronizationManager.getSynchronizations(); + for (TransactionSynchronization synchronization : suspendedSynchronizations) { + synchronization.suspend(); + } + TransactionSynchronizationManager.clearSynchronization(); + return suspendedSynchronizations; + } + + /** + * Reactivate transaction synchronization for the current thread + * and resume all given synchronizations. + * @param suspendedSynchronizations List of TransactionSynchronization objects + */ + private void doResumeSynchronization(List suspendedSynchronizations) { + TransactionSynchronizationManager.initSynchronization(); + for (TransactionSynchronization synchronization : suspendedSynchronizations) { + synchronization.resume(); + TransactionSynchronizationManager.registerSynchronization(synchronization); + } + } + + + /** + * This implementation of commit handles participating in existing + * transactions and programmatic rollback requests. + * Delegates to {@code isRollbackOnly}, {@code doCommit} + * and {@code rollback}. + * @see com.fr.third.springframework.transaction.TransactionStatus#isRollbackOnly() + * @see #doCommit + * @see #rollback + */ + @Override + public final void commit(TransactionStatus status) throws TransactionException { + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + if (defStatus.isLocalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug("Transactional code has requested rollback"); + } + processRollback(defStatus); + return; + } + if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); + } + processRollback(defStatus); + // Throw UnexpectedRollbackException only at outermost transaction boundary + // or if explicitly asked to. + if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { + throw new UnexpectedRollbackException( + "Transaction rolled back because it has been marked as rollback-only"); + } + return; + } + + processCommit(defStatus); + } + + /** + * Process an actual commit. + * Rollback-only flags have already been checked and applied. + * @param status object representing the transaction + * @throws TransactionException in case of commit failure + */ + private void processCommit(DefaultTransactionStatus status) throws TransactionException { + try { + boolean beforeCompletionInvoked = false; + try { + prepareForCommit(status); + triggerBeforeCommit(status); + triggerBeforeCompletion(status); + beforeCompletionInvoked = true; + boolean globalRollbackOnly = false; + if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { + globalRollbackOnly = status.isGlobalRollbackOnly(); + } + if (status.hasSavepoint()) { + if (status.isDebug()) { + logger.debug("Releasing transaction savepoint"); + } + status.releaseHeldSavepoint(); + } + else if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction commit"); + } + doCommit(status); + } + // Throw UnexpectedRollbackException if we have a global rollback-only + // marker but still didn't get a corresponding exception from commit. + if (globalRollbackOnly) { + throw new UnexpectedRollbackException( + "Transaction silently rolled back because it has been marked as rollback-only"); + } + } + catch (UnexpectedRollbackException ex) { + // can only be caused by doCommit + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + throw ex; + } + catch (TransactionException ex) { + // can only be caused by doCommit + if (isRollbackOnCommitFailure()) { + doRollbackOnCommitException(status, ex); + } + else { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + } + throw ex; + } + catch (RuntimeException ex) { + if (!beforeCompletionInvoked) { + triggerBeforeCompletion(status); + } + doRollbackOnCommitException(status, ex); + throw ex; + } + catch (Error err) { + if (!beforeCompletionInvoked) { + triggerBeforeCompletion(status); + } + doRollbackOnCommitException(status, err); + throw err; + } + + // Trigger afterCommit callbacks, with an exception thrown there + // propagated to callers but the transaction still considered as committed. + try { + triggerAfterCommit(status); + } + finally { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); + } + + } + finally { + cleanupAfterCompletion(status); + } + } + + /** + * This implementation of rollback handles participating in existing + * transactions. Delegates to {@code doRollback} and + * {@code doSetRollbackOnly}. + * @see #doRollback + * @see #doSetRollbackOnly + */ + @Override + public final void rollback(TransactionStatus status) throws TransactionException { + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + processRollback(defStatus); + } + + /** + * Process an actual rollback. + * The completed flag has already been checked. + * @param status object representing the transaction + * @throws TransactionException in case of rollback failure + */ + private void processRollback(DefaultTransactionStatus status) { + try { + try { + triggerBeforeCompletion(status); + if (status.hasSavepoint()) { + if (status.isDebug()) { + logger.debug("Rolling back transaction to savepoint"); + } + status.rollbackToHeldSavepoint(); + } + else if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction rollback"); + } + doRollback(status); + } + else if (status.hasTransaction()) { + if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { + if (status.isDebug()) { + logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); + } + doSetRollbackOnly(status); + } + else { + if (status.isDebug()) { + logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); + } + } + } + else { + logger.debug("Should roll back transaction but cannot - no transaction available"); + } + } + catch (RuntimeException ex) { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw ex; + } + catch (Error err) { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw err; + } + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + } + finally { + cleanupAfterCompletion(status); + } + } + + /** + * Invoke {@code doRollback}, handling rollback exceptions properly. + * @param status object representing the transaction + * @param ex the thrown application exception or error + * @throws TransactionException in case of rollback failure + * @see #doRollback + */ + private void doRollbackOnCommitException(DefaultTransactionStatus status, Throwable ex) throws TransactionException { + try { + if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction rollback after commit exception", ex); + } + doRollback(status); + } + else if (status.hasTransaction() && isGlobalRollbackOnParticipationFailure()) { + if (status.isDebug()) { + logger.debug("Marking existing transaction as rollback-only after commit exception", ex); + } + doSetRollbackOnly(status); + } + } + catch (RuntimeException rbex) { + logger.error("Commit exception overridden by rollback exception", ex); + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw rbex; + } + catch (Error rberr) { + logger.error("Commit exception overridden by rollback exception", ex); + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw rberr; + } + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + } + + + /** + * Trigger {@code beforeCommit} callbacks. + * @param status object representing the transaction + */ + protected final void triggerBeforeCommit(DefaultTransactionStatus status) { + if (status.isNewSynchronization()) { + if (status.isDebug()) { + logger.trace("Triggering beforeCommit synchronization"); + } + TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly()); + } + } + + /** + * Trigger {@code beforeCompletion} callbacks. + * @param status object representing the transaction + */ + protected final void triggerBeforeCompletion(DefaultTransactionStatus status) { + if (status.isNewSynchronization()) { + if (status.isDebug()) { + logger.trace("Triggering beforeCompletion synchronization"); + } + TransactionSynchronizationUtils.triggerBeforeCompletion(); + } + } + + /** + * Trigger {@code afterCommit} callbacks. + * @param status object representing the transaction + */ + private void triggerAfterCommit(DefaultTransactionStatus status) { + if (status.isNewSynchronization()) { + if (status.isDebug()) { + logger.trace("Triggering afterCommit synchronization"); + } + TransactionSynchronizationUtils.triggerAfterCommit(); + } + } + + /** + * Trigger {@code afterCompletion} callbacks. + * @param status object representing the transaction + * @param completionStatus completion status according to TransactionSynchronization constants + */ + private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) { + if (status.isNewSynchronization()) { + List synchronizations = TransactionSynchronizationManager.getSynchronizations(); + if (!status.hasTransaction() || status.isNewTransaction()) { + if (status.isDebug()) { + logger.trace("Triggering afterCompletion synchronization"); + } + // No transaction or new transaction for the current scope -> + // invoke the afterCompletion callbacks immediately + invokeAfterCompletion(synchronizations, completionStatus); + } + else if (!synchronizations.isEmpty()) { + // Existing transaction that we participate in, controlled outside + // of the scope of this Spring transaction manager -> try to register + // an afterCompletion callback with the existing (JTA) transaction. + registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations); + } + } + } + + /** + * Actually invoke the {@code afterCompletion} methods of the + * given Spring TransactionSynchronization objects. + *

To be called by this abstract manager itself, or by special implementations + * of the {@code registerAfterCompletionWithExistingTransaction} callback. + * @param synchronizations List of TransactionSynchronization objects + * @param completionStatus the completion status according to the + * constants in the TransactionSynchronization interface + * @see #registerAfterCompletionWithExistingTransaction(Object, java.util.List) + * @see TransactionSynchronization#STATUS_COMMITTED + * @see TransactionSynchronization#STATUS_ROLLED_BACK + * @see TransactionSynchronization#STATUS_UNKNOWN + */ + protected final void invokeAfterCompletion(List synchronizations, int completionStatus) { + TransactionSynchronizationUtils.invokeAfterCompletion(synchronizations, completionStatus); + } + + /** + * Clean up after completion, clearing synchronization if necessary, + * and invoking doCleanupAfterCompletion. + * @param status object representing the transaction + * @see #doCleanupAfterCompletion + */ + private void cleanupAfterCompletion(DefaultTransactionStatus status) { + status.setCompleted(); + if (status.isNewSynchronization()) { + TransactionSynchronizationManager.clear(); + } + if (status.isNewTransaction()) { + doCleanupAfterCompletion(status.getTransaction()); + } + if (status.getSuspendedResources() != null) { + if (status.isDebug()) { + logger.debug("Resuming suspended transaction after completion of inner transaction"); + } + resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources()); + } + } + + + //--------------------------------------------------------------------- + // Template methods to be implemented in subclasses + //--------------------------------------------------------------------- + + /** + * Return a transaction object for the current transaction state. + *

The returned object will usually be specific to the concrete transaction + * manager implementation, carrying corresponding transaction state in a + * modifiable fashion. This object will be passed into the other template + * methods (e.g. doBegin and doCommit), either directly or as part of a + * DefaultTransactionStatus instance. + *

The returned object should contain information about any existing + * transaction, that is, a transaction that has already started before the + * current {@code getTransaction} call on the transaction manager. + * Consequently, a {@code doGetTransaction} implementation will usually + * look for an existing transaction and store corresponding state in the + * returned transaction object. + * @return the current transaction object + * @throws com.fr.third.springframework.transaction.CannotCreateTransactionException + * if transaction support is not available + * @throws TransactionException in case of lookup or system errors + * @see #doBegin + * @see #doCommit + * @see #doRollback + * @see DefaultTransactionStatus#getTransaction + */ + protected abstract Object doGetTransaction() throws TransactionException; + + /** + * Check if the given transaction object indicates an existing transaction + * (that is, a transaction which has already started). + *

The result will be evaluated according to the specified propagation + * behavior for the new transaction. An existing transaction might get + * suspended (in case of PROPAGATION_REQUIRES_NEW), or the new transaction + * might participate in the existing one (in case of PROPAGATION_REQUIRED). + *

The default implementation returns {@code false}, assuming that + * participating in existing transactions is generally not supported. + * Subclasses are of course encouraged to provide such support. + * @param transaction transaction object returned by doGetTransaction + * @return if there is an existing transaction + * @throws TransactionException in case of system errors + * @see #doGetTransaction + */ + protected boolean isExistingTransaction(Object transaction) throws TransactionException { + return false; + } + + /** + * Return whether to use a savepoint for a nested transaction. + *

Default is {@code true}, which causes delegation to DefaultTransactionStatus + * for creating and holding a savepoint. If the transaction object does not implement + * the SavepointManager interface, a NestedTransactionNotSupportedException will be + * thrown. Else, the SavepointManager will be asked to create a new savepoint to + * demarcate the start of the nested transaction. + *

Subclasses can override this to return {@code false}, causing a further + * call to {@code doBegin} - within the context of an already existing transaction. + * The {@code doBegin} implementation needs to handle this accordingly in such + * a scenario. This is appropriate for JTA, for example. + * @see DefaultTransactionStatus#createAndHoldSavepoint + * @see DefaultTransactionStatus#rollbackToHeldSavepoint + * @see DefaultTransactionStatus#releaseHeldSavepoint + * @see #doBegin + */ + protected boolean useSavepointForNestedTransaction() { + return true; + } + + /** + * Begin a new transaction with semantics according to the given transaction + * definition. Does not have to care about applying the propagation behavior, + * as this has already been handled by this abstract manager. + *

This method gets called when the transaction manager has decided to actually + * start a new transaction. Either there wasn't any transaction before, or the + * previous transaction has been suspended. + *

A special scenario is a nested transaction without savepoint: If + * {@code useSavepointForNestedTransaction()} returns "false", this method + * will be called to start a nested transaction when necessary. In such a context, + * there will be an active transaction: The implementation of this method has + * to detect this and start an appropriate nested transaction. + * @param transaction transaction object returned by {@code doGetTransaction} + * @param definition TransactionDefinition instance, describing propagation + * behavior, isolation level, read-only flag, timeout, and transaction name + * @throws TransactionException in case of creation or system errors + */ + protected abstract void doBegin(Object transaction, TransactionDefinition definition) + throws TransactionException; + + /** + * Suspend the resources of the current transaction. + * Transaction synchronization will already have been suspended. + *

The default implementation throws a TransactionSuspensionNotSupportedException, + * assuming that transaction suspension is generally not supported. + * @param transaction transaction object returned by {@code doGetTransaction} + * @return an object that holds suspended resources + * (will be kept unexamined for passing it into doResume) + * @throws com.fr.third.springframework.transaction.TransactionSuspensionNotSupportedException + * if suspending is not supported by the transaction manager implementation + * @throws TransactionException in case of system errors + * @see #doResume + */ + protected Object doSuspend(Object transaction) throws TransactionException { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); + } + + /** + * Resume the resources of the current transaction. + * Transaction synchronization will be resumed afterwards. + *

The default implementation throws a TransactionSuspensionNotSupportedException, + * assuming that transaction suspension is generally not supported. + * @param transaction transaction object returned by {@code doGetTransaction} + * @param suspendedResources the object that holds suspended resources, + * as returned by doSuspend + * @throws com.fr.third.springframework.transaction.TransactionSuspensionNotSupportedException + * if resuming is not supported by the transaction manager implementation + * @throws TransactionException in case of system errors + * @see #doSuspend + */ + protected void doResume(Object transaction, Object suspendedResources) throws TransactionException { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); + } + + /** + * Return whether to call {@code doCommit} on a transaction that has been + * marked as rollback-only in a global fashion. + *

Does not apply if an application locally sets the transaction to rollback-only + * via the TransactionStatus, but only to the transaction itself being marked as + * rollback-only by the transaction coordinator. + *

Default is "false": Local transaction strategies usually don't hold the rollback-only + * marker in the transaction itself, therefore they can't handle rollback-only transactions + * as part of transaction commit. Hence, AbstractPlatformTransactionManager will trigger + * a rollback in that case, throwing an UnexpectedRollbackException afterwards. + *

Override this to return "true" if the concrete transaction manager expects a + * {@code doCommit} call even for a rollback-only transaction, allowing for + * special handling there. This will, for example, be the case for JTA, where + * {@code UserTransaction.commit} will check the read-only flag itself and + * throw a corresponding RollbackException, which might include the specific reason + * (such as a transaction timeout). + *

If this method returns "true" but the {@code doCommit} implementation does not + * throw an exception, this transaction manager will throw an UnexpectedRollbackException + * itself. This should not be the typical case; it is mainly checked to cover misbehaving + * JTA providers that silently roll back even when the rollback has not been requested + * by the calling code. + * @see #doCommit + * @see DefaultTransactionStatus#isGlobalRollbackOnly() + * @see DefaultTransactionStatus#isLocalRollbackOnly() + * @see com.fr.third.springframework.transaction.TransactionStatus#setRollbackOnly() + * @see com.fr.third.springframework.transaction.UnexpectedRollbackException + * @see javax.transaction.UserTransaction#commit() + * @see javax.transaction.RollbackException + */ + protected boolean shouldCommitOnGlobalRollbackOnly() { + return false; + } + + /** + * Make preparations for commit, to be performed before the + * {@code beforeCommit} synchronization callbacks occur. + *

Note that exceptions will get propagated to the commit caller + * and cause a rollback of the transaction. + * @param status the status representation of the transaction + * @throws RuntimeException in case of errors; will be propagated to the caller + * (note: do not throw TransactionException subclasses here!) + */ + protected void prepareForCommit(DefaultTransactionStatus status) { + } + + /** + * Perform an actual commit of the given transaction. + *

An implementation does not need to check the "new transaction" flag + * or the rollback-only flag; this will already have been handled before. + * Usually, a straight commit will be performed on the transaction object + * contained in the passed-in status. + * @param status the status representation of the transaction + * @throws TransactionException in case of commit or system errors + * @see DefaultTransactionStatus#getTransaction + */ + protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException; + + /** + * Perform an actual rollback of the given transaction. + *

An implementation does not need to check the "new transaction" flag; + * this will already have been handled before. Usually, a straight rollback + * will be performed on the transaction object contained in the passed-in status. + * @param status the status representation of the transaction + * @throws TransactionException in case of system errors + * @see DefaultTransactionStatus#getTransaction + */ + protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException; + + /** + * Set the given transaction rollback-only. Only called on rollback + * if the current transaction participates in an existing one. + *

The default implementation throws an IllegalTransactionStateException, + * assuming that participating in existing transactions is generally not + * supported. Subclasses are of course encouraged to provide such support. + * @param status the status representation of the transaction + * @throws TransactionException in case of system errors + */ + protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException { + throw new IllegalTransactionStateException( + "Participating in existing transactions is not supported - when 'isExistingTransaction' " + + "returns true, appropriate 'doSetRollbackOnly' behavior must be provided"); + } + + /** + * Register the given list of transaction synchronizations with the existing transaction. + *

Invoked when the control of the Spring transaction manager and thus all Spring + * transaction synchronizations end, without the transaction being completed yet. This + * is for example the case when participating in an existing JTA or EJB CMT transaction. + *

The default implementation simply invokes the {@code afterCompletion} methods + * immediately, passing in "STATUS_UNKNOWN". This is the best we can do if there's no + * chance to determine the actual outcome of the outer transaction. + * @param transaction transaction object returned by {@code doGetTransaction} + * @param synchronizations List of TransactionSynchronization objects + * @throws TransactionException in case of system errors + * @see #invokeAfterCompletion(java.util.List, int) + * @see TransactionSynchronization#afterCompletion(int) + * @see TransactionSynchronization#STATUS_UNKNOWN + */ + protected void registerAfterCompletionWithExistingTransaction( + Object transaction, List synchronizations) throws TransactionException { + + logger.debug("Cannot register Spring after-completion synchronization with existing transaction - " + + "processing Spring after-completion callbacks immediately, with outcome status 'unknown'"); + invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); + } + + /** + * Cleanup resources after transaction completion. + *

Called after {@code doCommit} and {@code doRollback} execution, + * on any outcome. The default implementation does nothing. + *

Should not throw any exceptions but just issue warnings on errors. + * @param transaction transaction object returned by {@code doGetTransaction} + */ + protected void doCleanupAfterCompletion(Object transaction) { + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization; just initialize state after deserialization. + ois.defaultReadObject(); + + // Initialize transient fields. + this.logger = LogFactory.getLog(getClass()); + } + + + /** + * Holder for suspended resources. + * Used internally by {@code suspend} and {@code resume}. + */ + protected static class SuspendedResourcesHolder { + + private final Object suspendedResources; + + private List suspendedSynchronizations; + + private String name; + + private boolean readOnly; + + private Integer isolationLevel; + + private boolean wasActive; + + private SuspendedResourcesHolder(Object suspendedResources) { + this.suspendedResources = suspendedResources; + } + + private SuspendedResourcesHolder( + Object suspendedResources, List suspendedSynchronizations, + String name, boolean readOnly, Integer isolationLevel, boolean wasActive) { + this.suspendedResources = suspendedResources; + this.suspendedSynchronizations = suspendedSynchronizations; + this.name = name; + this.readOnly = readOnly; + this.isolationLevel = isolationLevel; + this.wasActive = wasActive; + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/AbstractTransactionStatus.java b/fine-spring/src/com/fr/third/springframework/transaction/support/AbstractTransactionStatus.java new file mode 100644 index 000000000..05547b69c --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/AbstractTransactionStatus.java @@ -0,0 +1,218 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import com.fr.third.springframework.transaction.NestedTransactionNotSupportedException; +import com.fr.third.springframework.transaction.SavepointManager; +import com.fr.third.springframework.transaction.TransactionException; +import com.fr.third.springframework.transaction.TransactionStatus; +import com.fr.third.springframework.transaction.TransactionUsageException; + +/** + * Abstract base implementation of the + * {@link com.fr.third.springframework.transaction.TransactionStatus} interface. + * + *

Pre-implements the handling of local rollback-only and completed flags, and + * delegation to an underlying {@link com.fr.third.springframework.transaction.SavepointManager}. + * Also offers the option of a holding a savepoint within the transaction. + * + *

Does not assume any specific internal transaction handling, such as an + * underlying transaction object, and no transaction synchronization mechanism. + * + * @author Juergen Hoeller + * @since 1.2.3 + * @see #setRollbackOnly() + * @see #isRollbackOnly() + * @see #setCompleted() + * @see #isCompleted() + * @see #getSavepointManager() + * @see SimpleTransactionStatus + * @see DefaultTransactionStatus + */ +public abstract class AbstractTransactionStatus implements TransactionStatus { + + private boolean rollbackOnly = false; + + private boolean completed = false; + + private Object savepoint; + + + //--------------------------------------------------------------------- + // Handling of current transaction state + //--------------------------------------------------------------------- + + @Override + public void setRollbackOnly() { + this.rollbackOnly = true; + } + + /** + * Determine the rollback-only flag via checking both the local rollback-only flag + * of this TransactionStatus and the global rollback-only flag of the underlying + * transaction, if any. + * @see #isLocalRollbackOnly() + * @see #isGlobalRollbackOnly() + */ + @Override + public boolean isRollbackOnly() { + return (isLocalRollbackOnly() || isGlobalRollbackOnly()); + } + + /** + * Determine the rollback-only flag via checking this TransactionStatus. + *

Will only return "true" if the application called {@code setRollbackOnly} + * on this TransactionStatus object. + */ + public boolean isLocalRollbackOnly() { + return this.rollbackOnly; + } + + /** + * Template method for determining the global rollback-only flag of the + * underlying transaction, if any. + *

This implementation always returns {@code false}. + */ + public boolean isGlobalRollbackOnly() { + return false; + } + + /** + * This implementations is empty, considering flush as a no-op. + */ + @Override + public void flush() { + } + + /** + * Mark this transaction as completed, that is, committed or rolled back. + */ + public void setCompleted() { + this.completed = true; + } + + @Override + public boolean isCompleted() { + return this.completed; + } + + + //--------------------------------------------------------------------- + // Handling of current savepoint state + //--------------------------------------------------------------------- + + /** + * Set a savepoint for this transaction. Useful for PROPAGATION_NESTED. + * @see com.fr.third.springframework.transaction.TransactionDefinition#PROPAGATION_NESTED + */ + protected void setSavepoint(Object savepoint) { + this.savepoint = savepoint; + } + + /** + * Get the savepoint for this transaction, if any. + */ + protected Object getSavepoint() { + return this.savepoint; + } + + @Override + public boolean hasSavepoint() { + return (this.savepoint != null); + } + + /** + * Create a savepoint and hold it for the transaction. + * @throws com.fr.third.springframework.transaction.NestedTransactionNotSupportedException + * if the underlying transaction does not support savepoints + */ + public void createAndHoldSavepoint() throws TransactionException { + setSavepoint(getSavepointManager().createSavepoint()); + } + + /** + * Roll back to the savepoint that is held for the transaction. + */ + public void rollbackToHeldSavepoint() throws TransactionException { + if (!hasSavepoint()) { + throw new TransactionUsageException("No savepoint associated with current transaction"); + } + getSavepointManager().rollbackToSavepoint(getSavepoint()); + setSavepoint(null); + } + + /** + * Release the savepoint that is held for the transaction. + */ + public void releaseHeldSavepoint() throws TransactionException { + if (!hasSavepoint()) { + throw new TransactionUsageException("No savepoint associated with current transaction"); + } + getSavepointManager().releaseSavepoint(getSavepoint()); + setSavepoint(null); + } + + + //--------------------------------------------------------------------- + // Implementation of SavepointManager + //--------------------------------------------------------------------- + + /** + * This implementation delegates to a SavepointManager for the + * underlying transaction, if possible. + * @see #getSavepointManager() + * @see com.fr.third.springframework.transaction.SavepointManager + */ + @Override + public Object createSavepoint() throws TransactionException { + return getSavepointManager().createSavepoint(); + } + + /** + * This implementation delegates to a SavepointManager for the + * underlying transaction, if possible. + * @throws com.fr.third.springframework.transaction.NestedTransactionNotSupportedException + * @see #getSavepointManager() + * @see com.fr.third.springframework.transaction.SavepointManager + */ + @Override + public void rollbackToSavepoint(Object savepoint) throws TransactionException { + getSavepointManager().rollbackToSavepoint(savepoint); + } + + /** + * This implementation delegates to a SavepointManager for the + * underlying transaction, if possible. + * @see #getSavepointManager() + * @see com.fr.third.springframework.transaction.SavepointManager + */ + @Override + public void releaseSavepoint(Object savepoint) throws TransactionException { + getSavepointManager().releaseSavepoint(savepoint); + } + + /** + * Return a SavepointManager for the underlying transaction, if possible. + *

Default implementation always throws a NestedTransactionNotSupportedException. + * @throws com.fr.third.springframework.transaction.NestedTransactionNotSupportedException + * if the underlying transaction does not support savepoints + */ + protected SavepointManager getSavepointManager() { + throw new NestedTransactionNotSupportedException("This transaction does not support savepoints"); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/CallbackPreferringPlatformTransactionManager.java b/fine-spring/src/com/fr/third/springframework/transaction/support/CallbackPreferringPlatformTransactionManager.java new file mode 100644 index 000000000..30536ed9f --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/CallbackPreferringPlatformTransactionManager.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import com.fr.third.springframework.transaction.PlatformTransactionManager; +import com.fr.third.springframework.transaction.TransactionDefinition; +import com.fr.third.springframework.transaction.TransactionException; + +/** + * Extension of the {@link com.fr.third.springframework.transaction.PlatformTransactionManager} + * interface, exposing a method for executing a given callback within a transaction. + * + *

Implementors of this interface automatically express a preference for + * callbacks over programmatic {@code getTransaction}, {@code commit} + * and {@code rollback} calls. Calling code may check whether a given + * transaction manager implements this interface to choose to prepare a + * callback instead of explicit transaction demarcation control. + * + *

Spring's {@link TransactionTemplate} and + * {@link com.fr.third.springframework.transaction.interceptor.TransactionInterceptor} + * detect and use this PlatformTransactionManager variant automatically. + * + * @author Juergen Hoeller + * @since 2.0 + * @see TransactionTemplate + * @see com.fr.third.springframework.transaction.interceptor.TransactionInterceptor + */ +public interface CallbackPreferringPlatformTransactionManager extends PlatformTransactionManager { + + /** + * Execute the action specified by the given callback object within a transaction. + *

Allows for returning a result object created within the transaction, that is, + * a domain object or a collection of domain objects. A RuntimeException thrown + * by the callback is treated as a fatal exception that enforces a rollback. + * Such an exception gets propagated to the caller of the template. + * @param definition the definition for the transaction to wrap the callback in + * @param callback the callback object that specifies the transactional action + * @return a result object returned by the callback, or {@code null} if none + * @throws TransactionException in case of initialization, rollback, or system errors + * @throws RuntimeException if thrown by the TransactionCallback + */ + T execute(TransactionDefinition definition, TransactionCallback callback) + throws TransactionException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/DefaultTransactionDefinition.java b/fine-spring/src/com/fr/third/springframework/transaction/support/DefaultTransactionDefinition.java new file mode 100644 index 000000000..54a4523eb --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/DefaultTransactionDefinition.java @@ -0,0 +1,277 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import java.io.Serializable; + +import com.fr.third.springframework.core.Constants; +import com.fr.third.springframework.transaction.TransactionDefinition; + +/** + * Default implementation of the {@link TransactionDefinition} interface, + * offering bean-style configuration and sensible default values + * (PROPAGATION_REQUIRED, ISOLATION_DEFAULT, TIMEOUT_DEFAULT, readOnly=false). + * + *

Base class for both {@link TransactionTemplate} and + * {@link com.fr.third.springframework.transaction.interceptor.DefaultTransactionAttribute}. + * + * @author Juergen Hoeller + * @since 08.05.2003 + */ +@SuppressWarnings("serial") +public class DefaultTransactionDefinition implements TransactionDefinition, Serializable { + + /** Prefix for the propagation constants defined in TransactionDefinition */ + public static final String PREFIX_PROPAGATION = "PROPAGATION_"; + + /** Prefix for the isolation constants defined in TransactionDefinition */ + public static final String PREFIX_ISOLATION = "ISOLATION_"; + + /** Prefix for transaction timeout values in description strings */ + public static final String PREFIX_TIMEOUT = "timeout_"; + + /** Marker for read-only transactions in description strings */ + public static final String READ_ONLY_MARKER = "readOnly"; + + + /** Constants instance for TransactionDefinition */ + static final Constants constants = new Constants(TransactionDefinition.class); + + private int propagationBehavior = PROPAGATION_REQUIRED; + + private int isolationLevel = ISOLATION_DEFAULT; + + private int timeout = TIMEOUT_DEFAULT; + + private boolean readOnly = false; + + private String name; + + + /** + * Create a new DefaultTransactionDefinition, with default settings. + * Can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + */ + public DefaultTransactionDefinition() { + } + + /** + * Copy constructor. Definition can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + */ + public DefaultTransactionDefinition(TransactionDefinition other) { + this.propagationBehavior = other.getPropagationBehavior(); + this.isolationLevel = other.getIsolationLevel(); + this.timeout = other.getTimeout(); + this.readOnly = other.isReadOnly(); + this.name = other.getName(); + } + + /** + * Create a new DefaultTransactionDefinition with the the given + * propagation behavior. Can be modified through bean property setters. + * @param propagationBehavior one of the propagation constants in the + * TransactionDefinition interface + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + */ + public DefaultTransactionDefinition(int propagationBehavior) { + this.propagationBehavior = propagationBehavior; + } + + + /** + * Set the propagation behavior by the name of the corresponding constant in + * TransactionDefinition, e.g. "PROPAGATION_REQUIRED". + * @param constantName name of the constant + * @exception IllegalArgumentException if the supplied value is not resolvable + * to one of the {@code PROPAGATION_} constants or is {@code null} + * @see #setPropagationBehavior + * @see #PROPAGATION_REQUIRED + */ + public final void setPropagationBehaviorName(String constantName) throws IllegalArgumentException { + if (constantName == null || !constantName.startsWith(PREFIX_PROPAGATION)) { + throw new IllegalArgumentException("Only propagation constants allowed"); + } + setPropagationBehavior(constants.asNumber(constantName).intValue()); + } + + /** + * Set the propagation behavior. Must be one of the propagation constants + * in the TransactionDefinition interface. Default is PROPAGATION_REQUIRED. + * @exception IllegalArgumentException if the supplied value is not + * one of the {@code PROPAGATION_} constants + * @see #PROPAGATION_REQUIRED + */ + public final void setPropagationBehavior(int propagationBehavior) { + if (!constants.getValues(PREFIX_PROPAGATION).contains(propagationBehavior)) { + throw new IllegalArgumentException("Only values of propagation constants allowed"); + } + this.propagationBehavior = propagationBehavior; + } + + @Override + public final int getPropagationBehavior() { + return this.propagationBehavior; + } + + /** + * Set the isolation level by the name of the corresponding constant in + * TransactionDefinition, e.g. "ISOLATION_DEFAULT". + * @param constantName name of the constant + * @exception IllegalArgumentException if the supplied value is not resolvable + * to one of the {@code ISOLATION_} constants or is {@code null} + * @see #setIsolationLevel + * @see #ISOLATION_DEFAULT + */ + public final void setIsolationLevelName(String constantName) throws IllegalArgumentException { + if (constantName == null || !constantName.startsWith(PREFIX_ISOLATION)) { + throw new IllegalArgumentException("Only isolation constants allowed"); + } + setIsolationLevel(constants.asNumber(constantName).intValue()); + } + + /** + * Set the isolation level. Must be one of the isolation constants + * in the TransactionDefinition interface. Default is ISOLATION_DEFAULT. + * @exception IllegalArgumentException if the supplied value is not + * one of the {@code ISOLATION_} constants + * @see #ISOLATION_DEFAULT + */ + public final void setIsolationLevel(int isolationLevel) { + if (!constants.getValues(PREFIX_ISOLATION).contains(isolationLevel)) { + throw new IllegalArgumentException("Only values of isolation constants allowed"); + } + this.isolationLevel = isolationLevel; + } + + @Override + public final int getIsolationLevel() { + return this.isolationLevel; + } + + /** + * Set the timeout to apply, as number of seconds. + * Default is TIMEOUT_DEFAULT (-1). + * @see #TIMEOUT_DEFAULT + */ + public final void setTimeout(int timeout) { + if (timeout < TIMEOUT_DEFAULT) { + throw new IllegalArgumentException("Timeout must be a positive integer or TIMEOUT_DEFAULT"); + } + this.timeout = timeout; + } + + @Override + public final int getTimeout() { + return this.timeout; + } + + /** + * Set whether to optimize as read-only transaction. + * Default is "false". + */ + public final void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + @Override + public final boolean isReadOnly() { + return this.readOnly; + } + + /** + * Set the name of this transaction. Default is none. + *

This will be used as transaction name to be shown in a + * transaction monitor, if applicable (for example, WebLogic's). + */ + public final void setName(String name) { + this.name = name; + } + + @Override + public final String getName() { + return this.name; + } + + + /** + * This implementation compares the {@code toString()} results. + * @see #toString() + */ + @Override + public boolean equals(Object other) { + return (other instanceof TransactionDefinition && toString().equals(other.toString())); + } + + /** + * This implementation returns {@code toString()}'s hash code. + * @see #toString() + */ + @Override + public int hashCode() { + return toString().hashCode(); + } + + /** + * Return an identifying description for this transaction definition. + *

The format matches the one used by + * {@link com.fr.third.springframework.transaction.interceptor.TransactionAttributeEditor}, + * to be able to feed {@code toString} results into bean properties of type + * {@link com.fr.third.springframework.transaction.interceptor.TransactionAttribute}. + *

Has to be overridden in subclasses for correct {@code equals} + * and {@code hashCode} behavior. Alternatively, {@link #equals} + * and {@link #hashCode} can be overridden themselves. + * @see #getDefinitionDescription() + * @see com.fr.third.springframework.transaction.interceptor.TransactionAttributeEditor + */ + @Override + public String toString() { + return getDefinitionDescription().toString(); + } + + /** + * Return an identifying description for this transaction definition. + *

Available to subclasses, for inclusion in their {@code toString()} result. + */ + protected final StringBuilder getDefinitionDescription() { + StringBuilder result = new StringBuilder(); + result.append(constants.toCode(this.propagationBehavior, PREFIX_PROPAGATION)); + result.append(','); + result.append(constants.toCode(this.isolationLevel, PREFIX_ISOLATION)); + if (this.timeout != TIMEOUT_DEFAULT) { + result.append(','); + result.append(PREFIX_TIMEOUT).append(this.timeout); + } + if (this.readOnly) { + result.append(','); + result.append(READ_ONLY_MARKER); + } + return result; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/DefaultTransactionStatus.java b/fine-spring/src/com/fr/third/springframework/transaction/support/DefaultTransactionStatus.java new file mode 100644 index 000000000..1d9e69b26 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/DefaultTransactionStatus.java @@ -0,0 +1,195 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import com.fr.third.springframework.transaction.NestedTransactionNotSupportedException; +import com.fr.third.springframework.transaction.SavepointManager; + +/** + * Default implementation of the {@link com.fr.third.springframework.transaction.TransactionStatus} + * interface, used by {@link AbstractPlatformTransactionManager}. Based on the concept + * of an underlying "transaction object". + * + *

Holds all status information that {@link AbstractPlatformTransactionManager} + * needs internally, including a generic transaction object determined by the + * concrete transaction manager implementation. + * + *

Supports delegating savepoint-related methods to a transaction object + * that implements the {@link SavepointManager} interface. + * + *

NOTE: This is not intended for use with other PlatformTransactionManager + * implementations, in particular not for mock transaction managers in testing environments. + * Use the alternative {@link SimpleTransactionStatus} class or a mock for the plain + * {@link com.fr.third.springframework.transaction.TransactionStatus} interface instead. + * + * @author Juergen Hoeller + * @since 19.01.2004 + * @see AbstractPlatformTransactionManager + * @see com.fr.third.springframework.transaction.SavepointManager + * @see #getTransaction + * @see #createSavepoint + * @see #rollbackToSavepoint + * @see #releaseSavepoint + * @see SimpleTransactionStatus + */ +public class DefaultTransactionStatus extends AbstractTransactionStatus { + + private final Object transaction; + + private final boolean newTransaction; + + private final boolean newSynchronization; + + private final boolean readOnly; + + private final boolean debug; + + private final Object suspendedResources; + + + /** + * Create a new DefaultTransactionStatus instance. + * @param transaction underlying transaction object that can hold + * state for the internal transaction implementation + * @param newTransaction if the transaction is new, + * else participating in an existing transaction + * @param newSynchronization if a new transaction synchronization + * has been opened for the given transaction + * @param readOnly whether the transaction is read-only + * @param debug should debug logging be enabled for the handling of this transaction? + * Caching it in here can prevent repeated calls to ask the logging system whether + * debug logging should be enabled. + * @param suspendedResources a holder for resources that have been suspended + * for this transaction, if any + */ + public DefaultTransactionStatus( + Object transaction, boolean newTransaction, boolean newSynchronization, + boolean readOnly, boolean debug, Object suspendedResources) { + + this.transaction = transaction; + this.newTransaction = newTransaction; + this.newSynchronization = newSynchronization; + this.readOnly = readOnly; + this.debug = debug; + this.suspendedResources = suspendedResources; + } + + + /** + * Return the underlying transaction object. + */ + public Object getTransaction() { + return this.transaction; + } + + /** + * Return whether there is an actual transaction active. + */ + public boolean hasTransaction() { + return (this.transaction != null); + } + + @Override + public boolean isNewTransaction() { + return (hasTransaction() && this.newTransaction); + } + + /** + * Return if a new transaction synchronization has been opened + * for this transaction. + */ + public boolean isNewSynchronization() { + return this.newSynchronization; + } + + /** + * Return if this transaction is defined as read-only transaction. + */ + public boolean isReadOnly() { + return this.readOnly; + } + + /** + * Return whether the progress of this transaction is debugged. This is used + * by AbstractPlatformTransactionManager as an optimization, to prevent repeated + * calls to logger.isDebug(). Not really intended for client code. + */ + public boolean isDebug() { + return this.debug; + } + + /** + * Return the holder for resources that have been suspended for this transaction, + * if any. + */ + public Object getSuspendedResources() { + return this.suspendedResources; + } + + + //--------------------------------------------------------------------- + // Enable functionality through underlying transaction object + //--------------------------------------------------------------------- + + /** + * Determine the rollback-only flag via checking both the transaction object, + * provided that the latter implements the {@link SmartTransactionObject} interface. + *

Will return "true" if the transaction itself has been marked rollback-only + * by the transaction coordinator, for example in case of a timeout. + * @see SmartTransactionObject#isRollbackOnly + */ + @Override + public boolean isGlobalRollbackOnly() { + return ((this.transaction instanceof SmartTransactionObject) && + ((SmartTransactionObject) this.transaction).isRollbackOnly()); + } + + /** + * Delegate the flushing to the transaction object, + * provided that the latter implements the {@link SmartTransactionObject} interface. + */ + @Override + public void flush() { + if (this.transaction instanceof SmartTransactionObject) { + ((SmartTransactionObject) this.transaction).flush(); + } + } + + /** + * This implementation exposes the SavepointManager interface + * of the underlying transaction object, if any. + */ + @Override + protected SavepointManager getSavepointManager() { + if (!isTransactionSavepointManager()) { + throw new NestedTransactionNotSupportedException( + "Transaction object [" + getTransaction() + "] does not support savepoints"); + } + return (SavepointManager) getTransaction(); + } + + /** + * Return whether the underlying transaction implements the + * SavepointManager interface. + * @see #getTransaction + * @see com.fr.third.springframework.transaction.SavepointManager + */ + public boolean isTransactionSavepointManager() { + return (getTransaction() instanceof SavepointManager); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/DelegatingTransactionDefinition.java b/fine-spring/src/com/fr/third/springframework/transaction/support/DelegatingTransactionDefinition.java new file mode 100644 index 000000000..c48e664b6 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/DelegatingTransactionDefinition.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import java.io.Serializable; + +import com.fr.third.springframework.transaction.TransactionDefinition; +import com.fr.third.springframework.util.Assert; + +/** + * {@link TransactionDefinition} implementation that delegates all calls to a given target + * {@link TransactionDefinition} instance. Abstract because it is meant to be subclassed, + * with subclasses overriding specific methods that are not supposed to simply delegate + * to the target instance. + * + * @author Juergen Hoeller + * @since 3.0 + */ +@SuppressWarnings("serial") +public abstract class DelegatingTransactionDefinition implements TransactionDefinition, Serializable { + + private final TransactionDefinition targetDefinition; + + + /** + * Create a DelegatingTransactionAttribute for the given target attribute. + * @param targetDefinition the target TransactionAttribute to delegate to + */ + public DelegatingTransactionDefinition(TransactionDefinition targetDefinition) { + Assert.notNull(targetDefinition, "Target definition must not be null"); + this.targetDefinition = targetDefinition; + } + + + @Override + public int getPropagationBehavior() { + return this.targetDefinition.getPropagationBehavior(); + } + + @Override + public int getIsolationLevel() { + return this.targetDefinition.getIsolationLevel(); + } + + @Override + public int getTimeout() { + return this.targetDefinition.getTimeout(); + } + + @Override + public boolean isReadOnly() { + return this.targetDefinition.isReadOnly(); + } + + @Override + public String getName() { + return this.targetDefinition.getName(); + } + + + @Override + public boolean equals(Object obj) { + return this.targetDefinition.equals(obj); + } + + @Override + public int hashCode() { + return this.targetDefinition.hashCode(); + } + + @Override + public String toString() { + return this.targetDefinition.toString(); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceHolder.java b/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceHolder.java new file mode 100644 index 000000000..b5e601e37 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceHolder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +/** + * Generic interface to be implemented by resource holders. + * Allows Spring's transaction infrastructure to introspect + * and reset the holder when necessary. + * + * @author Juergen Hoeller + * @since 2.5.5 + * @see ResourceHolderSupport + * @see ResourceHolderSynchronization + */ +public interface ResourceHolder { + + /** + * Reset the transactional state of this holder. + */ + void reset(); + + /** + * Notify this holder that it has been unbound from transaction synchronization. + */ + void unbound(); + + /** + * Determine whether this holder is considered as 'void', + * i.e. as a leftover from a previous thread. + */ + boolean isVoid(); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceHolderSupport.java b/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceHolderSupport.java new file mode 100644 index 000000000..a7782200a --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceHolderSupport.java @@ -0,0 +1,196 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import java.util.Date; + +import com.fr.third.springframework.transaction.TransactionTimedOutException; + +/** + * Convenient base class for resource holders. + * + *

Features rollback-only support for nested transactions. + * Can expire after a certain number of seconds or milliseconds, + * to determine transactional timeouts. + * + * @author Juergen Hoeller + * @since 02.02.2004 + * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin + * @see com.fr.third.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout + */ +public abstract class ResourceHolderSupport implements ResourceHolder { + + private boolean synchronizedWithTransaction = false; + + private boolean rollbackOnly = false; + + private Date deadline; + + private int referenceCount = 0; + + private boolean isVoid = false; + + + /** + * Mark the resource as synchronized with a transaction. + */ + public void setSynchronizedWithTransaction(boolean synchronizedWithTransaction) { + this.synchronizedWithTransaction = synchronizedWithTransaction; + } + + /** + * Return whether the resource is synchronized with a transaction. + */ + public boolean isSynchronizedWithTransaction() { + return this.synchronizedWithTransaction; + } + + /** + * Mark the resource transaction as rollback-only. + */ + public void setRollbackOnly() { + this.rollbackOnly = true; + } + + /** + * Return whether the resource transaction is marked as rollback-only. + */ + public boolean isRollbackOnly() { + return this.rollbackOnly; + } + + /** + * Set the timeout for this object in seconds. + * @param seconds number of seconds until expiration + */ + public void setTimeoutInSeconds(int seconds) { + setTimeoutInMillis(seconds * 1000); + } + + /** + * Set the timeout for this object in milliseconds. + * @param millis number of milliseconds until expiration + */ + public void setTimeoutInMillis(long millis) { + this.deadline = new Date(System.currentTimeMillis() + millis); + } + + /** + * Return whether this object has an associated timeout. + */ + public boolean hasTimeout() { + return (this.deadline != null); + } + + /** + * Return the expiration deadline of this object. + * @return the deadline as Date object + */ + public Date getDeadline() { + return this.deadline; + } + + /** + * Return the time to live for this object in seconds. + * Rounds up eagerly, e.g. 9.00001 still to 10. + * @return number of seconds until expiration + * @throws TransactionTimedOutException if the deadline has already been reached + */ + public int getTimeToLiveInSeconds() { + double diff = ((double) getTimeToLiveInMillis()) / 1000; + int secs = (int) Math.ceil(diff); + checkTransactionTimeout(secs <= 0); + return secs; + } + + /** + * Return the time to live for this object in milliseconds. + * @return number of millseconds until expiration + * @throws TransactionTimedOutException if the deadline has already been reached + */ + public long getTimeToLiveInMillis() throws TransactionTimedOutException{ + if (this.deadline == null) { + throw new IllegalStateException("No timeout specified for this resource holder"); + } + long timeToLive = this.deadline.getTime() - System.currentTimeMillis(); + checkTransactionTimeout(timeToLive <= 0); + return timeToLive; + } + + /** + * Set the transaction rollback-only if the deadline has been reached, + * and throw a TransactionTimedOutException. + */ + private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException { + if (deadlineReached) { + setRollbackOnly(); + throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline); + } + } + + /** + * Increase the reference count by one because the holder has been requested + * (i.e. someone requested the resource held by it). + */ + public void requested() { + this.referenceCount++; + } + + /** + * Decrease the reference count by one because the holder has been released + * (i.e. someone released the resource held by it). + */ + public void released() { + this.referenceCount--; + } + + /** + * Return whether there are still open references to this holder. + */ + public boolean isOpen() { + return (this.referenceCount > 0); + } + + /** + * Clear the transactional state of this resource holder. + */ + public void clear() { + this.synchronizedWithTransaction = false; + this.rollbackOnly = false; + this.deadline = null; + } + + /** + * Reset this resource holder - transactional state as well as reference count. + */ + @Override + public void reset() { + clear(); + this.referenceCount = 0; + } + + @Override + public void unbound() { + this.isVoid = true; + } + + @Override + public boolean isVoid() { + return this.isVoid; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceHolderSynchronization.java b/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceHolderSynchronization.java new file mode 100644 index 000000000..12fcbd1af --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceHolderSynchronization.java @@ -0,0 +1,183 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +/** + * {@link TransactionSynchronization} implementation that manages a + * {@link ResourceHolder} bound through {@link TransactionSynchronizationManager}. + * + * @author Juergen Hoeller + * @since 2.5.5 + */ +public abstract class ResourceHolderSynchronization + implements TransactionSynchronization { + + private final H resourceHolder; + + private final K resourceKey; + + private volatile boolean holderActive = true; + + + /** + * Create a new ResourceHolderSynchronization for the given holder. + * @param resourceHolder the ResourceHolder to manage + * @param resourceKey the key to bind the ResourceHolder for + * @see TransactionSynchronizationManager#bindResource + */ + public ResourceHolderSynchronization(H resourceHolder, K resourceKey) { + this.resourceHolder = resourceHolder; + this.resourceKey = resourceKey; + } + + + @Override + public void suspend() { + if (this.holderActive) { + TransactionSynchronizationManager.unbindResource(this.resourceKey); + } + } + + @Override + public void resume() { + if (this.holderActive) { + TransactionSynchronizationManager.bindResource(this.resourceKey, this.resourceHolder); + } + } + + @Override + public void flush() { + flushResource(this.resourceHolder); + } + + @Override + public void beforeCommit(boolean readOnly) { + } + + @Override + public void beforeCompletion() { + if (shouldUnbindAtCompletion()) { + TransactionSynchronizationManager.unbindResource(this.resourceKey); + this.holderActive = false; + if (shouldReleaseBeforeCompletion()) { + releaseResource(this.resourceHolder, this.resourceKey); + } + } + } + + @Override + public void afterCommit() { + if (!shouldReleaseBeforeCompletion()) { + processResourceAfterCommit(this.resourceHolder); + } + } + + @Override + public void afterCompletion(int status) { + if (shouldUnbindAtCompletion()) { + boolean releaseNecessary = false; + if (this.holderActive) { + // The thread-bound resource holder might not be available anymore, + // since afterCompletion might get called from a different thread. + this.holderActive = false; + TransactionSynchronizationManager.unbindResourceIfPossible(this.resourceKey); + this.resourceHolder.unbound(); + releaseNecessary = true; + } + else { + releaseNecessary = shouldReleaseAfterCompletion(this.resourceHolder); + } + if (releaseNecessary) { + releaseResource(this.resourceHolder, this.resourceKey); + } + } + else { + // Probably a pre-bound resource... + cleanupResource(this.resourceHolder, this.resourceKey, (status == STATUS_COMMITTED)); + } + this.resourceHolder.reset(); + } + + + /** + * Return whether this holder should be unbound at completion + * (or should rather be left bound to the thread after the transaction). + *

The default implementation returns {@code true}. + */ + protected boolean shouldUnbindAtCompletion() { + return true; + } + + /** + * Return whether this holder's resource should be released before + * transaction completion ({@code true}) or rather after + * transaction completion ({@code false}). + *

Note that resources will only be released when they are + * unbound from the thread ({@link #shouldUnbindAtCompletion()}). + *

The default implementation returns {@code true}. + * @see #releaseResource + */ + protected boolean shouldReleaseBeforeCompletion() { + return true; + } + + /** + * Return whether this holder's resource should be released after + * transaction completion ({@code true}). + *

The default implementation returns {@code !shouldReleaseBeforeCompletion()}, + * releasing after completion if no attempt was made before completion. + * @see #releaseResource + */ + protected boolean shouldReleaseAfterCompletion(H resourceHolder) { + return !shouldReleaseBeforeCompletion(); + } + + /** + * Flush callback for the given resource holder. + * @param resourceHolder the resource holder to flush + */ + protected void flushResource(H resourceHolder) { + } + + /** + * After-commit callback for the given resource holder. + * Only called when the resource hasn't been released yet + * ({@link #shouldReleaseBeforeCompletion()}). + * @param resourceHolder the resource holder to process + */ + protected void processResourceAfterCommit(H resourceHolder) { + } + + /** + * Release the given resource (after it has been unbound from the thread). + * @param resourceHolder the resource holder to process + * @param resourceKey the key that the ResourceHolder was bound for + */ + protected void releaseResource(H resourceHolder, K resourceKey) { + } + + /** + * Perform a cleanup on the given resource (which is left bound to the thread). + * @param resourceHolder the resource holder to process + * @param resourceKey the key that the ResourceHolder was bound for + * @param committed whether the transaction has committed ({@code true}) + * or rolled back ({@code false}) + */ + protected void cleanupResource(H resourceHolder, K resourceKey, boolean committed) { + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceTransactionManager.java b/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceTransactionManager.java new file mode 100644 index 000000000..403208fe7 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/ResourceTransactionManager.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import com.fr.third.springframework.transaction.PlatformTransactionManager; + +/** + * Extension of the {@link com.fr.third.springframework.transaction.PlatformTransactionManager} + * interface, indicating a native resource transaction manager, operating on a single + * target resource. Such transaction managers differ from JTA transaction managers in + * that they do not use XA transaction enlistment for an open number of resources but + * rather focus on leveraging the native power and simplicity of a single target resource. + * + *

This interface is mainly used for abstract introspection of a transaction manager, + * giving clients a hint on what kind of transaction manager they have been given + * and on what concrete resource the transaction manager is operating on. + * + * @author Juergen Hoeller + * @since 2.0.4 + * @see TransactionSynchronizationManager + */ +public interface ResourceTransactionManager extends PlatformTransactionManager { + + /** + * Return the resource factory that this transaction manager operates on, + * e.g. a JDBC DataSource or a JMS ConnectionFactory. + *

This target resource factory is usually used as resource key for + * {@link TransactionSynchronizationManager}'s resource bindings per thread. + * @return the target resource factory (never {@code null}) + * @see TransactionSynchronizationManager#bindResource + * @see TransactionSynchronizationManager#getResource + */ + Object getResourceFactory(); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/SimpleTransactionStatus.java b/fine-spring/src/com/fr/third/springframework/transaction/support/SimpleTransactionStatus.java new file mode 100644 index 000000000..a986eeac9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/SimpleTransactionStatus.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +/** + * A simple {@link com.fr.third.springframework.transaction.TransactionStatus} + * implementation. + * + *

Derives from {@link AbstractTransactionStatus} and adds an explicit + * {@link #isNewTransaction() "newTransaction"} flag. + * + *

This class is not used by any of Spring's pre-built + * {@link com.fr.third.springframework.transaction.PlatformTransactionManager} + * implementations. It is mainly provided as a start for custom transaction + * manager implementations and as a static mock for testing transactional + * code (either as part of a mock {@code PlatformTransactionManager} or + * as argument passed into a {@link TransactionCallback} to be tested). + * + * @author Juergen Hoeller + * @since 1.2.3 + * @see #SimpleTransactionStatus(boolean) + * @see TransactionCallback + */ +public class SimpleTransactionStatus extends AbstractTransactionStatus { + + private final boolean newTransaction; + + + /** + * Create a new instance of the {@link SimpleTransactionStatus} class, + * indicating a new transaction. + */ + public SimpleTransactionStatus() { + this(true); + } + + /** + * Create a new instance of the {@link SimpleTransactionStatus} class. + * @param newTransaction whether to indicate a new transaction + */ + public SimpleTransactionStatus(boolean newTransaction) { + this.newTransaction = newTransaction; + } + + + @Override + public boolean isNewTransaction() { + return this.newTransaction; + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/SmartTransactionObject.java b/fine-spring/src/com/fr/third/springframework/transaction/support/SmartTransactionObject.java new file mode 100644 index 000000000..415afd9ef --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/SmartTransactionObject.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import java.io.Flushable; + +/** + * Interface to be implemented by transaction objects that are able to + * return an internal rollback-only marker, typically from a another + * transaction that has participated and marked it as rollback-only. + * + *

Autodetected by DefaultTransactionStatus, to always return a + * current rollbackOnly flag even if not resulting from the current + * TransactionStatus. + * + * @author Juergen Hoeller + * @since 1.1 + * @see DefaultTransactionStatus#isRollbackOnly + */ +public interface SmartTransactionObject extends Flushable { + + /** + * Return whether the transaction is internally marked as rollback-only. + * Can, for example, check the JTA UserTransaction. + * @see javax.transaction.UserTransaction#getStatus + * @see javax.transaction.Status#STATUS_MARKED_ROLLBACK + */ + boolean isRollbackOnly(); + + /** + * Flush the underlying sessions to the datastore, if applicable: + * for example, all affected Hibernate/JPA sessions. + */ + @Override + void flush(); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionCallback.java b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionCallback.java new file mode 100644 index 000000000..56c691416 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionCallback.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import com.fr.third.springframework.transaction.TransactionStatus; + +/** + * Callback interface for transactional code. Used with {@link TransactionTemplate}'s + * {@code execute} method, often as anonymous class within a method implementation. + * + *

Typically used to assemble various calls to transaction-unaware data access + * services into a higher-level service method with transaction demarcation. As an + * alternative, consider the use of declarative transaction demarcation (e.g. through + * Spring's {@link com.fr.third.springframework.transaction.annotation.Transactional} annotation). + * + * @author Juergen Hoeller + * @since 17.03.2003 + * @see TransactionTemplate + * @see CallbackPreferringPlatformTransactionManager + */ +public interface TransactionCallback { + + /** + * Gets called by {@link TransactionTemplate#execute} within a transactional context. + * Does not need to care about transactions itself, although it can retrieve + * and influence the status of the current transaction via the given status + * object, e.g. setting rollback-only. + * + *

Allows for returning a result object created within the transaction, i.e. + * a domain object or a collection of domain objects. A RuntimeException thrown + * by the callback is treated as application exception that enforces a rollback. + * Any such exception will be propagated to the caller of the template, unless + * there is a problem rolling back, in which case a TransactionException will be + * thrown. + * + * @param status associated transaction status + * @return a result object, or {@code null} + * @see TransactionTemplate#execute + * @see CallbackPreferringPlatformTransactionManager#execute + */ + T doInTransaction(TransactionStatus status); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionCallbackWithoutResult.java b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionCallbackWithoutResult.java new file mode 100644 index 000000000..9e2394065 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionCallbackWithoutResult.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import com.fr.third.springframework.transaction.TransactionStatus; + +/** + * Simple convenience class for TransactionCallback implementation. + * Allows for implementing a doInTransaction version without result, + * i.e. without the need for a return statement. + * + * @author Juergen Hoeller + * @since 28.03.2003 + * @see TransactionTemplate + */ +public abstract class TransactionCallbackWithoutResult implements TransactionCallback { + + @Override + public final Object doInTransaction(TransactionStatus status) { + doInTransactionWithoutResult(status); + return null; + } + + /** + * Gets called by {@code TransactionTemplate.execute} within a transactional + * context. Does not need to care about transactions itself, although it can retrieve + * and influence the status of the current transaction via the given status object, + * e.g. setting rollback-only. + * + *

A RuntimeException thrown by the callback is treated as application + * exception that enforces a rollback. An exception gets propagated to the + * caller of the template. + * + *

Note when using JTA: JTA transactions only work with transactional + * JNDI resources, so implementations need to use such resources if they + * want transaction support. + * + * @param status associated transaction status + * @see TransactionTemplate#execute + */ + protected abstract void doInTransactionWithoutResult(TransactionStatus status); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionOperations.java b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionOperations.java new file mode 100644 index 000000000..4cd66aabd --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionOperations.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import com.fr.third.springframework.transaction.TransactionException; + +/** + * Interface specifying basic transaction execution operations. + * Implemented by {@link TransactionTemplate}. Not often used directly, + * but a useful option to enhance testability, as it can easily be + * mocked or stubbed. + * + * @author Juergen Hoeller + * @since 2.0.4 + */ +public interface TransactionOperations { + + /** + * Execute the action specified by the given callback object within a transaction. + *

Allows for returning a result object created within the transaction, that is, + * a domain object or a collection of domain objects. A RuntimeException thrown + * by the callback is treated as a fatal exception that enforces a rollback. + * Such an exception gets propagated to the caller of the template. + * @param action the callback object that specifies the transactional action + * @return a result object returned by the callback, or {@code null} if none + * @throws TransactionException in case of initialization, rollback, or system errors + * @throws RuntimeException if thrown by the TransactionCallback + */ + T execute(TransactionCallback action) throws TransactionException; + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronization.java b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronization.java new file mode 100644 index 000000000..24fae190b --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronization.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import java.io.Flushable; + +/** + * Interface for transaction synchronization callbacks. + * Supported by AbstractPlatformTransactionManager. + * + *

TransactionSynchronization implementations can implement the Ordered interface + * to influence their execution order. A synchronization that does not implement the + * Ordered interface is appended to the end of the synchronization chain. + * + *

System synchronizations performed by Spring itself use specific order values, + * allowing for fine-grained interaction with their execution order (if necessary). + * + * @author Juergen Hoeller + * @since 02.06.2003 + * @see TransactionSynchronizationManager + * @see AbstractPlatformTransactionManager + * @see com.fr.third.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER + */ +public interface TransactionSynchronization extends Flushable { + + /** Completion status in case of proper commit */ + int STATUS_COMMITTED = 0; + + /** Completion status in case of proper rollback */ + int STATUS_ROLLED_BACK = 1; + + /** Completion status in case of heuristic mixed completion or system errors */ + int STATUS_UNKNOWN = 2; + + + /** + * Suspend this synchronization. + * Supposed to unbind resources from TransactionSynchronizationManager if managing any. + * @see TransactionSynchronizationManager#unbindResource + */ + void suspend(); + + /** + * Resume this synchronization. + * Supposed to rebind resources to TransactionSynchronizationManager if managing any. + * @see TransactionSynchronizationManager#bindResource + */ + void resume(); + + /** + * Flush the underlying session to the datastore, if applicable: + * for example, a Hibernate/JPA session. + * @see com.fr.third.springframework.transaction.TransactionStatus#flush() + */ + @Override + void flush(); + + /** + * Invoked before transaction commit (before "beforeCompletion"). + * Can e.g. flush transactional O/R Mapping sessions to the database. + *

This callback does not mean that the transaction will actually be committed. + * A rollback decision can still occur after this method has been called. This callback + * is rather meant to perform work that's only relevant if a commit still has a chance + * to happen, such as flushing SQL statements to the database. + *

Note that exceptions will get propagated to the commit caller and cause a + * rollback of the transaction. + * @param readOnly whether the transaction is defined as read-only transaction + * @throws RuntimeException in case of errors; will be propagated to the caller + * (note: do not throw TransactionException subclasses here!) + * @see #beforeCompletion + */ + void beforeCommit(boolean readOnly); + + /** + * Invoked before transaction commit/rollback. + * Can perform resource cleanup before transaction completion. + *

This method will be invoked after {@code beforeCommit}, even when + * {@code beforeCommit} threw an exception. This callback allows for + * closing resources before transaction completion, for any outcome. + * @throws RuntimeException in case of errors; will be logged but not propagated + * (note: do not throw TransactionException subclasses here!) + * @see #beforeCommit + * @see #afterCompletion + */ + void beforeCompletion(); + + /** + * Invoked after transaction commit. Can perform further operations right + * after the main transaction has successfully committed. + *

Can e.g. commit further operations that are supposed to follow on a successful + * commit of the main transaction, like confirmation messages or emails. + *

NOTE: The transaction will have been committed already, but the + * transactional resources might still be active and accessible. As a consequence, + * any data access code triggered at this point will still "participate" in the + * original transaction, allowing to perform some cleanup (with no commit following + * anymore!), unless it explicitly declares that it needs to run in a separate + * transaction. Hence: Use {@code PROPAGATION_REQUIRES_NEW} for any + * transactional operation that is called from here. + * @throws RuntimeException in case of errors; will be propagated to the caller + * (note: do not throw TransactionException subclasses here!) + */ + void afterCommit(); + + /** + * Invoked after transaction commit/rollback. + * Can perform resource cleanup after transaction completion. + *

NOTE: The transaction will have been committed or rolled back already, + * but the transactional resources might still be active and accessible. As a + * consequence, any data access code triggered at this point will still "participate" + * in the original transaction, allowing to perform some cleanup (with no commit + * following anymore!), unless it explicitly declares that it needs to run in a + * separate transaction. Hence: Use {@code PROPAGATION_REQUIRES_NEW} + * for any transactional operation that is called from here. + * @param status completion status according to the {@code STATUS_*} constants + * @throws RuntimeException in case of errors; will be logged but not propagated + * (note: do not throw TransactionException subclasses here!) + * @see #STATUS_COMMITTED + * @see #STATUS_ROLLED_BACK + * @see #STATUS_UNKNOWN + * @see #beforeCompletion + */ + void afterCompletion(int status); + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronizationAdapter.java b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronizationAdapter.java new file mode 100644 index 000000000..6588557e9 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronizationAdapter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import com.fr.third.springframework.core.Ordered; + +/** + * Simple {@link TransactionSynchronization} adapter containing empty + * method implementations, for easier overriding of single methods. + * + *

Also implements the {@link Ordered} interface to enable the execution + * order of synchronizations to be controlled declaratively. The default + * {@link #getOrder() order} is {@link Ordered#LOWEST_PRECEDENCE}, indicating + * late execution; return a lower value for earlier execution. + * + * @author Juergen Hoeller + * @since 22.01.2004 + */ +public abstract class TransactionSynchronizationAdapter implements TransactionSynchronization, Ordered { + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public void suspend() { + } + + @Override + public void resume() { + } + + @Override + public void flush() { + } + + @Override + public void beforeCommit(boolean readOnly) { + } + + @Override + public void beforeCompletion() { + } + + @Override + public void afterCommit() { + } + + @Override + public void afterCompletion(int status) { + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronizationManager.java b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronizationManager.java new file mode 100644 index 000000000..a60d2668d --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronizationManager.java @@ -0,0 +1,471 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.core.NamedThreadLocal; +import com.fr.third.springframework.core.OrderComparator; +import com.fr.third.springframework.util.Assert; + +/** + * Central delegate that manages resources and transaction synchronizations per thread. + * To be used by resource management code but not by typical application code. + * + *

Supports one resource per key without overwriting, that is, a resource needs + * to be removed before a new one can be set for the same key. + * Supports a list of transaction synchronizations if synchronization is active. + * + *

Resource management code should check for thread-bound resources, e.g. JDBC + * Connections or Hibernate Sessions, via {@code getResource}. Such code is + * normally not supposed to bind resources to threads, as this is the responsibility + * of transaction managers. A further option is to lazily bind on first use if + * transaction synchronization is active, for performing transactions that span + * an arbitrary number of resources. + * + *

Transaction synchronization must be activated and deactivated by a transaction + * manager via {@link #initSynchronization()} and {@link #clearSynchronization()}. + * This is automatically supported by {@link AbstractPlatformTransactionManager}, + * and thus by all standard Spring transaction managers, such as + * {@link com.fr.third.springframework.transaction.jta.JtaTransactionManager} and + * {@link com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager}. + * + *

Resource management code should only register synchronizations when this + * manager is active, which can be checked via {@link #isSynchronizationActive}; + * it should perform immediate resource cleanup else. If transaction synchronization + * isn't active, there is either no current transaction, or the transaction manager + * doesn't support transaction synchronization. + * + *

Synchronization is for example used to always return the same resources + * within a JTA transaction, e.g. a JDBC Connection or a Hibernate Session for + * any given DataSource or SessionFactory, respectively. + * + * @author Juergen Hoeller + * @since 02.06.2003 + * @see #isSynchronizationActive + * @see #registerSynchronization + * @see TransactionSynchronization + * @see AbstractPlatformTransactionManager#setTransactionSynchronization + * @see com.fr.third.springframework.transaction.jta.JtaTransactionManager + * @see com.fr.third.springframework.jdbc.datasource.DataSourceTransactionManager + * @see com.fr.third.springframework.jdbc.datasource.DataSourceUtils#getConnection + */ +public abstract class TransactionSynchronizationManager { + + private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class); + + private static final ThreadLocal> resources = + new NamedThreadLocal>("Transactional resources"); + + private static final ThreadLocal> synchronizations = + new NamedThreadLocal>("Transaction synchronizations"); + + private static final ThreadLocal currentTransactionName = + new NamedThreadLocal("Current transaction name"); + + private static final ThreadLocal currentTransactionReadOnly = + new NamedThreadLocal("Current transaction read-only status"); + + private static final ThreadLocal currentTransactionIsolationLevel = + new NamedThreadLocal("Current transaction isolation level"); + + private static final ThreadLocal actualTransactionActive = + new NamedThreadLocal("Actual transaction active"); + + + //------------------------------------------------------------------------- + // Management of transaction-associated resource handles + //------------------------------------------------------------------------- + + /** + * Return all resources that are bound to the current thread. + *

Mainly for debugging purposes. Resource managers should always invoke + * {@code hasResource} for a specific resource key that they are interested in. + * @return a Map with resource keys (usually the resource factory) and resource + * values (usually the active resource object), or an empty Map if there are + * currently no resources bound + * @see #hasResource + */ + public static Map getResourceMap() { + Map map = resources.get(); + return (map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap()); + } + + /** + * Check if there is a resource for the given key bound to the current thread. + * @param key the key to check (usually the resource factory) + * @return if there is a value bound to the current thread + * @see ResourceTransactionManager#getResourceFactory() + */ + public static boolean hasResource(Object key) { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Object value = doGetResource(actualKey); + return (value != null); + } + + /** + * Retrieve a resource for the given key that is bound to the current thread. + * @param key the key to check (usually the resource factory) + * @return a value bound to the current thread (usually the active + * resource object), or {@code null} if none + * @see ResourceTransactionManager#getResourceFactory() + */ + public static Object getResource(Object key) { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Object value = doGetResource(actualKey); + if (value != null && logger.isTraceEnabled()) { + logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + + Thread.currentThread().getName() + "]"); + } + return value; + } + + /** + * Actually check the value of the resource that is bound for the given key. + */ + private static Object doGetResource(Object actualKey) { + Map map = resources.get(); + if (map == null) { + return null; + } + Object value = map.get(actualKey); + // Transparently remove ResourceHolder that was marked as void... + if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { + map.remove(actualKey); + // Remove entire ThreadLocal if empty... + if (map.isEmpty()) { + resources.remove(); + } + value = null; + } + return value; + } + + /** + * Bind the given resource for the given key to the current thread. + * @param key the key to bind the value to (usually the resource factory) + * @param value the value to bind (usually the active resource object) + * @throws IllegalStateException if there is already a value bound to the thread + * @see ResourceTransactionManager#getResourceFactory() + */ + public static void bindResource(Object key, Object value) throws IllegalStateException { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Assert.notNull(value, "Value must not be null"); + Map map = resources.get(); + // set ThreadLocal Map if none found + if (map == null) { + map = new HashMap(); + resources.set(map); + } + Object oldValue = map.put(actualKey, value); + // Transparently suppress a ResourceHolder that was marked as void... + if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) { + oldValue = null; + } + if (oldValue != null) { + throw new IllegalStateException("Already value [" + oldValue + "] for key [" + + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); + } + if (logger.isTraceEnabled()) { + logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + + Thread.currentThread().getName() + "]"); + } + } + + /** + * Unbind a resource for the given key from the current thread. + * @param key the key to unbind (usually the resource factory) + * @return the previously bound value (usually the active resource object) + * @throws IllegalStateException if there is no value bound to the thread + * @see ResourceTransactionManager#getResourceFactory() + */ + public static Object unbindResource(Object key) throws IllegalStateException { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Object value = doUnbindResource(actualKey); + if (value == null) { + throw new IllegalStateException( + "No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); + } + return value; + } + + /** + * Unbind a resource for the given key from the current thread. + * @param key the key to unbind (usually the resource factory) + * @return the previously bound value, or {@code null} if none bound + */ + public static Object unbindResourceIfPossible(Object key) { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + return doUnbindResource(actualKey); + } + + /** + * Actually remove the value of the resource that is bound for the given key. + */ + private static Object doUnbindResource(Object actualKey) { + Map map = resources.get(); + if (map == null) { + return null; + } + Object value = map.remove(actualKey); + // Remove entire ThreadLocal if empty... + if (map.isEmpty()) { + resources.remove(); + } + // Transparently suppress a ResourceHolder that was marked as void... + if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { + value = null; + } + if (value != null && logger.isTraceEnabled()) { + logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" + + Thread.currentThread().getName() + "]"); + } + return value; + } + + + //------------------------------------------------------------------------- + // Management of transaction synchronizations + //------------------------------------------------------------------------- + + /** + * Return if transaction synchronization is active for the current thread. + * Can be called before register to avoid unnecessary instance creation. + * @see #registerSynchronization + */ + public static boolean isSynchronizationActive() { + return (synchronizations.get() != null); + } + + /** + * Activate transaction synchronization for the current thread. + * Called by a transaction manager on transaction begin. + * @throws IllegalStateException if synchronization is already active + */ + public static void initSynchronization() throws IllegalStateException { + if (isSynchronizationActive()) { + throw new IllegalStateException("Cannot activate transaction synchronization - already active"); + } + logger.trace("Initializing transaction synchronization"); + synchronizations.set(new LinkedHashSet()); + } + + /** + * Register a new transaction synchronization for the current thread. + * Typically called by resource management code. + *

Note that synchronizations can implement the + * {@link com.fr.third.springframework.core.Ordered} interface. + * They will be executed in an order according to their order value (if any). + * @param synchronization the synchronization object to register + * @throws IllegalStateException if transaction synchronization is not active + * @see com.fr.third.springframework.core.Ordered + */ + public static void registerSynchronization(TransactionSynchronization synchronization) + throws IllegalStateException { + + Assert.notNull(synchronization, "TransactionSynchronization must not be null"); + if (!isSynchronizationActive()) { + throw new IllegalStateException("Transaction synchronization is not active"); + } + synchronizations.get().add(synchronization); + } + + /** + * Return an unmodifiable snapshot list of all registered synchronizations + * for the current thread. + * @return unmodifiable List of TransactionSynchronization instances + * @throws IllegalStateException if synchronization is not active + * @see TransactionSynchronization + */ + public static List getSynchronizations() throws IllegalStateException { + Set synchs = synchronizations.get(); + if (synchs == null) { + throw new IllegalStateException("Transaction synchronization is not active"); + } + // Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions + // while iterating and invoking synchronization callbacks that in turn + // might register further synchronizations. + if (synchs.isEmpty()) { + return Collections.emptyList(); + } + else { + // Sort lazily here, not in registerSynchronization. + List sortedSynchs = new ArrayList(synchs); + OrderComparator.sort(sortedSynchs); + return Collections.unmodifiableList(sortedSynchs); + } + } + + /** + * Deactivate transaction synchronization for the current thread. + * Called by the transaction manager on transaction cleanup. + * @throws IllegalStateException if synchronization is not active + */ + public static void clearSynchronization() throws IllegalStateException { + if (!isSynchronizationActive()) { + throw new IllegalStateException("Cannot deactivate transaction synchronization - not active"); + } + logger.trace("Clearing transaction synchronization"); + synchronizations.remove(); + } + + + //------------------------------------------------------------------------- + // Exposure of transaction characteristics + //------------------------------------------------------------------------- + + /** + * Expose the name of the current transaction, if any. + * Called by the transaction manager on transaction begin and on cleanup. + * @param name the name of the transaction, or {@code null} to reset it + * @see com.fr.third.springframework.transaction.TransactionDefinition#getName() + */ + public static void setCurrentTransactionName(String name) { + currentTransactionName.set(name); + } + + /** + * Return the name of the current transaction, or {@code null} if none set. + * To be called by resource management code for optimizations per use case, + * for example to optimize fetch strategies for specific named transactions. + * @see com.fr.third.springframework.transaction.TransactionDefinition#getName() + */ + public static String getCurrentTransactionName() { + return currentTransactionName.get(); + } + + /** + * Expose a read-only flag for the current transaction. + * Called by the transaction manager on transaction begin and on cleanup. + * @param readOnly {@code true} to mark the current transaction + * as read-only; {@code false} to reset such a read-only marker + * @see com.fr.third.springframework.transaction.TransactionDefinition#isReadOnly() + */ + public static void setCurrentTransactionReadOnly(boolean readOnly) { + currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null); + } + + /** + * Return whether the current transaction is marked as read-only. + * To be called by resource management code when preparing a newly + * created resource (for example, a Hibernate Session). + *

Note that transaction synchronizations receive the read-only flag + * as argument for the {@code beforeCommit} callback, to be able + * to suppress change detection on commit. The present method is meant + * to be used for earlier read-only checks, for example to set the + * flush mode of a Hibernate Session to "FlushMode.NEVER" upfront. + * @see com.fr.third.springframework.transaction.TransactionDefinition#isReadOnly() + * @see TransactionSynchronization#beforeCommit(boolean) + */ + public static boolean isCurrentTransactionReadOnly() { + return (currentTransactionReadOnly.get() != null); + } + + /** + * Expose an isolation level for the current transaction. + * Called by the transaction manager on transaction begin and on cleanup. + * @param isolationLevel the isolation level to expose, according to the + * JDBC Connection constants (equivalent to the corresponding Spring + * TransactionDefinition constants), or {@code null} to reset it + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + * @see com.fr.third.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED + * @see com.fr.third.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED + * @see com.fr.third.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ + * @see com.fr.third.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE + * @see com.fr.third.springframework.transaction.TransactionDefinition#getIsolationLevel() + */ + public static void setCurrentTransactionIsolationLevel(Integer isolationLevel) { + currentTransactionIsolationLevel.set(isolationLevel); + } + + /** + * Return the isolation level for the current transaction, if any. + * To be called by resource management code when preparing a newly + * created resource (for example, a JDBC Connection). + * @return the currently exposed isolation level, according to the + * JDBC Connection constants (equivalent to the corresponding Spring + * TransactionDefinition constants), or {@code null} if none + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + * @see com.fr.third.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED + * @see com.fr.third.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED + * @see com.fr.third.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ + * @see com.fr.third.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE + * @see com.fr.third.springframework.transaction.TransactionDefinition#getIsolationLevel() + */ + public static Integer getCurrentTransactionIsolationLevel() { + return currentTransactionIsolationLevel.get(); + } + + /** + * Expose whether there currently is an actual transaction active. + * Called by the transaction manager on transaction begin and on cleanup. + * @param active {@code true} to mark the current thread as being associated + * with an actual transaction; {@code false} to reset that marker + */ + public static void setActualTransactionActive(boolean active) { + actualTransactionActive.set(active ? Boolean.TRUE : null); + } + + /** + * Return whether there currently is an actual transaction active. + * This indicates whether the current thread is associated with an actual + * transaction rather than just with active transaction synchronization. + *

To be called by resource management code that wants to discriminate + * between active transaction synchronization (with or without backing + * resource transaction; also on PROPAGATION_SUPPORTS) and an actual + * transaction being active (with backing resource transaction; + * on PROPAGATION_REQUIRES, PROPAGATION_REQUIRES_NEW, etc). + * @see #isSynchronizationActive() + */ + public static boolean isActualTransactionActive() { + return (actualTransactionActive.get() != null); + } + + + /** + * Clear the entire transaction synchronization state for the current thread: + * registered synchronizations as well as the various transaction characteristics. + * @see #clearSynchronization() + * @see #setCurrentTransactionName + * @see #setCurrentTransactionReadOnly + * @see #setCurrentTransactionIsolationLevel + * @see #setActualTransactionActive + */ + public static void clear() { + clearSynchronization(); + setCurrentTransactionName(null); + setCurrentTransactionReadOnly(false); + setCurrentTransactionIsolationLevel(null); + setActualTransactionActive(false); + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronizationUtils.java b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronizationUtils.java new file mode 100644 index 000000000..4a4df0dd7 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionSynchronizationUtils.java @@ -0,0 +1,193 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.aop.scope.ScopedObject; +import com.fr.third.springframework.core.InfrastructureProxy; +import com.fr.third.springframework.util.Assert; +import com.fr.third.springframework.util.ClassUtils; + +/** + * Utility methods for triggering specific {@link TransactionSynchronization} + * callback methods on all currently registered synchronizations. + * + * @author Juergen Hoeller + * @since 2.0 + * @see TransactionSynchronization + * @see TransactionSynchronizationManager#getSynchronizations() + */ +public abstract class TransactionSynchronizationUtils { + + private static final Log logger = LogFactory.getLog(TransactionSynchronizationUtils.class); + + private static final boolean aopAvailable = ClassUtils.isPresent( + "com.fr.third.springframework.aop.scope.ScopedObject", TransactionSynchronizationUtils.class.getClassLoader()); + + + /** + * Check whether the given resource transaction managers refers to the given + * (underlying) resource factory. + * @see ResourceTransactionManager#getResourceFactory() + * @see com.fr.third.springframework.core.InfrastructureProxy#getWrappedObject() + */ + public static boolean sameResourceFactory(ResourceTransactionManager tm, Object resourceFactory) { + return unwrapResourceIfNecessary(tm.getResourceFactory()).equals(unwrapResourceIfNecessary(resourceFactory)); + } + + /** + * Unwrap the given resource handle if necessary; otherwise return + * the given handle as-is. + * @see com.fr.third.springframework.core.InfrastructureProxy#getWrappedObject() + */ + static Object unwrapResourceIfNecessary(Object resource) { + Assert.notNull(resource, "Resource must not be null"); + Object resourceRef = resource; + // unwrap infrastructure proxy + if (resourceRef instanceof InfrastructureProxy) { + resourceRef = ((InfrastructureProxy) resourceRef).getWrappedObject(); + } + if (aopAvailable) { + // now unwrap scoped proxy + resourceRef = ScopedProxyUnwrapper.unwrapIfNecessary(resourceRef); + } + return resourceRef; + } + + + /** + * Trigger {@code flush} callbacks on all currently registered synchronizations. + * @throws RuntimeException if thrown by a {@code flush} callback + * @see TransactionSynchronization#flush() + */ + public static void triggerFlush() { + for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) { + synchronization.flush(); + } + } + + /** + * Trigger {@code beforeCommit} callbacks on all currently registered synchronizations. + * @param readOnly whether the transaction is defined as read-only transaction + * @throws RuntimeException if thrown by a {@code beforeCommit} callback + * @see TransactionSynchronization#beforeCommit(boolean) + */ + public static void triggerBeforeCommit(boolean readOnly) { + for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) { + synchronization.beforeCommit(readOnly); + } + } + + /** + * Trigger {@code beforeCompletion} callbacks on all currently registered synchronizations. + * @see TransactionSynchronization#beforeCompletion() + */ + public static void triggerBeforeCompletion() { + for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) { + try { + synchronization.beforeCompletion(); + } + catch (Throwable tsex) { + logger.error("TransactionSynchronization.beforeCompletion threw exception", tsex); + } + } + } + + /** + * Trigger {@code afterCommit} callbacks on all currently registered synchronizations. + * @throws RuntimeException if thrown by a {@code afterCommit} callback + * @see TransactionSynchronizationManager#getSynchronizations() + * @see TransactionSynchronization#afterCommit() + */ + public static void triggerAfterCommit() { + invokeAfterCommit(TransactionSynchronizationManager.getSynchronizations()); + } + + /** + * Actually invoke the {@code afterCommit} methods of the + * given Spring TransactionSynchronization objects. + * @param synchronizations List of TransactionSynchronization objects + * @see TransactionSynchronization#afterCommit() + */ + public static void invokeAfterCommit(List synchronizations) { + if (synchronizations != null) { + for (TransactionSynchronization synchronization : synchronizations) { + synchronization.afterCommit(); + } + } + } + + /** + * Trigger {@code afterCompletion} callbacks on all currently registered synchronizations. + * @see TransactionSynchronizationManager#getSynchronizations() + * @param completionStatus the completion status according to the + * constants in the TransactionSynchronization interface + * @see TransactionSynchronization#afterCompletion(int) + * @see TransactionSynchronization#STATUS_COMMITTED + * @see TransactionSynchronization#STATUS_ROLLED_BACK + * @see TransactionSynchronization#STATUS_UNKNOWN + */ + public static void triggerAfterCompletion(int completionStatus) { + List synchronizations = TransactionSynchronizationManager.getSynchronizations(); + invokeAfterCompletion(synchronizations, completionStatus); + } + + /** + * Actually invoke the {@code afterCompletion} methods of the + * given Spring TransactionSynchronization objects. + * @param synchronizations List of TransactionSynchronization objects + * @param completionStatus the completion status according to the + * constants in the TransactionSynchronization interface + * @see TransactionSynchronization#afterCompletion(int) + * @see TransactionSynchronization#STATUS_COMMITTED + * @see TransactionSynchronization#STATUS_ROLLED_BACK + * @see TransactionSynchronization#STATUS_UNKNOWN + */ + public static void invokeAfterCompletion(List synchronizations, int completionStatus) { + if (synchronizations != null) { + for (TransactionSynchronization synchronization : synchronizations) { + try { + synchronization.afterCompletion(completionStatus); + } + catch (Throwable tsex) { + logger.error("TransactionSynchronization.afterCompletion threw exception", tsex); + } + } + } + } + + + /** + * Inner class to avoid hard-coded dependency on AOP module. + */ + private static class ScopedProxyUnwrapper { + + public static Object unwrapIfNecessary(Object resource) { + if (resource instanceof ScopedObject) { + return ((ScopedObject) resource).getTargetObject(); + } + else { + return resource; + } + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionTemplate.java b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionTemplate.java new file mode 100644 index 000000000..bb8320fe3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/TransactionTemplate.java @@ -0,0 +1,181 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.springframework.transaction.support; + +import java.lang.reflect.UndeclaredThrowableException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.fr.third.springframework.beans.factory.InitializingBean; +import com.fr.third.springframework.transaction.PlatformTransactionManager; +import com.fr.third.springframework.transaction.TransactionDefinition; +import com.fr.third.springframework.transaction.TransactionException; +import com.fr.third.springframework.transaction.TransactionStatus; +import com.fr.third.springframework.transaction.TransactionSystemException; + +/** + * Template class that simplifies programmatic transaction demarcation and + * transaction exception handling. + * + *

The central method is {@link #execute}, supporting transactional code that + * implements the {@link TransactionCallback} interface. This template handles + * the transaction lifecycle and possible exceptions such that neither the + * TransactionCallback implementation nor the calling code needs to explicitly + * handle transactions. + * + *

Typical usage: Allows for writing low-level data access objects that use + * resources such as JDBC DataSources but are not transaction-aware themselves. + * Instead, they can implicitly participate in transactions handled by higher-level + * application services utilizing this class, making calls to the low-level + * services via an inner-class callback object. + * + *

Can be used within a service implementation via direct instantiation with + * a transaction manager reference, or get prepared in an application context + * and passed to services as bean reference. Note: The transaction manager should + * always be configured as bean in the application context: in the first case given + * to the service directly, in the second case given to the prepared template. + * + *

Supports setting the propagation behavior and the isolation level by name, + * for convenient configuration in context definitions. + * + * @author Juergen Hoeller + * @since 17.03.2003 + * @see #execute + * @see #setTransactionManager + * @see com.fr.third.springframework.transaction.PlatformTransactionManager + */ +@SuppressWarnings("serial") +public class TransactionTemplate extends DefaultTransactionDefinition + implements TransactionOperations, InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private PlatformTransactionManager transactionManager; + + + /** + * Construct a new TransactionTemplate for bean usage. + *

Note: The PlatformTransactionManager needs to be set before + * any {@code execute} calls. + * @see #setTransactionManager + */ + public TransactionTemplate() { + } + + /** + * Construct a new TransactionTemplate using the given transaction manager. + * @param transactionManager the transaction management strategy to be used + */ + public TransactionTemplate(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Construct a new TransactionTemplate using the given transaction manager, + * taking its default settings from the given transaction definition. + * @param transactionManager the transaction management strategy to be used + * @param transactionDefinition the transaction definition to copy the + * default settings from. Local properties can still be set to change values. + */ + public TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) { + super(transactionDefinition); + this.transactionManager = transactionManager; + } + + + /** + * Set the transaction management strategy to be used. + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Return the transaction management strategy to be used. + */ + public PlatformTransactionManager getTransactionManager() { + return this.transactionManager; + } + + @Override + public void afterPropertiesSet() { + if (this.transactionManager == null) { + throw new IllegalArgumentException("Property 'transactionManager' is required"); + } + } + + + @Override + public T execute(TransactionCallback action) throws TransactionException { + if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { + return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); + } + else { + TransactionStatus status = this.transactionManager.getTransaction(this); + T result; + try { + result = action.doInTransaction(status); + } + catch (RuntimeException ex) { + // Transactional code threw application exception -> rollback + rollbackOnException(status, ex); + throw ex; + } + catch (Error err) { + // Transactional code threw error -> rollback + rollbackOnException(status, err); + throw err; + } + catch (Exception ex) { + // Transactional code threw unexpected exception -> rollback + rollbackOnException(status, ex); + throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); + } + this.transactionManager.commit(status); + return result; + } + } + + /** + * Perform a rollback, handling rollback exceptions properly. + * @param status object representing the transaction + * @param ex the thrown application exception or error + * @throws TransactionException in case of a rollback error + */ + private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException { + logger.debug("Initiating transaction rollback on application exception", ex); + try { + this.transactionManager.rollback(status); + } + catch (TransactionSystemException ex2) { + logger.error("Application exception overridden by rollback exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } + catch (RuntimeException ex2) { + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } + catch (Error err) { + logger.error("Application exception overridden by rollback error", ex); + throw err; + } + } + +} diff --git a/fine-spring/src/com/fr/third/springframework/transaction/support/package-info.java b/fine-spring/src/com/fr/third/springframework/transaction/support/package-info.java new file mode 100644 index 000000000..2ea15a3f3 --- /dev/null +++ b/fine-spring/src/com/fr/third/springframework/transaction/support/package-info.java @@ -0,0 +1,10 @@ + +/** + * + * Support classes for the com.fr.third.springframework.transaction package. + * Provides an abstract base class for transaction manager implementations, + * and a template plus callback for transaction demarcation. + * + */ +package com.fr.third.springframework.transaction.support; +