/*******************************************************************************
* 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.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;
import org.apache.commons.lang.Validate;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jubula.client.core.businessprocess.CompNameCacheFactory;
import org.eclipse.jubula.client.core.businessprocess.ParamNameBPDecorator;
import org.eclipse.jubula.client.core.businessprocess.CompNameManager;
import org.eclipse.jubula.client.core.businessprocess.IWritableComponentNameCache;
import org.eclipse.jubula.client.core.i18n.Messages;
import org.eclipse.jubula.client.core.model.IComponentNamePO;
import org.eclipse.jubula.client.core.model.INodePO;
import org.eclipse.jubula.client.core.model.IParamDescriptionPO;
import org.eclipse.jubula.client.core.model.IParameterInterfacePO;
import org.eclipse.jubula.client.core.model.IPersistentObject;
import org.eclipse.jubula.client.core.model.IProjectPO;
import org.eclipse.jubula.client.core.model.ISpecTestCasePO;
import org.eclipse.jubula.client.core.model.ITcParamDescriptionPO;
import org.eclipse.jubula.client.core.model.ITestDataCategoryPO;
import org.eclipse.jubula.client.core.model.ITestJobPO;
import org.eclipse.jubula.client.core.model.ITestSuitePO;
import org.eclipse.jubula.client.core.persistence.CompNamePM.SaveCompNamesData;
import org.eclipse.jubula.tools.internal.constants.StringConstants;
import org.eclipse.jubula.tools.internal.exception.Assert;
import org.eclipse.jubula.tools.internal.exception.JBFatalAbortException;
import org.eclipse.jubula.tools.internal.exception.ProjectDeletedException;
import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author BREDEX GmbH
* @created 15.07.2005
*/
public class EditSupport {
/** standard logging */
private static Logger log = LoggerFactory.getLogger(EditSupport.class);
/** Persistence (JPA / EclipseLink) session for editing */
private EntityManager m_session;
/** working version of persistent object */
private IPersistentObject m_workVersion;
/**
* <code>m_lockedObjects</code>objects are locked by current edit support
*/
private List<IPersistentObject> m_lockedObjects =
new ArrayList<IPersistentObject>();
/**
* <code>m_transaction</code> actual transaction
*/
private EntityTransaction m_transaction = null;
/**
* <code>m_isLocked</code> lock status for m_workVersion
*/
private boolean m_isLocked = false;
/**
* <code>m_isValid</code> signals the validity of this instance
*/
private boolean m_isValid = true;
/**
* <code>m_mapper</code>mapper for resolving and persistence of parameter names
*/
private ParamNameBPDecorator m_paramMapper = null;
/**
* <code>m_compMapper</code>mapper for resolving and persistence of
* component names
*/
private IWritableComponentNameCache m_cache;
/**
* Instantiate edit support for the supplied persistent object
*
* @param po Master instance for the new editable persistent object
* @param paramMapper mapper for resolving and persistence of parameter names
* the mapper is null in case of po objects not derived of NodePO
* @throws PMException in case of unexpected db error
*
*/
public EditSupport(IPersistentObject po, ParamNameBPDecorator paramMapper)
throws PMException {
init();
m_workVersion = createWorkVersion(po);
m_paramMapper = paramMapper;
m_cache = CompNameCacheFactory.createCompNameCache(
m_workVersion);
}
/**
* (re)set internal data
*/
private void init() {
m_workVersion = null;
m_isValid = true;
m_session = Persistor.instance().openSession();
m_transaction = Persistor.instance().getTransaction(m_session);
}
/**
* @param po
* the persistent object for which the working version shall be
* created.
* @return a working version of the PO supplied to the constructor. This
* version is db identical to its original, but not Java identical.
* @throws PMException
* in case of unspecified db error
*
*/
public IPersistentObject createWorkVersion(IPersistentObject po)
throws PMException {
Assert.verify(m_isValid,
Messages.InvalidInstanceForInvokingOfThisMethod);
Validate.notNull(po,
Messages.OriginalObjectForCreatingOfWorkversionIsNull
+ StringConstants.DOT);
try {
IPersistentObject result = m_session
.find(po.getClass(), po.getId());
if (result == null) {
throw new EntityNotFoundException(
Messages.UnableToFind + StringConstants.SPACE
+ po.getClass().getName()
+ StringConstants.SPACE + Messages.WithID
+ StringConstants.SPACE + po.getId());
}
/* if po in the mastersession is newer than the corresponding
object in the editor session, the instance in the editor session
must be from the session cache; therefore the instance from the
editor session must be evicted and reloaded */
if ((result.getVersion() == null)
|| (po.getVersion().intValue()
> result.getVersion().intValue())) {
m_session.detach(result);
result = m_session.find(po.getClass(), po.getId());
if (result == null) {
throw new EntityNotFoundException(
Messages.UnableToFind + StringConstants.SPACE
+ po.getClass().getName()
+ StringConstants.SPACE + Messages.WithID
+ StringConstants.SPACE + po.getId());
}
}
return result;
} catch (PersistenceException e) {
PersistenceManager.handleDBExceptionForEditor(po, e, this);
}
return null;
}
/**
* locks the actual work version for modification. This method may be called
* several times during one editing session if the node is locked and the
* user retries the edit operation.
*
* @throws PMDirtyVersionException
* in case of version conflict
* @throws PMAlreadyLockedException
* if another user has locked this object
* @throws PMReadException
* in case of reading error of DB
* @throws PMException
* in case of general db error
*/
public void lockWorkVersion() throws PMReadException,
PMAlreadyLockedException, PMDirtyVersionException, PMException {
Assert.verify(m_isValid,
Messages.InvalidInstanceForInvokingOfThisMethod);
try {
if (m_workVersion instanceof ISpecTestCasePO) {
List<IParamDescriptionPO> params =
((ISpecTestCasePO)m_workVersion)
.getParameterList();
for (IParamDescriptionPO desc : params) {
((ITcParamDescriptionPO)desc)
.setParamNameMapper(m_paramMapper);
}
} else if (m_workVersion instanceof ITestDataCategoryPO) {
for (IParameterInterfacePO pio
: ((ITestDataCategoryPO)m_workVersion)
.getTestDataChildren()) {
List<IParamDescriptionPO> params = pio.getParameterList();
for (IParamDescriptionPO desc : params) {
((ITcParamDescriptionPO)desc)
.setParamNameMapper(m_paramMapper);
}
}
}
Persistor.instance().lockPO(m_session, m_workVersion);
m_lockedObjects.add(m_workVersion);
m_isLocked = true;
} catch (PersistenceException e) {
PersistenceManager.handleDBExceptionForEditor(m_workVersion, e,
this);
}
}
/**
* closes the actual session
*/
private void closeSession() {
if (Persistor.instance() != null) {
Persistor.instance().dropSession(m_session);
}
invalidate();
}
/**
* persists the workversion in database
*
* @throws PMReadException
* in case of stale state exception for object to refresh
* @throws PMSaveException
* if commit failed
* @throws PMException
* in case of failed rollback
* @throws ProjectDeletedException
* if the project was deleted in another instance
*/
public void saveWorkVersion()
throws PMReadException, PMSaveException, PMException,
ProjectDeletedException {
SaveCompNamesData saveData = null;
if (!m_isValid) {
throw new JBFatalAbortException(
Messages.NotAllowedToSaveAnUnlockedWorkversion
+ StringConstants.DOT, MessageIDs.E_CANNOT_SAVE_INVALID);
}
if (!m_isLocked) {
throw new JBFatalAbortException(
Messages.NotAllowedToSaveAnUnlockedWorkversion
+ StringConstants.DOT, MessageIDs.E_CANNOT_SAVE_UNLOCKED);
}
trackChanges();
boolean stayLocked = false;
try {
boolean mayModifyParamNames =
m_workVersion instanceof ISpecTestCasePO
|| m_workVersion instanceof ITestDataCategoryPO;
if (mayModifyParamNames) {
saveParamNames();
}
/* CARE: isDirty() only checks for the synchronisation state
* of the session and the database; --> e.g. isDirty() is
* false if the session has been flushed
*/
// The saving of param names that occurs above may flush the
// session, which would make the session not "dirty".
// We cover this case by assuming that any situation in
// which param names may be modified is a situation where we definitely want to save+commit.
if (mayModifyParamNames
|| m_session.unwrap(JpaEntityManager.class)
.getUnitOfWork().hasChanges()) {
Long projId = GeneralStorage.getInstance().getProject()
.getId();
saveData = CompNamePM.flushCompNames(
m_session, projId, m_cache);
Persistor.instance().commitTransaction(m_session,
m_transaction);
if (m_paramMapper != null) {
m_paramMapper.updateStandardMapperAndCleanup(projId);
}
refreshOriginalVersions();
if (saveData != null) {
CompNameManager.getInstance().compNamesChanged(saveData);
}
} else {
Persistor.instance().rollbackTransaction(m_session,
m_transaction);
}
m_lockedObjects.clear();
if (m_session != null) {
m_transaction = m_session.getTransaction();
m_transaction.begin();
} else {
init();
}
} catch (PersistenceException e) {
PersistenceManager.handleDBExceptionForEditor(
m_workVersion, e, this);
} finally {
m_isLocked = stayLocked;
detachObjects(saveData);
}
}
/**
* Detaching all managed Component Names
* @param data t save data
*/
private void detachObjects(SaveCompNamesData data) {
if (m_session == null || data == null) {
return;
}
for (IComponentNamePO cN : data.getDBVersions()) {
try {
m_session.detach(cN);
} catch (IllegalArgumentException e) {
// Should not happen, but even if it does, it means the
// Component Name is already detached, so we rejoice
}
}
}
/**
* Tracks, that a test case, a test suite, or a test job has been modified.
*/
private void trackChanges() {
if (m_workVersion instanceof ISpecTestCasePO
|| m_workVersion instanceof ITestSuitePO
|| m_workVersion instanceof ITestJobPO) {
INodePO node = (INodePO) m_workVersion;
node.addTrackedChange("modified", true); //$NON-NLS-1$
}
}
/**
* Persists the Parameter Names.
* @throws PMException
*/
private void saveParamNames() throws PMException {
m_paramMapper.persist(m_session,
GeneralStorage.getInstance().getProject().getId());
}
/**
* Refreshes the original versions, which were possibly modified in editor
*
* @throws ProjectDeletedException
* if the project was deleted in another instance
*/
private void refreshOriginalVersions() throws ProjectDeletedException {
try {
final EntityManager masterSession = GeneralStorage.getInstance()
.getMasterSession();
IPersistentObject original = getOriginal();
if (original != null) {
masterSession.refresh(masterSession.merge(getWorkVersion()));
GeneralStorage.getInstance().fireDataModified(original);
}
} catch (PersistenceException e) {
log.error(Messages.RefreshOfOriginalVersionFailed
+ StringConstants.DOT, e);
GeneralStorage.getInstance().reloadMasterSession(
new NullProgressMonitor());
}
}
/**
* discards the work version
*
*/
public void close() {
Assert.verify(m_isValid,
Messages.InvalidInstanceForInvokingOfThisMethod);
closeSession();
}
/**
* resets all instance variables
*/
private void invalidate() {
m_isValid = false;
m_isLocked = false;
m_workVersion = null;
m_transaction = null;
m_session = null;
m_lockedObjects.clear();
}
/**
* @return Returns the original.
*/
public IPersistentObject getOriginal() {
return GeneralStorage.getInstance().getMasterSession()
.find(m_workVersion.getClass(), m_workVersion.getId());
}
/**
* @return locked objects of current edit support
*/
public List<IPersistentObject> getLockedObjects() {
return m_lockedObjects;
}
/**
* @return Returns the workVersion.
*/
public IPersistentObject getWorkVersion() {
return m_workVersion;
}
/**
* @return Returns the session.
*/
public EntityManager getSession() {
return m_session;
}
/**
* attachs the detached workVersion to a new session
* to use for postprocessing of Persistence (JPA / EclipseLink) exceptions without refresh of objects
* @throws PMException in case of any db error
*/
public void reinitializeEditSupport() throws PMException {
try {
IPersistentObject workVersion = m_workVersion;
close();
init();
m_workVersion = workVersion;
m_workVersion = m_session.merge(m_workVersion);
m_cache = CompNameCacheFactory.createCompNameCache(
m_workVersion);
} catch (PersistenceException e) {
final String msg = Messages.ReinitOfSessionFailed;
log.error(msg);
throw new PMException(msg,
MessageIDs.E_DATABASE_GENERAL);
}
}
/**
* refreshs the editSession
* @throws PMException in case of any db error
*/
public void reloadEditSession() throws PMException {
try {
IPersistentObject workVersion = m_workVersion;
close();
init();
m_workVersion = createWorkVersion(workVersion);
m_cache = CompNameCacheFactory.createCompNameCache(
m_workVersion);
if (m_paramMapper != null) {
Long projId =
GeneralStorage.getInstance().getProject().getId();
m_paramMapper.updateStandardMapperAndCleanup(projId);
}
} catch (PersistenceException e) {
final String msg = Messages.ReinitOfSessionFailed;
log.error(msg);
throw new PMException(msg,
MessageIDs.E_DATABASE_GENERAL);
}
}
/**
* @return project associated with current session
* @throws PMException
* in case of unspecified db error
*
*/
public IProjectPO getWorkProject() throws PMException {
IProjectPO masterProj = GeneralStorage.getInstance().getProject();
IProjectPO workProj = null;
try {
workProj = m_session.find(
masterProj.getClass(), masterProj.getId());
if (workProj == null) {
throw new EntityNotFoundException(
Messages.UnableToFind + StringConstants.SPACE
+ masterProj.getClass().getName()
+ StringConstants.SPACE + Messages.WithID
+ StringConstants.SPACE + masterProj.getId());
}
return workProj;
} catch (PersistenceException e) {
PersistenceManager.handleDBExceptionForEditor(masterProj, e, this);
}
return null;
}
/**
* @return the ParamMapper, businessLogic for Parameter Names.
*/
public ParamNameBPDecorator getParamMapper() {
return m_paramMapper;
}
/**
* Returns the cache
* @return the cache
*/
public IWritableComponentNameCache getCache() {
return m_cache;
}
}