CHAPTER 9

image

LDAP Transactions

In this chapter, you will learn

  • The basics of transactions.
  • Spring transaction abstraction.
  • Spring LDAP support for transactions.

Transaction Basics

Transactions are an integral part of enterprise applications. Put simply, a transaction is a series of operations that are performed together. For a transaction to be completed or committed, all its operations must succeed. If, for any reason, one operation fails, the entire transaction fails and is rolled back. In that scenario, all the previous operations that have succeeded must be undone. This ensures that the end state matches the state that was in place before the transaction started.

In your day-to-day world, you run into transactions all the time. Consider an online banking scenario where you wish to transfer $300 from your savings account to your checking account. This operation involves debiting the savings account by $300 and crediting the checking account by $300. If the debiting part of the operation were to succeed and the crediting part fail, you would end up with $300 less in your combined accounts. (Ideally, we all would like the debit operation to fail and the credit operation to succeed, but the bank might be knocking on our door the next day.) Banks ensure that accounts never end up in such inconsistent states by using transactions.

Transactions are usually associated with the following four well-known characteristics, often referred to as ACID properties:

  • Atomicity: This property ensures that a transaction executes completely or not at all. So in our above example, we either successfully transfer the money or our transfer fails. This all-or-nothing property is also referred to as single unit of work or logical unit of work.
  • Consistency: This property ensures that a transaction leaves the system in a consistent state after its completion. For example, with a database system, this means that all the integrity constraints, such as primary key or referential integrity, are satisfied.
  • Isolation: This property ensures that a transaction executes independent of other parallel transactions. Changes or side effects of a transaction that has not yet completed will never be seen by other transactions. In the money transfer scenario, another owner of the account will only see the balances before or after the transfer. They will never be able to see the intermediate balances no matter how long the transaction takes to complete. Many database systems relax this property and provide several levels of isolation. Table 9-1 lists the primary transaction levels and descriptions. As the isolation level increases, transaction concurrency decreases and transaction consistency increases.

    Table 9-1. Isolation Levels

    Isolation Level Description
    Read Uncommitted This isolation level allows a running transaction to see changes made by other uncommitted transactions. Changes made by this transaction become visible to other transactions even before it completes. This is the lowest level of isolation and can more appropriately be considered as lack of isolation. Since it completely violates one of the ACID properties, it is not supported by most database vendors.
    Read Committed This isolation level allows a query in a running transaction to see only data committed before the query began. However, all uncommitted changes or changes committed by concurrent transactions during query execution will not be seen. This is the default isolation level for most databases including Oracle, MySQL, and PostgreSQL.
    Repeatable Read This isolation level allows a query in a running transaction to read the same data every time it is executed. To achieve this, the transaction acquires locks on all the rows examined (not just fetched) until it is complete.
    Serializable This is the strictest and most expensive of all the isolation levels. Interleaving transactions are stacked up so that transactions are executed one after another rather than concurrently. With this isolation level, queries will only see the data that has been committed before the start of the transaction and will never see uncommitted changes or commits by concurrent transactions.
  • Durability: This property ensures that the results of a committed transaction never get lost due to a failure. Revisiting the bank transfer scenario, when you receive a confirmation that the transfer has succeeded, the durability property makes sure that this change becomes permanent.

Local vs. Global Transactions

Transactions are often categorized into either local or global transactions depending on the number of resources that participate in the transaction. Examples of these resources include a database system or a JMS queue. Resource managers such as a JDBC driver are typically used to manage resources.

Local transactions are transactions that involve a single resource. The most common example is a transaction associated with a single database. These transactions are usually managed via objects used to access the resource. In the case of a JDBC database transaction, implementations of the java.sql.Connection interface are used to access the database. These implementations also provide commit and rollback methods for managing transactions. In the case of a JMS queue, the javax.jms.Session instance provides methods for controlling transactions.

Global transactions, on the other hand, deal with multiple resources. For example, a global transaction can be used to read a message from a JMS queue and write a record to the database all in one transaction.

