private void addLookupEndpoints(SwaggerModel swaggerModel, Entity entity, Locale locale) { for (Lookup lookup : entity.getLookupsExposedByRest()) { String lookupUrl = ClassName.restLookupUrl( entity.getName(), entity.getModule(), entity.getNamespace(), lookup.getMethodName()); swaggerModel.addPathEntry(lookupUrl, HttpMethod.GET, lookupPathEntry(entity, lookup, locale)); } }
private static void assertRelationshipIsHistoryCompatible( Field field, RelationshipHolder holder, List<Entity> allEntities) { Entity relatedEntity = findEntityByName(holder.getRelatedClass(), allEntities); if (!hasCorrectTrackingSettings(field, relatedEntity, holder)) { String relatedClassName = relatedEntity == null ? "null" : relatedEntity.getClassName(); throw new InvalidEntitySettingsException(field.getEntity().getClassName(), relatedClassName); } }
private static Entity findEntityByName(String name, List<Entity> allEntities) { for (Entity entity : allEntities) { if (entity.getClassName().equals(name)) { return entity; } } return null; }
private Response lookupResponse(Entity entity, Lookup lookup, Locale locale) { if (lookup.isSingleObjectReturn()) { return new ResponseWithSchema( msg(locale, RESPONSE_SINGLE_DESC_KEY, entity.getName()), definitionWithMetadataPath(entity.getClassName())); } else { return new ResponseWithSchema( msg(locale, RESPONSE_LIST_DESC_KEY, entity.getName()), definitionWithMetadataPath(entity.getClassName())); } }
private ImportExportBlueprint sortBlueprintRecords(ImportExportBlueprint blueprint) { List<Entity> entities = new ArrayList<>(blueprint.size()); for (ImportExportBlueprint.Record record : blueprint) { entities.add(allEntities.retrieveByClassName(record.getEntityName())); } RelationshipSorter relationshipSorter = new RelationshipSorter(); relationshipSorter.sort(entities); ImportExportBlueprint sortedBlueprint = new ImportExportBlueprint(); for (Entity entity : entities) { String entityName = entity.getClassName(); sortedBlueprint.includeEntitySchema(entityName, blueprint.isIncludeEntitySchema(entityName)); sortedBlueprint.includeEntityData(entityName, blueprint.isIncludeEntityData(entityName)); } return sortedBlueprint; }
private List<Parameter> lookupParameters(Entity entity, Lookup lookup, Locale locale) { List<Parameter> parameters = new ArrayList<>(); for (String lookupFieldName : lookup.getFieldsOrder()) { LookupFieldType lookupFieldType = lookup.getLookupFieldType(lookupFieldName); Field lookupField; if (lookupFieldName.contains(".")) { lookupField = getRelatedField( lookup .getLookupFieldByName(LookupName.getFieldName(lookupFieldName)) .getMetadata(Constants.MetadataKeys.RELATED_CLASS) .getValue(), LookupName.getRelatedFieldName(lookupFieldName)); } else { lookupField = lookup.getLookupFieldByName(lookupFieldName); } String paramDesc = lookupParamDescription(lookupField, lookupFieldType, locale); Parameter parameter = SwaggerFieldConverter.lookupParameter( lookupFieldName, lookupField, lookupFieldType, paramDesc); parameters.add(parameter); } parameters.addAll(queryParamsParameters(entity.getFieldsExposedByRest(), locale)); return parameters; }
private PathEntry deletePathEntry(Entity entity, Locale locale) { final PathEntry pathEntry = new PathEntry(); final String entityName = entity.getName(); pathEntry.setDescription(msg(locale, DELETE_DESC_KEY, entityName)); pathEntry.setOperationId(msg(locale, DELETE_ID_KEY, entityName)); pathEntry.setProduces(json()); pathEntry.addTag(entity.getClassName()); pathEntry.addParameter(deleteIdPathParameter(locale)); addCommonResponses(pathEntry, locale); pathEntry.addResponse(HttpStatus.OK, deleteResponse(entity, locale)); return pathEntry; }
private RestOptions restOptionsOrDefault(Entity entity) { RestOptions restOptions = entity.getRestOptions(); if (restOptions == null) { // everything off restOptions = new RestOptions(); } return restOptions; }
private PathEntry readPathEntry(Entity entity, Locale locale) { final PathEntry pathEntry = new PathEntry(); final String entityName = entity.getName(); pathEntry.setDescription(msg(locale, READ_DESC_KEY, entityName)); pathEntry.setOperationId(msg(locale, READ_ID_KEY, entityName)); pathEntry.setProduces(json()); pathEntry.addTag(entity.getClassName()); pathEntry.setParameters(queryParamsParameters(entity.getFieldsExposedByRest(), locale)); pathEntry.addParameter(idQueryParameter(locale)); pathEntry.addResponse(HttpStatus.OK, readResponse(entity, locale)); addCommonResponses(pathEntry, locale); return pathEntry; }
private PathEntry putPathEntry(Entity entity, Locale locale) { final PathEntry pathEntry = new PathEntry(); final String entityName = entity.getName(); pathEntry.setDescription(msg(locale, UPDATE_DESC_KEY, entityName)); pathEntry.setOperationId(msg(locale, UPDATE_ID_KEY, entityName)); pathEntry.setProduces(json()); pathEntry.addTag(entity.getClassName()); pathEntry.addParameter(updateEntityParameter(entity, locale)); addCommonResponses(pathEntry, locale); pathEntry.addResponse(HttpStatus.OK, updatedItemResponse(entity, locale)); pathEntry.addResponse(HttpStatus.NOT_FOUND, notFoundResponse(entity, locale)); return pathEntry; }
private void addDefinitions(SwaggerModel swaggerModel, Entity entity) { RestOptions restOptions = restOptionsOrDefault(entity); if (restOptions.supportsAnyOperation() || !entity.getLookupsExposedByRest().isEmpty()) { // all fields, including generated ones swaggerModel.addDefinition(entity.getClassName(), definition(entity, true, true)); swaggerModel.addDefinition( entity.getClassName() + "-WithMetadata", definitionWithMetadata(entity)); } if (restOptions.isAllowCreate()) { // no auto-generated fields swaggerModel.addDefinition( definitionNewName(entity.getClassName()), definition(entity, false, false)); } if (restOptions.isAllowUpdate()) { // no auto-generated fields, except ID swaggerModel.addDefinition( definitionUpdateName(entity.getClassName()), definition(entity, false, true)); } }
/** * Takes a list of entities and sorts them by the inheritance tree. The entities that extend the * Object class or MdsEntity class will be moved to the beggining of the list. After that, the * entites that are already present on the list will be added, up the inheritance tree. * * @param list Initial list of entities to sort * @return List of entities, sorted by inheritance tree */ public static List<Entity> sortByInheritance(List<Entity> list) { List<Entity> sorted = new ArrayList<>(list.size()); // firstly we add entities with base class equal to Object class or MdsEntity class for (Iterator<Entity> iterator = list.iterator(); iterator.hasNext(); ) { Entity entity = iterator.next(); if (entity.isBaseEntity()) { sorted.add(entity); iterator.remove(); } } // then we add entities which base classes are in sorted list // we do that after all entities will be added to sorted list while (!list.isEmpty()) { for (Iterator<Entity> iterator = list.iterator(); iterator.hasNext(); ) { final Entity entity = iterator.next(); Entity superClass = (Entity) CollectionUtils.find( sorted, new Predicate() { @Override public boolean evaluate(Object object) { return object instanceof Entity && ((Entity) object).getClassName().equals(entity.getSuperClass()); } }); if (null != superClass) { sorted.add(entity); iterator.remove(); } } } return sorted; }
private static boolean hasCorrectTrackingSettings( Field field, Entity relatedEntity, RelationshipHolder holder) { boolean recordsHistory = field.getEntity().isRecordHistory(); boolean relatedRecordsHistory = relatedEntity.isRecordHistory(); if (holder.isBiDirectional()) { // Both sides of bi-directional relationship must have the same history tracking settings return !(recordsHistory ^ relatedRecordsHistory); } else { // For uni-directional relationship, the related side must not have more strict options than // the entity that defines the relationship return recordsHistory ? relatedRecordsHistory : true; } }
private void addCrudEndpoints(SwaggerModel swaggerModel, Entity entity, Locale locale) { final String entityPath = ClassName.restUrl(entity.getName(), entity.getModule(), entity.getNamespace()); RestOptions restOptions = restOptionsOrDefault(entity); if (restOptions.isAllowRead()) { // retrieveAll and retrieveById swaggerModel.addPathEntry(entityPath, HttpMethod.GET, readPathEntry(entity, locale)); } if (restOptions.isAllowCreate()) { // post new item swaggerModel.addPathEntry(entityPath, HttpMethod.POST, postPathEntry(entity, locale)); } if (restOptions.isAllowUpdate()) { // update an existing item swaggerModel.addPathEntry(entityPath, HttpMethod.PUT, putPathEntry(entity, locale)); } if (restOptions.isAllowDelete()) { // delete an item swaggerModel.addPathEntry( entityPath + ID_PATHVAR, HttpMethod.DELETE, deletePathEntry(entity, locale)); } }
private PathEntry lookupPathEntry(Entity entity, Lookup lookup, Locale locale) { final PathEntry pathEntry = new PathEntry(); pathEntry.setDescription(msg(locale, LOOKUP_DESC_KEY, lookup.getLookupName())); pathEntry.setOperationId(lookup.getMethodName()); pathEntry.setProduces(json()); pathEntry.addTag(entity.getClassName()); pathEntry.setParameters(lookupParameters(entity, lookup, locale)); pathEntry.addResponse(HttpStatus.OK, lookupResponse(entity, lookup, locale)); addCommonResponses(pathEntry, locale); if (lookup.isSingleObjectReturn()) { pathEntry.addResponse(HttpStatus.NOT_FOUND, lookup404Response(entity, locale)); } return pathEntry; }
private Definition definitionWithMetadata(Entity entity) { final Definition definition = new Definition(); Property metadata = new Property(); metadata.setRef(definitionPath("Metadata")); Property data = new Property(ARRAY_TYPE); data.setRef(definitionPath(entity.getClassName())); final Map<String, Property> properties = new LinkedHashMap<>(); properties.put("metadata", metadata); properties.put("data", data); final List<String> required = new ArrayList<>(); required.add("metadata"); required.add("data"); definition.setProperties(properties); definition.setRequired(required); return definition; }
private void buildDefinitionProperties( Map<String, Property> properties, List<String> required, Entity entity, boolean includeAuto, boolean includeId) { if (includeId) { properties.put(Constants.Util.ID_FIELD_NAME, new Property(INTEGER_TYPE, INT64_FORMAT)); } for (Field field : entity.getFields()) { final String fieldName = field.getName(); if (field.isExposedViaRest()) { // auto generated fields included only in responses if (!field.isAutoGenerated() || includeAuto) { Property property = SwaggerFieldConverter.fieldToProperty(field); properties.put(fieldName, property); if (field.isRequired()) { required.add(fieldName); } } } } }
private Response readResponse(Entity entity, Locale locale) { return new ResponseWithSchema( msg(locale, RESPONSE_LIST_DESC_KEY, entity.getName()), definitionWithMetadataPath(entity.getClassName())); }
private Field getRelatedField(String entityClassName, String fieldName) { Entity entity = allEntities.retrieveByClassName(entityClassName); return entity.getField(fieldName); }
private Parameter newEntityParameter(Entity entity, Locale locale) { return bodyParameter( entity.getName(), msg(locale, CREATE_BODY_DESC_KEY, entity.getName()), definitionNewPath(entity.getClassName())); }
private Response notFoundResponse(Entity entity, Locale locale) { return new Response(msg(locale, RESPONSE_NOT_FOUND_KEY, entity.getName())); }
private Response deleteResponse(Entity entity, Locale locale) { return new Response(msg(locale, RESPONSE_DELETE_DESC_KEY, entity.getName())); }
private Response updatedItemResponse(Entity entity, Locale locale) { return new ResponseWithSchema( msg(locale, RESPONSE_UPDATED_DESC_KEY, entity.getName()), definitionPath(entity.getClassName())); }
private Parameter updateEntityParameter(Entity entity, Locale locale) { return bodyParameter( entity.getName(), msg(locale, UPDATE_BODY_DESC_KEY, entity.getName()), definitionUpdatePath(entity.getClassName())); }
/** * Takes a list of entities and sorts them, according to relationships they have. The entities * that have uni-directional relationship with another entity, will be moved to the position * behind the entity they are related with. The bi-directional relationships are not sorted, * moreover if invalid bi-directional relationship is found, an exception is thrown. * * @param list Initial list of entities to sort * @return List of entities, sorted by relationship */ public static List<Entity> sortByHasARelation(List<Entity> list) { List<Entity> sorted = new ArrayList<>(list); MultiValueMap<String, String> unresolvedRelations = new LinkedMultiValueMap<>(); // we need to check if classes have 'has-a' relation // these classes should be later in list // we do that after all entities will be added to sorted list for (int i = 0; i < sorted.size(); ++i) { Entity entity = sorted.get(i); List<Field> fields = (List<Field>) CollectionUtils.select( entity.getFields(), new Predicate() { @Override public boolean evaluate(Object object) { return object instanceof Field && ((Field) object).getType().isRelationship(); } }); if (CollectionUtils.isNotEmpty(fields)) { int max = i; for (Field field : fields) { final RelationshipHolder holder = new RelationshipHolder(field); // For each field we perform a validation to spot circular, unresolvable relations, // which means the data model is incorrect unresolvedRelations = validateRelationship(unresolvedRelations, field, holder); assertRelationshipIsHistoryCompatible(field, holder, list); Entity relation = (Entity) CollectionUtils.find( sorted, new Predicate() { @Override public boolean evaluate(Object object) { return object instanceof Entity && ((Entity) object) .getClassName() .equalsIgnoreCase(holder.getRelatedClass()); } }); // In case the relation is bidirectional, we shouldn't move the class, // in order to avoid infinite loop boolean biDirectional = field.getMetadata(RELATED_FIELD) != null; max = Math.max(max, biDirectional ? -1 : sorted.indexOf(relation)); } if (max != i) { sorted.remove(i); --i; if (max < sorted.size()) { sorted.add(max, entity); } else { sorted.add(entity); } } } } return sorted; }
private Response lookup404Response(Entity entity, Locale locale) { return new Response(msg(locale, RESPONSE_LOOKUP_NOT_FOUND_KEY, entity.getName())); }