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<String> toskip = Arrays.asList((membersToSkip + ",createdDate,changedDate").split(","));
List<String> tokeep = Arrays.asList((membersToKeep + ",System.owner,System.changedBy").split(","));
List<String> revAssoc = Arrays.asList(reverseAssociations.split(","));
List<String> skipEntities = Arrays.asList(excludeEntities.split(","));
List<String> skipModules = Arrays.asList(excludeModules.split(","));
Map<IMendixIdentifier, IMendixIdentifier> mappedIDs = new HashMap<IMendixIdentifier, IMendixIdentifier>();
duplicate(c, source, target, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedIDs);
}
private static void duplicate(IContext ctx, IMendixObject src, IMendixObject tar,
List<String> toskip, List<String> tokeep, List<String> revAssoc,
List<String> skipEntities, List<String> skipModules,
Map<IMendixIdentifier, IMendixIdentifier> mappedObjects) throws CoreException
{
mappedObjects.put(src.getId(), tar.getId());
Map<String, ? extends IMendixObjectMember<?>> 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<IMendixIdentifier> res = new ArrayList<IMendixIdentifier>();
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<String> toskip, List<String> tokeep, List<String> revAssoc,
List<String> skipEntities, List<String> skipModules,
Map<IMendixIdentifier, IMendixIdentifier> 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<String> toskip, List<String> tokeep, List<String> revAssocs,
List<String> skipEntities, List<String> skipModules,
Map<IMendixIdentifier, IMendixIdentifier> 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<IMendixObject> 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<IMendixIdentifier> 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<String, ? extends IMendixObjectMember<?>> 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<Long, ISession> locks = new ConcurrentHashMap<Long, ISession>();
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<IMendixObject> items = Core.retrieveXPathQuery(c, String.format("//%s[%s = '%s']", entityName, member, value), 1, 0, new HashMap<String, String>());
if (items == null || items.size() == 0)
return null;
return items.get(0);
}
public synchronized static void releaseOldLocks()
{
Set<ISession> activeSessions = new HashSet<ISession>(Core.getActiveSessions()); //Lookup with Ord(log(n)) instead of Ord(n).
List<Long> tbrm = new ArrayList<Long>();
for (Entry<Long, ISession> 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()));
}
}
}