diff --git a/DatabaseConnector.mpr b/DatabaseConnector.mpr index 2b5c037..f89a78a 100644 Binary files a/DatabaseConnector.mpr and b/DatabaseConnector.mpr differ diff --git a/DatabaseConnector.pom.xml b/DatabaseConnector.pom.xml new file mode 100644 index 0000000..cd767b0 --- /dev/null +++ b/DatabaseConnector.pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + + com.mendix.databaseconnector + databaseconnector-main + 3.0.0 + . + + + databaseconnector + 3.0.0 + + + + com.zaxxer + HikariCP + 2.6.1 + + + org.slf4j + slf4j-api + 1.7.21 + + + com.ibm.db2.jcc + db2jcc + db2jcc4 + runtime + + + org.hsqldb + hsqldb + 2.4.1 + runtime + + + org.mariadb.jdbc + mariadb-java-client + 2.4.0 + runtime + + + com.microsoft.sqlserver + mssql-jdbc + 7.2.1.jre8 + runtime + + + com.oracle.database.jdbc + ojdbc8 + 12.2.0.1 + runtime + + + org.postgresql + postgresql + 42.2.9 + runtime + + + com.sap.cloud.db.jdbc + ngdbc + 2.3.58 + runtime + + + diff --git a/DatabaseConnectorTest.pom.xml b/DatabaseConnectorTest.pom.xml new file mode 100644 index 0000000..5559178 --- /dev/null +++ b/DatabaseConnectorTest.pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + + com.mendix.databaseconnector + databaseconnector-main + 3.0.0 + . + + + databaseconnectortest + 3.0.0 + + + + org.mockito + mockito-core + 3.2.4 + + + commons-io + commons-io + 2.3 + + + diff --git a/javasource/databaseconnector/actions/ExecuteParameterisedQuery.java b/javasource/databaseconnector/actions/ExecuteParameterizedQuery.java similarity index 92% rename from javasource/databaseconnector/actions/ExecuteParameterisedQuery.java rename to javasource/databaseconnector/actions/ExecuteParameterizedQuery.java index 34d22fe..14a6182 100644 --- a/javasource/databaseconnector/actions/ExecuteParameterisedQuery.java +++ b/javasource/databaseconnector/actions/ExecuteParameterizedQuery.java @@ -22,7 +22,7 @@ /** *

- * 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