diff --git a/DIST/RestServices_mx6_3.2.0.mpk b/DIST/RestServices_mx6_3.2.0.mpk new file mode 100644 index 0000000..ef82851 Binary files /dev/null and b/DIST/RestServices_mx6_3.2.0.mpk differ diff --git a/RestServices.mpr b/RestServices.mpr index eb4c8f5..112a5c4 100644 Binary files a/RestServices.mpr and b/RestServices.mpr differ diff --git a/javasource/communitycommons/ConversationLog.java b/javasource/communitycommons/ConversationLog.java deleted file mode 100644 index 1b5a936..0000000 --- a/javasource/communitycommons/ConversationLog.java +++ /dev/null @@ -1,471 +0,0 @@ -package communitycommons; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; - -import com.mendix.core.Core; -import com.mendix.logging.ILogNode; - -/** - * - * @author mwe - * @date 11-10-2012 - * - * This class can be used to allow more advanced logging, especially, to LOG short-running-conversations. - * - * This conversations leave the notions of INFO/DEBUG/TRACE and instead use notion of less details and more details. - * See @see section() and @see endSection() methods. See @see setBaseDetailVisibilityLevel about how much detail is visible - * - * Loglines are marked with the conversation id, so loglines of a single conversation can be aggregated together. - * - * The ConversationLog has a playback notion, for example, if an exception occurs, all loglines, even the detailed ones which would not be visible otherwise, are displayed. - * - * A typical logline will look like this: - * - * - * MxSync - [sDvX][Thu 08:56:00] starting module ... FAILED (in 20 ms. 3 hidden) - * ^ ^ ^ ^ ^ ^ ^ ^ - * | | | | | | | | - * lognode | timestamp | LOG message | | nr. of hidden details - * | | | | - * conversation ID | result | - * | | - * indent, indicates the level of detail timing behavior - * - * - * - */ -public class ConversationLog -{ - private final ILogNode log; - private final int id; - - private static final AtomicInteger conversationNumber = new AtomicInteger(); - - private enum LogType { SECTION_START, NORMAL, SECTION_END, WARNING, ERROR } - - private static final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("HH:mm:ss"); //DateTimeFormat is thread safe, simpleDateFormat is not! - - private static class LogLine { - final LogType type; - final String msg; - final long time; - final int level; - int closed = -1; - boolean hasError; - int started; - final Throwable exception; - - LogLine(LogType type, String msg, int level, Throwable e) { - this.type = type; - this.msg = msg; - this.time = System.currentTimeMillis(); - this.level = level; - this.exception = e; - } - - public LogLine(LogType type, String msg, int level) - { - this(type, msg, level, null); - } - - @Override - public String toString() { - return msg == null ? "(empty)" : msg.toString(); - } - } - - private List messages = new ArrayList(); - private int currentlevel = 0; - private int flushedUntil = 0; - private int basevisiblelevel = 0; - private boolean closed = false; - - - - /** - * Create a new conversion - * @param M2ee loglevel to report to - */ - public ConversationLog(String lognode) { - this.log = Core.getLogger(lognode); - id = conversationNumber.incrementAndGet(); - - } - - /** - * Create a new conversion - * @param M2ee loglevel to report to - * @param Log message of the first section that will be created - */ - public ConversationLog(String lognode, String firstSection) { - this.log = Core.getLogger(lognode); - id = conversationNumber.incrementAndGet(); - - section(firstSection); - } - - /** - * @return the Id of this converstion - */ - public int getId() { - return id; - } - - /** - * Starts a section. That is, first print the provided then increase the level of detail. - * - * For each section, timing behavior and result status will be measured. - * - * Note that all sections should be ended! @see endSection - * - * If an exception is thrown, that ends the conversation entirely, this constraint can be dropped, - * but in all other cases (no- or catched exceptions) all sections should be ended, probably by using finally. - * - * @param message to print - * @param optional message arguments - * @return - */ - public ConversationLog section(String msg, Object... args) - { - messages.add(new LogLine(LogType.SECTION_START, String.format(msg + " ... ", args), currentlevel)); - currentlevel += 1; - - return this; - } - - public ConversationLog endSection() { - return endSection(null); - } - - /** - * Ends the current section, generates a report of the section (if visible). - * @see section() about when and how to end sections - * - * @param The result message of this section to be reported back. Defaults to "DONE". - * @return - */ - public ConversationLog endSection(String result) { - if (currentlevel < 1) - warn("(ConversationLog too many endsection invocations)"); - currentlevel -= 1; - - LogLine l = new LogLine(LogType.SECTION_END, result, currentlevel); - - for (int i = messages.size() - 1; i >= 0; i--) - if (messages.get(i).level == currentlevel && messages.get(i).type == LogType.SECTION_START) { - messages.get(i).closed = messages.size(); - l.started = i; - break; - } - - messages.add(l); - - flushIfLastLineVisible(); - - return this; - } - - - public void log(String msg, Object... args) { - messages.add(new LogLine(LogType.NORMAL, String.format(msg, args), currentlevel)); - - flushIfLastLineVisible(); - } - - public ConversationLog warn(String msg, Object... args) { - return warn(null, msg, args); - } - - public ConversationLog warn(Throwable e, String msg, Object... args) { - messages.add(new LogLine(LogType.WARNING, String.format(msg, args), currentlevel, e)); - - flushIfLastLineVisible(); - return this; - } - - public void error(Throwable e) { - error(e, e.getMessage()); - } - - public void error(String msg, Object... args) { - error(null, msg, args); - } - - public void error(Throwable e, String msg, Object... args) { - messages.add(new LogLine(LogType.ERROR, String.format(msg, args), currentlevel, e)); - - int minlevel = currentlevel; - - for (int i = messages.size() -1; i >= 0; i--) { - LogLine line = messages.get(i); - if (line.level <= minlevel) { - if (line.hasError) //been there, done that, appereantly there ar multiple errors.. - break; - line.hasError = true; - - minlevel = Math.min(minlevel, line.level); //once we seen a lower level, we do not want to go into higher levels anymor - } - } - - flush(); //always flush on exceptions - } - - - long lastResportedTime = 0; - final static int REPORT_INTERVAL = 10; - final static int PROGRESSBAR_WIDTH = 10; - - /** - * Reports progress about a batch. The total can default to zero if not provided. - * This function is state-less, so many calls to it will not result in heap issues. - * - * @param msg - * @param current - * @param total - */ - public void reportProgress(String msg, long current, long total) { - long currentReportTime = System.currentTimeMillis(); - - boolean report = (currentReportTime - lastResportedTime > REPORT_INTERVAL * 1000) || total == current; - - if (report) { - lastResportedTime = currentReportTime; - - if (total == 0 || total < current) - log.info(String.format("[%s| %d / ?] %s", - StringUtils.leftPad("", (long) (PROGRESSBAR_WIDTH + 1), "_"), - current, msg - )); - - else { - int chars = Math.round((current / total) * PROGRESSBAR_WIDTH); - - log.info(String.format("[%s%s| %d / %d] %s", - StringUtils.leftPad("|", (long) chars, "="), - StringUtils.leftPad("", (long) (PROGRESSBAR_WIDTH - chars), "_"), - current, total, msg - )); - } - } - } - - public void reportProgress(String msg, float progress) { - reportProgress(msg, Math.round(progress * 100), 100); - } - - /** - * Sets the detail level of the current conversation. This determines how deep sections can be nested before becoming invisible in the LOG - * - * If the detail level is for example 3, this means that the contents of the first 2 nested sections are visible. - * - * This holds only if the loglevel of the M2EE LOG is set to 'INFO'. - * If the loglevel is set to 'DEBUG', the effective detail level is always 2 higher than the assigned level. - * If the loglevel is set to 'TRACE', all loglines are always visible, regardless the assigned level. - * - * Use getDetailVisibilityLevel to get the effective visibility level. - * - * Furthermore, warnings and errors are always visible. - * - * @param detaillevel The detail level, defaults two 2. - */ - public ConversationLog setBaseDetailVisibilityLevel(int detaillevel) { - this.basevisiblelevel = detaillevel; - return this; - } - - /** - * Returns the ACTIVE visibility level of this conversation. This depends on the base detail visibility level and the loglevel of the - * M2ee node. @see setBaseDetailVisibilityLevel for more info. - * @return - */ - public int getVisibleLogLegel() { - return log.isTraceEnabled() ? 1000 : log.isDebugEnabled() ? this.basevisiblelevel + 1 : this.basevisiblelevel; - } - - private void flushIfLastLineVisible() - { - //flush if this is a visible section - if (currentlevel <= getVisibleLogLegel()) - flush(); - } - - /** - * Force all current known loglines to be written to the M2ee LOG. - * @return - */ - public ConversationLog flush() { - int hidden = 0; - int actuallevel = getVisibleLogLegel(); - - while (flushedUntil < messages.size()) { - LogLine line = messages.get(flushedUntil); - - switch (line.type) { - case ERROR: - writeToLog(line, (line.msg != null ? line.msg : line.exception != null ? line.exception.getMessage() : "")); - break; - - case WARNING: - writeToLog(line,(line.msg != null ? line.msg : line.exception != null ? line.exception.getMessage() : "")); - break; - - case NORMAL: - if (line.level <= actuallevel || line.hasError) - writeToLog(line); - else - hidden += 1; - break; - - case SECTION_START: - /** - * error INSIDE this section? - */ - if (line.hasError && flushedUntil + 1 < messages.size() /*avoid out of bounds*/) { - LogLine nextline = messages.get(flushedUntil + 1); - - //is the error ínside this section? - if (nextline.hasError && nextline.type != LogType.SECTION_END) - writeToLog(line); - } - - /** - * visible level and not ended yet. We are flushing so we want to display that the section did start - */ - else if (line.level <= actuallevel && line.closed == -1) - writeToLog(line); - - /** - * section is ended, but there are submessages and they are visible. Show begin - */ - else if (line.closed > flushedUntil + 1 && line.level < actuallevel) - writeToLog(line); - - - /** - * we are flushing a subsection of the current section, that is, there are visible messages inside this section, - * even if this section is not closed. Note that this only works because the following holds. - * - * - The SECTION is not closed yet (it would have been flushed earlier otherwise) - * - Something triggered a flush (ENDSECTION or ERROR) which is inside this section (it would have feen flushed earlier otherwise) - * - */ - else if (flushedUntil < messages.size() && line.level < actuallevel && line.closed == 0) - writeToLog(line); - - /** - * Not printing, report hidden - */ - else if (line.level >= actuallevel) - hidden += 1; - - //all other cases, this line is written by end_section - break; - - case SECTION_END: - LogLine begin = this.messages.get(line.started); - boolean outputline = false; - - /** - * begin section has error, generate report - */ - if (begin.hasError) - outputline = true; - - /** - * visible, but empty section, generate report - */ - else if (line.level <= actuallevel) - outputline = true; - - if (outputline) { - String hiddenmsg = hidden == 0 ? "": String.format("%d hidden", hidden); - String statusmsg = line.msg == null ? "DONE" : line.msg; - long delta = line.time - begin.time; - String msg = String.format("%s %s (in %d ms. %s)", begin.msg, statusmsg, delta, hiddenmsg); - writeToLog(line, msg); - } - - //reset hidden - if (line.level >= actuallevel) - hidden = 0; - - break; - - } - - flushedUntil += 1; - } - - return this; - } - - private void writeToLog(LogLine line) { - writeToLog(line, line.msg); - } - - private void writeToLog(LogLine line, String msg) - { - String base = String.format("[%04d][%s]%s %s", - this.getId(), - dateFormat.print(line.time), - StringUtils.leftPad("", line.level * 4L, " "), - msg - ); - - switch(line.type) { - case ERROR: - log.error(base, line.exception); - break; - case NORMAL: - case SECTION_END: - case SECTION_START: - log.info(base); - break; - case WARNING: - log.warn(base, line.exception); - break; - } - - if (closed) - log.warn(String.format("[%s] (Objection! Messages were added to the conversation after being closed!)", this.id)); - } - - /** - * Closes the conversation. This is a kind of assertion to see if your code properly ends all sections. Warnings are printed otherwise. - */ - public void close() { - - if (flushedUntil < messages.size()) { - flush(); - } - if (currentlevel > 0) - log.warn(String.format("[%04d] (Objection! Being destructed, but found %d unclosed sections. The conversation did not end normally?)", getId(), currentlevel)); - - this.closed = true; - } - - @Override - public String toString() { - return messages.toString(); - } - - @Override - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } -} - - public int getCurrentLevel() - { - return currentlevel; - } - -} diff --git a/javasource/communitycommons/DateTime.java b/javasource/communitycommons/DateTime.java index a823b80..e133261 100644 --- a/javasource/communitycommons/DateTime.java +++ b/javasource/communitycommons/DateTime.java @@ -1,68 +1,68 @@ -package communitycommons; - -import java.util.Calendar; -import java.util.Date; - -import communitycommons.proxies.DatePartSelector; - -public class DateTime -{ - /** - * @author mwe - * Berekent aantal jaar sinds een bepaalde datum. Als einddatum == null, het huidige tijdstip wordt gebruikt - * Code is gebaseerd op http://stackoverflow.com/questions/1116123/how-do-i-calculate-someones-age-in-java - */ - public static long yearsBetween(Date birthdate, Date comparedate) { - if (birthdate == null) - return -1L; - - Calendar now = Calendar.getInstance(); - if (comparedate != null) - now.setTime(comparedate); - Calendar dob = Calendar.getInstance(); - dob.setTime(birthdate); - if (dob.after(now)) - return -1L; - - int year1 = now.get(Calendar.YEAR); - int year2 = dob.get(Calendar.YEAR); - long age = year1 - year2; - int month1 = now.get(Calendar.MONTH); - int month2 = dob.get(Calendar.MONTH); - if (month2 > month1) { - age--; - } else if (month1 == month2) { - int day1 = now.get(Calendar.DAY_OF_MONTH); - int day2 = dob.get(Calendar.DAY_OF_MONTH); - if (day2 > day1) { - age--; - } - } - return age; - } - - public static long dateTimeToLong(Date date) - { - return date.getTime(); - } - - public static Date longToDateTime(Long value) - { - return new Date(value); - } - - public static long dateTimeToInteger(Date date, DatePartSelector selectorObj) - { - Calendar newDate = Calendar.getInstance(); - newDate.setTime(date); - int value = -1; - switch (selectorObj) { - case year : value = newDate.get(Calendar.YEAR); break; - case month : value = newDate.get(Calendar.MONTH)+1; break; // Return starts at 0 - case day : value = newDate.get(Calendar.DAY_OF_MONTH); break; - default : break; - } - return value; - } - -} +package communitycommons; + +import java.util.Calendar; +import java.util.Date; + +import communitycommons.proxies.DatePartSelector; + +public class DateTime +{ + /** + * @author mwe + * Berekent aantal jaar sinds een bepaalde datum. Als einddatum == null, het huidige tijdstip wordt gebruikt + * Code is gebaseerd op http://stackoverflow.com/questions/1116123/how-do-i-calculate-someones-age-in-java + */ + public static long yearsBetween(Date birthdate, Date comparedate) { + if (birthdate == null) + return -1L; + + Calendar now = Calendar.getInstance(); + if (comparedate != null) + now.setTime(comparedate); + Calendar dob = Calendar.getInstance(); + dob.setTime(birthdate); + if (dob.after(now)) + return -1L; + + int year1 = now.get(Calendar.YEAR); + int year2 = dob.get(Calendar.YEAR); + long age = year1 - year2; + int month1 = now.get(Calendar.MONTH); + int month2 = dob.get(Calendar.MONTH); + if (month2 > month1) { + age--; + } else if (month1 == month2) { + int day1 = now.get(Calendar.DAY_OF_MONTH); + int day2 = dob.get(Calendar.DAY_OF_MONTH); + if (day2 > day1) { + age--; + } + } + return age; + } + + public static long dateTimeToLong(Date date) + { + return date.getTime(); + } + + public static Date longToDateTime(Long value) + { + return new Date(value); + } + + public static long dateTimeToInteger(Date date, DatePartSelector selectorObj) + { + Calendar newDate = Calendar.getInstance(); + newDate.setTime(date); + int value = -1; + switch (selectorObj) { + case year : value = newDate.get(Calendar.YEAR); break; + case month : value = newDate.get(Calendar.MONTH)+1; break; // Return starts at 0 + case day : value = newDate.get(Calendar.DAY_OF_MONTH); break; + default : break; + } + return value; + } + +} diff --git a/javasource/communitycommons/ImmutablePair.java b/javasource/communitycommons/ImmutablePair.java index 7e8d502..c787a53 100644 --- a/javasource/communitycommons/ImmutablePair.java +++ b/javasource/communitycommons/ImmutablePair.java @@ -1,58 +1,58 @@ -package communitycommons; - -import org.apache.commons.lang3.builder.HashCodeBuilder; - - -public class ImmutablePair -{ - public static ImmutablePair of(T left, U right) { - return new ImmutablePair(left, right); - } - - private final T left; - private final U right; - - private ImmutablePair(T left, U right) { - if (left == null) - throw new IllegalArgumentException("Left is NULL"); - if (right == null) - throw new IllegalArgumentException("Right is NULL"); - - this.left = left; - this.right = right; - } - - public T getLeft() { - return left; - } - - public U getRight() { - return right; - } - - @Override - public String toString() { - return "<" + left.toString()+ "," + right.toString() + ">"; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof ImmutablePair)) - return false; - - if (this == other) - return true; - - ImmutablePair o = (ImmutablePair) other; - return left.equals(o.getLeft()) && right.equals(o.getRight()); - } - - @Override - public int hashCode() { - return new HashCodeBuilder(19, 85) - .append(left) - .append(right) - .toHashCode(); - } - -} +package communitycommons; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + + +public class ImmutablePair +{ + public static ImmutablePair of(T left, U right) { + return new ImmutablePair(left, right); + } + + private final T left; + private final U right; + + private ImmutablePair(T left, U right) { + if (left == null) + throw new IllegalArgumentException("Left is NULL"); + if (right == null) + throw new IllegalArgumentException("Right is NULL"); + + this.left = left; + this.right = right; + } + + public T getLeft() { + return left; + } + + public U getRight() { + return right; + } + + @Override + public String toString() { + return "<" + left.toString()+ "," + right.toString() + ">"; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ImmutablePair)) + return false; + + if (this == other) + return true; + + ImmutablePair o = (ImmutablePair) other; + return left.equals(o.getLeft()) && right.equals(o.getRight()); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(19, 85) + .append(left) + .append(right) + .toHashCode(); + } + +} diff --git a/javasource/communitycommons/Logging.java b/javasource/communitycommons/Logging.java index abdda65..4af44d9 100644 --- a/javasource/communitycommons/Logging.java +++ b/javasource/communitycommons/Logging.java @@ -1,65 +1,69 @@ -package communitycommons; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import com.mendix.core.Core; -import com.mendix.logging.ILogNode; -import communitycommons.proxies.LogLevel; - -public class Logging -{ - private static Map timers = new HashMap(); - - public static void log(String lognode, LogLevel loglevel, String message) - { - log(lognode, loglevel, message, null); - } - - public static void log(String lognode, LogLevel loglevel, String message, Throwable e) { - ILogNode logger = Core.getLogger(lognode); - switch (loglevel) { - case Critical: - logger.critical(message,e); - break; - case Warning: - logger.warn(message,e); - break; - case Debug: - logger.debug(message); - break; - case Error: - logger.error(message,e); - break; - case Info: - logger.info(message); - break; - case Trace: - logger.trace(message); - break; - } - } - - public static void simpleLog(String message) - { - Core.getLogger("Community_Commons").info(message); - } - - - public static Long measureEnd(String timerName, LogLevel loglevel, - String message) - { - Long cur = new Date().getTime(); - if (!timers.containsKey(timerName)) - throw new IllegalArgumentException(); - String time = String.format("%d", cur - timers.get(timerName)); - log("Utility_log", loglevel, "Timer " + timerName + " finished in " + time + " ms. " + message); - return timers.get(timerName); - } - - public static void measureStart(String timerName) - { - timers.put(timerName, new Date().getTime()); - } -} +package communitycommons; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.mendix.core.Core; +import com.mendix.logging.ILogNode; +import communitycommons.proxies.LogLevel; + +public class Logging +{ + private static Map timers = new HashMap(); + + public static void log(String lognode, LogLevel loglevel, String message) + { + log(lognode, loglevel, message, null); + } + + public static void log(String lognode, LogLevel loglevel, String message, Throwable e) { + ILogNode logger = Core.getLogger(lognode); + switch (loglevel) { + case Critical: + logger.critical(message,e); + break; + case Warning: + logger.warn(message,e); + break; + case Debug: + logger.debug(message); + break; + case Error: + logger.error(message,e); + break; + case Info: + logger.info(message); + break; + case Trace: + logger.trace(message); + break; + } + } + + public static void simpleLog(String message) + { + Core.getLogger("Community_Commons").info(message); + } + + + public static Long measureEnd(String timerName, LogLevel loglevel, + String message) + { + Long cur = new Date().getTime(); + if (!timers.containsKey(timerName)) + throw new IllegalArgumentException(); + String time = String.format("%d", cur - timers.get(timerName)); + log("Utility_log", loglevel, "Timer " + timerName + " finished in " + time + " ms. " + message); + return timers.get(timerName); + } + + public static void measureStart(String timerName) + { + timers.put(timerName, new Date().getTime()); + } + + public static void createLogNode(String logNode) { + Core.getLogger(logNode); + } +} diff --git a/javasource/communitycommons/Misc.java b/javasource/communitycommons/Misc.java index 34f1b58..ad6ded2 100644 --- a/javasource/communitycommons/Misc.java +++ b/javasource/communitycommons/Misc.java @@ -1,672 +1,684 @@ -package communitycommons; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicLong; - -import org.apache.commons.fileupload.util.LimitedInputStream; -import org.apache.commons.io.IOUtils; -import org.apache.pdfbox.Overlay; -import org.apache.pdfbox.exceptions.COSVisitorException; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.util.PDFMergerUtility; - -import system.proxies.FileDocument; -import system.proxies.Language; - -import com.google.common.collect.ImmutableMap; -import com.mendix.core.Core; -import com.mendix.core.CoreException; -import com.mendix.core.conf.RuntimeVersion; -import com.mendix.core.objectmanagement.member.MendixBoolean; -import com.mendix.integration.WebserviceException; -import com.mendix.logging.ILogNode; -import com.mendix.systemwideinterfaces.core.IContext; -import com.mendix.systemwideinterfaces.core.IMendixObject; -import com.mendix.systemwideinterfaces.core.ISession; -import com.mendix.systemwideinterfaces.core.IUser; - - -public class Misc -{ - - public abstract static class IterateCallback - { - boolean start = false; - boolean stop = false; - private Iterator mapIter; - - public abstract void hit(T1 key, T2 value) throws Exception; - - public void exit() { - stop = true; - } - - public void remove() { - mapIter.remove(); - } - - synchronized void runOn(Map map) throws Exception { - if (start) - throw new IllegalMonitorStateException(); - start = true; - - try { - this.mapIter = map.keySet().iterator(); - - while ( mapIter.hasNext() ) - { - T1 key = mapIter.next(); - T2 value = map.get(key); - - hit(key, value); - - if (stop) - break; - } - } - - finally { - //reset state to allow reuse, even when exceptions occur - mapIter = null; - stop = false; - start = false; - } - } - } - - /** - * Because you cannot remove items from a map while iteration, this function is introduced. - * In the callback, you can use this.remove() or this.exit() to either remove or break the loop. Use return; to continue - * @throws Exception - */ - public static void iterateMap(Map map, IterateCallback callback) throws Exception { - //http://marxsoftware.blogspot.com/2008/04/removing-entry-from-java-map-during.html - if (map == null || callback == null) - throw new IllegalArgumentException(); - - callback.runOn(map); - } - - public static String getApplicationURL() - { - return Core.getConfiguration().getApplicationRootUrl(); - } - - public static String getRuntimeVersion() - { - RuntimeVersion runtimeVersion = RuntimeVersion.getInstance(); - return runtimeVersion.toString(); - } - - public static void throwException(String message) throws UserThrownException - { - throw new UserThrownException(message); - } - - public static void throwWebserviceException(String faultstring) throws WebserviceException { - throw new WebserviceException(WebserviceException.clientFaultCode, faultstring); - } - - public static String retrieveURL(String url, String postdata) throws Exception - { - // Send data, appname - URLConnection conn = new URL(url).openConnection(); - - conn.setDoInput(true); - conn.setDoOutput(true); - conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - - if (postdata != null) { - IOUtils.copy(new ByteArrayInputStream(postdata.getBytes("UTF-8")), conn.getOutputStream()); - } - IOUtils.closeQuietly(conn.getOutputStream()); - - // Get the response - String result = new String(IOUtils.toString(conn.getInputStream())); - IOUtils.closeQuietly(conn.getInputStream()); - return result; - } - - public static Boolean duplicateFileDocument(IContext context, IMendixObject toClone, IMendixObject target) throws Exception - { - if (toClone == null || target == null) - throw new Exception("No file to clone or to clone into provided"); - - MendixBoolean hasContents = (MendixBoolean) toClone.getMember(context, FileDocument.MemberNames.HasContents.toString()); - if (!hasContents.getValue(context)) - return false; - - InputStream inputStream = Core.getFileDocumentContent(context, toClone); - - Core.storeFileDocumentContent(context, target, (String) toClone.getValue(context, system.proxies.FileDocument.MemberNames.Name.toString()), inputStream); - return true; - } - - public static Boolean duplicateImage(IContext context, IMendixObject toClone, IMendixObject target, int thumbWidth, int thumbHeight) throws Exception - { - if (toClone == null || target == null) - throw new Exception("No file to clone or to clone into provided"); - - MendixBoolean hasContents = (MendixBoolean) toClone.getMember(context, FileDocument.MemberNames.HasContents.toString()); - if (!hasContents.getValue(context)) - return false; - - InputStream inputStream = Core.getImage(context, toClone, false); - - Core.storeImageDocumentContent(context, target, inputStream, thumbWidth, thumbHeight); - - return true; - } - - public static Boolean storeURLToFileDocument(IContext context, String url, IMendixObject __document, String filename) throws Exception - { - if (__document == null || url == null || filename == null) - throw new Exception("No document, filename or URL provided"); - - final int MAX_REMOTE_FILESIZE = 1024 * 1024 * 200; //maxium of 200 MB - URL imageUrl = new URL(url); - URLConnection connection = imageUrl.openConnection(); - //we connect in 20 seconds or not at all - connection.setConnectTimeout(20000); - connection.setReadTimeout(20000); - connection.connect(); - - //check on forehand the size of the remote file, we don't want to kill the server by providing a 3 terabyte image. - if (connection.getContentLength() > MAX_REMOTE_FILESIZE) { //maximum of 200 mb - throw new IllegalArgumentException("MxID: importing image, wrong filesize of remote url: " + connection.getContentLength()+ " (max: " + String.valueOf(MAX_REMOTE_FILESIZE)+ ")"); - } else if (connection.getContentLength() < 0) { - // connection has not specified content length, wrap stream in a LimitedInputStream - LimitedInputStream limitStream = new LimitedInputStream(connection.getInputStream(), MAX_REMOTE_FILESIZE) { - @Override - protected void raiseError(long pSizeMax, long pCount) throws IOException { - throw new IllegalArgumentException("MxID: importing image, wrong filesize of remote url (max: " + String.valueOf(MAX_REMOTE_FILESIZE)+ ")"); - } - }; - Core.storeFileDocumentContent(context, __document, filename, limitStream); - } else { - // connection has specified correct content length, read the stream normally - //NB; stream is closed by the core - Core.storeFileDocumentContent(context, __document, filename, connection.getInputStream()); - } - - return true; - } - - public static Long getFileSize(IContext context, IMendixObject document) - { - final int BUFFER_SIZE = 4096; - long size = 0; - - if (context != null) { - InputStream inputStream = null; - byte[] buffer = new byte[BUFFER_SIZE]; - - try { - inputStream = Core.getFileDocumentContent(context, document); - int i; - while ((i = inputStream.read(buffer)) != -1) - size += i; - } catch (IOException e) { - Core.getLogger("FileUtil").error( - "Couldn't determine filesize of FileDocument '" + document.getId()); - } finally { - IOUtils.closeQuietly(inputStream); - } - } - - return size; - } - - public static void delay(long delaytime) throws InterruptedException - { - Thread.sleep(delaytime); - } - - public static IContext getContextFor(IContext context, String username, boolean sudoContext) { - if (username == null || username.isEmpty()) { - throw new RuntimeException("Assertion: No username provided"); - } - - ISession session = getSessionFor(context, username); - - IContext c = session.createContext(); - if (sudoContext) { - return c.getSudoContext(); - } - - return c; - } - - private static ISession getSessionFor(IContext context, String username) { - ISession session = Core.getActiveSession(username); - - if (session == null) { - IContext newContext = context.getSession().createContext().getSudoContext(); - newContext.startTransaction(); - try { - session = initializeSessionForUser(newContext, username); - } catch (CoreException e) { - newContext.rollbackTransAction(); - - throw new RuntimeException("Failed to initialize session for user: " + username + ": " + e.getMessage(), e); - } finally { - newContext.endTransaction(); - } - } - - return session; - } - - private static ISession initializeSessionForUser(IContext context, String username) throws CoreException { - IUser user = Core.getUser(context, username); - - if (user == null) { - throw new RuntimeException("Assertion: user with username '" + username + "' does not exist"); - } - - return Core.initializeSession(user, null); - } - - public static Object executeMicroflowAsUser(IContext context, - String microflowName, String username, Boolean sudoContext, Object... args) throws Exception - { - - if (context == null) - throw new Exception("Assertion: No context provided"); - if (microflowName == null || microflowName.isEmpty()) - throw new Exception("Assertion: No context provided"); - if (!Core.getMicroflowNames().contains(microflowName)) - throw new Exception("Assertion: microflow not found: " + microflowName); - if (args.length % 2 != 0) - throw new Exception("Assertion: odd number of dynamic arguments provided, please name every argument: " + args.length); - - Map params = new LinkedHashMap(); - for(int i = 0; i < args.length; i+= 2) if (args[i] != null) - params.put(args[i].toString(), args[i + 1]); - - IContext c = getContextFor(context, username, sudoContext); - - return Core.execute(c, microflowName, params); - } - - //MWE: based on: http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Executor.html - - static class MFSerialExecutor { - private static final ILogNode LOG = Core.getLogger("communitycommons"); - - private static MFSerialExecutor _instance = new MFSerialExecutor(); - - private final AtomicLong tasknr = new AtomicLong(); - private final ExecutorService executor; - - public static MFSerialExecutor instance() { - return _instance; - } - - private MFSerialExecutor() { - executor = Executors.newSingleThreadExecutor(new ThreadFactory() { - - //Default thread factory takes care of setting the proper thread context - private final ThreadFactory defaultFactory = Executors.defaultThreadFactory(); - - @Override - public Thread newThread(Runnable runnable) { - Thread t = defaultFactory.newThread(runnable); - t.setPriority(Thread.MIN_PRIORITY); - t.setName("CommunityCommons background pool executor thread"); - return t; - } - - }); - } - - public void execute(final Runnable command) - { - if (command == null) { - throw new NullPointerException("command"); - } - - final long currenttasknr = tasknr.incrementAndGet(); - LOG.info("[RunMicroflowAsyncInQueue] Scheduling task #" + currenttasknr); - - executor.submit(new Runnable() { - @Override - public void run() { - LOG.info("[RunMicroflowAsyncInQueue] Running task #" + currenttasknr); - try { - command.run(); - } catch(RuntimeException e) { - LOG.error("[RunMicroflowAsyncInQueue] Execution of task #" + currenttasknr + " failed: " + e.getMessage(), e); - throw e; //single thread executor will continue, even if an exception is thrown. - } - LOG.info("[RunMicroflowAsyncInQueue] Completed task #" + currenttasknr + ". Tasks left: " + (tasknr.get() - currenttasknr)); - } - }); - } - } - - public static Boolean runMicroflowAsyncInQueue(final String microflowName) - { - MFSerialExecutor.instance().execute(new Runnable() { - @Override - public void run() - { - try - { - Future future = Core.executeAsync(Core.createSystemContext(), microflowName, true, new HashMap()); //MWE: somehow, it only works with system context... well thats OK for now. - future.get(); - } - catch (Exception e) - { - throw new RuntimeException("Failed to run Async: "+ microflowName + ": " + e.getMessage(), e); - } - } - }); - return true; - } - - public static Boolean runMicroflowInBackground(final IContext context, final String microflowName, - final IMendixObject paramObject) - { - final ISession session = context.getSession(); - - if (paramObject != null) - session.retain(paramObject); - - MFSerialExecutor.instance().execute(new Runnable() { - - @Override - public void run() - { - try - { - IContext c = Core.createSystemContext(); - if (paramObject != null) { - Core.executeAsync(c, microflowName, true, paramObject).get(); //MWE: somehow, it only works with system context... well thats OK for now. - } - else - Core.executeAsync(c, microflowName, true, new HashMap()).get(); //MWE: somehow, it only works with system context... well thats OK for now. - } - catch (Exception e) - { - throw new RuntimeException("Failed to run Async: "+ microflowName + ": " + e.getMessage(), e); - } - - finally { - if (paramObject != null) - session.release(paramObject.getId()); - } - } - - }); - return true; - } - - private interface IBatchItemHandler - { - - void exec(IContext context, IMendixObject obj) throws Exception; - - } - - private static class BatchState { - private int state = 0; //-1 = error, 1 = done. - private final IBatchItemHandler callback; - - public BatchState(IBatchItemHandler callback) { - this.callback = callback; - } - - public void setState(int state) - { - this.state = state; - } - - public int getState() - { - return state; - } - - public void handle(IContext context, IMendixObject obj) throws Exception { - callback.exec(context, obj); - } - } - - public static Boolean executeMicroflowInBatches(String xpath, final String microflow, int batchsize, boolean waitUntilFinished, boolean asc) throws CoreException, InterruptedException { - Core.getLogger("communitycommons").info("[ExecuteInBatches] Starting microflow batch '" + microflow + "..."); - - return executeInBatches(xpath, new BatchState(new IBatchItemHandler() { - - @Override - public void exec(IContext context, IMendixObject obj) throws Exception - { - Core.executeAsync(context, microflow, true, obj).get(); - } - - }), batchsize, waitUntilFinished, asc); - } - - public static Boolean recommitInBatches(String xpath, int batchsize, - boolean waitUntilFinished, Boolean asc) throws CoreException, InterruptedException - { - Core.getLogger("communitycommons").info("[ExecuteInBatches] Starting recommit batch..."); - - return executeInBatches(xpath, new BatchState(new IBatchItemHandler() { - - @Override - public void exec(IContext context, IMendixObject obj) throws Exception - { - Core.commit(context, obj); - } - - }), batchsize, waitUntilFinished, asc); - } - - public static Boolean executeInBatches(String xpathRaw, BatchState batchState, int batchsize, boolean waitUntilFinished, boolean asc) throws CoreException, InterruptedException - { - String xpath = xpathRaw.startsWith("//") ? xpathRaw : "//" + xpathRaw; - - long count = Core.retrieveXPathQueryAggregate(Core.createSystemContext(), "count(" + xpath + ")"); - int loop = (int) Math.ceil(((float)count) / ((float)batchsize)); - - - Core.getLogger("communitycommons").info( - "[ExecuteInBatches] Starting batch on ~ " + count + " objects divided over ~ " + loop + " batches. " - + (waitUntilFinished ? "Waiting until the batch has finished..." : "") - ); - - executeInBatchesHelper(xpath, batchsize, 0, batchState, count, asc); - - if (waitUntilFinished) { - while (batchState.getState() == 0) { - Thread.sleep(5000); - } - if (batchState.getState() == 1) { - Core.getLogger("communitycommons").debug("[ExecuteInBatches] Successfully finished batch"); - return true; - } - Core.getLogger("communitycommons").error("[ExecuteInBatches] Failed to finish batch. Please check the application log for more details."); - return false; - } - - return true; - } - - static void executeInBatchesHelper(final String xpath, final int batchsize, final long last, final BatchState batchState, final long count, final boolean asc) { - MFSerialExecutor.instance().execute(new Runnable() { - - @Override - public void run() - { - try - { - Thread.sleep(200); - IContext c = Core.createSystemContext(); - - List objects = Core.retrieveXPathQuery(c, xpath + (last > 0 ? "[id " + (asc ? "> " : "< ") + last + "]" : ""), batchsize, 0, ImmutableMap.of("id", asc ? "asc" : "desc")); - - //no new objects found :) - if (objects.size() == 0) { - Core.getLogger("communitycommons").info("[ExecuteInBatches] Succesfully finished batch on ~" + count + " objects."); - batchState.setState(1); - } - else { - - //process objects - for(IMendixObject obj: objects) - batchState.handle(c, obj); - - //invoke next batch - executeInBatchesHelper(xpath, batchsize, objects.get(objects.size() - 1).getId().toLong(), batchState, count, asc); - } - } - catch (Exception e) - { - batchState.setState(-1); - throw new RuntimeException("[ExecuteInBatches] Failed to run in batch: " + e.getMessage(), e); - } - } - - }); - } - - /** - * Tests if two objects are equal with throwing unecessary null pointer exceptions. - * - * This is almost the most stupid function ever, since it should be part of Java itself. - * - * In java 7 it will finally be available as static method Object.equals() - * @param left - * @param right - * @return - */ - public static boolean objectsAreEqual(Object left, Object right) { - if (left == null && right == null) - return true; - if (left == null || right == null) - return false; - return left.equals(right); - } - - /** - * Get the default language - * @param context - * @return The default language - * @throws CoreException - */ - public static Language getDefaultLanguage(IContext context) throws CoreException { - String languageCode = Core.getDefaultLanguage().getCode(); - List languageList = Language.load(context, "[Code = '" + languageCode + "']"); - if (languageList == null || languageList.isEmpty()) { - throw new RuntimeException("No language found for default language constant value " + languageCode); - } - return languageList.get(0); - } - - public static boolean mergePDF(IContext context,List documents, IMendixObject mergedDocument ){ - - int i = 0; - PDFMergerUtility mergePdf = new PDFMergerUtility(); - for(i=0; i < documents.size(); i++) - { - FileDocument file = documents.get(i); - InputStream content = Core.getFileDocumentContent(context, file.getMendixObject()); - mergePdf.addSource(content); - } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - mergePdf.setDestinationStream(out); - try { - mergePdf.mergeDocuments(); - } catch (COSVisitorException e) { - throw new RuntimeException("Failed to merge documents" + e.getMessage(), e); - - } catch (IOException e) { - throw new RuntimeException("Failed to merge documents" + e.getMessage(), e); - - } - - Core.storeFileDocumentContent(context, mergedDocument, new ByteArrayInputStream(out.toByteArray())); - - out.reset(); - documents.clear(); - - return true; - } - - - /** - * Overlay a generated PDF document with another PDF (containing the company stationary for example) - * @param context - * @param generatedDocumentMendixObject The document to overlay - * @param overlayMendixObject The document containing the overlay - * @return boolean - * @throws IOException - * @throws COSVisitorException - */ - public static boolean overlayPdf(IContext context, IMendixObject generatedDocumentMendixObject, IMendixObject overlayMendixObject) throws IOException, COSVisitorException { - - ILogNode logger = Core.getLogger("OverlayPdf"); - logger.trace("Overlay PDF start, retrieve overlay PDF"); - PDDocument overlayDoc = PDDocument.load(Core.getFileDocumentContent(context, overlayMendixObject)); - int overlayPageCount = overlayDoc.getNumberOfPages(); - PDPage lastOverlayPage = (PDPage)overlayDoc.getDocumentCatalog().getAllPages().get(overlayPageCount - 1); - - logger.trace("Retrieve generated document"); - PDDocument offerteDoc = PDDocument.load(Core.getFileDocumentContent(context, generatedDocumentMendixObject)); - - int pageCount = offerteDoc.getNumberOfPages(); - if (logger.isTraceEnabled()) { - logger.trace("Number of pages in overlay: " + overlayPageCount + ", in generated document: " + pageCount); - } - if (pageCount > overlayPageCount) { - logger.trace("Duplicate last overlay page to match number of pages"); - for (int i = overlayPageCount; i < pageCount; i++) { - overlayDoc.importPage(lastOverlayPage); - } - } else if (overlayPageCount > pageCount) { - logger.trace("Delete unnecessary pages from the overlay to match number of pages"); - for (int i = pageCount; i < overlayPageCount; i++) { - overlayDoc.removePage(i); - } - } - - logger.trace("Perform overlay"); - Overlay overlay = new Overlay(); - overlay.overlay(offerteDoc,overlayDoc); - - logger.trace("Save result in output stream"); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - overlayDoc.save(baos); - - logger.trace("Duplicate result in input stream"); - InputStream overlayedContent = new ByteArrayInputStream(baos.toByteArray()); - - logger.trace("Store result in original document"); - Core.storeFileDocumentContent(context, generatedDocumentMendixObject, overlayedContent); - - logger.trace("Close PDFs"); - overlayDoc.close(); - offerteDoc.close(); - - logger.trace("Overlay PDF end"); - return true; - - } - - -} +package communitycommons; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; +import java.util.Arrays; + +import org.apache.commons.io.IOUtils; +import org.apache.pdfbox.multipdf.Overlay; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.multipdf.PDFMergerUtility; + +import system.proxies.FileDocument; +import system.proxies.Language; + +import com.google.common.collect.ImmutableMap; +import com.mendix.core.Core; +import com.mendix.core.CoreException; +import com.mendix.core.conf.RuntimeVersion; +import com.mendix.core.objectmanagement.member.MendixBoolean; +import com.mendix.integration.WebserviceException; +import com.mendix.logging.ILogNode; +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.IMendixObject; +import com.mendix.systemwideinterfaces.core.ISession; +import com.mendix.systemwideinterfaces.core.IUser; + +import static communitycommons.proxies.constants.Constants.getMergeMultiplePdfs_MaxAtOnce; +import java.util.ArrayList; + + +public class Misc +{ + + static final ILogNode LOG = Core.getLogger("communitycommons"); + + public abstract static class IterateCallback + { + boolean start = false; + boolean stop = false; + private Iterator mapIter; + + public abstract void hit(T1 key, T2 value) throws Exception; + + public void exit() { + stop = true; + } + + public void remove() { + mapIter.remove(); + } + + synchronized void runOn(Map map) throws Exception { + if (start) + throw new IllegalMonitorStateException(); + start = true; + + try { + this.mapIter = map.keySet().iterator(); + + while ( mapIter.hasNext() ) + { + T1 key = mapIter.next(); + T2 value = map.get(key); + + hit(key, value); + + if (stop) + break; + } + } + + finally { + //reset state to allow reuse, even when exceptions occur + mapIter = null; + stop = false; + start = false; + } + } + } + + /** + * Because you cannot remove items from a map while iteration, this function is introduced. + * In the callback, you can use this.remove() or this.exit() to either remove or break the loop. Use return; to continue + * @throws Exception + */ + public static void iterateMap(Map map, IterateCallback callback) throws Exception { + //http://marxsoftware.blogspot.com/2008/04/removing-entry-from-java-map-during.html + if (map == null || callback == null) + throw new IllegalArgumentException(); + + callback.runOn(map); + } + + public static String getApplicationURL() + { + return Core.getConfiguration().getApplicationRootUrl(); + } + + public static String getRuntimeVersion() + { + RuntimeVersion runtimeVersion = RuntimeVersion.getInstance(); + return runtimeVersion.toString(); + } + + public static void throwException(String message) throws UserThrownException + { + throw new UserThrownException(message); + } + + public static void throwWebserviceException(String faultstring) throws WebserviceException { + throw new WebserviceException(WebserviceException.clientFaultCode, faultstring); + } + + public static String retrieveURL(String url, String postdata) throws Exception + { + // Send data, appname + URLConnection conn = new URL(url).openConnection(); + + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + + if (postdata != null) { + try ( + OutputStream os = conn.getOutputStream() + ) { + IOUtils.copy(new ByteArrayInputStream(postdata.getBytes("UTF-8")), os); + } + } + + String result; + try ( + InputStream is = conn.getInputStream() + ) { + // Get the response + result = new String(IOUtils.toString(is)); + } + + return result; + } + + public static Boolean duplicateFileDocument(IContext context, IMendixObject toClone, IMendixObject target) throws Exception + { + if (toClone == null || target == null) + throw new Exception("No file to clone or to clone into provided"); + + MendixBoolean hasContents = (MendixBoolean) toClone.getMember(context, FileDocument.MemberNames.HasContents.toString()); + + if (!hasContents.getValue(context)) + return false; + + try ( + InputStream inputStream = Core.getFileDocumentContent(context, toClone) + ) { + Core.storeFileDocumentContent(context, target, (String) toClone.getValue(context, system.proxies.FileDocument.MemberNames.Name.toString()), inputStream); + } + + return true; + } + + public static Boolean duplicateImage(IContext context, IMendixObject toClone, IMendixObject target, int thumbWidth, int thumbHeight) throws Exception + { + if (toClone == null || target == null) + throw new Exception("No file to clone or to clone into provided"); + + MendixBoolean hasContents = (MendixBoolean) toClone.getMember(context, FileDocument.MemberNames.HasContents.toString()); + if (!hasContents.getValue(context)) + return false; + + try ( + InputStream inputStream = Core.getImage(context, toClone, false) + ) { + Core.storeImageDocumentContent(context, target, inputStream, thumbWidth, thumbHeight); + } + + return true; + } + + public static Boolean storeURLToFileDocument(IContext context, String url, IMendixObject __document, String filename) throws IOException { + + ILogNode LOG = Core.getLogger("CommunityCommons"); + if (__document == null || url == null || filename == null) { + throw new IllegalArgumentException("No document, filename or URL provided"); + } + + final int MAX_REMOTE_FILESIZE = 1024 * 1024 * 200; //maximum of 200 MB + try { + URL imageUrl = new URL(url); + URLConnection connection = imageUrl.openConnection(); + //we connect in 20 seconds or not at all + connection.setConnectTimeout(20000); + connection.setReadTimeout(20000); + connection.connect(); + + int contentLength = connection.getContentLength(); + + //check on forehand the size of the remote file, we don't want to kill the server by providing a 3 terabyte image. + LOG.trace(String.format("Remote filesize: %d", contentLength)); + + if (contentLength > MAX_REMOTE_FILESIZE) { //maximum of 200 mb + throw new IllegalArgumentException(String.format("Wrong filesize of remote url: %d (max: %d)", contentLength, MAX_REMOTE_FILESIZE)); + } + + InputStream fileContentIS; + try (InputStream connectionInputStream = connection.getInputStream()) { + if (contentLength >= 0) { + fileContentIS = connectionInputStream; + } else { // contentLength is negative or unknown + LOG.trace(String.format("Unknown content length; limiting to %d", MAX_REMOTE_FILESIZE)); + byte[] outBytes = new byte[MAX_REMOTE_FILESIZE]; + int actualLength = IOUtils.read(connectionInputStream, outBytes, 0, MAX_REMOTE_FILESIZE); + fileContentIS = new ByteArrayInputStream(Arrays.copyOf(outBytes, actualLength)); + } + Core.storeFileDocumentContent(context, __document, filename, fileContentIS); + } + } catch (IOException ioe) { + LOG.error(String.format("A problem occurred while reading from URL %s: %s", url, ioe.getMessage())); + throw ioe; + } + + return true; + } + + public static Long getFileSize(IContext context, IMendixObject document) + { + final int BUFFER_SIZE = 4096; + long size = 0; + + if (context != null) { + byte[] buffer = new byte[BUFFER_SIZE]; + + try ( + InputStream inputStream = Core.getFileDocumentContent(context, document) + ) { + int i; + while ((i = inputStream.read(buffer)) != -1) + size += i; + } catch (IOException e) { + Core.getLogger("FileUtil").error( + "Couldn't determine filesize of FileDocument '" + document.getId()); + } + } + + return size; + } + + public static void delay(long delaytime) throws InterruptedException + { + Thread.sleep(delaytime); + } + + public static IContext getContextFor(IContext context, String username, boolean sudoContext) { + if (username == null || username.isEmpty()) { + throw new RuntimeException("Assertion: No username provided"); + } + + // Session does not have a user when it's a scheduled event. + if (context.getSession().getUser() != null && username.equals(context.getSession().getUser().getName())) + { + return context; + } + else + { + ISession session = getSessionFor(context, username); + + IContext c = session.createContext(); + if (sudoContext) { + return c.createSudoClone(); + } + + return c; + } + } + + private static ISession getSessionFor(IContext context, String username) { + ISession session = Core.getActiveSession(username); + + if (session == null) { + IContext newContext = context.getSession().createContext().createSudoClone(); + newContext.startTransaction(); + try { + session = initializeSessionForUser(newContext, username); + } catch (CoreException e) { + newContext.rollbackTransAction(); + + throw new RuntimeException("Failed to initialize session for user: " + username + ": " + e.getMessage(), e); + } finally { + newContext.endTransaction(); + } + } + + return session; + } + + private static ISession initializeSessionForUser(IContext context, String username) throws CoreException { + IUser user = Core.getUser(context, username); + + if (user == null) { + throw new RuntimeException("Assertion: user with username '" + username + "' does not exist"); + } + + return Core.initializeSession(user, null); + } + + public static Object executeMicroflowAsUser(IContext context, + String microflowName, String username, Boolean sudoContext, Object... args) throws Exception + { + + if (context == null) + throw new Exception("Assertion: No context provided"); + if (microflowName == null || microflowName.isEmpty()) + throw new Exception("Assertion: No context provided"); + if (!Core.getMicroflowNames().contains(microflowName)) + throw new Exception("Assertion: microflow not found: " + microflowName); + if (args.length % 2 != 0) + throw new Exception("Assertion: odd number of dynamic arguments provided, please name every argument: " + args.length); + + Map params = new LinkedHashMap(); + for(int i = 0; i < args.length; i+= 2) if (args[i] != null) + params.put(args[i].toString(), args[i + 1]); + + IContext c = getContextFor(context, username, sudoContext); + + return Core.execute(c, microflowName, params); + } + + //MWE: based on: http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Executor.html + + static class MFSerialExecutor { + private static final ILogNode LOG = Core.getLogger("communitycommons"); + + private static MFSerialExecutor _instance = new MFSerialExecutor(); + + private final AtomicLong tasknr = new AtomicLong(); + private final ExecutorService executor; + + public static MFSerialExecutor instance() { + return _instance; + } + + private MFSerialExecutor() { + executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + + //Default thread factory takes care of setting the proper thread context + private final ThreadFactory defaultFactory = Executors.defaultThreadFactory(); + + @Override + public Thread newThread(Runnable runnable) { + Thread t = defaultFactory.newThread(runnable); + t.setPriority(Thread.MIN_PRIORITY); + t.setName("CommunityCommons background pool executor thread"); + return t; + } + + }); + } + + public void execute(final Runnable command) + { + if (command == null) { + throw new NullPointerException("command"); + } + + final long currenttasknr = tasknr.incrementAndGet(); + LOG.debug("[RunMicroflowAsyncInQueue] Scheduling task #" + currenttasknr); + + executor.submit(new Runnable() { + @Override + public void run() { + LOG.debug("[RunMicroflowAsyncInQueue] Running task #" + currenttasknr); + try { + command.run(); + } catch(RuntimeException e) { + LOG.error("[RunMicroflowAsyncInQueue] Execution of task #" + currenttasknr + " failed: " + e.getMessage(), e); + throw e; //single thread executor will continue, even if an exception is thrown. + } + LOG.debug("[RunMicroflowAsyncInQueue] Completed task #" + currenttasknr + ". Tasks left: " + (tasknr.get() - currenttasknr)); + } + }); + } + } + + public static Boolean runMicroflowAsyncInQueue(final String microflowName) + { + MFSerialExecutor.instance().execute(new Runnable() { + @Override + public void run() + { + try + { + Future future = Core.executeAsync(Core.createSystemContext(), microflowName, true, new HashMap()); //MWE: somehow, it only works with system context... well thats OK for now. + future.get(); + } + catch (Exception e) + { + throw new RuntimeException("Failed to run Async: "+ microflowName + ": " + e.getMessage(), e); + } + } + }); + return true; + } + + public static Boolean runMicroflowInBackground(final IContext context, final String microflowName, + final IMendixObject paramObject) + { + + MFSerialExecutor.instance().execute(new Runnable() { + + @Override + public void run() + { + try + { + IContext c = Core.createSystemContext(); + if (paramObject != null) { + Core.executeAsync(c, microflowName, true, paramObject).get(); //MWE: somehow, it only works with system context... well thats OK for now. + } + else + Core.executeAsync(c, microflowName, true, new HashMap()).get(); //MWE: somehow, it only works with system context... well thats OK for now. + } + catch (Exception e) + { + throw new RuntimeException("Failed to run Async: "+ microflowName + ": " + e.getMessage(), e); + } + + } + + }); + return true; + } + + private interface IBatchItemHandler + { + + void exec(IContext context, IMendixObject obj) throws Exception; + + } + + private static class BatchState { + private int state = 0; //-1 = error, 1 = done. + private final IBatchItemHandler callback; + + public BatchState(IBatchItemHandler callback) { + this.callback = callback; + } + + public void setState(int state) + { + this.state = state; + } + + public int getState() + { + return state; + } + + public void handle(IContext context, IMendixObject obj) throws Exception { + callback.exec(context, obj); + } + } + + public static Boolean executeMicroflowInBatches(String xpath, final String microflow, int batchsize, boolean waitUntilFinished, boolean asc) throws CoreException, InterruptedException { + Core.getLogger("communitycommons").debug("[ExecuteInBatches] Starting microflow batch '" + microflow + "..."); + + return executeInBatches(xpath, new BatchState(new IBatchItemHandler() { + + @Override + public void exec(IContext context, IMendixObject obj) throws Exception + { + Core.executeAsync(context, microflow, true, obj).get(); + } + + }), batchsize, waitUntilFinished, asc); + } + + public static Boolean recommitInBatches(String xpath, int batchsize, + boolean waitUntilFinished, Boolean asc) throws CoreException, InterruptedException + { + Core.getLogger("communitycommons").debug("[ExecuteInBatches] Starting recommit batch..."); + + return executeInBatches(xpath, new BatchState(new IBatchItemHandler() { + + @Override + public void exec(IContext context, IMendixObject obj) throws Exception + { + Core.commit(context, obj); + } + + }), batchsize, waitUntilFinished, asc); + } + + public static Boolean executeInBatches(String xpathRaw, BatchState batchState, int batchsize, boolean waitUntilFinished, boolean asc) throws CoreException, InterruptedException + { + String xpath = xpathRaw.startsWith("//") ? xpathRaw : "//" + xpathRaw; + + long count = Core.retrieveXPathQueryAggregate(Core.createSystemContext(), "count(" + xpath + ")"); + int loop = (int) Math.ceil(((float)count) / ((float)batchsize)); + + + Core.getLogger("communitycommons").debug( + "[ExecuteInBatches] Starting batch on ~ " + count + " objects divided over ~ " + loop + " batches. " + + (waitUntilFinished ? "Waiting until the batch has finished..." : "") + ); + + executeInBatchesHelper(xpath, batchsize, 0, batchState, count, asc); + + if (waitUntilFinished) { + while (batchState.getState() == 0) { + Thread.sleep(5000); + } + if (batchState.getState() == 1) { + Core.getLogger("communitycommons").debug("[ExecuteInBatches] Successfully finished batch"); + return true; + } + Core.getLogger("communitycommons").error("[ExecuteInBatches] Failed to finish batch. Please check the application log for more details."); + return false; + } + + return true; + } + + static void executeInBatchesHelper(final String xpath, final int batchsize, final long last, final BatchState batchState, final long count, final boolean asc) { + MFSerialExecutor.instance().execute(new Runnable() { + + @Override + public void run() + { + try + { + Thread.sleep(200); + IContext c = Core.createSystemContext(); + + List objects = Core.retrieveXPathQuery(c, xpath + (last > 0 ? "[id " + (asc ? "> " : "< ") + last + "]" : ""), batchsize, 0, ImmutableMap.of("id", asc ? "asc" : "desc")); + + //no new objects found :) + if (objects.size() == 0) { + Core.getLogger("communitycommons").debug("[ExecuteInBatches] Succesfully finished batch on ~" + count + " objects."); + batchState.setState(1); + } + else { + + //process objects + for(IMendixObject obj: objects) + batchState.handle(c, obj); + + //invoke next batch + executeInBatchesHelper(xpath, batchsize, objects.get(objects.size() - 1).getId().toLong(), batchState, count, asc); + } + } + catch (Exception e) + { + batchState.setState(-1); + throw new RuntimeException("[ExecuteInBatches] Failed to run in batch: " + e.getMessage(), e); + } + } + + }); + } + + /** + * Tests if two objects are equal with throwing unecessary null pointer exceptions. + * + * This is almost the most stupid function ever, since it should be part of Java itself. + * + * In java 7 it will finally be available as static method Object.equals() + * @param left + * @param right + * @return + */ + public static boolean objectsAreEqual(Object left, Object right) { + if (left == null && right == null) + return true; + if (left == null || right == null) + return false; + return left.equals(right); + } + + /** + * Get the default language + * @param context + * @return The default language + * @throws CoreException + */ + public static Language getDefaultLanguage(IContext context) throws CoreException { + String languageCode = Core.getDefaultLanguage().getCode(); + List languageList = Language.load(context, "[Code = '" + languageCode + "']"); + if (languageList == null || languageList.isEmpty()) { + throw new RuntimeException("No language found for default language constant value " + languageCode); + } + return languageList.get(0); + } + + public static boolean mergePDF(IContext context,List documents, IMendixObject mergedDocument ) throws IOException{ + if (getMergeMultiplePdfs_MaxAtOnce() <= 0 || documents.size() <= getMergeMultiplePdfs_MaxAtOnce()) { + + List sources = new ArrayList<>(); + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + PDFMergerUtility mergePdf = new PDFMergerUtility(); + + for(FileDocument file: documents) { + InputStream content = Core.getFileDocumentContent(context, file.getMendixObject()); + sources.add(content); + } + mergePdf.addSources(sources); + mergePdf.setDestinationStream(out); + mergePdf.mergeDocuments(null); + + Core.storeFileDocumentContent(context, mergedDocument, new ByteArrayInputStream(out.toByteArray())); + + out.reset(); + documents.clear(); + } catch (IOException e) { + throw new RuntimeException("Failed to merge documents" + e.getMessage(), e); + } finally { // We cannot use try-with-resources because streams would be prematurely closed + for (InputStream is : sources) { + is.close(); + } + } + + return true; + } else { + throw new IllegalArgumentException("MergeMultiplePDFs: you cannot merge more than " + getMergeMultiplePdfs_MaxAtOnce() + + " PDF files at once. You are trying to merge " + documents.size() + " PDF files."); + } + } + + + /** + * Overlay a generated PDF document with another PDF (containing the company stationary for example) + * @param context + * @param generatedDocumentMendixObject The document to overlay + * @param overlayMendixObject The document containing the overlay + * @return boolean + * @throws IOException + */ + public static boolean overlayPdf(IContext context, IMendixObject generatedDocumentMendixObject, IMendixObject overlayMendixObject, boolean onTopOfContent) throws IOException { + LOG.trace("Retrieve generated document"); + try ( + PDDocument inputDoc = PDDocument.load(Core.getFileDocumentContent(context, generatedDocumentMendixObject)); + PDDocument overlayDoc = PDDocument.load(Core.getFileDocumentContent(context, overlayMendixObject)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ) { + LOG.trace("Overlay PDF start, retrieve overlay PDF"); + + LOG.trace("Perform overlay"); + Overlay overlay = new Overlay(); + overlay.setInputPDF(inputDoc); + overlay.setDefaultOverlayPDF(overlayDoc); + if (onTopOfContent == true){ + overlay.setOverlayPosition(Overlay.Position.FOREGROUND); + } else { + overlay.setOverlayPosition(Overlay.Position.BACKGROUND); + } + + LOG.trace("Save result in output stream"); + + overlay.overlay(new HashMap<>()).save(baos); + + LOG.trace("Duplicate result in input stream"); + try ( InputStream overlayedContent = new ByteArrayInputStream(baos.toByteArray()) ) { + LOG.trace("Store result in original document"); + Core.storeFileDocumentContent(context, generatedDocumentMendixObject, overlayedContent); + } + } + + LOG.trace("Overlay PDF end"); + return true; + } +} diff --git a/javasource/communitycommons/ORM.java b/javasource/communitycommons/ORM.java index aab52dd..0a33fe4 100644 --- a/javasource/communitycommons/ORM.java +++ b/javasource/communitycommons/ORM.java @@ -1,439 +1,438 @@ -package communitycommons; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import com.mendix.core.Core; -import com.mendix.core.CoreException; -import com.mendix.core.objectmanagement.member.MendixAutoNumber; -import com.mendix.core.objectmanagement.member.MendixDateTime; -import com.mendix.core.objectmanagement.member.MendixEnum; -import com.mendix.core.objectmanagement.member.MendixObjectReference; -import com.mendix.core.objectmanagement.member.MendixObjectReferenceSet; -import com.mendix.systemwideinterfaces.core.IContext; -import com.mendix.systemwideinterfaces.core.IMendixIdentifier; -import com.mendix.systemwideinterfaces.core.IMendixObject; -import com.mendix.systemwideinterfaces.core.IMendixObject.ObjectState; -import com.mendix.systemwideinterfaces.core.IMendixObjectMember; -import com.mendix.systemwideinterfaces.core.IMendixObjectMember.MemberState; -import com.mendix.systemwideinterfaces.core.ISession; -import com.mendix.systemwideinterfaces.core.meta.IMetaAssociation; -import com.mendix.systemwideinterfaces.core.meta.IMetaAssociation.AssociationType; -import com.mendix.systemwideinterfaces.core.meta.IMetaEnumValue; -import com.mendix.systemwideinterfaces.core.meta.IMetaEnumeration; -import com.mendix.systemwideinterfaces.core.meta.IMetaObject; -import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive; -import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive.PrimitiveType; - -public class ORM -{ - - - - public static Long getGUID(IMendixObject item) - { - return item.getId().toLong(); - } - - public static String getOriginalValueAsString(IContext context, IMendixObject item, - String member) - { - return String.valueOf(item.getMember(context, member).getOriginalValue(context)); - } - - public static boolean objectHasChanged(IMendixObject anyobject) { - if (anyobject == null) - throw new IllegalArgumentException("The provided object is empty"); - return anyobject.isChanged(); - } - - /** - * checks whether a certain member of an object has changed. If the objects itself is still new, we consider to be changes as well. - * @param item - * @param member - * @param context - * @return - */ - public static boolean memberHasChanged(IContext context, IMendixObject item, String member) - { - if (item == null) - throw new IllegalArgumentException("The provided object is empty"); - if (!item.hasMember(member)) - throw new IllegalArgumentException("Unknown member: " + member); - return item.getMember(context, member).getState() == MemberState.CHANGED || item.getState() != ObjectState.NORMAL; - } - - public static void deepClone(IContext c, IMendixObject source, IMendixObject target, String membersToSkip, String membersToKeep, String reverseAssociations, String excludeEntities, String excludeModules) throws CoreException - { - List toskip = Arrays.asList((membersToSkip + ",createdDate,changedDate").split(",")); - List tokeep = Arrays.asList((membersToKeep + ",System.owner,System.changedBy").split(",")); - List revAssoc = Arrays.asList(reverseAssociations.split(",")); - List skipEntities = Arrays.asList(excludeEntities.split(",")); - List skipModules = Arrays.asList(excludeModules.split(",")); - Map mappedIDs = new HashMap(); - duplicate(c, source, target, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedIDs); - } - - private static void duplicate(IContext ctx, IMendixObject src, IMendixObject tar, - List toskip, List tokeep, List revAssoc, - List skipEntities, List skipModules, - Map mappedObjects) throws CoreException - { - mappedObjects.put(src.getId(), tar.getId()); - - Map> members = src.getMembers(ctx); - String type = src.getType() + "/"; - - for(String key : members.keySet()) - if (!toskip.contains(key) && !toskip.contains(type + key)){ - IMendixObjectMember m = members.get(key); - if (m.isVirtual() || m instanceof MendixAutoNumber) - continue; - - boolean keep = tokeep.contains(key) || tokeep.contains(type + key); - - if (m instanceof MendixObjectReference && !keep && m.getValue(ctx) != null) { - IMendixObject o = Core.retrieveId(ctx, ((MendixObjectReference) m).getValue(ctx)); - IMendixIdentifier refObj = getCloneOfObject(ctx, o, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); - tar.setValue(ctx, key, refObj); - } - - else if (m instanceof MendixObjectReferenceSet && !keep && m.getValue(ctx) != null) { - MendixObjectReferenceSet rs = (MendixObjectReferenceSet) m; - List res = new ArrayList(); - for(IMendixIdentifier item : rs.getValue(ctx)) { - IMendixObject o = Core.retrieveId(ctx, item); - IMendixIdentifier refObj = getCloneOfObject(ctx, o, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); - res.add(refObj); - } - tar.setValue(ctx, key, res); - } - - else if (m instanceof MendixAutoNumber) //skip autonumbers! Ticket 14893 - continue; - - else { - tar.setValue(ctx, key, m.getValue(ctx)); - } - } - Core.commitWithoutEvents(ctx, tar); - duplicateReverseAssociations(ctx, src, tar, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); - } - - private static IMendixIdentifier getCloneOfObject(IContext ctx, IMendixObject src, - List toskip, List tokeep, List revAssoc, - List skipEntities, List skipModules, - Map mappedObjects) throws CoreException - { - String objType = src.getMetaObject().getName(); - String modName = src.getMetaObject().getModuleName(); - - // if object is already being cloned, return ref to clone - if (mappedObjects.containsKey(src.getId())) { - return mappedObjects.get(src.getId()); - // if object should be skipped based on module or entity, return source object - } else if (skipEntities.contains(objType) || skipModules.contains(modName)) { - return src.getId(); - // if not already being cloned, create clone - } else { - IMendixObject clone = Core.instantiate(ctx, src.getType()); - duplicate(ctx, src, clone, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); - return clone.getId(); - } - } - - private static void duplicateReverseAssociations(IContext ctx, IMendixObject src, IMendixObject tar, - List toskip, List tokeep, List revAssocs, - List skipEntities, List skipModules, - Map mappedObjects) throws CoreException - { - for(String fullAssocName : revAssocs) { - String[] parts = fullAssocName.split("/"); - - if (parts.length != 1 && parts.length != 3) //specifying entity has no meaning anymore, but remain backward compatible. - throw new IllegalArgumentException("Reverse association is not defined correctly, please mention the relation name only: '" + fullAssocName + "'"); - - String assocname = parts.length == 3 ? parts[1] : parts[0]; //support length 3 for backward compatibility - - IMetaAssociation massoc = src.getMetaObject().getDeclaredMetaAssociationChild(assocname); - - if (massoc != null) { - IMetaObject relationParent = massoc.getParent(); - // if the parent is in the exclude list, we can't clone the parent, and setting the - // references to the newly cloned target object will screw up the source data. - if (skipEntities.contains(relationParent.getName()) || skipModules.contains(relationParent.getModuleName())){ - throw new IllegalArgumentException("A reverse reference has been specified that starts at an entity in the exclude list, this is not possible to clone: '" + fullAssocName + "'"); - } - - //MWE: what to do with reverse reference sets? -> to avoid spam creating objects on - //reverse references, do not support referenceset (todo: we could keep a map of converted guids and reuse that!) - if (massoc.getType() == AssociationType.REFERENCESET) { - throw new IllegalArgumentException("It is not possible to clone reverse referencesets: '" + fullAssocName + "'"); - } - - List objs = Core.retrieveXPathQueryEscaped(ctx, "//%s[%s='%s']", - relationParent.getName(), assocname, String.valueOf(src.getId().toLong())); - - for(IMendixObject obj : objs) { - @SuppressWarnings("unused") // object is unused on purpose - IMendixIdentifier refObj = getCloneOfObject(ctx, obj, toskip, tokeep, revAssocs, skipEntities, skipModules, mappedObjects); - // setting reference explicitly is not necessary, this has been done in the - // duplicate() call. - } - } - } - } - - public static Boolean commitWithoutEvents(IContext context, IMendixObject subject) throws CoreException - { - Core.commitWithoutEvents(context, subject); - return true; - } - - public static String getValueOfPath(IContext context, IMendixObject substitute, String fullpath, String datetimeformat) throws Exception - { - String[] path = fullpath.split("/"); - if (path.length == 1) { - IMendixObjectMember member = substitute.getMember(context, path[0]); - - //special case, see ticket 9135, format datetime. - if (member instanceof MendixDateTime) { - Date time = ((MendixDateTime) member).getValue(context); - if (time == null) - return ""; - String f = datetimeformat != null && !datetimeformat.isEmpty() ? datetimeformat : "EEE dd MMM yyyy, HH:mm"; - return new SimpleDateFormat(f).format(time); - } - - if (member instanceof MendixEnum) { - String value = member.parseValueToString(context); - if (value == null || value.isEmpty()) - return ""; - - IMetaEnumeration enumeration = ((MendixEnum)member).getEnumeration(); - IMetaEnumValue evalue = enumeration.getEnumValues().get(value); - return Core.getInternationalizedString(context, evalue.getI18NCaptionKey()); - } - //default - return member.parseValueToString(context); - } - - else if (path.length == 0) - throw new Exception("communitycommons.ORM.getValueOfPath: Unexpected end of path."); - - else { - IMendixObjectMember member = substitute.getMember(context, path[0]); - if (member instanceof MendixObjectReference) { - MendixObjectReference ref = (MendixObjectReference) member; - IMendixIdentifier id = ref.getValue(context); - if (id == null) - return ""; - IMendixObject obj = Core.retrieveId(context, id); - if (obj == null) - return ""; - return getValueOfPath(context, obj, fullpath.substring(fullpath.indexOf("/") + 1), datetimeformat); - } - - else if (member instanceof MendixObjectReferenceSet) { - MendixObjectReferenceSet ref = (MendixObjectReferenceSet) member; - List ids = ref.getValue(context); - if (ids == null) - return ""; - StringBuilder res = new StringBuilder(); - for(IMendixIdentifier id : ids) { - if (id == null) - continue; - IMendixObject obj = Core.retrieveId(context, id); - if (obj == null) - continue; - res.append(", "); - res.append(getValueOfPath(context, obj, fullpath.substring(fullpath.indexOf("/") + 1), datetimeformat)); - } - return res.length() > 1 ? res.toString().substring(2) : ""; - } - else throw new Exception("communitycommons.ORM.getValueOfPath: Not a valid reference: '"+path[0]+"' in '"+ fullpath +"'"); - } - } - - public static Boolean cloneObject(IContext c, IMendixObject source, - IMendixObject target, Boolean withAssociations) - { - Map> members = source.getMembers(c); - - for(String key : members.keySet()) { - IMendixObjectMember m = members.get(key); - if (m.isVirtual()) - continue; - if (m instanceof MendixAutoNumber) - continue; - if (withAssociations || ((!(m instanceof MendixObjectReference) && !(m instanceof MendixObjectReferenceSet)&& !(m instanceof MendixAutoNumber)))) - target.setValue(c, key, m.getValue(c)); - } - return true; - } - - private static ConcurrentHashMap locks = new ConcurrentHashMap(); - - public static synchronized Boolean acquireLock(IContext context, IMendixObject item) - { - if (!isLocked(item)) { - if (!context.getSession().getUser().getName().equals(getLockOwner(item))) - locks.put(item.getId().toLong(), context.getSession()); - return true; - } - else if (locks.get(item.getId().toLong()).equals(context.getSession())) - return true; //lock owned by this session - return false; - } - - private static boolean isLocked(IMendixObject item) - { - if (item == null) - throw new IllegalArgumentException("No item provided"); - if (!locks.containsKey(item.getId().toLong())) - return false; - if (!sessionIsActive(locks.get(item.getId().toLong()))) { - locks.remove(item.getId().toLong()); //Remove locks which are nolonger active - return false; - } - return true; - } - - private static boolean sessionIsActive(ISession session) - { - for (ISession s : Core.getActiveSessions()) - if (s.equals(session)) - return true; - return false; - } - - public synchronized static Boolean releaseLock(IContext context, IMendixObject item, Boolean force) - { - if (locks.containsKey(item.getId().toLong())) { - if (force || locks.get(item.getId().toLong()).equals(context.getSession())) - locks.remove(item.getId().toLong()); - } - return true; - } - - public static Boolean waitForLock(IContext context, IMendixObject item, - Long timeOutSeconds) throws InterruptedException - { - boolean res = false; - long started = new Date().getTime(); - while (!res) { - res = acquireLock(context, item); - if (!res) - Thread.sleep(1000); - if (((new Date().getTime()) - started) > 1000 * timeOutSeconds) - break; - } - return res; - } - - public static String getLockOwner(IMendixObject item) - { - ISession session = locks.get(item.getId().toLong()); - return session == null ? null : session.getUser().getName(); - } - - public static IMendixObject firstWhere(IContext c, String entityName, - Object member, String value) throws CoreException - { - List items = Core.retrieveXPathQuery(c, String.format("//%s[%s = '%s']", entityName, member, value), 1, 0, new HashMap()); - if (items == null || items.size() == 0) - return null; - return items.get(0); - } - - public synchronized static void releaseOldLocks() - { - Set activeSessions = new HashSet(Core.getActiveSessions()); //Lookup with Ord(log(n)) instead of Ord(n). - - List tbrm = new ArrayList(); - for (Entry lock : locks.entrySet()) - if (!activeSessions.contains(lock.getValue())) - tbrm.add(lock.getKey()); - - for(Long key : tbrm) - locks.remove(key); - } - - public static IMendixObject getLastChangedByUser(IContext context, - IMendixObject thing) throws CoreException - { - if (thing == null || !thing.hasChangedByAttribute()) - return null; - - IMendixIdentifier itemId = thing.getChangedBy(context); - if (itemId == null) - return null; - - return Core.retrieveId(context, itemId); - } - - public static IMendixObject getCreatedByUser(IContext context, - IMendixObject thing) throws CoreException - { - if (thing == null || !thing.hasOwnerAttribute()) - return null; - - IMendixIdentifier itemId = thing.getOwner(context); - if (itemId == null) - return null; - - return Core.retrieveId(context, itemId); - } - - public static boolean encryptMemberIfChanged(IContext context, IMendixObject item, - String member, String key) throws Exception - { - if (memberHasChanged(context, item, member)) { - - if (item.getMetaObject().getMetaPrimitive(member).getType() != PrimitiveType.String) - throw new IllegalArgumentException("The member '" + member + "' is not a string attribute!"); - - item.setValue(context, member, StringUtils.encryptString(key, (String) item.getValue(context, member))); - return true; - } - return false; - } - - public static void commitSilent(IContext c, IMendixObject mendixObject) - { - try - { - Core.commit(c, mendixObject); - } - catch (CoreException e) - { - throw new RuntimeException(e); - } - } - - public static void copyAttributes(IContext context, IMendixObject source, IMendixObject target) - { - if (source == null) - throw new IllegalStateException("source is null"); - if (target == null) - throw new IllegalStateException("target is null"); - - for(IMetaPrimitive e : target.getMetaObject().getMetaPrimitives()) { - if (!source.hasMember(e.getName())) - continue; - if (e.isVirtual() || e.getType() == PrimitiveType.AutoNumber) - continue; - - target.setValue(context, e.getName(), source.getValue(context, e.getName())); - } - } -} +package communitycommons; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.mendix.core.Core; +import com.mendix.core.CoreException; +import com.mendix.core.objectmanagement.member.MendixAutoNumber; +import com.mendix.core.objectmanagement.member.MendixDateTime; +import com.mendix.core.objectmanagement.member.MendixEnum; +import com.mendix.core.objectmanagement.member.MendixObjectReference; +import com.mendix.core.objectmanagement.member.MendixObjectReferenceSet; +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.IMendixIdentifier; +import com.mendix.systemwideinterfaces.core.IMendixObject; +import com.mendix.systemwideinterfaces.core.IMendixObject.ObjectState; +import com.mendix.systemwideinterfaces.core.IMendixObjectMember; +import com.mendix.systemwideinterfaces.core.IMendixObjectMember.MemberState; +import com.mendix.systemwideinterfaces.core.ISession; +import com.mendix.systemwideinterfaces.core.meta.IMetaAssociation; +import com.mendix.systemwideinterfaces.core.meta.IMetaAssociation.AssociationType; +import com.mendix.systemwideinterfaces.core.meta.IMetaEnumValue; +import com.mendix.systemwideinterfaces.core.meta.IMetaEnumeration; +import com.mendix.systemwideinterfaces.core.meta.IMetaObject; +import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive; +import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive.PrimitiveType; + +public class ORM +{ + + + + public static Long getGUID(IMendixObject item) + { + return item.getId().toLong(); + } + + public static String getOriginalValueAsString(IContext context, IMendixObject item, + String member) + { + return String.valueOf(item.getMember(context, member).getOriginalValue(context)); + } + + public static boolean objectHasChanged(IMendixObject anyobject) { + if (anyobject == null) + throw new IllegalArgumentException("The provided object is empty"); + return anyobject.isChanged(); + } + + /** + * checks whether a certain member of an object has changed. If the objects itself is still new, we consider to be changes as well. + * @param item + * @param member + * @param context + * @return + */ + public static boolean memberHasChanged(IContext context, IMendixObject item, String member) + { + if (item == null) + throw new IllegalArgumentException("The provided object is empty"); + if (!item.hasMember(member)) + throw new IllegalArgumentException("Unknown member: " + member); + return item.getMember(context, member).getState() == MemberState.CHANGED || item.getState() != ObjectState.NORMAL; + } + + public static void deepClone(IContext c, IMendixObject source, IMendixObject target, String membersToSkip, String membersToKeep, String reverseAssociations, String excludeEntities, String excludeModules) throws CoreException + { + List toskip = Arrays.asList((membersToSkip + ",createdDate,changedDate").split(",")); + List tokeep = Arrays.asList((membersToKeep + ",System.owner,System.changedBy").split(",")); + List revAssoc = Arrays.asList(reverseAssociations.split(",")); + List skipEntities = Arrays.asList(excludeEntities.split(",")); + List skipModules = Arrays.asList(excludeModules.split(",")); + Map mappedIDs = new HashMap(); + duplicate(c, source, target, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedIDs); + } + + private static void duplicate(IContext ctx, IMendixObject src, IMendixObject tar, + List toskip, List tokeep, List revAssoc, + List skipEntities, List skipModules, + Map mappedObjects) throws CoreException + { + mappedObjects.put(src.getId(), tar.getId()); + + Map> members = src.getMembers(ctx); + String type = src.getType() + "/"; + + for(String key : members.keySet()) + if (!toskip.contains(key) && !toskip.contains(type + key)){ + IMendixObjectMember m = members.get(key); + if (m.isVirtual() || m instanceof MendixAutoNumber) + continue; + + boolean keep = tokeep.contains(key) || tokeep.contains(type + key); + + if (m instanceof MendixObjectReference && !keep && m.getValue(ctx) != null) { + IMendixObject o = Core.retrieveId(ctx, ((MendixObjectReference) m).getValue(ctx)); + IMendixIdentifier refObj = getCloneOfObject(ctx, o, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); + tar.setValue(ctx, key, refObj); + } + + else if (m instanceof MendixObjectReferenceSet && !keep && m.getValue(ctx) != null) { + MendixObjectReferenceSet rs = (MendixObjectReferenceSet) m; + List res = new ArrayList(); + for(IMendixIdentifier item : rs.getValue(ctx)) { + IMendixObject o = Core.retrieveId(ctx, item); + IMendixIdentifier refObj = getCloneOfObject(ctx, o, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); + res.add(refObj); + } + tar.setValue(ctx, key, res); + } + + else if (m instanceof MendixAutoNumber) //skip autonumbers! Ticket 14893 + continue; + + else { + tar.setValue(ctx, key, m.getValue(ctx)); + } + } + Core.commitWithoutEvents(ctx, tar); + duplicateReverseAssociations(ctx, src, tar, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); + } + + private static IMendixIdentifier getCloneOfObject(IContext ctx, IMendixObject src, + List toskip, List tokeep, List revAssoc, + List skipEntities, List skipModules, + Map mappedObjects) throws CoreException + { + String objType = src.getMetaObject().getName(); + String modName = src.getMetaObject().getModuleName(); + + // if object is already being cloned, return ref to clone + if (mappedObjects.containsKey(src.getId())) { + return mappedObjects.get(src.getId()); + // if object should be skipped based on module or entity, return source object + } else if (skipEntities.contains(objType) || skipModules.contains(modName)) { + return src.getId(); + // if not already being cloned, create clone + } else { + IMendixObject clone = Core.instantiate(ctx, src.getType()); + duplicate(ctx, src, clone, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); + return clone.getId(); + } + } + + private static void duplicateReverseAssociations(IContext ctx, IMendixObject src, IMendixObject tar, + List toskip, List tokeep, List revAssocs, + List skipEntities, List skipModules, + Map mappedObjects) throws CoreException + { + for(String fullAssocName : revAssocs) { + String[] parts = fullAssocName.split("/"); + + if (parts.length != 1 && parts.length != 3) //specifying entity has no meaning anymore, but remain backward compatible. + throw new IllegalArgumentException("Reverse association is not defined correctly, please mention the relation name only: '" + fullAssocName + "'"); + + String assocname = parts.length == 3 ? parts[1] : parts[0]; //support length 3 for backward compatibility + + IMetaAssociation massoc = src.getMetaObject().getDeclaredMetaAssociationChild(assocname); + + if (massoc != null) { + IMetaObject relationParent = massoc.getParent(); + // if the parent is in the exclude list, we can't clone the parent, and setting the + // references to the newly cloned target object will screw up the source data. + if (skipEntities.contains(relationParent.getName()) || skipModules.contains(relationParent.getModuleName())){ + throw new IllegalArgumentException("A reverse reference has been specified that starts at an entity in the exclude list, this is not possible to clone: '" + fullAssocName + "'"); + } + + //MWE: what to do with reverse reference sets? -> to avoid spam creating objects on + //reverse references, do not support referenceset (todo: we could keep a map of converted guids and reuse that!) + if (massoc.getType() == AssociationType.REFERENCESET) { + throw new IllegalArgumentException("It is not possible to clone reverse referencesets: '" + fullAssocName + "'"); + } + + List objs = Core.retrieveXPathQueryEscaped(ctx, "//%s[%s='%s']", + relationParent.getName(), assocname, String.valueOf(src.getId().toLong())); + + for(IMendixObject obj : objs) { + @SuppressWarnings("unused") // object is unused on purpose + IMendixIdentifier refObj = getCloneOfObject(ctx, obj, toskip, tokeep, revAssocs, skipEntities, skipModules, mappedObjects); + // setting reference explicitly is not necessary, this has been done in the + // duplicate() call. + } + } + } + } + + public static Boolean commitWithoutEvents(IContext context, IMendixObject subject) throws CoreException + { + Core.commitWithoutEvents(context, subject); + return true; + } + + public static String getValueOfPath(IContext context, IMendixObject substitute, String fullpath, String datetimeformat) throws Exception + { + String[] path = fullpath.split("/"); + if (path.length == 1) { + IMendixObjectMember member = substitute.getMember(context, path[0]); + + //special case, see ticket 9135, format datetime. + if (member instanceof MendixDateTime) { + Date time = ((MendixDateTime) member).getValue(context); + if (time == null) + return ""; + String f = datetimeformat != null && !datetimeformat.isEmpty() ? datetimeformat : "EEE dd MMM yyyy, HH:mm"; + return new SimpleDateFormat(f).format(time); + } + + if (member instanceof MendixEnum) { + String value = member.parseValueToString(context); + if (value == null || value.isEmpty()) + return ""; + + IMetaEnumeration enumeration = ((MendixEnum)member).getEnumeration(); + IMetaEnumValue evalue = enumeration.getEnumValues().get(value); + return Core.getInternationalizedString(context, evalue.getI18NCaptionKey()); + } + //default + return member.parseValueToString(context); + } + + else if (path.length == 0) + throw new Exception("communitycommons.ORM.getValueOfPath: Unexpected end of path."); + + else { + IMendixObjectMember member = substitute.getMember(context, path[0]); + if (member instanceof MendixObjectReference) { + MendixObjectReference ref = (MendixObjectReference) member; + IMendixIdentifier id = ref.getValue(context); + if (id == null) + return ""; + IMendixObject obj = Core.retrieveId(context, id); + if (obj == null) + return ""; + return getValueOfPath(context, obj, fullpath.substring(fullpath.indexOf("/") + 1), datetimeformat); + } + + else if (member instanceof MendixObjectReferenceSet) { + MendixObjectReferenceSet ref = (MendixObjectReferenceSet) member; + List ids = ref.getValue(context); + if (ids == null) + return ""; + StringBuilder res = new StringBuilder(); + for(IMendixIdentifier id : ids) { + if (id == null) + continue; + IMendixObject obj = Core.retrieveId(context, id); + if (obj == null) + continue; + res.append(", "); + res.append(getValueOfPath(context, obj, fullpath.substring(fullpath.indexOf("/") + 1), datetimeformat)); + } + return res.length() > 1 ? res.toString().substring(2) : ""; + } + else throw new Exception("communitycommons.ORM.getValueOfPath: Not a valid reference: '"+path[0]+"' in '"+ fullpath +"'"); + } + } + + public static Boolean cloneObject(IContext c, IMendixObject source, + IMendixObject target, Boolean withAssociations) + { + Map> members = source.getMembers(c); + + for(String key : members.keySet()) { + IMendixObjectMember m = members.get(key); + if (m.isVirtual()) + continue; + if (m instanceof MendixAutoNumber) + continue; + if (withAssociations || ((!(m instanceof MendixObjectReference) && !(m instanceof MendixObjectReferenceSet)&& !(m instanceof MendixAutoNumber)))) + target.setValue(c, key, m.getValue(c)); + } + return true; + } + + private static ConcurrentHashMap locks = new ConcurrentHashMap(); + + public static synchronized Boolean acquireLock(IContext context, IMendixObject item) + { + if (!isLocked(item)) { + locks.put(item.getId().toLong(), context.getSession()); + return true; + } + else if (locks.get(item.getId().toLong()).equals(context.getSession())) + return true; //lock owned by this session + return false; + } + + private static boolean isLocked(IMendixObject item) + { + if (item == null) + throw new IllegalArgumentException("No item provided"); + if (!locks.containsKey(item.getId().toLong())) + return false; + if (!sessionIsActive(locks.get(item.getId().toLong()))) { + locks.remove(item.getId().toLong()); //Remove locks which are nolonger active + return false; + } + return true; + } + + private static boolean sessionIsActive(ISession session) + { + for (ISession s : Core.getActiveSessions()) + if (s.equals(session)) + return true; + return false; + } + + public synchronized static Boolean releaseLock(IContext context, IMendixObject item, Boolean force) + { + if (locks.containsKey(item.getId().toLong())) { + if (force || locks.get(item.getId().toLong()).equals(context.getSession())) + locks.remove(item.getId().toLong()); + } + return true; + } + + public static Boolean waitForLock(IContext context, IMendixObject item, + Long timeOutSeconds) throws InterruptedException + { + boolean res = false; + long started = new Date().getTime(); + while (!res) { + res = acquireLock(context, item); + if (!res) + Thread.sleep(1000); + if (((new Date().getTime()) - started) > 1000 * timeOutSeconds) + break; + } + return res; + } + + public static String getLockOwner(IMendixObject item) + { + ISession session = locks.get(item.getId().toLong()); + return session == null ? null : session.getUser().getName(); + } + + public static IMendixObject firstWhere(IContext c, String entityName, + Object member, String value) throws CoreException + { + List items = Core.retrieveXPathQuery(c, String.format("//%s[%s = '%s']", entityName, member, value), 1, 0, new HashMap()); + if (items == null || items.size() == 0) + return null; + return items.get(0); + } + + public synchronized static void releaseOldLocks() + { + Set activeSessions = new HashSet(Core.getActiveSessions()); //Lookup with Ord(log(n)) instead of Ord(n). + + List tbrm = new ArrayList(); + for (Entry lock : locks.entrySet()) + if (!activeSessions.contains(lock.getValue())) + tbrm.add(lock.getKey()); + + for(Long key : tbrm) + locks.remove(key); + } + + public static IMendixObject getLastChangedByUser(IContext context, + IMendixObject thing) throws CoreException + { + if (thing == null || !thing.hasChangedByAttribute()) + return null; + + IMendixIdentifier itemId = thing.getChangedBy(context); + if (itemId == null) + return null; + + return Core.retrieveId(context, itemId); + } + + public static IMendixObject getCreatedByUser(IContext context, + IMendixObject thing) throws CoreException + { + if (thing == null || !thing.hasOwnerAttribute()) + return null; + + IMendixIdentifier itemId = thing.getOwner(context); + if (itemId == null) + return null; + + return Core.retrieveId(context, itemId); + } + + public static boolean encryptMemberIfChanged(IContext context, IMendixObject item, + String member, String key) throws Exception + { + if (memberHasChanged(context, item, member)) { + + if (item.getMetaObject().getMetaPrimitive(member).getType() != PrimitiveType.String) + throw new IllegalArgumentException("The member '" + member + "' is not a string attribute!"); + + item.setValue(context, member, StringUtils.encryptString(key, (String) item.getValue(context, member))); + return true; + } + return false; + } + + public static void commitSilent(IContext c, IMendixObject mendixObject) + { + try + { + Core.commit(c, mendixObject); + } + catch (CoreException e) + { + throw new RuntimeException(e); + } + } + + public static void copyAttributes(IContext context, IMendixObject source, IMendixObject target) + { + if (source == null) + throw new IllegalStateException("source is null"); + if (target == null) + throw new IllegalStateException("target is null"); + + for(IMetaPrimitive e : target.getMetaObject().getMetaPrimitives()) { + if (!source.hasMember(e.getName())) + continue; + if (e.isVirtual() || e.getType() == PrimitiveType.AutoNumber) + continue; + + target.setValue(context, e.getName(), source.getValue(context, e.getName())); + } + } +} diff --git a/javasource/communitycommons/StringUtils.java b/javasource/communitycommons/StringUtils.java index cedb2b8..07afbd7 100644 --- a/javasource/communitycommons/StringUtils.java +++ b/javasource/communitycommons/StringUtils.java @@ -1,387 +1,425 @@ -package communitycommons; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; -import java.security.DigestException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.UUID; -import java.util.regex.MatchResult; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import javax.swing.text.MutableAttributeSet; -import javax.swing.text.html.HTML; -import javax.swing.text.html.HTMLEditorKit; -import javax.swing.text.html.parser.ParserDelegator; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringEscapeUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.owasp.validator.html.AntiSamy; -import org.owasp.validator.html.CleanResults; -import org.owasp.validator.html.Policy; - -import system.proxies.FileDocument; - -import com.google.common.base.Function; -import com.mendix.core.Core; -import com.mendix.systemwideinterfaces.MendixRuntimeException; -import com.mendix.systemwideinterfaces.core.IContext; -import com.mendix.systemwideinterfaces.core.IMendixObject; - -import communitycommons.proxies.XSSPolicy; - -public class StringUtils -{ - - public static final String HASH_ALGORITHM = "SHA-256"; - - public static String hash(String value, int length) throws NoSuchAlgorithmException, DigestException - { - byte[] inBytes = value.getBytes(); - byte[] outBytes = new byte[length]; - - MessageDigest alg=MessageDigest.getInstance(HASH_ALGORITHM); - alg.update(inBytes); - - alg.digest(outBytes, 0, length); - return String.valueOf(outBytes); - } - - public static String regexReplaceAll(String haystack, String needleRegex, - String replacement) - { - Pattern pattern = Pattern.compile(needleRegex); - Matcher matcher = pattern.matcher(haystack); - return matcher.replaceAll(replacement); - } - - public static boolean regexTest(String value, String regex) - { - return Pattern.matches(regex, value); - } - - public static String leftPad(String value, Long amount, String fillCharacter) - { - if (fillCharacter == null || fillCharacter.length() == 0) { - return org.apache.commons.lang3.StringUtils.leftPad(value, amount.intValue(), " "); - } - return org.apache.commons.lang3.StringUtils.leftPad(value, amount.intValue(), fillCharacter); - } - - public static String rightPad(String value, Long amount, String fillCharacter) - { - if (fillCharacter == null || fillCharacter.length() == 0) { - return org.apache.commons.lang3.StringUtils.rightPad(value, amount.intValue(), " "); - } - return org.apache.commons.lang3.StringUtils.rightPad(value, amount.intValue(), fillCharacter); - } - - public static String randomString(int length) - { - return org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(length); - } - - public static String substituteTemplate(final IContext context, String template, - final IMendixObject substitute, final boolean HTMLEncode, final String datetimeformat) { - return regexReplaceAll(template, "\\{(@)?([\\w./]+)\\}", new Function() { - - @Override - public String apply(MatchResult match) - { - String value; - String path = match.group(2); - if (match.group(1) != null) - value = String.valueOf(Core.getConfiguration().getConstantValue(path)); - else { - try - { - value = ORM.getValueOfPath(context, substitute, path, datetimeformat); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - } - return HTMLEncode ? HTMLEncode(value) : value; - } - - }); - } - - public static String regexReplaceAll(String source, String regexString, Function replaceFunction) { - if (source == null || source.trim().isEmpty()) // avoid NPE's, save CPU - return ""; - - StringBuffer resultString = new StringBuffer(); - Pattern regex = Pattern.compile(regexString); - Matcher regexMatcher = regex.matcher(source); - - while (regexMatcher.find()) { - MatchResult match = regexMatcher.toMatchResult(); - String value = replaceFunction.apply(match); - regexMatcher.appendReplacement(resultString, Matcher.quoteReplacement(value)); - } - regexMatcher.appendTail(resultString); - - return resultString.toString(); - } - - public static String HTMLEncode(String value) - { - return StringEscapeUtils.escapeHtml4(value); - } - - public static String randomHash() - { - return UUID.randomUUID().toString(); - } - - public static String base64Decode(String encoded) - { - if (encoded == null) - return null; - return new String(Base64.decodeBase64(encoded.getBytes())); - } - - public static void base64DecodeToFile(IContext context, String encoded, FileDocument targetFile) throws Exception - { - if (targetFile == null) - throw new IllegalArgumentException("Source file is null"); - if (encoded == null) - throw new IllegalArgumentException("Source data is null"); - - byte [] decoded = Base64.decodeBase64(encoded.getBytes()); - Core.storeFileDocumentContent(context, targetFile.getMendixObject(), new ByteArrayInputStream(decoded)); - } - - public static String base64Encode(String value) - { - if (value == null) - return null; - return new String(Base64.encodeBase64(value.getBytes())); - } - - public static String base64EncodeFile(IContext context, FileDocument file) throws IOException - { - if (file == null) - throw new IllegalArgumentException("Source file is null"); - if (!file.getHasContents()) - throw new IllegalArgumentException("Source file has no contents!"); - InputStream f = Core.getFileDocumentContent(context, file.getMendixObject()); - return new String(Base64.encodeBase64(IOUtils.toByteArray(f))); - } - - public static String stringFromFile(IContext context, FileDocument source) throws IOException - { - if (source == null) - return null; - InputStream f = Core.getFileDocumentContent(context, source.getMendixObject()); - return org.apache.commons.io.IOUtils.toString(f); - } - - public static void stringToFile(IContext context, String value, FileDocument destination) - { - if (destination == null) - throw new IllegalArgumentException("Destination file is null"); - if (value == null) - throw new IllegalArgumentException("Value to write is null"); - Core.storeFileDocumentContent(context, destination.getMendixObject(), IOUtils.toInputStream(value)); - } - - public static String HTMLToPlainText(String html) throws IOException - { - if (html == null) - return ""; - final StringBuffer result = new StringBuffer(); - - HTMLEditorKit.ParserCallback callback = - new HTMLEditorKit.ParserCallback () { - @Override - public void handleText(char[] data, int pos) { - result.append(data); //TODO: needds to be html entity decode? - } - - @Override - public void handleComment(char[] data, int pos) { - //Do nothing - } - - @Override - public void handleError(String errorMsg, int pos) { - //Do nothing - } - - @Override - public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet a, int pos) { - if (tag == HTML.Tag.BR) - result.append("\r\n"); - } - - @Override - public void handleEndTag(HTML.Tag tag, int pos){ - if (tag == HTML.Tag.P) - result.append("\r\n"); - } - }; - - new ParserDelegator().parse(new StringReader(html), callback, true); - - return result.toString(); - } - - public static String XSSSanitize(String html, XSSPolicy policy) - throws Exception { - if (html == null) - return ""; - // return HtmlSanitizer.sanitize(html); - String policyString = policy == null ? "tinymce" : policy.toString() - .toLowerCase(); - return XSSSanitize(html, policyString); - } - - public static String XSSSanitize(String html, String policyString) - throws Exception { - if (html == null) - return ""; - if (policyString == null) - throw new Exception("Unable to perform XSS sanitization: policyString is null"); - - String filename = Core.getConfiguration().getResourcesPath() + File.separator - + "communitycommons" + File.separator + "antisamy" - + File.separator + "antisamy-" + policyString + "-1.4.4.xml"; - - AntiSamy as = new AntiSamy(); // Create AntiSamy object - Policy p = Policy.getInstance(filename); - try { - CleanResults cr = as.scan(html, p, AntiSamy.SAX); - return cr.getCleanHTML(); - } catch (Exception e) { - throw new Exception("Unable to perform XSS sanitization: " - + e.getMessage(), e); - } - } - - private static final String ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static final String ALPHA = "abcdefghijklmnopqrstuvwxyz"; - private static final String NUM = "0123456789"; - private static final String SPL_CHARS = "!@#$%^&*_=+-/"; - /** - * Returns a random strong password containing at least one number, lowercase character, uppercase character and strange character - * @param length - * @return - */ - public static String randomStrongPassword(int minLen, int maxLen, int noOfCAPSAlpha, - int noOfDigits, int noOfSplChars) { - if(minLen > maxLen) - throw new IllegalArgumentException("Min. Length > Max. Length!"); - if( (noOfCAPSAlpha + noOfDigits + noOfSplChars) > minLen ) - throw new IllegalArgumentException - ("Min. Length should be atleast sum of (CAPS, DIGITS, SPL CHARS) Length!"); - Random rnd = new Random(); - int len = rnd.nextInt(maxLen - minLen + 1) + minLen; - char[] pswd = new char[len]; - int index = 0; - for (int i = 0; i < noOfCAPSAlpha; i++) { - index = getNextIndex(rnd, len, pswd); - pswd[index] = ALPHA_CAPS.charAt(rnd.nextInt(ALPHA_CAPS.length())); - } - for (int i = 0; i < noOfDigits; i++) { - index = getNextIndex(rnd, len, pswd); - pswd[index] = NUM.charAt(rnd.nextInt(NUM.length())); - } - for (int i = 0; i < noOfSplChars; i++) { - index = getNextIndex(rnd, len, pswd); - pswd[index] = SPL_CHARS.charAt(rnd.nextInt(SPL_CHARS.length())); - } - for(int i = 0; i < len; i++) { - if(pswd[i] == 0) { - pswd[i] = ALPHA.charAt(rnd.nextInt(ALPHA.length())); - } - } - return String.valueOf(pswd); - } - private static int getNextIndex(Random rnd, int len, char[] pswd) { - int index = rnd.nextInt(len); - while(pswd[index = rnd.nextInt(len)] != 0); - return index; - } - - public static String encryptString(String key, String valueToEncrypt) throws Exception - { - if (valueToEncrypt == null) - return null; - if (key == null) - throw new MendixRuntimeException("Key should not be empty"); - if (key.length() != 16) - throw new MendixRuntimeException("Key length should be 16"); - Cipher c = Cipher.getInstance("AES/CBC/PKCS5PADDING"); - SecretKeySpec k = new SecretKeySpec(key.getBytes(), "AES"); - c.init(Cipher.ENCRYPT_MODE, k); - byte[] encryptedData = c.doFinal(valueToEncrypt.getBytes()); - byte[] iv = c.getIV(); - - return new String(Base64.encodeBase64(iv)) + ";" + new String(Base64.encodeBase64(encryptedData)); - } - - public static String decryptString(String key, String valueToDecrypt) throws Exception - { - if (valueToDecrypt == null) - return null; - if (key == null) - throw new MendixRuntimeException("Key should not be empty"); - if (key.length() != 16) - throw new MendixRuntimeException("Key length should be 16"); - Cipher c = Cipher.getInstance("AES/CBC/PKCS5PADDING"); - SecretKeySpec k = new SecretKeySpec(key.getBytes(), "AES"); - String[] s = valueToDecrypt.split(";"); - if (s.length < 2) //Not an encrypted string, just return the original value. - return valueToDecrypt; - byte[] iv = Base64.decodeBase64(s[0].getBytes()); - byte[] encryptedData = Base64.decodeBase64(s[1].getBytes()); - c.init(Cipher.DECRYPT_MODE, k, new IvParameterSpec(iv)); - return new String(c.doFinal(encryptedData)); - } - - public static String generateHmacSha256Hash(String key, String valueToEncrypt) - { - try { - SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(secretKey); - mac.update(valueToEncrypt.getBytes("UTF-8")); - byte[] hmacData = mac.doFinal(); - - return new String(Base64.encodeBase64(hmacData)); - } - catch (Exception e) { - throw new RuntimeException("CommunityCommons::EncodeHmacSha256::Unable to encode: " + e.getMessage(), e); - } - } - - public static String escapeHTML(String input) { - return input.replace("\"", """) - .replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("'", "'");// notice this one: for xml "'" would be "'" (http://blogs.msdn.com/b/kirillosenkov/archive/2010/03/19/apos-is-in-xml-in-html-use-39.aspx) - // OWASP also advises to escape "/" but give no convincing reason why: https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet - } - - public static String regexQuote(String unquotedLiteral) { - return Pattern.quote(unquotedLiteral); - } -} +package communitycommons; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Random; +import java.util.UUID; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.html.HTML; +import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.html.parser.ParserDelegator; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.mendix.core.Core; +import com.mendix.systemwideinterfaces.MendixRuntimeException; +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.IMendixObject; + +import communitycommons.proxies.SanitizerPolicy; +import static communitycommons.proxies.SanitizerPolicy.BLOCKS; +import static communitycommons.proxies.SanitizerPolicy.FORMATTING; +import static communitycommons.proxies.SanitizerPolicy.IMAGES; +import static communitycommons.proxies.SanitizerPolicy.LINKS; +import static communitycommons.proxies.SanitizerPolicy.STYLES; +import static communitycommons.proxies.SanitizerPolicy.TABLES; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; + +import java.util.Base64; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringEscapeUtils; +import org.owasp.html.PolicyFactory; +import org.owasp.html.Sanitizers; + +import system.proxies.FileDocument; + +public class StringUtils +{ + + static final Map SANITIZER_POLICIES = new ImmutableMap.Builder() + .put(BLOCKS.name(), Sanitizers.BLOCKS) + .put(FORMATTING.name(), Sanitizers.FORMATTING) + .put(IMAGES.name(), Sanitizers.IMAGES) + .put(LINKS.name(), Sanitizers.LINKS) + .put(STYLES.name(), Sanitizers.STYLES) + .put(TABLES.name(), Sanitizers.TABLES) + .build(); + + public static final String HASH_ALGORITHM = "SHA-256"; + + public static String hash(String value, int length) throws NoSuchAlgorithmException, DigestException + { + byte[] inBytes = value.getBytes(StandardCharsets.UTF_8); + byte[] outBytes = new byte[length]; + + MessageDigest alg=MessageDigest.getInstance(HASH_ALGORITHM); + alg.update(inBytes); + + alg.digest(outBytes, 0, length); + + StringBuffer hexString = new StringBuffer(); + for (int i = 0; i < outBytes.length; i++) { + String hex = Integer.toHexString(0xff & outBytes[i]); + if(hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + + return hexString.toString(); + } + + public static String regexReplaceAll(String haystack, String needleRegex, + String replacement) + { + Pattern pattern = Pattern.compile(needleRegex); + Matcher matcher = pattern.matcher(haystack); + return matcher.replaceAll(replacement); + } + + public static boolean regexTest(String value, String regex) + { + return Pattern.matches(regex, value); + } + + public static String leftPad(String value, Long amount, String fillCharacter) + { + if (fillCharacter == null || fillCharacter.length() == 0) { + return org.apache.commons.lang3.StringUtils.leftPad(value, amount.intValue(), " "); + } + return org.apache.commons.lang3.StringUtils.leftPad(value, amount.intValue(), fillCharacter); + } + + public static String rightPad(String value, Long amount, String fillCharacter) + { + if (fillCharacter == null || fillCharacter.length() == 0) { + return org.apache.commons.lang3.StringUtils.rightPad(value, amount.intValue(), " "); + } + return org.apache.commons.lang3.StringUtils.rightPad(value, amount.intValue(), fillCharacter); + } + + public static String randomString(int length) + { + return org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(length); + } + + public static String substituteTemplate(final IContext context, String template, + final IMendixObject substitute, final boolean HTMLEncode, final String datetimeformat) { + return regexReplaceAll(template, "\\{(@)?([\\w./]+)\\}", new Function() { + + @Override + public String apply(MatchResult match) + { + String value; + String path = match.group(2); + if (match.group(1) != null) + value = String.valueOf(Core.getConfiguration().getConstantValue(path)); + else { + try + { + value = ORM.getValueOfPath(context, substitute, path, datetimeformat); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + return HTMLEncode ? HTMLEncode(value) : value; + } + + }); + } + + public static String regexReplaceAll(String source, String regexString, Function replaceFunction) { + if (source == null || source.trim().isEmpty()) // avoid NPE's, save CPU + return ""; + + StringBuffer resultString = new StringBuffer(); + Pattern regex = Pattern.compile(regexString); + Matcher regexMatcher = regex.matcher(source); + + while (regexMatcher.find()) { + MatchResult match = regexMatcher.toMatchResult(); + String value = replaceFunction.apply(match); + regexMatcher.appendReplacement(resultString, Matcher.quoteReplacement(value)); + } + regexMatcher.appendTail(resultString); + + return resultString.toString(); + } + + public static String HTMLEncode(String value) + { + return StringEscapeUtils.escapeHtml4(value); + } + + public static String randomHash() + { + return UUID.randomUUID().toString(); + } + + public static String base64Decode(String encoded) + { + if (encoded == null) + return null; + return new String(Base64.getDecoder().decode(encoded.getBytes())); + } + + public static void base64DecodeToFile(IContext context, String encoded, FileDocument targetFile) throws Exception + { + if (targetFile == null) + throw new IllegalArgumentException("Source file is null"); + if (encoded == null) + throw new IllegalArgumentException("Source data is null"); + + byte [] decoded = Base64.getDecoder().decode(encoded.getBytes()); + + try ( + ByteArrayInputStream bais = new ByteArrayInputStream(decoded); + ) { + Core.storeFileDocumentContent(context, targetFile.getMendixObject(), bais); + } + } + + public static String base64Encode(String value) + { + if (value == null) + return null; + return Base64.getEncoder().encodeToString(value.getBytes()); + } + + public static String base64EncodeFile(IContext context, FileDocument file) throws IOException + { + if (file == null) + throw new IllegalArgumentException("Source file is null"); + if (!file.getHasContents()) + throw new IllegalArgumentException("Source file has no contents!"); + + try ( + InputStream f = Core.getFileDocumentContent(context, file.getMendixObject()) + ) { + return Base64.getEncoder().encodeToString(IOUtils.toByteArray(f)); + } + } + + public static String stringFromFile(IContext context, FileDocument source) throws IOException + { + if (source == null) + return null; + try ( + InputStream f = Core.getFileDocumentContent(context, source.getMendixObject()); + ) { + return IOUtils.toString(f, StandardCharsets.UTF_8); + } + } + + public static void stringToFile(IContext context, String value, FileDocument destination) throws IOException + { + if (destination == null) + throw new IllegalArgumentException("Destination file is null"); + if (value == null) + throw new IllegalArgumentException("Value to write is null"); + + try ( + InputStream is = IOUtils.toInputStream(value, StandardCharsets.UTF_8) + ) { + Core.storeFileDocumentContent(context, destination.getMendixObject(), is); + } + } + + public static String HTMLToPlainText(String html) throws IOException + { + if (html == null) + return ""; + final StringBuffer result = new StringBuffer(); + + HTMLEditorKit.ParserCallback callback = + new HTMLEditorKit.ParserCallback () { + @Override + public void handleText(char[] data, int pos) { + result.append(data); //TODO: needds to be html entity decode? + } + + @Override + public void handleComment(char[] data, int pos) { + //Do nothing + } + + @Override + public void handleError(String errorMsg, int pos) { + //Do nothing + } + + @Override + public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet a, int pos) { + if (tag == HTML.Tag.BR) + result.append("\r\n"); + } + + @Override + public void handleEndTag(HTML.Tag tag, int pos){ + if (tag == HTML.Tag.P) + result.append("\r\n"); + } + }; + + new ParserDelegator().parse(new StringReader(html), callback, true); + + return result.toString(); + } + + public static String sanitizeHTML(String html, List policyParams) { + PolicyFactory policyFactory = null; + + for (SanitizerPolicy param : policyParams) { + policyFactory = (policyFactory == null) ? SANITIZER_POLICIES.get(param.name()) : policyFactory.and(SANITIZER_POLICIES.get(param.name())); + } + + return sanitizeHTML(html, policyFactory); + } + + public static String sanitizeHTML(String html, PolicyFactory policyFactory) { + return policyFactory.sanitize(html); + } + + private static final String ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String ALPHA = "abcdefghijklmnopqrstuvwxyz"; + private static final String NUM = "0123456789"; + private static final String SPL_CHARS = "!@#$%^&*_=+-/"; + /** + * Returns a random strong password containing at least one number, lowercase character, uppercase character and strange character + * @param length + * @return + */ + public static String randomStrongPassword(int minLen, int maxLen, int noOfCAPSAlpha, + int noOfDigits, int noOfSplChars) { + if(minLen > maxLen) + throw new IllegalArgumentException("Min. Length > Max. Length!"); + if( (noOfCAPSAlpha + noOfDigits + noOfSplChars) > minLen ) + throw new IllegalArgumentException + ("Min. Length should be atleast sum of (CAPS, DIGITS, SPL CHARS) Length!"); + Random rnd = new Random(); + int len = rnd.nextInt(maxLen - minLen + 1) + minLen; + char[] pswd = new char[len]; + int index = 0; + for (int i = 0; i < noOfCAPSAlpha; i++) { + index = getNextIndex(rnd, len, pswd); + pswd[index] = ALPHA_CAPS.charAt(rnd.nextInt(ALPHA_CAPS.length())); + } + for (int i = 0; i < noOfDigits; i++) { + index = getNextIndex(rnd, len, pswd); + pswd[index] = NUM.charAt(rnd.nextInt(NUM.length())); + } + for (int i = 0; i < noOfSplChars; i++) { + index = getNextIndex(rnd, len, pswd); + pswd[index] = SPL_CHARS.charAt(rnd.nextInt(SPL_CHARS.length())); + } + for(int i = 0; i < len; i++) { + if(pswd[i] == 0) { + pswd[i] = ALPHA.charAt(rnd.nextInt(ALPHA.length())); + } + } + return String.valueOf(pswd); + } + private static int getNextIndex(Random rnd, int len, char[] pswd) { + int index = rnd.nextInt(len); + while(pswd[index = rnd.nextInt(len)] != 0); + return index; + } + + public static String encryptString(String key, String valueToEncrypt) throws Exception + { + if (valueToEncrypt == null) + return null; + if (key == null) + throw new MendixRuntimeException("Key should not be empty"); + if (key.length() != 16) + throw new MendixRuntimeException("Key length should be 16"); + Cipher c = Cipher.getInstance("AES/CBC/PKCS5PADDING"); + SecretKeySpec k = new SecretKeySpec(key.getBytes(), "AES"); + c.init(Cipher.ENCRYPT_MODE, k); + byte[] encryptedData = c.doFinal(valueToEncrypt.getBytes()); + byte[] iv = c.getIV(); + + return Base64.getEncoder().encodeToString(iv) + ";" + Base64.getEncoder().encodeToString(encryptedData); + } + + public static String decryptString(String key, String valueToDecrypt) throws Exception + { + if (valueToDecrypt == null) + return null; + if (key == null) + throw new MendixRuntimeException("Key should not be empty"); + if (key.length() != 16) + throw new MendixRuntimeException("Key length should be 16"); + Cipher c = Cipher.getInstance("AES/CBC/PKCS5PADDING"); + SecretKeySpec k = new SecretKeySpec(key.getBytes(), "AES"); + String[] s = valueToDecrypt.split(";"); + if (s.length < 2) //Not an encrypted string, just return the original value. + return valueToDecrypt; + byte[] iv = Base64.getDecoder().decode(s[0].getBytes()); + byte[] encryptedData = Base64.getDecoder().decode(s[1].getBytes()); + c.init(Cipher.DECRYPT_MODE, k, new IvParameterSpec(iv)); + return new String(c.doFinal(encryptedData)); + } + + public static String generateHmacSha256Hash(String key, String valueToEncrypt) + { + try { + SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(secretKey); + mac.update(valueToEncrypt.getBytes("UTF-8")); + byte[] hmacData = mac.doFinal(); + + return Base64.getEncoder().encodeToString(hmacData); + } + catch (UnsupportedEncodingException | IllegalStateException | InvalidKeyException | NoSuchAlgorithmException e) { + throw new RuntimeException("CommunityCommons::EncodeHmacSha256::Unable to encode: " + e.getMessage(), e); + } + } + + public static String escapeHTML(String input) { + return input.replace("\"", """) + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("'", "'");// notice this one: for xml "'" would be "'" (http://blogs.msdn.com/b/kirillosenkov/archive/2010/03/19/apos-is-in-xml-in-html-use-39.aspx) + // OWASP also advises to escape "/" but give no convincing reason why: https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet + } + + public static String regexQuote(String unquotedLiteral) { + return Pattern.quote(unquotedLiteral); + } + + public static String substringBefore(String str, String separator) { + return org.apache.commons.lang3.StringUtils.substringBefore(str, separator); + } + + public static String substringBeforeLast(String str, String separator) { + return org.apache.commons.lang3.StringUtils.substringBeforeLast(str, separator); + } + + public static String substringAfter(String str, String separator) { + return org.apache.commons.lang3.StringUtils.substringAfter(str, separator); + } + + public static String substringAfterLast(String str, String separator) { + return org.apache.commons.lang3.StringUtils.substringAfterLast(str, separator); + } +} diff --git a/javasource/communitycommons/UserThrownException.java b/javasource/communitycommons/UserThrownException.java index f15069f..5c67787 100644 --- a/javasource/communitycommons/UserThrownException.java +++ b/javasource/communitycommons/UserThrownException.java @@ -1,15 +1,15 @@ -package communitycommons; - -public class UserThrownException extends Exception -{ - /** - * - */ - private static final long serialVersionUID = -55911261625752858L; - - public UserThrownException(String arg0) - { - super(arg0); - } - -} +package communitycommons; + +public class UserThrownException extends Exception +{ + /** + * + */ + private static final long serialVersionUID = -55911261625752858L; + + public UserThrownException(String arg0) + { + super(arg0); + } + +} diff --git a/javasource/communitycommons/XPath.java b/javasource/communitycommons/XPath.java index ba57659..7d4f72c 100644 --- a/javasource/communitycommons/XPath.java +++ b/javasource/communitycommons/XPath.java @@ -1,822 +1,831 @@ -package communitycommons; - -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 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().getSudoContext(); - 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 communitycommons; + +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 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; + } + + public XPath gt(Object attr, Object valuecomparison) { + return compare(attr,">=", valuecomparison); + } + + public XPath gt(Object... pathAndValue) { + assertEven(pathAndValue); + return compare(Arrays.copyOfRange(pathAndValue, 0, pathAndValue.length -1), ">=", pathAndValue[pathAndValue.length -1 ]); + } + + 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/communitycommons/actions/CreateLogNode.java b/javasource/communitycommons/actions/CreateLogNode.java new file mode 100644 index 0000000..7a947c0 --- /dev/null +++ b/javasource/communitycommons/actions/CreateLogNode.java @@ -0,0 +1,49 @@ +// This file was generated by Mendix Modeler. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package communitycommons.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.webui.CustomJavaAction; +import communitycommons.Logging; + +/** + * + */ +public class CreateLogNode extends CustomJavaAction +{ + private String LogNode; + + public CreateLogNode(IContext context, String LogNode) + { + super(context); + this.LogNode = LogNode; + } + + @Override + public Boolean executeAction() throws Exception + { + // BEGIN USER CODE + Logging.createLogNode(LogNode); + return true; + // END USER CODE + } + + /** + * Returns a string representation of this action + */ + @Override + public String toString() + { + return "CreateLogNode"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/javasource/communitycommons/actions/EndTransaction.java b/javasource/communitycommons/actions/EndTransaction.java index 663a165..1aeca9e 100644 --- a/javasource/communitycommons/actions/EndTransaction.java +++ b/javasource/communitycommons/actions/EndTransaction.java @@ -13,7 +13,7 @@ import com.mendix.webui.CustomJavaAction; /** - * Ends the current transaction. + * Commit the transaction, this will end this transaction or remove a save point from the queue if the transaction is nested */ public class EndTransaction extends CustomJavaAction { diff --git a/javasource/communitycommons/actions/FileDocumentFromFile.java b/javasource/communitycommons/actions/FileDocumentFromFile.java index 64d6c30..5c0ab01 100644 --- a/javasource/communitycommons/actions/FileDocumentFromFile.java +++ b/javasource/communitycommons/actions/FileDocumentFromFile.java @@ -38,10 +38,13 @@ public Boolean executeAction() throws Exception this.fileDocument = __fileDocument == null ? null : system.proxies.FileDocument.initialize(getContext(), __fileDocument); // BEGIN USER CODE - FileInputStream fis = new FileInputStream(new File(this.file)); - Core.storeFileDocumentContent(getContext(), fileDocument.getMendixObject(), + try ( + FileInputStream fis = new FileInputStream(new File(this.file)) + ) { + Core.storeFileDocumentContent(getContext(), fileDocument.getMendixObject(), this.file, fis); - fis.close(); + } + return true; // END USER CODE } diff --git a/javasource/communitycommons/actions/FileFromFileDocument.java b/javasource/communitycommons/actions/FileFromFileDocument.java index f6c172a..ccd1c06 100644 --- a/javasource/communitycommons/actions/FileFromFileDocument.java +++ b/javasource/communitycommons/actions/FileFromFileDocument.java @@ -41,12 +41,14 @@ public Boolean executeAction() throws Exception // BEGIN USER CODE File output = new File(targetFile); - FileOutputStream fos = new FileOutputStream(output); - InputStream is = Core.getFileDocumentContent(getContext(), - fileDocument.getMendixObject()); - IOUtils.copy(is, fos); - fos.close(); - is.close(); + + try ( + FileOutputStream fos = new FileOutputStream(output); + InputStream is = Core.getFileDocumentContent(getContext(), fileDocument.getMendixObject()); + ) { + IOUtils.copy(is, fos); + } + return true; // END USER CODE } diff --git a/javasource/communitycommons/actions/GetFileContentsFromResource.java b/javasource/communitycommons/actions/GetFileContentsFromResource.java index b29ae66..ecdc53e 100644 --- a/javasource/communitycommons/actions/GetFileContentsFromResource.java +++ b/javasource/communitycommons/actions/GetFileContentsFromResource.java @@ -41,9 +41,11 @@ public Boolean executeAction() throws Exception File myFile = new File(Core.getConfiguration().getResourcesPath() + File.separator + filename); - FileInputStream fis = new FileInputStream(myFile); - Core.storeFileDocumentContent(getContext(), fileDocument.getMendixObject(), - filename, fis); + try ( + FileInputStream fis = new FileInputStream(myFile) + ) { + Core.storeFileDocumentContent(getContext(), fileDocument.getMendixObject(), filename, fis); + } return true; // END USER CODE diff --git a/javasource/communitycommons/actions/GetImageDimensions.java b/javasource/communitycommons/actions/GetImageDimensions.java new file mode 100644 index 0000000..c88170c --- /dev/null +++ b/javasource/communitycommons/actions/GetImageDimensions.java @@ -0,0 +1,59 @@ +// This file was generated by Mendix Modeler. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package communitycommons.actions; + +import java.awt.image.BufferedImage; +import javax.imageio.ImageIO; +import com.mendix.core.Core; +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.IMendixObject; +import com.mendix.webui.CustomJavaAction; +import communitycommons.proxies.ImageDimensions; + +/** + * + */ +public class GetImageDimensions extends CustomJavaAction +{ + private IMendixObject __ImageParameter1; + private system.proxies.Image ImageParameter1; + + public GetImageDimensions(IContext context, IMendixObject ImageParameter1) + { + super(context); + this.__ImageParameter1 = ImageParameter1; + } + + @Override + public IMendixObject executeAction() throws Exception + { + this.ImageParameter1 = __ImageParameter1 == null ? null : system.proxies.Image.initialize(getContext(), __ImageParameter1); + + // BEGIN USER CODE + BufferedImage bimg = ImageIO.read(Core.getImage(getContext(), this.ImageParameter1.getMendixObject(), false)); + ImageDimensions imageDimentions = new ImageDimensions(getContext()); + imageDimentions.setHeight(bimg.getHeight()); + imageDimentions.setWidth(bimg.getWidth()); + return imageDimentions.getMendixObject(); + // END USER CODE + } + + /** + * Returns a string representation of this action + */ + @Override + public String toString() + { + return "GetImageDimensions"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/javasource/communitycommons/actions/MergeMultiplePdfs.java b/javasource/communitycommons/actions/MergeMultiplePdfs.java index eb9e39b..d323e88 100644 --- a/javasource/communitycommons/actions/MergeMultiplePdfs.java +++ b/javasource/communitycommons/actions/MergeMultiplePdfs.java @@ -15,7 +15,7 @@ import communitycommons.Misc; /** - * + * Restricted to 10 files at once for Mendix Cloud v4 compatibility. If you need to merge more than 10 files at once merge recursively instead or change the MergeMultiplePdfs_MaxAtOnce constant. */ public class MergeMultiplePdfs extends CustomJavaAction { @@ -42,7 +42,7 @@ public Boolean executeAction() throws Exception this.MergedDocument = __MergedDocument == null ? null : system.proxies.FileDocument.initialize(getContext(), __MergedDocument); // BEGIN USER CODE - return Misc.mergePDF(this.getContext(), this.FilesToMerge, __MergedDocument); + return Misc.mergePDF(this.getContext(), this.FilesToMerge, this.MergedDocument.getMendixObject()); // END USER CODE } diff --git a/javasource/communitycommons/actions/MonthsBetween.java b/javasource/communitycommons/actions/MonthsBetween.java new file mode 100644 index 0000000..c9b12f5 --- /dev/null +++ b/javasource/communitycommons/actions/MonthsBetween.java @@ -0,0 +1,62 @@ +// This file was generated by Mendix Modeler. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package communitycommons.actions; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.webui.CustomJavaAction; + +/** + * + */ +public class MonthsBetween extends CustomJavaAction +{ + private java.util.Date date1; + private java.util.Date date2; + + public MonthsBetween(IContext context, java.util.Date date1, java.util.Date date2) + { + super(context); + this.date1 = date1; + this.date2 = date2; + } + + @Override + public Long executeAction() throws Exception + { + // BEGIN USER CODE + LocalDate localDate1 = date1.toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDate(); + LocalDate localDate2 = date2.toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDate(); + YearMonth m1 = YearMonth.from(localDate1); + YearMonth m2 = YearMonth.from(localDate2); + + return m1.until(m2, ChronoUnit.MONTHS) + 1; + // END USER CODE + } + + /** + * Returns a string representation of this action + */ + @Override + public String toString() + { + return "MonthsBetween"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/javasource/communitycommons/actions/OverlayPdfDocument.java b/javasource/communitycommons/actions/OverlayPdfDocument.java index f335574..9e59a33 100644 --- a/javasource/communitycommons/actions/OverlayPdfDocument.java +++ b/javasource/communitycommons/actions/OverlayPdfDocument.java @@ -23,12 +23,14 @@ public class OverlayPdfDocument extends CustomJavaAction private system.proxies.FileDocument generatedDocument; private IMendixObject __overlay; private system.proxies.FileDocument overlay; + private Boolean onTopOfContent; - public OverlayPdfDocument(IContext context, IMendixObject generatedDocument, IMendixObject overlay) + public OverlayPdfDocument(IContext context, IMendixObject generatedDocument, IMendixObject overlay, Boolean onTopOfContent) { super(context); this.__generatedDocument = generatedDocument; this.__overlay = overlay; + this.onTopOfContent = onTopOfContent; } @Override @@ -39,7 +41,7 @@ public Boolean executeAction() throws Exception this.overlay = __overlay == null ? null : system.proxies.FileDocument.initialize(getContext(), __overlay); // BEGIN USER CODE - return Misc.overlayPdf(getContext(), __generatedDocument, __overlay); + return Misc.overlayPdf(getContext(), __generatedDocument, __overlay, onTopOfContent); // END USER CODE } diff --git a/javasource/communitycommons/actions/StartTransaction.java b/javasource/communitycommons/actions/StartTransaction.java index 5f9ae7e..6e1b299 100644 --- a/javasource/communitycommons/actions/StartTransaction.java +++ b/javasource/communitycommons/actions/StartTransaction.java @@ -13,7 +13,7 @@ import com.mendix.webui.CustomJavaAction; /** - * Starts a new transaction. + * Start a transaction, if a transaction is already started for this context, a savepoint will be added */ public class StartTransaction extends CustomJavaAction { diff --git a/javasource/communitycommons/actions/SubstringAfter.java b/javasource/communitycommons/actions/SubstringAfter.java new file mode 100644 index 0000000..4459f32 --- /dev/null +++ b/javasource/communitycommons/actions/SubstringAfter.java @@ -0,0 +1,50 @@ +// This file was generated by Mendix Modeler. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package communitycommons.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.webui.CustomJavaAction; +import communitycommons.StringUtils; + +/** + * Gets the substring after the first occurrence of a separator. + */ +public class SubstringAfter extends CustomJavaAction +{ + private String str; + private String separator; + + public SubstringAfter(IContext context, String str, String separator) + { + super(context); + this.str = str; + this.separator = separator; + } + + @Override + public String executeAction() throws Exception + { + // BEGIN USER CODE + return StringUtils.substringAfter(str, separator); + // END USER CODE + } + + /** + * Returns a string representation of this action + */ + @Override + public String toString() + { + return "SubstringAfter"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/javasource/communitycommons/actions/SubstringAfterLast.java b/javasource/communitycommons/actions/SubstringAfterLast.java new file mode 100644 index 0000000..5f0ea60 --- /dev/null +++ b/javasource/communitycommons/actions/SubstringAfterLast.java @@ -0,0 +1,50 @@ +// This file was generated by Mendix Modeler. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package communitycommons.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.webui.CustomJavaAction; +import communitycommons.StringUtils; + +/** + * Gets the substring after the last occurrence of a separator. + */ +public class SubstringAfterLast extends CustomJavaAction +{ + private String str; + private String separator; + + public SubstringAfterLast(IContext context, String str, String separator) + { + super(context); + this.str = str; + this.separator = separator; + } + + @Override + public String executeAction() throws Exception + { + // BEGIN USER CODE + return StringUtils.substringAfterLast(str, separator); + // END USER CODE + } + + /** + * Returns a string representation of this action + */ + @Override + public String toString() + { + return "SubstringAfterLast"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/javasource/communitycommons/actions/SubstringBefore.java b/javasource/communitycommons/actions/SubstringBefore.java new file mode 100644 index 0000000..108397a --- /dev/null +++ b/javasource/communitycommons/actions/SubstringBefore.java @@ -0,0 +1,50 @@ +// This file was generated by Mendix Modeler. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package communitycommons.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.webui.CustomJavaAction; +import communitycommons.StringUtils; + +/** + * Gets the substring before the first occurrence of a separator. + */ +public class SubstringBefore extends CustomJavaAction +{ + private String str; + private String separator; + + public SubstringBefore(IContext context, String str, String separator) + { + super(context); + this.str = str; + this.separator = separator; + } + + @Override + public String executeAction() throws Exception + { + // BEGIN USER CODE + return StringUtils.substringBefore(str, separator); + // END USER CODE + } + + /** + * Returns a string representation of this action + */ + @Override + public String toString() + { + return "SubstringBefore"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/javasource/communitycommons/actions/SubstringBeforeLast.java b/javasource/communitycommons/actions/SubstringBeforeLast.java new file mode 100644 index 0000000..8ea3b00 --- /dev/null +++ b/javasource/communitycommons/actions/SubstringBeforeLast.java @@ -0,0 +1,50 @@ +// This file was generated by Mendix Modeler. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package communitycommons.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.webui.CustomJavaAction; +import communitycommons.StringUtils; + +/** + * Gets the substring before the last occurrence of a separator. + */ +public class SubstringBeforeLast extends CustomJavaAction +{ + private String str; + private String separator; + + public SubstringBeforeLast(IContext context, String str, String separator) + { + super(context); + this.str = str; + this.separator = separator; + } + + @Override + public String executeAction() throws Exception + { + // BEGIN USER CODE + return StringUtils.substringBeforeLast(str, separator); + // END USER CODE + } + + /** + * Returns a string representation of this action + */ + @Override + public String toString() + { + return "SubstringBeforeLast"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/javasource/communitycommons/actions/XSSSanitize.java b/javasource/communitycommons/actions/XSSSanitize.java index 6877e30..3617621 100644 --- a/javasource/communitycommons/actions/XSSSanitize.java +++ b/javasource/communitycommons/actions/XSSSanitize.java @@ -9,9 +9,15 @@ package communitycommons.actions; -import communitycommons.StringUtils; +import com.google.common.collect.Lists; +import com.mendix.systemwideinterfaces.MendixRuntimeException; +import org.apache.commons.lang3.StringUtils; import com.mendix.systemwideinterfaces.core.IContext; import com.mendix.webui.CustomJavaAction; +import communitycommons.proxies.SanitizerPolicy; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; /** * Removes all potiential dangerous HTML from a string so that it can be safely displayed in a browser. @@ -19,39 +25,59 @@ * This function should be applied to all HTML which is displayed in the browser, and can be entered by (untrusted) users. * * - HTML: The html to sanitize - * - policy: The policy that defines the allowed HTML tags a user is allowd to use: - * - * (see the developers guide in the resources folder for more details about the policies) - * - * TinyMCE: Based on the HTML WYSIWYG editor, relatively safe. This policy file only allows text formatting, and may be a good choice if users are submitting HTML to be used in a blog post. - * - * Allow anything: A very dangerous policy file, this will allow all HTML, CSS and JavaScript. You shouldn't use this in production. - * - * Ebay: Based on the content filtering for the popular electronic auction website, relatively safe. This policy file gives the user a little bit of freedom, and may be a good choice if users are submitting HTML for a large portion of a page. - * - * MySpace: Based on the content filtering for the popular social networking site, relatively dangerous. This policy file gives the user a lot of freedom, and may be a good choice if users are submitting HTML for an entire page. - * - * Slashdot: Based on the comment filtering on the popular news site, but not quite as strict. This policy file only allows strict text formatting, and may be a good choice if users are submitting HTML in a comment thread. - * - * BootstrapRTE: Based on TinyMCE and allows hyperlinks and embedded images. Basically allows what the Bootstrap Rich Text widget provides. + * - policy1... policy6: one or more values of SanitizerPolicy. You may leave these poliy parameters explicitly empty if you don't want to allow additional elements. (I.e. provide 'empty' without the quotes as parameter value) + * + * BLOCKS: Allows common block elements including

,

, etc. + * FORMATTING: Allows common formatting elements including , , etc. + * IMAGES: Allows elements from HTTP, HTTPS, and relative sources. + * LINKS: Allows HTTP, HTTPS, MAILTO and relative links + * STYLES: Allows certain safe CSS properties in style="..." attributes. + * TABLES: Allows commons table elements. + * + * For more information, visit: + * + * http://javadoc.io/doc/com.googlecode.owasp-java-html-sanitizer/owasp-java-html-sanitizer/20180219.1 */ public class XSSSanitize extends CustomJavaAction { private String html; - private communitycommons.proxies.XSSPolicy policy; + private communitycommons.proxies.SanitizerPolicy policy1; + private communitycommons.proxies.SanitizerPolicy policy2; + private communitycommons.proxies.SanitizerPolicy policy3; + private communitycommons.proxies.SanitizerPolicy policy4; + private communitycommons.proxies.SanitizerPolicy policy5; + private communitycommons.proxies.SanitizerPolicy policy6; - public XSSSanitize(IContext context, String html, String policy) + public XSSSanitize(IContext context, String html, String policy1, String policy2, String policy3, String policy4, String policy5, String policy6) { super(context); this.html = html; - this.policy = policy == null ? null : communitycommons.proxies.XSSPolicy.valueOf(policy); + this.policy1 = policy1 == null ? null : communitycommons.proxies.SanitizerPolicy.valueOf(policy1); + this.policy2 = policy2 == null ? null : communitycommons.proxies.SanitizerPolicy.valueOf(policy2); + this.policy3 = policy3 == null ? null : communitycommons.proxies.SanitizerPolicy.valueOf(policy3); + this.policy4 = policy4 == null ? null : communitycommons.proxies.SanitizerPolicy.valueOf(policy4); + this.policy5 = policy5 == null ? null : communitycommons.proxies.SanitizerPolicy.valueOf(policy5); + this.policy6 = policy6 == null ? null : communitycommons.proxies.SanitizerPolicy.valueOf(policy6); } @Override public String executeAction() throws Exception { // BEGIN USER CODE - return StringUtils.XSSSanitize(html, policy); + if (StringUtils.isEmpty(html)) { + return ""; + } + + List policyParams = Lists.newArrayList(policy1, policy2, policy3, policy4, policy5, policy6) + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (policyParams.isEmpty()) { + throw new MendixRuntimeException("At least one policy is required"); + } + + return communitycommons.StringUtils.sanitizeHTML(html, policyParams); // END USER CODE } diff --git a/javasource/restservices/RestServices.java b/javasource/restservices/RestServices.java index 0f73a84..e1b9aec 100644 --- a/javasource/restservices/RestServices.java +++ b/javasource/restservices/RestServices.java @@ -20,7 +20,7 @@ public class RestServices { /** * Version of the RestServices module */ - public static final String VERSION = "3.1.0"; + public static final String VERSION = "3.2.0"; /** * Amount of objects that are processed by the module at the same time. diff --git a/javasource/system/UserActionsRegistrar.java b/javasource/system/UserActionsRegistrar.java index b85e572..e92f19e 100644 --- a/javasource/system/UserActionsRegistrar.java +++ b/javasource/system/UserActionsRegistrar.java @@ -47,6 +47,7 @@ public void handleEvent(Event event) component.actionRegistry().registerUserAction(communitycommons.actions.commitInSeparateDatabaseTransaction.class); component.actionRegistry().registerUserAction(communitycommons.actions.commitWithoutEvents.class); component.actionRegistry().registerUserAction(communitycommons.actions.copyAttributes.class); + component.actionRegistry().registerUserAction(communitycommons.actions.CreateLogNode.class); component.actionRegistry().registerUserAction(communitycommons.actions.DateTimeToLong.class); component.actionRegistry().registerUserAction(communitycommons.actions.DecryptString.class); component.actionRegistry().registerUserAction(communitycommons.actions.DeepClone.class); @@ -72,6 +73,7 @@ public void handleEvent(Event event) component.actionRegistry().registerUserAction(communitycommons.actions.GetFileContentsFromResource.class); component.actionRegistry().registerUserAction(communitycommons.actions.getFileSize.class); component.actionRegistry().registerUserAction(communitycommons.actions.getGUID.class); + component.actionRegistry().registerUserAction(communitycommons.actions.GetImageDimensions.class); component.actionRegistry().registerUserAction(communitycommons.actions.GetIntFromDateTime.class); component.actionRegistry().registerUserAction(communitycommons.actions.getLastChangedByUser.class); component.actionRegistry().registerUserAction(communitycommons.actions.getLockOwner.class); @@ -86,6 +88,7 @@ public void handleEvent(Event event) component.actionRegistry().registerUserAction(communitycommons.actions.LongToDateTime.class); component.actionRegistry().registerUserAction(communitycommons.actions.memberHasChanged.class); component.actionRegistry().registerUserAction(communitycommons.actions.MergeMultiplePdfs.class); + component.actionRegistry().registerUserAction(communitycommons.actions.MonthsBetween.class); component.actionRegistry().registerUserAction(communitycommons.actions.objectHasChanged.class); component.actionRegistry().registerUserAction(communitycommons.actions.objectIsNew.class); component.actionRegistry().registerUserAction(communitycommons.actions.OverlayPdfDocument.class); @@ -114,6 +117,10 @@ public void handleEvent(Event event) component.actionRegistry().registerUserAction(communitycommons.actions.StringTrim.class); component.actionRegistry().registerUserAction(communitycommons.actions.SubstituteTemplate.class); component.actionRegistry().registerUserAction(communitycommons.actions.SubstituteTemplate2.class); + component.actionRegistry().registerUserAction(communitycommons.actions.SubstringAfter.class); + component.actionRegistry().registerUserAction(communitycommons.actions.SubstringAfterLast.class); + component.actionRegistry().registerUserAction(communitycommons.actions.SubstringBefore.class); + component.actionRegistry().registerUserAction(communitycommons.actions.SubstringBeforeLast.class); component.actionRegistry().registerUserAction(communitycommons.actions.ThrowException.class); component.actionRegistry().registerUserAction(communitycommons.actions.ThrowWebserviceException.class); component.actionRegistry().registerUserAction(communitycommons.actions.TimeMeasureEnd.class); diff --git a/userlib/antisamy-1.5.3.jar b/userlib/antisamy-1.5.3.jar deleted file mode 100644 index 97011b0..0000000 Binary files a/userlib/antisamy-1.5.3.jar and /dev/null differ diff --git a/userlib/com.google.guava-14.0.1.jar b/userlib/com.google.guava-14.0.1.jar deleted file mode 100644 index 3a3d925..0000000 Binary files a/userlib/com.google.guava-14.0.1.jar and /dev/null differ diff --git a/userlib/com.springsource.org.apache.batik.css-1.7.0.jar b/userlib/com.springsource.org.apache.batik.css-1.7.0.jar deleted file mode 100644 index db2d761..0000000 Binary files a/userlib/com.springsource.org.apache.batik.css-1.7.0.jar and /dev/null differ diff --git a/userlib/commons-fileupload-1.3.3.jar b/userlib/commons-fileupload-1.3.3.jar new file mode 100644 index 0000000..915d87e Binary files /dev/null and b/userlib/commons-fileupload-1.3.3.jar differ diff --git a/userlib/antisamy-1.5.3.jar.CommunityCommons.RequiredLib b/userlib/commons-fileupload-1.3.3.jar.RestServices.RequiredLib similarity index 100% rename from userlib/antisamy-1.5.3.jar.CommunityCommons.RequiredLib rename to userlib/commons-fileupload-1.3.3.jar.RestServices.RequiredLib diff --git a/userlib/commons-io-2.6.jar b/userlib/commons-io-2.6.jar new file mode 100644 index 0000000..00556b1 Binary files /dev/null and b/userlib/commons-io-2.6.jar differ diff --git a/userlib/com.google.guava-14.0.1.jar.CommunityCommons.RequiredLib b/userlib/commons-io-2.6.jar.CommunityCommons.RequiredLib similarity index 100% rename from userlib/com.google.guava-14.0.1.jar.CommunityCommons.RequiredLib rename to userlib/commons-io-2.6.jar.CommunityCommons.RequiredLib diff --git a/userlib/commons-lang3-3.7.jar b/userlib/commons-lang3-3.7.jar new file mode 100644 index 0000000..f37ded6 Binary files /dev/null and b/userlib/commons-lang3-3.7.jar differ diff --git a/userlib/com.springsource.org.apache.batik.css-1.7.0.jar.CommunityCommons.RequiredLib b/userlib/commons-lang3-3.7.jar.CommunityCommons.RequiredLib similarity index 100% rename from userlib/com.springsource.org.apache.batik.css-1.7.0.jar.CommunityCommons.RequiredLib rename to userlib/commons-lang3-3.7.jar.CommunityCommons.RequiredLib diff --git a/userlib/commons-logging-1.2.jar b/userlib/commons-logging-1.2.jar new file mode 100644 index 0000000..93a3b9f Binary files /dev/null and b/userlib/commons-logging-1.2.jar differ diff --git a/userlib/fontbox-1.8.5.jar.CommunityCommons.RequiredLib b/userlib/commons-logging-1.2.jar.CommunityCommons.RequiredLib similarity index 100% rename from userlib/fontbox-1.8.5.jar.CommunityCommons.RequiredLib rename to userlib/commons-logging-1.2.jar.CommunityCommons.RequiredLib diff --git a/userlib/fontbox-1.8.5.jar b/userlib/fontbox-1.8.5.jar deleted file mode 100644 index 0b93d88..0000000 Binary files a/userlib/fontbox-1.8.5.jar and /dev/null differ diff --git a/userlib/fontbox-2.0.11.jar b/userlib/fontbox-2.0.11.jar new file mode 100644 index 0000000..8f38ac6 Binary files /dev/null and b/userlib/fontbox-2.0.11.jar differ diff --git a/userlib/jempbox-1.8.5.jar.CommunityCommons.RequiredLib b/userlib/fontbox-2.0.11.jar.CommunityCommons.RequiredLib similarity index 100% rename from userlib/jempbox-1.8.5.jar.CommunityCommons.RequiredLib rename to userlib/fontbox-2.0.11.jar.CommunityCommons.RequiredLib diff --git a/userlib/guava-19.0.jar b/userlib/guava-19.0.jar new file mode 100644 index 0000000..b175ca8 Binary files /dev/null and b/userlib/guava-19.0.jar differ diff --git a/userlib/joda-time-1.6.2.jar.CommunityCommons.RequiredLib b/userlib/guava-19.0.jar.CommunityCommons.RequiredLib similarity index 100% rename from userlib/joda-time-1.6.2.jar.CommunityCommons.RequiredLib rename to userlib/guava-19.0.jar.CommunityCommons.RequiredLib diff --git a/userlib/jempbox-1.8.5.jar b/userlib/jempbox-1.8.5.jar deleted file mode 100644 index b4880d3..0000000 Binary files a/userlib/jempbox-1.8.5.jar and /dev/null differ diff --git a/userlib/joda-time-1.6.2.jar b/userlib/joda-time-1.6.2.jar deleted file mode 100644 index 9b045c3..0000000 Binary files a/userlib/joda-time-1.6.2.jar and /dev/null differ diff --git a/userlib/nekohtml.jar b/userlib/nekohtml.jar deleted file mode 100644 index 16261d2..0000000 Binary files a/userlib/nekohtml.jar and /dev/null differ diff --git a/userlib/nekohtml.txt b/userlib/nekohtml.txt deleted file mode 100644 index f433b1a..0000000 --- a/userlib/nekohtml.txt +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/userlib/org.apache.commons.fileupload-1.2.1.jar b/userlib/org.apache.commons.fileupload-1.2.1.jar deleted file mode 100644 index bd4cccd..0000000 Binary files a/userlib/org.apache.commons.fileupload-1.2.1.jar and /dev/null differ diff --git a/userlib/org.apache.commons.io-2.3.0.jar b/userlib/org.apache.commons.io-2.3.0.jar deleted file mode 100644 index 2b6deb4..0000000 Binary files a/userlib/org.apache.commons.io-2.3.0.jar and /dev/null differ diff --git a/userlib/org.apache.commons.lang3.jar b/userlib/org.apache.commons.lang3.jar deleted file mode 100644 index 5de04f9..0000000 Binary files a/userlib/org.apache.commons.lang3.jar and /dev/null differ diff --git a/userlib/org.apache.commons.lang3.jar.CommunityCommons.RequiredLib b/userlib/org.apache.commons.lang3.jar.CommunityCommons.RequiredLib deleted file mode 100644 index e69de29..0000000 diff --git a/userlib/org.apache.servicemix.bundles.commons-codec-1.3.0.jar.CommunityCommons.RequiredLib b/userlib/org.apache.servicemix.bundles.commons-codec-1.3.0.jar.CommunityCommons.RequiredLib deleted file mode 100644 index e69de29..0000000 diff --git a/userlib/nekohtml.jar.CommunityCommons.RequiredLib b/userlib/org.apache.servicemix.bundles.commons-codec-1.3.0.jar.RestServices.RequiredLib similarity index 100% rename from userlib/nekohtml.jar.CommunityCommons.RequiredLib rename to userlib/org.apache.servicemix.bundles.commons-codec-1.3.0.jar.RestServices.RequiredLib diff --git a/userlib/owasp-java-html-sanitizer-20180219.1.jar b/userlib/owasp-java-html-sanitizer-20180219.1.jar new file mode 100644 index 0000000..aca5797 Binary files /dev/null and b/userlib/owasp-java-html-sanitizer-20180219.1.jar differ diff --git a/userlib/org.apache.commons.fileupload-1.2.1.jar.CommunityCommons.RequiredLib b/userlib/owasp-java-html-sanitizer-20180219.1.jar.CommunityCommons.RequiredLib similarity index 100% rename from userlib/org.apache.commons.fileupload-1.2.1.jar.CommunityCommons.RequiredLib rename to userlib/owasp-java-html-sanitizer-20180219.1.jar.CommunityCommons.RequiredLib diff --git a/userlib/pdfbox-1.8.5.jar b/userlib/pdfbox-1.8.5.jar deleted file mode 100644 index db95cc1..0000000 Binary files a/userlib/pdfbox-1.8.5.jar and /dev/null differ diff --git a/userlib/pdfbox-1.8.5.jar.CommunityCommons.RequiredLib b/userlib/pdfbox-1.8.5.jar.CommunityCommons.RequiredLib deleted file mode 100644 index e69de29..0000000 diff --git a/userlib/pdfbox-2.0.11.jar b/userlib/pdfbox-2.0.11.jar new file mode 100644 index 0000000..4195d0e Binary files /dev/null and b/userlib/pdfbox-2.0.11.jar differ diff --git a/userlib/org.apache.commons.io-2.3.0.jar.CommunityCommons.RequiredLib b/userlib/pdfbox-2.0.11.jar.CommunityCommons.RequiredLib similarity index 100% rename from userlib/org.apache.commons.io-2.3.0.jar.CommunityCommons.RequiredLib rename to userlib/pdfbox-2.0.11.jar.CommunityCommons.RequiredLib diff --git a/userlib/xml-apis-ext.jar b/userlib/xml-apis-ext.jar deleted file mode 100644 index a7869d6..0000000 Binary files a/userlib/xml-apis-ext.jar and /dev/null differ diff --git a/userlib/xml-apis-ext.jar.CommunityCommons.RequiredLib b/userlib/xml-apis-ext.jar.CommunityCommons.RequiredLib deleted file mode 100644 index e69de29..0000000