0001: /**
0002: * Sequoia: Database clustering technology.
0003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
0004: * Science And Control (INRIA).
0005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
0006: * Copyright (C) 2005-2006 Continuent, Inc.
0007: * Contact: sequoia@continuent.org
0008: *
0009: * Licensed under the Apache License, Version 2.0 (the "License");
0010: * you may not use this file except in compliance with the License.
0011: * You may obtain a copy of the License at
0012: *
0013: * http://www.apache.org/licenses/LICENSE-2.0
0014: *
0015: * Unless required by applicable law or agreed to in writing, software
0016: * distributed under the License is distributed on an "AS IS" BASIS,
0017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0018: * See the License for the specific language governing permissions and
0019: * limitations under the License.
0020: *
0021: * Initial developer(s): Emmanuel Cecchet.
0022: * Contributor(s): Mathieu Peltier, Nicolas Modrzyk, Vadim Kassin, Olivier Fambon, Jean-Bernard van Zuylen.
0023: */package org.continuent.sequoia.controller.virtualdatabase;
0024:
0025: import java.sql.SQLException;
0026: import java.sql.SQLWarning;
0027: import java.text.SimpleDateFormat;
0028: import java.util.ArrayList;
0029: import java.util.ConcurrentModificationException;
0030: import java.util.Date;
0031: import java.util.Hashtable;
0032: import java.util.Iterator;
0033: import java.util.LinkedList;
0034: import java.util.List;
0035: import java.util.Map;
0036:
0037: import javax.management.MalformedObjectNameException;
0038: import javax.management.Notification;
0039: import javax.management.NotificationBroadcasterSupport;
0040: import javax.management.ObjectName;
0041:
0042: import org.continuent.sequoia.common.authentication.AuthenticationManager;
0043: import org.continuent.sequoia.common.exceptions.BackupException;
0044: import org.continuent.sequoia.common.exceptions.NoMoreBackendException;
0045: import org.continuent.sequoia.common.exceptions.VirtualDatabaseException;
0046: import org.continuent.sequoia.common.i18n.Translate;
0047: import org.continuent.sequoia.common.jmx.JmxConstants;
0048: import org.continuent.sequoia.common.jmx.management.BackendInfo;
0049: import org.continuent.sequoia.common.jmx.management.BackendState;
0050: import org.continuent.sequoia.common.jmx.management.DumpInfo;
0051: import org.continuent.sequoia.common.jmx.monitoring.backend.BackendStatistics;
0052: import org.continuent.sequoia.common.jmx.notifications.SequoiaNotificationList;
0053: import org.continuent.sequoia.common.locks.ReadPrioritaryFIFOWriteLock;
0054: import org.continuent.sequoia.common.log.Trace;
0055: import org.continuent.sequoia.common.sql.schema.DatabaseSchema;
0056: import org.continuent.sequoia.common.users.AdminUser;
0057: import org.continuent.sequoia.common.users.VirtualDatabaseUser;
0058: import org.continuent.sequoia.common.util.Constants;
0059: import org.continuent.sequoia.common.xml.DatabasesXmlTags;
0060: import org.continuent.sequoia.common.xml.XmlComponent;
0061: import org.continuent.sequoia.common.xml.XmlTools;
0062: import org.continuent.sequoia.controller.backend.DatabaseBackend;
0063: import org.continuent.sequoia.controller.backend.result.ControllerResultSet;
0064: import org.continuent.sequoia.controller.backend.result.ExecuteResult;
0065: import org.continuent.sequoia.controller.backend.result.ExecuteUpdateResult;
0066: import org.continuent.sequoia.controller.backend.result.GeneratedKeysResult;
0067: import org.continuent.sequoia.controller.backup.Backuper;
0068: import org.continuent.sequoia.controller.cache.result.AbstractResultCache;
0069: import org.continuent.sequoia.controller.core.Controller;
0070: import org.continuent.sequoia.controller.core.shutdown.VirtualDatabaseForceShutdownThread;
0071: import org.continuent.sequoia.controller.core.shutdown.VirtualDatabaseSafeShutdownThread;
0072: import org.continuent.sequoia.controller.core.shutdown.VirtualDatabaseShutdownThread;
0073: import org.continuent.sequoia.controller.core.shutdown.VirtualDatabaseWaitShutdownThread;
0074: import org.continuent.sequoia.controller.jmx.MBeanServerManager;
0075: import org.continuent.sequoia.controller.loadbalancer.AllBackendsFailedException;
0076: import org.continuent.sequoia.controller.monitoring.SQLMonitoring;
0077: import org.continuent.sequoia.controller.recoverylog.BackendRecoveryInfo;
0078: import org.continuent.sequoia.controller.recoverylog.RecoverThread;
0079: import org.continuent.sequoia.controller.recoverylog.RecoveryLog;
0080: import org.continuent.sequoia.controller.requestmanager.RAIDbLevels;
0081: import org.continuent.sequoia.controller.requestmanager.RequestManager;
0082: import org.continuent.sequoia.controller.requests.AbstractRequest;
0083: import org.continuent.sequoia.controller.requests.AbstractWriteRequest;
0084: import org.continuent.sequoia.controller.requests.SelectRequest;
0085: import org.continuent.sequoia.controller.requests.StoredProcedure;
0086: import org.continuent.sequoia.controller.virtualdatabase.management.AbstractAdminOperation;
0087: import org.continuent.sequoia.controller.virtualdatabase.management.BackupBackendOperation;
0088: import org.continuent.sequoia.controller.virtualdatabase.management.EnableBackendOperation;
0089: import org.continuent.sequoia.controller.virtualdatabase.management.RestoreDumpOperation;
0090:
0091: /**
0092: * A <code>VirtualDatabase</code> represents a database from client point of
0093: * view and hide the complexity of the cluster distribution to the client. The
0094: * client always uses the virtual database name and the Sequoia Controller will
0095: * use the real connections when an SQL request comes in.
0096: *
0097: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
0098: * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
0099: * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
0100: * @author <a href="mailto:vadim@kase.kz">Vadim Kassin </a>
0101: * @author <a href="mailto:jbvanzuylen@transwide.com">Jean-Bernard van Zuylen
0102: * </a>
0103: * @version 1.0
0104: */
0105: public class VirtualDatabase implements XmlComponent {
0106: private static final long serialVersionUID = 1399418136380336827L;
0107:
0108: //
0109: // How the code is organized ?
0110: //
0111: // 1. Member variables
0112: // 2. Constructor(s)
0113: // 3. Request handling
0114: // 4. Transaction handling
0115: // 5. Database backend management
0116: // 6. Checkpoint management
0117: // 7. Getter/Setter (possibly in alphabetical order)
0118: // 8. Shutdown
0119: //
0120:
0121: /** Virtual database name */
0122: protected String name;
0123:
0124: /**
0125: * Authentification manager matching virtual database login/password to
0126: * backends login/password
0127: */
0128: protected AuthenticationManager authenticationManager;
0129:
0130: /** <code>ArrayList</code> of <code>DatabaseBackend</code> objects */
0131: protected ArrayList backends;
0132:
0133: /** Read/Write lock for backend list */
0134: protected ReadPrioritaryFIFOWriteLock rwLock;
0135:
0136: /** The request manager to use for this database */
0137: protected RequestManager requestManager;
0138:
0139: /** ArrayList to store the order of requests */
0140: protected LinkedList totalOrderQueue = null;
0141:
0142: /** Virtual database logger */
0143: protected Trace logger = null;
0144: protected Trace requestLogger = null;
0145:
0146: /** end user logger */
0147: static Trace endUserLogger = Trace
0148: .getLogger("org.continuent.sequoia.enduser");
0149:
0150: // List of current active Worker Threads
0151: private ArrayList activeThreads = new ArrayList();
0152: // List of current idle Worker Threads
0153: private int idleThreads = 0;
0154: // List of current pending connections (Socket objects)
0155: private ArrayList pendingConnections = new ArrayList();
0156:
0157: /** Maximum number of concurrent accepted for this virtual database */
0158: protected int maxNbOfConnections;
0159:
0160: /** If false one worker thread is forked per connection else */
0161: protected boolean poolConnectionThreads;
0162:
0163: /** Maximum time a worker thread can remain idle before dying */
0164: protected long maxThreadIdleTime;
0165:
0166: /**
0167: * Minimum number of worker threads to keep in the pool if
0168: * poolConnectionThreads is true
0169: */
0170: protected int minNbOfThreads;
0171:
0172: /** Maximum number of worker threads to fork */
0173: protected int maxNbOfThreads;
0174:
0175: /** Current number of worker threads */
0176: protected int currentNbOfThreads;
0177:
0178: /** Virtual Database MetaData */
0179: protected VirtualDatabaseDynamicMetaData metadata;
0180: private boolean useStaticResultSetMetaData = true;
0181: protected VirtualDatabaseStaticMetaData staticMetadata;
0182:
0183: private SQLMonitoring sqlMonitor = null;
0184:
0185: /** Use for method getAndCheck */
0186: public static final int CHECK_BACKEND_ENABLE = 1;
0187: /** Use for method getAndCheck */
0188: public static final int CHECK_BACKEND_DISABLE = 0;
0189: /** Use for method getAndCheck */
0190: public static final int NO_CHECK_BACKEND = -1;
0191:
0192: /** Short form of SQL statements to include in traces and exceptions */
0193: private int sqlShortFormLength;
0194:
0195: /** The controller we belong to */
0196: Controller controller;
0197:
0198: /** Comma separated list of database product names (one instance per name) */
0199: private String databaseProductNames = "Sequoia";
0200:
0201: /** Marker to see if the database is shutting down */
0202: protected boolean shuttingDown = false;
0203: private boolean refusingNewTransaction = false;
0204: /** List of currently executing admin operations (preventing shutdown) */
0205: private List currentAdminOperations;
0206:
0207: protected NotificationBroadcasterSupport notificationBroadcasterSupport;
0208:
0209: protected int notificationSequence = 0;
0210:
0211: protected long connectionId = 0;
0212:
0213: private boolean enforceTableExistenceIntoSchema = false;
0214:
0215: /**
0216: * Creates a new <code>VirtualDatabase</code> instance.
0217: *
0218: * @param name the virtual database name.
0219: * @param maxConnections maximum number of concurrent connections.
0220: * @param pool should we use a pool of threads for handling connections?
0221: * @param minThreads minimum number of threads in the pool
0222: * @param maxThreads maximum number of threads in the pool
0223: * @param maxThreadIdleTime maximum time a thread can remain idle before being
0224: * removed from the pool.
0225: * @param sqlShortFormLength maximum number of characters of an SQL statement
0226: * to diplay in traces or exceptions
0227: * @param useStaticResultSetMetaData true if DatabaseResultSetMetaData should
0228: * use static fields or try to fetch the metadata from the underlying
0229: * database
0230: * @param controller the controller we belong to
0231: */
0232: public VirtualDatabase(Controller controller, String name,
0233: int maxConnections, boolean pool, int minThreads,
0234: int maxThreads, long maxThreadIdleTime,
0235: int sqlShortFormLength, boolean useStaticResultSetMetaData,
0236: boolean enforceTableExistenceIntoSchema) {
0237: this .controller = controller;
0238: this .name = name;
0239: this .maxNbOfConnections = maxConnections;
0240: this .poolConnectionThreads = pool;
0241: this .minNbOfThreads = minThreads;
0242: this .maxNbOfThreads = maxThreads;
0243: this .maxThreadIdleTime = maxThreadIdleTime;
0244: this .sqlShortFormLength = sqlShortFormLength;
0245: this .useStaticResultSetMetaData = useStaticResultSetMetaData;
0246: this .enforceTableExistenceIntoSchema = enforceTableExistenceIntoSchema;
0247: backends = new ArrayList();
0248: currentAdminOperations = new LinkedList();
0249:
0250: rwLock = new ReadPrioritaryFIFOWriteLock();
0251: logger = Trace
0252: .getLogger("org.continuent.sequoia.controller.virtualdatabase."
0253: + name);
0254: requestLogger = Trace
0255: .getLogger("org.continuent.sequoia.controller.virtualdatabase.request."
0256: + name);
0257: }
0258:
0259: /**
0260: * Sets the NotificationBroadcasterSupport associated with the MBean managing
0261: * this virtual database.
0262: *
0263: * @param notificationBroadcasterSupport the notificationBroadcasterSuppor
0264: * associated with the mbean managing this virtual database
0265: */
0266: public void setNotificationBroadcasterSupport(
0267: NotificationBroadcasterSupport notificationBroadcasterSupport) {
0268: this .notificationBroadcasterSupport = notificationBroadcasterSupport;
0269: }
0270:
0271: /**
0272: * Sends a JMX Notification on behalf of the MBean associated with this
0273: * virtual database
0274: *
0275: * @param type type of the JMX notification
0276: * @param message message associated with the notification
0277: * @see SequoiaNotificationList
0278: */
0279: protected void sendJmxNotification(String type, String message) {
0280: if (!MBeanServerManager.isJmxEnabled()) {
0281: // do not send jmx notification if jmx is not enabled
0282: return;
0283: }
0284: try {
0285: notificationBroadcasterSupport
0286: .sendNotification(new Notification(
0287: type,
0288: JmxConstants
0289: .getVirtualDataBaseObjectName(name),
0290: notificationSequence++, message));
0291: } catch (MalformedObjectNameException e) {
0292: // unable to get a correct vdb object name: do nothing
0293: logger.warn("Unable to send JMX notification", e);
0294: }
0295: }
0296:
0297: /**
0298: * Acquires a read lock on the backend lists (both enabled and disabled
0299: * backends). This should be called prior traversing the backend
0300: * <code>ArrayList</code>.
0301: *
0302: * @throws InterruptedException if an error occurs
0303: */
0304: public final void acquireReadLockBackendLists()
0305: throws InterruptedException {
0306: rwLock.acquireRead();
0307: }
0308:
0309: /**
0310: * Releases the read lock on the backend lists (both enabled and disabled
0311: * backends). This should be called after traversing the backend
0312: * <code>ArrayList</code>.
0313: */
0314: public final void releaseReadLockBackendLists() {
0315: rwLock.releaseRead();
0316: }
0317:
0318: /**
0319: * Is this virtual database distributed ?
0320: *
0321: * @return false
0322: */
0323: public boolean isDistributed() {
0324: return false;
0325: }
0326:
0327: /* Request Handling */
0328:
0329: /**
0330: * Checks if a given virtual login/password is ok.
0331: *
0332: * @param virtualLogin the virtual user login
0333: * @param virtualPassword the virtual user password
0334: * @return <code>true</code> if the login/password is known from the
0335: * <code>AuthenticationManager</code>. Returns <code>false</code>
0336: * if no <code>AuthenticationManager</code> is defined.
0337: */
0338: public boolean checkUserAuthentication(String virtualLogin,
0339: String virtualPassword) {
0340: if (authenticationManager == null) {
0341: logger
0342: .error("No authentification manager defined to check login '"
0343: + virtualLogin + "'");
0344: return false;
0345: } else {
0346: boolean result = authenticationManager
0347: .isValidVirtualUser(new VirtualDatabaseUser(
0348: virtualLogin, virtualPassword));
0349: if (!result)
0350: endUserLogger.error(Translate.get(
0351: "virtualdatabase.authentication.failed",
0352: virtualLogin));
0353: return result;
0354: }
0355: }
0356:
0357: /**
0358: * Checks if a given admin login/password is ok.
0359: *
0360: * @param adminLogin admin user login
0361: * @param adminPassword admin user password
0362: * @return <code>true</code> if the login/password is known from the
0363: * <code>AuthenticationManager</code>. Returns <code>false</code>
0364: * if no <code>AuthenticationManager</code> is defined.
0365: */
0366: public boolean checkAdminAuthentication(String adminLogin,
0367: String adminPassword) {
0368: if (authenticationManager == null) {
0369: endUserLogger.info(Translate.get(
0370: "virtualdatabase.checking.authentication",
0371: new String[] { adminLogin,
0372: this .getVirtualDatabaseName() }));
0373: String msg = "No authentification manager defined to check admin login '"
0374: + adminLogin + "'";
0375: logger.error(msg);
0376: endUserLogger.error(Translate.get(
0377: "virtualdatabase.check.authentication.failed",
0378: new String[] { adminLogin,
0379: this .getVirtualDatabaseName(), msg }));
0380: return false;
0381: } else {
0382: boolean result = authenticationManager
0383: .isValidAdminUser(new AdminUser(adminLogin,
0384: adminPassword));
0385: if (!result)
0386: endUserLogger.error(Translate.get(
0387: "virtualdatabase.authentication.failed",
0388: adminLogin));
0389: return result;
0390: }
0391: }
0392:
0393: /**
0394: * Adds a new vdb user. Uses the vdb login/password as real user
0395: * login/password. Only creates the user if vdb login/password are valid for
0396: * all backends hosting the vdb.
0397: *
0398: * @param vdbUser vdb user to be added.
0399: */
0400: public void checkAndAddVirtualDatabaseUser(
0401: VirtualDatabaseUser vdbUser) {
0402: // If user does not exist in all backends leave
0403: if (!isValidUserForAllBackends(vdbUser)) {
0404: if (logger.isWarnEnabled()) {
0405: logger.warn("Could not create new vdb user "
0406: + vdbUser.getLogin()
0407: + " because it does not exist on all backends");
0408: }
0409: return;
0410: }
0411:
0412: // Add user
0413: try {
0414: performAddVirtualDatabaseUser(vdbUser);
0415: if (logger.isInfoEnabled()) {
0416: logger.info("Added new vdb user " + vdbUser.getLogin());
0417: }
0418: } catch (SQLException e) {
0419: if (logger.isWarnEnabled()) {
0420: logger
0421: .warn("Problem when adding default connection manager for user "
0422: + vdbUser.getLogin()
0423: + ", trying to clean-up...");
0424: removeVirtualDatabaseUser(vdbUser);
0425: }
0426: }
0427: }
0428:
0429: /**
0430: * Removes vdb user.
0431: *
0432: * @param vdbUser vdb user to be removed.
0433: */
0434: private void removeVirtualDatabaseUser(VirtualDatabaseUser vdbUser) {
0435: performRemoveVirtualDatabaseUser(vdbUser);
0436: }
0437:
0438: /**
0439: * Adds new vdb user and its corresponding connection managers.
0440: *
0441: * @param vdbUser vdb user to be added.
0442: * @throws SQLException thrown if problem occurred when adding connection
0443: * manager
0444: */
0445: public void performAddVirtualDatabaseUser(
0446: VirtualDatabaseUser vdbUser) throws SQLException {
0447: for (Iterator iter = backends.iterator(); iter.hasNext();) {
0448: DatabaseBackend backend = (DatabaseBackend) iter.next();
0449: backend.addDefaultConnectionManager(vdbUser);
0450: }
0451: authenticationManager.addVirtualUser(vdbUser);
0452: // Should we invoke authenticationManager.addRealUser() here?
0453: }
0454:
0455: /**
0456: * Removes vdb user and its corresponding connection managers.
0457: *
0458: * @param vdbUser vdb user to be removed.
0459: */
0460: public void performRemoveVirtualDatabaseUser(
0461: VirtualDatabaseUser vdbUser) {
0462: authenticationManager.removeVirtualUser(vdbUser);
0463: for (Iterator iter = backends.iterator(); iter.hasNext();) {
0464: DatabaseBackend backend = (DatabaseBackend) iter.next();
0465: try {
0466: backend.removeConnectionManager(vdbUser);
0467: } catch (SQLException e) {
0468: if (logger.isWarnEnabled()) {
0469: logger
0470: .warn("Problem when removing default connection manager for user "
0471: + vdbUser.getLogin());
0472: }
0473: }
0474: }
0475: if (logger.isInfoEnabled()) {
0476: logger.info("Removed vdb user " + vdbUser.getLogin());
0477: }
0478: }
0479:
0480: /**
0481: * Checks if a vdb user is valid as a user for allbackends.
0482: *
0483: * @param vdbUser vdb user to be checked.
0484: * @return true if vdb user is valid for all backends, false otherwise.
0485: */
0486: public boolean isValidUserForAllBackends(VirtualDatabaseUser vdbUser) {
0487: boolean result = true;
0488: for (Iterator iter = backends.iterator(); iter.hasNext();) {
0489: DatabaseBackend backend = (DatabaseBackend) iter.next();
0490: if (!backend.isValidBackendUser(vdbUser)) {
0491: result = false;
0492: break;
0493: }
0494: }
0495: return result;
0496: }
0497:
0498: /**
0499: * Performs a read request and returns the reply.
0500: *
0501: * @param request the request to execute
0502: * @return a <code>ControllerResultSet</code> value
0503: * @exception SQLException if the request fails
0504: */
0505: protected ControllerResultSet statementExecuteQuery(
0506: SelectRequest request) throws SQLException {
0507: if (request == null) {
0508: String msg = "Request failed (null read request received)";
0509: logger.warn(msg);
0510: throw new SQLException(msg);
0511: }
0512:
0513: try {
0514: if (requestLogger.isInfoEnabled())
0515: requestLogger.info("S " + request.getId() + " "
0516: + request.getTransactionId() + " "
0517: + request.getUniqueKey());
0518:
0519: request.setStartTime(System.currentTimeMillis());
0520:
0521: ControllerResultSet rs = requestManager
0522: .statementExecuteQuery(request);
0523:
0524: request.setEndTime(System.currentTimeMillis());
0525: if (sqlMonitor != null && sqlMonitor.isActive())
0526: sqlMonitor.logRequestTime(request);
0527:
0528: return rs;
0529: } catch (SQLException e) {
0530: String msg = "Request '" + request.getId() + "' failed ("
0531: + e.getMessage() + ")";
0532: if (!request.isAutoCommit()) {
0533: // If the request fails in a transaction, the transaction is likely
0534: // to be rollbacked by the underlying database. Then we have to abort
0535: // the transaction.
0536: msg = Translate
0537: .get(
0538: "loadbalancer.request.failed.and.abort",
0539: new String[] {
0540: request
0541: .getSqlShortForm(getSqlShortFormLength()),
0542: e.getMessage() });
0543: try {
0544: abort(request.getTransactionId(), true, false);
0545: } catch (SQLException e1) {
0546: if (logger.isInfoEnabled())
0547: logger
0548: .info(
0549: "Abort after request failure in transaction did not succeed probably because the transaction has already been aborted",
0550: e);
0551: }
0552: }
0553: logger.warn(msg);
0554: if (sqlMonitor != null && sqlMonitor.isActive())
0555: sqlMonitor.logError(request);
0556: throw e;
0557: }
0558: }
0559:
0560: /**
0561: * Performs a write request and returns the number of rows affected.
0562: *
0563: * @param request the request to execute
0564: * @return number of rows affected
0565: * @exception SQLException if the request fails
0566: */
0567: protected ExecuteUpdateResult statementExecuteUpdate(
0568: AbstractWriteRequest request) throws SQLException {
0569: if (request == null) {
0570: String msg = "Request failed (null write request received)";
0571: logger.warn(msg);
0572: throw new SQLException(msg);
0573: }
0574:
0575: try {
0576: if (requestLogger.isInfoEnabled())
0577: requestLogger.info("W " + request.getId() + " "
0578: + request.getTransactionId() + " "
0579: + request.getUniqueKey());
0580:
0581: request.setStartTime(System.currentTimeMillis());
0582:
0583: ExecuteUpdateResult result = requestManager
0584: .statementExecuteUpdate(request);
0585:
0586: request.setEndTime(System.currentTimeMillis());
0587: if (sqlMonitor != null && sqlMonitor.isActive())
0588: sqlMonitor.logRequestTime(request);
0589:
0590: return result;
0591: } catch (SQLException e) {
0592: String msg = "Request '" + request.getId() + "' failed ("
0593: + e.getMessage() + ")";
0594:
0595: if (!request.isAutoCommit()) {
0596: // If the request fails in a transaction, the transaction is likely
0597: // to be rollbacked by the underlying database. Then we have to abort
0598: // the transaction.
0599: msg = Translate
0600: .get(
0601: "loadbalancer.request.failed.and.abort",
0602: new String[] {
0603: request
0604: .getSqlShortForm(getSqlShortFormLength()),
0605: e.getMessage() });
0606: try {
0607: if (requestManager.getTransactionMetaData(new Long(
0608: request.getTransactionId())) != null)
0609: abort(request.getTransactionId(), true, false);
0610: } catch (SQLException e1) {
0611: if (logger.isInfoEnabled())
0612: logger
0613: .info(
0614: "Abort after request failure in transaction did not succeed probably because the transaction has already been aborted",
0615: e);
0616: }
0617: }
0618:
0619: logger.warn(msg);
0620: if (sqlMonitor != null && sqlMonitor.isActive())
0621: sqlMonitor.logError(request);
0622: throw e;
0623: }
0624: }
0625:
0626: /**
0627: * Performs a write request and returns the auto generated keys.
0628: *
0629: * @param request the request to execute
0630: * @return auto generated keys
0631: * @exception SQLException if the request fails
0632: */
0633: protected GeneratedKeysResult statementExecuteUpdateWithKeys(
0634: AbstractWriteRequest request) throws SQLException {
0635: if (request == null) {
0636: String msg = "Request failed (null write request received)";
0637: logger.warn(msg);
0638: throw new SQLException(msg);
0639: }
0640:
0641: try {
0642: if (requestLogger.isInfoEnabled())
0643: requestLogger.info("W " + request.getId() + " "
0644: + request.getTransactionId() + " "
0645: + request.getUniqueKey());
0646:
0647: request.setStartTime(System.currentTimeMillis());
0648:
0649: GeneratedKeysResult result = requestManager
0650: .statementExecuteUpdateWithKeys(request);
0651:
0652: request.setEndTime(System.currentTimeMillis());
0653: if (sqlMonitor != null && sqlMonitor.isActive())
0654: sqlMonitor.logRequestTime(request);
0655:
0656: return result;
0657: } catch (SQLException e) {
0658: String msg = "Request '" + request.getId() + "' failed ("
0659: + e.getMessage() + ")";
0660: if (!request.isAutoCommit()) {
0661: // If the request fails in a transaction, the transaction is likely
0662: // to be rollbacked by the underlying database. Then we have to abort
0663: // the transaction.
0664: abort(request.getTransactionId(), true, false);
0665: msg = Translate
0666: .get(
0667: "loadbalancer.request.failed.and.abort",
0668: new String[] {
0669: request
0670: .getSqlShortForm(getSqlShortFormLength()),
0671: e.getMessage() });
0672: }
0673: logger.warn(msg);
0674: if (sqlMonitor != null && sqlMonitor.isActive())
0675: sqlMonitor.logError(request);
0676: throw e;
0677: }
0678: }
0679:
0680: /**
0681: * Execute a request using Statement.execute(). Handle this as a stored
0682: * procedure for which we have no metadata information.
0683: *
0684: * @param request the request to execute
0685: * @return an <code>ExecuteResult</code> object
0686: * @exception SQLException if an error occurs
0687: */
0688: protected ExecuteResult statementExecute(AbstractRequest request)
0689: throws SQLException {
0690: if (request == null) {
0691: String msg = "Statement.execute() failed (null request received)";
0692: logger.warn(msg);
0693: throw new SQLException(msg);
0694: }
0695:
0696: try {
0697: if (requestLogger.isInfoEnabled())
0698: requestLogger.info("E " + request.getId() + " "
0699: + request.getTransactionId() + " "
0700: + request.getUniqueKey());
0701:
0702: request.setStartTime(System.currentTimeMillis());
0703:
0704: ExecuteResult result = requestManager
0705: .statementExecute(request);
0706:
0707: request.setEndTime(System.currentTimeMillis());
0708: if (sqlMonitor != null && sqlMonitor.isActive())
0709: sqlMonitor.logRequestTime(request);
0710:
0711: return result;
0712: } catch (AllBackendsFailedException e) {
0713: String msg = Translate
0714: .get(
0715: "loadbalancer.storedprocedure.failed.on.all.backends",
0716: new String[] {
0717: String.valueOf(request.getId()),
0718: e.getMessage() });
0719: if (!request.isAutoCommit()) {
0720: // If the request fails in a transaction, the transaction is likely
0721: // to be rollbacked by the underlying database. Then we have to abort
0722: // the transaction.
0723: abort(request.getTransactionId(), true, false);
0724: msg = Translate
0725: .get(
0726: "loadbalancer.request.failed.and.abort",
0727: new String[] {
0728: request
0729: .getSqlShortForm(getSqlShortFormLength()),
0730: e.getMessage() });
0731: }
0732: logger.warn(msg);
0733: if (sqlMonitor != null && sqlMonitor.isActive())
0734: sqlMonitor.logError(request);
0735: throw new SQLException(msg);
0736: } catch (SQLException e) {
0737: String msg = Translate.get(
0738: "loadbalancer.storedprocedure.failed",
0739: new String[] { String.valueOf(request.getId()),
0740: e.getMessage() });
0741: if (!request.isAutoCommit()) {
0742: // If the request fails in a transaction, the transaction is likely
0743: // to be rollbacked by the underlying database. Then we have to abort
0744: // the transaction.
0745: abort(request.getTransactionId(), true, false);
0746: msg = Translate
0747: .get(
0748: "loadbalancer.request.failed.and.abort",
0749: new String[] {
0750: request
0751: .getSqlShortForm(getSqlShortFormLength()),
0752: e.getMessage() });
0753: }
0754: logger.warn(msg);
0755: if (sqlMonitor != null && sqlMonitor.isActive())
0756: sqlMonitor.logError(request);
0757: throw e;
0758: }
0759: }
0760:
0761: /**
0762: * Call a stored procedure that returns a ResultSet.
0763: *
0764: * @param proc the stored procedure call
0765: * @return a <code>java.sql.ResultSet</code> value
0766: * @exception SQLException if an error occurs
0767: */
0768: protected ControllerResultSet callableStatementExecuteQuery(
0769: StoredProcedure proc) throws SQLException {
0770: if (proc == null) {
0771: String msg = "Request failed (null stored procedure received)";
0772: logger.warn(msg);
0773: throw new SQLException(msg);
0774: }
0775:
0776: try {
0777: if (requestLogger.isInfoEnabled())
0778: requestLogger.info("S " + proc.getId() + " "
0779: + proc.getTransactionId() + " "
0780: + proc.getUniqueKey());
0781:
0782: proc.setStartTime(System.currentTimeMillis());
0783:
0784: ControllerResultSet rs = requestManager
0785: .callableStatementExecuteQuery(proc);
0786:
0787: proc.setEndTime(System.currentTimeMillis());
0788: if (sqlMonitor != null && sqlMonitor.isActive())
0789: sqlMonitor.logRequestTime(proc);
0790:
0791: return rs;
0792: } catch (AllBackendsFailedException e) {
0793: String msg = Translate
0794: .get(
0795: "loadbalancer.storedprocedure.failed.on.all.backends",
0796: new String[] {
0797: String.valueOf(proc.getId()),
0798: e.getMessage() });
0799: if (!proc.isAutoCommit()) {
0800: // If the request fails in a transaction, the transaction is likely
0801: // to be rollbacked by the underlying database. Then we have to abort
0802: // the transaction.
0803: abort(proc.getTransactionId(), true, false);
0804: msg = Translate
0805: .get(
0806: "loadbalancer.request.failed.and.abort",
0807: new String[] {
0808: proc
0809: .getSqlShortForm(getSqlShortFormLength()),
0810: e.getMessage() });
0811: }
0812: logger.warn(msg);
0813: if (sqlMonitor != null && sqlMonitor.isActive())
0814: sqlMonitor.logError(proc);
0815: throw new SQLException(msg);
0816: } catch (SQLException e) {
0817: String msg = Translate.get(
0818: "loadbalancer.storedprocedure.failed",
0819: new String[] { String.valueOf(proc.getId()),
0820: e.getMessage() });
0821: if (!proc.isAutoCommit()) {
0822: // If the request fails in a transaction, the transaction is likely
0823: // to be rollbacked by the underlying database. Then we have to abort
0824: // the transaction.
0825: abort(proc.getTransactionId(), true, false);
0826: msg = Translate
0827: .get(
0828: "loadbalancer.request.failed.and.abort",
0829: new String[] {
0830: proc
0831: .getSqlShortForm(getSqlShortFormLength()),
0832: e.getMessage() });
0833: }
0834: logger.warn(msg);
0835: if (sqlMonitor != null && sqlMonitor.isActive())
0836: sqlMonitor.logError(proc);
0837: throw e;
0838: }
0839: }
0840:
0841: /**
0842: * Call a stored procedure that performs an update.
0843: *
0844: * @param proc the stored procedure call
0845: * @return number of rows affected
0846: * @exception SQLException if an error occurs
0847: */
0848: protected ExecuteUpdateResult callableStatementExecuteUpdate(
0849: StoredProcedure proc) throws SQLException {
0850: if (proc == null) {
0851: String msg = "Request failed (null stored procedure received)";
0852: logger.warn(msg);
0853: throw new SQLException(msg);
0854: }
0855:
0856: try {
0857: if (requestLogger.isInfoEnabled())
0858: requestLogger.info("W " + proc.getId() + " "
0859: + proc.getTransactionId() + " "
0860: + proc.getUniqueKey());
0861:
0862: proc.setStartTime(System.currentTimeMillis());
0863:
0864: ExecuteUpdateResult result = requestManager
0865: .callableStatementExecuteUpdate(proc);
0866:
0867: proc.setEndTime(System.currentTimeMillis());
0868: if (sqlMonitor != null && sqlMonitor.isActive())
0869: sqlMonitor.logRequestTime(proc);
0870:
0871: return result;
0872: } catch (AllBackendsFailedException e) {
0873: String msg = Translate
0874: .get(
0875: "loadbalancer.storedprocedure.failed.on.all.backends",
0876: new String[] {
0877: String.valueOf(proc.getId()),
0878: e.getMessage() });
0879: if (!proc.isAutoCommit()) {
0880: // If the request fails in a transaction, the transaction is likely
0881: // to be rollbacked by the underlying database. Then we have to abort
0882: // the transaction.
0883: abort(proc.getTransactionId(), true, false);
0884: msg = Translate
0885: .get(
0886: "loadbalancer.request.failed.and.abort",
0887: new String[] {
0888: proc
0889: .getSqlShortForm(getSqlShortFormLength()),
0890: e.getMessage() });
0891: }
0892: logger.warn(msg);
0893: if (sqlMonitor != null && sqlMonitor.isActive())
0894: sqlMonitor.logError(proc);
0895: throw new SQLException(msg);
0896: } catch (SQLException e) {
0897: String msg = Translate.get(
0898: "loadbalancer.storedprocedure.failed",
0899: new String[] { String.valueOf(proc.getId()),
0900: e.getMessage() });
0901: if (!proc.isAutoCommit()) {
0902: // If the request fails in a transaction, the transaction is likely
0903: // to be rollbacked by the underlying database. Then we have to abort
0904: // the transaction.
0905: abort(proc.getTransactionId(), true, false);
0906: msg = Translate
0907: .get(
0908: "loadbalancer.request.failed.and.abort",
0909: new String[] {
0910: proc
0911: .getSqlShortForm(getSqlShortFormLength()),
0912: e.getMessage() });
0913: }
0914: logger.warn(msg);
0915: if (sqlMonitor != null && sqlMonitor.isActive())
0916: sqlMonitor.logError(proc);
0917: throw e;
0918: }
0919: }
0920:
0921: /**
0922: * Execute a call to CallableStatement.execute() and returns a suite of
0923: * updateCount and/or ResultSets.
0924: *
0925: * @param proc the stored procedure to execute
0926: * @return an <code>ExecuteResult</code> object
0927: * @exception SQLException if an error occurs
0928: */
0929: protected ExecuteResult callableStatementExecute(
0930: StoredProcedure proc) throws SQLException {
0931: if (proc == null) {
0932: String msg = "Request failed (null stored procedure received)";
0933: logger.warn(msg);
0934: throw new SQLException(msg);
0935: }
0936:
0937: try {
0938: if (requestLogger.isInfoEnabled())
0939: requestLogger.info("E " + proc.getId() + " "
0940: + proc.getTransactionId() + " "
0941: + proc.getUniqueKey());
0942:
0943: proc.setStartTime(System.currentTimeMillis());
0944:
0945: ExecuteResult result = requestManager
0946: .callableStatementExecute(proc);
0947:
0948: proc.setEndTime(System.currentTimeMillis());
0949: if (sqlMonitor != null && sqlMonitor.isActive())
0950: sqlMonitor.logRequestTime(proc);
0951:
0952: return result;
0953: } catch (AllBackendsFailedException e) {
0954: String msg = Translate
0955: .get(
0956: "loadbalancer.storedprocedure.failed.on.all.backends",
0957: new String[] {
0958: String.valueOf(proc.getId()),
0959: e.getMessage() });
0960: if (!proc.isAutoCommit()) {
0961: // If the request fails in a transaction, the transaction is likely
0962: // to be rollbacked by the underlying database. Then we have to abort
0963: // the transaction.
0964: abort(proc.getTransactionId(), true, false);
0965: msg = Translate
0966: .get(
0967: "loadbalancer.request.failed.and.abort",
0968: new String[] {
0969: proc
0970: .getSqlShortForm(getSqlShortFormLength()),
0971: e.getMessage() });
0972: }
0973: logger.warn(msg);
0974: if (sqlMonitor != null && sqlMonitor.isActive())
0975: sqlMonitor.logError(proc);
0976: throw new SQLException(msg);
0977: } catch (SQLException e) {
0978: String msg = Translate.get(
0979: "loadbalancer.storedprocedure.failed",
0980: new String[] { String.valueOf(proc.getId()),
0981: e.getMessage() });
0982: if (!proc.isAutoCommit()) {
0983: // If the request fails in a transaction, the transaction is likely
0984: // to be rollbacked by the underlying database. Then we have to abort
0985: // the transaction.
0986: abort(proc.getTransactionId(), true, false);
0987: msg = Translate
0988: .get(
0989: "loadbalancer.request.failed.and.abort",
0990: new String[] {
0991: proc
0992: .getSqlShortForm(getSqlShortFormLength()),
0993: e.getMessage() });
0994: }
0995: logger.warn(msg);
0996: if (sqlMonitor != null && sqlMonitor.isActive())
0997: sqlMonitor.logError(proc);
0998: throw e;
0999: }
1000: }
1001:
1002: /**
1003: * Close the given persistent connection.
1004: *
1005: * @param login login to use to retrieve the right connection pool
1006: * @param persistentConnectionId id of the persistent connection to close
1007: */
1008: public void closePersistentConnection(String login,
1009: long persistentConnectionId) {
1010: requestManager.closePersistentConnection(login,
1011: persistentConnectionId);
1012: }
1013:
1014: /**
1015: * Returns true if the virtual database has opened the given persistent
1016: * connection.
1017: *
1018: * @param persistentConnectionId id of the persistent connection to check
1019: * @return true if the connection is open
1020: */
1021: public boolean hasPersistentConnection(long persistentConnectionId) {
1022: return requestManager
1023: .hasPersistentConnection(persistentConnectionId);
1024: }
1025:
1026: /**
1027: * Open the given persistent connection.
1028: *
1029: * @param login login to use to retrieve the right connection pool
1030: * @param persistentConnectionId id of the persistent connection to open
1031: * @throws SQLException if an error occurs while opening the connection
1032: */
1033: public void openPersistentConnection(String login,
1034: long persistentConnectionId) throws SQLException {
1035: requestManager.openPersistentConnection(login,
1036: persistentConnectionId, null);
1037: }
1038:
1039: /**
1040: * Notify the failover of the given transaction (really useful for
1041: * DistributedVirtualDatabase)
1042: *
1043: * @param currentTid transaction id
1044: */
1045: public void failoverForTransaction(long currentTid) {
1046: logger
1047: .info("Transparent client failover operated for transaction "
1048: + currentTid);
1049: }
1050:
1051: /**
1052: * Notify the failover of the given persistent connection (really useful for
1053: * DistributedVirtualDatabase)
1054: *
1055: * @param persistentConnectionId the persistent connection id
1056: */
1057: public void failoverForPersistentConnection(
1058: long persistentConnectionId) {
1059: // This should never happen when we have a single controller since when we
1060: // die there is no one to fail over.
1061: logger
1062: .info("Unexpected transparent client failover operated for persistent connection "
1063: + persistentConnectionId);
1064: }
1065:
1066: protected final Object CONNECTION_ID_SYNC_OBJECT = new Object();
1067:
1068: /**
1069: * Return the next connection identifier (monotically increasing number).
1070: *
1071: * @return a connection identifier
1072: */
1073: public long getNextConnectionId() {
1074: synchronized (CONNECTION_ID_SYNC_OBJECT) {
1075: return connectionId++;
1076: }
1077: }
1078:
1079: /**
1080: * Return the next request identifier (monotically increasing number).
1081: *
1082: * @return a request identifier
1083: */
1084: public long getNextRequestId() {
1085: return requestManager.getNextRequestId();
1086: }
1087:
1088: /**
1089: * Return a ControllerResultSet containing the PreparedStatement metaData of
1090: * the given sql template
1091: *
1092: * @param request the request containing the sql template
1093: * @return an empty ControllerResultSet with the metadata
1094: * @throws SQLException if a database error occurs
1095: */
1096: public ControllerResultSet getPreparedStatementGetMetaData(
1097: AbstractRequest request) throws SQLException {
1098: try {
1099: return requestManager
1100: .getPreparedStatementGetMetaData(request);
1101: } catch (NoMoreBackendException e) {
1102: throw e;
1103: }
1104: }
1105:
1106: /*
1107: * Transaction management
1108: */
1109:
1110: /**
1111: * Begins a new transaction and returns the corresponding transaction
1112: * identifier. This method is called from the driver when
1113: * {@link org.continuent.sequoia.driver.Connection#setAutoCommit(boolean)}is
1114: * called with <code>false</code> argument.
1115: * <p>
1116: * Note that the transaction begin is not logged in the recovery log by this
1117: * method, you will have to call logLazyTransactionBegin.
1118: *
1119: * @param login the login used by the connection
1120: * @param isPersistentConnection true if the transaction is started on a
1121: * persistent connection
1122: * @param persistentConnectionId persistent connection id if the transaction
1123: * must be started on a persistent connection
1124: * @return an unique transaction identifier
1125: * @exception SQLException if an error occurs
1126: * @see RequestManager#logLazyTransactionBegin(long)
1127: */
1128: public long begin(String login, boolean isPersistentConnection,
1129: long persistentConnectionId) throws SQLException {
1130: try {
1131: long tid = requestManager.begin(login,
1132: isPersistentConnection, persistentConnectionId);
1133: if (requestLogger.isInfoEnabled())
1134: requestLogger.info("B " + tid);
1135: return tid;
1136: } catch (SQLException e) {
1137: String msg = "Begin failed (" + e.getMessage() + ")";
1138: logger.warn(msg);
1139: throw e;
1140: }
1141: }
1142:
1143: /**
1144: * Abort a transaction that has been started but in which no query was
1145: * executed. As we use lazy transaction begin, there is no need to rollback
1146: * such transaction but just to cleanup the metadata associated with this not
1147: * effectively started transaction.
1148: *
1149: * @param transactionId id of the transaction to abort
1150: * @param logAbort true if the abort (in fact rollback) should be logged in
1151: * the recovery log
1152: * @param forceAbort true if the abort will be forced. Actually, abort will do
1153: * nothing when a transaction has savepoints (we do not abort the
1154: * whole transaction, so that the user can rollback to a previous
1155: * savepoint), except when the connection is closed. In this last
1156: * case, if the transaction is not aborted, it prevents future
1157: * maintenance operations such as shutdowns, enable/disable from
1158: * completing, so we have to force this abort operation. It also
1159: * applies to the DeadlockDetectionThread and the cleanup of the
1160: * VirtualDatabaseWorkerThread.
1161: * @throws SQLException if an error occurs
1162: */
1163: public void abort(long transactionId, boolean logAbort,
1164: boolean forceAbort) throws SQLException {
1165: requestManager.abort(transactionId, logAbort, forceAbort);
1166: // Emulate this as a rollback for the RequestPlayer
1167: if (requestLogger.isInfoEnabled())
1168: requestLogger.info("R " + transactionId);
1169: }
1170:
1171: /**
1172: * Commits a transaction given its id.
1173: *
1174: * @param transactionId the transaction id
1175: * @param logCommit true if the commit should be logged in the recovery log
1176: * @param emptyTransaction true if this transaction has not executed any
1177: * request
1178: * @exception SQLException if an error occurs
1179: */
1180: public void commit(long transactionId, boolean logCommit,
1181: boolean emptyTransaction) throws SQLException {
1182: try {
1183: if (requestLogger.isInfoEnabled())
1184: requestLogger.info("C " + transactionId);
1185: requestManager.commit(transactionId, logCommit,
1186: emptyTransaction);
1187: } catch (SQLException e) {
1188:
1189: String msg = "Commit of transaction '" + transactionId
1190: + "' failed (" + e.getMessage() + ")";
1191:
1192: // If the commit fails in a transaction, the transaction is likely
1193: // to be rollbacked by the underlying database. Then we have to abort
1194: // the transaction.
1195: msg = Translate.get("loadbalancer.commit.failed.and.abort",
1196: new String[] { Long.toString(transactionId),
1197: e.getMessage() });
1198: try {
1199: abort(transactionId, true, false);
1200: } catch (SQLException e1) {
1201: if (logger.isInfoEnabled())
1202: logger
1203: .info(
1204: "Abort after commit failure in transaction did not succeed probably because the transaction has already been aborted",
1205: e);
1206: }
1207:
1208: logger.warn(msg);
1209: throw e;
1210: }
1211: }
1212:
1213: /**
1214: * Rollbacks a transaction given its id.
1215: *
1216: * @param transactionId the transaction id
1217: * @param logRollback true if the rollback should be logged in the recovery
1218: * log
1219: * @exception SQLException if an error occurs
1220: */
1221: public void rollback(long transactionId, boolean logRollback)
1222: throws SQLException {
1223: try {
1224: if (requestLogger.isInfoEnabled())
1225: requestLogger.info("R " + transactionId);
1226: requestManager.rollback(transactionId, logRollback);
1227: } catch (SQLException e) {
1228: String msg = "Rollback of transaction '" + transactionId
1229: + "' failed (" + e.getMessage() + ")";
1230:
1231: // If the rollback fails in a transaction, the transaction is likely
1232: // to be rollbacked by the underlying database. Then we have to abort
1233: // the transaction.
1234: msg = Translate.get(
1235: "loadbalancer.rollback.failed.and.abort",
1236: new String[] { Long.toString(transactionId),
1237: e.getMessage() });
1238: try {
1239: abort(transactionId, true, false);
1240: } catch (SQLException e1) {
1241: if (logger.isInfoEnabled())
1242: logger
1243: .info(
1244: "Abort after rollback failure in transaction did not succeed probably because the transaction has already been aborted",
1245: e);
1246: }
1247:
1248: logger.warn(msg);
1249: throw e;
1250: }
1251: }
1252:
1253: /**
1254: * Rollbacks a transaction given its id to a savepoint given its name
1255: *
1256: * @param transactionId the transaction id
1257: * @param savepointName the name of the savepoint
1258: * @exception SQLException if an error occurs
1259: */
1260: public void rollback(long transactionId, String savepointName)
1261: throws SQLException {
1262: try {
1263: if (requestLogger.isInfoEnabled())
1264: requestLogger.info("R " + transactionId + " "
1265: + savepointName);
1266: requestManager.rollback(transactionId, savepointName);
1267: } catch (SQLException e) {
1268: String msg = "Rollback to savepoint '" + savepointName
1269: + "' for " + "transaction '" + transactionId
1270: + "' failed (" + e.getMessage() + ")";
1271:
1272: // If the rollback fails in a transaction, the transaction is likely
1273: // to be rollbacked by the underlying database. Then we have to abort
1274: // the transaction.
1275: msg = Translate.get(
1276: "loadbalancer.rollback.failed.and.abort",
1277: new String[] { Long.toString(transactionId),
1278: e.getMessage() });
1279: try {
1280: abort(transactionId, true, false);
1281: } catch (SQLException e1) {
1282: if (logger.isInfoEnabled())
1283: logger
1284: .info(
1285: "Abort after rollback failure in transaction did not succeed probably because the transaction has already been aborted",
1286: e);
1287: }
1288:
1289: logger.warn(msg);
1290: throw e;
1291: }
1292: }
1293:
1294: /**
1295: * Sets a unnamed savepoint to a transaction given its id.
1296: *
1297: * @param transactionId the transaction id
1298: * @return the savepoint id
1299: * @exception SQLException if an error occurs
1300: */
1301: public int setSavepoint(long transactionId) throws SQLException {
1302: try {
1303: int savepointId = requestManager
1304: .setSavepoint(transactionId);
1305: if (requestLogger.isInfoEnabled())
1306: requestLogger.info("P " + transactionId + " "
1307: + savepointId);
1308: return savepointId;
1309: } catch (SQLException e) {
1310: String msg = "Setting unnamed savepoint to transaction '"
1311: + transactionId + "' failed (" + e.getMessage()
1312: + ")";
1313: logger.warn(msg);
1314: throw e;
1315: }
1316: }
1317:
1318: /**
1319: * Sets a savepoint given its desired name to a transaction given its id.
1320: *
1321: * @param transactionId the transaction id
1322: * @param name the desired name of the savepoint
1323: * @exception SQLException if an error occurs
1324: */
1325: public void setSavepoint(long transactionId, String name)
1326: throws SQLException {
1327: try {
1328: if (requestLogger.isInfoEnabled())
1329: requestLogger.info("P " + transactionId + " " + name);
1330: requestManager.setSavepoint(transactionId, name);
1331: } catch (SQLException e) {
1332: String msg = "Setting savepoint with name '" + name
1333: + "' to transaction " + "'" + transactionId
1334: + "' failed (" + e.getMessage() + ")";
1335: logger.warn(msg);
1336: throw e;
1337: }
1338: }
1339:
1340: /**
1341: * Releases a savepoint given its name from a transaction given its id.
1342: *
1343: * @param transactionId the transaction id
1344: * @param savepointName the name of the savepoint
1345: * @exception SQLException if an error occurs
1346: */
1347: public void releaseSavepoint(long transactionId,
1348: String savepointName) throws SQLException {
1349: try {
1350: if (requestLogger.isInfoEnabled())
1351: requestLogger.info("F " + transactionId + " "
1352: + savepointName);
1353: requestManager.releaseSavepoint(transactionId,
1354: savepointName);
1355: } catch (SQLException e) {
1356: String msg = "Releasing savepoint with name '"
1357: + savepointName + "' from " + "transaction '"
1358: + transactionId + "' failed (" + e.getMessage()
1359: + ")";
1360: logger.warn(msg);
1361: throw e;
1362: }
1363: }
1364:
1365: //
1366: // Database backends management
1367: //
1368:
1369: /**
1370: * Add a backend to this virtual database.
1371: *
1372: * @param db the database backend to add
1373: * @throws VirtualDatabaseException if an error occurs
1374: */
1375: public void addBackend(DatabaseBackend db)
1376: throws VirtualDatabaseException {
1377: this .addBackend(db, true);
1378: }
1379:
1380: /**
1381: * Add a backend to this virtual database.
1382: *
1383: * @param db the database backend to add
1384: * @param checkForCompliance should load the driver ?
1385: * @throws VirtualDatabaseException if an error occurs
1386: */
1387: public void addBackend(DatabaseBackend db,
1388: boolean checkForCompliance) throws VirtualDatabaseException {
1389: if (db == null) {
1390: String msg = "Illegal null database backend in addBackend(DatabaseBackend) method";
1391: logger.error(msg);
1392: throw new VirtualDatabaseException(msg);
1393: }
1394:
1395: if (db.isReadEnabled()) {
1396: String msg = "It is not allowed to add an enabled database.";
1397: logger.error(msg);
1398: throw new VirtualDatabaseException(msg);
1399: }
1400:
1401: // Get the lock on the list of backends
1402: try {
1403: rwLock.acquireWrite();
1404: } catch (InterruptedException e) {
1405: String msg = Translate
1406: .get(
1407: "loadbalancer.backendlist.acquire.writelock.failed",
1408: e);
1409: logger.error(msg);
1410: throw new VirtualDatabaseException(msg);
1411: }
1412:
1413: // Check that the backend is not already up
1414: if (backends.indexOf(db) != -1) {
1415: rwLock.releaseWrite();
1416: String msg = "Duplicate backend " + db.getURL();
1417: logger.warn(msg);
1418: throw new VirtualDatabaseException(msg);
1419: }
1420:
1421: // Check the authentication manager has all virtual logins defined
1422: ArrayList logins = authenticationManager.getVirtualLogins();
1423: VirtualDatabaseUser vdu;
1424: String login;
1425: for (int i = 0; i < logins.size(); i++) {
1426: vdu = (VirtualDatabaseUser) logins.get(i);
1427: login = vdu.getLogin();
1428: if (db.getConnectionManager(login) == null) {
1429: rwLock.releaseWrite();
1430: throw new VirtualDatabaseException(Translate.get(
1431: "backend.missing.connection.manager", login));
1432: }
1433: }
1434:
1435: // Initialize the driver and check the compliance
1436: try {
1437: if (logger.isDebugEnabled())
1438: logger.debug("Checking driver compliance");
1439: if (checkForCompliance)
1440: db.checkDriverCompliance(); // Also loads the driver
1441: } catch (Exception e) {
1442: rwLock.releaseWrite();
1443: String msg = "Error while adding database backend "
1444: + db.getName() + " (" + e + ")";
1445: logger.warn(msg);
1446: throw new VirtualDatabaseException(msg);
1447: }
1448:
1449: db.setSqlShortFormLength(getSqlShortFormLength());
1450:
1451: // Add the backend to the list
1452: backends.add(db);
1453: if (logger.isDebugEnabled())
1454: logger.debug("Backend " + db.getName()
1455: + " added successfully");
1456:
1457: // Set the backend state listener so that the state is logged into the
1458: // recovery log - if any. When there is no recovery log,
1459: // getBackendStateListener returns null, and stateListener is consequently
1460: // set to null. Looks like the only state listner ever is the recoveryLog.
1461: /*
1462: * Note: getRequestManager() is null, at load time, thus the test. At load
1463: * time, if there is a recovery log, the state listner eventually gets set,
1464: * but in a different fashion: it is set by RequestManager c'tor, when
1465: * calling setRecoveryLog().
1466: */
1467: if (getRequestManager() != null) {
1468: db.setStateListener(getRequestManager()
1469: .getBackendStateListener());
1470: }
1471:
1472: // Release the lock
1473: rwLock.releaseWrite();
1474:
1475: // Notify Jmx listeners of the backend addition
1476: sendJmxNotification(
1477: SequoiaNotificationList.VIRTUALDATABASE_BACKEND_ADDED,
1478: Translate.get("notification.backend.added", db
1479: .getName()));
1480:
1481: // Add backend mbean to jmx server
1482: if (MBeanServerManager.isJmxEnabled()) {
1483: try {
1484: ObjectName objectName = JmxConstants
1485: .getDatabaseBackendObjectName(name, db
1486: .getName());
1487: org.continuent.sequoia.controller.backend.management.DatabaseBackend managingBackend = new org.continuent.sequoia.controller.backend.management.DatabaseBackend(
1488: db);
1489: db.setNotificationBroadcaster(managingBackend
1490: .getBroadcaster());
1491: MBeanServerManager.registerMBean(managingBackend,
1492: objectName);
1493: } catch (Exception e) {
1494: logger.error(Translate.get(
1495: "virtualdatabase.fail.register.backend.mbean",
1496: db.getName()), e);
1497: }
1498: }
1499: }
1500:
1501: /**
1502: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#forceDisableBackend(String)
1503: */
1504: public void forceDisableBackend(String backendName)
1505: throws VirtualDatabaseException {
1506: try {
1507: DatabaseBackend db = getAndCheckBackend(backendName,
1508: CHECK_BACKEND_DISABLE);
1509: requestManager.disableBackend(db, true);
1510: requestManager
1511: .setDatabaseSchema(
1512: getDatabaseSchemaFromActiveBackendsAndRefreshDatabaseProductNames(),
1513: false);
1514:
1515: sendJmxNotification(
1516: SequoiaNotificationList.VIRTUALDATABASE_BACKEND_DISABLED,
1517: Translate.get("notification.backend.disabled", db
1518: .getName()));
1519: } catch (Exception e) {
1520: logger.error("An error occured while disabling backend "
1521: + backendName + " (" + e + ")");
1522: throw new VirtualDatabaseException(e.getMessage(), e);
1523: }
1524: }
1525:
1526: /**
1527: * Prepare this virtual database for shutdown. This turns off all the backends
1528: * by cutting communication from this database. This does not prevents other
1529: * virtual database to use shared backends. This doesn't create checkpoints
1530: * either.
1531: *
1532: * @param forceEnable true if backend disabling must be forced, false for
1533: * regular/clean disabling
1534: * @throws VirtualDatabaseException if an error occurs
1535: */
1536: public void disableAllBackends(boolean forceEnable)
1537: throws VirtualDatabaseException {
1538: try {
1539: int size = this .backends.size();
1540: DatabaseBackend dbe;
1541: for (int i = 0; i < size; i++) {
1542: dbe = (DatabaseBackend) backends.get(i);
1543: if (dbe.isReadEnabled())
1544: requestManager.disableBackend(getAndCheckBackend(
1545: dbe.getName(), CHECK_BACKEND_DISABLE),
1546: forceEnable);
1547: }
1548: } catch (Exception e) {
1549: throw new VirtualDatabaseException(e.getMessage(), e);
1550: }
1551: }
1552:
1553: /**
1554: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#disableBackendWithCheckpoint(String)
1555: */
1556: public void disableBackendWithCheckpoint(String backendName)
1557: throws VirtualDatabaseException {
1558: try {
1559: DatabaseBackend backend = getAndCheckBackend(backendName,
1560: NO_CHECK_BACKEND);
1561: if (backend.isDisabled()) {
1562: logger.info("Backend " + backendName
1563: + " is already disabled.");
1564: // disabling a disabled backend is a no-op
1565: return;
1566: }
1567: requestManager.disableBackendWithCheckpoint(backend,
1568: buildCheckpointName("disable " + backendName));
1569: // Force a schema refresh if we are not in RAIDb-1
1570: if (requestManager.getLoadBalancer().getRAIDbLevel() != RAIDbLevels.RAIDb1)
1571: requestManager
1572: .setDatabaseSchema(
1573: getDatabaseSchemaFromActiveBackendsAndRefreshDatabaseProductNames(),
1574: false);
1575: } catch (Exception e) {
1576: logger.error("An error occured while disabling backend "
1577: + backendName + " (" + e + ")");
1578: throw new VirtualDatabaseException(e.getMessage(), e);
1579: }
1580: }
1581:
1582: /**
1583: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#disableAllBackendsWithCheckpoint(java.lang.String)
1584: */
1585: public void disableAllBackendsWithCheckpoint(String checkpoint)
1586: throws VirtualDatabaseException {
1587: if (checkpoint == null) {
1588: disableAllBackends(false);
1589: return;
1590: }
1591:
1592: try {
1593: this .acquireReadLockBackendLists();
1594: } catch (InterruptedException e) {
1595: throw new VirtualDatabaseException(e.getMessage(), e);
1596: }
1597:
1598: try {
1599: ArrayList backendInfos = new ArrayList();
1600: Iterator iter = backends.iterator();
1601: while (iter.hasNext()) {
1602: DatabaseBackend backend = (DatabaseBackend) iter.next();
1603: backendInfos.add(new BackendInfo(backend));
1604: }
1605: requestManager.disableBackendsWithCheckpoint(backendInfos,
1606: checkpoint);
1607: } catch (Exception e) {
1608: throw new VirtualDatabaseException(e.getMessage(), e);
1609: } finally {
1610: this .releaseReadLockBackendLists();
1611: }
1612: }
1613:
1614: /**
1615: * Check that the virtual database is not resyncing its recovery log or
1616: * shutting down. This would prevent enable operations to take place.
1617: *
1618: * @throws VirtualDatabaseException if virtual database is resyncing or
1619: * shutting down.
1620: */
1621: private void enableBackendSanityChecks()
1622: throws VirtualDatabaseException {
1623: if (isResyncing()) {
1624: String msg = Translate
1625: .get("virtualdatabase.fail.enable.cause.resyncing");
1626: logger.warn(msg);
1627: throw new VirtualDatabaseException(msg);
1628: }
1629:
1630: if (isShuttingDown()) {
1631: String msg = Translate
1632: .get("virtualdatabase.fail.enable.cause.shutdown");
1633: logger.warn(msg);
1634: throw new VirtualDatabaseException(msg);
1635: }
1636: }
1637:
1638: /**
1639: * Enable the given backend from the given checkpoint. This method returns
1640: * once the recovery is complete.
1641: *
1642: * @param backendName backend to enable
1643: * @param checkpointName checkpoint to enable from
1644: * @throws VirtualDatabaseException if an error occurs
1645: */
1646: public void enableBackendFromCheckpoint(String backendName,
1647: String checkpointName) throws VirtualDatabaseException {
1648: enableBackendSanityChecks();
1649:
1650: EnableBackendOperation enableOperation = new EnableBackendOperation(
1651: backendName);
1652: addAdminOperation(enableOperation);
1653:
1654: // Call the Request Manager
1655: try {
1656: DatabaseBackend backend = getAndCheckBackend(backendName,
1657: CHECK_BACKEND_ENABLE);
1658: RecoverThread recoverThread = requestManager
1659: .enableBackendFromCheckpoint(backend,
1660: checkpointName);
1661: // Wait for recovery to complete
1662: recoverThread.join();
1663: if (recoverThread.getException() != null) {
1664: throw recoverThread.getException();
1665: }
1666: requestManager.setSchemaIsDirty(true);
1667:
1668: // Update the static metadata
1669: getStaticMetaData().gatherStaticMetadata(backend);
1670:
1671: // Update the list of database product names
1672: if (databaseProductNames.indexOf(backend
1673: .getDatabaseProductName()) == -1)
1674: databaseProductNames += ","
1675: + backend.getDatabaseProductName();
1676: } catch (Exception e) {
1677: String msg = Translate.get(
1678: "virtualdatabase.enable.from.checkpoint.failed",
1679: new Object[] { name, backendName, e });
1680: logger.warn(msg, e);
1681: throw new VirtualDatabaseException(msg, e);
1682: } finally {
1683: removeAdminOperation(enableOperation);
1684: }
1685: }
1686:
1687: /**
1688: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#enableBackendFromCheckpoint(java.lang.String)
1689: */
1690: public void enableBackendFromCheckpoint(String backendName)
1691: throws VirtualDatabaseException {
1692: enableBackendSanityChecks();
1693:
1694: DatabaseBackend backend = getAndCheckBackend(backendName,
1695: NO_CHECK_BACKEND);
1696: String checkpoint = backend.getLastKnownCheckpoint();
1697: if ((checkpoint == null) || ("".equals(checkpoint)))
1698: throw new VirtualDatabaseException(
1699: "Cannot enable backend "
1700: + backendName
1701: + " from a known state. Resynchronize this backend by restoring a dump.");
1702: else {
1703: if (logger.isDebugEnabled())
1704: logger.debug("Enabling backend " + backendName
1705: + " from its last checkpoint "
1706: + backend.getLastKnownCheckpoint());
1707: }
1708: enableBackendFromCheckpoint(backendName, backend
1709: .getLastKnownCheckpoint());
1710: }
1711:
1712: /**
1713: * Enable all the backends without any check.
1714: *
1715: * @throws VirtualDatabaseException if fails
1716: */
1717: public void enableAllBackends() throws VirtualDatabaseException {
1718: enableBackendSanityChecks();
1719:
1720: try {
1721: int size = this .backends.size();
1722: DatabaseBackend dbe;
1723: for (int i = 0; i < size; i++) {
1724: dbe = (DatabaseBackend) backends.get(i);
1725: if (!dbe.isReadEnabled())
1726: forceEnableBackend(((DatabaseBackend) backends
1727: .get(i)).getName());
1728: }
1729: } catch (RuntimeException e) {
1730: logger.error("Runtime error in enableAllBackends", e);
1731: throw new VirtualDatabaseException(e.getMessage(), e);
1732: }
1733: }
1734:
1735: /**
1736: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#enableAllBackendsFromCheckpoint()
1737: */
1738: public void enableAllBackendsFromCheckpoint()
1739: throws VirtualDatabaseException {
1740: RecoveryLog log = requestManager.getRecoveryLog();
1741: if (log == null) {// If no recovery log is defined ignore fallback to a forced enable
1742: logger
1743: .warn("No recovery log has been configured, enabling backend without checkpoint.");
1744: enableAllBackends();
1745: } else {
1746: enableBackendSanityChecks();
1747:
1748: try {
1749: int size = this .backends.size();
1750: DatabaseBackend dbe;
1751: String backendName;
1752: BackendRecoveryInfo info;
1753: for (int i = 0; i < size; i++) {
1754: dbe = (DatabaseBackend) backends.get(i);
1755: backendName = dbe.getName();
1756: info = log
1757: .getBackendRecoveryInfo(name, backendName);
1758: switch (info.getBackendState()) {
1759: case BackendState.DISABLED:
1760: String checkpoint = info.getCheckpoint();
1761: if (checkpoint == null || checkpoint.equals("")) {
1762: logger
1763: .warn("Cannot enable backend "
1764: + backendName
1765: + " from a known state. Resynchronize this backend by restoring a dump.");
1766: } else {
1767: logger.info("Enabling backend "
1768: + backendName + " from checkpoint "
1769: + checkpoint);
1770: enableBackendFromCheckpoint(dbe.getName(),
1771: checkpoint);
1772: }
1773: continue;
1774: case BackendState.UNKNOWN:
1775: logger.info("Unknown last state for backend "
1776: + backendName
1777: + ". Leaving node in "
1778: + (dbe.isReadEnabled() ? "enabled"
1779: : "disabled") + " state.");
1780: continue;
1781: case BackendState.BACKUPING:
1782: case BackendState.DISABLING:
1783: case BackendState.RESTORING:
1784: case BackendState.REPLAYING:
1785: if (!dbe.isReadEnabled()) {
1786: logger
1787: .info("Unexpected transition state ("
1788: + info.getBackendState()
1789: + ") for backend "
1790: + backendName
1791: + ". Forcing backend to disabled state.");
1792: info.setBackendState(BackendState.DISABLED);
1793: log.storeBackendRecoveryInfo(name, info);
1794: } else
1795: logger
1796: .info("Unexpected transition state ("
1797: + info.getBackendState()
1798: + ") for backend "
1799: + backendName
1800: + ". Leaving backend in its current state.");
1801: continue;
1802: default:
1803: if (!dbe.isReadEnabled()) {
1804: logger
1805: .info("Unexpected enabled state ("
1806: + info.getBackendState()
1807: + ") for backend "
1808: + backendName
1809: + ". Forcing backend to disabled state.");
1810: info.setBackendState(BackendState.DISABLED);
1811: log.storeBackendRecoveryInfo(name, info);
1812: } else
1813: logger
1814: .info("Unexpected enabled state ("
1815: + info.getBackendState()
1816: + ") for backend "
1817: + backendName
1818: + ". Leaving backend in its current state.");
1819: continue;
1820: }
1821: }
1822: } catch (Exception e) {
1823: throw new VirtualDatabaseException(e.getMessage(), e);
1824: }
1825: }
1826: }
1827:
1828: /**
1829: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#forceEnableBackend(String)
1830: */
1831: public void forceEnableBackend(String backendName)
1832: throws VirtualDatabaseException {
1833: enableBackendSanityChecks();
1834:
1835: EnableBackendOperation enableOperation = new EnableBackendOperation(
1836: backendName + " (force)");
1837: addAdminOperation(enableOperation);
1838:
1839: // Call the Request Manager
1840: try {
1841: DatabaseBackend backend = getAndCheckBackend(backendName,
1842: CHECK_BACKEND_ENABLE);
1843:
1844: requestManager.enableBackend(backend);
1845: requestManager.setSchemaIsDirty(true);
1846:
1847: // Update the list of database product names
1848: if (databaseProductNames.indexOf(backend
1849: .getDatabaseProductName()) == -1)
1850: databaseProductNames += ","
1851: + backend.getDatabaseProductName();
1852:
1853: // Update the static metadata
1854: getStaticMetaData().gatherStaticMetadata(backend);
1855:
1856: sendJmxNotification(
1857: SequoiaNotificationList.VIRTUALDATABASE_BACKEND_ENABLED,
1858: Translate.get("notification.backend.enabled"));
1859: } catch (Exception e) {
1860: throw new VirtualDatabaseException(e.getMessage(), e);
1861: } finally {
1862: removeAdminOperation(enableOperation);
1863: }
1864: }
1865:
1866: /**
1867: * Prepare this virtual database for startup. This turns on all the backends
1868: * from the given checkpoint. If the checkpoint is null or an empty String,
1869: * the backends are enabled without further check else the backend states are
1870: * overriden to use the provided checkpoint.
1871: *
1872: * @param checkpoint checkpoint for recovery log
1873: * @throws VirtualDatabaseException if fails
1874: */
1875: public void forceEnableAllBackendsFromCheckpoint(String checkpoint)
1876: throws VirtualDatabaseException {
1877: enableBackendSanityChecks();
1878:
1879: if (checkpoint == null || checkpoint.equals(""))
1880: enableAllBackends();
1881: else {
1882: try {
1883: int size = this .backends.size();
1884: DatabaseBackend backend;
1885: for (int i = 0; i < size; i++) {
1886: backend = (DatabaseBackend) backends.get(i);
1887: if (!backend.isReadEnabled()) {
1888: backend.setLastKnownCheckpoint(checkpoint);
1889: enableBackendFromCheckpoint(backend.getName(),
1890: checkpoint);
1891: }
1892: }
1893: } catch (RuntimeException e) {
1894: logger
1895: .error(
1896: "Runtime error in forceEnableAllBackendsFromCheckpoint",
1897: e);
1898: throw new VirtualDatabaseException(e.getMessage(), e);
1899: }
1900: }
1901: }
1902:
1903: /**
1904: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#getAllBackendNames()
1905: */
1906: public ArrayList getAllBackendNames()
1907: throws VirtualDatabaseException {
1908: try {
1909: acquireReadLockBackendLists();
1910: } catch (InterruptedException e) {
1911: String msg = "Unable to acquire read lock on backend list in getAllBackendNames ("
1912: + e + ")";
1913: logger.error(msg);
1914: throw new VirtualDatabaseException(msg);
1915: }
1916:
1917: int size = backends.size();
1918: ArrayList result = new ArrayList();
1919: for (int i = 0; i < size; i++) {
1920: result.add(((DatabaseBackend) backends.get(i)).getName());
1921: }
1922:
1923: releaseReadLockBackendLists();
1924: return result;
1925: }
1926:
1927: /**
1928: * Find the DatabaseBackend corresponding to the given backend name and check
1929: * if it is possible to disable this backend. In the case enable, this method
1930: * also updates the virtual database schema by merging it with the one
1931: * provided by this backend.
1932: *
1933: * @param backendName backend to look for
1934: * @param testEnable NO_CHECK_BACKEND no check is done, CHECK_BACKEND_DISABLE
1935: * check if it is possible to disable the backend,
1936: * CHECK_BACKEND_ENABLE check if it is possible to enable the backend
1937: * @return the backend to disable
1938: * @throws VirtualDatabaseException if an error occurs
1939: */
1940: public DatabaseBackend getAndCheckBackend(String backendName,
1941: int testEnable) throws VirtualDatabaseException {
1942: try {
1943: acquireReadLockBackendLists();
1944: } catch (InterruptedException e) {
1945: String msg = "Unable to acquire read lock on backend list in getAndCheckBackend ("
1946: + e + ")";
1947: logger.error(msg);
1948: throw new VirtualDatabaseException(msg);
1949: }
1950:
1951: DatabaseBackend b;
1952: try {
1953: // Find the backend
1954: int size = backends.size();
1955: b = null;
1956: for (int i = 0; i < size; i++) {
1957: b = (DatabaseBackend) backends.get(i);
1958: if (b.getName().equals(backendName))
1959: break;
1960: else
1961: b = null;
1962: }
1963:
1964: // Check not null
1965: if (b == null) {
1966: String msg = "Trying to access a non-existing backend "
1967: + backendName;
1968: logger.warn(msg);
1969: throw new VirtualDatabaseException(msg);
1970: }
1971:
1972: // Check enable/disable
1973: switch (testEnable) {
1974: case NO_CHECK_BACKEND:
1975: break;
1976: case CHECK_BACKEND_DISABLE:
1977: if (!b.isReadEnabled()) {
1978: String msg = "Backend " + backendName
1979: + " is already disabled";
1980: logger.warn(msg);
1981: throw new VirtualDatabaseException(msg);
1982: }
1983: break;
1984: case CHECK_BACKEND_ENABLE:
1985: if (b.isReadEnabled()) {
1986: String msg = "Backend " + backendName
1987: + " is already enabled";
1988: logger.warn(msg);
1989: throw new VirtualDatabaseException(msg);
1990: }
1991: break;
1992: default:
1993: String msg = "Unexpected parameter in getAndCheckBackend(...)";
1994: logger.error(msg);
1995: throw new VirtualDatabaseException(msg);
1996: }
1997: } finally {
1998: releaseReadLockBackendLists();
1999: }
2000:
2001: if (testEnable == CHECK_BACKEND_ENABLE) {
2002: // Initialize backend for enable
2003: try {
2004: if (logger.isDebugEnabled())
2005: logger
2006: .debug("Initializing connections for backend "
2007: + b.getName());
2008: b.initializeConnections();
2009:
2010: b.checkDriverCompliance();
2011:
2012: if (logger.isDebugEnabled())
2013: logger.debug("Checking schema for backend "
2014: + b.getName());
2015: b.checkDatabaseSchema(null);
2016:
2017: DatabaseSchema backendSchema = b.getDatabaseSchema();
2018:
2019: if (backendSchema != null)
2020: requestManager.mergeDatabaseSchema(backendSchema);
2021: else
2022: logger.warn("Backend " + b.getName()
2023: + " has no defined schema.");
2024: } catch (SQLException e) {
2025: String msg = "Error while initalizing database backend "
2026: + b.getName() + " (" + e + ")";
2027: logger.warn(msg, e);
2028: throw new VirtualDatabaseException(msg, e);
2029: }
2030: }
2031:
2032: return b;
2033: }
2034:
2035: /**
2036: * Returns true if this vdb is resynching. This can only happen for
2037: * distributed vdbs that have recovery logs.
2038: *
2039: * @return true if db is resynching
2040: */
2041: protected boolean isResyncing() {
2042: return false;
2043: }
2044:
2045: /**
2046: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#replicateBackend(java.lang.String,
2047: * java.lang.String, java.util.Map)
2048: */
2049: public void replicateBackend(String backendName,
2050: String newBackendName, Map parameters)
2051: throws VirtualDatabaseException {
2052: // Access the backend we want to replicate
2053: DatabaseBackend backend = getAndCheckBackend(backendName,
2054: NO_CHECK_BACKEND);
2055: DatabaseBackend newBackend = null;
2056:
2057: // Create a clone of the backend with additionnal parameters
2058: try {
2059: newBackend = backend.copy(newBackendName, parameters);
2060: } catch (Exception e) {
2061: String msg = Translate.get(
2062: "virtualdatabase.fail.backend.copy", e);
2063: logger.warn(msg, e);
2064: throw new VirtualDatabaseException(msg, e);
2065: }
2066:
2067: // Add the backend to the virtual database.
2068: addBackend(newBackend);
2069: }
2070:
2071: /**
2072: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#removeBackend(java.lang.String)
2073: */
2074: public void removeBackend(String backend)
2075: throws VirtualDatabaseException {
2076: removeBackend(getAndCheckBackend(backend, NO_CHECK_BACKEND));
2077: }
2078:
2079: /**
2080: * Remove a backend from this virtual database.
2081: *
2082: * @param db the database backend to remove
2083: * @throws VirtualDatabaseException if an error occurs
2084: */
2085: public void removeBackend(DatabaseBackend db)
2086: throws VirtualDatabaseException {
2087: if (db == null) {
2088: String msg = "Illegal null database backend in removeBackend(DatabaseBackend) method";
2089: logger.error(msg);
2090: throw new VirtualDatabaseException(msg);
2091: }
2092:
2093: try {
2094: rwLock.acquireWrite();
2095: } catch (InterruptedException e) {
2096: String msg = Translate
2097: .get(
2098: "loadbalancer.backendlist.acquire.writelock.failed",
2099: e);
2100: logger.error(msg);
2101: throw new VirtualDatabaseException(msg);
2102: }
2103:
2104: // Sanity checks
2105: int idx = backends.indexOf(db);
2106: if (idx == -1) {
2107: rwLock.releaseWrite(); // Release the lock
2108: String msg = "Trying to remove a non-existing backend "
2109: + db.getName();
2110: logger.warn(msg);
2111: throw new VirtualDatabaseException(msg);
2112: }
2113:
2114: if (((DatabaseBackend) backends.get(idx)).isReadEnabled()) {
2115: rwLock.releaseWrite(); // Release the lock
2116: String msg = "Trying to remove an enabled backend "
2117: + db.getName();
2118: logger.error(msg);
2119: throw new VirtualDatabaseException(msg);
2120: }
2121:
2122: // Remove it
2123: backends.remove(idx);
2124: rwLock.releaseWrite(); // Relase the lock
2125:
2126: sendJmxNotification(
2127: SequoiaNotificationList.VIRTUALDATABASE_BACKEND_REMOVED,
2128: Translate.get("notification.backend.removed"));
2129:
2130: // Remove backend mbean to jmx server
2131: if (MBeanServerManager.isJmxEnabled()) {
2132: try {
2133: ObjectName objectName = JmxConstants
2134: .getDatabaseBackendObjectName(name, db
2135: .getName());
2136: MBeanServerManager.unregister(objectName);
2137: } catch (Exception e) {
2138: logger
2139: .error(
2140: Translate
2141: .get(
2142: "virtualdatabase.fail.unregister.backend.mbean",
2143: db.getName()), e);
2144: }
2145: }
2146:
2147: if (logger.isDebugEnabled())
2148: logger.debug("Backend " + db.getName()
2149: + " removed successfully");
2150: }
2151:
2152: /**
2153: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#transferBackend(java.lang.String,
2154: * java.lang.String)
2155: */
2156: public void transferBackend(String backend,
2157: String controllerDestination)
2158: throws VirtualDatabaseException {
2159: throw new VirtualDatabaseException(
2160: "Cannot transfer backend to controller:"
2161: + controllerDestination
2162: + " because database is not distributed");
2163: }
2164:
2165: //
2166: // Backup & Checkpoint management
2167: //
2168:
2169: /**
2170: * Returns a cluster-wide unique checkpoint name.
2171: * <p>
2172: * Unicity is needed since checkpoints are always set cluster-wide.
2173: *
2174: * @param event the reason why this checkpoint is being set
2175: * @return a cluster-wide unique checkpoint name
2176: */
2177: public String buildCheckpointName(String event) {
2178: /*
2179: * Checkpoints name are now built such as they can be sorted alphabetically
2180: */
2181: SimpleDateFormat dateFormat = new SimpleDateFormat(
2182: "yyyyMMddHHmmssSSSZ");
2183: return (event + "-" + controller.getControllerName() + "-" + dateFormat
2184: .format(new Date(System.currentTimeMillis())));
2185: }
2186:
2187: /**
2188: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#backupBackend(String,
2189: * String, String, String, String, String, boolean, ArrayList)
2190: */
2191: public void backupBackend(String backendName, String login,
2192: String password, String dumpName, String backuperName,
2193: String path, boolean force, ArrayList tables)
2194: throws VirtualDatabaseException {
2195: // Sanity checks
2196: if (!isDumpNameAvailable(dumpName)) {
2197: throw new VirtualDatabaseException(Translate.get(
2198: "virtualdatabase.backup.dumpNameError", dumpName));
2199: }
2200: if (!force && (getNumberOfEnabledBackends() == 1)) {
2201: throw new VirtualDatabaseException(Translate
2202: .get("virtualdatabase.backup.onlyOneBackendLeft"));
2203: }
2204:
2205: // Perform the backup
2206: BackupBackendOperation backupOperation = new BackupBackendOperation(
2207: backendName, dumpName);
2208: try {
2209: addAdminOperation(backupOperation);
2210: DatabaseBackend db = getAndCheckBackend(backendName,
2211: NO_CHECK_BACKEND);
2212: requestManager.backupBackend(db, login, password, dumpName,
2213: backuperName, path, tables);
2214: } catch (SQLException sql) {
2215: throw new VirtualDatabaseException(sql);
2216: } finally {
2217: removeAdminOperation(backupOperation);
2218: }
2219: }
2220:
2221: protected int getNumberOfEnabledBackends()
2222: throws VirtualDatabaseException {
2223: // This check is not sufficient. Disable functionality. (see SEQUOIA-556)
2224: if (true)
2225: return -1;
2226:
2227: try {
2228: acquireReadLockBackendLists();
2229: } catch (InterruptedException e) {
2230: String msg = Translate
2231: .get("virtualdatabase.fail.read.lock");
2232: logger.error(msg, e);
2233: throw new VirtualDatabaseException(msg, e);
2234: }
2235:
2236: int nbActive = 0;
2237: DatabaseBackend b;
2238: int size = backends.size();
2239: b = null;
2240: for (int i = 0; i < size; i++) {
2241: b = (DatabaseBackend) backends.get(i);
2242: if (b.isReadEnabled() || b.isWriteEnabled())
2243: // test symetrical to RequestManager.backupBackend()
2244: nbActive++;
2245: }
2246:
2247: releaseReadLockBackendLists();
2248:
2249: return nbActive;
2250: }
2251:
2252: /**
2253: * Checks if the dump name is available. If the <code>dumpName</code> is
2254: * already taken by an existing dump, return <code>false</code>; else
2255: * return <code>true</code>
2256: *
2257: * @param tentativeDumpName tentative dump name we want to check availability
2258: * @return <code>true</code> is the name is available, <code>false</code>
2259: * else.
2260: */
2261: public boolean isDumpNameAvailable(String tentativeDumpName) {
2262: DumpInfo[] dumps;
2263: try {
2264: dumps = getAvailableDumps();
2265: } catch (VirtualDatabaseException e) {
2266: return true;
2267: }
2268: for (int i = 0; i < dumps.length; i++) {
2269: DumpInfo dump = dumps[i];
2270: if (dump.getDumpName().equals(tentativeDumpName)) {
2271: return false;
2272: }
2273: }
2274: return true;
2275: }
2276:
2277: /**
2278: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#copyDump(java.lang.String,
2279: * java.lang.String)
2280: */
2281: public void copyDump(String dumpName, String remoteControllerName)
2282: throws VirtualDatabaseException {
2283: if (!isDistributed())
2284: throw new VirtualDatabaseException(
2285: "can not copy dumps on non-distributed virtual database");
2286: }
2287:
2288: /**
2289: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#copyLogFromCheckpoint(java.lang.String,
2290: * java.lang.String)
2291: */
2292: public void copyLogFromCheckpoint(String dumpName,
2293: String controllerName) throws VirtualDatabaseException {
2294: if (!hasRecoveryLog())
2295: throw new VirtualDatabaseException(Translate
2296: .get("virtualdatabase.no.recovery.log"));
2297: if (!isDistributed())
2298: throw new VirtualDatabaseException(Translate
2299: .get("virtualdatabase.not.distributed"));
2300:
2301: /**
2302: * Implemented in the distributed incarnation of the vdb.
2303: *
2304: * @see org.continuent.sequoia.controller.virtualdatabase.DistributedVirtualDatabase#copyLogFromCheckpoint(String,
2305: * String)
2306: */
2307: }
2308:
2309: /**
2310: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#deleteLogUpToCheckpoint(java.lang.String)
2311: */
2312: public void deleteLogUpToCheckpoint(String checkpointName)
2313: throws VirtualDatabaseException {
2314: if (!hasRecoveryLog())
2315: throw new VirtualDatabaseException(Translate
2316: .get("virtualdatabase.no.recovery.log"));
2317:
2318: try {
2319: getRequestManager().getRecoveryLog()
2320: .deleteLogEntriesBeforeCheckpoint(checkpointName);
2321: } catch (SQLException e) {
2322: throw new VirtualDatabaseException(e);
2323: }
2324: }
2325:
2326: /**
2327: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#getBackuperNames()
2328: */
2329: public String[] getBackuperNames() {
2330: return requestManager.getBackupManager().getBackuperNames();
2331: }
2332:
2333: /**
2334: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#getAvailableDumps()
2335: */
2336: public DumpInfo[] getAvailableDumps()
2337: throws VirtualDatabaseException {
2338: try {
2339: RecoveryLog recoveryLog = requestManager.getRecoveryLog();
2340: if (recoveryLog == null) {
2341: return new DumpInfo[0];
2342: } else {
2343: ArrayList dumps = recoveryLog.getDumpList();
2344: return (DumpInfo[]) dumps.toArray(new DumpInfo[dumps
2345: .size()]);
2346: }
2347: } catch (SQLException e) {
2348: throw new VirtualDatabaseException(e);
2349: }
2350: }
2351:
2352: /**
2353: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#getDumpFormatForBackuper(java.lang.String)
2354: */
2355: public String getDumpFormatForBackuper(String backuperName) {
2356: Backuper backuper = requestManager.getBackupManager()
2357: .getBackuperByName(backuperName);
2358: if (backuper == null) {
2359: return null;
2360: }
2361: return backuper.getDumpFormat();
2362: }
2363:
2364: /**
2365: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#initializeFromBackend(java.lang.String,
2366: * boolean)
2367: */
2368: public void initializeFromBackend(String databaseBackendName,
2369: boolean force) throws VirtualDatabaseException {
2370: RecoveryLog log = requestManager.getRecoveryLog();
2371: if (log == null)
2372: throw new VirtualDatabaseException(Translate
2373: .get("virtualdatabase.no.recovery.log"));
2374:
2375: try {
2376: // Check that all backends are in a disabled state without any last known
2377: // checkpoint
2378: int size = this .backends.size();
2379: DatabaseBackend backendToInitializeFrom = null;
2380: for (int i = 0; i < size; i++) {
2381: DatabaseBackend dbe = (DatabaseBackend) backends.get(i);
2382: String backendName = dbe.getName();
2383: if (backendName.equals(databaseBackendName)) {
2384: backendToInitializeFrom = dbe;
2385: }
2386: if (force)
2387: continue;
2388: BackendRecoveryInfo info = log.getBackendRecoveryInfo(
2389: name, backendName);
2390: if ((info.getBackendState() != BackendState.DISABLED)
2391: && (info.getBackendState() != BackendState.UNKNOWN))
2392: throw new VirtualDatabaseException(
2393: "Backend "
2394: + backendName
2395: + " is not in a disabled state (current state is "
2396: + BackendState.description(info
2397: .getBackendState()) + ")");
2398: String checkpoint = info.getCheckpoint();
2399: if ((checkpoint != null) && !checkpoint.equals(""))
2400: throw new VirtualDatabaseException("Backend "
2401: + backendName
2402: + " has a last known checkpoint ("
2403: + checkpoint + ")");
2404: }
2405: if (backendToInitializeFrom == null) {
2406: throw new VirtualDatabaseException("backend "
2407: + databaseBackendName + " does not exist");
2408: }
2409: // Ok, the backends are in a clean state, clean the recovery log
2410: log.resetRecoveryLog(true);
2411: // set the last known checkpoint to Initial_empty_recovery_log
2412: BackendRecoveryInfo info = log.getBackendRecoveryInfo(name,
2413: databaseBackendName);
2414: backendToInitializeFrom
2415: .setLastKnownCheckpoint("Initial_empty_recovery_log");
2416: backendToInitializeFrom.setState(BackendState.DISABLED);
2417: info.setCheckpoint("Initial_empty_recovery_log");
2418: info.setBackendState(BackendState.DISABLED);
2419: log.storeBackendRecoveryInfo(name, info);
2420: } catch (SQLException e) {
2421: throw new VirtualDatabaseException(e.getMessage());
2422: }
2423: }
2424:
2425: /**
2426: * Remove a checkpoint from the recovery log of this virtual database
2427: *
2428: * @param checkpointName to remove
2429: * @throws VirtualDatabaseException if an error occurs
2430: */
2431: public void removeCheckpoint(String checkpointName)
2432: throws VirtualDatabaseException {
2433: try {
2434: requestManager.removeCheckpoint(checkpointName);
2435: } catch (Exception e) {
2436: throw new VirtualDatabaseException(e.getMessage());
2437: }
2438: }
2439:
2440: /**
2441: * Delete the dump entry associated to the <code>dumpName</code>.<br />
2442: * If <code>keepsFile</code> is false, the dump file is also removed from
2443: * the file system.
2444: *
2445: * @param dumpName name of the dump entry to remove
2446: * @param keepsFile <code>true</code> if the dump should be also removed
2447: * from the file system, <code>false</code> else
2448: * @throws VirtualDatabaseException if an exception occured while removing the
2449: * dump entry or the dump file
2450: */
2451: public void deleteDump(String dumpName, boolean keepsFile)
2452: throws VirtualDatabaseException {
2453: if (dumpName == null) {
2454: throw new VirtualDatabaseException(
2455: "dump name can not be null");
2456: }
2457: RecoveryLog recoveryLog = requestManager.getRecoveryLog();
2458: if (recoveryLog == null) {
2459: throw new VirtualDatabaseException(
2460: "no recovery log for the virtual database"
2461: + getVirtualDatabaseName());
2462: }
2463: DumpInfo dumpInfo;
2464: try {
2465: dumpInfo = recoveryLog.getDumpInfo(dumpName);
2466: } catch (SQLException e) {
2467: throw new VirtualDatabaseException(e);
2468: }
2469: if (dumpInfo == null) {
2470: throw new VirtualDatabaseException(
2471: "Dump of name "
2472: + dumpName
2473: + " not found in the recovery log of virtual database "
2474: + getVirtualDatabaseName());
2475: }
2476: Backuper backuper = requestManager.getBackupManager()
2477: .getBackuperByFormat(dumpInfo.getDumpFormat());
2478: if (backuper == null) {
2479: throw new VirtualDatabaseException(
2480: "No backuper found for format "
2481: + dumpInfo.getDumpFormat()
2482: + " for the virtual database "
2483: + getVirtualDatabaseName());
2484: }
2485: try {
2486: recoveryLog.removeDump(dumpInfo);
2487: } catch (SQLException e) {
2488: throw new VirtualDatabaseException(e);
2489: }
2490: if (!keepsFile) {
2491: try {
2492: backuper.deleteDump(dumpInfo.getDumpPath(), dumpInfo
2493: .getDumpName());
2494: } catch (BackupException e) {
2495: throw new VirtualDatabaseException(e);
2496: }
2497: }
2498: }
2499:
2500: /**
2501: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#restoreDumpOnBackend(String,
2502: * String, String, String, ArrayList)
2503: */
2504: public void restoreDumpOnBackend(String databaseBackendName,
2505: String login, String password, String dumpName,
2506: ArrayList tables) throws VirtualDatabaseException {
2507: DatabaseBackend backend = getAndCheckBackend(
2508: databaseBackendName, NO_CHECK_BACKEND);
2509: // Backend cannot be null, otherwise the above throws a
2510: // VirtualDatabaseException
2511:
2512: RestoreDumpOperation restoreOperation = new RestoreDumpOperation(
2513: databaseBackendName, dumpName);
2514: try {
2515: addAdminOperation(restoreOperation);
2516: requestManager.restoreBackendFromBackupCheckpoint(backend,
2517: login, password, dumpName, tables);
2518: } catch (BackupException e) {
2519: throw new VirtualDatabaseException(e);
2520: } finally {
2521: removeAdminOperation(restoreOperation);
2522: }
2523: }
2524:
2525: /**
2526: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#setBackendLastKnownCheckpoint
2527: */
2528: public void setBackendLastKnownCheckpoint(String backendName,
2529: String checkpoint) throws VirtualDatabaseException {
2530: RecoveryLog log = requestManager.getRecoveryLog();
2531: DatabaseBackend backend = getAndCheckBackend(backendName,
2532: NO_CHECK_BACKEND);
2533: if (log == null)
2534: throw new VirtualDatabaseException(
2535: "No recovery log has been defined");
2536: else {
2537: if (!backend.isDisabled())
2538: throw new VirtualDatabaseException(
2539: "Cannot setLastKnownCheckpoint on a non-disabled backend");
2540: else {
2541: try {
2542: log
2543: .storeBackendRecoveryInfo(this .name,
2544: new BackendRecoveryInfo(backend
2545: .getName(), checkpoint,
2546: backend.getStateValue(),
2547: this .name));
2548:
2549: backend.setLastKnownCheckpoint(checkpoint);
2550: } catch (SQLException e) {
2551: throw new VirtualDatabaseException(
2552: "Failed to store recovery info for backend '"
2553: + backendName + "' (" + e + ")");
2554: }
2555: }
2556: }
2557: }
2558:
2559: /**
2560: * Sets a checkpoint indicating that this vdb has shutdown.
2561: */
2562: public void setShutdownCheckpoint() {
2563: RecoveryLog recoveryLog = requestManager.getRecoveryLog();
2564: if (recoveryLog != null)
2565: try {
2566: recoveryLog.storeCheckpoint("shutdown"
2567: + controller.getControllerName());
2568: } catch (SQLException e) {
2569: logger.warn("Error while setting shutdown checkpoint",
2570: e);
2571: }
2572: }
2573:
2574: /**
2575: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#transferDump(java.lang.String,
2576: * java.lang.String, boolean)
2577: */
2578: public void transferDump(String dumpName,
2579: String remoteControllerName, boolean noCopy)
2580: throws VirtualDatabaseException {
2581: if (!isDistributed())
2582: throw new VirtualDatabaseException(
2583: "can not transfer dumps on non-distributed virtual database");
2584: }
2585:
2586: /**
2587: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#updateDumpPath(java.lang.String,
2588: * java.lang.String)
2589: */
2590: public void updateDumpPath(String dumpName, String newPath)
2591: throws VirtualDatabaseException {
2592: try {
2593: RecoveryLog recoveryLog = requestManager.getRecoveryLog();
2594: if (recoveryLog == null) {
2595: throw new VirtualDatabaseException("no recovery log"); // TODO I18N
2596: } else {
2597: recoveryLog.updateDumpPath(dumpName, newPath);
2598: }
2599: } catch (SQLException e) {
2600: throw new VirtualDatabaseException(e);
2601: }
2602: }
2603:
2604: /**
2605: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#viewCheckpointNames()
2606: */
2607: public ArrayList viewCheckpointNames() {
2608: try {
2609: RecoveryLog recoveryLog = requestManager.getRecoveryLog();
2610: if (recoveryLog == null)
2611: return new ArrayList();
2612: else
2613: return recoveryLog.getCheckpointNames();
2614: } catch (SQLException e) {
2615: return new ArrayList();
2616: }
2617: }
2618:
2619: //
2620: // Thread management mainly used by controller and monitoring
2621: //
2622:
2623: /**
2624: * Add a VirtualDatabaseWorkerThread to the list of active threads.
2625: *
2626: * @param thread the VirtualDatabaseWorkerThread to add
2627: */
2628: public void addVirtualDatabaseWorkerThread(
2629: VirtualDatabaseWorkerThread thread) {
2630: synchronized (activeThreads) {
2631: activeThreads.add(thread);
2632: incrementCurrentNbOfThread();
2633: }
2634: }
2635:
2636: /**
2637: * Substract one to currentNbOfThreads. Warning! This method is not
2638: * synchronized.
2639: */
2640: protected void decreaseCurrentNbOfThread() {
2641: currentNbOfThreads--;
2642: }
2643:
2644: /**
2645: * Remove an idle thread. Warning! This method must be called in a
2646: * synchronized block on activeThreads.
2647: */
2648: protected void decreaseIdleThread() {
2649: idleThreads--;
2650: }
2651:
2652: /**
2653: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#getCurrentNbOfThreads()
2654: */
2655: public int getCurrentNbOfThreads() {
2656: return currentNbOfThreads;
2657: }
2658:
2659: /**
2660: * Returns the number of idle worker threads. Warning! This method must be
2661: * called in a synchronized block on activeThreads.
2662: *
2663: * @return int number of idle worker threads
2664: */
2665: public int getIdleThreads() {
2666: return idleThreads;
2667: }
2668:
2669: /**
2670: * Returns the maxNbOfThreads.
2671: *
2672: * @return int maximum number of threads
2673: */
2674: public int getMaxNbOfThreads() {
2675: return maxNbOfThreads;
2676: }
2677:
2678: /**
2679: * Returns the maxThreadIdleTime.
2680: *
2681: * @return long maximum thread idle time in ms
2682: */
2683: public long getMaxThreadIdleTime() {
2684: return maxThreadIdleTime;
2685: }
2686:
2687: /**
2688: * Returns the minNbOfThreads.
2689: *
2690: * @return int minimum number of threads
2691: */
2692: public int getMinNbOfThreads() {
2693: return minNbOfThreads;
2694: }
2695:
2696: //
2697: // Thread management mainly used by controller and monitoring
2698: //
2699:
2700: /**
2701: * Return the VirtualDatabaseWorkerThread that is currently executing the
2702: * transaction identified by the provided id.
2703: *
2704: * @param transactionId the transaction id to look for
2705: * @return the corresponding VirtualDatabaseWorkerThread or null if none is
2706: * found
2707: */
2708: public VirtualDatabaseWorkerThread getVirtualDatabaseWorkerThreadForTransaction(
2709: long transactionId) {
2710: synchronized (activeThreads) {
2711: for (Iterator iter = activeThreads.iterator(); iter
2712: .hasNext();) {
2713: VirtualDatabaseWorkerThread vdbwt = (VirtualDatabaseWorkerThread) iter
2714: .next();
2715: if (vdbwt.getCurrentTransactionId() == transactionId)
2716: return vdbwt;
2717: }
2718: }
2719: return null;
2720: }
2721:
2722: /**
2723: * Return the VirtualDatabaseWorkerThread that is currently executing the
2724: * persistent connection identified by the provided id.
2725: *
2726: * @param persistentConnectionId the transaction id to look for
2727: * @return the corresponding VirtualDatabaseWorkerThread or null if none is
2728: * found
2729: */
2730: public VirtualDatabaseWorkerThread getVirtualDatabaseWorkerThreadForPersistentConnection(
2731: long persistentConnectionId) {
2732: synchronized (activeThreads) {
2733: for (Iterator iter = activeThreads.iterator(); iter
2734: .hasNext();) {
2735: VirtualDatabaseWorkerThread vdbwt = (VirtualDatabaseWorkerThread) iter
2736: .next();
2737: if (vdbwt.getPersistentConnectionId() == persistentConnectionId)
2738: return vdbwt;
2739: }
2740: }
2741: return null;
2742: }
2743:
2744: /**
2745: * Adds one to currentNbOfThreads. Warning! This method is not synchronized.
2746: */
2747: protected void incrementCurrentNbOfThread() {
2748: currentNbOfThreads++;
2749: }
2750:
2751: /**
2752: * Method add an idle thread. Warning! This method must be called in a
2753: * synchronized block on activeThreads.
2754: */
2755: protected void incrementIdleThreadCount() {
2756: idleThreads++;
2757: }
2758:
2759: /**
2760: * Returns the poolConnectionThreads.
2761: *
2762: * @return boolean true if threads are pooled
2763: */
2764: public boolean isPoolConnectionThreads() {
2765: return poolConnectionThreads;
2766: }
2767:
2768: /**
2769: * Sets the maxThreadIdleTime.
2770: *
2771: * @param maxThreadIdleTime The maxThreadIdleTime to set
2772: */
2773: public void setMaxThreadIdleTime(long maxThreadIdleTime) {
2774: this .maxThreadIdleTime = maxThreadIdleTime;
2775: }
2776:
2777: /**
2778: * Sets the minNbOfThreads.
2779: *
2780: * @param minNbOfThreads The minNbOfThreads to set
2781: */
2782: public void setMinNbOfThreads(int minNbOfThreads) {
2783: this .minNbOfThreads = minNbOfThreads;
2784: }
2785:
2786: /**
2787: * Sets the poolConnectionThreads.
2788: *
2789: * @param poolConnectionThreads The poolConnectionThreads to set
2790: */
2791: public void setPoolConnectionThreads(boolean poolConnectionThreads) {
2792: this .poolConnectionThreads = poolConnectionThreads;
2793: }
2794:
2795: //
2796: // Getter/Setter and tools (equals, ...)
2797: //
2798:
2799: /**
2800: * Returns the activeThreads list.
2801: *
2802: * @return ArrayList of <code>VirtualDatabaseWorkerThread</code>
2803: */
2804: public ArrayList getActiveThreads() {
2805: return activeThreads;
2806: }
2807:
2808: /**
2809: * Returns the authentication manager of this virtual database.
2810: *
2811: * @return an <code>AuthenticationManager</code> instance
2812: */
2813: public AuthenticationManager getAuthenticationManager() {
2814: return authenticationManager;
2815: }
2816:
2817: /**
2818: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#getBackendInformation(String)
2819: */
2820: public String getBackendInformation(String backendName)
2821: throws VirtualDatabaseException {
2822: try {
2823: acquireReadLockBackendLists();
2824: } catch (InterruptedException e) {
2825: String msg = "Unable to acquire read lock on backend list in getBackendInformation ("
2826: + e + ")";
2827: logger.error(msg);
2828: throw new VirtualDatabaseException(msg);
2829: }
2830:
2831: // Find the backend
2832: int size = backends.size();
2833: DatabaseBackend b = null;
2834: for (int i = 0; i < size; i++) {
2835: b = (DatabaseBackend) backends.get(i);
2836: if (b.getName().equals(backendName))
2837: break;
2838: else
2839: b = null;
2840: }
2841:
2842: if (b == null) {
2843: releaseReadLockBackendLists();
2844: String msg = "Backend " + backendName + " does not exists.";
2845: logger.warn(msg);
2846: throw new VirtualDatabaseException(msg);
2847: }
2848:
2849: releaseReadLockBackendLists();
2850: return b.getXml();
2851: }
2852:
2853: /**
2854: * Return the list of all backends
2855: *
2856: * @return <code>ArrayList</code> of <code>DatabaseBackend</code> Objects
2857: */
2858: public ArrayList getBackends() {
2859: return backends;
2860: }
2861:
2862: /**
2863: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#getBackendSchema(java.lang.String)
2864: */
2865: public String getBackendSchema(String backendName)
2866: throws VirtualDatabaseException {
2867: DatabaseBackend backend = getAndCheckBackend(backendName,
2868: NO_CHECK_BACKEND);
2869: // we know the backend is not null, otherwise we have a
2870: // VirtualDatabaseException ...
2871: try {
2872: return XmlTools.prettyXml(backend.getSchemaXml(true));
2873: } catch (Exception e) {
2874: throw new VirtualDatabaseException(e.getMessage());
2875: }
2876: }
2877:
2878: /**
2879: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#getBackendState(java.lang.String)
2880: */
2881: public String getBackendState(String backendName)
2882: throws VirtualDatabaseException {
2883: DatabaseBackend backend = getAndCheckBackend(backendName,
2884: NO_CHECK_BACKEND);
2885: return backend.getState();
2886: }
2887:
2888: /**
2889: * Retrieves the warnings for the given connection on the first available
2890: * backend
2891: *
2892: * @param connId the persistent connection id to retrieve warnings from
2893: * @exception SQLException if a database access error occurs or this method is
2894: * called on a closed connection
2895: * @return connection SQL warnings or null
2896: */
2897: public SQLWarning getConnectionWarnings(long connId)
2898: throws SQLException {
2899: DatabaseBackend b = getFirstAvailableBackend();
2900: return b.getPersistentConnectionWarnings(connId);
2901: }
2902:
2903: /**
2904: * Forwards call to clearWarnings() to the given persistent connections on
2905: * each backend.
2906: *
2907: * @param connId the persistent connection id to clear warnings from
2908: * @exception SQLException if a database access error occurs
2909: */
2910: public void clearConnectionWarnings(long connId)
2911: throws SQLException {
2912: // Loop round the list in a failfast manner
2913: if (backends == null)
2914: return;
2915: try {
2916: for (Iterator iter = backends.iterator(); iter.hasNext();) {
2917: DatabaseBackend b = (DatabaseBackend) iter.next();
2918: if (b.isWriteEnabled() && b.isJDBCConnected())
2919: b.clearPersistentConnectionWarnings(connId);
2920: }
2921: } catch (ConcurrentModificationException e) {
2922: // loop until available
2923: clearConnectionWarnings(connId);
2924: }
2925: }
2926:
2927: /**
2928: * Gets the virtual database name to be used by the client (Sequoia driver)
2929: * This method should be used for local references only (it is faster). For
2930: * remote RMI calls, use {@link #getVirtualDatabaseName()}.
2931: *
2932: * @return the virtual database name
2933: * @see VirtualDatabase#getVirtualDatabaseName()
2934: */
2935: public String getDatabaseName() {
2936: return name;
2937: }
2938:
2939: /**
2940: * @see org.continuent.sequoia.driver.DatabaseMetaData#getDatabaseProductName()
2941: */
2942: public String getDatabaseProductName() {
2943: return databaseProductNames;
2944: }
2945:
2946: /**
2947: * @see org.continuent.sequoia.driver.DatabaseMetaData
2948: * @return associated metada for this database
2949: */
2950: public VirtualDatabaseDynamicMetaData getDynamicMetaData() {
2951: if (metadata == null) {
2952: metadata = new VirtualDatabaseDynamicMetaData(this );
2953: }
2954: return metadata;
2955: }
2956:
2957: /**
2958: * Get the current database schema from merging the schemas of all active
2959: * backends.
2960: *
2961: * @return the current database schema dynamically gathered
2962: * @throws SQLException if an error occurs
2963: */
2964: public DatabaseSchema getDatabaseSchemaFromActiveBackends()
2965: throws SQLException {
2966: boolean isRaidb1 = requestManager.getLoadBalancer()
2967: .getRAIDbLevel() == RAIDbLevels.RAIDb1;
2968:
2969: try {
2970: acquireReadLockBackendLists();
2971: } catch (InterruptedException e) {
2972: String msg = "Unable to acquire read lock on backend list in getDatabaseSchemaFromActiveBackends ("
2973: + e + ")";
2974: logger.error(msg);
2975: throw new SQLException(msg);
2976: }
2977:
2978: DatabaseSchema schema = null;
2979: try {
2980: // Build the new schema from all active backend's schemas
2981: int size = backends.size();
2982: DatabaseBackend b = null;
2983: for (int i = 0; i < size; i++) {
2984: b = (DatabaseBackend) backends.get(i);
2985: if (b.isReadEnabled()) {
2986: DatabaseSchema backendSchema = b
2987: .getDatabaseSchema();
2988: if (backendSchema != null) { // The backend schema might be null during the recovery phase
2989: if (schema == null)
2990: schema = new DatabaseSchema(backendSchema);
2991: else
2992: schema.mergeSchema(backendSchema);
2993:
2994: // In RAIDb-1 all backends are the same so there is no need to merge
2995: // and we just take the schema of the first backend
2996: if (isRaidb1)
2997: break;
2998: }
2999: }
3000: }
3001:
3002: // Note that if the RecoveryLog points to the same database it will appear
3003: // in the database schema but this is a normal behavior.
3004: } finally {
3005: releaseReadLockBackendLists();
3006: }
3007:
3008: return schema;
3009: }
3010:
3011: /**
3012: * Get the current database schema from merging the schemas of all active
3013: * backends. This is needed when a backend is disabled.
3014: *
3015: * @return the current database schema dynamically gathered
3016: * @throws SQLException if an error occurs
3017: */
3018: public DatabaseSchema getDatabaseSchemaFromActiveBackendsAndRefreshDatabaseProductNames()
3019: throws SQLException {
3020: try {
3021: acquireReadLockBackendLists();
3022: } catch (InterruptedException e) {
3023: String msg = "Unable to acquire read lock on backend list in getDatabaseSchemaFromActiveBackendsAndRefreshDatabaseProductNames ("
3024: + e + ")";
3025: logger.error(msg);
3026: throw new SQLException(msg);
3027: }
3028:
3029: DatabaseSchema schema = null;
3030: String dbProductNames = "Sequoia";
3031: try {
3032: // Build the new schema from all active backend's schemas
3033: int size = backends.size();
3034: DatabaseBackend b = null;
3035: for (int i = 0; i < size; i++) {
3036: b = (DatabaseBackend) backends.get(i);
3037: if (b.isReadEnabled()) {
3038: DatabaseSchema backendSchema = b
3039: .getDatabaseSchema();
3040: if (backendSchema != null) { // The backend schema might be null during the recovery phase
3041: if (schema == null)
3042: schema = new DatabaseSchema(backendSchema);
3043: else
3044: schema.mergeSchema(backendSchema);
3045: }
3046: }
3047:
3048: // Update the list of database product names
3049: if (dbProductNames.indexOf(b.getDatabaseProductName()) == -1)
3050: dbProductNames += "," + b.getDatabaseProductName();
3051: }
3052: } finally {
3053: releaseReadLockBackendLists();
3054: }
3055: databaseProductNames = dbProductNames;
3056:
3057: // Note that if the RecoveryLog points to the same database it will appear
3058: // in the database schema but this is a normal behavior.
3059:
3060: return schema;
3061: }
3062:
3063: /**
3064: * Retrieves the first available backend from this virtual database
3065: *
3066: * @return the first available backend or null if no backend enabled is found
3067: */
3068: DatabaseBackend getFirstAvailableBackend() {
3069: // Parse the list in a failfast manner
3070: if (backends == null)
3071: return null;
3072: try {
3073: for (Iterator iter = backends.iterator(); iter.hasNext();) {
3074: DatabaseBackend b = (DatabaseBackend) iter.next();
3075: if (b.isReadEnabled() && b.isJDBCConnected())
3076: return b;
3077: }
3078: } catch (ConcurrentModificationException e) {
3079: return getFirstAvailableBackend();
3080: }
3081:
3082: return null;
3083: }
3084:
3085: /**
3086: * Returns the logger value.
3087: *
3088: * @return Returns the logger.
3089: */
3090: public final Trace getLogger() {
3091: return logger;
3092: }
3093:
3094: /**
3095: * Returns the maxNbOfConnections.
3096: *
3097: * @return int
3098: */
3099: public int getMaxNbOfConnections() {
3100: return maxNbOfConnections;
3101: }
3102:
3103: /**
3104: * Returns the number of savepoints that are defined for a given transaction
3105: * by asking to the request manager.
3106: *
3107: * @param tId the transaction id
3108: * @return the number of savepoints that are defined in the transaction whose
3109: * id is tId
3110: */
3111: public int getNumberOfSavepointsInTransaction(long tId) {
3112: return requestManager.getNumberOfSavepointsInTransaction(tId);
3113: }
3114:
3115: /**
3116: * Returns the pendingConnections.
3117: *
3118: * @return ArrayList
3119: */
3120: public ArrayList getPendingConnections() {
3121: return pendingConnections;
3122: }
3123:
3124: /**
3125: * Gets the request manager associated to this database.
3126: *
3127: * @return a <code>RequestManager</code> instance
3128: */
3129: public RequestManager getRequestManager() {
3130: return requestManager;
3131: }
3132:
3133: /**
3134: * Get the whole static metadata for this virtual database. A new empty
3135: * metadata object is created if there was none yet. It will be filled later
3136: * by gatherStaticMetadata() when the backend is enabled.
3137: *
3138: * @return Virtual database static metadata
3139: */
3140: public VirtualDatabaseStaticMetaData getStaticMetaData() {
3141: return doGetStaticMetaData();
3142: }
3143:
3144: /**
3145: * @see #getStaticMetaData()
3146: */
3147: public VirtualDatabaseStaticMetaData doGetStaticMetaData() {
3148: if (staticMetadata == null) {
3149: staticMetadata = new VirtualDatabaseStaticMetaData(this );
3150: }
3151: return staticMetadata;
3152: }
3153:
3154: /**
3155: * Gets the virtual database name to be used by the client (Sequoia driver)
3156: *
3157: * @return the virtual database name
3158: */
3159: public String getVirtualDatabaseName() {
3160: return name;
3161: }
3162:
3163: /**
3164: * Returns the current SQL monitor
3165: *
3166: * @return a <code>SQLMonitoring</code> instance or null if no monitor is
3167: * defined
3168: */
3169: public SQLMonitoring getSQLMonitor() {
3170: return sqlMonitor;
3171: }
3172:
3173: /**
3174: * Return the sql short form length to use when reporting an error.
3175: *
3176: * @return sql short form length
3177: * @see org.continuent.sequoia.controller.requests.AbstractRequest#getSqlShortForm(int)
3178: */
3179: public int getSqlShortFormLength() {
3180: return sqlShortFormLength;
3181: }
3182:
3183: /**
3184: * Returns the totalOrderQueue value.
3185: *
3186: * @return Returns the totalOrderQueue.
3187: */
3188: public LinkedList getTotalOrderQueue() {
3189: return totalOrderQueue;
3190: }
3191:
3192: /**
3193: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#hasRecoveryLog()
3194: */
3195: public boolean hasRecoveryLog() {
3196: RecoveryLog log = requestManager.getRecoveryLog();
3197: if (log == null)
3198: return false;
3199: else
3200: return true;
3201: }
3202:
3203: /**
3204: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#hasResultCache()
3205: */
3206: public boolean hasResultCache() {
3207: AbstractResultCache cache = requestManager.getResultCache();
3208: if (cache == null)
3209: return false;
3210: else
3211: return true;
3212: }
3213:
3214: /**
3215: * Sets the authentication manager for this virtual database.
3216: *
3217: * @param authenticationManager the <code>AuthenticationManager</code> to
3218: * set
3219: */
3220: public void setAuthenticationManager(
3221: AuthenticationManager authenticationManager) {
3222: this .authenticationManager = authenticationManager;
3223: }
3224:
3225: /**
3226: * Sets a new static database schema for this database if no one exist or
3227: * merge the given schema to the existing one. A static schema can only be
3228: * replaced by another static schema.
3229: *
3230: * @param schema the new database shema
3231: */
3232: public void setStaticDatabaseSchema(DatabaseSchema schema) {
3233: if (requestManager != null)
3234: requestManager.setDatabaseSchema(schema, true);
3235: else
3236: logger
3237: .warn("Unable to set database schema, no request manager has been defined.");
3238: }
3239:
3240: /**
3241: * Sets the maxNbOfConnections.
3242: *
3243: * @param maxNbOfConnections The maxNbOfConnections to set
3244: */
3245: public void setMaxNbOfConnections(int maxNbOfConnections) {
3246: this .maxNbOfConnections = maxNbOfConnections;
3247: }
3248:
3249: /**
3250: * Sets the maxNbOfThreads.
3251: *
3252: * @param maxNbOfThreads The maxNbOfThreads to set
3253: */
3254: public void setMaxNbOfThreads(int maxNbOfThreads) {
3255: this .maxNbOfThreads = maxNbOfThreads;
3256: }
3257:
3258: /**
3259: * Sets a new request manager for this database.
3260: *
3261: * @param requestManager the new request manager.
3262: */
3263: public void setRequestManager(RequestManager requestManager) {
3264: this .requestManager = requestManager;
3265: }
3266:
3267: /**
3268: * Sets a new SQL Monitor
3269: *
3270: * @param sqlMonitor the new SQL monitor
3271: */
3272: public void setSQLMonitor(SQLMonitoring sqlMonitor) {
3273: this .sqlMonitor = sqlMonitor;
3274: }
3275:
3276: /**
3277: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#setMonitoringToActive(boolean)
3278: */
3279: public void setMonitoringToActive(boolean active)
3280: throws VirtualDatabaseException {
3281: if (sqlMonitor == null)
3282: throw new VirtualDatabaseException(Translate
3283: .get("virtualdatabase.monitoring.not.defined"));
3284: else
3285: sqlMonitor.setActive(active);
3286: }
3287:
3288: /**
3289: * Returns the useStaticResultSetMetaData value.
3290: *
3291: * @return Returns the useStaticResultSetMetaData.
3292: */
3293: public final boolean useStaticResultSetMetaData() {
3294: return useStaticResultSetMetaData;
3295: }
3296:
3297: /**
3298: * Two virtual databases are equal if they have the same name and group.
3299: *
3300: * @param other the object to compare with
3301: * @return <code>true</code> if the two virtual databases are equals
3302: */
3303: public boolean equals(Object other) {
3304: if ((other == null) || (!(other instanceof VirtualDatabase)))
3305: return false;
3306: else {
3307: VirtualDatabase db = (VirtualDatabase) other;
3308: return name.equals(db.getDatabaseName());
3309: }
3310: }
3311:
3312: // /////////////////////////////////////////
3313: // JMX
3314: // ////////////////////////////////////////
3315:
3316: /**
3317: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#cleanMonitoringData()
3318: */
3319: public void cleanMonitoringData() throws VirtualDatabaseException {
3320: if (sqlMonitor == null)
3321: throw new VirtualDatabaseException(Translate
3322: .get("virtualdatabase.monitoring.not.defined"));
3323: else
3324: sqlMonitor.cleanStats();
3325: }
3326:
3327: /**
3328: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#retrieveBackendsData()
3329: */
3330: public String[][] retrieveBackendsData()
3331: throws VirtualDatabaseException {
3332: try {
3333: acquireReadLockBackendLists();
3334: } catch (InterruptedException e) {
3335: String msg = Translate.get(
3336: "virtualdatabase.fail.read.lock", e);
3337: throw new VirtualDatabaseException(msg);
3338: }
3339: ArrayList localBackends = this .getBackends();
3340: int backendListSize = localBackends.size();
3341: String[][] data = new String[backendListSize][];
3342: for (int i = 0; i < backendListSize; i++) {
3343: data[i] = ((DatabaseBackend) localBackends.get(i))
3344: .getBackendData();
3345: }
3346: releaseReadLockBackendLists();
3347: return data;
3348: }
3349:
3350: /**
3351: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#getBackendStatistics(java.lang.String)
3352: */
3353: public BackendStatistics getBackendStatistics(String backendName)
3354: throws VirtualDatabaseException {
3355: try {
3356: acquireReadLockBackendLists();
3357: } catch (InterruptedException e) {
3358: String msg = Translate.get(
3359: "virtualdatabase.fail.read.lock", e);
3360: throw new VirtualDatabaseException(msg);
3361: }
3362: BackendStatistics stat = null;
3363: ArrayList backendList = this .getBackends();
3364: for (Iterator iter = backendList.iterator(); iter.hasNext();) {
3365: DatabaseBackend backend = (DatabaseBackend) iter.next();
3366: if (backend.getName().equals(backendName)) {
3367: stat = backend.getBackendStats();
3368: }
3369: }
3370: releaseReadLockBackendLists();
3371: return stat;
3372: }
3373:
3374: /**
3375: * Add an admin operation to the list of current admin operations
3376: *
3377: * @param operation the operation to add
3378: */
3379: protected void addAdminOperation(AbstractAdminOperation operation) {
3380: synchronized (currentAdminOperations) {
3381: currentAdminOperations.add(operation);
3382: }
3383: }
3384:
3385: /**
3386: * Remove a currently executing admin operation. Returns true if the command
3387: * was successfully removed from the list.
3388: *
3389: * @param operation the admin operation to remove.
3390: * @return true if operation was found and removed from the list
3391: */
3392: protected boolean removeAdminOperation(
3393: AbstractAdminOperation operation) {
3394: synchronized (currentAdminOperations) {
3395: currentAdminOperations.notify();
3396: return currentAdminOperations.remove(operation);
3397: }
3398: }
3399:
3400: /**
3401: * Wait for all current admin operations to complete.
3402: */
3403: private void waitForAdminOperationsToComplete() {
3404: synchronized (currentAdminOperations) {
3405: while (!currentAdminOperations.isEmpty()) {
3406: for (Iterator iter = currentAdminOperations.iterator(); iter
3407: .hasNext();) {
3408: AbstractAdminOperation op = (AbstractAdminOperation) iter
3409: .next();
3410: logger.info("Waiting for command '" + op
3411: + "' to complete");
3412: }
3413: try {
3414: currentAdminOperations.wait();
3415: } catch (InterruptedException ignore) {
3416: }
3417: }
3418: }
3419: }
3420:
3421: //
3422: // Shutdown
3423: //
3424:
3425: /**
3426: * Return true if this database is shutting down.
3427: *
3428: * @return true if this database is shutting down.
3429: */
3430: public boolean isShuttingDown() {
3431: return shuttingDown;
3432: }
3433:
3434: /**
3435: * Set the VDB shutting down state
3436: *
3437: * @param shuttingDown TRUE when vdb is shutting down
3438: */
3439: public void setShuttingDown(boolean shuttingDown) {
3440: this .shuttingDown = shuttingDown;
3441: }
3442:
3443: public boolean isRejectingNewTransaction() {
3444: return refusingNewTransaction;
3445: }
3446:
3447: public void setRejectingNewTransaction(
3448: boolean canAcceptNewTransaction) {
3449: this .refusingNewTransaction = canAcceptNewTransaction;
3450: }
3451:
3452: /**
3453: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#shutdown(int)
3454: */
3455: public void shutdown(int level) {
3456: VirtualDatabaseShutdownThread vdst = null;
3457: String msg = Translate.get("virtualdatabase.shutting.down",
3458: this .getVirtualDatabaseName());
3459: synchronized (this ) {
3460: if (shuttingDown && !(level == Constants.SHUTDOWN_FORCE))
3461: return;
3462: switch (level) {
3463: case Constants.SHUTDOWN_WAIT:
3464: vdst = new VirtualDatabaseWaitShutdownThread(this );
3465: msg = Translate.get(
3466: "virtualdatabase.shutdown.type.wait", this
3467: .getVirtualDatabaseName());
3468: logger.info(msg);
3469: endUserLogger.info(msg);
3470: break;
3471: case Constants.SHUTDOWN_SAFE:
3472: shuttingDown = true;
3473: vdst = new VirtualDatabaseSafeShutdownThread(this );
3474: msg = Translate.get(
3475: "virtualdatabase.shutdown.type.safe", this
3476: .getVirtualDatabaseName());
3477: logger.info(msg);
3478: endUserLogger.info(msg);
3479: break;
3480: case Constants.SHUTDOWN_FORCE:
3481: shuttingDown = true;
3482: vdst = new VirtualDatabaseForceShutdownThread(this );
3483: msg = Translate.get(
3484: "virtualdatabase.shutdown.type.force", this
3485: .getVirtualDatabaseName());
3486: logger.warn(msg);
3487: endUserLogger.info(msg);
3488: break;
3489: default:
3490: msg = Translate.get(
3491: "virtualdatabase.shutdown.unknown.level",
3492: new Object[] { new Integer(level),
3493: this .getVirtualDatabaseName() });
3494: logger.error(msg);
3495: endUserLogger.error(msg);
3496: throw new RuntimeException(msg);
3497: }
3498: }
3499:
3500: if (level != Constants.SHUTDOWN_FORCE) {
3501: // Wait for all blocking admin operations to complete
3502: waitForAdminOperationsToComplete();
3503: }
3504:
3505: Thread thread = new Thread(vdst.getShutdownGroup(), vdst,
3506: "VirtualDatabase Shutdown Thread");
3507: thread.start();
3508: try {
3509: logger.info("Waiting for virtual database " + name
3510: + " shutdown");
3511: thread.join();
3512: controller.removeVirtualDatabase(name);
3513: msg = Translate.get(
3514: "notification.virtualdatabase.shutdown", name);
3515: logger.info(msg);
3516: endUserLogger.info(msg);
3517:
3518: if (MBeanServerManager.isJmxEnabled()) {
3519: try {
3520: MBeanServerManager.unregister(JmxConstants
3521: .getVirtualDataBaseObjectName(name));
3522: if (MBeanServerManager
3523: .getInstance()
3524: .isRegistered(
3525: JmxConstants
3526: .getRecoveryLogObjectName(name))) {
3527: MBeanServerManager.unregister(JmxConstants
3528: .getRecoveryLogObjectName(name));
3529: }
3530: MBeanServerManager.unregister(JmxConstants
3531: .getAbstractSchedulerObjectName(name));
3532: MBeanServerManager.unregister(JmxConstants
3533: .getLoadBalancerObjectName(name));
3534: MBeanServerManager.unregister(JmxConstants
3535: .getRequestManagerObjectName(name));
3536: ArrayList backendNames = getAllBackendNames();
3537: for (int i = 0; i < backendNames.size(); i++) {
3538: String backendName = (String) backendNames
3539: .get(i);
3540: MBeanServerManager.unregister(JmxConstants
3541: .getDatabaseBackendObjectName(name,
3542: backendName));
3543: }
3544: } catch (Exception e) {
3545: logger.error(Translate.get(
3546: "virtualdatabase.fail.unregister.mbean",
3547: name), e);
3548: }
3549: }
3550: } catch (InterruptedException e) {
3551: e.printStackTrace();
3552: }
3553: }
3554:
3555: /**
3556: * Write the checkpoints for all backends on the recovery log
3557: */
3558: public void storeBackendsInfo() {
3559: requestManager.storeBackendsInfo(this .name, getBackends());
3560: }
3561:
3562: /**
3563: * Get all users connected to that database
3564: *
3565: * @return an <code>ArrayList</code> of strings containing the clients
3566: * username
3567: */
3568: public ArrayList viewAllClientNames() {
3569: ArrayList list = this .getActiveThreads();
3570: int size = list.size();
3571: ArrayList clients = new ArrayList(size);
3572: for (int i = 0; i < list.size(); i++)
3573: clients.add(((VirtualDatabaseWorkerThread) list.get(i))
3574: .getUser());
3575: return clients;
3576: }
3577:
3578: /**
3579: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#viewBackendInformation(java.lang.String)
3580: */
3581: public String[] viewBackendInformation(String backendName)
3582: throws VirtualDatabaseException {
3583: DatabaseBackend backend = getAndCheckBackend(backendName,
3584: NO_CHECK_BACKEND);
3585: return backend.getBackendData();
3586: }
3587:
3588: /**
3589: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#viewControllerList()
3590: */
3591: public String[] viewControllerList() {
3592: return new String[] { viewOwningController() };
3593: }
3594:
3595: /**
3596: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#viewGroupBackends()
3597: */
3598: public Hashtable viewGroupBackends()
3599: throws VirtualDatabaseException {
3600: Hashtable map = new Hashtable();
3601: try {
3602: acquireReadLockBackendLists();
3603: } catch (InterruptedException e) {
3604: String msg = "Unable to acquire read lock on backend list in getAllBackendNames ("
3605: + e + ")";
3606: logger.error(msg);
3607: throw new VirtualDatabaseException(msg);
3608: }
3609:
3610: // Create an ArrayList<BackendInfo> from the backend list
3611: int size = backends.size();
3612: ArrayList backendInfos = new ArrayList(size);
3613: for (int i = 0; i < size; i++)
3614: backendInfos.add(new BackendInfo(
3615: ((DatabaseBackend) backends.get(i))));
3616:
3617: releaseReadLockBackendLists();
3618:
3619: // Return a map with the controller JMX name and its ArrayList<BackendInfo>
3620: map.put(controller.getJmxName(), backendInfos);
3621: return map;
3622: }
3623:
3624: /**
3625: * @see org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean#viewOwningController()
3626: */
3627: public String viewOwningController() {
3628: return controller.getJmxName();
3629: }
3630:
3631: /**
3632: * Retrieves this <code>VirtualDatabase</code> object in xml format
3633: *
3634: * @return xml formatted string that conforms to sequoia.dtd
3635: */
3636: public String getXml() {
3637: StringBuffer info = new StringBuffer();
3638: info.append("<" + DatabasesXmlTags.ELT_VirtualDatabase + " "
3639: + DatabasesXmlTags.ATT_name + "=\""
3640: + this .getVirtualDatabaseName() + "\" "
3641: + DatabasesXmlTags.ATT_maxNbOfConnections + "=\""
3642: + this .getMaxNbOfConnections() + "\" "
3643: + DatabasesXmlTags.ATT_poolThreads + "=\""
3644: + this .isPoolConnectionThreads() + "\" "
3645: + DatabasesXmlTags.ATT_minNbOfThreads + "=\""
3646: + this .getMinNbOfThreads() + "\" "
3647: + DatabasesXmlTags.ATT_maxNbOfThreads + "=\""
3648: + this .getMaxNbOfThreads() + "\" "
3649: + DatabasesXmlTags.ATT_maxThreadIdleTime + "=\""
3650: + this .getMaxThreadIdleTime() / 1000 + "\" "
3651: + DatabasesXmlTags.ATT_sqlDumpLength + "=\""
3652: + this .sqlShortFormLength + "\">");
3653:
3654: info.append(getDistributionXml());
3655:
3656: if (this .getSQLMonitor() != null)
3657: info.append(sqlMonitor.getXml());
3658:
3659: info.append(requestManager.getBackupManager().getXml());
3660:
3661: if (this .getAuthenticationManager() != null)
3662: info.append(authenticationManager.getXml());
3663:
3664: try {
3665: acquireReadLockBackendLists();
3666: int size = backends.size();
3667: for (int i = 0; i < size; i++)
3668: info.append(((DatabaseBackend) backends.get(i))
3669: .getXml());
3670: releaseReadLockBackendLists();
3671: } catch (InterruptedException e) {
3672: logger.error(Translate.get(
3673: "virtualdatabase.fail.read.lock", e));
3674: }
3675: if (requestManager != null)
3676: info.append(requestManager.getXml());
3677: info.append("</" + DatabasesXmlTags.ELT_VirtualDatabase + ">");
3678: return info.toString();
3679: }
3680:
3681: /**
3682: * Get the XML dump of the Distribution element if any.
3683: *
3684: * @return ""
3685: */
3686: protected String getDistributionXml() {
3687: return "";
3688: }
3689:
3690: /**
3691: * Indicates if the virtual database requires that tables referenced in a
3692: * query exist into the schema. This concerns only write queries. If true,
3693: * queries containing references to tables that do not exist in the database
3694: * schema will be rejected. Otherwise, queries will be executed against the
3695: * backends using the conflicting queue. This is done to avoid unwanted
3696: * cross-database queries to be executed on the cluster.
3697: *
3698: * @return enforceTableExistenceIntoSchema that is true if tables have to be
3699: * found in the database schema to let a query to be executed against
3700: * backends
3701: */
3702: public boolean enforceTableExistenceIntoSchema() {
3703: return enforceTableExistenceIntoSchema;
3704: }
3705: }
|