- * This Java action provides a consistent environment for Mendix projects to perform an arbitrary parameterised SELECT SQL query on relational external databases.
+ * This Java action provides a consistent environment for Mendix projects to perform an arbitrary parameterized SELECT SQL query on relational external databases.
* JDBC (Java Database Connectivity) API, a standard Java API, is used when this Java action attempts
* to connect with a Relational Database for which a JDBC driver exists.
* The JDBC drivers for the databases you want to connect to, must be placed inside the userlib directory of a project.
@@ -55,7 +55,7 @@
* @return >
* SELECT Query result as a list of objects.
*/
-public class ExecuteParameterisedQuery extends CustomJavaAction>
+public class ExecuteParameterizedQuery extends CustomJavaAction>
{
private java.lang.String jdbcUrl;
private java.lang.String userName;
@@ -63,7 +63,7 @@ public class ExecuteParameterisedQuery extends CustomJavaAction executeAction() throws Exception
@java.lang.Override
public java.lang.String toString()
{
- return "ExecuteParameterisedQuery";
+ return "ExecuteParameterizedQuery";
}
// BEGIN EXTRA CODE
diff --git a/javasource/databaseconnector/actions/ExecuteParameterisedStatement.java b/javasource/databaseconnector/actions/ExecuteParameterizedStatement.java
similarity index 90%
rename from javasource/databaseconnector/actions/ExecuteParameterisedStatement.java
rename to javasource/databaseconnector/actions/ExecuteParameterizedStatement.java
index dcd1a18..a78d504 100644
--- a/javasource/databaseconnector/actions/ExecuteParameterisedStatement.java
+++ b/javasource/databaseconnector/actions/ExecuteParameterizedStatement.java
@@ -17,7 +17,7 @@
/**
*
- * This Java action provides a consistent environment for Mendix projects to perform an arbitrary parameterised SQL statement on relational
+ * This Java action provides a consistent environment for Mendix projects to perform an arbitrary parameterized SQL statement on relational
* external databases.
* JDBC (Java Database Connectivity) API, a standard Java API, is used when this Java action attempts
* to connect with a Relational Database for which a JDBC driver exists.
@@ -48,14 +48,14 @@
* @return
* Number of affected rows.
*/
-public class ExecuteParameterisedStatement extends CustomJavaAction
+public class ExecuteParameterizedStatement extends CustomJavaAction
{
private java.lang.String jdbcUrl;
private java.lang.String userName;
private java.lang.String password;
private com.mendix.systemwideinterfaces.javaactions.parameters.IStringTemplate sql;
- public ExecuteParameterisedStatement(IContext context, java.lang.String jdbcUrl, java.lang.String userName, java.lang.String password, com.mendix.systemwideinterfaces.javaactions.parameters.IStringTemplate sql)
+ public ExecuteParameterizedStatement(IContext context, java.lang.String jdbcUrl, java.lang.String userName, java.lang.String password, com.mendix.systemwideinterfaces.javaactions.parameters.IStringTemplate sql)
{
super(context);
this.jdbcUrl = jdbcUrl;
@@ -78,7 +78,7 @@ public java.lang.Long executeAction() throws Exception
@java.lang.Override
public java.lang.String toString()
{
- return "ExecuteParameterisedStatement";
+ return "ExecuteParameterizedStatement";
}
// BEGIN EXTRA CODE
diff --git a/javasource/databaseconnectortest/actions/AssertEqualsListEntityValues.java b/javasource/databaseconnectortest/actions/AssertEqualsListEntityValues.java
index e2ca13f..2251b35 100644
--- a/javasource/databaseconnectortest/actions/AssertEqualsListEntityValues.java
+++ b/javasource/databaseconnectortest/actions/AssertEqualsListEntityValues.java
@@ -11,7 +11,6 @@
import static java.lang.String.format;
import static java.util.stream.Collectors.joining;
-import static org.apache.commons.lang3.StringUtils.isEmpty;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
@@ -90,6 +89,10 @@ private Optional compare(int objectNr, IMendixObject expected, IMendixOb
return isEmpty(message) ? Optional.empty() : Optional.of(format("Row %s: ", objectNr) + message);
}
+ private boolean isEmpty(String message) {
+ return message == null || message.isBlank();
+ }
+
private Optional compare(PrimitiveType primitiveType, IMendixObjectMember> expected, IMendixObjectMember> actual) {
Object expectedValue = toComparableValue(expected.getValue(getContext()));
Object actualValue = toComparableValue(actual.getValue(getContext()));
diff --git a/javasource/databaseconnectortest/test/PreparedStatementCreatorTest.java b/javasource/databaseconnectortest/test/PreparedStatementCreatorTest.java
index b9e69ea..943202f 100644
--- a/javasource/databaseconnectortest/test/PreparedStatementCreatorTest.java
+++ b/javasource/databaseconnectortest/test/PreparedStatementCreatorTest.java
@@ -1,6 +1,5 @@
package databaseconnectortest.test;
-import com.amazon.dsi.exceptions.InvalidArgumentException;
import com.mendix.systemwideinterfaces.javaactions.parameters.IStringTemplate;
import com.mendix.systemwideinterfaces.javaactions.parameters.ITemplateParameter;
import com.mendix.systemwideinterfaces.javaactions.parameters.TemplateParameterType;
@@ -111,7 +110,7 @@ public void testStringQuery() throws SQLException {
}
@Test(expected = IllegalArgumentException.class)
- public void testUnknownParameterType() throws InvalidArgumentException, SQLException {
+ public void testUnknownParameterType() throws SQLException {
StringTemplateBuilder builder = new StringTemplateBuilder();
builder.addParameter(null, TemplateParameterType.valueOf("nonexisting value"));
diff --git a/javasource/objecthandling/XPath.java b/javasource/objecthandling/XPath.java
index d62c14d..24eeaf8 100644
--- a/javasource/objecthandling/XPath.java
+++ b/javasource/objecthandling/XPath.java
@@ -1,823 +1,823 @@
-package objecthandling;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import objecthandling.ImmutablePair;
-
-import org.apache.commons.lang3.StringEscapeUtils;
-import org.apache.commons.lang3.StringUtils;
-
-import com.mendix.core.Core;
-import com.mendix.core.CoreException;
-import com.mendix.systemwideinterfaces.core.IContext;
-import com.mendix.systemwideinterfaces.core.IMendixIdentifier;
-import com.mendix.systemwideinterfaces.core.IMendixObject;
-
-public class XPath
-{
- /** Build in tokens, see: https://world.mendix.com/display/refguide3/XPath+Keywords+and+System+Variables
- *
- */
-
- public static final String CurrentUser = "[%CurrentUser%]";
- public static final String CurrentObject = "[%CurrentObject%]";
-
- public static final String CurrentDateTime = "[%CurrentDateTime%]";
- public static final String BeginOfCurrentDay = "[%BeginOfCurrentDay%]";
- public static final String EndOfCurrentDay = "[%EndOfCurrentDay%]";
- public static final String BeginOfCurrentHour = "[%BeginOfCurrentHour%]";
- public static final String EndOfCurrentHour = "[%EndOfCurrentHour%]";
- public static final String BeginOfCurrentMinute = "[%BeginOfCurrentMinute%]";
- public static final String EndOfCurrentMinute = "[%EndOfCurrentMinute%]";
- public static final String BeginOfCurrentMonth = "[%BeginOfCurrentMonth%]";
- public static final String EndOfCurrentMonth = "[%EndOfCurrentMonth%]";
- public static final String BeginOfCurrentWeek = "[%BeginOfCurrentWeek%]";
- public static final String EndOfCurrentWeek = "[%EndOfCurrentWeek%]";
-
- public static final String DayLength = "[%DayLength%]";
- public static final String HourLength = "[%HourLength%]";
- public static final String MinuteLength = "[%MinuteLength%]";
- public static final String SecondLength = "[%SecondLength%]";
- public static final String WeekLength = "[%WeekLength%]";
- public static final String YearLength = "[%YearLength%]";
- public static final String ID = "id";
-
- /** End builtin tokens */
-
- private String entity;
- private int offset = 0;
- private int limit = -1;
- private LinkedHashMap sorting = new LinkedHashMap(); //important, linked map!
- private LinkedList closeStack = new LinkedList();
- private StringBuffer builder = new StringBuffer();
- private IContext context;
- private Class proxyClass;
- private boolean requiresBinOp = false; //state property, indicates whether and 'and' needs to be inserted before the next constraint
-
- public static XPath create(IContext c, String entityType) {
- XPath res = new XPath(c, IMendixObject.class);
-
- res.entity = entityType;
-
- return res;
- }
-
- public static XPath create(IContext c, Class proxyClass) {
- return new XPath(c, proxyClass);
- }
-
- private XPath(IContext c, Class proxyClass) {
- try
- {
- if (proxyClass != IMendixObject.class)
- this.entity = (String) proxyClass.getMethod("getType").invoke(null);
- }
- catch (Exception e)
- {
- throw new IllegalArgumentException("Failed to determine entity type of proxy class. Did you provide a valid proxy class? '" + proxyClass.getName() + "'");
- }
-
- this.proxyClass = proxyClass;
- this.context = c;
- }
-
- private XPath autoInsertAnd() {
- if (requiresBinOp)
- and();
- return this;
- }
-
- private XPath requireBinOp(boolean requires) {
- requiresBinOp = requires;
- return this;
- }
-
- public XPath offset(int offset2) {
- if (offset2 < 0)
- throw new IllegalArgumentException("Offset should not be negative");
- this.offset = offset2;
- return this;
- }
-
- public XPath limit(int limit2) {
- if (limit2 < -1 || limit2 == 0)
- throw new IllegalArgumentException("Limit should be larger than zero or -1. ");
-
- this.limit = limit2;
- return this;
- }
-
- public XPath addSortingAsc(Object... sortparts) {
- assertOdd(sortparts);
- sorting.put(StringUtils.join(sortparts, '/'), "asc");
- return this;
- }
-
- public XPath addSortingDesc(Object... sortparts) {
- sorting.put(StringUtils.join(sortparts, "/"), "desc");
- return this;
- }
-
- public XPath eq(Object attr, Object valuecomparison) {
- return compare(attr, "=", valuecomparison);
- }
-
- public XPath eq(Object... pathAndValue) {
- assertEven(pathAndValue);
- return compare(Arrays.copyOfRange(pathAndValue, 0, pathAndValue.length -1), "=", pathAndValue[pathAndValue.length -1 ]);
- }
-
- public XPath equalsIgnoreCase(Object attr, String value) {
- //(contains(Name, $email) and length(Name) = length($email)
- return
- subconstraint()
- .contains(attr, value)
- .and()
- .append(" length(" + attr + ") = ").append(value == null ? "0" : valueToXPathValue(value.length()))
- .close();
- }
-
- public XPath notEq(Object attr, Object valuecomparison) {
- return compare(attr, "!=", valuecomparison);
- }
-
- public XPath notEq(Object... pathAndValue) {
- assertEven(pathAndValue);
- return compare(Arrays.copyOfRange(pathAndValue, 0, pathAndValue.length -1), "!=", pathAndValue[pathAndValue.length -1 ]);
- }
-
- public XPath contains(Object attr, String value)
- {
- autoInsertAnd().append(" contains(").append(String.valueOf(attr)).append(",").append(valueToXPathValue(value)).append(") ");
- return this.requireBinOp(true);
- }
-
- public XPath compare(Object attr, String operator, Object value) {
- return compare(new Object[] {attr}, operator, value);
- }
-
- public XPath compare(Object[] path, String operator, Object value) {
- assertOdd(path);
- autoInsertAnd().append(StringUtils.join(path, '/')).append(" ").append(operator).append(" ").append(valueToXPathValue(value));
- return this.requireBinOp(true);
- }
-
- public XPath hasReference(Object... path)
- {
- assertEven(path); //Reference + entity type
- autoInsertAnd().append(StringUtils.join(path, '/'));
- return this.requireBinOp(true);
- }
-
- public XPath subconstraint(Object... path) {
- assertEven(path);
- autoInsertAnd().append(StringUtils.join(path, '/')).append("[");
- closeStack.push("]");
- return this.requireBinOp(false);
- }
-
- public XPath subconstraint() {
- autoInsertAnd().append("(");
- closeStack.push(")");
- return this.requireBinOp(false);
- }
-
- public XPath addConstraint()
- {
- if (!closeStack.isEmpty() && !closeStack.peek().equals("]"))
- throw new IllegalStateException("Cannot add a constraint while in the middle of something else..");
-
- return append("][").requireBinOp(false);
- }
-
- public XPath close() {
- if (closeStack.isEmpty())
- throw new IllegalStateException("XPathbuilder close stack is empty!");
- append(closeStack.pop());
- return requireBinOp(true);
- //MWE: note that a close does not necessary require a binary operator, for example with two subsequent block([bla][boe]) constraints,
- //but openening a binary constraint reset the flag, so that should be no issue
- }
-
- public XPath or() {
- if (!requiresBinOp)
- throw new IllegalStateException("Received 'or' but no binary operator was expected");
- return append(" or ").requireBinOp(false);
- }
-
- public XPath and() {
- if (!requiresBinOp)
- throw new IllegalStateException("Received 'and' but no binary operator was expected");
- return append(" and ").requireBinOp(false);
- }
-
- public XPath not() {
- autoInsertAnd();
- closeStack.push(")");
- return append(" not(").requireBinOp(false);
- }
-
- private void assertOdd(Object[] stuff) {
- if (stuff == null || stuff.length == 0 || stuff.length % 2 == 0)
- throw new IllegalArgumentException("Expected an odd number of xpath path parts");
- }
-
- private void assertEven(Object[] stuff) {
- if (stuff == null || stuff.length == 0 || stuff.length % 2 == 1)
- throw new IllegalArgumentException("Expected an even number of xpath path parts");
- }
-
- public XPath append(String s) {
- builder.append(s);
- return this;
- }
-
- public String getXPath() {
-
- if (builder.length() > 0)
- return "//" + this.entity + "[" + builder.toString() + "]";
- return "//" + this.entity;
- }
-
- private void assertEmptyStack() throws IllegalStateException
- {
- if (!closeStack.isEmpty())
- throw new IllegalStateException("Invalid xpath expression, not all items where closed");
- }
-
- public long count( ) throws CoreException {
- assertEmptyStack();
-
- return Core.retrieveXPathQueryAggregate(context, "count(" + getXPath() +")");
- }
-
-
- public IMendixObject firstMendixObject() throws CoreException {
- assertEmptyStack();
-
- List result = Core.retrieveXPathQuery(context, getXPath(), 1, offset, sorting);
- if (result.isEmpty())
- return null;
- return result.get(0);
- }
-
- public T first() throws CoreException {
- return createProxy(context, proxyClass, firstMendixObject());
- }
-
-
- /**
- * Given a set of attribute names and values, tries to find the first object that matches all conditions, or creates one
- *
- * @param autoCommit: whether the object should be committed once created (default: true)
- * @param keysAndValues
- * @return
- * @throws CoreException
- */
- public T findOrCreateNoCommit(Object... keysAndValues) throws CoreException
- {
- T res = findFirst(keysAndValues);
-
- return res != null ? res : constructInstance(false, keysAndValues);
- }
-
-
- public T findOrCreate(Object... keysAndValues) throws CoreException {
- T res = findFirst(keysAndValues);
-
- return res != null ? res : constructInstance(true, keysAndValues);
-
- }
-
- public T findOrCreateSynchronized(Object... keysAndValues) throws CoreException, InterruptedException {
- T res = findFirst(keysAndValues);
-
- if (res != null) {
- return res;
- } else {
- synchronized (Core.getMetaObject(entity)) {
- IContext synchronizedContext = context.getSession().createContext().createSudoClone();
- try {
- synchronizedContext.startTransaction();
- res = createProxy(synchronizedContext, proxyClass, XPath.create(synchronizedContext, entity).findOrCreate(keysAndValues));
- synchronizedContext.endTransaction();
- return res;
- } catch (CoreException e) {
- if (synchronizedContext.isInTransaction()) {
- synchronizedContext.rollbackTransAction();
- }
- throw e;
- }
- }
- }
- }
-
- public T findFirst(Object... keysAndValues)
- throws IllegalStateException, CoreException
- {
- if (builder.length() > 0)
- throw new IllegalStateException("FindFirst can only be used on XPath which do not have constraints already");
-
- assertEven(keysAndValues);
- for(int i = 0; i < keysAndValues.length; i+= 2)
- eq(keysAndValues[i], keysAndValues[i + 1]);
-
- T res = this.first();
- return res;
- }
-
-
- /**
- * Creates one instance of the type of this XPath query, and initializes the provided attributes to the provided values.
- * @param keysAndValues AttributeName, AttributeValue, AttributeName2, AttributeValue2... list.
- * @return
- * @throws CoreException
- */
- public T constructInstance(boolean autoCommit, Object... keysAndValues) throws CoreException
- {
- assertEven(keysAndValues);
- IMendixObject newObj = Core.instantiate(context, this.entity);
-
- for(int i = 0; i < keysAndValues.length; i+= 2)
- newObj.setValue(context, String.valueOf(keysAndValues[i]), toMemberValue(keysAndValues[i + 1]));
-
- if (autoCommit)
- Core.commit(context, newObj);
-
- return createProxy(context, proxyClass, newObj);
- }
-
- /**
- * Given a current collection of primitive values, checks if for each value in the collection an object in the database exists.
- * It creates a new object if needed, and removes any superfluos objects in the database that are no longer in the collection.
- *
- * @param currentCollection The collection that act as reference for the objects that should be in this database in the end.
- * @param comparisonAttribute The attribute that should store the value as decribed in the collection
- * @param autoDelete Automatically remove any superfluous objects form the database
- * @param keysAndValues Constraints that should hold for the set of objects that are deleted or created. Objects outside this constraint are not processed.
- *
- * @return A pair of lists. The first list contains the newly created objects, the second list contains the objects that (should be or are) removed.
- * @throws CoreException
- */
- public ImmutablePair, List> syncDatabaseWithCollection(Collection currentCollection, Object comparisonAttribute, boolean autoDelete, Object... keysAndValues) throws CoreException {
- if (builder.length() > 0)
- throw new IllegalStateException("syncDatabaseWithCollection can only be used on XPath which do not have constraints already");
-
-
- List added = new ArrayList();
- List removed = new ArrayList();
-
- Set col = new HashSet(currentCollection);
-
- for(int i = 0; i < keysAndValues.length; i+= 2)
- eq(keysAndValues[i], keysAndValues[i + 1]);
-
- for(IMendixObject existingItem : this.allMendixObjects()) {
- //Item is still available
- if (col.remove(existingItem.getValue(context, String.valueOf(comparisonAttribute))))
- continue;
-
- //No longer available
- removed.add(createProxy(context, this.proxyClass, existingItem));
- if (autoDelete)
- Core.delete(context, existingItem);
- }
-
- //Some items where not found in the database
- for(U value : col) {
-
- //In apache lang3, this would just be: ArrayUtils.addAll(keysAndValues, comparisonAttribute, value)
- Object[] args = new Object[keysAndValues.length + 2];
- for(int i = 0; i < keysAndValues.length; i++)
- args[i] = keysAndValues[i];
- args[keysAndValues.length] = comparisonAttribute;
- args[keysAndValues.length + 1] = value;
-
- T newItem = constructInstance(true, args);
- added.add(newItem);
- }
-
- //Oké, stupid, Pair is also only available in apache lang3, so lets use a simple pair implementation for now
- return ImmutablePair.of(added, removed);
- }
-
- public T firstOrWait(long timeoutMSecs) throws CoreException, InterruptedException
- {
- IMendixObject result = null;
-
- long start = System.currentTimeMillis();
- int sleepamount = 200;
- int loopcount = 0;
-
- while (result == null) {
- loopcount += 1;
- result = firstMendixObject();
-
- long now = System.currentTimeMillis();
-
- if (start + timeoutMSecs < now) //Time expired
- break;
-
- if (loopcount % 5 == 0)
- sleepamount *= 1.5;
-
- //not expired, wait a bit
- if (result == null)
- Thread.sleep(sleepamount);
- }
-
- return createProxy(context, proxyClass, result);
- }
-
-
- public List allMendixObjects() throws CoreException {
- assertEmptyStack();
-
- return Core.retrieveXPathQuery(context, getXPath(), limit, offset, sorting);
- }
-
- public List all() throws CoreException {
- List res = new ArrayList();
- for(IMendixObject o : allMendixObjects())
- res.add(createProxy(context, proxyClass, o));
-
- return res;
- }
-
- @Override
- public String toString() {
- return getXPath();
- }
-
-
- /**
- *
- *
- * Static utility functions
- *
- *
- */
-
- //cache for proxy constructors. Reflection is slow, so reuse as much as possible
- private static Map initializers = new HashMap();
-
- public static List createProxyList(IContext c, Class proxieClass, List objects) {
- List res = new ArrayList();
- if (objects == null || objects.size() == 0)
- return res;
-
- for(IMendixObject o : objects)
- res.add(createProxy(c, proxieClass, o));
-
- return res;
- }
-
- public static T createProxy(IContext c, Class proxieClass, IMendixObject object) {
- //Borrowed from nl.mweststrate.pages.MxQ package
-
- if (object == null)
- return null;
-
- if (c == null || proxieClass == null)
- throw new IllegalArgumentException("[CreateProxy] No context or proxieClass provided. ");
-
- //jeuj, we expect IMendixObject's. Thats nice..
- if (proxieClass == IMendixObject.class)
- return proxieClass.cast(object); //.. since we can do a direct cast
-
- try {
- String entityType = object.getType();
-
- if (!initializers.containsKey(entityType)) {
-
- String[] entType = object.getType().split("\\.");
- Class> realClass = Class.forName(entType[0].toLowerCase()+".proxies."+entType[1]);
-
- initializers.put(entityType, realClass.getMethod("initialize", IContext.class, IMendixObject.class));
- }
-
- //find constructor
- Method m = initializers.get(entityType);
-
- //create proxy object
- Object result = m.invoke(null, c, object);
-
- //cast, but check first is needed because the actual type might be a subclass of the requested type
- if (!proxieClass.isAssignableFrom(result.getClass()))
- throw new IllegalArgumentException("The type of the object ('" + object.getType() + "') is not (a subclass) of '" + proxieClass.getName()+"'");
-
- T proxie = proxieClass.cast(result);
- return proxie;
- }
- catch (Exception e) {
- throw new RuntimeException("Unable to instantiate proxie: " + e.getMessage(), e);
- }
- }
-
- public static String valueToXPathValue(Object value)
- {
- if (value == null)
- return "NULL";
-
- //Complex objects
- if (value instanceof IMendixIdentifier)
- return "'" + String.valueOf(((IMendixIdentifier) value).toLong()) + "'";
- if (value instanceof IMendixObject)
- return valueToXPathValue(((IMendixObject)value).getId());
- if (value instanceof List>)
- throw new IllegalArgumentException("List based values are not supported!");
-
- //Primitives
- if (value instanceof Date)
- return String.valueOf(((Date) value).getTime());
- if (value instanceof Long || value instanceof Integer)
- return String.valueOf(value);
- if (value instanceof Double || value instanceof Float) {
- //make sure xpath understands our number formatting
- NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH);
- format.setMaximumFractionDigits(10);
- format.setGroupingUsed(false);
- return format.format(value);
- }
- if (value instanceof Boolean) {
- return value.toString() + "()"; //xpath boolean, you know..
- }
- if (value instanceof String) {
- return "'" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "'";
- }
-
- //Object, assume its a proxy and deproxiefy
- try
- {
- IMendixObject mo = proxyToMendixObject(value);
- return valueToXPathValue(mo);
- }
- catch (NoSuchMethodException e)
- {
- //This is O.K. just not a proxy object...
- }
- catch (Exception e) {
- throw new RuntimeException("Failed to retrieve MendixObject from proxy: " + e.getMessage(), e);
- }
-
- //assume some string representation
- return "'" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "'";
- }
-
- public static IMendixObject proxyToMendixObject(Object value)
- throws NoSuchMethodException, SecurityException, IllegalAccessException,
- IllegalArgumentException, InvocationTargetException
- {
- Method m = value.getClass().getMethod("getMendixObject");
- IMendixObject mo = (IMendixObject) m.invoke(value);
- return mo;
- }
-
- public static List proxyListToMendixObjectList(
- List objects) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
- {
- ArrayList res = new ArrayList(objects.size());
- for(T i : objects)
- res.add(proxyToMendixObject(i));
- return res;
- }
-
- public static Object toMemberValue(Object value)
- {
- if (value == null)
- return null;
-
- //Complex objects
- if (value instanceof IMendixIdentifier)
- return value;
- if (value instanceof IMendixObject)
- return ((IMendixObject)value).getId();
-
- if (value instanceof List>)
- throw new IllegalArgumentException("List based values are not supported!");
-
- //Primitives
- if ( value instanceof Date
- || value instanceof Long
- || value instanceof Integer
- || value instanceof Double
- || value instanceof Float
- || value instanceof Boolean
- || value instanceof String) {
- return value;
- }
-
- if (value.getClass().isEnum())
- return value.toString();
-
- //Object, assume its a proxy and deproxiefy
- try
- {
- Method m = value.getClass().getMethod("getMendixObject");
- IMendixObject mo = (IMendixObject) m.invoke(value);
- return toMemberValue(mo);
- }
- catch (NoSuchMethodException e)
- {
- //This is O.K. just not a proxy object...
- }
- catch (Exception e) {
- throw new RuntimeException("Failed to convert object to IMendixMember compatible value '" + value + "': " + e.getMessage(), e);
- }
-
- throw new RuntimeException("Failed to convert object to IMendixMember compatible value: " + value);
- }
-
- public static interface IBatchProcessor {
- public void onItem(T item, long offset, long total) throws Exception;
- }
-
- private static final class ParallelJobRunner implements Callable
- {
- private final XPath self;
- private final IBatchProcessor batchProcessor;
- private final IMendixObject item;
- private long index;
- private long count;
-
- ParallelJobRunner(XPath self, IBatchProcessor batchProcessor, IMendixObject item, long index, long count)
- {
- this.self = self;
- this.batchProcessor = batchProcessor;
- this.item = item;
- this.index = index;
- this.count = count;
- }
-
- @Override
- public Boolean call()
- {
- try
- {
- batchProcessor.onItem(XPath.createProxy(Core.createSystemContext(), self.proxyClass, item), index, count); //mwe: hmm, many contexts..
- return true;
- }
- catch (Exception e)
- {
- throw new RuntimeException(String.format("Failed to execute batch on '%s' offset %d: %s", self.toString(), self.offset, e.getMessage()), e);
- }
- }
- }
-
-
- /**
- * Retreives all items in this xpath query in batches of a limited size.
- * Not that this function does not start a new transaction for all the batches,
- * rather, it just limits the number of objects being retrieved and kept in memory at the same time.
- *
- * So it only batches the retrieve process, not the optional manipulations done in the onItem method.
- * @param batchsize
- * @param batchProcessor
- * @throws CoreException
- */
- public void batch(int batchsize, IBatchProcessor batchProcessor) throws CoreException
- {
- if (this.sorting.isEmpty())
- this.addSortingAsc(XPath.ID);
-
- long count = this.count();
-
- int baseoffset = this.offset;
- int baselimit = this.limit;
-
- boolean useBaseLimit = baselimit > -1;
-
- this.offset(baseoffset);
- List data;
-
- long i = 0;
-
- do {
- int newlimit = useBaseLimit ? Math.min(batchsize, baseoffset + baselimit - this.offset) : batchsize;
- if (newlimit == 0)
- break; //where done, no more data is needed
-
- this.limit(newlimit);
- data = this.all();
-
- for(T item : data) {
- i += 1;
- try
- {
- batchProcessor.onItem(item, i, Math.max(i, count));
- }
- catch (Exception e)
- {
- throw new RuntimeException(String.format("Failed to execute batch on '%s' offset %d: %s", this.toString(), this.offset, e.getMessage()), e);
- }
- }
-
- this.offset(this.offset + data.size());
- } while(data.size() > 0);
- }
-
- /**
- * Batch with parallelization.
- *
- * IMPORTANT NOTE: DO NOT USE THE CONTEXT OF THE XPATH OBJECT ITSELF INSIDE THE BATCH PROCESSOR!
- *
- * Instead, use: Item.getContext(); !!
- *
- *
- * @param batchsize
- * @param threads
- * @param batchProcessor
- * @throws CoreException
- * @throws InterruptedException
- * @throws ExecutionException
- */
- public void batch(int batchsize, int threads, final IBatchProcessor batchProcessor)
- throws CoreException, InterruptedException, ExecutionException
- {
- if (this.sorting.isEmpty())
- this.addSortingAsc(XPath.ID);
-
- ExecutorService pool = Executors.newFixedThreadPool(threads);
-
- final long count = this.count();
-
- final XPath self = this;
-
- int progress = 0;
- List> futures = new ArrayList>(batchsize); //no need to synchronize
-
- this.offset(0);
- this.limit(batchsize);
-
- List data = this.allMendixObjects();
-
- while (data.size() > 0)
- {
-
- for (final IMendixObject item : data)
- {
- futures.add(pool.submit(new ParallelJobRunner(self, batchProcessor, item, progress, count)));
- progress += 1;
- }
-
- while (!futures.isEmpty())
- futures.remove(0).get(); //wait for all futures before proceeding to next iteration
-
- this.offset(this.offset + data.size());
- data = this.allMendixObjects();
- }
-
- if (pool.shutdownNow().size() > 0)
- throw new IllegalStateException("Not all tasks where finished!");
-
- }
-
- public static Class> getProxyClassForEntityName(String entityname)
- {
- {
- String [] parts = entityname.split("\\.");
- try
- {
- return Class.forName(parts[0].toLowerCase() + ".proxies." + parts[1]);
- }
- catch (ClassNotFoundException e)
- {
- throw new RuntimeException("Cannot find class for entity: " + entityname + ": " + e.getMessage(), e);
- }
- }
- }
-
- public boolean deleteAll() throws CoreException
- {
- this.limit(1000);
- List objs = allMendixObjects();
- while (!objs.isEmpty()) {
- if (!Core.delete(context, objs.toArray(new IMendixObject[objs.size()])))
- return false; //TODO: throw?
-
- objs = allMendixObjects();
- }
- return true;
- }
-
-
-
-}
+package objecthandling;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import objecthandling.ImmutablePair;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import com.mendix.core.Core;
+import com.mendix.core.CoreException;
+import com.mendix.systemwideinterfaces.core.IContext;
+import com.mendix.systemwideinterfaces.core.IMendixIdentifier;
+import com.mendix.systemwideinterfaces.core.IMendixObject;
+
+public class XPath
+{
+ /** Build in tokens, see: https://world.mendix.com/display/refguide3/XPath+Keywords+and+System+Variables
+ *
+ */
+
+ public static final String CurrentUser = "[%CurrentUser%]";
+ public static final String CurrentObject = "[%CurrentObject%]";
+
+ public static final String CurrentDateTime = "[%CurrentDateTime%]";
+ public static final String BeginOfCurrentDay = "[%BeginOfCurrentDay%]";
+ public static final String EndOfCurrentDay = "[%EndOfCurrentDay%]";
+ public static final String BeginOfCurrentHour = "[%BeginOfCurrentHour%]";
+ public static final String EndOfCurrentHour = "[%EndOfCurrentHour%]";
+ public static final String BeginOfCurrentMinute = "[%BeginOfCurrentMinute%]";
+ public static final String EndOfCurrentMinute = "[%EndOfCurrentMinute%]";
+ public static final String BeginOfCurrentMonth = "[%BeginOfCurrentMonth%]";
+ public static final String EndOfCurrentMonth = "[%EndOfCurrentMonth%]";
+ public static final String BeginOfCurrentWeek = "[%BeginOfCurrentWeek%]";
+ public static final String EndOfCurrentWeek = "[%EndOfCurrentWeek%]";
+
+ public static final String DayLength = "[%DayLength%]";
+ public static final String HourLength = "[%HourLength%]";
+ public static final String MinuteLength = "[%MinuteLength%]";
+ public static final String SecondLength = "[%SecondLength%]";
+ public static final String WeekLength = "[%WeekLength%]";
+ public static final String YearLength = "[%YearLength%]";
+ public static final String ID = "id";
+
+ /** End builtin tokens */
+
+ private String entity;
+ private int offset = 0;
+ private int limit = -1;
+ private LinkedHashMap sorting = new LinkedHashMap(); //important, linked map!
+ private LinkedList closeStack = new LinkedList();
+ private StringBuffer builder = new StringBuffer();
+ private IContext context;
+ private Class proxyClass;
+ private boolean requiresBinOp = false; //state property, indicates whether and 'and' needs to be inserted before the next constraint
+
+ public static XPath create(IContext c, String entityType) {
+ XPath res = new XPath(c, IMendixObject.class);
+
+ res.entity = entityType;
+
+ return res;
+ }
+
+ public static XPath create(IContext c, Class proxyClass) {
+ return new XPath(c, proxyClass);
+ }
+
+ private XPath(IContext c, Class proxyClass) {
+ try
+ {
+ if (proxyClass != IMendixObject.class)
+ this.entity = (String) proxyClass.getMethod("getType").invoke(null);
+ }
+ catch (Exception e)
+ {
+ throw new IllegalArgumentException("Failed to determine entity type of proxy class. Did you provide a valid proxy class? '" + proxyClass.getName() + "'");
+ }
+
+ this.proxyClass = proxyClass;
+ this.context = c;
+ }
+
+ private XPath autoInsertAnd() {
+ if (requiresBinOp)
+ and();
+ return this;
+ }
+
+ private XPath requireBinOp(boolean requires) {
+ requiresBinOp = requires;
+ return this;
+ }
+
+ public XPath offset(int offset2) {
+ if (offset2 < 0)
+ throw new IllegalArgumentException("Offset should not be negative");
+ this.offset = offset2;
+ return this;
+ }
+
+ public XPath limit(int limit2) {
+ if (limit2 < -1 || limit2 == 0)
+ throw new IllegalArgumentException("Limit should be larger than zero or -1. ");
+
+ this.limit = limit2;
+ return this;
+ }
+
+ public XPath addSortingAsc(Object... sortparts) {
+ assertOdd(sortparts);
+ sorting.put(StringUtils.join(sortparts, '/'), "asc");
+ return this;
+ }
+
+ public XPath addSortingDesc(Object... sortparts) {
+ sorting.put(StringUtils.join(sortparts, "/"), "desc");
+ return this;
+ }
+
+ public XPath eq(Object attr, Object valuecomparison) {
+ return compare(attr, "=", valuecomparison);
+ }
+
+ public XPath eq(Object... pathAndValue) {
+ assertEven(pathAndValue);
+ return compare(Arrays.copyOfRange(pathAndValue, 0, pathAndValue.length -1), "=", pathAndValue[pathAndValue.length -1 ]);
+ }
+
+ public XPath equalsIgnoreCase(Object attr, String value) {
+ //(contains(Name, $email) and length(Name) = length($email)
+ return
+ subconstraint()
+ .contains(attr, value)
+ .and()
+ .append(" length(" + attr + ") = ").append(value == null ? "0" : valueToXPathValue(value.length()))
+ .close();
+ }
+
+ public XPath notEq(Object attr, Object valuecomparison) {
+ return compare(attr, "!=", valuecomparison);
+ }
+
+ public XPath notEq(Object... pathAndValue) {
+ assertEven(pathAndValue);
+ return compare(Arrays.copyOfRange(pathAndValue, 0, pathAndValue.length -1), "!=", pathAndValue[pathAndValue.length -1 ]);
+ }
+
+ public XPath contains(Object attr, String value)
+ {
+ autoInsertAnd().append(" contains(").append(String.valueOf(attr)).append(",").append(valueToXPathValue(value)).append(") ");
+ return this.requireBinOp(true);
+ }
+
+ public XPath compare(Object attr, String operator, Object value) {
+ return compare(new Object[] {attr}, operator, value);
+ }
+
+ public XPath compare(Object[] path, String operator, Object value) {
+ assertOdd(path);
+ autoInsertAnd().append(StringUtils.join(path, '/')).append(" ").append(operator).append(" ").append(valueToXPathValue(value));
+ return this.requireBinOp(true);
+ }
+
+ public XPath hasReference(Object... path)
+ {
+ assertEven(path); //Reference + entity type
+ autoInsertAnd().append(StringUtils.join(path, '/'));
+ return this.requireBinOp(true);
+ }
+
+ public XPath subconstraint(Object... path) {
+ assertEven(path);
+ autoInsertAnd().append(StringUtils.join(path, '/')).append("[");
+ closeStack.push("]");
+ return this.requireBinOp(false);
+ }
+
+ public XPath subconstraint() {
+ autoInsertAnd().append("(");
+ closeStack.push(")");
+ return this.requireBinOp(false);
+ }
+
+ public XPath addConstraint()
+ {
+ if (!closeStack.isEmpty() && !closeStack.peek().equals("]"))
+ throw new IllegalStateException("Cannot add a constraint while in the middle of something else..");
+
+ return append("][").requireBinOp(false);
+ }
+
+ public XPath close() {
+ if (closeStack.isEmpty())
+ throw new IllegalStateException("XPathbuilder close stack is empty!");
+ append(closeStack.pop());
+ return requireBinOp(true);
+ //MWE: note that a close does not necessary require a binary operator, for example with two subsequent block([bla][boe]) constraints,
+ //but openening a binary constraint reset the flag, so that should be no issue
+ }
+
+ public XPath or() {
+ if (!requiresBinOp)
+ throw new IllegalStateException("Received 'or' but no binary operator was expected");
+ return append(" or ").requireBinOp(false);
+ }
+
+ public XPath and() {
+ if (!requiresBinOp)
+ throw new IllegalStateException("Received 'and' but no binary operator was expected");
+ return append(" and ").requireBinOp(false);
+ }
+
+ public XPath not() {
+ autoInsertAnd();
+ closeStack.push(")");
+ return append(" not(").requireBinOp(false);
+ }
+
+ private void assertOdd(Object[] stuff) {
+ if (stuff == null || stuff.length == 0 || stuff.length % 2 == 0)
+ throw new IllegalArgumentException("Expected an odd number of xpath path parts");
+ }
+
+ private void assertEven(Object[] stuff) {
+ if (stuff == null || stuff.length == 0 || stuff.length % 2 == 1)
+ throw new IllegalArgumentException("Expected an even number of xpath path parts");
+ }
+
+ public XPath append(String s) {
+ builder.append(s);
+ return this;
+ }
+
+ public String getXPath() {
+
+ if (builder.length() > 0)
+ return "//" + this.entity + "[" + builder.toString() + "]";
+ return "//" + this.entity;
+ }
+
+ private void assertEmptyStack() throws IllegalStateException
+ {
+ if (!closeStack.isEmpty())
+ throw new IllegalStateException("Invalid xpath expression, not all items where closed");
+ }
+
+ public long count( ) throws CoreException {
+ assertEmptyStack();
+
+ return Core.retrieveXPathQueryAggregate(context, "count(" + getXPath() +")");
+ }
+
+
+ public IMendixObject firstMendixObject() throws CoreException {
+ assertEmptyStack();
+
+ List result = Core.retrieveXPathQuery(context, getXPath(), 1, offset, sorting);
+ if (result.isEmpty())
+ return null;
+ return result.get(0);
+ }
+
+ public T first() throws CoreException {
+ return createProxy(context, proxyClass, firstMendixObject());
+ }
+
+
+ /**
+ * Given a set of attribute names and values, tries to find the first object that matches all conditions, or creates one
+ *
+ * @param autoCommit: whether the object should be committed once created (default: true)
+ * @param keysAndValues
+ * @return
+ * @throws CoreException
+ */
+ public T findOrCreateNoCommit(Object... keysAndValues) throws CoreException
+ {
+ T res = findFirst(keysAndValues);
+
+ return res != null ? res : constructInstance(false, keysAndValues);
+ }
+
+
+ public T findOrCreate(Object... keysAndValues) throws CoreException {
+ T res = findFirst(keysAndValues);
+
+ return res != null ? res : constructInstance(true, keysAndValues);
+
+ }
+
+ public T findOrCreateSynchronized(Object... keysAndValues) throws CoreException, InterruptedException {
+ T res = findFirst(keysAndValues);
+
+ if (res != null) {
+ return res;
+ } else {
+ synchronized (Core.getMetaObject(entity)) {
+ IContext synchronizedContext = context.getSession().createContext().createSudoClone();
+ try {
+ synchronizedContext.startTransaction();
+ res = createProxy(synchronizedContext, proxyClass, XPath.create(synchronizedContext, entity).findOrCreate(keysAndValues));
+ synchronizedContext.endTransaction();
+ return res;
+ } catch (CoreException e) {
+ if (synchronizedContext.isInTransaction()) {
+ synchronizedContext.rollbackTransAction();
+ }
+ throw e;
+ }
+ }
+ }
+ }
+
+ public T findFirst(Object... keysAndValues)
+ throws IllegalStateException, CoreException
+ {
+ if (builder.length() > 0)
+ throw new IllegalStateException("FindFirst can only be used on XPath which do not have constraints already");
+
+ assertEven(keysAndValues);
+ for(int i = 0; i < keysAndValues.length; i+= 2)
+ eq(keysAndValues[i], keysAndValues[i + 1]);
+
+ T res = this.first();
+ return res;
+ }
+
+
+ /**
+ * Creates one instance of the type of this XPath query, and initializes the provided attributes to the provided values.
+ * @param keysAndValues AttributeName, AttributeValue, AttributeName2, AttributeValue2... list.
+ * @return
+ * @throws CoreException
+ */
+ public T constructInstance(boolean autoCommit, Object... keysAndValues) throws CoreException
+ {
+ assertEven(keysAndValues);
+ IMendixObject newObj = Core.instantiate(context, this.entity);
+
+ for(int i = 0; i < keysAndValues.length; i+= 2)
+ newObj.setValue(context, String.valueOf(keysAndValues[i]), toMemberValue(keysAndValues[i + 1]));
+
+ if (autoCommit)
+ Core.commit(context, newObj);
+
+ return createProxy(context, proxyClass, newObj);
+ }
+
+ /**
+ * Given a current collection of primitive values, checks if for each value in the collection an object in the database exists.
+ * It creates a new object if needed, and removes any superfluos objects in the database that are no longer in the collection.
+ *
+ * @param currentCollection The collection that act as reference for the objects that should be in this database in the end.
+ * @param comparisonAttribute The attribute that should store the value as decribed in the collection
+ * @param autoDelete Automatically remove any superfluous objects form the database
+ * @param keysAndValues Constraints that should hold for the set of objects that are deleted or created. Objects outside this constraint are not processed.
+ *
+ * @return A pair of lists. The first list contains the newly created objects, the second list contains the objects that (should be or are) removed.
+ * @throws CoreException
+ */
+ public ImmutablePair, List> syncDatabaseWithCollection(Collection currentCollection, Object comparisonAttribute, boolean autoDelete, Object... keysAndValues) throws CoreException {
+ if (builder.length() > 0)
+ throw new IllegalStateException("syncDatabaseWithCollection can only be used on XPath which do not have constraints already");
+
+
+ List added = new ArrayList();
+ List removed = new ArrayList();
+
+ Set col = new HashSet(currentCollection);
+
+ for(int i = 0; i < keysAndValues.length; i+= 2)
+ eq(keysAndValues[i], keysAndValues[i + 1]);
+
+ for(IMendixObject existingItem : this.allMendixObjects()) {
+ //Item is still available
+ if (col.remove(existingItem.getValue(context, String.valueOf(comparisonAttribute))))
+ continue;
+
+ //No longer available
+ removed.add(createProxy(context, this.proxyClass, existingItem));
+ if (autoDelete)
+ Core.delete(context, existingItem);
+ }
+
+ //Some items where not found in the database
+ for(U value : col) {
+
+ //In apache lang3, this would just be: ArrayUtils.addAll(keysAndValues, comparisonAttribute, value)
+ Object[] args = new Object[keysAndValues.length + 2];
+ for(int i = 0; i < keysAndValues.length; i++)
+ args[i] = keysAndValues[i];
+ args[keysAndValues.length] = comparisonAttribute;
+ args[keysAndValues.length + 1] = value;
+
+ T newItem = constructInstance(true, args);
+ added.add(newItem);
+ }
+
+ //Oké, stupid, Pair is also only available in apache lang3, so lets use a simple pair implementation for now
+ return ImmutablePair.of(added, removed);
+ }
+
+ public T firstOrWait(long timeoutMSecs) throws CoreException, InterruptedException
+ {
+ IMendixObject result = null;
+
+ long start = System.currentTimeMillis();
+ int sleepamount = 200;
+ int loopcount = 0;
+
+ while (result == null) {
+ loopcount += 1;
+ result = firstMendixObject();
+
+ long now = System.currentTimeMillis();
+
+ if (start + timeoutMSecs < now) //Time expired
+ break;
+
+ if (loopcount % 5 == 0)
+ sleepamount *= 1.5;
+
+ //not expired, wait a bit
+ if (result == null)
+ Thread.sleep(sleepamount);
+ }
+
+ return createProxy(context, proxyClass, result);
+ }
+
+
+ public List allMendixObjects() throws CoreException {
+ assertEmptyStack();
+
+ return Core.retrieveXPathQuery(context, getXPath(), limit, offset, sorting);
+ }
+
+ public List all() throws CoreException {
+ List res = new ArrayList();
+ for(IMendixObject o : allMendixObjects())
+ res.add(createProxy(context, proxyClass, o));
+
+ return res;
+ }
+
+ @Override
+ public String toString() {
+ return getXPath();
+ }
+
+
+ /**
+ *
+ *
+ * Static utility functions
+ *
+ *
+ */
+
+ //cache for proxy constructors. Reflection is slow, so reuse as much as possible
+ private static Map initializers = new HashMap();
+
+ public static List createProxyList(IContext c, Class proxieClass, List objects) {
+ List res = new ArrayList();
+ if (objects == null || objects.size() == 0)
+ return res;
+
+ for(IMendixObject o : objects)
+ res.add(createProxy(c, proxieClass, o));
+
+ return res;
+ }
+
+ public static T createProxy(IContext c, Class proxieClass, IMendixObject object) {
+ //Borrowed from nl.mweststrate.pages.MxQ package
+
+ if (object == null)
+ return null;
+
+ if (c == null || proxieClass == null)
+ throw new IllegalArgumentException("[CreateProxy] No context or proxieClass provided. ");
+
+ //jeuj, we expect IMendixObject's. Thats nice..
+ if (proxieClass == IMendixObject.class)
+ return proxieClass.cast(object); //.. since we can do a direct cast
+
+ try {
+ String entityType = object.getType();
+
+ if (!initializers.containsKey(entityType)) {
+
+ String[] entType = object.getType().split("\\.");
+ Class> realClass = Class.forName(entType[0].toLowerCase()+".proxies."+entType[1]);
+
+ initializers.put(entityType, realClass.getMethod("initialize", IContext.class, IMendixObject.class));
+ }
+
+ //find constructor
+ Method m = initializers.get(entityType);
+
+ //create proxy object
+ Object result = m.invoke(null, c, object);
+
+ //cast, but check first is needed because the actual type might be a subclass of the requested type
+ if (!proxieClass.isAssignableFrom(result.getClass()))
+ throw new IllegalArgumentException("The type of the object ('" + object.getType() + "') is not (a subclass) of '" + proxieClass.getName()+"'");
+
+ T proxie = proxieClass.cast(result);
+ return proxie;
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Unable to instantiate proxie: " + e.getMessage(), e);
+ }
+ }
+
+ public static String valueToXPathValue(Object value)
+ {
+ if (value == null)
+ return "NULL";
+
+ //Complex objects
+ if (value instanceof IMendixIdentifier)
+ return "'" + String.valueOf(((IMendixIdentifier) value).toLong()) + "'";
+ if (value instanceof IMendixObject)
+ return valueToXPathValue(((IMendixObject)value).getId());
+ if (value instanceof List>)
+ throw new IllegalArgumentException("List based values are not supported!");
+
+ //Primitives
+ if (value instanceof Date)
+ return String.valueOf(((Date) value).getTime());
+ if (value instanceof Long || value instanceof Integer)
+ return String.valueOf(value);
+ if (value instanceof Double || value instanceof Float) {
+ //make sure xpath understands our number formatting
+ NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH);
+ format.setMaximumFractionDigits(10);
+ format.setGroupingUsed(false);
+ return format.format(value);
+ }
+ if (value instanceof Boolean) {
+ return value.toString() + "()"; //xpath boolean, you know..
+ }
+ if (value instanceof String) {
+ return "'" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "'";
+ }
+
+ //Object, assume its a proxy and deproxiefy
+ try
+ {
+ IMendixObject mo = proxyToMendixObject(value);
+ return valueToXPathValue(mo);
+ }
+ catch (NoSuchMethodException e)
+ {
+ //This is O.K. just not a proxy object...
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Failed to retrieve MendixObject from proxy: " + e.getMessage(), e);
+ }
+
+ //assume some string representation
+ return "'" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "'";
+ }
+
+ public static IMendixObject proxyToMendixObject(Object value)
+ throws NoSuchMethodException, SecurityException, IllegalAccessException,
+ IllegalArgumentException, InvocationTargetException
+ {
+ Method m = value.getClass().getMethod("getMendixObject");
+ IMendixObject mo = (IMendixObject) m.invoke(value);
+ return mo;
+ }
+
+ public static List proxyListToMendixObjectList(
+ List objects) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
+ {
+ ArrayList res = new ArrayList(objects.size());
+ for(T i : objects)
+ res.add(proxyToMendixObject(i));
+ return res;
+ }
+
+ public static Object toMemberValue(Object value)
+ {
+ if (value == null)
+ return null;
+
+ //Complex objects
+ if (value instanceof IMendixIdentifier)
+ return value;
+ if (value instanceof IMendixObject)
+ return ((IMendixObject)value).getId();
+
+ if (value instanceof List>)
+ throw new IllegalArgumentException("List based values are not supported!");
+
+ //Primitives
+ if ( value instanceof Date
+ || value instanceof Long
+ || value instanceof Integer
+ || value instanceof Double
+ || value instanceof Float
+ || value instanceof Boolean
+ || value instanceof String) {
+ return value;
+ }
+
+ if (value.getClass().isEnum())
+ return value.toString();
+
+ //Object, assume its a proxy and deproxiefy
+ try
+ {
+ Method m = value.getClass().getMethod("getMendixObject");
+ IMendixObject mo = (IMendixObject) m.invoke(value);
+ return toMemberValue(mo);
+ }
+ catch (NoSuchMethodException e)
+ {
+ //This is O.K. just not a proxy object...
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Failed to convert object to IMendixMember compatible value '" + value + "': " + e.getMessage(), e);
+ }
+
+ throw new RuntimeException("Failed to convert object to IMendixMember compatible value: " + value);
+ }
+
+ public static interface IBatchProcessor {
+ public void onItem(T item, long offset, long total) throws Exception;
+ }
+
+ private static final class ParallelJobRunner implements Callable
+ {
+ private final XPath self;
+ private final IBatchProcessor batchProcessor;
+ private final IMendixObject item;
+ private long index;
+ private long count;
+
+ ParallelJobRunner(XPath self, IBatchProcessor batchProcessor, IMendixObject item, long index, long count)
+ {
+ this.self = self;
+ this.batchProcessor = batchProcessor;
+ this.item = item;
+ this.index = index;
+ this.count = count;
+ }
+
+ @Override
+ public Boolean call()
+ {
+ try
+ {
+ batchProcessor.onItem(XPath.createProxy(Core.createSystemContext(), self.proxyClass, item), index, count); //mwe: hmm, many contexts..
+ return true;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(String.format("Failed to execute batch on '%s' offset %d: %s", self.toString(), self.offset, e.getMessage()), e);
+ }
+ }
+ }
+
+
+ /**
+ * Retreives all items in this xpath query in batches of a limited size.
+ * Not that this function does not start a new transaction for all the batches,
+ * rather, it just limits the number of objects being retrieved and kept in memory at the same time.
+ *
+ * So it only batches the retrieve process, not the optional manipulations done in the onItem method.
+ * @param batchsize
+ * @param batchProcessor
+ * @throws CoreException
+ */
+ public void batch(int batchsize, IBatchProcessor batchProcessor) throws CoreException
+ {
+ if (this.sorting.isEmpty())
+ this.addSortingAsc(XPath.ID);
+
+ long count = this.count();
+
+ int baseoffset = this.offset;
+ int baselimit = this.limit;
+
+ boolean useBaseLimit = baselimit > -1;
+
+ this.offset(baseoffset);
+ List data;
+
+ long i = 0;
+
+ do {
+ int newlimit = useBaseLimit ? Math.min(batchsize, baseoffset + baselimit - this.offset) : batchsize;
+ if (newlimit == 0)
+ break; //where done, no more data is needed
+
+ this.limit(newlimit);
+ data = this.all();
+
+ for(T item : data) {
+ i += 1;
+ try
+ {
+ batchProcessor.onItem(item, i, Math.max(i, count));
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(String.format("Failed to execute batch on '%s' offset %d: %s", this.toString(), this.offset, e.getMessage()), e);
+ }
+ }
+
+ this.offset(this.offset + data.size());
+ } while(data.size() > 0);
+ }
+
+ /**
+ * Batch with parallelization.
+ *
+ * IMPORTANT NOTE: DO NOT USE THE CONTEXT OF THE XPATH OBJECT ITSELF INSIDE THE BATCH PROCESSOR!
+ *
+ * Instead, use: Item.getContext(); !!
+ *
+ *
+ * @param batchsize
+ * @param threads
+ * @param batchProcessor
+ * @throws CoreException
+ * @throws InterruptedException
+ * @throws ExecutionException
+ */
+ public void batch(int batchsize, int threads, final IBatchProcessor batchProcessor)
+ throws CoreException, InterruptedException, ExecutionException
+ {
+ if (this.sorting.isEmpty())
+ this.addSortingAsc(XPath.ID);
+
+ ExecutorService pool = Executors.newFixedThreadPool(threads);
+
+ final long count = this.count();
+
+ final XPath self = this;
+
+ int progress = 0;
+ List> futures = new ArrayList>(batchsize); //no need to synchronize
+
+ this.offset(0);
+ this.limit(batchsize);
+
+ List data = this.allMendixObjects();
+
+ while (data.size() > 0)
+ {
+
+ for (final IMendixObject item : data)
+ {
+ futures.add(pool.submit(new ParallelJobRunner(self, batchProcessor, item, progress, count)));
+ progress += 1;
+ }
+
+ while (!futures.isEmpty())
+ futures.remove(0).get(); //wait for all futures before proceeding to next iteration
+
+ this.offset(this.offset + data.size());
+ data = this.allMendixObjects();
+ }
+
+ if (pool.shutdownNow().size() > 0)
+ throw new IllegalStateException("Not all tasks where finished!");
+
+ }
+
+ public static Class> getProxyClassForEntityName(String entityname)
+ {
+ {
+ String [] parts = entityname.split("\\.");
+ try
+ {
+ return Class.forName(parts[0].toLowerCase() + ".proxies." + parts[1]);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new RuntimeException("Cannot find class for entity: " + entityname + ": " + e.getMessage(), e);
+ }
+ }
+ }
+
+ public boolean deleteAll() throws CoreException
+ {
+ this.limit(1000);
+ List objs = allMendixObjects();
+ while (!objs.isEmpty()) {
+ if (!Core.delete(context, objs.toArray(new IMendixObject[objs.size()])))
+ return false; //TODO: throw?
+
+ objs = allMendixObjects();
+ }
+ return true;
+ }
+
+
+
+}
diff --git a/javasource/objecthandling/actions/deleteAll.java b/javasource/objecthandling/actions/deleteAll.java
index fc6ef23..09cfd54 100644
--- a/javasource/objecthandling/actions/deleteAll.java
+++ b/javasource/objecthandling/actions/deleteAll.java
@@ -31,7 +31,7 @@ public deleteAll(IContext context, IMendixObject entityType)
public java.lang.Boolean executeAction() throws Exception
{
// BEGIN USER CODE
- return XPath.create(this.getContext(), entityType.toString()).deleteAll();
+ return XPath.create(this.getContext(), entityType.getType()).deleteAll();
// END USER CODE
}
diff --git a/javasource/unittesting/TestManager.java b/javasource/unittesting/TestManager.java
index 409fefd..9c1c688 100644
--- a/javasource/unittesting/TestManager.java
+++ b/javasource/unittesting/TestManager.java
@@ -127,10 +127,13 @@ private void runMfSetup(TestSuite testSuite)
if (Core.getMicroflowNames().contains(testSuite.getModule() + ".Setup")) {
try {
LOG.info("Running Setup microflow..");
- if (testSuite.getAutoRollbackMFs())
+ if (testSuite.getAutoRollbackMFs()) {
setupContext = Core.createSystemContext();
- setupContext.startTransaction();;
+ setupContext.startTransaction();
Core.execute(setupContext, testSuite.getModule() + ".Setup", emptyArguments);
+ } else {
+ Core.execute(Core.createSystemContext(), testSuite.getModule() + ".Setup", emptyArguments);
+ }
}
catch(Exception e) {
LOG.error("Exception during SetUp microflow: " + e.getMessage(), e);
@@ -141,30 +144,33 @@ private void runMfSetup(TestSuite testSuite)
private void runMfTearDown(TestSuite testSuite)
{
- if (testSuite.getAutoRollbackMFs() && Core.getMicroflowNames().contains(testSuite.getModule() + ".Setup") && !Core.getMicroflowNames().contains(testSuite.getModule() + ".TearDown"))
- setupContext.rollbackTransAction();
-
+ IContext tearDownContext = setupContext;
if (Core.getMicroflowNames().contains(testSuite.getModule() + ".TearDown")) {
try
{
LOG.info("Running TearDown microflow..");
- if (testSuite.getAutoRollbackMFs() && Core.getMicroflowNames().contains(testSuite.getModule() + ".Setup")) {
- Core.execute(setupContext, testSuite.getModule() + ".TearDown", emptyArguments);
- } else {
- Core.execute(Core.createSystemContext(), testSuite.getModule() + ".TearDown", emptyArguments);
+ if (tearDownContext == null) {
+ tearDownContext = Core.createSystemContext();
+ }
+ if (testSuite.getAutoRollbackMFs()) {
+ tearDownContext.startTransaction();
}
+ Core.execute(tearDownContext, testSuite.getModule() + ".TearDown", emptyArguments);
}
catch (Exception e)
{
- LOG.error("Severe: exception in unittest TearDown microflow '" + testSuite.getModule() + ".Setup': " +e.getMessage(), e);
+ LOG.error("Severe: exception in unittest TearDown microflow '" + testSuite.getModule() + ".TearDown': " +e.getMessage(), e);
throw new RuntimeException(e);
}
- finally {
- if (testSuite.getAutoRollbackMFs() && Core.getMicroflowNames().contains(testSuite.getModule() + ".Setup")) {
- setupContext.rollbackTransAction();
- }
- }
}
+
+ // Either we had a teardown a teardown or
+ if (testSuite.getAutoRollbackMFs() && tearDownContext != null) {
+ tearDownContext.rollbackTransAction();
+ }
+
+ // Make sure we clean setupContext after running this test/suite
+ setupContext = null;
}
public synchronized void runTestSuites() throws CoreException {
@@ -330,8 +336,11 @@ else if (Core.getReturnType(mf).getType() != IDataType.DataTypeEnum.Boolean &&
IContext mfContext = null;
- if (testSuite.getAutoRollbackMFs() && Core.getMicroflowNames().contains(testSuite.getModule() + ".Setup")) {
- mfContext = setupContext.clone();
+ if (testSuite.getAutoRollbackMFs()) {
+ if (Core.getMicroflowNames().contains(testSuite.getModule() + ".Setup"))
+ mfContext = setupContext.clone();
+ else
+ mfContext = Core.createSystemContext();
mfContext.startTransaction();
} else {
mfContext = Core.createSystemContext();
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..fe3c5f9
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+ com.mendix.databaseconnector
+ databaseconnector-main
+ 3.0.0
+
+
+ Apache License, Version 2.0
+
+
+ pom
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ userlib-copy
+
+ true
+ userlib
+
+
+
+
+
+
+
+ DatabaseConnector.pom.xml
+ DatabaseConnectorTest.pom.xml
+
+
diff --git a/theme_old/index-rtl.html b/theme_old/index-rtl.html
deleted file mode 100644
index 9e7f001..0000000
--- a/theme_old/index-rtl.html
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
- Mendix
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/theme_old/index.html b/theme_old/index.html
deleted file mode 100644
index da47225..0000000
--- a/theme_old/index.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
- Mendix
-
-
-
-
-
-
-
-
-
-
-
diff --git a/theme_old/login.html b/theme_old/login.html
deleted file mode 100644
index 5bfda8a..0000000
--- a/theme_old/login.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
- Mendix 5 - Login
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/update-dependencies.sh b/update-dependencies.sh
new file mode 100755
index 0000000..6c5d6fe
--- /dev/null
+++ b/update-dependencies.sh
@@ -0,0 +1,28 @@
+#! /usr/bin/env sh
+
+# First download and copy the new jdbc drivers to userlib
+mvn dependency:copy-dependencies@userlib-copy
+
+# Then create remove the old RequiredLib files:
+rm -f userlib/*DatabaseConnector.RequiredLib
+rm -f userlib/*DatabaseConnectorTest.RequiredLib
+
+# Recreate those RequiredLib files:
+mvn dependency:list -pl :databaseconnector \
+ | sed -nE 's#[^\s]* (.*):(.*):jar:(.*):(compile|runtime)#userlib/\1.\2-\3.jar.DatabaseConnector.RequiredLib#p' \
+ | xargs touch
+
+mvn dependency:list -pl :databaseconnectortest \
+ | sed -nE 's#[^\s]* (.*):(.*):jar:(.*):(compile|runtime)#userlib/\1.\2-\3.jar.DatabaseConnectorTest.RequiredLib#p' \
+ | xargs touch
+
+# Update the license report file
+mvn project-info-reports:dependencies -pl :databaseconnector
+
+# Create a neat zip file with that information
+cd target/site
+zip -r ../../userlib/database-connector-dependencies.zip .
+cd ../../
+
+# Clean up
+mvn clean
\ No newline at end of file
diff --git a/userlib/RedshiftJDBC41-1.1.13.1013.jar b/userlib/RedshiftJDBC41-1.1.13.1013.jar
deleted file mode 100644
index ab6a53a..0000000
Binary files a/userlib/RedshiftJDBC41-1.1.13.1013.jar and /dev/null differ
diff --git a/userlib/com.ibm.db2.jcc.db2jcc-db2jcc4.jar b/userlib/com.ibm.db2.jcc.db2jcc-db2jcc4.jar
new file mode 100644
index 0000000..7a791c7
Binary files /dev/null and b/userlib/com.ibm.db2.jcc.db2jcc-db2jcc4.jar differ
diff --git a/userlib/HikariCP-2.6.1.jar.DatabaseConnector.RequiredLib b/userlib/com.ibm.db2.jcc.db2jcc-db2jcc4.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/HikariCP-2.6.1.jar.DatabaseConnector.RequiredLib
rename to userlib/com.ibm.db2.jcc.db2jcc-db2jcc4.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/com.microsoft.sqlserver.mssql-jdbc-7.2.1.jre8.jar b/userlib/com.microsoft.sqlserver.mssql-jdbc-7.2.1.jre8.jar
new file mode 100644
index 0000000..15c1829
Binary files /dev/null and b/userlib/com.microsoft.sqlserver.mssql-jdbc-7.2.1.jre8.jar differ
diff --git a/userlib/RedshiftJDBC41-1.1.13.1013.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.microsoft.sqlserver.mssql-jdbc-7.2.1.jre8.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/RedshiftJDBC41-1.1.13.1013.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.microsoft.sqlserver.mssql-jdbc-7.2.1.jre8.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/com.oracle.database.ha.ons-12.2.0.1.jar b/userlib/com.oracle.database.ha.ons-12.2.0.1.jar
new file mode 100644
index 0000000..637cf11
Binary files /dev/null and b/userlib/com.oracle.database.ha.ons-12.2.0.1.jar differ
diff --git a/userlib/byte-buddy-1.10.5.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.oracle.database.ha.ons-12.2.0.1.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/byte-buddy-1.10.5.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.oracle.database.ha.ons-12.2.0.1.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/com.oracle.database.ha.simplefan-12.2.0.1.jar b/userlib/com.oracle.database.ha.simplefan-12.2.0.1.jar
new file mode 100644
index 0000000..3c498ff
Binary files /dev/null and b/userlib/com.oracle.database.ha.simplefan-12.2.0.1.jar differ
diff --git a/userlib/byte-buddy-agent-1.10.5.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.oracle.database.ha.simplefan-12.2.0.1.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/byte-buddy-agent-1.10.5.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.oracle.database.ha.simplefan-12.2.0.1.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/com.oracle.database.jdbc.ojdbc8-12.2.0.1.jar b/userlib/com.oracle.database.jdbc.ojdbc8-12.2.0.1.jar
new file mode 100644
index 0000000..bf41243
Binary files /dev/null and b/userlib/com.oracle.database.jdbc.ojdbc8-12.2.0.1.jar differ
diff --git a/userlib/commons-logging-1.1.3.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.oracle.database.jdbc.ojdbc8-12.2.0.1.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/commons-logging-1.1.3.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.oracle.database.jdbc.ojdbc8-12.2.0.1.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/com.oracle.database.jdbc.ucp-12.2.0.1.jar b/userlib/com.oracle.database.jdbc.ucp-12.2.0.1.jar
new file mode 100644
index 0000000..3d86a52
Binary files /dev/null and b/userlib/com.oracle.database.jdbc.ucp-12.2.0.1.jar differ
diff --git a/userlib/hsqldb-2.3.4.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.oracle.database.jdbc.ucp-12.2.0.1.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/hsqldb-2.3.4.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.oracle.database.jdbc.ucp-12.2.0.1.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/com.oracle.database.security.oraclepki-12.2.0.1.jar b/userlib/com.oracle.database.security.oraclepki-12.2.0.1.jar
new file mode 100644
index 0000000..df1fe7a
Binary files /dev/null and b/userlib/com.oracle.database.security.oraclepki-12.2.0.1.jar differ
diff --git a/userlib/joda-time-2.8.1.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.oracle.database.security.oraclepki-12.2.0.1.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/joda-time-2.8.1.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.oracle.database.security.oraclepki-12.2.0.1.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/com.oracle.database.security.osdt_cert-12.2.0.1.jar b/userlib/com.oracle.database.security.osdt_cert-12.2.0.1.jar
new file mode 100644
index 0000000..c9976c1
Binary files /dev/null and b/userlib/com.oracle.database.security.osdt_cert-12.2.0.1.jar differ
diff --git a/userlib/mariadb-java-client-1.4.6.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.oracle.database.security.osdt_cert-12.2.0.1.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/mariadb-java-client-1.4.6.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.oracle.database.security.osdt_cert-12.2.0.1.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/com.oracle.database.security.osdt_core-12.2.0.1.jar b/userlib/com.oracle.database.security.osdt_core-12.2.0.1.jar
new file mode 100644
index 0000000..3f1e068
Binary files /dev/null and b/userlib/com.oracle.database.security.osdt_core-12.2.0.1.jar differ
diff --git a/userlib/mockito-core-3.2.4.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.oracle.database.security.osdt_core-12.2.0.1.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/mockito-core-3.2.4.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.oracle.database.security.osdt_core-12.2.0.1.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/com.sap.cloud.db.jdbc.ngdbc-2.3.58.jar b/userlib/com.sap.cloud.db.jdbc.ngdbc-2.3.58.jar
new file mode 100644
index 0000000..36a9a89
Binary files /dev/null and b/userlib/com.sap.cloud.db.jdbc.ngdbc-2.3.58.jar differ
diff --git a/userlib/objenesis-2.6.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.sap.cloud.db.jdbc.ngdbc-2.3.58.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/objenesis-2.6.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.sap.cloud.db.jdbc.ngdbc-2.3.58.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/HikariCP-2.6.1.jar b/userlib/com.zaxxer.HikariCP-2.6.1.jar
similarity index 100%
rename from userlib/HikariCP-2.6.1.jar
rename to userlib/com.zaxxer.HikariCP-2.6.1.jar
diff --git a/userlib/ojdbc7.jar.DatabaseConnectorTest.RequiredLib b/userlib/com.zaxxer.HikariCP-2.6.1.jar.DatabaseConnector.RequiredLib
similarity index 100%
rename from userlib/ojdbc7.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/com.zaxxer.HikariCP-2.6.1.jar.DatabaseConnector.RequiredLib
diff --git a/userlib/commons-io.commons-io-2.3.jar b/userlib/commons-io.commons-io-2.3.jar
new file mode 100644
index 0000000..d5a0771
Binary files /dev/null and b/userlib/commons-io.commons-io-2.3.jar differ
diff --git a/userlib/org.slf4j.slf4j-api.jar.DatabaseConnector.RequiredLib b/userlib/commons-io.commons-io-2.3.jar.DatabaseConnectorTest.RequiredLib
similarity index 100%
rename from userlib/org.slf4j.slf4j-api.jar.DatabaseConnector.RequiredLib
rename to userlib/commons-io.commons-io-2.3.jar.DatabaseConnectorTest.RequiredLib
diff --git a/userlib/commons-logging-1.1.3.jar b/userlib/commons-logging-1.1.3.jar
deleted file mode 100644
index ab51254..0000000
Binary files a/userlib/commons-logging-1.1.3.jar and /dev/null differ
diff --git a/userlib/database-connector-dependencies.zip b/userlib/database-connector-dependencies.zip
new file mode 100644
index 0000000..cfad1e9
Binary files /dev/null and b/userlib/database-connector-dependencies.zip differ
diff --git a/userlib/hsqldb-2.3.4.jar b/userlib/hsqldb-2.3.4.jar
deleted file mode 100644
index 4197289..0000000
Binary files a/userlib/hsqldb-2.3.4.jar and /dev/null differ
diff --git a/userlib/joda-time-2.8.1.jar b/userlib/joda-time-2.8.1.jar
deleted file mode 100644
index 94be659..0000000
Binary files a/userlib/joda-time-2.8.1.jar and /dev/null differ
diff --git a/userlib/mariadb-java-client-1.4.6.jar b/userlib/mariadb-java-client-1.4.6.jar
deleted file mode 100644
index 2aba5d0..0000000
Binary files a/userlib/mariadb-java-client-1.4.6.jar and /dev/null differ
diff --git a/userlib/byte-buddy-1.10.5.jar b/userlib/net.bytebuddy.byte-buddy-1.10.5.jar
similarity index 100%
rename from userlib/byte-buddy-1.10.5.jar
rename to userlib/net.bytebuddy.byte-buddy-1.10.5.jar
diff --git a/userlib/postgresql-9.4.1208.jar.DatabaseConnectorTest.RequiredLib b/userlib/net.bytebuddy.byte-buddy-1.10.5.jar.DatabaseConnectorTest.RequiredLib
similarity index 100%
rename from userlib/postgresql-9.4.1208.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/net.bytebuddy.byte-buddy-1.10.5.jar.DatabaseConnectorTest.RequiredLib
diff --git a/userlib/byte-buddy-agent-1.10.5.jar b/userlib/net.bytebuddy.byte-buddy-agent-1.10.5.jar
similarity index 100%
rename from userlib/byte-buddy-agent-1.10.5.jar
rename to userlib/net.bytebuddy.byte-buddy-agent-1.10.5.jar
diff --git a/userlib/sqljdbc42.jar.DatabaseConnectorTest.RequiredLib b/userlib/net.bytebuddy.byte-buddy-agent-1.10.5.jar.DatabaseConnectorTest.RequiredLib
similarity index 100%
rename from userlib/sqljdbc42.jar.DatabaseConnectorTest.RequiredLib
rename to userlib/net.bytebuddy.byte-buddy-agent-1.10.5.jar.DatabaseConnectorTest.RequiredLib
diff --git a/userlib/ojdbc7.jar b/userlib/ojdbc7.jar
deleted file mode 100644
index cf9eb48..0000000
Binary files a/userlib/ojdbc7.jar and /dev/null differ
diff --git a/userlib/org.hsqldb.hsqldb-2.4.1.jar b/userlib/org.hsqldb.hsqldb-2.4.1.jar
new file mode 100644
index 0000000..0a91057
Binary files /dev/null and b/userlib/org.hsqldb.hsqldb-2.4.1.jar differ
diff --git a/userlib/org.hsqldb.hsqldb-2.4.1.jar.DatabaseConnector.RequiredLib b/userlib/org.hsqldb.hsqldb-2.4.1.jar.DatabaseConnector.RequiredLib
new file mode 100644
index 0000000..e69de29
diff --git a/userlib/org.mariadb.jdbc.mariadb-java-client-2.4.0.jar b/userlib/org.mariadb.jdbc.mariadb-java-client-2.4.0.jar
new file mode 100644
index 0000000..aed97b5
Binary files /dev/null and b/userlib/org.mariadb.jdbc.mariadb-java-client-2.4.0.jar differ
diff --git a/userlib/org.mariadb.jdbc.mariadb-java-client-2.4.0.jar.DatabaseConnector.RequiredLib b/userlib/org.mariadb.jdbc.mariadb-java-client-2.4.0.jar.DatabaseConnector.RequiredLib
new file mode 100644
index 0000000..e69de29
diff --git a/userlib/mockito-core-3.2.4.jar b/userlib/org.mockito.mockito-core-3.2.4.jar
similarity index 100%
rename from userlib/mockito-core-3.2.4.jar
rename to userlib/org.mockito.mockito-core-3.2.4.jar
diff --git a/userlib/org.mockito.mockito-core-3.2.4.jar.DatabaseConnectorTest.RequiredLib b/userlib/org.mockito.mockito-core-3.2.4.jar.DatabaseConnectorTest.RequiredLib
new file mode 100644
index 0000000..e69de29
diff --git a/userlib/objenesis-2.6.jar b/userlib/org.objenesis.objenesis-2.6.jar
similarity index 100%
rename from userlib/objenesis-2.6.jar
rename to userlib/org.objenesis.objenesis-2.6.jar
diff --git a/userlib/org.objenesis.objenesis-2.6.jar.DatabaseConnectorTest.RequiredLib b/userlib/org.objenesis.objenesis-2.6.jar.DatabaseConnectorTest.RequiredLib
new file mode 100644
index 0000000..e69de29
diff --git a/userlib/org.postgresql.postgresql-42.2.9.jar b/userlib/org.postgresql.postgresql-42.2.9.jar
new file mode 100644
index 0000000..7aba662
Binary files /dev/null and b/userlib/org.postgresql.postgresql-42.2.9.jar differ
diff --git a/userlib/org.postgresql.postgresql-42.2.9.jar.DatabaseConnector.RequiredLib b/userlib/org.postgresql.postgresql-42.2.9.jar.DatabaseConnector.RequiredLib
new file mode 100644
index 0000000..e69de29
diff --git a/userlib/org.slf4j.slf4j-api.jar b/userlib/org.slf4j.slf4j-api-1.7.21.jar
similarity index 100%
rename from userlib/org.slf4j.slf4j-api.jar
rename to userlib/org.slf4j.slf4j-api-1.7.21.jar
diff --git a/userlib/org.slf4j.slf4j-api-1.7.21.jar.DatabaseConnector.RequiredLib b/userlib/org.slf4j.slf4j-api-1.7.21.jar.DatabaseConnector.RequiredLib
new file mode 100644
index 0000000..e69de29
diff --git a/userlib/postgresql-9.4.1208.jar b/userlib/postgresql-9.4.1208.jar
deleted file mode 100644
index 9858b27..0000000
Binary files a/userlib/postgresql-9.4.1208.jar and /dev/null differ
diff --git a/userlib/sqljdbc42.jar b/userlib/sqljdbc42.jar
deleted file mode 100644
index 2cfdf26..0000000
Binary files a/userlib/sqljdbc42.jar and /dev/null differ