/*
* JBoss, Home of Professional Open Source
* Copyright 2013, Red Hat, Inc. and/or its affiliates, and individual
* contributors by the @authors tag. See the copyright.txt in the
* distribution for a full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.as.quickstarts.xa;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.TextMessage;
import javax.jms.XAConnection;
import javax.jms.XAConnectionFactory;
import javax.jms.XASession;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
/**
* A bean for updating a database and sending a JMS message within a single JTA transaction
*
* @author Mike Musgrove
*/
public class XAService {
private final static Logger LOGGER = Logger.getLogger(XAService.class.getName());
/*
* Ask the container to inject a persistence context corresponding to the database that will hold our key/value pair table.
* The unit names corresponds to the one defined in the war archives' persistence.xml file
*/
@PersistenceContext
private EntityManager em;
/*
* Inject a UserTransaction for manual transaction demarcation (we could have used an EJB and asked the container to manager
* transactions but we'll manage them ourselves so it's clear what's going on.
*/
@Inject
private UserTransaction userTransaction;
@Resource(mappedName = "java:/JmsXA")
private XAConnectionFactory xaConnectionFactory; // we want to deliver JMS messages withing an XA transaction
// use our JMS queue. Note that messages must be persistent in order for them to survive an AS restart
@Resource(mappedName = "java:/queue/jta-crash-rec-quickstart")
private Queue queue;
private void notifyUpdate(Queue queue, String msg) throws Exception {
XAConnection connection = null;
try {
connection = xaConnectionFactory.createXAConnection();
XASession session = connection.createXASession();
MessageProducer messageProducer = session.createProducer(queue);
connection.start();
TextMessage message = session.createTextMessage();
message.setText(msg);
messageProducer.send(message);
messageProducer.close();
} finally {
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
LOGGER.info("Error closing JMS connection: " + e.getMessage());
}
}
}
}
// must be called inside a transaction
private String listPairs() {
StringBuilder result = new StringBuilder();
// list all key value pairs
@SuppressWarnings("unchecked")
final List<KVPair> list = em.createQuery("select k from KVPair k").getResultList();
result.append("<table><caption>Database Table Contents</caption><tr><th>Key</th><th>Value</th></tr>");
for (KVPair kvPair : list) {
result.append("<tr><td>");
result.append(kvPair.getKey());
result.append("</td><td>");
result.append(kvPair.getValue());
result.append("</td></tr>");
}
result.append("</table>");
return result.toString();
}
/**
* Update a key value database. The method must be called within a transaction.
*
* @param entityManager an open JPA entity manager
* @param delete if true then delete rows. If key is empty all rows are deleted.
* @param key if not null then a pair is inserted into the database
* @param value the value to be associated with the key
*
* @return true if a key was inserted or a value modified
*/
public boolean modifyKeyValueTable(EntityManager entityManager, boolean delete, String key, String value) {
boolean keyIsValid = (key != null && key.length() != 0);
if (delete) {
if (keyIsValid) {
KVPair pair = entityManager.find(KVPair.class, key);
// delete the requested key
if (pair != null)
entityManager.remove(pair);
} else {
// delete all entities
Query query = entityManager.createQuery("DELETE FROM KVPair k");
query.executeUpdate();
}
} else if (keyIsValid) {
KVPair pair = entityManager.find(KVPair.class, key);
if (pair == null) {
// insert a new entry into the the key/value table
entityManager.persist(new KVPair(key, value));
} else {
// there is already a value for this key - update it with the new value
pair.setValue(value);
entityManager.merge(pair);
}
return true;
}
return false;
}
public void modifyKeyValueTable(boolean delete, String key, String value) {
modifyKeyValueTable(em, delete, key, value);
}
/**
* Update a key value database. The method must not be called within a transaction.
*
* @param delete if true then delete rows. If key is empty all rows are deleted.
* @param key if not null then a pair is inserted into the database
* @param value the value to be associated with the key
*
* @return The contents of the table after the update
*/
public String updateKeyValueDatabase(boolean delete, String key, String value) {
StringBuilder result = new StringBuilder();
try {
userTransaction.begin();
// a row was inserted or modified notify the message consumer
if (modifyKeyValueTable(em, delete, key, value))
notifyUpdate(queue, key + "=" + value);
userTransaction.commit();
userTransaction.begin();
result.append(listPairs());
userTransaction.commit();
} catch (Exception e) {
result.append(e.getCause() != null ? e.getCause().getMessage() : e.getMessage());
} finally {
try {
if (userTransaction.getStatus() == Status.STATUS_ACTIVE
|| userTransaction.getStatus() == Status.STATUS_MARKED_ROLLBACK)
userTransaction.rollback();
} catch (Throwable e) {
result.append(" Transaction did not finish: ").append(e.getMessage());
}
}
return result.toString();
}
}