Global transactions are managed using a transaction manager that is external to the resources. It is responsible for communicating with resource managers and making the final commit or rollback decision on distributed transactions. In Java/JEE, global transactions are implemented using Java Transaction API (JTA). JTA provides standard interfaces for transaction manager and transaction participating components.

Transaction managers employ a “two phase commit” protocol to coordinate global transactions. As the names suggests, the two phase commit protocol has the following two phases:

  • Prepare phase: In this phase, all participating resource managers are asked if they are ready to commit their work. Upon receiving the request, the resource managers attempt to record their state. If successful, the resource manager responds positively. If it cannot commit, the resource manager responds negatively and rolls back the local changes.
  • Commit phase: If the transaction manager receives all positive responses, it commits the transaction and notifies all the participants of the commit. If it receives one or more negative responses, it rolls back the entire transaction and notifies all the participants.

The two phase commit protocol is shown in Figure 9-1.

9781430263975_Fig09-01.jpg

Figure 9-1. Two phase commit protocol

Programmatic vs. Declarative Transactions

Developers have two choices when it comes to adding transaction capabilities to their application.

Programmatically  

In this scenario, the transaction management code for starting, committing, or rolling back transactions surrounds the business code. This can provide extreme flexibility but can also make maintenance difficult. The following code gives an example of programmatic transaction using JTA and EJB 3.0:

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class OrderManager {
 
   @Resource
   private UserTransaction transaction;
    
   public void create(Order order) {
   try {
      transaction.begin();
      // business logic for processing order
      verifyAddress(order);
          processOrder(order);
      sendConfirmation(order);
      transaction.commit();
   }
   catch(Exception e) {
      transaction.rollback();
   }
   }
}

Declaratively

In this scenario, the container is responsible for starting, committing, or rolling back transactions. The developer usually specifies the transaction behavior via annotations or XML. This model cleanly separates the transaction management code from business logic. The following code gives an example of declarative transactions using JTA and EJB 3.0. When an exception happens during order processing, the setRollbackOnly method on the session context is called; this marks that the transaction must be rolled back.

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class OrderManager {
 
   @Resource
   private SessionContext context;
    
   @TransactionAttribute(TransactionAttributeType.REQUIRED)
   public void create(Order order) {
   try {
      // business logic for processing order
      verifyAddress(order);
          processOrder(order);
      sendConfirmation(order);
   }
   catch(Exception e) {
      context.setRollbackOnly();
   }
   }
}

Spring Transaction Abstraction

The Spring Framework provides a consistent programming model for handling both global and local transactions. The transaction abstraction hides the inner workings of different transaction APIs, such as JTA, JDBC, JMS, and JPA, and allows developers to write transaction-enabled code in an environment-neutral way. Behind the scenes, Spring simply delegates the transaction management to the underlying transaction providers. Both programmatic and declarative transaction management models are supported without requiring any EJBs. The declarative approach is usually recommended and that is what we will be using in this book.

Central to Spring’s transaction management is the PlatformTransactionManager abstraction. It exposes key aspects of transaction management in a technology-independent manner. It is responsible for creating and managing transactions and is required for both declarative and programmatic transactions. Several implementations of this interface, such as JtaTransactionManager, DataSourceTransactionManager and JmsTransactionManager, are available out of the box. The PlatformTransactionManager API is shown in Listing 9-1.

Listing 9-1.

package org.springframework.transaction;
 
public interface PlatformTransactionManager {
 
   TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
   void commit(TransactionStatus status) throws TransactionException;
   void rollback(TransactionStatus status) throws TransactionException;
   String getName();
}

The getTransaction method in the PlatformTransactionManager is used to retrieve an existing transaction. If no active transaction is found, this method might create a new transaction based on the transactional properties specified in the TransactionDefinition instance. The following is the list of properties that TransactionDefinition interface abstracts:

  • Read-only: This property indicates whether this transaction is read-only or not.
  • Timeout: This property mandates the time in which the transaction must complete. If the transaction fails to complete in the specified time, it will be rolled back automatically.
  • Isolation: This property controls the degree of isolation among transactions. The possible isolation levels are discussed in Table 9-1.
  • Propagation: Consider the scenario where an active transaction exists and Spring encounters code that needs to be executed in a transaction. One option in that scenario is to execute the code in the existing transaction. Another option is to suspend the existing transaction and start a new transaction to execute the code. The propagation property can be used to define such transaction behavior. Possible values include PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW, PROPAGATION_SUPPORTS, etc.

