/**
* 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.smscserver.message.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.sql.DataSource;
import org.apache.smscserver.SmscServerConfigurationException;
import org.apache.smscserver.message.DBMessageManagerFactory;
import org.apache.smscserver.smsclet.MessageManager;
import org.apache.smscserver.smsclet.ShortMessage;
import org.apache.smscserver.smsclet.ShortMessageStatus;
import org.apache.smscserver.smsclet.SmscException;
import org.apache.smscserver.smsclet.SmscOriginalNotFoundException;
import org.apache.smscserver.smsclet.User;
import org.apache.smscserver.util.DBUtils;
import org.apache.smscserver.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <strong>Internal class, do not use directly.</strong>
*
* Database implementation of {@link MessageManager}. It has been tested in MySQL and Oracle 8i database. The schema
* file is </code>res/smsc-message-db.sql</code>
*
* All the user attributes are replaced during run-time. So we can use your database schema. Then you need to modify the
* SQLs in the configuration file.
*
* @author hceylan
*/
public class DBMessageManager implements MessageManager {
private static final Logger LOG = LoggerFactory.getLogger(DBMessageManager.class);
private static final String ATTR_ID = "id";
private static final String ATTR_DATA_CODING = "datacoding";
private static final String ATTR_DEFAULT_MESSAGE_ID = "defaultmessage";
private static final String ATTR_DESTINATION_ADDRESS = "destaddr";
private static final String ATTR_DESTINATION_ADDRESS_NPI = DBMessageManager.ATTR_DESTINATION_ADDRESS + "npi";
private static final String ATTR_DESTINATION_ADDRESS_TON = DBMessageManager.ATTR_DESTINATION_ADDRESS + "ton";
private static final String ATTR_SOURCE_ADDRESS = "sourceaddr";
private static final String ATTR_SOURCE_ADDRESS_NPI = DBMessageManager.ATTR_SOURCE_ADDRESS + "npi";
private static final String ATTR_SOURCE_ADDRESS_TON = DBMessageManager.ATTR_SOURCE_ADDRESS + "ton";
private static final String ATTR_ESM_CLASS = "esmclass";
private static final String ATTR_MESSAGE_LENGTH = "messageLength";
private static final String ATTR_NEXT_TRY_DELIVERY_TIME = "nexttrydelivertime";
private static final String ATTR_PRIORITY_FLAG = "priorityflag";
private static final String ATTR_PROTOCOL_ID = "protocolid";
private static final String ATTR_RECEIVED = "received";
private static final String ATTR_REPLACED_BY = "replacedby";
private static final String ATTR_REPLACED = "replaced";
private static final String ATTR_SCHEDULE_DATE = "scheduledate";
private static final String ATTR_SERVICE_TYPE = "servicetype";
private static final String ATTR_SHORT_MESSAGE = "shortmessage";
private static final String ATTR_STATUS = "status";
private static final String ATTR_VALIDITY_PERIOD = "validityperiod";
private final DataSource datasource;
private final String sqlCreateTable;
private final String sqlInsertMessage;
private final String sqlSelectMessage;
private final String sqlUpdateMessage;
private final String sqlSelectLatestReplacableMessage;
private final String sqlSelectUserMessage;
/**
* Internal constructor, do not use directly. Use {@link DBMessageManagerFactory} instead.
*/
public DBMessageManager(DataSource datasource, String sqlCreateTable, String sqlInsertMessage,
String sqlSelectMessage, String sqlSelectUserMessage, String sqlUpdateMessage,
String sqlSelectLatestReplacableMessage) {
super();
this.datasource = datasource;
this.sqlCreateTable = sqlCreateTable;
this.sqlInsertMessage = sqlInsertMessage;
this.sqlSelectMessage = sqlSelectMessage;
this.sqlSelectUserMessage = sqlSelectUserMessage;
this.sqlUpdateMessage = sqlUpdateMessage;
this.sqlSelectLatestReplacableMessage = sqlSelectLatestReplacableMessage;
Connection con = null;
Statement stmt = null;
try {
// test the connection
con = this.createConnection();
// create table if not exists
stmt = con.createStatement();
stmt.execute(this.sqlCreateTable);
} catch (Exception e) {
String msg = "Failed to open connection to user database";
DBMessageManager.LOG.error(msg, e);
throw new SmscServerConfigurationException(msg, e);
} finally {
DBUtils.closeQuitelyWithConnection(stmt);
}
}
/**
* {@inheritDoc}
*
*/
public void cancelSM(ShortMessage _shortMessage) throws SmscException {
ShortMessageImpl shortMessage = (ShortMessageImpl) _shortMessage;
// Can we actually replace an existing short messages
ShortMessageImpl oldMessage = (ShortMessageImpl) this.selectLatestShortMessage(shortMessage.getSourceAddress(),
shortMessage.getDestinationAddress(), shortMessage.getServiceType());
if ((oldMessage == null) || (oldMessage.getStatus() != ShortMessageStatus.PENDING)) {
throw new SmscOriginalNotFoundException();
}
DBMessageManager.LOG.debug("Replacement possible with {}", oldMessage.getId());
Connection connection = null;
Statement stmt = null;
String sql = null;
try {
connection = this.createConnection();
stmt = connection.createStatement();
oldMessage.setStatus(ShortMessageStatus.CANCELED);
this.storeShortMessageImpl(oldMessage, stmt);
connection.commit();
} catch (Exception e) {
DBUtils.handleException(sql, e);
}
}
private Connection createConnection() throws SQLException {
return DBUtils.createConnection(this.datasource);
}
/**
* {@inheritDoc}
*
*/
public List<ShortMessage> getPendingMessagesForUser(User user) throws SmscException {
Statement stmt = null;
ResultSet rs = null;
String sql = null;
try {
// prepare statement
Map<String, Object> map = new HashMap<String, Object>();
map.put(DBMessageManager.ATTR_DESTINATION_ADDRESS, DBUtils.escapeString(user.getName()));
sql = StringUtils.replaceString(this.sqlSelectUserMessage, map);
DBMessageManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
rs = stmt.executeQuery(sql);
List<ShortMessage> messages = new ArrayList<ShortMessage>();
if (rs.next()) {
messages.add(this.propulateFrom(rs));
}
return messages;
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(rs, stmt);
}
}
private Map<String, Object> populateFrom(ShortMessageImpl shortMessage) throws SmscException {
if (shortMessage.getId() == null) {
shortMessage.setId(UUID.randomUUID().toString());
shortMessage.setStatus(ShortMessageStatus.PENDING);
shortMessage.setReceived(Calendar.getInstance().getTime());
}
Map<String, Object> map = new HashMap<String, Object>();
map.put(DBMessageManager.ATTR_DATA_CODING, shortMessage.getDataCoding());
map.put(DBMessageManager.ATTR_DEFAULT_MESSAGE_ID, shortMessage.getDefaultMessageId());
map.put(DBMessageManager.ATTR_DESTINATION_ADDRESS, DBUtils.escapeString(shortMessage.getDestinationAddress()));
map.put(DBMessageManager.ATTR_DESTINATION_ADDRESS_NPI, shortMessage.getDestinationAddressNPI());
map.put(DBMessageManager.ATTR_DESTINATION_ADDRESS_TON, shortMessage.getDestinationAddressTON());
map.put(DBMessageManager.ATTR_ESM_CLASS, shortMessage.getEsmClass());
map.put(DBMessageManager.ATTR_ID, DBUtils.escapeString(shortMessage.getId()));
map.put(DBMessageManager.ATTR_MESSAGE_LENGTH, shortMessage.getMessageLength());
map.put(DBMessageManager.ATTR_NEXT_TRY_DELIVERY_TIME, DBUtils.asString(shortMessage.getNextTryDeliverTime()));
map.put(DBMessageManager.ATTR_PRIORITY_FLAG, shortMessage.getPriorityFlag());
map.put(DBMessageManager.ATTR_PROTOCOL_ID, shortMessage.getProtocolId());
map.put(DBMessageManager.ATTR_RECEIVED, DBUtils.asString(shortMessage.getReceived()));
map.put(DBMessageManager.ATTR_REPLACED, DBUtils.escapeString(shortMessage.getReplaced()));
map.put(DBMessageManager.ATTR_REPLACED_BY, DBUtils.escapeString(shortMessage.getReplacedBy()));
map.put(DBMessageManager.ATTR_SCHEDULE_DATE, DBUtils.asString(shortMessage.getScheduleDeliveryTime()));
map.put(DBMessageManager.ATTR_SERVICE_TYPE, DBUtils.escapeString(shortMessage.getServiceType()));
map.put(DBMessageManager.ATTR_SHORT_MESSAGE, DBUtils.escapeString(shortMessage.getShortMessage()));
map.put(DBMessageManager.ATTR_SOURCE_ADDRESS, DBUtils.escapeString(shortMessage.getSourceAddress()));
map.put(DBMessageManager.ATTR_SOURCE_ADDRESS_NPI, shortMessage.getSourceAddressNPI());
map.put(DBMessageManager.ATTR_SOURCE_ADDRESS_TON, shortMessage.getSourceAddressTON());
map.put(DBMessageManager.ATTR_STATUS, shortMessage.asString(shortMessage.getStatus().toString()));
map.put(DBMessageManager.ATTR_VALIDITY_PERIOD, DBUtils.asString(shortMessage.getValidityPeriod()));
return map;
}
private ShortMessage propulateFrom(ResultSet rs) throws SQLException {
ShortMessageImpl shortMessage = new ShortMessageImpl();
shortMessage.setDatacoding(rs.getInt(DBMessageManager.ATTR_DATA_CODING));
shortMessage.setDefaultMessageId(rs.getInt(DBMessageManager.ATTR_DEFAULT_MESSAGE_ID));
shortMessage.setDestinationAddress(rs.getString(DBMessageManager.ATTR_DESTINATION_ADDRESS));
shortMessage.setDestinationAddressNPI(rs.getInt(DBMessageManager.ATTR_DESTINATION_ADDRESS_NPI));
shortMessage.setDestinationAddressTON(rs.getInt(DBMessageManager.ATTR_DESTINATION_ADDRESS_TON));
shortMessage.setEsmClass(rs.getInt(DBMessageManager.ATTR_ESM_CLASS));
shortMessage.setId(rs.getString(DBMessageManager.ATTR_ID));
shortMessage.setMessageLength(rs.getInt(DBMessageManager.ATTR_MESSAGE_LENGTH));
shortMessage.setNextTryDeliverTime(rs.getTimestamp(DBMessageManager.ATTR_NEXT_TRY_DELIVERY_TIME));
shortMessage.setPriorityFlag(rs.getInt(DBMessageManager.ATTR_PRIORITY_FLAG));
shortMessage.setProtocolId(rs.getInt(DBMessageManager.ATTR_PROTOCOL_ID));
shortMessage.setReceived(rs.getTimestamp(DBMessageManager.ATTR_RECEIVED));
shortMessage.setReplaced(rs.getString(DBMessageManager.ATTR_REPLACED));
shortMessage.setReplacedBy(rs.getString(DBMessageManager.ATTR_REPLACED_BY));
shortMessage.setScheduleDeliveryTime(rs.getDate(DBMessageManager.ATTR_SCHEDULE_DATE));
shortMessage.setServiceType(rs.getString(DBMessageManager.ATTR_SERVICE_TYPE));
shortMessage.setShortMessage(rs.getString(DBMessageManager.ATTR_SHORT_MESSAGE));
shortMessage.setSourceAddress(rs.getString(DBMessageManager.ATTR_SOURCE_ADDRESS));
shortMessage.setSourceAddressNPI(rs.getInt(DBMessageManager.ATTR_SOURCE_ADDRESS_NPI));
shortMessage.setSourceAddressTON(rs.getInt(DBMessageManager.ATTR_SOURCE_ADDRESS_TON));
shortMessage.setStatus(ShortMessageStatus.valueOf(rs.getString(DBMessageManager.ATTR_STATUS)));
shortMessage.setValidityPeriod(rs.getTime(DBMessageManager.ATTR_VALIDITY_PERIOD));
return shortMessage;
}
private boolean replaceImpl(ShortMessage _shortMessage, boolean replace) throws SmscException,
SmscOriginalNotFoundException {
ShortMessageImpl shortMessage = (ShortMessageImpl) _shortMessage;
// Only applicable for new short messages
if (shortMessage.getId() != null) {
throw new IllegalArgumentException("Id of the shortMessage must be null");
}
// Can we actually replace an existing short messages
ShortMessageImpl oldMessage = (ShortMessageImpl) this.selectLatestShortMessage(shortMessage.getSourceAddress(),
shortMessage.getDestinationAddress(), shortMessage.getServiceType());
if ((oldMessage == null) && replace) {
throw new SmscOriginalNotFoundException();
}
if (oldMessage == null) {
return false;
}
ShortMessageStatus status = oldMessage != null ? oldMessage.getStatus() : null;
if (replace && (status != ShortMessageStatus.PENDING)) {
throw new SmscOriginalNotFoundException();
}
// is the message still pending
DBMessageManager.LOG.debug("Replcaement possible with {}", oldMessage.getId());
Connection connection = null;
Statement stmt = null;
String sql = null;
try {
connection = this.createConnection();
stmt = connection.createStatement();
// begin transaction
connection.setAutoCommit(false);
try {
shortMessage.setReplaced(oldMessage.getId());
this.storeShortMessageImpl(shortMessage, stmt);
oldMessage.setReplacedBy(shortMessage.getId());
this.storeShortMessageImpl(oldMessage, stmt);
connection.commit();
return true;
} catch (Exception e) {
try {
connection.rollback();
} catch (Exception e2) {
DBMessageManager.LOG.error("Cannot rollback operation", e2);
}
throw e;
}
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
}
}
/**
* {@inheritDoc}
*
*/
public void replaceSM(ShortMessage _shortMessage) throws SmscException {
this.replaceImpl(_shortMessage, true);
}
private ShortMessage selectLatestShortMessage(String sourceAddress, String destinationAddress, String serviceType)
throws SmscException {
Statement stmt = null;
ResultSet rs = null;
String sql = null;
try {
// prepare statement
Map<String, Object> map = new HashMap<String, Object>();
map.put(DBMessageManager.ATTR_SOURCE_ADDRESS, DBUtils.escapeString(sourceAddress));
map.put(DBMessageManager.ATTR_DESTINATION_ADDRESS, DBUtils.escapeString(destinationAddress));
map.put(DBMessageManager.ATTR_SERVICE_TYPE, DBUtils.escapeString(serviceType));
sql = StringUtils.replaceString(this.sqlSelectLatestReplacableMessage, map);
DBMessageManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
rs = stmt.executeQuery(sql);
if (rs.next()) {
return this.propulateFrom(rs);
}
return null;
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(rs, stmt);
}
}
/**
* {@inheritDoc}
*
*/
public ShortMessage selectShortMessage(String id) throws SmscException {
Statement stmt = null;
ResultSet rs = null;
String sql = null;
try {
// prepare statement
Map<String, Object> map = new HashMap<String, Object>();
map.put(DBMessageManager.ATTR_ID, DBUtils.escapeString(id));
sql = StringUtils.replaceString(this.sqlSelectMessage, map);
DBMessageManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
rs = stmt.executeQuery(sql);
if (rs.next()) {
return this.propulateFrom(rs);
}
return null;
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(rs, stmt);
}
}
private String storeShortMessageImpl(ShortMessageImpl shortMessage, Statement stmt) throws SmscException,
SQLException {
String sql = shortMessage.getId() != null ? this.sqlUpdateMessage : this.sqlInsertMessage;
sql = StringUtils.replaceString(sql, this.populateFrom(shortMessage));
DBMessageManager.LOG.debug(sql);
// execute query
stmt.executeUpdate(sql);
return sql;
}
/**
* {@inheritDoc}
*
*/
public void submitSM(ShortMessage _shortMessage) throws SmscException {
ShortMessageImpl shortMessage = (ShortMessageImpl) _shortMessage;
if (shortMessage.isReplaceIfPresent()) {
DBMessageManager.LOG.debug("Falling back to replace...");
boolean replaced = this.replaceImpl(_shortMessage, false);
if (replaced) {
return;
}
}
Statement stmt = null;
String sql = null;
try {
stmt = this.createConnection().createStatement();
sql = this.storeShortMessageImpl(shortMessage, stmt);
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(stmt);
}
}
/**
* {@inheritDoc}
*
*/
public void updateMesage(ShortMessage _shortMessage) throws SmscException {
ShortMessageImpl shortMessage = (ShortMessageImpl) _shortMessage;
Statement stmt = null;
String sql = null;
try {
stmt = this.createConnection().createStatement();
sql = this.storeShortMessageImpl(shortMessage, stmt);
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(stmt);
}
}
}