/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES 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.apache.cxf.ws.rm.persistence.jdbc;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.sql.DataSource;
import org.apache.cxf.common.i18n.Message;
import org.apache.cxf.common.injection.NoJSR250Annotations;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.SystemPropertyAction;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.apache.cxf.ws.rm.DestinationSequence;
import org.apache.cxf.ws.rm.ProtocolVariation;
import org.apache.cxf.ws.rm.RMUtils;
import org.apache.cxf.ws.rm.SourceSequence;
import org.apache.cxf.ws.rm.persistence.PersistenceUtils;
import org.apache.cxf.ws.rm.persistence.RMMessage;
import org.apache.cxf.ws.rm.persistence.RMStore;
import org.apache.cxf.ws.rm.persistence.RMStoreException;
import org.apache.cxf.ws.rm.v200702.Identifier;
import org.apache.cxf.ws.rm.v200702.SequenceAcknowledgement;
@NoJSR250Annotations
public class RMTxStore implements RMStore {
public static final String DEFAULT_DATABASE_NAME = "rmdb";
private static final String[][] DEST_SEQUENCES_TABLE_COLS
= {{"SEQ_ID", "VARCHAR(256) NOT NULL"},
{"ACKS_TO", "VARCHAR(1024) NOT NULL"},
{"LAST_MSG_NO", "DECIMAL(19, 0)"},
{"ENDPOINT_ID", "VARCHAR(1024)"},
{"ACKNOWLEDGED", "BLOB"},
{"TERMINATED", "CHAR(1)"},
{"PROTOCOL_VERSION", "VARCHAR(256)"}};
private static final String[] DEST_SEQUENCES_TABLE_KEYS = {"SEQ_ID"};
private static final String[][] SRC_SEQUENCES_TABLE_COLS
= {{"SEQ_ID", "VARCHAR(256) NOT NULL"},
{"CUR_MSG_NO", "DECIMAL(19, 0) DEFAULT 1 NOT NULL"},
{"LAST_MSG", "CHAR(1)"},
{"EXPIRY", "DECIMAL(19, 0)"},
{"OFFERING_SEQ_ID", "VARCHAR(256)"},
{"ENDPOINT_ID", "VARCHAR(1024)"},
{"PROTOCOL_VERSION", "VARCHAR(256)"}};
private static final String[] SRC_SEQUENCES_TABLE_KEYS = {"SEQ_ID"};
private static final String[][] MESSAGES_TABLE_COLS
= {{"SEQ_ID", "VARCHAR(256) NOT NULL"},
{"MSG_NO", "DECIMAL(19, 0) NOT NULL"},
{"SEND_TO", "VARCHAR(256)"},
{"CREATED_TIME", "DECIMAL(19, 0)"},
{"CONTENT", "BLOB"},
{"CONTENT_TYPE", "VARCHAR(1024)"}};
private static final String[] MESSAGES_TABLE_KEYS = {"SEQ_ID", "MSG_NO"};
private static final String DEST_SEQUENCES_TABLE_NAME = "CXF_RM_DEST_SEQUENCES";
private static final String SRC_SEQUENCES_TABLE_NAME = "CXF_RM_SRC_SEQUENCES";
private static final String INBOUND_MSGS_TABLE_NAME = "CXF_RM_INBOUND_MESSAGES";
private static final String OUTBOUND_MSGS_TABLE_NAME = "CXF_RM_OUTBOUND_MESSAGES";
private static final String CREATE_DEST_SEQUENCES_TABLE_STMT =
buildCreateTableStatement(DEST_SEQUENCES_TABLE_NAME,
DEST_SEQUENCES_TABLE_COLS, DEST_SEQUENCES_TABLE_KEYS);
private static final String CREATE_SRC_SEQUENCES_TABLE_STMT =
buildCreateTableStatement(SRC_SEQUENCES_TABLE_NAME,
SRC_SEQUENCES_TABLE_COLS, SRC_SEQUENCES_TABLE_KEYS);
private static final String CREATE_MESSAGES_TABLE_STMT =
buildCreateTableStatement("{0}", MESSAGES_TABLE_COLS, MESSAGES_TABLE_KEYS);
private static final String CREATE_DEST_SEQUENCE_STMT_STR
= "INSERT INTO CXF_RM_DEST_SEQUENCES "
+ "(SEQ_ID, ACKS_TO, ENDPOINT_ID, PROTOCOL_VERSION) "
+ "VALUES(?, ?, ?, ?)";
private static final String CREATE_SRC_SEQUENCE_STMT_STR
= "INSERT INTO CXF_RM_SRC_SEQUENCES "
+ "(SEQ_ID, CUR_MSG_NO, LAST_MSG, EXPIRY, OFFERING_SEQ_ID, ENDPOINT_ID, PROTOCOL_VERSION) "
+ "VALUES(?, 1, '0', ?, ?, ?, ?)";
private static final String DELETE_DEST_SEQUENCE_STMT_STR =
"DELETE FROM CXF_RM_DEST_SEQUENCES WHERE SEQ_ID = ?";
private static final String DELETE_SRC_SEQUENCE_STMT_STR =
"DELETE FROM CXF_RM_SRC_SEQUENCES WHERE SEQ_ID = ?";
private static final String UPDATE_DEST_SEQUENCE_STMT_STR =
"UPDATE CXF_RM_DEST_SEQUENCES SET LAST_MSG_NO = ?, TERMINATED = ?, ACKNOWLEDGED = ? WHERE SEQ_ID = ?";
private static final String UPDATE_SRC_SEQUENCE_STMT_STR =
"UPDATE CXF_RM_SRC_SEQUENCES SET CUR_MSG_NO = ?, LAST_MSG = ? WHERE SEQ_ID = ?";
private static final String CREATE_MESSAGE_STMT_STR
= "INSERT INTO {0} (SEQ_ID, MSG_NO, SEND_TO, CREATED_TIME, CONTENT, CONTENT_TYPE) VALUES(?, ?, ?, ?, ?, ?)";
private static final String DELETE_MESSAGE_STMT_STR =
"DELETE FROM {0} WHERE SEQ_ID = ? AND MSG_NO = ?";
private static final String SELECT_DEST_SEQUENCE_STMT_STR =
"SELECT ACKS_TO, LAST_MSG_NO, PROTOCOL_VERSION, TERMINATED, ACKNOWLEDGED FROM CXF_RM_DEST_SEQUENCES "
+ "WHERE SEQ_ID = ?";
private static final String SELECT_SRC_SEQUENCE_STMT_STR =
"SELECT CUR_MSG_NO, LAST_MSG, EXPIRY, OFFERING_SEQ_ID, PROTOCOL_VERSION FROM CXF_RM_SRC_SEQUENCES "
+ "WHERE SEQ_ID = ?";
private static final String SELECT_DEST_SEQUENCES_STMT_STR =
"SELECT SEQ_ID, ACKS_TO, LAST_MSG_NO, PROTOCOL_VERSION, TERMINATED, ACKNOWLEDGED FROM CXF_RM_DEST_SEQUENCES "
+ "WHERE ENDPOINT_ID = ?";
private static final String SELECT_SRC_SEQUENCES_STMT_STR =
"SELECT SEQ_ID, CUR_MSG_NO, LAST_MSG, EXPIRY, OFFERING_SEQ_ID, PROTOCOL_VERSION "
+ "FROM CXF_RM_SRC_SEQUENCES WHERE ENDPOINT_ID = ?";
private static final String SELECT_MESSAGES_STMT_STR =
"SELECT MSG_NO, SEND_TO, CREATED_TIME, CONTENT, CONTENT_TYPE FROM {0} WHERE SEQ_ID = ?";
private static final String ALTER_TABLE_STMT_STR =
"ALTER TABLE {0} ADD {1} {2}";
private static final String CREATE_INBOUND_MESSAGE_STMT_STR =
MessageFormat.format(CREATE_MESSAGE_STMT_STR, INBOUND_MSGS_TABLE_NAME);
private static final String CREATE_OUTBOUND_MESSAGE_STMT_STR =
MessageFormat.format(CREATE_MESSAGE_STMT_STR, OUTBOUND_MSGS_TABLE_NAME);
private static final String DELETE_INBOUND_MESSAGE_STMT_STR =
MessageFormat.format(DELETE_MESSAGE_STMT_STR, INBOUND_MSGS_TABLE_NAME);
private static final String DELETE_OUTBOUND_MESSAGE_STMT_STR =
MessageFormat.format(DELETE_MESSAGE_STMT_STR, OUTBOUND_MSGS_TABLE_NAME);
private static final String SELECT_INBOUND_MESSAGES_STMT_STR =
MessageFormat.format(SELECT_MESSAGES_STMT_STR, INBOUND_MSGS_TABLE_NAME);
private static final String SELECT_OUTBOUND_MESSAGES_STMT_STR =
MessageFormat.format(SELECT_MESSAGES_STMT_STR, OUTBOUND_MSGS_TABLE_NAME);
// create_schema may not work for several reasons, if so, create one manually
private static final String CREATE_SCHEMA_STMT_STR = "CREATE SCHEMA {0}";
// given the schema, try these standard statements to switch to the schema
private static final String[] SET_SCHEMA_STMT_STRS = {"SET SCHEMA {0}",
"SET CURRENT_SCHEMA = {0}",
"ALTER SESSION SET CURRENT_SCHEMA = {0}"};
private static final String DERBY_TABLE_EXISTS_STATE = "X0Y32";
private static final int ORACLE_TABLE_EXISTS_CODE = 955;
private static final Logger LOG = LogUtils.getL7dLogger(RMTxStore.class);
// the connection and statements are cached only if
private boolean keepConnection = true;
private Connection connection;
private boolean createdConnection = true;
private Map<Statement, Lock> statementLocks;
private Map<String, PreparedStatement> cachedStatements;
private DataSource dataSource;
private String driverClassName = "org.apache.derby.jdbc.EmbeddedDriver";
private String url = MessageFormat.format("jdbc:derby:{0};create=true", DEFAULT_DATABASE_NAME);
private String userName;
private String password;
private String schemaName;
private long initialReconnectDelay = 60000L;
private int useExponentialBackOff = 2;
private int maxReconnectAttempts = 10;
private long reconnectDelay;
private int reconnectAttempts;
private long nextReconnectAttempt;
private String tableExistsState = DERBY_TABLE_EXISTS_STATE;
private int tableExistsCode = ORACLE_TABLE_EXISTS_CODE;
public RMTxStore() {
}
public void destroy() {
if (connection != null && createdConnection) {
try {
connection.close();
} catch (SQLException e) {
//ignore
}
connection = null;
}
}
// configuration
public void setDriverClassName(String dcn) {
driverClassName = dcn;
}
public String getDriverClassName() {
return driverClassName;
}
public void setPassword(String p) {
password = p;
}
public String getPassword() {
return password;
}
public void setUrl(String u) {
url = u;
}
public String getUrl() {
return url;
}
public void setUserName(String un) {
userName = un;
}
public String getUserName() {
return userName;
}
public String getSchemaName() {
return schemaName;
}
public void setSchemaName(String sn) {
if (sn == null || Pattern.matches("[a-zA-Z\\d]{1,32}", sn)) {
schemaName = sn;
} else {
throw new IllegalArgumentException("Invalid schema name: " + sn);
}
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource ds) {
dataSource = ds;
}
public String getTableExistsState() {
return tableExistsState;
}
public void setTableExistsState(String tableExistsState) {
this.tableExistsState = tableExistsState;
}
public int getTableExistsCode() {
return tableExistsCode;
}
public void setTableExistsCode(int tableExistsCode) {
this.tableExistsCode = tableExistsCode;
}
public boolean isKeepConnection() {
return keepConnection;
}
public void setKeepConnection(boolean keepConnection) {
this.keepConnection = keepConnection;
}
public long getInitialReconnectDelay() {
return initialReconnectDelay;
}
public void setInitialReconnectDelay(long initialReconnectDelay) {
this.initialReconnectDelay = initialReconnectDelay;
}
public int getMaxReconnectAttempts() {
return maxReconnectAttempts;
}
public void setMaxReconnectAttempts(int maxReconnectAttempts) {
this.maxReconnectAttempts = maxReconnectAttempts;
}
public void setConnection(Connection c) {
connection = c;
createdConnection = false;
}
// RMStore interface
public void createDestinationSequence(DestinationSequence seq) {
String sequenceIdentifier = seq.getIdentifier().getValue();
String endpointIdentifier = seq.getEndpointIdentifier();
String protocolVersion = encodeProtocolVersion(seq.getProtocol());
if (LOG.isLoggable(Level.FINE)) {
LOG.info("Creating destination sequence: " + sequenceIdentifier + ", (endpoint: "
+ endpointIdentifier + ")");
}
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
try {
beginTransaction();
stmt = getStatement(con, CREATE_DEST_SEQUENCE_STMT_STR);
stmt.setString(1, sequenceIdentifier);
String addr = seq.getAcksTo().getAddress().getValue();
stmt.setString(2, addr);
stmt.setString(3, endpointIdentifier);
stmt.setString(4, protocolVersion);
stmt.execute();
commit(con);
} catch (SQLException ex) {
abort(con);
conex = ex;
throw new RMStoreException(ex);
} finally {
releaseResources(stmt, null);
updateConnectionState(con, conex);
}
}
public void createSourceSequence(SourceSequence seq) {
String sequenceIdentifier = seq.getIdentifier().getValue();
String endpointIdentifier = seq.getEndpointIdentifier();
String protocolVersion = encodeProtocolVersion(seq.getProtocol());
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Creating source sequence: " + sequenceIdentifier + ", (endpoint: "
+ endpointIdentifier + ")");
}
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
try {
beginTransaction();
stmt = getStatement(con, CREATE_SRC_SEQUENCE_STMT_STR);
stmt.setString(1, sequenceIdentifier);
Date expiry = seq.getExpires();
stmt.setLong(2, expiry == null ? 0 : expiry.getTime());
Identifier osid = seq.getOfferingSequenceIdentifier();
stmt.setString(3, osid == null ? null : osid.getValue());
stmt.setString(4, endpointIdentifier);
stmt.setString(5, protocolVersion);
stmt.execute();
commit(con);
} catch (SQLException ex) {
conex = ex;
abort(con);
throw new RMStoreException(ex);
} finally {
releaseResources(stmt, null);
updateConnectionState(con, conex);
}
}
public DestinationSequence getDestinationSequence(Identifier sid) {
if (LOG.isLoggable(Level.FINE)) {
LOG.info("Getting destination sequence for id: " + sid);
}
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
ResultSet res = null;
try {
stmt = getStatement(con, SELECT_DEST_SEQUENCE_STMT_STR);
stmt.setString(1, sid.getValue());
res = stmt.executeQuery();
if (res.next()) {
EndpointReferenceType acksTo = RMUtils.createReference(res.getString(1));
long lm = res.getLong(2);
ProtocolVariation pv = decodeProtocolVersion(res.getString(3));
boolean t = res.getBoolean(4);
InputStream is = res.getBinaryStream(5);
SequenceAcknowledgement ack = null;
if (null != is) {
ack = PersistenceUtils.getInstance()
.deserialiseAcknowledgment(is);
}
return new DestinationSequence(sid, acksTo, lm, t, ack, pv);
}
} catch (SQLException ex) {
conex = ex;
LOG.log(Level.WARNING, new Message("SELECT_DEST_SEQ_FAILED_MSG", LOG).toString(), ex);
} finally {
releaseResources(stmt, res);
updateConnectionState(con, conex);
}
return null;
}
public SourceSequence getSourceSequence(Identifier sid) {
if (LOG.isLoggable(Level.FINE)) {
LOG.info("Getting source sequences for id: " + sid);
}
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
ResultSet res = null;
try {
stmt = getStatement(con, SELECT_SRC_SEQUENCE_STMT_STR);
stmt.setString(1, sid.getValue());
res = stmt.executeQuery();
if (res.next()) {
long cmn = res.getLong(1);
boolean lm = res.getBoolean(2);
long lval = res.getLong(3);
Date expiry = 0 == lval ? null : new Date(lval);
String oidValue = res.getString(4);
Identifier oi = null;
if (null != oidValue) {
oi = RMUtils.getWSRMFactory().createIdentifier();
oi.setValue(oidValue);
}
ProtocolVariation pv = decodeProtocolVersion(res.getString(5));
return new SourceSequence(sid, expiry, oi, cmn, lm, pv);
}
} catch (SQLException ex) {
conex = ex;
// ignore
LOG.log(Level.WARNING, new Message("SELECT_SRC_SEQ_FAILED_MSG", LOG).toString(), ex);
} finally {
releaseResources(stmt, res);
updateConnectionState(con, conex);
}
return null;
}
public void removeDestinationSequence(Identifier sid) {
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
try {
beginTransaction();
stmt = getStatement(con, DELETE_DEST_SEQUENCE_STMT_STR);
stmt.setString(1, sid.getValue());
stmt.execute();
commit(con);
} catch (SQLException ex) {
conex = ex;
abort(con);
throw new RMStoreException(ex);
} finally {
releaseResources(stmt, null);
updateConnectionState(con, conex);
}
}
public void removeSourceSequence(Identifier sid) {
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
try {
beginTransaction();
stmt = getStatement(con, DELETE_SRC_SEQUENCE_STMT_STR);
stmt.setString(1, sid.getValue());
stmt.execute();
commit(con);
} catch (SQLException ex) {
conex = ex;
abort(con);
throw new RMStoreException(ex);
} finally {
releaseResources(stmt, null);
updateConnectionState(con, conex);
}
}
public Collection<DestinationSequence> getDestinationSequences(String endpointIdentifier) {
if (LOG.isLoggable(Level.FINE)) {
LOG.info("Getting destination sequences for endpoint: " + endpointIdentifier);
}
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
Collection<DestinationSequence> seqs = new ArrayList<>();
ResultSet res = null;
try {
stmt = getStatement(con, SELECT_DEST_SEQUENCES_STMT_STR);
stmt.setString(1, endpointIdentifier);
res = stmt.executeQuery();
while (res.next()) {
Identifier sid = new Identifier();
sid.setValue(res.getString(1));
EndpointReferenceType acksTo = RMUtils.createReference(res.getString(2));
long lm = res.getLong(3);
ProtocolVariation pv = decodeProtocolVersion(res.getString(4));
boolean t = res.getBoolean(5);
InputStream is = res.getBinaryStream(6);
SequenceAcknowledgement ack = null;
if (null != is) {
ack = PersistenceUtils.getInstance()
.deserialiseAcknowledgment(is);
}
DestinationSequence seq = new DestinationSequence(sid, acksTo, lm, t, ack, pv);
seqs.add(seq);
}
} catch (SQLException ex) {
conex = ex;
LOG.log(Level.WARNING, new Message("SELECT_DEST_SEQ_FAILED_MSG", LOG).toString(), ex);
} finally {
releaseResources(stmt, res);
updateConnectionState(con, conex);
}
return seqs;
}
public Collection<SourceSequence> getSourceSequences(String endpointIdentifier) {
if (LOG.isLoggable(Level.FINE)) {
LOG.info("Getting source sequences for endpoint: " + endpointIdentifier);
}
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
Collection<SourceSequence> seqs = new ArrayList<>();
ResultSet res = null;
try {
stmt = getStatement(con, SELECT_SRC_SEQUENCES_STMT_STR);
stmt.setString(1, endpointIdentifier);
res = stmt.executeQuery();
while (res.next()) {
Identifier sid = new Identifier();
sid.setValue(res.getString(1));
long cmn = res.getLong(2);
boolean lm = res.getBoolean(3);
long lval = res.getLong(4);
Date expiry = 0 == lval ? null : new Date(lval);
String oidValue = res.getString(5);
Identifier oi = null;
if (null != oidValue) {
oi = new Identifier();
oi.setValue(oidValue);
}
ProtocolVariation pv = decodeProtocolVersion(res.getString(6));
SourceSequence seq = new SourceSequence(sid, expiry, oi, cmn, lm, pv);
seqs.add(seq);
}
} catch (SQLException ex) {
conex = ex;
// ignore
LOG.log(Level.WARNING, new Message("SELECT_SRC_SEQ_FAILED_MSG", LOG).toString(), ex);
} finally {
releaseResources(stmt, res);
updateConnectionState(con, conex);
}
return seqs;
}
public Collection<RMMessage> getMessages(Identifier sid, boolean outbound) {
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
Collection<RMMessage> msgs = new ArrayList<>();
ResultSet res = null;
try {
stmt = getStatement(con, outbound ? SELECT_OUTBOUND_MESSAGES_STMT_STR : SELECT_INBOUND_MESSAGES_STMT_STR);
stmt.setString(1, sid.getValue());
res = stmt.executeQuery();
while (res.next()) {
long mn = res.getLong(1);
String to = res.getString(2);
long ct = res.getLong(3);
Blob blob = res.getBlob(4);
String contentType = res.getString(5);
RMMessage msg = new RMMessage();
msg.setMessageNumber(mn);
msg.setTo(to);
msg.setCreatedTime(ct);
CachedOutputStream cos = new CachedOutputStream();
IOUtils.copyAndCloseInput(blob.getBinaryStream(), cos);
cos.flush();
msg.setContent(cos);
msg.setContentType(contentType);
msgs.add(msg);
}
} catch (SQLException ex) {
conex = ex;
LOG.log(Level.WARNING, new Message(outbound ? "SELECT_OUTBOUND_MSGS_FAILED_MSG"
: "SELECT_INBOUND_MSGS_FAILED_MSG", LOG).toString(), ex);
} catch (IOException e) {
abort(con);
throw new RMStoreException(e);
} finally {
releaseResources(stmt, res);
updateConnectionState(con, conex);
}
return msgs;
}
public void persistIncoming(DestinationSequence seq, RMMessage msg) {
Connection con = verifyConnection();
SQLException conex = null;
try {
beginTransaction();
updateDestinationSequence(con, seq);
if (msg != null && msg.getContent() != null) {
storeMessage(con, seq.getIdentifier(), msg, false);
}
commit(con);
} catch (SQLException ex) {
conex = ex;
abort(con);
throw new RMStoreException(ex);
} catch (IOException ex) {
abort(con);
throw new RMStoreException(ex);
} finally {
updateConnectionState(con, conex);
}
}
public void persistOutgoing(SourceSequence seq, RMMessage msg) {
Connection con = verifyConnection();
SQLException conex = null;
try {
beginTransaction();
updateSourceSequence(con, seq);
if (msg != null && msg.getContent() != null) {
storeMessage(con, seq.getIdentifier(), msg, true);
}
commit(con);
} catch (SQLException ex) {
conex = ex;
abort(con);
throw new RMStoreException(ex);
} catch (IOException ex) {
abort(con);
throw new RMStoreException(ex);
} finally {
updateConnectionState(con, conex);
}
}
public void removeMessages(Identifier sid, Collection<Long> messageNrs, boolean outbound) {
Connection con = verifyConnection();
PreparedStatement stmt = null;
SQLException conex = null;
try {
stmt = getStatement(con, outbound ? DELETE_OUTBOUND_MESSAGE_STMT_STR : DELETE_INBOUND_MESSAGE_STMT_STR);
beginTransaction();
stmt.setString(1, sid.getValue());
for (Long messageNr : messageNrs) {
stmt.setLong(2, messageNr);
stmt.execute();
}
commit(con);
} catch (SQLException ex) {
conex = ex;
abort(con);
throw new RMStoreException(ex);
} finally {
releaseResources(stmt, null);
updateConnectionState(con, conex);
}
}
// transaction demarcation
//
protected void beginTransaction() {
}
protected void commit(Connection con) throws SQLException {
con.commit();
}
/**
* This method assumes that the connection is held and reused.
* Otherwise, use commit(Connection con)
*/
protected void commit() throws SQLException {
commit(connection);
}
protected void abort(Connection con) {
try {
con.rollback();
} catch (SQLException ex) {
LogUtils.log(LOG, Level.SEVERE, "ABORT_FAILED_MSG", ex);
}
}
protected void abort() {
abort(connection);
}
// helpers
protected void storeMessage(Connection con, Identifier sid, RMMessage msg, boolean outbound)
throws IOException, SQLException {
String id = sid.getValue();
long nr = msg.getMessageNumber();
String to = msg.getTo();
String contentType = msg.getContentType();
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Storing {0} message number {1} for sequence {2}, to = {3}",
new Object[] {outbound ? "outbound" : "inbound", nr, id, to});
}
PreparedStatement stmt = null;
CachedOutputStream cos = msg.getContent();
InputStream msgin = null;
try {
msgin = cos.getInputStream();
stmt = getStatement(con, outbound ? CREATE_OUTBOUND_MESSAGE_STMT_STR : CREATE_INBOUND_MESSAGE_STMT_STR);
stmt.setString(1, id);
stmt.setLong(2, nr);
stmt.setString(3, to);
stmt.setLong(4, msg.getCreatedTime());
stmt.setBinaryStream(5, msgin);
stmt.setString(6, contentType);
stmt.execute();
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Successfully stored {0} message number {1} for sequence {2}",
new Object[] {outbound ? "outbound" : "inbound", nr, id});
}
} finally {
releaseResources(stmt, null);
if (null != msgin) {
msgin.close();
}
cos.close(); // needed to clean-up tmp file folder
}
}
/**
* this method is only useful when keepConnection is set to true
*/
protected void storeMessage(Identifier sid, RMMessage msg, boolean outbound)
throws IOException, SQLException {
storeMessage(connection, sid, msg, outbound);
}
protected void updateSourceSequence(Connection con, SourceSequence seq)
throws SQLException {
PreparedStatement stmt = null;
try {
stmt = getStatement(con, UPDATE_SRC_SEQUENCE_STMT_STR);
stmt.setLong(1, seq.getCurrentMessageNr());
stmt.setString(2, seq.isLastMessage() ? "1" : "0");
stmt.setString(3, seq.getIdentifier().getValue());
stmt.execute();
} finally {
releaseResources(stmt, null);
}
}
/**
* @throws SQLException
*/
protected void updateSourceSequence(SourceSequence seq) throws SQLException {
updateSourceSequence(connection, seq);
}
protected void updateDestinationSequence(Connection con, DestinationSequence seq)
throws SQLException, IOException {
PreparedStatement stmt = null;
try {
stmt = getStatement(con, UPDATE_DEST_SEQUENCE_STMT_STR);
long lastMessageNr = seq.getLastMessageNumber();
stmt.setLong(1, lastMessageNr);
stmt.setString(2, seq.isTerminated() ? "1" : "0");
InputStream is = PersistenceUtils.getInstance().serialiseAcknowledgment(seq.getAcknowledgment());
stmt.setBinaryStream(3, is, is.available());
stmt.setString(4, seq.getIdentifier().getValue());
stmt.execute();
} finally {
releaseResources(stmt, null);
}
}
/**
* @throws IOException
* @throws SQLException
*/
protected void updateDestinationSequence(DestinationSequence seq)
throws SQLException, IOException {
updateDestinationSequence(connection, seq);
}
protected void createTables() throws SQLException {
Connection con = verifyConnection();
Statement stmt = null;
try {
con.setAutoCommit(true);
stmt = con.createStatement();
try {
stmt.executeUpdate(CREATE_SRC_SEQUENCES_TABLE_STMT);
} catch (SQLException ex) {
if (!isTableExistsError(ex)) {
throw ex;
} else {
LOG.fine("Table CXF_RM_SRC_SEQUENCES already exists.");
verifyTable(con, SRC_SEQUENCES_TABLE_NAME, SRC_SEQUENCES_TABLE_COLS);
}
} finally {
stmt.close();
}
stmt = con.createStatement();
try {
stmt.executeUpdate(CREATE_DEST_SEQUENCES_TABLE_STMT);
} catch (SQLException ex) {
if (!isTableExistsError(ex)) {
throw ex;
} else {
LOG.fine("Table CXF_RM_DEST_SEQUENCES already exists.");
verifyTable(con, DEST_SEQUENCES_TABLE_NAME, DEST_SEQUENCES_TABLE_COLS);
}
} finally {
stmt.close();
}
for (String tableName : new String[] {OUTBOUND_MSGS_TABLE_NAME, INBOUND_MSGS_TABLE_NAME}) {
stmt = con.createStatement();
try {
stmt.executeUpdate(MessageFormat.format(CREATE_MESSAGES_TABLE_STMT, tableName));
} catch (SQLException ex) {
if (!isTableExistsError(ex)) {
throw ex;
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Table " + tableName + " already exists.");
}
verifyTable(con, tableName, MESSAGES_TABLE_COLS);
}
} finally {
stmt.close();
}
}
} finally {
con.setAutoCommit(false);
if (connection == null && con != null) {
con.close();
}
}
}
protected void verifyTable(Connection con, String tableName, String[][] tableCols) {
try {
DatabaseMetaData metadata = con.getMetaData();
ResultSet rs = metadata.getColumns(null, null, tableName, "%");
Set<String> dbCols = new HashSet<>();
List<String[]> newCols = new ArrayList<String[]>();
while (rs.next()) {
dbCols.add(rs.getString(4));
}
for (String[] col : tableCols) {
if (!dbCols.contains(col[0])) {
newCols.add(col);
}
}
if (!newCols.isEmpty()) {
// need to add the new columns
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Table " + tableName + " needs additional columns");
}
for (String[] newCol : newCols) {
Statement st = con.createStatement();
try {
st.executeUpdate(MessageFormat.format(ALTER_TABLE_STMT_STR,
tableName, newCol[0], newCol[1]));
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Successfully added column {0} to table {1}",
new Object[] {tableName, newCol[0]});
}
} finally {
st.close();
}
}
}
} catch (SQLException ex) {
LOG.log(Level.WARNING, "Table " + tableName + " cannot be altered.", ex);
}
}
protected void verifyTable(String tableName, String[][] tableCols) {
verifyTable(connection, tableName, tableCols);
}
/**
* Sets the current schema associated with the connection.
* If the connection is not set (e.g., keepConnection is false, it has no effect.
* @throws SQLException
*/
protected void setCurrentSchema() throws SQLException {
if (schemaName == null || connection == null) {
return;
}
Statement stmt = connection.createStatement();
// schemaName has been verified at setSchemaName(String)
try {
stmt.executeUpdate(MessageFormat.format(CREATE_SCHEMA_STMT_STR,
schemaName));
} catch (SQLException ex) {
// assume it is already created or no authorization is provided (create one manually)
} finally {
stmt.close();
}
stmt = connection.createStatement();
SQLException ex0 = null;
for (int i = 0; i < SET_SCHEMA_STMT_STRS.length; i++) {
try {
stmt.executeUpdate(MessageFormat.format(SET_SCHEMA_STMT_STRS[i], schemaName));
break;
} catch (SQLException ex) {
ex.setNextException(ex0);
ex0 = ex;
if (i == SET_SCHEMA_STMT_STRS.length - 1) {
throw ex0;
}
// continue
} finally {
// close the statement after its last use
if (ex0 == null || i == SET_SCHEMA_STMT_STRS.length - 1) {
stmt.close();
}
}
}
}
/**
* Returns either the locally cached statement or the one from the specified connection
* depending on whether the connection is held by this store. If the statement retrieved from
* the local cache, it is locked until it is released. The retrieved statement must be
* released using releaseResources(PreparedStatement stmt, ResultSet rs).
*
* @param con
* @param sql
* @return
* @throws SQLException
*/
protected PreparedStatement getStatement(Connection con, String sql) throws SQLException {
if (connection != null) {
PreparedStatement stmt = cachedStatements.get(sql);
statementLocks.get(stmt).lock();
return stmt;
} else {
return con.prepareStatement(sql);
}
}
/**
* Releases the statement and any result set.
*
* @param stmt
* @param rs
*/
protected void releaseResources(PreparedStatement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// ignore
}
}
if (stmt != null) {
if (connection != null) {
statementLocks.get(stmt).unlock();
} else {
try {
stmt.close();
} catch (SQLException e) {
// ignore
}
}
}
}
protected void cacheStatement(Connection con, String sql) throws SQLException {
PreparedStatement stmt = con.prepareStatement(sql);
cachedStatements.put(sql, stmt);
statementLocks.put(stmt, new ReentrantLock());
}
protected void cacheStatements() throws SQLException {
if (connection == null) {
// if the connection is not held, no statement is cached.
return;
}
// create a statement specific lock table
statementLocks = new HashMap<>();
cachedStatements = new HashMap<>();
// create the statements in advance if the connection is to be kept
cacheStatement(connection, CREATE_DEST_SEQUENCE_STMT_STR);
cacheStatement(connection, CREATE_SRC_SEQUENCE_STMT_STR);
cacheStatement(connection, DELETE_DEST_SEQUENCE_STMT_STR);
cacheStatement(connection, DELETE_SRC_SEQUENCE_STMT_STR);
cacheStatement(connection, UPDATE_DEST_SEQUENCE_STMT_STR);
cacheStatement(connection, UPDATE_SRC_SEQUENCE_STMT_STR);
cacheStatement(connection, SELECT_DEST_SEQUENCES_STMT_STR);
cacheStatement(connection, SELECT_SRC_SEQUENCES_STMT_STR);
cacheStatement(connection, SELECT_DEST_SEQUENCE_STMT_STR);
cacheStatement(connection, SELECT_SRC_SEQUENCE_STMT_STR);
cacheStatement(connection, CREATE_INBOUND_MESSAGE_STMT_STR);
cacheStatement(connection, CREATE_OUTBOUND_MESSAGE_STMT_STR);
cacheStatement(connection, DELETE_INBOUND_MESSAGE_STMT_STR);
cacheStatement(connection, DELETE_OUTBOUND_MESSAGE_STMT_STR);
cacheStatement(connection, SELECT_INBOUND_MESSAGES_STMT_STR);
cacheStatement(connection, SELECT_OUTBOUND_MESSAGES_STMT_STR);
}
public synchronized void init() {
if (keepConnection && connection == null) {
connection = createConnection();
}
try {
if (connection != null && schemaName != null) {
setCurrentSchema();
}
createTables();
if (connection != null) {
cacheStatements();
}
} catch (SQLException ex) {
LogUtils.log(LOG, Level.SEVERE, "CONNECT_EXC", ex);
SQLException se = ex;
while (se.getNextException() != null) {
se = se.getNextException();
LogUtils.log(LOG, Level.SEVERE, "CONNECT_EXC", se);
}
throw new RMStoreException(ex);
} catch (Throwable ex) {
LogUtils.log(LOG, Level.SEVERE, "INITIALIZATION_FAILED_MSG", ex);
}
}
Connection getConnection() {
return connection;
}
protected Connection createConnection() {
LOG.log(Level.FINE, "Using derby.system.home: {0}",
SystemPropertyAction.getProperty("derby.system.home"));
Connection con = null;
if (null != dataSource) {
try {
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Using dataSource: " + dataSource);
}
con = dataSource.getConnection();
} catch (SQLException ex) {
LogUtils.log(LOG, Level.SEVERE, "CONNECT_EXC", ex);
}
} else {
assert null != url;
assert null != driverClassName;
try {
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Using url: " + url);
}
Class.forName(driverClassName);
con = DriverManager.getConnection(url, userName, password);
} catch (ClassNotFoundException ex) {
LogUtils.log(LOG, Level.SEVERE, "CONNECT_EXC", ex);
} catch (SQLException ex) {
LogUtils.log(LOG, Level.SEVERE, "CONNECT_EXC", ex);
}
}
return con;
}
protected Connection verifyConnection() {
Connection con;
if (connection == null) {
// return a new connection
con = createConnection();
} else {
// return the cached connection or create and cache a new one if the old one is dead
synchronized (this) {
if (createdConnection && nextReconnectAttempt > 0
&& (maxReconnectAttempts < 0 || maxReconnectAttempts > reconnectAttempts)) {
if (System.currentTimeMillis() > nextReconnectAttempt) {
// destroy the broken connection
destroy();
// try to reconnect
reconnectAttempts++;
init();
// reset the next reconnect attempt time
nextReconnectAttempt = 0;
} else {
LogUtils.log(LOG, Level.INFO, "WAIT_RECONNECT_MSG");
}
}
}
con = connection;
}
return con;
}
protected void updateConnectionState(Connection con, SQLException e) {
if (connection == null) {
// close the locally created connection
try {
con.close();
} catch (SQLException ex) {
// ignore
}
} else {
synchronized (this) {
// update the status of the cached connection
if (e == null) {
// reset the previous error status
reconnectDelay = 0;
reconnectAttempts = 0;
nextReconnectAttempt = 0;
} else if (createdConnection && isRecoverableError(e)) {
// update the next reconnect schedule
if (reconnectDelay == 0) {
reconnectDelay = initialReconnectDelay;
}
if (nextReconnectAttempt < System.currentTimeMillis()) {
nextReconnectAttempt = System.currentTimeMillis() + reconnectDelay;
reconnectDelay = reconnectDelay * useExponentialBackOff;
}
}
}
}
}
public static void deleteDatabaseFiles() {
deleteDatabaseFiles(DEFAULT_DATABASE_NAME, true);
}
public static void deleteDatabaseFiles(String dbName, boolean now) {
String dsh = SystemPropertyAction.getPropertyOrNull("derby.system.home");
File root = null;
File log = null;
if (null == dsh) {
log = new File("derby.log");
root = new File(dbName);
} else {
log = new File(dsh, "derby.log");
root = new File(dsh, dbName);
}
if (log.exists()) {
if (now) {
boolean deleted = log.delete();
LOG.log(Level.FINE, "Deleted log file {0}: {1}", new Object[] {log, deleted});
} else {
log.deleteOnExit();
}
}
if (root.exists()) {
LOG.log(Level.FINE, "Trying to delete directory {0}", root);
recursiveDelete(root, now);
}
}
protected static String encodeProtocolVersion(ProtocolVariation pv) {
return pv.getCodec().getWSRMNamespace() + ' ' + pv.getCodec().getWSANamespace();
}
protected static ProtocolVariation decodeProtocolVersion(String pv) {
if (null != pv) {
int d = pv.indexOf(' ');
if (d > 0) {
return ProtocolVariation.findVariant(pv.substring(0, d), pv.substring(d + 1));
}
}
return ProtocolVariation.RM10WSA200408;
}
private static void recursiveDelete(File dir, boolean now) {
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
recursiveDelete(f, now);
} else {
if (now) {
f.delete();
} else {
f.deleteOnExit();
}
}
}
if (now) {
dir.delete();
} else {
dir.deleteOnExit();
}
}
private static String buildCreateTableStatement(String name, String[][] cols, String[] keys) {
StringBuilder buf = new StringBuilder();
buf.append("CREATE TABLE ").append(name).append(" (");
for (String[] col : cols) {
buf.append(col[0]).append(" ").append(col[1]).append(", ");
}
buf.append("PRIMARY KEY (");
for (int i = 0; i < keys.length; i++) {
if (i > 0) {
buf.append(", ");
}
buf.append(keys[i]);
}
buf.append("))");
return buf.toString();
}
protected boolean isTableExistsError(SQLException ex) {
// we could be deriving the state/code from the driver url to avoid explicit setting of them
return (null != tableExistsState && tableExistsState.equals(ex.getSQLState()))
|| tableExistsCode == ex.getErrorCode();
}
protected boolean isRecoverableError(SQLException ex) {
// check for a transient or non-transient connection exception
return ex.getSQLState() != null && ex.getSQLState().startsWith("08");
}
}