package org.fenixedu.bennu.core.api;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status;
import org.fenixedu.bennu.core.groups.Group;
import org.fenixedu.bennu.core.rest.BennuRestResource;
import pt.ist.fenixframework.DomainObject;
import pt.ist.fenixframework.FenixFramework;
import pt.ist.fenixframework.ValueTypeSerializer;
import pt.ist.fenixframework.core.AbstractDomainObject;
import pt.ist.fenixframework.dml.DomainClass;
import pt.ist.fenixframework.dml.DomainEntity;
import pt.ist.fenixframework.dml.ModifiableEntity;
import pt.ist.fenixframework.dml.Modifier;
import pt.ist.fenixframework.dml.Role;
import pt.ist.fenixframework.dml.Slot;
import pt.ist.fenixframework.dml.ValueType;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
@Path("/bennu-core/domain-browser")
public class DomainBrowserResource extends BennuRestResource {
@GET
@Path("/{oid}")
@Produces(MediaType.APPLICATION_JSON)
public JsonElement viewObject(@PathParam("oid") String oid) {
accessControl(Group.managers());
if (oid == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
DomainObject obj = FenixFramework.getDomainObject(oid);
if (FenixFramework.isDomainObjectValid(obj)) {
return describeObject(obj);
}
throw new WebApplicationException(Status.NOT_FOUND);
}
@GET
@Path("/{oid}/{role}/count")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject relationCounts(@PathParam("oid") String oid, @PathParam("role") String role) {
accessControl(Group.managers());
if (oid == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
DomainObject obj = FenixFramework.getDomainObject(oid);
if (FenixFramework.isDomainObjectValid(obj)) {
DomainClass domClass = FenixFramework.getDomainModel().findClass(obj.getClass().getName());
Role roleSlot = domClass.findRoleSlot(role);
if (roleSlot == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
Set<DomainObject> objects = getRelationSet(obj, roleSlot);
if (objects == null) {
throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
}
JsonObject json = new JsonObject();
json.addProperty("name", role);
json.addProperty("size", objects.size());
return json;
}
throw new WebApplicationException(Status.NOT_FOUND);
}
@GET
@Path("/{oid}/{role}")
@Produces(MediaType.APPLICATION_JSON)
public JsonArray showRelation(@PathParam("oid") String oid, @PathParam("role") String role) {
accessControl(Group.managers());
if (oid == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
DomainObject obj = FenixFramework.getDomainObject(oid);
if (FenixFramework.isDomainObjectValid(obj)) {
DomainClass domClass = FenixFramework.getDomainModel().findClass(obj.getClass().getName());
Role roleSlot = domClass.findRoleSlot(role);
if (roleSlot == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
Set<DomainObject> objects = getRelationSet(obj, roleSlot);
if (objects == null) {
throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
}
JsonArray array = new JsonArray();
for (DomainObject domainObject : objects) {
JsonObject json = new JsonObject();
json.addProperty("type", domainObject.getClass().getName());
json.addProperty("oid", domainObject.getExternalId());
array.add(json);
}
return array;
}
throw new WebApplicationException(Status.NOT_FOUND);
}
private JsonObject describeObject(DomainObject obj) {
JsonObject json = new JsonObject();
json.addProperty("oid", obj.getExternalId());
json.addProperty("type", obj.getClass().getName());
DomainClass domClass = FenixFramework.getDomainModel().findClass(obj.getClass().getName());
json.add("modifiers", modifiers(domClass));
DomainEntity superclass = domClass.getSuperclass();
if (superclass != null) {
json.addProperty("superclass", superclass.getFullName());
}
JsonArray slots = new JsonArray();
for (DomainClass dc = domClass; dc != null; dc = (DomainClass) dc.getSuperclass()) {
for (final Slot slot : dc.getSlotsList()) {
JsonObject slotJson = new JsonObject();
slotJson.addProperty("name", slot.getName());
slotJson.addProperty("type", slot.getSlotType().getFullname());
Object value = getSlotValue(obj, slot);
if (value instanceof byte[]) {
value = Base64.getEncoder().encodeToString((byte[]) value);
}
slotJson.addProperty("value", Objects.toString(value, null));
slotJson.add("modifiers", modifiers(slot));
slots.add(slotJson);
}
}
json.add("slots", slots);
JsonArray relationSlots = new JsonArray();
for (Role role : getRoles(domClass, true)) {
JsonObject roleJson = new JsonObject();
roleJson.addProperty("name", role.getName());
roleJson.addProperty("type", role.getType().getFullName());
roleJson.addProperty("value", getRelationSlot(obj, role));
roleJson.add("modifiers", modifiers(role));
relationSlots.add(roleJson);
}
json.add("relationSlots", relationSlots);
JsonArray relationSets = new JsonArray();
for (Role role : getRoles(domClass, false)) {
JsonObject roleJson = new JsonObject();
roleJson.addProperty("name", role.getName());
roleJson.addProperty("type", role.getType().getFullName());
roleJson.add("modifiers", modifiers(role));
relationSets.add(roleJson);
}
json.add("relationSets", relationSets);
return json;
}
private JsonElement modifiers(ModifiableEntity entity) {
JsonArray array = new JsonArray();
for (Modifier modifier : entity.getModifiers()) {
array.add(new JsonPrimitive(modifier.name()));
}
return array;
}
private String getRelationSlot(final DomainObject domainObject, final Role role) {
final Method method = getMethod(domainObject, role.getName());
if (method != null) {
try {
DomainObject obj = (DomainObject) method.invoke(domainObject);
return obj == null ? null : obj.getExternalId();
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
return "!!EXCEPTION!!";
}
}
return null;
}
private Object getSlotValue(final DomainObject domainObject, final Slot slot) {
final Method method = getMethod(domainObject, slot.getName());
if (method != null) {
try {
Object value = method.invoke(domainObject);
ValueType vt = slot.getSlotType();
if (vt.isBuiltin() || vt.isEnum() || value == null) {
return value;
}
return getPrimitiveValueFor(vt, value);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
return "!!EXCEPTION!!";
}
}
return null;
}
private Object getPrimitiveValueFor(ValueType vt, Object value) {
try {
Method method =
ValueTypeSerializer.class.getDeclaredMethod("serialize$" + vt.getDomainName().replace('.', '$'),
Class.forName(vt.getFullname()));
return method.invoke(null, value);
} catch (Exception e) {
return null;
}
}
protected Method getMethod(final DomainObject domainObject, final String slotName) {
final String methodName = "get" + Character.toUpperCase(slotName.charAt(0)) + slotName.substring(1);
if (domainObject != null && methodName != null && !methodName.isEmpty()) {
Class<?> clazz = domainObject.getClass();
while (clazz != AbstractDomainObject.class) {
try {
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
return method;
} catch (NoSuchMethodException | SecurityException e) {
}
clazz = clazz.getSuperclass();
}
}
return null;
}
@SuppressWarnings("unchecked")
private Set<DomainObject> getRelationSet(final DomainObject domainObject, final Role role) {
final Method method = getMethod(domainObject, role.getName() + "Set");
if (method != null) {
try {
return (Set<DomainObject>) method.invoke(domainObject);
} catch (final Exception e) {
return null;
}
}
return null;
}
private Set<Role> getRoles(final DomainClass domainClass, boolean one) {
final Set<Role> result = new HashSet<Role>();
for (DomainClass dc = domainClass; dc != null; dc = (DomainClass) dc.getSuperclass()) {
for (final Role role : dc.getRoleSlotsList()) {
//Roles without name are unidirectional and should be skipped
if (role.getName() == null) {
continue;
}
if (one ? (role.getMultiplicityUpper() == 1) : (role.getMultiplicityUpper() != 1)) {
result.add(role);
}
}
}
return result;
}
}