The getTransaction method returns an instance of TransactionStatus representing the status of the current transaction. Application code can use this interface to check if this is a new transaction or if the transaction has been completed. The interface can also be used to programmatically request a transaction rollback. The other two methods in the PlatformTransactionManager are commit and rollback which, as their names suggest, can be used to commit or roll back the transaction.

Declarative Transactions Using Spring

Spring provides two ways to declaratively add transaction behavior to applications: pure XML and annotations. The annotation approach is very popular and greatly simplifies the configuration. To demonstrate declarative transactions, consider the simple scenario of inserting a new record in a Person table in a database. Listing 9-2 gives the PersonRepositoryImpl class with a create method implementing this scenario.

Listing 9-2.

import org.springframework.jdbc.core.JdbcTemplate;
 
public class PersonRepositoryImpl implements PersonRepository {
 
   private JdbcTemplate jdbcTemplate;
    
   public void create(String firstName, String lastName) {
      String sql = "INSERT INTO PERSON (FIRST_NAME, " + "LAST_NAME) VALUES (?, ?)";
      jdbcTemplate.update(sql, new Object[]{firstName, lastName});
   }
}

Listing 9-3 shows the PersonRepository interface that the above class implements.

Listing 9-3.

public interface PersonRepository {
 
   public void create(String firstName, String lastName);
 
}

The next step is to make the create method transactional. This is done by simply annotating the method with @Transactional, as shown in Listing 9-4. (Note that I annotated the method in the implementation and not the method in the interface.)

Listing 9-4.

import org.springframework.transaction.annotation.Transactional;
 
public class PersonRepositoryImpl implements PersonRepository {
   ...........
   @Transactional
   public void create(String firstName, String lastName) {
   ...........
   }
}

The @Transactional annotation has several properties that can be used to specify additional information such as propagation and isolation. Listing 9-5 shows the method with default isolation and REQUIRES_NEW propagation.

Listing 9-5.

@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.DEFAULT)
public void create(String  firstName, String lastName) {
}

The next step is to specify a transaction manager for Spring to use. Since you are going after a single database, the org.springframework.jdbc.datasource.DataSourc eTransactionManager shown in Listing 9-6 is ideal for your case. From Listing 9-6,  you can see that the DataSourceTransactionManager needs a datasource in order to obtain and manage connections to the database.

Listing 9-6.

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <property name="dataSource" ref="dataSource"/>
</bean>

The complete application context configuration file for declarative transaction management is given in Listing 9-7.

Listing 9-7.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/tx/spring-aop.xsd">
 
   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
   </bean>
   <tx:annotation-driven transaction-manager="transactionManager"/>
   <aop:aspectj-autoproxy />

</beans>

The <tx:annotation-driven/> tag indicates that  you are using annotation-based transaction management. This tag, along with <aop:aspectj-autoproxy />, instructs Spring to use Aspect-Oriented Programming (AOP) and create proxies that manage transaction on behalf of the annotated class. So, when a call is made to a transactional method, the proxy intercepts the call and uses the transaction manager to obtain a transaction (new or existing). The called method is then invoked, and if the method completes successfully, the proxy using the transaction manager will commit the transaction. If the method fails, throwing an exception, the transaction will be rolled back. This AOP-based transaction processing is shown in Figure 9-2.

9781430263975_Fig09-02.jpg

Figure 9-2. AOP-based Spring transaction

LDAP Transaction Support

The LDAP protocol requires that all LDAP operations (such as modify or delete) follow ACID properties. This transactional behavior ensures consistency of the information stored in the LDAP server. However, LDAP does not define transactions across multiple operations. Consider the scenario where you want to add two LDAP entries as one atomic operation. A successful completion of the operation means that both entries get added to the LDAP server. If there is a failure and one of the entries can’t be added, the server will automatically undo the addition of the other entry. Such transactional behavior is not part of the LDAP specification and does not exist in the world of LDAP. Also, the lack of transactional semantics such as commit and rollback make it impossible to assure data consistency across multiple LDAP servers.

