/******************************************************************************* * 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.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.EntityTransaction; import javax.persistence.FlushModeType; import javax.persistence.NoResultException; import javax.persistence.PersistenceException; import javax.persistence.Query; import org.eclipse.jubula.client.core.businessprocess.IWritableComponentNameCache; import org.eclipse.jubula.client.core.businessprocess.progress.OperationCanceledUtil; import org.eclipse.jubula.client.core.i18n.Messages; import org.eclipse.jubula.client.core.model.ICompNamesPairPO; import org.eclipse.jubula.client.core.model.IComponentNamePO; import org.eclipse.jubula.client.core.model.IProjectPO; import org.eclipse.jubula.client.core.model.NodeMaker; import org.eclipse.jubula.client.core.persistence.locking.LockManager; import org.eclipse.jubula.client.core.persistence.locking.LockedObjectPO; 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.messagehandling.MessageIDs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Logic to persist Component Names. * * @author BREDEX GmbH * @created Apr 8, 2008 */ public class CompNamePM extends AbstractNamePM { /** * <code>COMP_NAME_TABLE_ID</code> */ private static final String COMP_NAME_TABLE_ID = "org.eclipse.jubula.client.core.model.ComponentNamePO"; //$NON-NLS-1$ /** * Query Parameter for component name guid. */ private static final String P_COMP_NAME_GUID = "compNameGuid"; //$NON-NLS-1$ /** * Query Parameter for parent project id. */ private static final String P_PARENT_PROJECT_ID = "parentProjectId"; //$NON-NLS-1$ /** Query Parameter for single Logical Names */ private static final String P_NAME = "logName"; //$NON-NLS-1$ /** Query to find a preexisting Component Name */ private static final String Q_PREEX_NAMES_SINGLE = "select compName from ComponentNamePO compName where compName.hbmParentProjectId = :" //$NON-NLS-1$ + P_PARENT_PROJECT_ID + " and compName.hbmName = :" + P_NAME; //$NON-NLS-1$ /** * Query to find the number of Comp Name Pairs having a CN as their first or second CN */ private static final String Q_NUM_REUSE_TYPE_PAIRS = "select count(compNamePair) from CompNamesPairPO as compNamePair" //$NON-NLS-1$ + " where (compNamePair.secondName = :" + P_COMP_NAME_GUID //$NON-NLS-1$ + " or compNamePair.firstName = :" + P_COMP_NAME_GUID //$NON-NLS-1$ + ") and compNamePair.hbmParentProjectId = :" + P_PARENT_PROJECT_ID; //$NON-NLS-1$ /** * Query to find the types of reuse of a component name by test steps. */ private static final String Q_REUSE_TYPE_CAPS_COUNT = "select count (cap.componentType) from CapPO as cap" //$NON-NLS-1$ + " where cap.componentName = :" + P_COMP_NAME_GUID //$NON-NLS-1$ + " and cap.hbmParentProjectId = :" + P_PARENT_PROJECT_ID; //$NON-NLS-1$ /** * Query to find the GUIDs of all Component Names within a given Project * that reference other Component Names. */ private static final String Q_REF_COMP_NAME_GUIDS = "select compName.hbmGuid from ComponentNamePO as compName" //$NON-NLS-1$ + " where compName.hbmReferencedGuid is not null" //$NON-NLS-1$ + " and compName.hbmParentProjectId = :" + P_PARENT_PROJECT_ID; //$NON-NLS-1$ /** * Query to find the GUIDs of all Component Names that are referenced by * one or more Test Steps in a given Project. */ private static final String Q_CAP_COMP_NAME_GUIDS = "select cap.componentName from CapPO as cap" //$NON-NLS-1$ + " where cap.hbmParentProjectId = :" + P_PARENT_PROJECT_ID; //$NON-NLS-1$ /** Query to find the number of non-trivial associations of a CN */ private static final String Q_ASSOC_COUNT = " select count(assoc) from ObjectMappingAssoziationPO as assoc" //$NON-NLS-1$ + " join assoc.logicalNames as logical" //$NON-NLS-1$ + " where logical = :" + P_COMP_NAME_GUID //$NON-NLS-1$ + " and assoc.technicalName is not null" //$NON-NLS-1$ + " and assoc.hbmParentProjectId = :" + P_PARENT_PROJECT_ID; //$NON-NLS-1$ /** * Query to find the GUIDs of all Component Names that are referenced by * the First Name of one or more Component Name Pairs in a given Project. */ private static final String Q_PAIR_FIRST_COMP_NAME_GUIDS = "select pair.firstName from CompNamesPairPO as pair" //$NON-NLS-1$ + " where pair.hbmParentProjectId = :" + P_PARENT_PROJECT_ID; //$NON-NLS-1$ /** * Query to find the GUIDs of all Component Names that are referenced by * the Second Name of one or more Component Name Pairs in a given Project. */ private static final String Q_PAIR_SECOND_COMP_NAME_GUIDS = "select pair.secondName from CompNamesPairPO as pair" //$NON-NLS-1$ + " where pair.hbmParentProjectId = :" + P_PARENT_PROJECT_ID; //$NON-NLS-1$ /** * Query to find the GUIDs of all Component Names that are referenced by * one or more Object Mapping Associations in a given Project. */ private static final String Q_ASSOC_COMP_NAME_GUIDS = "select logicalName from ObjectMappingAssoziationPO as assoc" //$NON-NLS-1$ + " join assoc.logicalNames as logicalName" //$NON-NLS-1$ + " where assoc.hbmParentProjectId = :" + P_PARENT_PROJECT_ID; //$NON-NLS-1$ /** * Query to find the GUIDs of all Component Names that are referenced by * one or more other Component Names in a given Project. */ private static final String Q_COMP_NAME_REF_GUIDS = "select compName.hbmReferencedGuid from ComponentNamePO as compName" //$NON-NLS-1$ + " where compName.hbmReferencedGuid is not null" //$NON-NLS-1$ + " and compName.hbmParentProjectId = :" + P_PARENT_PROJECT_ID; //$NON-NLS-1$ /** GUIDs of Component Names to delete from the DB */ private static final String P_COMP_NAME_REMOVAL_LIST = "compNameRemovalList"; //$NON-NLS-1$ /** * "Query" to delete all Component Names based on GUID and parent Project. */ private static final String Q_DELETE_COMP_NAMES = "delete from ComponentNamePO compName" //$NON-NLS-1$ + " where compName.hbmParentProjectId = :" + P_PARENT_PROJECT_ID //$NON-NLS-1$ + " and compName.hbmGuid in :" + P_COMP_NAME_REMOVAL_LIST; //$NON-NLS-1$ /** * Class used to collect data required after executing a save * @author BREDEX GmbH */ public static class SaveCompNamesData { /** The Component Names managed by the save session */ private List<IComponentNamePO> m_dbVersions; /** The Component Name guids that need to be changed due to duplicate logical names */ private Map<String, String> m_guidsToSwap; /** * Constructor * @param dbVersions the managed Component Names * @param guidsToSwap the required guid swaps */ public SaveCompNamesData(List<IComponentNamePO> dbVersions, Map<String, String> guidsToSwap) { m_dbVersions = dbVersions; m_guidsToSwap = guidsToSwap; } /** * returns the DB Versions * @return the DB Versions */ public List<IComponentNamePO> getDBVersions() { return m_dbVersions; } /** * returns the guids to swap * @return the guids to swap map */ public Map<String, String> getGuidsToSwap() { return m_guidsToSwap; } } /** * <code>log</code>logger */ private static Logger log = LoggerFactory.getLogger(ParamNamePM.class); /** The ILockedObjectPO for locks on ComponentNames tabe in database */ private static LockedObjectPO lockObj = null; /** * Read component names from the master session * @param parentProjectId id from root project * @return list of all param name objects for given project * @throws PMException in case of any db problem */ public static final List<IComponentNamePO> readAllCompNamesRO( Long parentProjectId) throws PMException { EntityManager s = GeneralStorage.getInstance().getMasterSession(); return readAllCompNames(parentProjectId, s); } /** * @param parentProjectId id from root project * @param s The session to use for the query. * @return list of all param name objects for given project * @throws PMException in case of any db problem */ @SuppressWarnings("unchecked") private static final List<IComponentNamePO> readAllCompNames( Long parentProjectId, EntityManager s) throws PMException { final List <IComponentNamePO> compNames = new ArrayList<IComponentNamePO>(); try { final Query q = s.createQuery( "select compName from ComponentNamePO compName where compName.hbmParentProjectId = :parentProjId"); //$NON-NLS-1$ q.setParameter("parentProjId", parentProjectId); //$NON-NLS-1$ compNames.addAll(q.getResultList()); } catch (PersistenceException e) { OperationCanceledUtil.checkForOperationCanceled(e); log.error(Messages.CouldNotReadComponentNamesFromDBOfProjectWithID + StringConstants.SPACE + StringConstants.APOSTROPHE + String.valueOf(parentProjectId) + StringConstants.APOSTROPHE, e); PersistenceManager.handleDBExceptionForAnySession(null, e, s); } return compNames; } /** * deletes all ComponentNames of the Project with the * given rootProjId from DataBase without a commit! * @param s the Session which is to use. * @param rootProjId the parent project ID. * @throws PMException in case of any db problem */ public static final void deleteCompNames(EntityManager s, Long rootProjId) throws PMException { try { lockComponentNames(s); final Query q = s.createQuery( "delete from ComponentNamePO c where c.hbmParentProjectId = :rootProjId"); //$NON-NLS-1$ q.setParameter("rootProjId", rootProjId); //$NON-NLS-1$ q.executeUpdate(); } catch (PersistenceException e) { OperationCanceledUtil.checkForOperationCanceled(e); log.error(Messages.CouldNotReadComponentNamesFromDBOfProjectWithID + StringConstants.SPACE + StringConstants.APOSTROPHE + String.valueOf(rootProjId) + StringConstants.APOSTROPHE, e); PersistenceManager.handleDBExceptionForAnySession(null, e, s); } } /** * Gets a lock of the ComponentNames Table in database. * @param s A Session. * @throws PMAlreadyLockedException if no lock is available. */ private static final void lockComponentNames(EntityManager s) throws PMObjectDeletedException, PMAlreadyLockedException { final long timeOut = 5000; try { if (lockObj == null) { initLockedObj(); } final long start = System.currentTimeMillis(); while (!LockManager.instance().lockPO(s, lockObj, false)) { try { Thread.sleep(100); } catch (InterruptedException e) { // nothing is OK here! } final long stop = System.currentTimeMillis(); if ((stop - start) > timeOut) { throw new PMAlreadyLockedException(lockObj, Messages.CouldNotGetALockOnTableCOMPONENT_NAMES, MessageIDs.E_DATABASE_GENERAL); } } } catch (PMDirtyVersionException e) { // cannot happen because checkVersion == false! Assert.notReached(Messages.ExceptionShouldNotHappen + StringConstants.COLON + StringConstants.SPACE + e); } } /** * Releases the lock of the ComponentNames Table in database. */ private static final void unlockComponentNames() { if (lockObj != null) { LockManager.instance().unlockPO(lockObj); } } /** * Initializes the locking of the CompNames table */ private static void loadLockedObj() { EntityManager sess = null; try { sess = Persistor.instance().openSession(); EntityTransaction tx = sess.getTransaction(); tx.begin(); final Query q = sess.createQuery( "select p from LockedObjectPO p where p.hbmObjectName = :hbmObjectName"); //$NON-NLS-1$ q.setParameter("hbmObjectName", COMP_NAME_TABLE_ID); //$NON-NLS-1$ try { lockObj = (LockedObjectPO)q.getSingleResult(); } catch (NoResultException nre) { lockObj = null; } tx.commit(); } catch (PersistenceException e) { throw new JBFatalAbortException( Messages.ErrorInitializingComponentNamesLocking + StringConstants.EXCLAMATION_MARK, e, MessageIDs.E_DATABASE_GENERAL); } finally { Persistor.instance().dropSessionWithoutLockRelease(sess); } } /** * Initializes the LockedObj. */ private static void initLockedObj() { createOrUpdateCompNamesLock(); loadLockedObj(); } /** * * @throws PersistenceException if we blow up */ private static void createOrUpdateCompNamesLock() throws PersistenceException { EntityManager s = null; EntityTransaction tx = null; try { s = Persistor.instance().openSession(); tx = s.getTransaction(); tx.begin(); final Query q = s.createQuery( "select p from LockedObjectPO p where p.hbmObjectName = :hbmObjectName"); //$NON-NLS-1$ q.setParameter("hbmObjectName", COMP_NAME_TABLE_ID); //$NON-NLS-1$ try { q.getSingleResult(); } catch (NoResultException nre) { s.persist(new LockedObjectPO(COMP_NAME_TABLE_ID)); } tx.commit(); } catch (PersistenceException e) { if (tx != null) { tx.rollback(); } throw e; } finally { Persistor.instance().dropSession(s); } } /** * Get the number of all current types of reuse for the * Component Name with the given GUID. * * @param session * The session in which to execute the various queries required * to find the types of reuse. * @param parentProjectId * The id of the active project. Only reuses and component names * belonging to the project with this id will be considered * during the check. * @param compNameGuid * The guid of the component name to check. * @return number of the reusage. */ public static synchronized long getNumberOfUsages( EntityManager session, Long parentProjectId, String compNameGuid) { // Number of references of the given component name long referenceCount = 0; FlushModeType flushMode = session.getFlushMode(); // Disable automatic flushing during this read-only operation because // a flush may cause database-level locks to be acquired. session.setFlushMode(FlushModeType.COMMIT); try { referenceCount += countOfReusedCompnameTypesInCaps(session, parentProjectId, compNameGuid); IProjectPO inSessionProject = session .find(NodeMaker.getProjectPOClass(), parentProjectId); referenceCount += getAutAssociations(session, parentProjectId, compNameGuid); referenceCount += getNumPairs(compNameGuid, parentProjectId, session); } finally { session.setFlushMode(flushMode); } return referenceCount; } /** * Get associated component types * * @param session * The session in which to execute the various queries required * to find the types of reuse. * @param parentProjectId * The id of the active project. Only reuses and component names * belonging to the project with this id will be considered * during the check. * @param compNameGuid * compNameGuid The guid of the component name to check. * @param assocCompTypes * @param compSystem * @param allAutsForProject * @return associated component types */ private static long getAutAssociations(EntityManager session, Long parentProjectId, String compNameGuid) { StringBuilder capQuerySb = new StringBuilder(Q_ASSOC_COUNT); final Query capQuery = session.createQuery(capQuerySb.toString()); capQuery.setParameter(P_PARENT_PROJECT_ID, parentProjectId); capQuery.setParameter(P_COMP_NAME_GUID, compNameGuid); long res = (long) capQuery.getSingleResult(); return res; } /** * @param session * the entity manager session * @param parentProjectId * The id of the active project. Only reuses and component names * belonging to the project with this id will be considered * during the check. * @param compNameGuid * The guid of the component name to check. * @return number of the types of reuse of a component name by test steps */ private static long countOfReusedCompnameTypesInCaps(EntityManager session, Long parentProjectId, String compNameGuid) { StringBuilder capQuerySb = new StringBuilder(Q_REUSE_TYPE_CAPS_COUNT); final Query capQuery = session.createQuery(capQuerySb.toString()); capQuery.setParameter(P_PARENT_PROJECT_ID, parentProjectId); capQuery.setParameter(P_COMP_NAME_GUID, compNameGuid); return (long) capQuery.getSingleResult(); } /** * Get reused component names. * @param compNameGuid The guid of the component name to check. * @param parentProjectId The id of the active project. Only reuses and * component names belonging to the project with this * id will be considered during the check. * @param session The session in which to execute the various queries required to * find the types of reuse. * @return reused component names */ private static long getNumPairs( String compNameGuid, Long parentProjectId, EntityManager session) { StringBuilder reuseQuerySb = new StringBuilder(Q_NUM_REUSE_TYPE_PAIRS); final Query reuseQuery = session.createQuery(reuseQuerySb.toString()); reuseQuery.setParameter(P_PARENT_PROJECT_ID, parentProjectId); reuseQuery.setParameter(P_COMP_NAME_GUID, compNameGuid); Collection<ICompNamesPairPO> compNamePairs = new HashSet<ICompNamesPairPO>(); return (long) reuseQuery.getResultList().get(0); } /** * Merges the Component Names from the cache to the session * @param sess the session * @param projId the ID of the project * @param cache the cache * @return the data */ public static SaveCompNamesData flushCompNames(EntityManager sess, Long projId, IWritableComponentNameCache cache) { ArrayList<IComponentNamePO> dbVersions = new ArrayList<>(); HashMap<String, String> guidsToSwap = new HashMap<>(); Map<String, IComponentNamePO> localChanges = cache.getLocalChanges(); if (localChanges == null) { // avoiding NullComponentNameMappers return null; } IComponentNamePO dbPO = null; IComponentNamePO newCN = null; Query q = sess.createQuery(Q_PREEX_NAMES_SINGLE); q.setParameter(P_PARENT_PROJECT_ID, projId).setMaxResults(1); List<IComponentNamePO> resList; for (String guid : localChanges.keySet()) { // First we check whether a Component Name with the same logical name // and current projectId already exists in the DB // If it does and has a different guid, we are going to use the DB version everywhere newCN = localChanges.get(guid); if (newCN.getParentProjectId() == null || projId.equals(newCN.getParentProjectId())) { q.setParameter(P_NAME, newCN.getName()); resList = q.getResultList(); if (!resList.isEmpty()) { if (!resList.get(0).getGuid().equals(guid)) { newCN = resList.get(0); guidsToSwap.put(guid, resList.get(0).getGuid()); } else { // Component Name with the same guid exists in DB // If it was created parallelly in separate editors // Then the current one may have a null id newCN.setId(resList.get(0).getId()); } sess.detach(resList.get(0)); } } // If Component Name has been removed from the DB by another user // we repersist it... dbPO = null; if (newCN.getId() != null) { dbPO = sess.find(newCN.getClass(), newCN.getId()); } if (dbPO == null) { sess.persist(newCN); dbPO = newCN; } if (newCN.getName() != null) { dbPO.setName(newCN.getName()); } if (dbPO.getParentProjectId() == null) { dbPO.setParentProjectId(projId); } dbVersions.add(dbPO); } if (!guidsToSwap.isEmpty()) { cache.handleExistingNames(guidsToSwap); } return new SaveCompNamesData(dbVersions, guidsToSwap); } /** * Merges the Component Names from the cache to the session * @param sess the session * @param projId the ID of the project * @param cache the cache */ public static void flushCompNamesImport(EntityManager sess, Long projId, IWritableComponentNameCache cache) { Map<String, IComponentNamePO> localChanges = cache.getLocalChanges(); for (String guid : localChanges.keySet()) { IComponentNamePO cN = localChanges.get(guid); if (cN.getParentProjectId() == null) { cN.setParentProjectId(projId); } sess.persist(localChanges.get(guid)); } } /** * Deletes all unused Component Names that reference other Component Names. * Will only delete Component Names belonging to the Project with the given * ID. The search for reuse instances is also limited to the scope of the * Project with the given ID. * * @param projectId The ID of the Project to use as the scope for this * operation. * @param session The session in which the operation will take place. */ @SuppressWarnings("unchecked") public static void removeUnusedCompNames( Long projectId, EntityManager session) { Query refCompNameGuidQuery = session.createQuery(Q_REF_COMP_NAME_GUIDS); refCompNameGuidQuery.setParameter(P_PARENT_PROJECT_ID, projectId); List refCompNameGuids = refCompNameGuidQuery.getResultList(); if (refCompNameGuids.isEmpty()) { return; } Query capQuery = session.createQuery(Q_CAP_COMP_NAME_GUIDS); capQuery.setParameter(P_PARENT_PROJECT_ID, projectId); List capCompNames = capQuery.getResultList(); refCompNameGuids.removeAll(capCompNames); if (refCompNameGuids.isEmpty()) { return; } Query pairQuery = session.createQuery(Q_PAIR_FIRST_COMP_NAME_GUIDS); pairQuery.setParameter(P_PARENT_PROJECT_ID, projectId); List pairCompNameGuids = pairQuery.getResultList(); refCompNameGuids.removeAll(pairCompNameGuids); if (refCompNameGuids.isEmpty()) { return; } pairQuery = session.createQuery(Q_PAIR_SECOND_COMP_NAME_GUIDS); pairQuery.setParameter(P_PARENT_PROJECT_ID, projectId); pairCompNameGuids = pairQuery.getResultList(); refCompNameGuids.removeAll(pairCompNameGuids); if (refCompNameGuids.isEmpty()) { return; } Query assocQuery = session.createQuery(Q_ASSOC_COMP_NAME_GUIDS); assocQuery.setParameter(P_PARENT_PROJECT_ID, projectId); List assocCompNameGuids = assocQuery.getResultList(); refCompNameGuids.removeAll(assocCompNameGuids); if (refCompNameGuids.isEmpty()) { return; } Query compNameRefQuery = session.createQuery(Q_COMP_NAME_REF_GUIDS); compNameRefQuery.setParameter(P_PARENT_PROJECT_ID, projectId); List compNameRefGuidList = compNameRefQuery.getResultList(); refCompNameGuids.removeAll(compNameRefGuidList); if (refCompNameGuids.isEmpty()) { return; } Query deleteQuery = session.createQuery(Q_DELETE_COMP_NAMES); deleteQuery.setParameter(P_PARENT_PROJECT_ID, projectId); deleteQuery.setParameter(P_COMP_NAME_REMOVAL_LIST, refCompNameGuids); deleteQuery.executeUpdate(); } /** * sets lockObj to null, when database is changed */ public static void dispose() { lockObj = null; } }