package restservices.publish; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import com.google.common.collect.ImmutableMap; import com.mendix.core.Core; import com.mendix.core.CoreException; import com.mendix.core.CoreRuntimeException; import com.mendix.systemwideinterfaces.core.IDataType; import com.mendix.systemwideinterfaces.core.IDataType.DataTypeEnum; import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive; import communitycommons.XPath; import restservices.RestServices; import restservices.proxies.DataServiceDefinition; import restservices.util.Utils; import system.proxies.User; import system.proxies.UserRole; public class ConsistencyChecker { public static String check(DataServiceDefinition def) { List<String> errors = new ArrayList<String>(); if (!Utils.isValidKey(def.getName())) errors.add("Invalid service name"); if (!def.getName().toLowerCase().equals(def.getName())) errors.add("Service name should be lowercased"); checkSource(def, errors); if (def.getEnableChangeLog() && def.getEnableGet()) checkOnPublishMf(def, errors); if (def.getEnableListing() && !def.getEnableGet()) errors.add("Listing requires get to be enabled"); if (def.getEnableCreate() || def.getEnableUpdate()) checkOnUpdateMF(def, errors); if (def.getEnableDelete() && Utils.isNotEmpty(def.getOnDeleteMicroflow())) //Delete microflow is optional checkOnDeleteMF(def, errors); //TODO: should only one service that defines 'GET object', which will be the default String secError = checkAccessRole(def.getAccessRole()); if (secError != null) errors.add(secError); return errors.size() == 0 ? null : "* " + StringUtils.join(errors, "\n* "); } public static String checkAccessRole(String accessRole) { if (accessRole == null || accessRole.trim().isEmpty()) return "No access role has been set. Use '*' for all, or provide a Userrole name or Microflow name"; if ("*".equals(accessRole)) return null; try { if (null != XPath.create(Core.createSystemContext(), UserRole.class).eq(UserRole.MemberNames.Name, accessRole).first()) return null; } catch (CoreException e) { throw new RuntimeException(e); } if (!Utils.microflowExists(accessRole)) return "'" + accessRole + "' doesn't seem to be an existing userrole or microflow"; if (Utils.getArgumentTypes(accessRole).size() != 0) return "The authentication microflow '" + accessRole + "' shouldn' t take any arguments"; IDataType rt = Core.getReturnType(accessRole); if (rt.getType() != DataTypeEnum.Object || !Core.isSubClassOf(User.entityName, rt.getObjectType())) return "The authentication microflow '" + accessRole + "' should return a 'System.User' object or derivate thereof"; return null; } private static void checkOnDeleteMF(DataServiceDefinition def, List<String> errors) { // TODO Auto-generated method stub } private static void checkOnUpdateMF(DataServiceDefinition def, List<String> errors) { try { DataService.extractArgInfoForUpdateMicroflow(def); } catch(Exception e) { errors.add("Invalid update microflow: " + e.getMessage()); } } private static void checkOnPublishMf(DataServiceDefinition def, List<String> errors) { if (!Utils.microflowExists(def.getOnPublishMicroflow())) errors.add("OnPublishMicroflow is not a valid microflow"); else { Map<String, String> args = Utils.getArgumentTypes(def.getOnPublishMicroflow()); if (args.size() != 1) errors.add("OnPublishMicroflow should have exact one argument"); if (!args.get(args.keySet().iterator().next()).equals(def.getSourceEntity())) errors.add("OnPublishMicroflow argument type should be " + def.getSourceEntity()); IDataType resType = Core.getReturnType(def.getOnPublishMicroflow()); if (!resType.isMendixObject() || resType.isList() || Core.getMetaObject(resType.getObjectType()).isPersistable()) errors.add("OnPublishMicroflow should return a transient object"); } } private static void checkSource(DataServiceDefinition def, List<String> errors) { if (def.getSourceEntity() == null || Core.getMetaObject(def.getSourceEntity()) == null) errors.add("Invalid source entity"); else { if (!Core.getMetaObject(def.getSourceEntity()).isPersistable()) errors.add("Source object should be a transient object"); IMetaPrimitive prim = Core.getMetaObject(def.getSourceEntity()).getMetaPrimitive(def.getSourceKeyAttribute()); if (prim == null) errors.add("Key attribute does not exist"); if (def.getSourceConstraint() != null) { if (def.getSourceConstraint().contains(RestServices.CURRENTUSER_TOKEN)) { if (def.getEnableChangeLog()) errors.add("The source constrained is not allowed to refer to the current user if change tracking is enabled"); if ("*".equals(def.getAccessRole())) errors.add("The source constrained is not allowed to refer to the current user if the service is world-readable or world-writable"); } String xpath = "//" + def.getSourceEntity() + def.getSourceConstraint(); xpath = xpath.replace(RestServices.CURRENTUSER_TOKEN, "empty"); try { Core.retrieveXPathQuery(Core.createSystemContext(), xpath, 1, 0, ImmutableMap.of("id", "ASC")); } catch (CoreRuntimeException e) { errors.add("Constraint is not a valid xpath query: " + ExceptionUtils.getRootCauseMessage(e)); } catch (CoreException e) { errors.add("Failed to verify validity of constraint: " + e.getMessage()); } } } } }