Though transactions are not part of the LDAP  specification, servers such as IBM Tivoli Directory Server and ApacheDS provide transaction support. The Begin transaction (OID  1.3.18.0.2.12.5) and End transaction (OID 1.3.18.0.2.12.6) extended controls supported by IBM Tivoli Directory Server can be used to demarcate a set of operations inside a transaction. The RFC 5805 (http://tools.ietf.org/html/rfc5805) attempts to standardize transactions in LDAP and is currently in experimental state.

Spring LDAP Transaction Support

The lack of transactions in LDAP might seem surprising at first. More importantly, it can act as a barrier to the widespread adoption of directory servers in enterprises. To address this, Spring LDAP offers a non-LDAP/JNDI-specific compensating transaction support. This transaction support integrates tightly with the Spring transaction management infrastructure you saw in the earlier section. Figure 9-3 shows the components responsible for Spring LDAP transaction support.

9781430263975_Fig09-03.jpg

Figure 9-3. Spring LDAP transaction support

The ContextSourceTransactionManager class implements PlatformTransactionManager and is responsible for managing LDAP-based transactions. This class, along with its collaborators, keeps track of the LDAP operations performed inside the transaction and makes a record of the state before each operation. If the transaction were to rollback, the transaction manager will take steps to restore the original state. To achieve this behavior, the transaction manager uses a TransactionAwareContextSourceProxy instead of working directly with LdapContextSource. This proxy class also ensures that a single javax.naming.directory.DirContext instance is used throughout the transaction and will not be closed until the transaction is finished.

Compensating Transactions

A compensating transaction undoes the effects of a previously committed transaction and restores the system to a previous consistent state. Consider a transaction that involves booking an airline ticket. A compensating transaction in that scenario is an operation that cancels the reservation. In the case of LDAP, if an operation adds a new LDAP entry, the corresponding compensating transaction simply involves removing that entry.

Compensating transactions are useful for resources such as LDAP and web services that don’t provide any standard transactional support. However, it is important to remember that compensating transactions provide an illusion and can never replace real transactions. So, if a server crashes or the connection to the LDAP server is lost before the compensating transaction completes, you will end up with inconsistent data. Also, since the transaction is already committed, concurrent transactions might see invalid data. Compensating transactions can result in additional overhead as the client has to deal with extra undo operations.

To understand Spring LDAP transactions better, let’s create a Patron service with transactional behavior. Listing 9-8 shows the PatronService interface with just a create method.

Listing 9-8.

package com.inflinx.book.ldap.transactions;
 
import com.inflinx.book.ldap.domain.Patron;
 
public interface PatronService {
   public void create(Patron patron);
}

Listing 9-9 shows the implementation of this service interface. The create method implementation simply delegates the call to the DAO layer.

Listing 9-9.

package com.inflinx.book.ldap.transactions;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.inflinx.book.ldap.domain.Patron;
 
@Service("patronService")
@Transactional
public class PatronServiceImpl implements PatronService {
 
   @Autowired
   @Qualifier("patronDao")
   private PatronDao patronDao;
 
   @Override
   public void create(Patron patron) {
      patronDao.create(patron);
   }
}

Notice the usage of @Transactional annotation at the top of the class declaration. Listing 9-10 and Listing 9-11 show the PatronDao interface and its implementation PatronDaoImpl, respectively.

Listing 9-10.

package com.inflinx.book.ldap.transactions;
 
import com.inflinx.book.ldap.domain.Patron;
 
public interface PatronDao {
   public void create(Patron patron);
}

Listing 9-11.

package com.inflinx.book.ldap.transactions;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.stereotype.Repository;
import com.inflinx.book.ldap.domain.Patron;
 
@Repository("patronDao")
public class PatronDaoImpl implements PatronDao {
 
   private static final String PATRON_BASE = "ou=patrons,dc=inflinx,dc=com";
 
   @Autowired
   @Qualifier("ldapTemplate")
   private LdapTemplate ldapTemplate;
 
   @Override
   public void create(Patron patron) {
      System.out.println("Inside the create method ...");
      DistinguishedName dn = new DistinguishedName(PATRON_BASE);
      dn.add("uid", patron.getUserId());
      DirContextAdapter context = new DirContextAdapter(dn);
      context.setAttributeValues("objectClass", new String[]
              {"top", "uidObject", "person", "organizationalPerson", "inetOrgPerson"});
      context.setAttributeValue("sn", patron.getLastName());
      context.setAttributeValue("cn", patron.getCn());
      ldapTemplate.bind(context);
   }
}

As you can see from these two listings, you create Patron DAO and its implementation following the concepts discussed in Chapter 5. The next step is to create a Spring configuration file that will autowire the components and will include the transaction semantics. Listing 9-12 gives the contents of the configuration file. Here you are using the locally installed OpenDJ LDAP server.

Listing 9-12.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/
spring-context.xsd http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
 
   <context:component-scan base-package="com.inflinx.book.ldap" />
   <bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource">
      <property name="url" value="ldap://localhost:11389" />
      <property name="userDn" value="cn=Directory Manager" />
      <property name="password" value="opendj" />
      <property name="base" value=""/>
   </bean>
   <bean id="contextSource" class="org.springframework.ldap.transaction.compensating.manager. TransactionAwareContextSourceProxy">
      <constructor-arg ref="contextSourceTarget" />
   </bean>
   <bean id="ldapTemplate" class="org.springframework.ldap. core.LdapTemplate">
      <constructor-arg ref="contextSource" />
   </bean>
   <bean id="transactionManager" class="org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager">
      <property name="contextSource" ref="contextSource" />
   </bean>
      <tx:annotation-driven transaction-manager="transactionManager" />
 </beans>

In this configuration, you start by defining a new LdapContextSource and providing it with your LDAP information. Up to this point, you referred to this bean with the id contextSource and injected it for use by LdapTemplate. However, in this new configuration, you are calling it contextSourceTarget. You then configure an instance of TransactionAwareContextSourceProxy and inject the contextSource bean into it. This newly configured TransactionAwareContextSourceProxy bean has the id contextSource and is used by LdapTemplate. Finally, you configure the transaction manager using ContextSourceTransactionManager class. As discussed earlier, this configuration allows a single DirContext instance to be used during a single transaction, which in turn enables transaction commit/rollback.

With this information in place, let’s verify if your create method and configuration behaves correctly during a transaction rollback. In order to simulate a transaction rollback, let’s modify the create method in the PatronServiceImpl class to throw a RuntimeException, as shown:

@Override
public void create(Patron  patron)  {
    patronDao.create(patron);
    throw new  RuntimeException(); // Will roll  back the  transaction
}

The next step in verifying the expected behavior is to write a test case that calls PatronServiceImpl’s create method in order to create a new Patron. The test case is shown in Listing 9-13. The repositoryContext-test.xml file contains the XML configuration defined in Listing 9-12.

Listing 9-13.

package com.inflinx.book.ldap.transactions;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:repositoryContext-test.xml")
public class PatronServiceImplTest {
 
   @Autowired
   private PatronService patronService;
 
   @Test(expected=RuntimeException.class)
   public void testCreate() {
      Patron patron = new Patron();
      patron.setUserId("patron10001");
      patron.setLastName("Patron10001");
      patron.setCn("Test Patron10001");
      patronService.create(patron);
   }
}

When you run the test, Spring LDAP should create a new patron; then, upon rolling back the transaction, it would remove the newly created patron. The inner workings of Spring LDAP’s compensating transactions can be seen by looking at OpenDJ log file. The log file is named access and is located in the OPENDJ_INSTALLlogs folder.

Listing 9-14 shows a portion of the log file for this create operation. You will notice that when the create method on the PatronDaoImpl gets invoked, an “ADD REQ” command is sent to the OpenDJ server to add the new Patron entry. When Spring LDAP rolls back the transaction, a new “DELETE REQ” command is sent to remove the entry.

Listing 9-14.

[14/Sep/2013:15:03:09 -0600] CONNECT conn=52 from=127.0.0.1:54792 to=127.0.0.1:11389 protocol=LDAP
[14/Sep/2013:15:03:09 -0600] BIND REQ conn=52 op=0 msgID=1 type=SIMPLE dn="cn=Directory Manager"
[14/Sep/2013:15:03:09 -0600] BIND RES conn=52 op=0 msgID=1 result=0 authDN="cn=Directory Manager,cn=Root DNs,cn=config" etime=0
[14/Sep/2013:15:03:09 -0600] ADD REQconn=52 op=1 msgID=2 dn="uid=patron10001,ou=patrons,dc=inflinx,dc=com"
[14/Sep/2013:15:03:09 -0600] ADD RES conn=52 op=1 msgID=2 result=0 etime=2
[14/Sep/2013:15:03:09 -0600] DELETE REQconn=52 op=2 msgID=3 dn="uid=patron10001,ou=patrons,dc=inflinx,dc=com"
[14/Sep/2013:15:03:09 -0600] DELETE RES conn=52 op=2 msgID=3 result=0 etime=4
[14/Sep/2013:15:03:09 -0600] UNBIND REQ conn=52 op=3 msgID=4
[14/Sep/2013:15:03:09 -0600] DISCONNECT conn=52 reason="Client Unbind""

This test verified that Spring LDAP’s compensating transaction infrastructure would automatically remove the newly added entry if the transaction were to roll back for any reason.

Now let’s continue implementing the PatronServiceImpl methods and verify their transactional behaviors. Listing 9-15 and Listing 9-16 show the delete method added to the PatronService interface and PatronServiceImpl class, respectively. Again, the actual delete method implementation is straightforward and simply involves calling the PatronDaoImpl’s delete method.

Listing 9-15.

public interface PatronDao {
   public void create(Patron patron);
   public void delete(String id) ;
}

Listing 9-16.

// Import and annotations remvoed for brevity
public class PatronServiceImpl implements PatronService {
 
   // Create method removed for brevity
   @Override
   public void delete(String id) {
      patronDao.delete(id);
   }
}

Listing 9-17 shows the PatronDaoImpl’s delete method implementation.

Listing 9-17.

// Annotation and imports removed for brevity
public class PatronDaoImpl implements PatronDao {
 
   // Removed other methods for brevity
   @Override
   public void delete(String id) {
      DistinguishedName dn = new DistinguishedName(PATRON_BASE);
      dn.add("uid", id);
      ldapTemplate.unbind(dn);
   }
}

With this code in hand, let’s write a test case that invokes your delete method in a transaction. Listing 9-18 shows the test case. The “uid=patron98” is an existing entry in your OpenDJ server and was created during the LDIF import in Chapter 3.

Listing 9-18.

@Test
public void testDeletePatron() {
   patronService.delete("uid=patron98");
}

When you run this test case and invoke the PatronServiceImpl’s delete method in a transaction, Spring LDAP’s transaction infrastructure simply renames the entry under a newly calculated temporary DN. Essentially, with a rename, Spring LDAP is moving your entry to a different location on the LDAP server. Upon a successful commit, the temporary entry is removed. On a rollback, the entry is renamed and thus will be moved from the temporary location to its original location.

Now, run the method and watch the access log under OpenDJ. Listing 9-19 shows the portion for the log file for the delete operation. Notice that the delete operation results in a “MODIFYDN REQ” command that renames the entry to be deleted. Upon a successful commit, the renamed entry is removed via “DELETE REQ” command.

Listing 9-19.

[[14/Sep/2013:16:21:56 -0600] CONNECT conn=54 from=127.0.0.1:54824 to=127.0.0.1:11389 protocol=LDAP
[14/Sep/2013:16:21:56 -0600] BIND REQ conn=54 op=0 msgID=1 type=SIMPLE dn="cn=Directory Manager"
[14/Sep/2013:16:21:56 -0600] BIND RES conn=54 op=0 msgID=1 result=0 authDN="cn=Directory Manager,cn=Root DNs,cn=config" etime=1
[14/Sep/2013:16:21:56 -0600] MODIFYDN REQconn=54 op=1 msgID=2 dn="uid=patron97,ou=patrons,dc=inflinx,dc=com" newRDN="uid=patron97_temp" deleteOldRDN=true newSuperior="ou=patrons,dc=inflinx,dc=com
[14/Sep/2013:16:21:56 -0600] MODIFYDN RES conn=54 op=1 msgID=2 result=0 etime=4
[14/Sep/2013:16:21:56 -0600] DELETE REQconn=54 op=2 msgID=3 dn="uid=patron97_temp,ou=patrons,dc=inflinx,dc=com"
[14/Sep/2013:16:21:56 -0600] DELETE RES conn=54 op=2 msgID=3 result=0 etime=2
[14/Sep/2013:16:21:56 -0600] UNBIND REQ conn=54 op=3 msgID=4
[14/Sep/2013:16:21:56 -0600] DISCONNECT conn=54 reason="Client Unbind"

Now, let’s simulate a rollback for the delete method in the PatronServiceImpl class, as shown in Listing 9-20.

Listing 9-20.

public void delete(String id) {
   patronDao.delete(id);
   throw new RuntimeException(); // Need this to simulate a rollback
}

Now, let’s update the test case with a new Patron Id that you know still exists in the OpenDJ server, as shown in Listing 9-21.

Listing 9-21.

@Test(expected=RuntimeException.class)
public void testDeletePatron() {
   patronService.delete("uid=patron96");
}

When this code is run, the expected behavior is that Spring LDAP will rename the patron96 entry by changing its DN and then upon rollback will rename it again to the right DN. Listing 9-22 shows the OpenDJ’s access log for the above operation. Note that the delete operation first results in renaming of the entry by sending the first MODIFYDN REQ. Upon a rollback, a second “MODIFYDN REQ” is sent to rename the entry back to original location.

Listing 9-22.

[14/Sep/2013:16:33:43 -0600] CONNECT conn=55 from=127.0.0.1:54829 to=127.0.0.1:11389 protocol=LDAP
[14/Sep/2013:16:33:43 -0600] BIND REQ conn=55 op=0 msgID=1 type=SIMPLE dn="cn=Directory Manager"
[14/Sep/2013:16:33:43 -0600] BIND RES conn=55 op=0 msgID=1 result=0 authDN="cn=Directory Manager,cn=Root DNs,cn=config" etime=0
[14/Sep/2013:16:33:43 -0600] MODIFYDN REQ conn=55 op=1 msgID=2 dn="uid=patron96,ou=patrons,dc=inflinx,dc=com" newRDN="uid=patron96_temp" deleteOldRDN=true newSuperior="ou=patrons,dc=inflinx,dc=com
[14/Sep/2013:16:33:43 -0600] MODIFYDN RES conn=55 op=1 msgID=2 result=0 etime=1
[14/Sep/2013:16:33:43 -0600] MODIFYDN REQ conn=55 op=2 msgID=3 dn="uid=patron96_temp,ou=patrons,dc=inflinx,dc=com" newRDN="uid=patron96" deleteOldRDN=true newSuperior="ou=patrons,dc=inflinx,dc=com
[14/Sep/2013:16:33:43 -0600] MODIFYDN RES conn=55 op=2 msgID=3 result=0 etime=0
[14/Sep/2013:16:33:43 -0600] UNBIND REQ conn=55 op=3 msgID=4
[14/Sep/2013:16:33:43 -0600] DISCONNECT conn=55 reason="Client Unbind"

For an update operation, as you can guess by now, the Spring LDAP infrastructure calculates compensating ModificationItem list for the modifications that are made on the entry. On a commit, nothing needs to be done. But upon a rollback, the computed compensating ModificationItem list will be written back.

Summary

In this chapter, you explored the basics of transactions and looked at Spring LDAP’s transaction support. Spring LDAP keeps a record of the state in the LDAP tree before performing an operation. If a rollback were to happen, Spring LDAP performs compensating operations to restore the previous state. Keep in mind that this compensating transaction support gives an illusion of atomicity but doesn’t guarantee it.

In the next chapter, you will explore other Spring LDAP features such as connection pooling and LDIF parsing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset