View Javadoc
1   /*
2    * JBoss, Home of Professional Open Source
3    * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
4    * contributors by the @authors tag. See the copyright.txt in the
5    * distribution for a full listing of individual contributors.
6    *
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * http://www.apache.org/licenses/LICENSE-2.0
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.jboss.as.quickstarts.xa;
18  
19  import java.util.List;
20  import java.util.logging.Logger;
21  
22  import javax.annotation.Resource;
23  import javax.inject.Inject;
24  import javax.jms.JMSException;
25  import javax.jms.MessageProducer;
26  import javax.jms.Queue;
27  import javax.jms.TextMessage;
28  import javax.jms.XAConnection;
29  import javax.jms.XAConnectionFactory;
30  import javax.jms.XASession;
31  import javax.persistence.EntityManager;
32  import javax.persistence.PersistenceContext;
33  import javax.persistence.Query;
34  import javax.transaction.Status;
35  import javax.transaction.UserTransaction;
36  
37  /**
38   * A bean for updating a database and sending a JMS message within a single JTA transaction
39   * 
40   * @author Mike Musgrove
41   */
42  public class XAService {
43  
44      private final static Logger LOGGER = Logger.getLogger(XAService.class.getName());
45  
46      /*
47       * Ask the container to inject a persistence context corresponding to the database that will hold our key/value pair table.
48       * The unit names corresponds to the one defined in the war archives' persistence.xml file
49       */
50      @PersistenceContext
51      private EntityManager em;
52  
53      /*
54       * Inject a UserTransaction for manual transaction demarcation (we could have used an EJB and asked the container to manager
55       * transactions but we'll manage them ourselves so it's clear what's going on.
56       */
57      @Inject
58      private UserTransaction userTransaction;
59  
60      @Resource(mappedName = "java:/JmsXA")
61      private XAConnectionFactory xaConnectionFactory; // we want to deliver JMS messages withing an XA transaction
62  
63      // use our JMS queue. Note that messages must be persistent in order for them to survive an AS restart
64      @Resource(mappedName = "java:/queue/jta-crash-rec-quickstart")
65      private Queue queue;
66  
67      private void notifyUpdate(Queue queue, String msg) throws Exception {
68          XAConnection connection = null;
69  
70          try {
71              connection = xaConnectionFactory.createXAConnection();
72              XASession session = connection.createXASession();
73              MessageProducer messageProducer = session.createProducer(queue);
74  
75              connection.start();
76              TextMessage message = session.createTextMessage();
77              message.setText(msg);
78  
79              messageProducer.send(message);
80              messageProducer.close();
81          } finally {
82              if (connection != null) {
83                  try {
84                      connection.close();
85                  } catch (JMSException e) {
86                      LOGGER.info("Error closing JMS connection: " + e.getMessage());
87                  }
88              }
89          }
90      }
91  
92      // must be called inside a transaction
93      private String listPairs() {
94          StringBuilder result = new StringBuilder();
95  
96          // list all key value pairs
97          @SuppressWarnings("unchecked")
98          final List<KVPair> list = em.createQuery("select k from KVPair k").getResultList();
99  
100         result.append("<table><caption>Database Table Contents</caption><tr><th>Key</th><th>Value</th></tr>");
101 
102         for (KVPair kvPair : list) {
103             result.append("<tr><td>");
104             result.append(kvPair.getKey());
105             result.append("</td><td>");
106             result.append(kvPair.getValue());
107             result.append("</td></tr>");
108         }
109         result.append("</table>");
110 
111         return result.toString();
112     }
113 
114     /**
115      * Update a key value database. The method must be called within a transaction.
116      * 
117      * @param entityManager an open JPA entity manager
118      * @param delete if true then delete rows. If key is empty all rows are deleted.
119      * @param key if not null then a pair is inserted into the database
120      * @param value the value to be associated with the key
121      * 
122      * @return true if a key was inserted or a value modified
123      */
124     public boolean modifyKeyValueTable(EntityManager entityManager, boolean delete, String key, String value) {
125         boolean keyIsValid = (key != null && key.length() != 0);
126 
127         if (delete) {
128             if (keyIsValid) {
129                 KVPair pair = entityManager.find(KVPair.class, key);
130 
131                 // delete the requested key
132                 if (pair != null)
133                     entityManager.remove(pair);
134             } else {
135                 // delete all entities
136                 Query query = entityManager.createQuery("DELETE FROM KVPair k");
137 
138                 query.executeUpdate();
139             }
140         } else if (keyIsValid) {
141             KVPair pair = entityManager.find(KVPair.class, key);
142 
143             if (pair == null) {
144                 // insert a new entry into the the key/value table
145                 entityManager.persist(new KVPair(key, value));
146             } else {
147                 // there is already a value for this key - update it with the new value
148                 pair.setValue(value);
149                 entityManager.merge(pair);
150             }
151 
152             return true;
153         }
154 
155         return false;
156     }
157 
158     public void modifyKeyValueTable(boolean delete, String key, String value) {
159         modifyKeyValueTable(em, delete, key, value);
160     }
161 
162     /**
163      * Update a key value database. The method must not be called within a transaction.
164      * 
165      * @param delete if true then delete rows. If key is empty all rows are deleted.
166      * @param key if not null then a pair is inserted into the database
167      * @param value the value to be associated with the key
168      * 
169      * @return The contents of the table after the update
170      */
171     public String updateKeyValueDatabase(boolean delete, String key, String value) {
172         StringBuilder result = new StringBuilder();
173 
174         try {
175             userTransaction.begin();
176 
177             // a row was inserted or modified notify the message consumer
178             if (modifyKeyValueTable(em, delete, key, value))
179                 notifyUpdate(queue, key + "=" + value);
180 
181             userTransaction.commit();
182 
183             userTransaction.begin();
184 
185             result.append(listPairs());
186 
187             userTransaction.commit();
188         } catch (Exception e) {
189             result.append(e.getCause() != null ? e.getCause().getMessage() : e.getMessage());
190         } finally {
191             try {
192                 if (userTransaction.getStatus() == Status.STATUS_ACTIVE
193                         || userTransaction.getStatus() == Status.STATUS_MARKED_ROLLBACK)
194                     userTransaction.rollback();
195             } catch (Throwable e) {
196                 result.append(" Transaction did not finish: ").append(e.getMessage());
197             }
198         }
199         return result.toString();
200     }
201 }