|
| 1 | +package objecthandling; |
| 2 | + |
| 3 | +import java.text.SimpleDateFormat; |
| 4 | +import java.util.ArrayList; |
| 5 | +import java.util.Arrays; |
| 6 | +import java.util.Date; |
| 7 | +import java.util.HashMap; |
| 8 | +import java.util.List; |
| 9 | +import java.util.Map; |
| 10 | + |
| 11 | +import com.mendix.core.Core; |
| 12 | +import com.mendix.core.CoreException; |
| 13 | +import com.mendix.core.objectmanagement.member.MendixAutoNumber; |
| 14 | +import com.mendix.core.objectmanagement.member.MendixDateTime; |
| 15 | +import com.mendix.core.objectmanagement.member.MendixEnum; |
| 16 | +import com.mendix.core.objectmanagement.member.MendixObjectReference; |
| 17 | +import com.mendix.core.objectmanagement.member.MendixObjectReferenceSet; |
| 18 | +import com.mendix.systemwideinterfaces.core.IContext; |
| 19 | +import com.mendix.systemwideinterfaces.core.IMendixIdentifier; |
| 20 | +import com.mendix.systemwideinterfaces.core.IMendixObject; |
| 21 | +import com.mendix.systemwideinterfaces.core.IMendixObject.ObjectState; |
| 22 | +import com.mendix.systemwideinterfaces.core.IMendixObjectMember; |
| 23 | +import com.mendix.systemwideinterfaces.core.IMendixObjectMember.MemberState; |
| 24 | +import com.mendix.systemwideinterfaces.core.meta.IMetaAssociation; |
| 25 | +import com.mendix.systemwideinterfaces.core.meta.IMetaAssociation.AssociationType; |
| 26 | +import com.mendix.systemwideinterfaces.core.meta.IMetaEnumValue; |
| 27 | +import com.mendix.systemwideinterfaces.core.meta.IMetaEnumeration; |
| 28 | +import com.mendix.systemwideinterfaces.core.meta.IMetaObject; |
| 29 | +import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive; |
| 30 | +import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive.PrimitiveType; |
| 31 | + |
| 32 | +public class ORM |
| 33 | +{ |
| 34 | + |
| 35 | + public static Long getGUID(IMendixObject item) |
| 36 | + { |
| 37 | + return item.getId().toLong(); |
| 38 | + } |
| 39 | + |
| 40 | + public static String getOriginalValueAsString(IContext context, IMendixObject item, |
| 41 | + String member) |
| 42 | + { |
| 43 | + return String.valueOf(item.getMember(context, member).getOriginalValue(context)); |
| 44 | + } |
| 45 | + |
| 46 | + public static boolean objectHasChanged(IMendixObject anyobject) { |
| 47 | + if (anyobject == null) |
| 48 | + throw new IllegalArgumentException("The provided object is empty"); |
| 49 | + return anyobject.isChanged(); |
| 50 | + } |
| 51 | + |
| 52 | + /** |
| 53 | + * checks whether a certain member of an object has changed. If the objects itself is still new, we consider to be changes as well. |
| 54 | + * @param item |
| 55 | + * @param member |
| 56 | + * @param context |
| 57 | + * @return |
| 58 | + */ |
| 59 | + public static boolean memberHasChanged(IContext context, IMendixObject item, String member) |
| 60 | + { |
| 61 | + if (item == null) |
| 62 | + throw new IllegalArgumentException("The provided object is empty"); |
| 63 | + if (!item.hasMember(member)) |
| 64 | + throw new IllegalArgumentException("Unknown member: " + member); |
| 65 | + return item.getMember(context, member).getState() == MemberState.CHANGED || item.getState() != ObjectState.NORMAL; |
| 66 | + } |
| 67 | + |
| 68 | + public static void deepClone(IContext c, IMendixObject source, IMendixObject target, String membersToSkip, String membersToKeep, String reverseAssociations, String excludeEntities, String excludeModules) throws CoreException |
| 69 | + { |
| 70 | + List<String> toskip = Arrays.asList((membersToSkip + ",createdDate,changedDate").split(",")); |
| 71 | + List<String> tokeep = Arrays.asList((membersToKeep + ",System.owner,System.changedBy").split(",")); |
| 72 | + List<String> revAssoc = Arrays.asList(reverseAssociations.split(",")); |
| 73 | + List<String> skipEntities = Arrays.asList(excludeEntities.split(",")); |
| 74 | + List<String> skipModules = Arrays.asList(excludeModules.split(",")); |
| 75 | + Map<IMendixIdentifier, IMendixIdentifier> mappedIDs = new HashMap<IMendixIdentifier, IMendixIdentifier>(); |
| 76 | + duplicate(c, source, target, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedIDs); |
| 77 | + } |
| 78 | + |
| 79 | + private static void duplicate(IContext ctx, IMendixObject src, IMendixObject tar, |
| 80 | + List<String> toskip, List<String> tokeep, List<String> revAssoc, |
| 81 | + List<String> skipEntities, List<String> skipModules, |
| 82 | + Map<IMendixIdentifier, IMendixIdentifier> mappedObjects) throws CoreException |
| 83 | + { |
| 84 | + mappedObjects.put(src.getId(), tar.getId()); |
| 85 | + |
| 86 | + Map<String, ? extends IMendixObjectMember<?>> members = src.getMembers(ctx); |
| 87 | + String type = src.getType() + "/"; |
| 88 | + |
| 89 | + for(String key : members.keySet()) |
| 90 | + if (!toskip.contains(key) && !toskip.contains(type + key)){ |
| 91 | + IMendixObjectMember<?> m = members.get(key); |
| 92 | + if (m.isVirtual() || m instanceof MendixAutoNumber) |
| 93 | + continue; |
| 94 | + |
| 95 | + boolean keep = tokeep.contains(key) || tokeep.contains(type + key); |
| 96 | + |
| 97 | + if (m instanceof MendixObjectReference && !keep && m.getValue(ctx) != null) { |
| 98 | + IMendixObject o = Core.retrieveId(ctx, ((MendixObjectReference) m).getValue(ctx)); |
| 99 | + IMendixIdentifier refObj = getCloneOfObject(ctx, o, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); |
| 100 | + tar.setValue(ctx, key, refObj); |
| 101 | + } |
| 102 | + |
| 103 | + else if (m instanceof MendixObjectReferenceSet && !keep && m.getValue(ctx) != null) { |
| 104 | + MendixObjectReferenceSet rs = (MendixObjectReferenceSet) m; |
| 105 | + List<IMendixIdentifier> res = new ArrayList<IMendixIdentifier>(); |
| 106 | + for(IMendixIdentifier item : rs.getValue(ctx)) { |
| 107 | + IMendixObject o = Core.retrieveId(ctx, item); |
| 108 | + IMendixIdentifier refObj = getCloneOfObject(ctx, o, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); |
| 109 | + res.add(refObj); |
| 110 | + } |
| 111 | + tar.setValue(ctx, key, res); |
| 112 | + } |
| 113 | + |
| 114 | + else if (m instanceof MendixAutoNumber) //skip autonumbers! Ticket 14893 |
| 115 | + continue; |
| 116 | + |
| 117 | + else { |
| 118 | + tar.setValue(ctx, key, m.getValue(ctx)); |
| 119 | + } |
| 120 | + } |
| 121 | + Core.commitWithoutEvents(ctx, tar); |
| 122 | + duplicateReverseAssociations(ctx, src, tar, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); |
| 123 | + } |
| 124 | + |
| 125 | + private static IMendixIdentifier getCloneOfObject(IContext ctx, IMendixObject src, |
| 126 | + List<String> toskip, List<String> tokeep, List<String> revAssoc, |
| 127 | + List<String> skipEntities, List<String> skipModules, |
| 128 | + Map<IMendixIdentifier, IMendixIdentifier> mappedObjects) throws CoreException |
| 129 | + { |
| 130 | + String objType = src.getMetaObject().getName(); |
| 131 | + String modName = src.getMetaObject().getModuleName(); |
| 132 | + |
| 133 | + // if object is already being cloned, return ref to clone |
| 134 | + if (mappedObjects.containsKey(src.getId())) { |
| 135 | + return mappedObjects.get(src.getId()); |
| 136 | + // if object should be skipped based on module or entity, return source object |
| 137 | + } else if (skipEntities.contains(objType) || skipModules.contains(modName)) { |
| 138 | + return src.getId(); |
| 139 | + // if not already being cloned, create clone |
| 140 | + } else { |
| 141 | + IMendixObject clone = Core.instantiate(ctx, src.getType()); |
| 142 | + duplicate(ctx, src, clone, toskip, tokeep, revAssoc, skipEntities, skipModules, mappedObjects); |
| 143 | + return clone.getId(); |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + private static void duplicateReverseAssociations(IContext ctx, IMendixObject src, IMendixObject tar, |
| 148 | + List<String> toskip, List<String> tokeep, List<String> revAssocs, |
| 149 | + List<String> skipEntities, List<String> skipModules, |
| 150 | + Map<IMendixIdentifier, IMendixIdentifier> mappedObjects) throws CoreException |
| 151 | + { |
| 152 | + for(String fullAssocName : revAssocs) { |
| 153 | + String[] parts = fullAssocName.split("/"); |
| 154 | + |
| 155 | + if (parts.length != 1 && parts.length != 3) //specifying entity has no meaning anymore, but remain backward compatible. |
| 156 | + throw new IllegalArgumentException("Reverse association is not defined correctly, please mention the relation name only: '" + fullAssocName + "'"); |
| 157 | + |
| 158 | + String assocname = parts.length == 3 ? parts[1] : parts[0]; //support length 3 for backward compatibility |
| 159 | + |
| 160 | + IMetaAssociation massoc = src.getMetaObject().getDeclaredMetaAssociationChild(assocname); |
| 161 | + |
| 162 | + if (massoc != null) { |
| 163 | + IMetaObject relationParent = massoc.getParent(); |
| 164 | + // if the parent is in the exclude list, we can't clone the parent, and setting the |
| 165 | + // references to the newly cloned target object will screw up the source data. |
| 166 | + if (skipEntities.contains(relationParent.getName()) || skipModules.contains(relationParent.getModuleName())){ |
| 167 | + 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 + "'"); |
| 168 | + } |
| 169 | + |
| 170 | + //MWE: what to do with reverse reference sets? -> to avoid spam creating objects on |
| 171 | + //reverse references, do not support referenceset (todo: we could keep a map of converted guids and reuse that!) |
| 172 | + if (massoc.getType() == AssociationType.REFERENCESET) { |
| 173 | + throw new IllegalArgumentException("It is not possible to clone reverse referencesets: '" + fullAssocName + "'"); |
| 174 | + } |
| 175 | + |
| 176 | + List<IMendixObject> objs = Core.retrieveXPathQuery(ctx, String.format("//%s[%s='%s']", |
| 177 | + relationParent.getName(), assocname, String.valueOf(src.getId().toLong()))); |
| 178 | + |
| 179 | + for(IMendixObject obj : objs) { |
| 180 | + @SuppressWarnings("unused") // object is unused on purpose |
| 181 | + IMendixIdentifier refObj = getCloneOfObject(ctx, obj, toskip, tokeep, revAssocs, skipEntities, skipModules, mappedObjects); |
| 182 | + // setting reference explicitly is not necessary, this has been done in the |
| 183 | + // duplicate() call. |
| 184 | + } |
| 185 | + } |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + public static Boolean commitWithoutEvents(IContext context, IMendixObject subject) throws CoreException |
| 190 | + { |
| 191 | + Core.commitWithoutEvents(context, subject); |
| 192 | + return true; |
| 193 | + } |
| 194 | + |
| 195 | + public static String getValueOfPath(IContext context, IMendixObject substitute, String fullpath, String datetimeformat) throws Exception |
| 196 | + { |
| 197 | + String[] path = fullpath.split("/"); |
| 198 | + if (path.length == 1) { |
| 199 | + IMendixObjectMember<?> member = substitute.getMember(context, path[0]); |
| 200 | + |
| 201 | + //special case, see ticket 9135, format datetime. |
| 202 | + if (member instanceof MendixDateTime) { |
| 203 | + Date time = ((MendixDateTime) member).getValue(context); |
| 204 | + if (time == null) |
| 205 | + return ""; |
| 206 | + String f = datetimeformat != null && !datetimeformat.isEmpty() ? datetimeformat : "EEE dd MMM yyyy, HH:mm"; |
| 207 | + return new SimpleDateFormat(f).format(time); |
| 208 | + } |
| 209 | + |
| 210 | + if (member instanceof MendixEnum) { |
| 211 | + String value = member.parseValueToString(context); |
| 212 | + if (value == null || value.isEmpty()) |
| 213 | + return ""; |
| 214 | + |
| 215 | + IMetaEnumeration enumeration = ((MendixEnum)member).getEnumeration(); |
| 216 | + IMetaEnumValue evalue = enumeration.getEnumValues().get(value); |
| 217 | + return Core.getInternationalizedString(context, evalue.getI18NCaptionKey()); |
| 218 | + } |
| 219 | + //default |
| 220 | + return member.parseValueToString(context); |
| 221 | + } |
| 222 | + |
| 223 | + else if (path.length == 0) |
| 224 | + throw new Exception("communitycommons.ORM.getValueOfPath: Unexpected end of path."); |
| 225 | + |
| 226 | + else { |
| 227 | + IMendixObjectMember<?> member = substitute.getMember(context, path[0]); |
| 228 | + if (member instanceof MendixObjectReference) { |
| 229 | + MendixObjectReference ref = (MendixObjectReference) member; |
| 230 | + IMendixIdentifier id = ref.getValue(context); |
| 231 | + if (id == null) |
| 232 | + return ""; |
| 233 | + IMendixObject obj = Core.retrieveId(context, id); |
| 234 | + if (obj == null) |
| 235 | + return ""; |
| 236 | + return getValueOfPath(context, obj, fullpath.substring(fullpath.indexOf("/") + 1), datetimeformat); |
| 237 | + } |
| 238 | + |
| 239 | + else if (member instanceof MendixObjectReferenceSet) { |
| 240 | + MendixObjectReferenceSet ref = (MendixObjectReferenceSet) member; |
| 241 | + List<IMendixIdentifier> ids = ref.getValue(context); |
| 242 | + if (ids == null) |
| 243 | + return ""; |
| 244 | + StringBuilder res = new StringBuilder(); |
| 245 | + for(IMendixIdentifier id : ids) { |
| 246 | + if (id == null) |
| 247 | + continue; |
| 248 | + IMendixObject obj = Core.retrieveId(context, id); |
| 249 | + if (obj == null) |
| 250 | + continue; |
| 251 | + res.append(", "); |
| 252 | + res.append(getValueOfPath(context, obj, fullpath.substring(fullpath.indexOf("/") + 1), datetimeformat)); |
| 253 | + } |
| 254 | + return res.length() > 1 ? res.toString().substring(2) : ""; |
| 255 | + } |
| 256 | + else throw new Exception("communitycommons.ORM.getValueOfPath: Not a valid reference: '"+path[0]+"' in '"+ fullpath +"'"); |
| 257 | + } |
| 258 | + } |
| 259 | + |
| 260 | + public static Boolean cloneObject(IContext c, IMendixObject source, |
| 261 | + IMendixObject target, Boolean withAssociations) |
| 262 | + { |
| 263 | + Map<String, ? extends IMendixObjectMember<?>> members = source.getMembers(c); |
| 264 | + |
| 265 | + for(String key : members.keySet()) { |
| 266 | + IMendixObjectMember<?> m = members.get(key); |
| 267 | + if (m.isVirtual()) |
| 268 | + continue; |
| 269 | + if (m instanceof MendixAutoNumber) |
| 270 | + continue; |
| 271 | + if (withAssociations || ((!(m instanceof MendixObjectReference) && !(m instanceof MendixObjectReferenceSet)&& !(m instanceof MendixAutoNumber)))) |
| 272 | + target.setValue(c, key, m.getValue(c)); |
| 273 | + } |
| 274 | + return true; |
| 275 | + } |
| 276 | + |
| 277 | + public static IMendixObject getLastChangedByUser(IContext context, |
| 278 | + IMendixObject thing) throws CoreException |
| 279 | + { |
| 280 | + if (thing == null || !thing.hasChangedByAttribute()) |
| 281 | + return null; |
| 282 | + |
| 283 | + IMendixIdentifier itemId = thing.getChangedBy(context); |
| 284 | + if (itemId == null) |
| 285 | + return null; |
| 286 | + |
| 287 | + return Core.retrieveId(context, itemId); |
| 288 | + } |
| 289 | + |
| 290 | + public static IMendixObject getCreatedByUser(IContext context, |
| 291 | + IMendixObject thing) throws CoreException |
| 292 | + { |
| 293 | + if (thing == null || !thing.hasOwnerAttribute()) |
| 294 | + return null; |
| 295 | + |
| 296 | + IMendixIdentifier itemId = thing.getOwner(context); |
| 297 | + if (itemId == null) |
| 298 | + return null; |
| 299 | + |
| 300 | + return Core.retrieveId(context, itemId); |
| 301 | + } |
| 302 | + |
| 303 | + public static void commitSilent(IContext c, IMendixObject mendixObject) |
| 304 | + { |
| 305 | + try |
| 306 | + { |
| 307 | + Core.commit(c, mendixObject); |
| 308 | + } |
| 309 | + catch (CoreException e) |
| 310 | + { |
| 311 | + throw new RuntimeException(e); |
| 312 | + } |
| 313 | + } |
| 314 | + |
| 315 | + public static void copyAttributes(IContext context, IMendixObject source, IMendixObject target) |
| 316 | + { |
| 317 | + if (source == null) |
| 318 | + throw new IllegalStateException("source is null"); |
| 319 | + if (target == null) |
| 320 | + throw new IllegalStateException("target is null"); |
| 321 | + |
| 322 | + for(IMetaPrimitive e : target.getMetaObject().getMetaPrimitives()) { |
| 323 | + if (!source.hasMember(e.getName())) |
| 324 | + continue; |
| 325 | + if (e.isVirtual() || e.getType() == PrimitiveType.AutoNumber) |
| 326 | + continue; |
| 327 | + |
| 328 | + target.setValue(context, e.getName(), source.getValue(context, e.getName())); |
| 329 | + } |
| 330 | + } |
| 331 | +} |
0 commit comments