/*******************************************************************************
* Copyright (c) 2016 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.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jubula.client.core.model.IExecObjContPO;
import org.eclipse.jubula.client.core.model.INodePO;
import org.eclipse.jubula.client.core.model.IPersistentObject;
import org.eclipse.jubula.client.core.model.IProjectPO;
import org.eclipse.jubula.client.core.model.ISpecObjContPO;
import org.eclipse.jubula.client.core.persistence.GeneralStorage;
import org.eclipse.jubula.client.core.persistence.IExecPersistable;
import org.eclipse.jubula.client.core.persistence.ISpecPersistable;
/**
* Class helping handling native SQL queries
* Native SQL queries ignore our internal locks, so before executing these,
* the caller is responsible for acquiring proper locks for any possibly affected nodes
* The versions of the objects are properly increased in the DB
* @author BREDEX GmbH
*
*/
public class NativeSQLUtils {
/** Maximum number of elements in a DB Query list - 1000 for Oracle... */
private static final int MAXLGT = 990;
/** Exception message */
private static final String FAIL = "Operation failed due to database error."; //$NON-NLS-1$
/** private constructor */
private NativeSQLUtils() {
// private constructor
}
/**
* Returns a list of Ids in the collection
* @param objects the objects - should not be empty
* @return the list
*/
public static String getIdList(
Collection<? extends IPersistentObject> objects) {
if (objects.isEmpty()) {
throw new IllegalArgumentException("Collection should not be empty."); //$NON-NLS-1$
}
StringBuilder str = new StringBuilder("("); //$NON-NLS-1$
for (IPersistentObject per : objects) {
str.append(per.getId());
str.append(","); //$NON-NLS-1$
}
str.deleteCharAt(str.length() - 1);
str.append(")"); //$NON-NLS-1$
return str.toString();
}
/**
* Returns a list of lists of ids in the collection, each list is limited in size
* @param objects the objects
* @return the list of lists
*/
public static List<String> getSlicedIdLists(
Collection<? extends IPersistentObject> objects) {
if (objects.isEmpty()) {
throw new IllegalArgumentException("Collection should not be empty."); //$NON-NLS-1$
}
StringBuilder str = null;
int num = 0;
List<String> result = new ArrayList<>();
for (IPersistentObject per : objects) {
if (num == 0) {
str = new StringBuilder("("); //$NON-NLS-1$
}
str.append(per.getId());
str.append(","); //$NON-NLS-1$
num++;
if (num == MAXLGT) {
str.deleteCharAt(str.length() - 1);
str.append(")"); //$NON-NLS-1$
result.add(str.toString());
num = 0;
}
}
if (num != 0) {
str.deleteCharAt(str.length() - 1);
str.append(")"); //$NON-NLS-1$
result.add(str.toString());
}
return result;
}
/**
* Deletes a collection of independent top nodes from either the TC or TS Browsers
* The nodes have to be managed by the master session, and will be chaged
* @param sess the session
* @param nodes the nodes
* @param monitor the progress monitor or null
*/
public static void deleteFromTCTSTreeAFFECTS(EntityManager sess,
Collection<INodePO> nodes, IProgressMonitor monitor) {
for (INodePO node : nodes) {
node.goingToBeDeleted(sess);
removeNodeAFFECTS(sess, node);
if (monitor != null) {
monitor.worked(1);
}
}
Query q;
for (String smallList : getSlicedIdLists(nodes)) {
q = sess.createNativeQuery("delete from NODE where ID in " + smallList); //$NON-NLS-1$
q.executeUpdate();
}
}
/**
* Removes a node from its parent
* This operation changes both the DB and the memory data.
* @param sess the session
* @param node the node
*/
public static void removeNodeAFFECTS(EntityManager sess, INodePO node) {
Query q1, q2;
IProjectPO proj = GeneralStorage.getInstance().getProject();
int pos = -1;
INodePO par = node.getParentNode();
if (par == ISpecObjContPO.TCB_ROOT_NODE) {
pos = proj.getSpecObjCont().getSpecObjList().indexOf(node);
q1 = sess.createNativeQuery("delete from SPEC_CONT_NODE where SPECOBJCONTPO_ID = ?1 and HBMSPECOBJLIST_ID = ?2 and IDX = ?3"); //$NON-NLS-1$
q1.setParameter(1, proj.getSpecObjCont().getId());
q2 = sess.createNativeQuery("update SPEC_CONT_NODE set IDX = IDX - 1 where SPECOBJCONTPO_ID = ?1 and IDX > ?2"); //$NON-NLS-1$
q2.setParameter(1, proj.getSpecObjCont().getId());
} else if (par == IExecObjContPO.TSB_ROOT_NODE) {
pos = proj.getExecObjCont().getExecObjList().indexOf(node);
q1 = sess.createNativeQuery("delete from EXEC_CONT_NODE where EXECOBJCONTPO_ID = ?1 and HBMEXECOBJLIST_ID = ?2 and IDX = ?3"); //$NON-NLS-1$
q1.setParameter(1, proj.getExecObjCont().getId());
q2 = sess.createNativeQuery("update EXEC_CONT_NODE set IDX = IDX - 1 where EXECOBJCONTPO_ID = ?1 and IDX > ?2"); //$NON-NLS-1$
q2.setParameter(1, proj.getExecObjCont().getId());
} else {
pos = par.indexOf(node);
q1 = sess.createNativeQuery("update NODE set PARENT = null, IDX = null where ID = ?1"); //$NON-NLS-1$
q1.setParameter(1, node.getId()).executeUpdate();
q1 = null;
q2 = sess.createNativeQuery("update NODE set IDX = IDX - 1 where PARENT = ?1 and IDX > ?2"); //$NON-NLS-1$
q2.setParameter(1, par.getId());
}
// removing from a regular node parent requires a very different query which is already executed
if (q1 != null) {
q1.setParameter(2, node.getId()).setParameter(3, pos);
int res = q1.executeUpdate();
if (res != 1) {
throw new PersistenceException(FAIL);
}
}
q2.setParameter(2, pos).executeUpdate();
if (par == ISpecObjContPO.TCB_ROOT_NODE) {
proj.getSpecObjCont().removeSpecObject((ISpecPersistable) node);
} else if (par == IExecObjContPO.TSB_ROOT_NODE) {
proj.getExecObjCont().removeExecObject((IExecPersistable) node);
} else {
node.getParentNode().removeNode(node);
}
}
/**
* Adds a node to another to the end of the child list
* The parent node can be the TCB or TSB root node - in this case the master
* session's Spec(Exec)ObjCont is affected
* This operation changes both the DB and the memory data.
* @param sess the session
* @param toAdd the node to add
* @param par the target
*/
public static void addNodeAFFECTS(EntityManager sess, INodePO toAdd,
IPersistentObject par) {
Query q1;
IProjectPO proj = GeneralStorage.getInstance().getProject();
int pos = -1;
if (par instanceof ISpecObjContPO) {
pos = ((ISpecObjContPO) par).getSpecObjList().size();
q1 = sess.createNativeQuery("insert into SPEC_CONT_NODE (SPECOBJCONTPO_ID, HBMSPECOBJLIST_ID, IDX) values (?1, ?2, ?3)"); //$NON-NLS-1$
} else if (par instanceof IExecObjContPO) {
pos = ((IExecObjContPO) par).getExecObjList().size();
q1 = sess.createNativeQuery("insert into EXEC_CONT_NODE (EXECOBJCONTPO_ID, HBMEXECOBJLIST_ID, IDX) values (?1, ?2, ?3)"); //$NON-NLS-1$
} else {
pos = ((INodePO) par).getNodeListSize();
q1 = sess.createNativeQuery("update NODE set PARENT = ?1, IDX = ?3 where ID = ?2"); //$NON-NLS-1$
}
q1.setParameter(1, par.getId()).setParameter(2, toAdd.getId());
int res = q1.setParameter(3, pos).executeUpdate();
if (res != 1) {
throw new PersistenceException(FAIL);
}
if (par instanceof ISpecObjContPO) {
((ISpecObjContPO) par).addSpecObject((ISpecPersistable) toAdd);
} else if (par instanceof IExecObjContPO) {
((IExecObjContPO) par).addExecObject((IExecPersistable) toAdd);
} else {
((INodePO) par).addNode(toAdd);
}
}
/**
* Moves a node from somewhere to somewhere else
* This operation changes both the DB and the memory data.
* @param sess the session
* @param toMove node to move
* @param target the target
*/
public static void moveNode(EntityManager sess, INodePO toMove,
IPersistentObject target) {
removeNodeAFFECTS(sess, toMove);
addNodeAFFECTS(sess, toMove, target);
}
}