/*******************************************************************************
* Copyright (c) 2004, 2010 BREDEX GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.client.core.persistence;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jubula.client.core.Activator;
import org.eclipse.jubula.client.core.constants.PluginConstants;
import org.eclipse.jubula.client.core.errorhandling.IDatabaseVersionErrorHandler;
import org.eclipse.jubula.client.core.i18n.Messages;
import org.eclipse.jubula.client.core.model.DBVersionPO;
import org.eclipse.jubula.client.core.model.IPersistentObject;
import org.eclipse.jubula.client.core.persistence.locking.LockManager;
import org.eclipse.jubula.client.core.progress.JobChangeListener;
import org.eclipse.jubula.client.core.utils.DatabaseStateDispatcher;
import org.eclipse.jubula.client.core.utils.DatabaseStateEvent;
import org.eclipse.jubula.client.core.utils.DatabaseStateEvent.DatabaseState;
import org.eclipse.jubula.tools.internal.constants.StringConstants;
import org.eclipse.jubula.tools.internal.exception.JBException;
import org.eclipse.jubula.tools.internal.exception.JBFatalAbortException;
import org.eclipse.jubula.tools.internal.exception.JBFatalException;
import org.eclipse.jubula.tools.internal.exception.ProjectDeletedException;
import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs;
import org.eclipse.jubula.tools.internal.utils.IsAliveThread;
import org.eclipse.jubula.version.SemanticVersionUtil;
import org.eclipse.osgi.util.NLS;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.sessions.server.ServerSession;
import org.eclipse.persistence.tools.schemaframework.SchemaManager;
import org.osgi.framework.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author BREDEX GmbH
* @created 19.04.2005
*/
public class Persistor {
/** the name of the default persistence unit for Jubula */
private static final String DEFAULT_PU_NAME = "org.eclipse.jubula"; //$NON-NLS-1$
/** the database model version */
private static final Version MODEL_VERSION = new Version(70, 0, 0);
/** shutdown hook to dispose the current Persistor */
private static final Thread SHUTDOWN_HOOK = new IsAliveThread(
"Close Session Factory") { //$NON-NLS-1$
public void run() {
Persistor hib = Persistor.instance();
if (hib != null) {
try {
if (LockManager.isRunning()) {
LockManager.instance().dispose();
}
} catch (Throwable t) {
log.warn(Messages.CouldNotShutDownLockManager
+ StringConstants.DOT, t);
}
try {
hib.dispose();
} catch (Throwable t) {
log.warn(Messages.CouldNotShutDownDatabaseConnectionPool
+ StringConstants.DOT, t);
}
}
}
};
static {
Runtime.getRuntime().addShutdownHook(SHUTDOWN_HOOK);
}
/** standard logging */
private static Logger log = LoggerFactory.getLogger(Persistor.class);
/** singleton */
private static Persistor instance = null;
/** username */
private static String user = null;
/** password */
private static String pw = null;
/** db connection string */
private static String dburl = null;
/** contains information regarding how to connect to the database */
private static DatabaseConnectionInfo dbConnectionInfo = null;
/** Persistence (JPA / EclipseLink) configuration */
private EntityManagerFactory m_sf;
/** list of known open sessions */
private List<EntityManager> m_sessions = new ArrayList<EntityManager>();
/** set to true if a new scheme was installed */
private boolean m_newDbSchemeInstalled = false;
/** counts the number of threads which needs this instance of the DB.*/
private AtomicInteger m_dbLockCnt = new AtomicInteger();
/**
* Create the instance.
*
* @param userName
* The user name.
* @param pwd
* The password.
* @param url
* The password.
* @param monitor
* the progress monitor to use
* @throws JBException
* in case of configuration problems.
*/
private Persistor(String userName, String pwd, String url,
IProgressMonitor monitor)
throws JBException, DatabaseVersionConflictException {
try {
buildSessionFactoryWithLoginData(userName, pwd, url, monitor);
} catch (PersistenceException e) {
String msg = Messages.CantSetupPersistence;
log.error(msg, e);
throw new JBFatalException(msg,
MessageIDs.E_PERSISTENCE_CANT_SETUP);
} catch (DatabaseVersionConflictException dbvce) {
dispose();
throw dbvce;
}
}
/**
* Inits the persistor.
*
* @return boolean True, if Persistor could initialized. False, otherwise.
* @throws JBFatalException
* in case of setup problems.
*/
public static synchronized boolean init() throws JBFatalException {
if (instance == null) {
return connectToDB();
}
return true;
}
/**
* @return true if successful; false otherwise
*/
private static boolean connectToDB() {
Job connectToDBJob = new Job(Messages.ConnectingToDatabaseJob) {
protected IStatus run(IProgressMonitor monitor) {
Integer message = 0;
try {
instance(user, pw, dburl, monitor);
user = null;
pw = null;
if (instance.m_newDbSchemeInstalled) {
instance.m_newDbSchemeInstalled = false;
DatabaseStateDispatcher
.notifyListener(new DatabaseStateEvent(
DatabaseState.DB_SCHEME_CREATED));
}
return Status.OK_STATUS;
} catch (PMDatabaseConfException e) {
log.error(e.getLocalizedMessage());
if (e.getErrorId().equals(MessageIDs.
E_INVALID_DB_VERSION)) {
message = MessageIDs.E_INVALID_DB_VERSION;
} else if (e.getErrorId().equals(
MessageIDs.E_NOT_CHECKABLE_DB_VERSION)) {
message = MessageIDs.E_NOT_CHECKABLE_DB_VERSION;
} else if (e.getErrorId().equals(MessageIDs.
E_NO_DB_SCHEME)) {
message = MessageIDs.E_NO_DB_SCHEME;
} else if (e.getErrorId().equals(
MessageIDs.E_ERROR_IN_SCHEMA_CONFIG)) {
message = MessageIDs.E_ERROR_IN_SCHEMA_CONFIG;
} else {
message = MessageIDs.E_UNEXPECTED_EXCEPTION;
}
return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
message.toString(), e);
} catch (JBException e) {
if (e.getErrorId().equals(MessageIDs.E_NO_DB_CONNECTION)) {
message = MessageIDs.E_NO_DB_CONNECTION;
}
if (e.getErrorId().equals(MessageIDs.E_DB_IN_USE)) {
message = MessageIDs.E_DB_IN_USE;
}
return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
message.toString(), e);
}
}
};
final AtomicBoolean connectionGained = new AtomicBoolean();
connectToDBJob.addJobChangeListener(new JobChangeListener() {
/** {@inheritDoc} */
public void done(IJobChangeEvent event) {
connectionGained.set(event.getResult().isOK());
}
});
connectToDBJob.setUser(true);
connectToDBJob.schedule();
try {
connectToDBJob.join();
} catch (InterruptedException e) {
log.error(e.getLocalizedMessage(), e);
connectionGained.set(false);
}
return connectionGained.get();
}
/**
* <p>
* <FONT COLOR="#FF0000">ATTENTION:</FONT><b><i> Call this method, only when
* the database connection failed because of invalid login data and you want
* to login again with valid login data !</i></b>
* </p>
*
* @param userName
* The user name.
* @param pwd
* The password.
* @param url
* url connection string
* @param monitor
* the progress monitor to use
* @throws PersistenceException
* in case of configuration problems.
* @throws JBException
* in case of configuration problems.
* @throws PMDatabaseConfException
* in case of invalid db scheme
*/
private void buildSessionFactoryWithLoginData(String userName, String pwd,
String url, IProgressMonitor monitor) throws PersistenceException,
PMDatabaseConfException, JBException, DatabaseVersionConflictException {
if (userName != null) {
monitor.beginTask(
NLS.bind(Messages.ConnectingToDatabase, new Object[] {
userName, dbConnectionInfo.
getConnectionUrl() }),
IProgressMonitor.UNKNOWN);
m_sf = createEntityManagerFactory(dbConnectionInfo, userName, pwd,
url);
EntityManager em = null;
try {
em = m_sf.createEntityManager();
try {
validateDBVersion(em);
monitor.subTask(Messages.DatabaseConnectionEstablished);
} catch (AmbiguousDatabaseVersionException e) {
throw new PMDatabaseConfException(NLS.bind(
Messages.DBVersionProblem, new Object[] {
-1,
-1,
MODEL_VERSION.getMajor(),
MODEL_VERSION.getMinor()
}),
MessageIDs.E_NOT_CHECKABLE_DB_VERSION);
}
} catch (PersistenceException e) {
log.error(Messages.NoOrWrongUsernameOrPassword, e);
throw new JBException(e.getMessage(),
MessageIDs.E_NO_DB_CONNECTION);
} finally {
if (em != null) {
em.close();
}
}
} else {
throw new JBException(Messages.DatabaseProfileDoesNotExist,
MessageIDs.E_DB_PROFILE_NOT_EXIST);
}
}
/**
* Queries registered error handlers in an effort to resolve a database
* version conflict.
*
* @param dvce
* the database version conflict exception
* @return <code>true</code> if the conflict has been resolved as a result
* of this method call. Otherwise, <code>false</code>.
*/
private static boolean handleDatabaseVersionConflict(
DatabaseVersionConflictException dvce) {
List<IDatabaseVersionErrorHandler> errorHandlers =
new ArrayList<IDatabaseVersionErrorHandler>();
IConfigurationElement[] config = Platform.getExtensionRegistry()
.getConfigurationElementsFor(
PluginConstants.DB_VERSION_HANDLER_EXT_ID);
for (IConfigurationElement e : config) {
try {
final Object o = e
.createExecutableExtension(
PluginConstants.DB_VERSION_HANDLER_CLASS_ATTR);
if (o instanceof IDatabaseVersionErrorHandler) {
errorHandlers.add((IDatabaseVersionErrorHandler) o);
}
} catch (CoreException ce) {
log.warn(
Messages.ErrorOccurredInitializingDatabaseVersion
+ StringConstants.DOT, ce);
}
}
if (errorHandlers.isEmpty()) {
return false;
}
for (final IDatabaseVersionErrorHandler handler : errorHandlers) {
if (handler.getMinimumDatabaseMajorVersionNumber() <= dvce
.getDatabaseMajorVersion()) {
final AtomicBoolean isHandled = new AtomicBoolean(false);
ISafeRunnable runnable = new ISafeRunnable() {
public void handleException(Throwable t) {
log.warn(
Messages.
ErrorOccurredResolvingDatabaseVersionConflict
+ StringConstants.DOT, t);
}
public void run() throws Exception {
isHandled.set(handler.handleDatabaseError());
}
};
SafeRunner.run(runnable);
if (isHandled.get()) {
return true;
}
}
}
return false;
}
/**
* Validates the consistency of DbVersion and Jubula Client Version
*
* @param session
* current session
* @throws PMDatabaseConfException
* in case of any problem with db scheme
* @throws JBException
* in case of a configuration problem
* @throws DatabaseVersionConflictException
* @throws AmbiguousDatabaseVersionException
*/
@SuppressWarnings("unchecked")
private void validateDBVersion(EntityManager session)
throws PMDatabaseConfException, JBException,
DatabaseVersionConflictException, AmbiguousDatabaseVersionException {
List<DBVersionPO> hits = null;
try {
hits = session.createQuery("select version from DBVersionPO as version").getResultList(); //$NON-NLS-1$
} catch (RuntimeException e) {
// FIXME zeb We were catching a PersistenceException here, but that
// does not work for EclipseLink's JPA because they throw
// a DatabaseException if something goes wrong with the
// database (ex. missing table). We shouldn't have to
// worry about catching vendor-specific exceptions, but
// it looks like we do. See:
// http://stackoverflow.com/questions/2394885/what-exceptions-are-thrown-by-jpa-in-ejb-containers
// for a brief discussion on the topic.
Throwable cause = ExceptionUtils.getCause(e);
if (cause instanceof SQLException) {
SQLException se = (SQLException)cause;
if (se.getErrorCode() == 17002) {
final String msg = Messages.ProblemWithDatabaseSchemeConf
+ StringConstants.DOT;
log.error(msg);
throw new PMDatabaseConfException(msg,
MessageIDs.E_ERROR_IN_SCHEMA_CONFIG);
}
}
m_newDbSchemeInstalled =
installDbScheme(session.getEntityManagerFactory());
try {
hits = session.createQuery("select version from DBVersionPO as version").getResultList(); //$NON-NLS-1$
} catch (PersistenceException pe) {
final String msg = Messages.ProblemWithInstallingDBScheme
+ StringConstants.DOT;
log.error(msg);
throw new PMDatabaseConfException(msg,
MessageIDs.E_NO_DB_SCHEME);
} catch (DatabaseException dbe) {
// FIXME zeb EclipseLink's JPA throws a DatabaseException if
// something goes wrong with the database
// (ex. missing table), instead of wrapping it in a
// PersistenceException. We shouldn't have to
// worry about catching vendor-specific exceptions, but
// it looks like we do. See:
// http://stackoverflow.com/questions/2394885/what-exceptions-are-thrown-by-jpa-in-ejb-containers
// for a brief discussion on the topic.
final String msg = Messages.ProblemWithInstallingDBScheme
+ StringConstants.DOT;
log.error(msg);
throw new PMDatabaseConfException(msg,
MessageIDs.E_NO_DB_SCHEME);
}
}
if (!hits.isEmpty() && hits.size() == 1) {
DBVersionPO dbVersion = hits.get(0);
Version dbVers = new Version(dbVersion.getMajorVersion(),
dbVersion.getMinorVersion(), 0);
if (SemanticVersionUtil.isCompatibleWith(dbVers, MODEL_VERSION)) {
log.info(Messages.DBVersion + StringConstants.COLON
+ StringConstants.SPACE + Messages.OK);
} else {
log.error(Messages.DBVersion + StringConstants.COLON
+ StringConstants.SPACE + Messages.MajorVersionInvalid);
throw new DatabaseVersionConflictException(
dbVers.getMajor(), dbVers.getMinor());
}
} else {
log.error(Messages.DBVersion + StringConstants.COLON
+ StringConstants.SPACE + Messages.DBEntryMissingAmbiguous);
throw new AmbiguousDatabaseVersionException(hits);
}
}
/**
* Installs the DB scheme used by Jubula.
*
* @param entityManagerFactory
* The factory to use to create the entity manager in which the
* installation will occur.
*
* @return <code>true</code> if the installation was successful. Otherwise,
* <code>false</code>.
* @throws PMDatabaseConfException
* if the scheme couldn't be installed
* @throws JBException
* in case of configuration problems
*/
private static boolean installDbScheme(
EntityManagerFactory entityManagerFactory)
throws PMDatabaseConfException, JBException {
EntityManager em = null;
try {
em = entityManagerFactory.createEntityManager();
SchemaManager schemaManager =
new SchemaManager(em.unwrap(ServerSession.class));
schemaManager.replaceDefaultTables();
createOrUpdateDBVersion(em);
createOrUpdateDBGuard(em);
return true;
} catch (PersistenceException e) {
Throwable rootCause = ExceptionUtils.getRootCause(e);
if (rootCause instanceof SQLException) {
if (("08001").equals(((SQLException)rootCause).getSQLState())) { //$NON-NLS-1$
log.error(Messages.TheDBAllreadyUseAnotherProcess, e);
throw new JBException(rootCause.getMessage(),
MessageIDs.E_DB_IN_USE);
}
log.error(Messages.NoOrWrongUsernameOrPassword, e);
throw new JBException(e.getMessage(),
MessageIDs.E_NO_DB_CONNECTION);
}
final String msg = Messages.ProblemInstallingDBScheme
+ StringConstants.DOT;
log.error(msg);
throw new PMDatabaseConfException(msg, MessageIDs.E_NO_DB_SCHEME);
} finally {
if (em != null) {
try {
em.close();
} catch (Throwable e) {
// ignore
}
}
}
}
/**
*
* @param em
* entity manager
* @throws PersistenceException
* if we blow up
*/
private static void createOrUpdateDBVersion(EntityManager em)
throws PersistenceException {
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
try {
DBVersionPO version =
(DBVersionPO)em.createQuery("select version from DBVersionPO as version").getSingleResult(); //$NON-NLS-1$
version.setMajorVersion(MODEL_VERSION.getMajor());
version.setMinorVersion(MODEL_VERSION.getMinor());
em.merge(version);
} catch (NoResultException nre) {
em.merge(new DBVersionPO(MODEL_VERSION.getMajor(),
MODEL_VERSION.getMinor()));
}
tx.commit();
} catch (PersistenceException pe) {
if (tx != null) {
tx.rollback();
}
throw pe;
}
}
/**
*
* @param em
* entity manager
* @throws PersistenceException
* if we blow up
*/
private static void createOrUpdateDBGuard(EntityManager em)
throws PersistenceException {
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
try {
em.createQuery("select guard from DbGuardPO as guard").getSingleResult(); //$NON-NLS-1$
} catch (NoResultException nre) {
LockManager.initDbGuard(em);
}
tx.commit();
} catch (PersistenceException pe) {
if (tx != null) {
tx.rollback();
}
throw pe;
}
}
/**
* @return the only instance of the Persistor
*/
public static Persistor instance() {
return instance;
}
/**
* @param userName
* The user name.
* @param pwd
* The password.
* @param url
* connection string / URL
* @param monitor the progress monitor to use
* @return the only instance of the Persistor, building the connection to
* the database
* @throws JBFatalException .
* @throws JBException .
*/
private static Persistor instance(String userName, String pwd,
String url, IProgressMonitor monitor)
throws JBFatalException, JBException {
if (instance == null) {
try {
instance = new Persistor(userName, pwd, url, monitor);
DatabaseStateDispatcher.notifyListener(new DatabaseStateEvent(
DatabaseState.DB_LOGIN_SUCCEEDED));
} catch (DatabaseVersionConflictException e) {
final Integer dbMajorVersion = e.getDatabaseMajorVersion();
final Integer dbMinorVersion = e.getDatabaseMinorVersion();
final Integer cDBMajorVersion = MODEL_VERSION.getMajor();
final Integer cDBMinorVersion = MODEL_VERSION.getMinor();
final String errorMessage = NLS.bind(
Messages.DBVersionProblem, new Object[] { dbMajorVersion,
dbMinorVersion, cDBMajorVersion, cDBMinorVersion });
if (!handleDatabaseVersionConflict(e)) {
throw new PMDatabaseConfException(
errorMessage,
MessageIDs.E_INVALID_DB_VERSION);
}
}
LockManager.instance().startKeepAlive();
}
return instance;
}
/**
* Update internal db statistics after mass changes in the db.
* This is often needed to help the query optimizer doing the right things.
*/
public void updateDbStatistics() {
String cmd = dbConnectionInfo.getStatisticsCommand();
if (cmd != null) {
EntityManager em = openSession();
try {
Query q = em.createNativeQuery(cmd);
EntityTransaction tx = getTransaction(em);
q.executeUpdate();
commitTransaction(em, tx);
} catch (Throwable t) {
log.error("Updating DB statistics failed. This isn't critical, but will degrade performance.", t); //$NON-NLS-1$
} finally {
dropSession(em);
}
}
}
/**
* @return a new Session for the standard configuration
* @throws JBFatalAbortException
* if the open failed
*/
public EntityManager openSession() throws JBFatalAbortException {
try {
// FIXME zeb we used to configure the opened session with an
// interceptor in order to track progress. figure out a
// way to include something like an
// interceptor / entity listener.
// Reference: Session s = m_sf.openSession(HbmProgressInterceptor.getInstance());
EntityManager em = m_sf.createEntityManager();
m_sessions.add(em);
return em;
} catch (PersistenceException e) {
String msg = Messages.PersistenceErrorCreateEntityManagerFailed;
log.error(msg, e);
throw new JBFatalAbortException(msg, e,
MessageIDs.E_SESSION_FAILED);
}
}
/**
* @param s
* Session which is used for the transaction
* @param tx
* transaction to rollback
* @throws PMException
* in case of failed rollback
*/
public void rollbackTransaction(EntityManager s, EntityTransaction tx)
throws PMException {
Validate.notNull(s);
if (tx != null) {
// FIXME NLS
Validate.isTrue(tx.equals(s.getTransaction()),
"Session and Transaction don't match"); //$NON-NLS-1$
try {
tx.rollback();
} catch (PersistenceException e) {
log.error(Messages.RollbackFailed, e);
if (s.equals(GeneralStorage.getInstance().getMasterSession())) {
GeneralStorage.getInstance().recoverSession();
}
throw new PMException(Messages.RollbackFailed,
MessageIDs.E_DATABASE_GENERAL);
} finally {
removeLocks(s);
}
}
}
/**
* @param s
* Session to flush
* @throws PMException
* in case of failed flush
*/
public void flushSession(EntityManager s) throws PMException {
Validate.notNull(s, "No null value allowed"); //$NON-NLS-1$
try {
s.flush();
} catch (PersistenceException e) {
log.error(Messages.FlushFailed, e);
if (s.equals(GeneralStorage.getInstance().getMasterSession())) {
GeneralStorage.getInstance().recoverSession();
}
throw new PMException(Messages.FlushFailed,
MessageIDs.E_DATABASE_GENERAL);
} finally {
removeLocks(s);
}
}
/**
* Get a transaction for a session. If there is already a transaction
* active, this transaction will be used. Otherwise a new transaction will
* be started.
*
* @param s
* Session for this transaction
* @return A already active or a fresh transaction
*/
public EntityTransaction getTransaction(EntityManager s) {
EntityTransaction result = s.getTransaction();
if (result.isActive()) {
if (log.isDebugEnabled()) {
log.debug(Messages.JoiningTransaction);
}
} else {
result.begin();
if (log.isDebugEnabled()) {
log.debug(Messages.StartingTransaction);
}
}
return result;
}
/**
* @param s
* session
* @param tx
* transaction
* @throws PMReadException
* {@inheritDoc}
* @throws PMAlreadyLockedException
* {@inheritDoc}
* @throws PMDirtyVersionException
* {@inheritDoc}
* @throws PMException
* {@inheritDoc}
* @throws ProjectDeletedException
* if the project was deleted in another instance
*/
public void commitTransaction(EntityManager s, EntityTransaction tx)
throws PMReadException, PMAlreadyLockedException,
PMDirtyVersionException, PMException, ProjectDeletedException {
Validate.notNull(s);
Validate.notNull(tx);
Validate.isTrue(tx.equals(s.getTransaction()),
Messages.SessionAndTransactionDontMatch);
try {
tx.commit();
} catch (PersistenceException e) {
log.error("persistence exception occured", e); //$NON-NLS-1$
if (s != null
&& s.equals(GeneralStorage.getInstance().
getMasterSession())) {
PersistenceManager.handleDBExceptionForMasterSession(null, e);
} else {
PersistenceManager.handleDBExceptionForAnySession(null, e, s);
}
} finally {
removeLocks(s);
}
}
/**
* @param s
* session to close
* @param dropLocks
* should I drop LockManager locks?
* @throws PMException
* in case of any db error
*/
private void closeSession(EntityManager s, boolean dropLocks)
throws PMException {
Validate.notNull(s);
try {
if (s.isOpen()) {
try {
EntityTransaction tx = s.getTransaction();
if (tx.isActive()) {
rollbackTransaction(s, tx);
}
} finally {
s.close();
}
}
} catch (PersistenceException e) {
log.error(Messages.CloseSessionFailed, e);
} finally {
if (dropLocks) {
removeLocks(s);
}
m_sessions.remove(s);
}
}
/**
* This method performs a closeSession(), but will never report a failure.
* It's also null-safe.
*
* @param s
* the session to close, null allowed
*/
public void dropSession(EntityManager s) {
try {
if (s != null) {
closeSession(s, true);
}
} catch (PMException e) {
log.error(Messages.CouldntDropSsession, e);
}
}
/**
* This method performs a closeSession(), but will never report a failure.
* It's also null-safe. It will not release database locks, i.e. it's
* supposed to be used by the LockManager itself;
*
* @param s
* the session to close, null allowed
*/
public void dropSessionWithoutLockRelease(EntityManager s) {
try {
if (s != null) {
closeSession(s, false);
}
} catch (PMException e) {
log.error(Messages.CouldntDropSsession, e);
}
}
/**
* @param s
* session which contain object to refresh
* @param po
* object to refresh
* @param lockMode
* lock Mode for refresh operation
* @throws PMDirtyVersionException
* in case of version conflict
* @throws PMAlreadyLockedException
* if object is locked
* @throws PMException
* if rollback failed
* @throws ProjectDeletedException
* if the project was deleted in another instance
*/
public void refreshPO(EntityManager s, IPersistentObject po,
LockModeType lockMode)
throws PMDirtyVersionException, PMAlreadyLockedException,
PMException, ProjectDeletedException {
Validate.notNull(s, Messages.NoNullValueAllowed);
try {
s.refresh(po, lockMode);
} catch (PersistenceException e) {
PersistenceManager.handleDBExceptionForMasterSession(po, e);
}
}
/**
* @param s
* session for which to remove the list with locked objects
*/
private void removeLocks(EntityManager s) {
LockManager.instance().unlockPOs(s);
}
/**
* @param s
* session, which contain the current persistent object
* @param po
* object to lock
* @throws PMAlreadyLockedException
* in case of locked object
* @throws PMDirtyVersionException
* if the PO has a different version than its counterpart in the
* db
* @throws PMObjectDeletedException
* if the po was deleted by another app
*/
public void lockPO(EntityManager s, IPersistentObject po)
throws PMAlreadyLockedException, PMDirtyVersionException,
PMObjectDeletedException {
if (!LockManager.instance().lockPO(s, po, true)) {
String poName = po != null ? po.getName() : StringConstants.EMPTY;
long poId = po != null ? po.getId() : -1;
throw new PMAlreadyLockedException(po,
"PO " + po + " (name=" + poName + "; id=" + poId + ") locked in db.", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
MessageIDs.E_OBJECT_IN_USE);
}
}
/**
* tries to lock a set of POs
*
* @param sess
* Session
* @param objectsToLock
* Set<IPersistentObject>
* @throws PMAlreadyLockedException
* in case of locked object
* @throws PMDirtyVersionException
* if the PO has a different version than its counterpart in the
* db
* @throws PMObjectDeletedException
* if the po was deleted by another app
*/
public void lockPOSet(EntityManager sess,
Set<? extends IPersistentObject> objectsToLock)
throws PMAlreadyLockedException, PMDirtyVersionException,
PMObjectDeletedException {
boolean unlock = true;
try {
unlock = !LockManager.instance().lockPOs(sess, objectsToLock, true);
} finally {
if (unlock) {
removeLocks(sess);
}
}
}
/**
* @param s
* session, which contain the current persistent object
* @param po
* object to delete
* @throws PMException
* in case of failed rollback
* @throws PMAlreadyLockedException
* in case of locked object
* @throws PMDirtyVersionException
* in case of version conflict
* @throws ProjectDeletedException
* if the project was deleted in another instance
* @throws InterruptedException
* if the oepration was canceled
*/
public void deletePO(EntityManager s, IPersistentObject po)
throws PMException,
PMAlreadyLockedException, PMDirtyVersionException,
ProjectDeletedException, InterruptedException {
Validate.notNull(s);
try {
s.remove(po);
} catch (PersistenceException e) {
if (e.getCause() instanceof InterruptedException) {
// Operation was canceled.
throw new InterruptedException();
}
PersistenceManager.handleDBExceptionForMasterSession(po, e);
}
}
/**
* @param pwd
* The pw to set.
*/
public static void setPw(String pwd) {
Persistor.pw = pwd;
}
/**
* @param url
* connection strnig
*/
public static void setUrl(String url) {
Persistor.dburl = url;
}
/**
* @param usr
* The user to set.
*/
public static void setUser(String usr) {
Persistor.user = usr;
}
/**
* @param connectionName
* The schema name.
*/
public static void setDbConnectionName(
DatabaseConnectionInfo connectionName) {
Persistor.dbConnectionInfo = connectionName;
}
/**
*
* @return the username used in the currently active
* Entity Manager Factory. Returns <code>null</code> if no
* Entity Manager Factory is currently active or if the active
* Entity Manager Factory does not use a username.
*/
public String getCurrentDBUser() {
return getFactoryProperty(m_sf, PersistenceUnitProperties.JDBC_USER);
}
/**
*
* @return the password used in the currently active
* Entity Manager Factory. Returns <code>null</code> if no
* Entity Manager Factory is currently active or if the active
* Entity Manager Factory does not use a password.
*/
public String getCurrentDBPw() {
return getFactoryProperty(
m_sf, PersistenceUnitProperties.JDBC_PASSWORD);
}
/**
*
* @return the connection URL used in the currently active
* Entity Manager Factory. Returns <code>null</code> if no
* Entity Manager Factory is currently active or if the active
* Entity Manager Factory does not use a connection URL.
*/
public String getCurrentDBUrl() {
return getFactoryProperty(m_sf, PersistenceUnitProperties.JDBC_URL);
}
/**
*
* @param factory The factory from which to retrieve the property.
* @param key The key/name of the property to retrieve.
* @return the string value of the retrieved property. Returns
* <code>null</code> if the factory does not contain the desired
* property or if the value of the property is <code>null</code>
* or if the string representation of the property value is
* <code>null</code>.
*/
private static String getFactoryProperty(
EntityManagerFactory factory, String key) {
if (factory != null && factory.isOpen()) {
return ObjectUtils.toString(factory.getProperties().get(key));
}
return null;
}
/**
* Migrates the structure of the database to match the version of the
* currently running client. <em>This effectively deletes all Jubula data
* currently in the database, so be careful how you use it.</em>
*
* @throws JBFatalException
* if a fatal exception occurs during migration.
* @throws JBException
* if a general exception occurs during migration.
* @return true if migration succeeded; false otherwise
*/
public static boolean migrateDatabaseStructure() throws JBFatalException,
JBException {
boolean migrationSuccess = false;
EntityManagerFactory migrationEntityManagerFactory = null;
try {
migrationEntityManagerFactory =
createEntityManagerFactory(dbConnectionInfo, user, pw, dburl);
migrationSuccess = installDbScheme(migrationEntityManagerFactory);
instance = instance(user, pw, dburl, new NullProgressMonitor());
instance.m_newDbSchemeInstalled = true;
} finally {
if (migrationEntityManagerFactory != null) {
migrationEntityManagerFactory.close();
}
}
return migrationSuccess;
}
/**
*
* @param connectionInfo The information to use in initializing the
* factory. Must not be <code>null</code>.
* @param username The username to use in initializing the factory.
* @param password The password to use in initializing the factory.
* @param url The connection URL to use in initializing the factory.
* @return the created factory.
*/
private static EntityManagerFactory createEntityManagerFactory(
DatabaseConnectionInfo connectionInfo,
String username, String password, String url) {
Validate.notNull(connectionInfo);
// use the classloader for this bundle when initializing
// the EntityManagerFactory
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(PersistenceUnitProperties.CLASSLOADER,
Persistor.class.getClassLoader());
properties.put(PersistenceUnitProperties.JDBC_DRIVER,
connectionInfo.getDriverClassName());
properties.put(PersistenceUnitProperties.JDBC_USER, username);
properties.put(PersistenceUnitProperties.JDBC_PASSWORD, password);
properties.put(PersistenceUnitProperties.JDBC_URL,
StringUtils.defaultString(url,
connectionInfo.getConnectionUrl()));
properties.put(PersistenceUnitProperties.BATCH_WRITING,
connectionInfo.getBatchWriting());
String batchWritingSize = connectionInfo.getBatchWritingSize();
if (batchWritingSize != null) {
properties.put(PersistenceUnitProperties.BATCH_WRITING_SIZE,
batchWritingSize);
}
return Persistence.createEntityManagerFactory(
DEFAULT_PU_NAME, properties);
}
/**
*
*/
public void dispose() {
if (m_sf != null) {
try {
for (EntityManager sess : m_sessions) {
if (sess.isOpen()) {
sess.close();
}
}
m_sessions.clear();
m_sf.close();
DatabaseStateDispatcher.notifyListener(new DatabaseStateEvent(
DatabaseState.DB_LOGOUT_SUCCEEDED));
} catch (Throwable e) {
log.error(Messages.DisposeOfPersistorFailed, e);
}
}
instance = null;
}
/**
* @return the name of the JDBC driver class used in the currently active
* Entity Manager Factory. Returns <code>null</code> if no
* Entity Manager Factory is currently active or if the active
* Entity Manager Factory does not use a JDBC driver.
*/
public String getCurrentDBDriverClass() {
return getFactoryProperty(m_sf, PersistenceUnitProperties.JDBC_DRIVER);
}
/**
* this method counts the lock requests for the DB. It will not prevent any
* action but provide a hint that some thread needs this DB now.
*/
public void lockDB() {
m_dbLockCnt.incrementAndGet();
}
/**
* Decrements the lock count. See lockDB()
*/
public void unlockDB() {
if (m_dbLockCnt.decrementAndGet() < 0) {
m_dbLockCnt.set(0);
}
}
/**
*
* @return <code>true</code> if there are threads relying on this DB
* instance.
*/
public boolean isDBLocked() {
return m_dbLockCnt.get() > 0;
}
}