diff --git a/pom.xml b/pom.xml index 7c96e8e..baa9790 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,13 @@ 1.8 + + org.antlr + ST4 + 4.3 + compile + + diff --git a/src/main/java/com/patternpedia/api/PatternPediaAPI.java b/src/main/java/com/patternpedia/api/PatternPediaAPI.java index 3183bbf..60ebdf5 100644 --- a/src/main/java/com/patternpedia/api/PatternPediaAPI.java +++ b/src/main/java/com/patternpedia/api/PatternPediaAPI.java @@ -1,34 +1,23 @@ package com.patternpedia.api; -import com.patternpedia.api.rest.controller.UserController; -import com.patternpedia.api.service.IssueService; import com.vladmihalcea.hibernate.type.util.Configuration; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Contact; import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.info.License; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; -import org.springframework.web.bind.annotation.RestController; @EnableTransactionManagement @Slf4j -@RestController @SpringBootApplication @OpenAPIDefinition(info = @Info(title = "pattern-atlas-api", version = "1.0", contact = @Contact(url = "https://github.com/PatternAtlas/pattern-atlas-api", name = "Pattern Atlas API"))) public class PatternPediaAPI implements CommandLineRunner { - @Autowired - private UserController userController; - - @Autowired - private IssueService issueService; - public static void main(String[] args) { System.setProperty(Configuration.PropertyKey.PRINT_BANNER.getKey(), Boolean.FALSE.toString()); SpringApplication.run(PatternPediaAPI.class, args); @@ -36,15 +25,6 @@ public static void main(String[] args) { @Override public void run(String... args) { - log.info("PatternPediaAPI is up"); - // Used this for testing purposes, will be deleted in the final build -// userController.defaultUsers(); -// Issue issue = new Issue(); -// issue.setUri("uri"); -// issue.setName("name"); -// issue.setDescription("description"); -// Issue p = issueService.createIssue(issue); -// log.info(p.toString()); } } diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/AggregationData.java b/src/main/java/com/patternpedia/api/entities/designmodel/AggregationData.java new file mode 100644 index 0000000..3daa1e9 --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/AggregationData.java @@ -0,0 +1,32 @@ +package com.patternpedia.api.entities.designmodel; + +import com.patternpedia.api.rest.model.FileDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + + +@Data +public class AggregationData { + + private DesignModelPatternInstance source; + + private DesignModelPatternInstance target; + + private DesignModelPatternEdge edge; + + private Map templateContext = new HashMap<>(); + + private FileDTO result; + + + public AggregationData(DesignModelPatternInstance source, DesignModelPatternInstance target, DesignModelPatternEdge edge) { + this.source = source; + this.target = target; + this.edge = edge; + } +} diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/ConcreteSolution.java b/src/main/java/com/patternpedia/api/entities/designmodel/ConcreteSolution.java new file mode 100644 index 0000000..1c0deec --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/ConcreteSolution.java @@ -0,0 +1,35 @@ +package com.patternpedia.api.entities.designmodel; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.hateoas.server.core.Relation; + +import javax.persistence.*; +import java.util.List; +import java.util.UUID; + + +@Entity +@Data +@NoArgsConstructor +@EqualsAndHashCode +@Relation(value = "concreteSolution", collectionRelation = "concreteSolutions") +public class ConcreteSolution { + + @Id + @GeneratedValue(generator = "pg-uuid") + protected UUID id; + + @Column(nullable = false) + private String patternUri; + + private String name; + + @ElementCollection + private List properties; + + private String templateUri; + + private String aggregatorType; +} diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/DesignModel.java b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModel.java new file mode 100644 index 0000000..bf68cd5 --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModel.java @@ -0,0 +1,34 @@ +package com.patternpedia.api.entities.designmodel; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.patternpedia.api.entities.EntityWithURI; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public class DesignModel extends EntityWithURI { + + private URL logo; + + @JsonIgnore + @OneToMany(mappedBy = "designModel", cascade = CascadeType.ALL, orphanRemoval = true) + private List patterns = new ArrayList<>(); + + @JsonIgnore + @OneToMany(mappedBy = "designModel", cascade = CascadeType.ALL, orphanRemoval = true) + private List directedEdges; + + @JsonIgnore + @OneToMany(mappedBy = "designModel", cascade = CascadeType.ALL, orphanRemoval = true) + private List undirectedEdges; +} diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelEdgeType.java b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelEdgeType.java new file mode 100644 index 0000000..47aac0c --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelEdgeType.java @@ -0,0 +1,16 @@ +package com.patternpedia.api.entities.designmodel; + +import lombok.Data; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +@Data +public class DesignModelEdgeType { + + @Id + private String name; + + private Boolean swap; +} diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternEdge.java b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternEdge.java new file mode 100644 index 0000000..707714c --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternEdge.java @@ -0,0 +1,64 @@ +package com.patternpedia.api.entities.designmodel; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; + +import static java.lang.Boolean.TRUE; + +@Entity +@Data +@NoArgsConstructor +@EqualsAndHashCode +public class DesignModelPatternEdge { + + @EmbeddedId + @EqualsAndHashCode.Exclude + private DesignModelPatternEdgeId edgeId; + + @ManyToOne + @EqualsAndHashCode.Include + private DesignModel designModel; + + @ManyToOne + @MapsId("patternInstanceId1") + @EqualsAndHashCode.Include + private DesignModelPatternInstance patternInstance1; + + @ManyToOne + @MapsId("patternInstanceId2") + @EqualsAndHashCode.Include + private DesignModelPatternInstance patternInstance2; + + private Boolean isDirectedEdge; + + private String type; + + private String description; + + + public boolean isDirectedEdge() { + return TRUE.equals(isDirectedEdge); + } + + public void setPatternInstance1(DesignModelPatternInstance patternInstance) { + if(edgeId == null) { + this.edgeId = new DesignModelPatternEdgeId(); + } + this.edgeId.setPatternInstanceId1(patternInstance.getPatternInstanceId()); + this.patternInstance1 = patternInstance; + } + + public void setPatternInstance2(DesignModelPatternInstance patternInstance) { + if(edgeId == null) { + this.edgeId = new DesignModelPatternEdgeId(); + } + this.edgeId.setPatternInstanceId2(patternInstance.getPatternInstanceId()); + this.patternInstance2 = patternInstance; + } +} diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternEdgeId.java b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternEdgeId.java new file mode 100644 index 0000000..5ffc1b0 --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternEdgeId.java @@ -0,0 +1,22 @@ +package com.patternpedia.api.entities.designmodel; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Embeddable; +import java.io.Serializable; +import java.util.UUID; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class DesignModelPatternEdgeId implements Serializable { + + protected UUID patternInstanceId1; + + protected UUID patternInstanceId2; +} diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternGraphData.java b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternGraphData.java new file mode 100644 index 0000000..52768a0 --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternGraphData.java @@ -0,0 +1,23 @@ +package com.patternpedia.api.entities.designmodel; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Embeddable; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class DesignModelPatternGraphData { + + private Double x; + private Double y; + private Double vx; + private Double vy; + private String type; + private Integer index; +} diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternInstance.java b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternInstance.java new file mode 100644 index 0000000..97ed806 --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelPatternInstance.java @@ -0,0 +1,41 @@ +package com.patternpedia.api.entities.designmodel; + +import com.patternpedia.api.entities.Pattern; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.hateoas.server.core.Relation; + +import javax.persistence.*; +import java.util.UUID; + +@Entity +@Data +@NoArgsConstructor +@EqualsAndHashCode +@Relation(value = "pattern", collectionRelation = "patterns") +public class DesignModelPatternInstance { + + @Id + @GeneratedValue(generator = "pg-uuid") + protected UUID patternInstanceId; + + @ManyToOne + @EqualsAndHashCode.Include + private DesignModel designModel; + + @ManyToOne + @EqualsAndHashCode.Include + private Pattern pattern; + + private DesignModelPatternGraphData graphData; + + @Transient + private ConcreteSolution concreteSolution; + + + public DesignModelPatternInstance(DesignModel designModel, Pattern pattern) { + this.designModel = designModel; + this.pattern = pattern; + } +} diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelUndirectedEdge.java b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelUndirectedEdge.java new file mode 100644 index 0000000..604ca7b --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelUndirectedEdge.java @@ -0,0 +1,38 @@ +package com.patternpedia.api.entities.designmodel; + +import com.patternpedia.api.entities.UndirectedEdge; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; + +@Entity +@Data +@NoArgsConstructor +@EqualsAndHashCode +public class DesignModelUndirectedEdge { + + @EmbeddedId + @EqualsAndHashCode.Exclude + private DesignModelUndirectedEdgeId id; + + @ManyToOne + @MapsId("designModelId") + @EqualsAndHashCode.Include + private DesignModel designModel; + + @ManyToOne + @MapsId("undirectedEdgeId") + @EqualsAndHashCode.Include + private UndirectedEdge undirectedEdge; + + public DesignModelUndirectedEdge(DesignModel designModel, UndirectedEdge undirectedEdge) { + this.designModel = designModel; + this.undirectedEdge = undirectedEdge; + this.id = new DesignModelUndirectedEdgeId(designModel.getId(), undirectedEdge.getId()); + } +} diff --git a/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelUndirectedEdgeId.java b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelUndirectedEdgeId.java new file mode 100644 index 0000000..65a5d1e --- /dev/null +++ b/src/main/java/com/patternpedia/api/entities/designmodel/DesignModelUndirectedEdgeId.java @@ -0,0 +1,20 @@ +package com.patternpedia.api.entities.designmodel; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Embeddable; +import java.io.Serializable; +import java.util.UUID; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class DesignModelUndirectedEdgeId implements Serializable { + protected UUID designModelId; + protected UUID undirectedEdgeId; +} diff --git a/src/main/java/com/patternpedia/api/exception/AggregationException.java b/src/main/java/com/patternpedia/api/exception/AggregationException.java new file mode 100644 index 0000000..3d076ed --- /dev/null +++ b/src/main/java/com/patternpedia/api/exception/AggregationException.java @@ -0,0 +1,8 @@ +package com.patternpedia.api.exception; + +public class AggregationException extends RuntimeException { + + public AggregationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/patternpedia/api/exception/ConcreteSolutionNotFoundException.java b/src/main/java/com/patternpedia/api/exception/ConcreteSolutionNotFoundException.java new file mode 100644 index 0000000..bdf80d9 --- /dev/null +++ b/src/main/java/com/patternpedia/api/exception/ConcreteSolutionNotFoundException.java @@ -0,0 +1,21 @@ +package com.patternpedia.api.exception; + +import com.patternpedia.api.entities.designmodel.ConcreteSolution; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; + +import java.util.UUID; + +public class ConcreteSolutionNotFoundException extends ResourceNotFoundException { + + public ConcreteSolutionNotFoundException(String message) { + super(message); + } + + public ConcreteSolutionNotFoundException(UUID concreteSolutionId) { + super(String.format("ConcreteSolution \"%s\" not found!", concreteSolutionId)); + } + + public ConcreteSolutionNotFoundException(ConcreteSolution concreteSolution) { + super(String.format("ConcreteSolution \"%s\" not found!", concreteSolution.getId())); + } +} diff --git a/src/main/java/com/patternpedia/api/exception/DesignModelNotFoundException.java b/src/main/java/com/patternpedia/api/exception/DesignModelNotFoundException.java new file mode 100644 index 0000000..4261681 --- /dev/null +++ b/src/main/java/com/patternpedia/api/exception/DesignModelNotFoundException.java @@ -0,0 +1,21 @@ +package com.patternpedia.api.exception; + +import com.patternpedia.api.entities.designmodel.DesignModel; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; + +import java.util.UUID; + +public class DesignModelNotFoundException extends ResourceNotFoundException { + + public DesignModelNotFoundException(String message) { + super(message); + } + + public DesignModelNotFoundException(UUID designModelId) { + super(String.format("DesignModel \"%s\" not found!", designModelId)); + } + + public DesignModelNotFoundException(DesignModel designModel) { + super(String.format("DesignModel \"%s\" not found!", designModel.getId())); + } +} diff --git a/src/main/java/com/patternpedia/api/exception/DesignModelPatternInstanceNotFoundException.java b/src/main/java/com/patternpedia/api/exception/DesignModelPatternInstanceNotFoundException.java new file mode 100644 index 0000000..ec76f48 --- /dev/null +++ b/src/main/java/com/patternpedia/api/exception/DesignModelPatternInstanceNotFoundException.java @@ -0,0 +1,21 @@ +package com.patternpedia.api.exception; + +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; + +import java.util.UUID; + +public class DesignModelPatternInstanceNotFoundException extends ResourceNotFoundException { + + public DesignModelPatternInstanceNotFoundException(String message) { + super(message); + } + + public DesignModelPatternInstanceNotFoundException(UUID designModelId, UUID patternInstanceId) { + super(String.format("PatternInstance \"%s\" not found in DesignModel \"%s\"!", patternInstanceId, designModelId)); + } + + public DesignModelPatternInstanceNotFoundException(DesignModelPatternInstance patternInstance) { + super(String.format("PatternInstance \"%s\" not found in DesignModel \"%s\"!", patternInstance.getPatternInstanceId(), patternInstance.getDesignModel().getId())); + } +} diff --git a/src/main/java/com/patternpedia/api/exception/NullDesignModelException.java b/src/main/java/com/patternpedia/api/exception/NullDesignModelException.java new file mode 100644 index 0000000..6ece16c --- /dev/null +++ b/src/main/java/com/patternpedia/api/exception/NullDesignModelException.java @@ -0,0 +1,12 @@ +package com.patternpedia.api.exception; + +public class NullDesignModelException extends RuntimeException { + + public NullDesignModelException() { + super("DesignModel is null"); + } + + public NullDesignModelException(String message) { + super(message); + } +} diff --git a/src/main/java/com/patternpedia/api/repositories/ConcreteSolutionRepository.java b/src/main/java/com/patternpedia/api/repositories/ConcreteSolutionRepository.java new file mode 100644 index 0000000..9fd3076 --- /dev/null +++ b/src/main/java/com/patternpedia/api/repositories/ConcreteSolutionRepository.java @@ -0,0 +1,21 @@ +package com.patternpedia.api.repositories; + +import com.patternpedia.api.entities.designmodel.ConcreteSolution; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.net.URI; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + + +public interface ConcreteSolutionRepository extends JpaRepository { + + List findAllByPatternUri(String uri); + + Optional findTopByPatternUriAndAggregatorType(String uri, String technology); + + Optional findTopById(UUID uuid); + + boolean existsByPatternUri(URI uri); +} diff --git a/src/main/java/com/patternpedia/api/repositories/DesignModelEdgeTypeRepository.java b/src/main/java/com/patternpedia/api/repositories/DesignModelEdgeTypeRepository.java new file mode 100644 index 0000000..53bbcfd --- /dev/null +++ b/src/main/java/com/patternpedia/api/repositories/DesignModelEdgeTypeRepository.java @@ -0,0 +1,11 @@ +package com.patternpedia.api.repositories; + +import com.patternpedia.api.entities.designmodel.DesignModelEdgeType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface DesignModelEdgeTypeRepository extends JpaRepository { + + Optional findTopByName(String name); +} diff --git a/src/main/java/com/patternpedia/api/repositories/DesignModelPatternEdgeRepository.java b/src/main/java/com/patternpedia/api/repositories/DesignModelPatternEdgeRepository.java new file mode 100644 index 0000000..74780ad --- /dev/null +++ b/src/main/java/com/patternpedia/api/repositories/DesignModelPatternEdgeRepository.java @@ -0,0 +1,22 @@ +package com.patternpedia.api.repositories; + +import com.patternpedia.api.entities.designmodel.DesignModelPatternEdge; +import com.patternpedia.api.entities.designmodel.DesignModelPatternEdgeId; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@RepositoryRestResource(exported = false) +public interface DesignModelPatternEdgeRepository extends JpaRepository { + + Optional> findAllByDesignModelId(UUID patternViewId); + + Optional findTopByDesignModelIdAndEdgeId(UUID designModelId, DesignModelPatternEdgeId designModelPatternEdgeId); + + void deleteAllByDesignModel_IdAndPatternInstance1_PatternInstanceIdOrPatternInstance2_PatternInstanceId(UUID designModelId, UUID patternInstanceId1, UUID patternInstanceId2); + + void deleteAllByDesignModel_IdAndPatternInstance1_PatternInstanceIdAndPatternInstance2_PatternInstanceId(UUID designModelId, UUID patternInstanceId1, UUID patternInstanceId2); +} diff --git a/src/main/java/com/patternpedia/api/repositories/DesignModelPatternInstanceRepository.java b/src/main/java/com/patternpedia/api/repositories/DesignModelPatternInstanceRepository.java new file mode 100644 index 0000000..da261d6 --- /dev/null +++ b/src/main/java/com/patternpedia/api/repositories/DesignModelPatternInstanceRepository.java @@ -0,0 +1,19 @@ +package com.patternpedia.api.repositories; + +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@RepositoryRestResource(exported = false) +public interface DesignModelPatternInstanceRepository extends JpaRepository { + + Optional> findAllByDesignModelId(UUID patternViewId); + + Optional findTopByDesignModel_IdAndPatternInstanceId(UUID designModelId, UUID patternInstanceId); + + void deleteAllByDesignModel_IdAndPatternInstanceId(UUID designModelId, UUID patternInstanceId); +} diff --git a/src/main/java/com/patternpedia/api/repositories/DesignModelRepository.java b/src/main/java/com/patternpedia/api/repositories/DesignModelRepository.java new file mode 100644 index 0000000..8a9cd5c --- /dev/null +++ b/src/main/java/com/patternpedia/api/repositories/DesignModelRepository.java @@ -0,0 +1,16 @@ +package com.patternpedia.api.repositories; + +import com.patternpedia.api.entities.designmodel.DesignModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface DesignModelRepository extends JpaRepository { + + Optional findByUri(String uri); + + boolean existsByUri(String uri); + + boolean existsById(UUID designModelId); +} diff --git a/src/main/java/com/patternpedia/api/rest/controller/DesignModelController.java b/src/main/java/com/patternpedia/api/rest/controller/DesignModelController.java new file mode 100644 index 0000000..2c8b432 --- /dev/null +++ b/src/main/java/com/patternpedia/api/rest/controller/DesignModelController.java @@ -0,0 +1,226 @@ +package com.patternpedia.api.rest.controller; + +import com.patternpedia.api.entities.Pattern; +import com.patternpedia.api.entities.designmodel.*; +import com.patternpedia.api.rest.model.EdgeDTO; +import com.patternpedia.api.rest.model.FileDTO; +import com.patternpedia.api.rest.model.PatternInstanceDTO; +import com.patternpedia.api.rest.model.PositionDTO; +import com.patternpedia.api.service.ConcreteSolutionService; +import com.patternpedia.api.service.DesignModelService; +import lombok.extern.apachecommons.CommonsLog; +import org.apache.commons.text.CaseUtils; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.Link; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; + + +@RestController +@CommonsLog +@CrossOrigin(allowedHeaders = "*", origins = "*") +@RequestMapping(value = "/design-models", produces = "application/hal+json") +public class DesignModelController { + + private DesignModelService designModelService; + private ConcreteSolutionService concreteSolutionService; + + + public DesignModelController(DesignModelService designModelService, ConcreteSolutionService concreteSolutionService) { + this.designModelService = designModelService; + this.concreteSolutionService = concreteSolutionService; + } + + + private static List getDesignModelCollectionLinks() { + return Arrays.asList( + linkTo(methodOn(DesignModelController.class).getDesignModels()).withSelfRel() + .andAffordance(afford(methodOn(DesignModelController.class).createDesignModel(null))), + linkTo(methodOn(DesignModelController.class).getDesignModel(null)).withRel("designModel"), + linkTo(methodOn(DesignModelController.class).getDesignModelPatternEdgeTypes()).withRel("edgeTypes") + ); + } + + + private static List getDesignModelLinks(UUID designModelId, String selfRel) { + Map linkMap = new HashMap<>(); + + linkMap.put("designModels", methodOn(DesignModelController.class).getDesignModels()); + linkMap.put("designModel", methodOn(DesignModelController.class).getDesignModel(designModelId)); + linkMap.put("patterns", methodOn(DesignModelController.class).getDesignModelPatternInstances(designModelId)); + linkMap.put("edges", methodOn(DesignModelController.class).getDesignModelPatternEdges(designModelId)); + linkMap.put("edgeTypes", methodOn(DesignModelController.class).getDesignModelPatternEdgeTypes()); + linkMap.put("concreteSolutions", methodOn(DesignModelController.class).checkConcreteSolutions(designModelId)); + linkMap.put("aggregate", methodOn(DesignModelController.class).aggregateConcreteSolutions(designModelId, null)); + + List linkList = new ArrayList<>(); + if (linkMap.containsKey(selfRel)) { + linkList.add(linkTo(linkMap.get(selfRel)).withSelfRel()); + } else { + log.error("_self link for " + selfRel + " not found in linkMap"); + } + for (Map.Entry linkPair : linkMap.entrySet()) { + linkList.add(linkTo(linkPair.getValue()).withRel(linkPair.getKey())); + } + + return linkList; + } + + + @GetMapping("") + public CollectionModel> getDesignModels() { + + List> designModels = this.designModelService.getAllDesignModels() + .stream() + .map(designModel -> new EntityModel<>(designModel, getDesignModelLinks(designModel.getId(), "designModel"))) + .collect(toList()); + + return new CollectionModel<>(designModels, getDesignModelCollectionLinks()); + } + + + @PostMapping("") + @CrossOrigin(exposedHeaders = "Location") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createDesignModel(@RequestBody DesignModel designModel) { + String nameAsCamelCase = CaseUtils.toCamelCase(designModel.getName(), false); + String uri = String.format("https://patternpedia.org/designModels/%s", nameAsCamelCase); + designModel.setUri(uri); + + DesignModel createdDesignModel = this.designModelService.createDesignModel(designModel); + + return ResponseEntity.created(linkTo(methodOn(DesignModelController.class) + .getDesignModel(createdDesignModel.getId())).toUri()).build(); + } + + + @GetMapping("/edge-types") + public EntityModel>> getDesignModelPatternEdgeTypes() { + + List edgeTypes = this.designModelService.getDesignModelEdgeTypes().stream() + .map(DesignModelEdgeType::getName) + .collect(toList()); + + return new EntityModel<>(Collections.singletonMap("edgeTypes", edgeTypes), getDesignModelLinks(null, "edgeTypes")); + } + + + @GetMapping("/{designModelId}") + public EntityModel getDesignModel(@PathVariable UUID designModelId) { + DesignModel designModel = this.designModelService.getDesignModel(designModelId); + + return new EntityModel<>(designModel, getDesignModelLinks(designModel.getId(), "designModel")); + } + + + @GetMapping("/{designModelId}/patterns") + public CollectionModel> getDesignModelPatternInstances(@PathVariable UUID designModelId) { + List patternInstances = this.designModelService.getDesignModel(designModelId).getPatterns(); + + List> patterns = patternInstances.stream() + .map(PatternInstanceDTO::from) + .map(patternModel -> new EntityModel<>(patternModel)) + .collect(toList()); + + return new CollectionModel<>(patterns, getDesignModelLinks(designModelId, "patterns")); + } + + + @PostMapping("/{designModelId}/patterns") + @CrossOrigin(exposedHeaders = "Location") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity addDesignModelPatternInstance(@PathVariable UUID designModelId, @RequestBody Pattern pattern) { + + this.designModelService.addPatternInstance(designModelId, pattern.getId()); + + return ResponseEntity.created(linkTo(methodOn(DesignModelController.class).getDesignModelPatternInstances(designModelId)).toUri()).build(); + } + + + @PutMapping("/{designModelId}/patterns/{patternInstanceId}/position") + public ResponseEntity putDesignModelPatternInstancePosition(@PathVariable UUID designModelId, @PathVariable UUID patternInstanceId, @RequestBody PositionDTO position) { + + this.designModelService.updatePatternInstancePosition(designModelId, patternInstanceId, position.getX(), position.getY()); + + return ResponseEntity.created(linkTo(methodOn(DesignModelController.class).getDesignModelPatternInstances(designModelId)).toUri()).build(); + } + + + @DeleteMapping("/{designModelId}/patterns/{patternInstanceId}") + public ResponseEntity deleteDesignModelPatternInstance(@PathVariable UUID designModelId, @PathVariable UUID patternInstanceId) { + + this.designModelService.deletePatternInstance(designModelId, patternInstanceId); + + return ResponseEntity.ok().build(); + } + + + @PostMapping("/{designModelId}/edges") + @CrossOrigin(exposedHeaders = "Location") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity addDesignModelEdge(@PathVariable UUID designModelId, @RequestBody EdgeDTO edgeDTO) { + + this.designModelService.addEdge(designModelId, edgeDTO.getFirstPatternId(), edgeDTO.getSecondPatternId(), + edgeDTO.isDirectedEdge(), edgeDTO.getType(), edgeDTO.getDescription()); + + return ResponseEntity.created(linkTo(methodOn(DesignModelController.class) + .getDesignModelPatternEdges(designModelId)).toUri()).build(); + } + + + @GetMapping("/{designModelId}/edges") + public CollectionModel> getDesignModelPatternEdges(@PathVariable UUID designModelId) { + + List designModelPatternEdges = this.designModelService.getEdges(designModelId); + + List> edges = designModelPatternEdges.parallelStream() + .map(EdgeDTO::from) + .map(edgeDTO -> new EntityModel<>(edgeDTO)) + .collect(toList()); + + return new CollectionModel<>(edges, getDesignModelLinks(designModelId, "edges")); + } + + + @DeleteMapping("/{designModelId}/edges/{sourceId}/{targetId}") + public ResponseEntity getDesignModelPatternEdges(@PathVariable UUID designModelId, @PathVariable UUID sourceId, @PathVariable UUID targetId) { + + this.designModelService.deleteEdge(designModelId, sourceId, targetId); + + return ResponseEntity.ok().build(); + } + + + @GetMapping("/{designModelId}/concrete-solutions") + public CollectionModel checkConcreteSolutions(@PathVariable UUID designModelId) { + List patternInstanceList = this.designModelService.getDesignModel(designModelId).getPatterns(); + Set patternUris = patternInstanceList.stream().map(patternInstance -> patternInstance.getPattern().getUri()).collect(Collectors.toSet()); + Set concreteSolutionSet = new HashSet<>(); + + for (String uri : patternUris) { + this.concreteSolutionService.getConcreteSolutions(URI.create(uri)).forEach(concreteSolution -> concreteSolutionSet.add(concreteSolution)); + } + + return new CollectionModel<>(concreteSolutionSet, getDesignModelLinks(designModelId, "concreteSolutions")); + } + + + @PostMapping("/{designModelId}/aggregate") + public List aggregateConcreteSolutions(@PathVariable UUID designModelId, @RequestBody Map patternConcreteSolutionMap) { + + DesignModel designModel = this.designModelService.getDesignModel(designModelId); + List patternInstanceList = designModel.getPatterns(); + List directedEdgeList = designModel.getDirectedEdges(); + + return this.concreteSolutionService.aggregate(patternInstanceList, directedEdgeList, patternConcreteSolutionMap); + } +} diff --git a/src/main/java/com/patternpedia/api/rest/controller/PatternLanguageController.java b/src/main/java/com/patternpedia/api/rest/controller/PatternLanguageController.java index 3779b68..8038024 100644 --- a/src/main/java/com/patternpedia/api/rest/controller/PatternLanguageController.java +++ b/src/main/java/com/patternpedia/api/rest/controller/PatternLanguageController.java @@ -11,7 +11,7 @@ import com.patternpedia.api.entities.PatternLanguage; import com.patternpedia.api.entities.PatternSchema; -import com.patternpedia.api.rest.model.PatternLanguageGraphModel; +import com.patternpedia.api.rest.model.GraphModel; import com.patternpedia.api.rest.model.PatternLanguageModel; import com.patternpedia.api.service.PatternLanguageService; @@ -161,7 +161,7 @@ ResponseEntity updatePatternSchema(@PathVariable UUID patternLanguageId, @ HttpEntity> getPatternLanguageGraph(@PathVariable UUID patternLanguageId) { Object graph = this.patternLanguageService.getGraphOfPatternLanguage(patternLanguageId); - PatternLanguageGraphModel model = new PatternLanguageGraphModel(); + GraphModel model = new GraphModel(); if (null == graph) { model.setGraph(this.objectMapper.createArrayNode()); } else { diff --git a/src/main/java/com/patternpedia/api/rest/controller/PatternViewController.java b/src/main/java/com/patternpedia/api/rest/controller/PatternViewController.java index 195fe36..36e1ea5 100644 --- a/src/main/java/com/patternpedia/api/rest/controller/PatternViewController.java +++ b/src/main/java/com/patternpedia/api/rest/controller/PatternViewController.java @@ -1,19 +1,10 @@ package com.patternpedia.api.rest.controller; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -import com.patternpedia.api.entities.PatternView; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.ObjectMapper; -import com.patternpedia.api.rest.model.PatternLanguageGraphModel; +import com.patternpedia.api.entities.PatternView; +import com.patternpedia.api.rest.model.GraphModel; import com.patternpedia.api.service.PatternViewService; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -25,9 +16,15 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.afford; -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; @RestController @CrossOrigin(allowedHeaders = "*", origins = "*") @@ -82,7 +79,7 @@ private static List getPatternViewLinks(PatternView patternView) { ResponseEntity getPatterViewGraph(@PathVariable UUID patternViewId) { Object graph = this.patternViewService.getGraphOfPatternView(patternViewId); - PatternLanguageGraphModel model = new PatternLanguageGraphModel(); + GraphModel model = new GraphModel(); if (null == graph) { model.setGraph(this.objectMapper.createArrayNode()); } else { diff --git a/src/main/java/com/patternpedia/api/rest/exception/RestResponseExceptionHandler.java b/src/main/java/com/patternpedia/api/rest/exception/RestResponseExceptionHandler.java index fcfb91d..59b3a90 100644 --- a/src/main/java/com/patternpedia/api/rest/exception/RestResponseExceptionHandler.java +++ b/src/main/java/com/patternpedia/api/rest/exception/RestResponseExceptionHandler.java @@ -1,6 +1,8 @@ package com.patternpedia.api.rest.exception; -import com.patternpedia.api.exception.*; +import com.patternpedia.api.exception.NullPatternSchemaException; +import com.patternpedia.api.rest.model.ErrorMessageDTO; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -9,26 +11,32 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + @ControllerAdvice public class RestResponseExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(value = { - PatternLanguageNotFoundException.class, - PatternNotFoundException.class, - PatternSchemaNotFoundException.class, - PatternViewNotFoundException.class, - DirectedEdgeNotFoundException.class, - UndirectedEdgeNotFoundException.class + ResourceNotFoundException.class }) protected ResponseEntity handleEntityNotFoundExceptions(RuntimeException ex, WebRequest request) { - return handleExceptionInternal(ex, ex.getMessage(), new HttpHeaders(), HttpStatus.NOT_FOUND, request); + ErrorMessageDTO errorMessage = new ErrorMessageDTO(ex.getMessage(), HttpStatus.NOT_FOUND); + return handleExceptionInternal(ex, errorMessage, new HttpHeaders(), HttpStatus.NOT_FOUND, request); } @ExceptionHandler(value = { NullPatternSchemaException.class }) protected ResponseEntity handleNullPatternSchemaException(RuntimeException ex, WebRequest request) { - return handleExceptionInternal(ex, ex.getMessage(), new HttpHeaders(), HttpStatus.BAD_REQUEST, request); + ErrorMessageDTO errorMessage = new ErrorMessageDTO(ex.getMessage(), HttpStatus.BAD_REQUEST); + return handleExceptionInternal(ex, errorMessage, new HttpHeaders(), HttpStatus.BAD_REQUEST, request); + } + + @ExceptionHandler(value = { + Exception.class + }) + protected ResponseEntity handleStorageExceptions(RuntimeException ex, WebRequest request) { + ErrorMessageDTO errorMessage = new ErrorMessageDTO(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + return handleExceptionInternal(ex, errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request); } } diff --git a/src/main/java/com/patternpedia/api/rest/model/EdgeDTO.java b/src/main/java/com/patternpedia/api/rest/model/EdgeDTO.java new file mode 100644 index 0000000..cc39b89 --- /dev/null +++ b/src/main/java/com/patternpedia/api/rest/model/EdgeDTO.java @@ -0,0 +1,68 @@ +package com.patternpedia.api.rest.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.patternpedia.api.entities.designmodel.DesignModelPatternEdge; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.hateoas.server.core.Relation; + +import java.util.UUID; + + +@NoArgsConstructor +@Data +@EqualsAndHashCode +@Relation(value = "edge", collectionRelation = "edges") +@JsonInclude(JsonInclude.Include.NON_NULL) +public class EdgeDTO { + + private UUID sourcePatternId; + + private UUID targetPatternId; + + private UUID pattern1Id; + + private UUID pattern2Id; + + private String type; + + private String description; + + + public boolean isDirectedEdge() { + return sourcePatternId != null && targetPatternId != null; + } + + @JsonIgnore + public UUID getFirstPatternId() { + return isDirectedEdge() ? sourcePatternId : pattern1Id; + } + + @JsonIgnore + public UUID getSecondPatternId() { + return isDirectedEdge() ? targetPatternId : pattern2Id; + } + + + public static EdgeDTO from(DesignModelPatternEdge designModelPatternEdge) { + EdgeDTO edgeDTO = new EdgeDTO(); + + UUID pattern1 = designModelPatternEdge.getPatternInstance1().getPatternInstanceId(); + UUID pattern2 = designModelPatternEdge.getPatternInstance2().getPatternInstanceId(); + + if (designModelPatternEdge.isDirectedEdge()) { + edgeDTO.setSourcePatternId(pattern1); + edgeDTO.setTargetPatternId(pattern2); + } else { + edgeDTO.setPattern1Id(pattern1); + edgeDTO.setPattern2Id(pattern2); + } + + edgeDTO.setType(designModelPatternEdge.getType()); + edgeDTO.setDescription(designModelPatternEdge.getDescription()); + + return edgeDTO; + } +} diff --git a/src/main/java/com/patternpedia/api/rest/model/ErrorMessageDTO.java b/src/main/java/com/patternpedia/api/rest/model/ErrorMessageDTO.java new file mode 100644 index 0000000..3d01518 --- /dev/null +++ b/src/main/java/com/patternpedia/api/rest/model/ErrorMessageDTO.java @@ -0,0 +1,32 @@ +package com.patternpedia.api.rest.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import org.springframework.http.HttpStatus; + +import java.time.ZonedDateTime; + + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ErrorMessageDTO { + + protected Integer status; + protected String message; + protected ZonedDateTime time; + + public ErrorMessageDTO(String message) { + this.message = message; + this.time = ZonedDateTime.now(); + } + + public ErrorMessageDTO(String message, int status) { + this(message); + this.status = status; + } + + public ErrorMessageDTO(String message, HttpStatus status) { + this(message); + this.status = status.value(); + } +} diff --git a/src/main/java/com/patternpedia/api/rest/model/FileDTO.java b/src/main/java/com/patternpedia/api/rest/model/FileDTO.java new file mode 100644 index 0000000..20c3512 --- /dev/null +++ b/src/main/java/com/patternpedia/api/rest/model/FileDTO.java @@ -0,0 +1,22 @@ +package com.patternpedia.api.rest.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + + +@NoArgsConstructor +@AllArgsConstructor +@Data +@EqualsAndHashCode +@JsonInclude(JsonInclude.Include.NON_NULL) +public class FileDTO { + + private String name; + + private String mime; + + private String file; +} diff --git a/src/main/java/com/patternpedia/api/rest/model/PatternLanguageGraphModel.java b/src/main/java/com/patternpedia/api/rest/model/GraphModel.java similarity index 78% rename from src/main/java/com/patternpedia/api/rest/model/PatternLanguageGraphModel.java rename to src/main/java/com/patternpedia/api/rest/model/GraphModel.java index 4459c9f..f8d1035 100644 --- a/src/main/java/com/patternpedia/api/rest/model/PatternLanguageGraphModel.java +++ b/src/main/java/com/patternpedia/api/rest/model/GraphModel.java @@ -5,6 +5,6 @@ @NoArgsConstructor @Data -public class PatternLanguageGraphModel { +public class GraphModel { private Object graph; } diff --git a/src/main/java/com/patternpedia/api/rest/model/PatternInstanceDTO.java b/src/main/java/com/patternpedia/api/rest/model/PatternInstanceDTO.java new file mode 100644 index 0000000..91a9a66 --- /dev/null +++ b/src/main/java/com/patternpedia/api/rest/model/PatternInstanceDTO.java @@ -0,0 +1,47 @@ +package com.patternpedia.api.rest.model; + +import com.patternpedia.api.entities.PatternLanguage; +import com.patternpedia.api.entities.designmodel.DesignModelPatternGraphData; +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.hateoas.server.core.Relation; + +import java.util.UUID; + + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +@Relation(value = "pattern", collectionRelation = "patterns") +public class PatternInstanceDTO extends PatternModel { + + protected DesignModelPatternGraphData graphData; + + protected UUID patternLanguageId; + + protected double x; + + protected double y; + + + public static PatternInstanceDTO from(DesignModelPatternInstance dmpi) { + PatternInstanceDTO gpm = new PatternInstanceDTO(); + + + gpm.setName(dmpi.getPattern().getName()); + gpm.setId(dmpi.getPatternInstanceId()); + gpm.setPattern(dmpi.getPattern()); + gpm.setUri(dmpi.getPattern().getUri()); + gpm.setIconUrl(dmpi.getPattern().getIconUrl()); + + PatternLanguage patternLanguage = dmpi.getPattern().getPatternLanguage(); + gpm.setPatternLanguageId(patternLanguage.getId()); + gpm.setPatternLanguageName(patternLanguage.getName()); + + gpm.setGraphData(dmpi.getGraphData()); + + return gpm; + } +} diff --git a/src/main/java/com/patternpedia/api/rest/model/PatternModel.java b/src/main/java/com/patternpedia/api/rest/model/PatternModel.java index 6fd0a9f..0a6be1c 100644 --- a/src/main/java/com/patternpedia/api/rest/model/PatternModel.java +++ b/src/main/java/com/patternpedia/api/rest/model/PatternModel.java @@ -1,34 +1,33 @@ package com.patternpedia.api.rest.model; -import java.util.UUID; - -import com.patternpedia.api.entities.Pattern; - import com.fasterxml.jackson.annotation.JsonIgnore; +import com.patternpedia.api.entities.Pattern; import com.patternpedia.api.entities.PatternLanguage; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.UUID; + @NoArgsConstructor @Data @EqualsAndHashCode(callSuper = false) public class PatternModel { - private UUID id; + protected UUID id; - private String uri; + protected String uri; - private String name; + protected String name; - private String iconUrl; + protected String iconUrl; - private UUID patternLanguageId; + protected UUID patternLanguageId; - private String patternLanguageName; + protected String patternLanguageName; @JsonIgnore - private Pattern pattern; + protected Pattern pattern; private PatternModel(Pattern pattern) { this.pattern = pattern; diff --git a/src/main/java/com/patternpedia/api/rest/model/PositionDTO.java b/src/main/java/com/patternpedia/api/rest/model/PositionDTO.java new file mode 100644 index 0000000..6659607 --- /dev/null +++ b/src/main/java/com/patternpedia/api/rest/model/PositionDTO.java @@ -0,0 +1,16 @@ +package com.patternpedia.api.rest.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class PositionDTO { + + protected double x; + + protected double y; +} diff --git a/src/main/java/com/patternpedia/api/service/ConcreteSolutionService.java b/src/main/java/com/patternpedia/api/service/ConcreteSolutionService.java new file mode 100644 index 0000000..929a8a3 --- /dev/null +++ b/src/main/java/com/patternpedia/api/service/ConcreteSolutionService.java @@ -0,0 +1,23 @@ +package com.patternpedia.api.service; + +import com.patternpedia.api.entities.designmodel.ConcreteSolution; +import com.patternpedia.api.entities.designmodel.DesignModelPatternEdge; +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; +import com.patternpedia.api.rest.model.FileDTO; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.UUID; + + +public interface ConcreteSolutionService { + + List getConcreteSolutions(); + + List getConcreteSolutions(URI patternUri); + + ConcreteSolution getConcreteSolution(UUID uuid); + + List aggregate(List patternInstances, List edges, Map concreteSolutionMapping); +} diff --git a/src/main/java/com/patternpedia/api/service/ConcreteSolutionServiceImpl.java b/src/main/java/com/patternpedia/api/service/ConcreteSolutionServiceImpl.java new file mode 100644 index 0000000..30da930 --- /dev/null +++ b/src/main/java/com/patternpedia/api/service/ConcreteSolutionServiceImpl.java @@ -0,0 +1,200 @@ +package com.patternpedia.api.service; + +import com.patternpedia.api.entities.designmodel.*; +import com.patternpedia.api.exception.AggregationException; +import com.patternpedia.api.exception.ConcreteSolutionNotFoundException; +import com.patternpedia.api.repositories.ConcreteSolutionRepository; +import com.patternpedia.api.repositories.DesignModelEdgeTypeRepository; +import com.patternpedia.api.rest.model.FileDTO; +import com.patternpedia.api.util.aggregator.Aggregator; +import com.patternpedia.api.util.aggregator.AggregatorScanner; +import lombok.extern.apachecommons.CommonsLog; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; + + +@Service +@CommonsLog +public class ConcreteSolutionServiceImpl implements ConcreteSolutionService { + + private final ConcreteSolutionRepository concreteSolutionRepository; + private final DesignModelEdgeTypeRepository designModelEdgeTypeRepository; + + + public ConcreteSolutionServiceImpl(ConcreteSolutionRepository concreteSolutionRepository, + DesignModelEdgeTypeRepository designModelEdgeTypeRepository) { + this.concreteSolutionRepository = concreteSolutionRepository; + this.designModelEdgeTypeRepository = designModelEdgeTypeRepository; + } + + + public List getConcreteSolutions() { + return this.concreteSolutionRepository.findAll(); + } + + + public List getConcreteSolutions(URI patternUri) { + return this.concreteSolutionRepository.findAllByPatternUri(patternUri.toString()); + } + + + public ConcreteSolution getConcreteSolution(UUID uuid) { + return this.concreteSolutionRepository.findTopById(uuid) + .orElseThrow(() -> new ConcreteSolutionNotFoundException(uuid)); + } + + + private void linkConcreteSolutionsToPatternInstances(List patternInstances, Map concreteSolutionMapping) { + for (DesignModelPatternInstance patternInstance : patternInstances) { + UUID piId = patternInstance.getPatternInstanceId(); + UUID csId = concreteSolutionMapping.get(piId); + ConcreteSolution cs = this.concreteSolutionRepository.findTopById(csId) + .orElseThrow(() -> new ConcreteSolutionNotFoundException(csId)); + patternInstance.setConcreteSolution(cs); + } + } + + + private void swapEdgeDirections(List edges) { + Set edgeTypesToSwapDirections = this.designModelEdgeTypeRepository.findAll().stream() + .filter(DesignModelEdgeType::getSwap) + .map(DesignModelEdgeType::getName) + .collect(Collectors.toSet()); + + for (DesignModelPatternEdge edge : edges) { + if (edge.isDirectedEdge() && edgeTypesToSwapDirections.contains(edge.getType())) { + DesignModelPatternInstance source = edge.getPatternInstance1(); + edge.setPatternInstance1(edge.getPatternInstance2()); + edge.setPatternInstance2(source); + } + } + } + + + private Set findRootNodes(List patternInstances, List edges) { + Set rootNodes = new HashSet<>(); + + for (DesignModelPatternInstance patternInstance : patternInstances) { + rootNodes.add(patternInstance.getPatternInstanceId()); + } + + for (DesignModelPatternEdge edge : edges) { + rootNodes.remove(edge.getPatternInstance1().getPatternInstanceId()); + } + + log.info("Root nodes: " + rootNodes.toString()); + return rootNodes; + } + + + private Set findLeafNodes(List patternInstances, List edges) { + Set leafNodes = new HashSet<>(); + + for (DesignModelPatternInstance patternInstance : patternInstances) { + leafNodes.add(patternInstance.getPatternInstanceId()); + } + + for (DesignModelPatternEdge edge : edges) { + leafNodes.remove(edge.getPatternInstance2().getPatternInstanceId()); + } + + return leafNodes; + } + + + private UUID lastLeafUUID = null; + + private AggregationData getLeafAndPredecessor(List patternInstances, List edges) { + Set leafNodes = findLeafNodes(patternInstances, edges); + + if (leafNodes.isEmpty()) { + throw new RuntimeException("No leaf node found"); + } + + final UUID leafNodeUUID = leafNodes.contains(lastLeafUUID) ? lastLeafUUID : leafNodes.iterator().next(); + lastLeafUUID = leafNodeUUID; + + DesignModelPatternInstance leafPatternInstance = patternInstances.stream() + .filter(designModelPatternInstance -> leafNodeUUID.equals(designModelPatternInstance.getPatternInstanceId())) + .findAny().orElse(null); + + if (patternInstances.size() == 1) { + return new AggregationData(leafPatternInstance, null, null); + } + + DesignModelPatternEdge edge = edges.stream().filter(designModelPatternEdge -> leafNodeUUID.equals(designModelPatternEdge.getPatternInstance1().getPatternInstanceId())).findAny().orElse(null); + + DesignModelPatternInstance predecessorPatternInstance = null; + + if (edge != null) { + UUID predecessorNodeUUID = edge.getPatternInstance2().getPatternInstanceId(); + + predecessorPatternInstance = patternInstances.stream() + .filter(designModelPatternInstance -> predecessorNodeUUID.equals(designModelPatternInstance.getPatternInstanceId())) + .findAny().orElse(null); + + log.info("Found leaf [" + leafNodeUUID.toString() + "] and predecessor [" + predecessorNodeUUID.toString() + "]: " + leafPatternInstance.getPattern().getName() + " ---" + edge.getType() + "--- " + predecessorPatternInstance.getPattern().getName()); + } + + return new AggregationData(leafPatternInstance, predecessorPatternInstance, edge); + } + + + private void aggregate(AggregationData aggregationData) { + + String sourceAggregationType = aggregationData.getSource().getConcreteSolution().getAggregatorType(); + String targetAggregationType = null; + try { + targetAggregationType = aggregationData.getTarget().getConcreteSolution().getAggregatorType(); + } catch (NullPointerException ignored) { + } + + Aggregator aggregator = AggregatorScanner.findMatchingAggregatorImpl(sourceAggregationType, targetAggregationType); + + if (aggregator == null) { + throw new AggregationException("Aggregation type combination is not yet supported: [" + sourceAggregationType + "] --> [" + targetAggregationType + "]"); + } + + try { + aggregator.aggregate(aggregationData); + } catch (AggregationException e) { + throw new AggregationException("Failed to aggregate concrete solutions"); + } + } + + + public List aggregate(List patternInstances, List edges, Map concreteSolutionMapping) { + + linkConcreteSolutionsToPatternInstances(patternInstances, concreteSolutionMapping); + + swapEdgeDirections(edges); + + Map templateContext = new HashMap<>(); + List aggregatedFiles = new ArrayList<>(); + + while (!patternInstances.isEmpty()) { + AggregationData aggregationData = getLeafAndPredecessor(patternInstances, edges); + + aggregationData.setTemplateContext(templateContext); + + aggregate(aggregationData); + + templateContext = aggregationData.getTemplateContext(); + + if (aggregationData.getResult() != null) { + aggregatedFiles.add(aggregationData.getResult()); + } + + edges.remove(aggregationData.getEdge()); + if (!edges.stream().anyMatch(edge -> aggregationData.getSource().getPatternInstanceId().equals(edge.getPatternInstance1().getPatternInstanceId()))) { + patternInstances.remove(aggregationData.getSource()); + } + } + + + return aggregatedFiles; + } +} diff --git a/src/main/java/com/patternpedia/api/service/DesignModelService.java b/src/main/java/com/patternpedia/api/service/DesignModelService.java new file mode 100644 index 0000000..2fd527c --- /dev/null +++ b/src/main/java/com/patternpedia/api/service/DesignModelService.java @@ -0,0 +1,34 @@ +package com.patternpedia.api.service; + +import com.patternpedia.api.entities.designmodel.DesignModel; +import com.patternpedia.api.entities.designmodel.DesignModelEdgeType; +import com.patternpedia.api.entities.designmodel.DesignModelPatternEdge; +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; + +import java.util.List; +import java.util.UUID; + +public interface DesignModelService { + + DesignModel createDesignModel(DesignModel designModel); + + List getAllDesignModels(); + + DesignModel getDesignModel(UUID designModelId); + + List getDesignModelEdgeTypes(); + + void addPatternInstance(UUID designModelId, UUID patternId); + + void deletePatternInstance(UUID designModelId, UUID patternInstanceId); + + DesignModelPatternInstance getPatternInstance(UUID designModelId, UUID patternInstanceId); + + void updatePatternInstancePosition(UUID designModelId, UUID patternInstanceId, Double x, Double y); + + List getEdges(UUID designModelId); + + void addEdge(UUID designModelId, UUID patternInstanceId1, UUID patternInstanceId2, Boolean directed, String type, String description); + + void deleteEdge(UUID designModelId, UUID patternInstanceId1, UUID patternInstanceId2); +} diff --git a/src/main/java/com/patternpedia/api/service/DesignModelServiceImpl.java b/src/main/java/com/patternpedia/api/service/DesignModelServiceImpl.java new file mode 100644 index 0000000..301337f --- /dev/null +++ b/src/main/java/com/patternpedia/api/service/DesignModelServiceImpl.java @@ -0,0 +1,179 @@ +package com.patternpedia.api.service; + +import com.patternpedia.api.entities.Pattern; +import com.patternpedia.api.entities.designmodel.*; +import com.patternpedia.api.exception.DesignModelNotFoundException; +import com.patternpedia.api.exception.DesignModelPatternInstanceNotFoundException; +import com.patternpedia.api.exception.NullDesignModelException; +import com.patternpedia.api.repositories.*; +import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +@Service +@Transactional +public class DesignModelServiceImpl implements DesignModelService { + + private PatternService patternService; + private DesignModelRepository designModelRepository; + private DesignModelPatternInstanceRepository designModelPatternInstanceRepository; + private DesignModelPatternEdgeRepository designModelPatternEdgeRepository; + private DesignModelEdgeTypeRepository designModelEdgeTypeRepository; + + + public DesignModelServiceImpl(PatternService patternService, + PatternRelationDescriptorService patternRelationDescriptorService, + DesignModelRepository designModelRepository, + DesignModelPatternInstanceRepository designModelPatternInstanceRepository, + DesignModelPatternEdgeRepository designModelPatternEdgeRepository, + PatternViewDirectedEdgeRepository patternViewDirectedEdgeRepository, + PatternViewUndirectedEdgeRepository patternViewUndirectedEdgeRepository, + DirectedEdgeRepository directedEdgeRepository, + UndirectedEdgeReository undirectedEdgeReository, + DesignModelEdgeTypeRepository designModelEdgeTypeRepository) { + this.patternService = patternService; + this.designModelRepository = designModelRepository; + this.designModelPatternInstanceRepository = designModelPatternInstanceRepository; + this.designModelPatternEdgeRepository = designModelPatternEdgeRepository; + this.designModelEdgeTypeRepository = designModelEdgeTypeRepository; + } + + + @Override + @Transactional + public DesignModel createDesignModel(DesignModel designModel) { + if (null == designModel) { + throw new NullDesignModelException(); + } + + return this.designModelRepository.save(designModel); + } + + + @Override + @Transactional(readOnly = true) + public List getAllDesignModels() { + try { + return this.designModelRepository.findAll(); + } catch (InvalidDataAccessResourceUsageException e) { + return Collections.emptyList(); + } + } + + + @Override + @Transactional(readOnly = true) + public DesignModel getDesignModel(UUID designModelId) { + DesignModel designModel = this.designModelRepository.findById(designModelId) + .orElseThrow(() -> new DesignModelNotFoundException(designModelId)); + + return designModel; + } + + + public List getDesignModelEdgeTypes() { + return this.designModelEdgeTypeRepository.findAll(); + } + + + @Override + @Transactional(readOnly = true) + public DesignModelPatternInstance getPatternInstance(UUID designModelId, UUID patternInstanceId) { + DesignModelPatternInstance patternInstance = this.designModelPatternInstanceRepository + .findTopByDesignModel_IdAndPatternInstanceId(designModelId, patternInstanceId) + .orElseThrow(() -> new DesignModelPatternInstanceNotFoundException(designModelId, patternInstanceId)); + + return patternInstance; + } + + + @Override + @Transactional + public void addPatternInstance(UUID patternViewId, UUID patternId) { + DesignModel designModel = this.getDesignModel(patternViewId); + Pattern pattern = this.patternService.getPatternById(patternId); + + DesignModelPatternInstance designModelPattern = new DesignModelPatternInstance(designModel, pattern); + this.designModelPatternInstanceRepository.save(designModelPattern); + } + + + @Override + @Transactional + public void deletePatternInstance(UUID designModelId, UUID patternInstanceId) { + this.designModelPatternEdgeRepository.deleteAllByDesignModel_IdAndPatternInstance1_PatternInstanceIdOrPatternInstance2_PatternInstanceId( + designModelId, patternInstanceId, patternInstanceId + ); + this.designModelPatternInstanceRepository.deleteAllByDesignModel_IdAndPatternInstanceId(designModelId, patternInstanceId); + } + + + @Override + @Transactional + public void updatePatternInstancePosition(UUID designModelId, UUID patternInstanceId, Double x, Double y) { + DesignModelPatternInstance patternInstance = this.designModelPatternInstanceRepository + .findTopByDesignModel_IdAndPatternInstanceId(designModelId, patternInstanceId) + .orElseThrow(() -> new DesignModelPatternInstanceNotFoundException(designModelId, patternInstanceId)); + + DesignModelPatternGraphData graphData = new DesignModelPatternGraphData(); + graphData.setX(x); + graphData.setY(y); + + patternInstance.setGraphData(graphData); + + this.designModelPatternInstanceRepository.save(patternInstance); + } + + + // DirectedEdge Handling + @Override + @Transactional(readOnly = true) + public List getEdges(UUID designModelId) { + List edgeList = this.designModelPatternEdgeRepository.findAllByDesignModelId(designModelId).orElse(Collections.emptyList()); + + if (edgeList.isEmpty() && !this.designModelRepository.existsById(designModelId)) { + throw new DesignModelNotFoundException(designModelId); + } + + return edgeList; + } + + + @Override + @Transactional + public void addEdge(UUID designModelId, UUID patternInstanceId1, UUID patternInstanceId2, Boolean directed, String type, String description) { + DesignModel designModel = this.designModelRepository.findById(designModelId) + .orElseThrow(() -> new DesignModelNotFoundException(designModelId)); + + DesignModelPatternInstance patternInstance1 = this.designModelPatternInstanceRepository + .findTopByDesignModel_IdAndPatternInstanceId(designModelId, patternInstanceId1) + .orElseThrow(() -> new DesignModelPatternInstanceNotFoundException(designModelId, patternInstanceId1)); + + DesignModelPatternInstance patternInstance2 = this.designModelPatternInstanceRepository + .findTopByDesignModel_IdAndPatternInstanceId(designModelId, patternInstanceId2) + .orElseThrow(() -> new DesignModelPatternInstanceNotFoundException(designModelId, patternInstanceId2)); + + DesignModelPatternEdge designModelEdge = new DesignModelPatternEdge(); + designModelEdge.setDesignModel(designModel); + designModelEdge.setPatternInstance1(patternInstance1); + designModelEdge.setPatternInstance2(patternInstance2); + designModelEdge.setIsDirectedEdge(directed); + designModelEdge.setType(type); + designModelEdge.setDescription(description); + + this.designModelPatternEdgeRepository.save(designModelEdge); + } + + + @Override + @Transactional + public void deleteEdge(UUID designModelId, UUID patternInstanceId1, UUID patternInstanceId2) { + this.designModelPatternEdgeRepository.deleteAllByDesignModel_IdAndPatternInstance1_PatternInstanceIdAndPatternInstance2_PatternInstanceId( + designModelId, patternInstanceId1, patternInstanceId2 + ); + } +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/AWSCloudFormationJsonAggregator.java b/src/main/java/com/patternpedia/api/util/aggregator/AWSCloudFormationJsonAggregator.java new file mode 100644 index 0000000..9a9180a --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/AWSCloudFormationJsonAggregator.java @@ -0,0 +1,71 @@ +package com.patternpedia.api.util.aggregator; + +import com.patternpedia.api.entities.designmodel.AggregationData; +import com.patternpedia.api.entities.designmodel.ConcreteSolution; +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; +import com.patternpedia.api.rest.model.FileDTO; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + + +@AggregatorMetadata(sourceTypes = {"AWS-CloudFormation-JSON"}, targetTypes = {"", "AWS-CloudFormation-JSON"}) +public class AWSCloudFormationJsonAggregator extends ActiveMQAggregator { + + private static final String FILENAME = "CloudFormation-Template.json"; + private static final String MIME_TYPE = "application/json"; + private static final String WRAPPER_TEMPLATE = CONCRETE_SOLUTION_REPO + "aws-cloudformation-json/cloudformation.st"; + + + @Override + public void aggregate(AggregationData aggregationData) { + + + DesignModelPatternInstance sourcePattern = aggregationData.getSource(); + ConcreteSolution concreteSolution = sourcePattern.getConcreteSolution(); + String patternInstanceId = sourcePattern.getPatternInstanceId().toString(); + Map templateContext = aggregationData.getTemplateContext(); + + String concreteSolutionTemplate = readFile(concreteSolution.getTemplateUri()); + + String cloudFormationTemplate = extendVariables(concreteSolutionTemplate, patternInstanceId) + "\n"; + + // Make possible Camel XML JSON string compatible + if (templateContext.containsKey(patternInstanceId + "-configuration")) { + templateContext.compute( + patternInstanceId + "-configuration", + (String key, Object value) -> ((String) value).replaceAll("[\r\n\t ]+", " ").replaceAll("\"", "\\\\\"") + ); + } + + if (aggregationData.getEdge() != null) { + addInputOutputChannelContext(aggregationData); + } + + // Render template and wrap into camel context + String renderedCloudFormationTemplate = renderTemplate(cloudFormationTemplate, templateContext); + + if (renderedCloudFormationTemplate != null && !renderedCloudFormationTemplate.trim().isEmpty()) { + templateContext.compute("resources", (String key, Object value) -> { + List resources = new ArrayList<>(); + if (value != null) { + resources.addAll((Collection) value); + } + resources.add(renderedCloudFormationTemplate); + return resources; + }); + } + + if (aggregationData.getTarget() != null && "AWS-CloudFormation-JSON".equals(aggregationData.getTarget().getConcreteSolution().getAggregatorType())) { + return; + } + + String wrapperTemplate = readFile(WRAPPER_TEMPLATE); + String completeTemplate = renderTemplate(wrapperTemplate, templateContext); + + FileDTO aggregationResult = new FileDTO(FILENAME, MIME_TYPE, completeTemplate); + aggregationData.setResult(aggregationResult); + } +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/ActiveMQAggregator.java b/src/main/java/com/patternpedia/api/util/aggregator/ActiveMQAggregator.java new file mode 100644 index 0000000..a58a703 --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/ActiveMQAggregator.java @@ -0,0 +1,59 @@ +package com.patternpedia.api.util.aggregator; + +import com.patternpedia.api.entities.designmodel.AggregationData; +import com.patternpedia.api.entities.designmodel.DesignModelPatternEdge; +import com.patternpedia.api.entities.designmodel.DesignModelPatternEdgeId; +import lombok.extern.apachecommons.CommonsLog; + +import java.util.*; +import java.util.function.BiFunction; + + +@CommonsLog +public abstract class ActiveMQAggregator extends AggregatorImpl { + + @Override + public abstract void aggregate(AggregationData aggregationData); + + + public static Object getQueueList(Collection edges) { + if (edges.size() == 1) { + DesignModelPatternEdgeId edgeId = edges.iterator().next().getEdgeId(); + return "queue" + edgeId.getPatternInstanceId2().toString(); + } + if (edges.size() >= 2) { + Set queueNames = new HashSet<>(); + for (DesignModelPatternEdge edge : edges) { + queueNames.add("queue" + edge.getEdgeId().getPatternInstanceId2().toString()); + } + return queueNames; + } + return null; + } + + + protected static void addInputOutputChannelContext(AggregationData aggregationData) { + + final String channelName = getIdentifier(aggregationData.getSource()); + + BiFunction computeMapEntries = (Object key, Object value) -> { + String newValue = channelName; + + if (value == null || value.equals(newValue)) { + return newValue; + } else if (value instanceof Collection) { + List values = new ArrayList((Collection) value); + values.add(newValue); + return values; + } else { + return Arrays.asList(value, newValue); + } + }; + + Map context = aggregationData.getTemplateContext(); + context.compute(aggregationData.getSource().getPatternInstanceId() + "-input", computeMapEntries); + if (aggregationData.getTarget() != null) { + context.compute(aggregationData.getTarget().getPatternInstanceId().toString() + "-output", computeMapEntries); + } + } +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/ActiveMQJavaAggregator.java b/src/main/java/com/patternpedia/api/util/aggregator/ActiveMQJavaAggregator.java new file mode 100644 index 0000000..48f890e --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/ActiveMQJavaAggregator.java @@ -0,0 +1,62 @@ +package com.patternpedia.api.util.aggregator; + +import com.patternpedia.api.entities.designmodel.AggregationData; +import com.patternpedia.api.entities.designmodel.ConcreteSolution; +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; +import com.patternpedia.api.rest.model.FileDTO; + +import java.util.Collections; + + +@AggregatorMetadata(sourceTypes = {"ActiveMQ-Java"}, targetTypes = {"", "ActiveMQ-Java", "ActiveMQ-XML"}) +public class ActiveMQJavaAggregator extends ActiveMQAggregator { + + private static final String FILENAME = "PatternAtlasRouteBuilder.java"; + private static final String MIME_TYPE = "text/x-java"; + private static final String WRAPPER_TEMPLATE = CONCRETE_SOLUTION_REPO + "eip-activemq-java/camel.st"; + private static final String TEMPLATE_KEY = "-template-jdsl"; + + + @Override + public void aggregate(AggregationData aggregationData) { + + StringBuilder camelContext = new StringBuilder(); + + DesignModelPatternInstance sourcePattern = aggregationData.getSource(); + ConcreteSolution concreteSolution = sourcePattern.getConcreteSolution(); + String patternInstanceId = sourcePattern.getPatternInstanceId().toString(); + String targetInstanceId = aggregationData.getTarget().getPatternInstanceId().toString(); + + camelContext.append(aggregationData.getTemplateContext().getOrDefault(patternInstanceId + TEMPLATE_KEY, "")); + + String concreteSolutionTemplate = readFile(concreteSolution.getTemplateUri()); + + String idComment = "/* " + getIdentifier(aggregationData.getSource()) + " */"; + if (!camelContext.toString().contains(idComment)) { + camelContext.insert(0, "\n" + idComment + extendVariables(concreteSolutionTemplate, patternInstanceId) + "\n"); + } + + aggregationData.getTemplateContext().put(targetInstanceId + TEMPLATE_KEY, camelContext.toString()); + + if (aggregationData.getEdge() != null) { + + addInputOutputChannelContext(aggregationData); + + if ("ActiveMQ-Java".equals(aggregationData.getTarget().getConcreteSolution().getAggregatorType())) { + return; + } + } + + + // Render template and wrap into camel context + String renderedCamelContext = renderTemplate(camelContext.toString(), aggregationData.getTemplateContext()); + + String wrapperTemplate = readFile(WRAPPER_TEMPLATE); + String camelConfig = renderTemplate(wrapperTemplate, Collections.singletonMap("camelContext", renderedCamelContext)); + + aggregationData.getTemplateContext().put(targetInstanceId + TEMPLATE_KEY, camelContext.toString()); + + FileDTO aggregationResult = new FileDTO(FILENAME, MIME_TYPE, camelConfig); + aggregationData.setResult(aggregationResult); + } +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/ActiveMQXMLAggregator.java b/src/main/java/com/patternpedia/api/util/aggregator/ActiveMQXMLAggregator.java new file mode 100644 index 0000000..20306b9 --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/ActiveMQXMLAggregator.java @@ -0,0 +1,73 @@ +package com.patternpedia.api.util.aggregator; + +import com.patternpedia.api.entities.designmodel.AggregationData; +import com.patternpedia.api.entities.designmodel.ConcreteSolution; +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; +import com.patternpedia.api.rest.model.FileDTO; +import lombok.extern.apachecommons.CommonsLog; + +import java.util.Collections; + + +@CommonsLog +@AggregatorMetadata(sourceTypes = {"ActiveMQ-XML"}, targetTypes = {"", "ActiveMQ-XML", "ActiveMQ-Java", "AWS-CloudFormation-JSON", "MessageEndpoint"}) +public class ActiveMQXMLAggregator extends ActiveMQAggregator { + + private static final String FILENAME = "camel.xml"; + private static final String MIME_TYPE = "text/xml"; + private static final String WRAPPER_TEMPLATE = CONCRETE_SOLUTION_REPO + "eip-activemq-xml/camel.st"; + private static final String TEMPLATE_KEY = "-template"; + + + @Override + public void aggregate(AggregationData aggregationData) { + + StringBuilder camelContext = new StringBuilder(); + + DesignModelPatternInstance sourcePattern = aggregationData.getSource(); + ConcreteSolution concreteSolution = sourcePattern.getConcreteSolution(); + String patternInstanceId = sourcePattern.getPatternInstanceId().toString(); + String targetInstanceId = aggregationData.getTarget() == null ? null : aggregationData.getTarget().getPatternInstanceId().toString(); + + camelContext.append(aggregationData.getTemplateContext().getOrDefault(patternInstanceId + TEMPLATE_KEY, "")); + + String concreteSolutionTemplate = readFile(concreteSolution.getTemplateUri()); + + String idComment = ""; + if (!camelContext.toString().contains(idComment)) { + camelContext.insert(0, idComment + "\n" + extendVariables(concreteSolutionTemplate, patternInstanceId) + "\n"); + } + + if (targetInstanceId != null) { + aggregationData.getTemplateContext().put(targetInstanceId + TEMPLATE_KEY, camelContext.toString()); + } + + if (aggregationData.getEdge() != null) { + addInputOutputChannelContext(aggregationData); + + if ("ActiveMQ-XML".equals(aggregationData.getTarget().getConcreteSolution().getAggregatorType())) { + return; + } + } + + + // Render template and wrap into camel context + String renderedCamelContext = renderTemplate(camelContext.toString(), aggregationData.getTemplateContext()); + + if (aggregationData.getTarget() != null && "AWS-CloudFormation-JSON".equals(aggregationData.getTarget().getConcreteSolution().getAggregatorType())) { + String id = aggregationData.getTarget().getPatternInstanceId().toString(); + aggregationData.getTemplateContext().put(id + "-configuration", renderedCamelContext); + return; + } + + String wrapperTemplate = readFile(WRAPPER_TEMPLATE); + String camelConfig = renderTemplate(wrapperTemplate, Collections.singletonMap("camelContext", renderedCamelContext)); + + if (targetInstanceId != null) { + aggregationData.getTemplateContext().put(targetInstanceId + TEMPLATE_KEY, camelConfig); + } + + FileDTO aggregationResult = new FileDTO(FILENAME, MIME_TYPE, camelConfig); + aggregationData.setResult(aggregationResult); + } +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/Aggregator.java b/src/main/java/com/patternpedia/api/util/aggregator/Aggregator.java new file mode 100644 index 0000000..f557bb8 --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/Aggregator.java @@ -0,0 +1,9 @@ +package com.patternpedia.api.util.aggregator; + +import com.patternpedia.api.entities.designmodel.AggregationData; + + +public interface Aggregator { + + void aggregate(AggregationData aggregationData); +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/AggregatorImpl.java b/src/main/java/com/patternpedia/api/util/aggregator/AggregatorImpl.java new file mode 100644 index 0000000..34c0da0 --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/AggregatorImpl.java @@ -0,0 +1,59 @@ +package com.patternpedia.api.util.aggregator; + +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; +import com.patternpedia.api.exception.AggregationException; +import lombok.extern.apachecommons.CommonsLog; +import org.stringtemplate.v4.ST; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; + + +@CommonsLog +public abstract class AggregatorImpl implements Aggregator { + + protected static final String CONCRETE_SOLUTION_REPO = "https://raw.githubusercontent.com/marzn/pattern-atlas-pattern-implementations/main/"; + + protected static final Random RANDOM = new Random(); + + + protected static String readFile(String url) { + log.info("Read file from " + url); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(url).openStream()))) { + return reader.lines().collect(Collectors.joining("\n")); + + } catch (IOException e) { + String errorMsg = "Failed to read file from " + url; + log.error(errorMsg); + throw new AggregationException(errorMsg); + } + } + + + protected static String renderTemplate(String concreteSolutionTemplate, Map dataContainer) { + ST template = new ST(concreteSolutionTemplate, '$', '$'); + + template.add("random", RANDOM.nextInt(Integer.MAX_VALUE)); + + for (Map.Entry entry : dataContainer.entrySet()) { + template.add(entry.getKey(), entry.getValue()); + } + + return template.render(); + } + + + protected static String extendVariables(String template, String id) { + return template.replaceAll("\\$(.*?)(input|output|configuration)(.*?)(\\$|:\\{)", "\\$$1" + id + "-$2$3$4"); + } + + + protected static String getIdentifier(DesignModelPatternInstance patternInstance) { + return patternInstance.getPattern().getName().replace(" ", "-").toLowerCase() + "-" + patternInstance.getPatternInstanceId().toString(); + } +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/AggregatorMetadata.java b/src/main/java/com/patternpedia/api/util/aggregator/AggregatorMetadata.java new file mode 100644 index 0000000..57a9913 --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/AggregatorMetadata.java @@ -0,0 +1,13 @@ +package com.patternpedia.api.util.aggregator; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +@Retention(RetentionPolicy.RUNTIME) +public @interface AggregatorMetadata { + + String[] sourceTypes(); + + String[] targetTypes(); +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/AggregatorScanner.java b/src/main/java/com/patternpedia/api/util/aggregator/AggregatorScanner.java new file mode 100644 index 0000000..89bb7ba --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/AggregatorScanner.java @@ -0,0 +1,78 @@ +package com.patternpedia.api.util.aggregator; + +import lombok.extern.apachecommons.CommonsLog; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.util.ClassUtils; +import org.springframework.util.SystemPropertyUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +@CommonsLog +public class AggregatorScanner { + + public static Aggregator findMatchingAggregatorImpl(String sourceType, String targetType) { + try { + if (targetType == null) { + targetType = ""; + } + + List classList = findAggregatorImplementations(AggregatorScanner.class.getPackage().getName()); + for (Class implCandidate : classList) { + AggregatorMetadata annotation = (AggregatorMetadata) implCandidate.getAnnotation(AggregatorMetadata.class); + List sourceTypes = Arrays.asList(annotation.sourceTypes()); + List targetTypes = Arrays.asList(annotation.targetTypes()); + + if (sourceTypes.contains(sourceType) && targetTypes.contains(targetType)) { + return (Aggregator) implCandidate.newInstance(); + } + } + + } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) { + log.error(e.getMessage(), e); + } + return null; + } + + private static List findAggregatorImplementations(String basePackage) throws IOException, ClassNotFoundException { + ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); + + List candidates = new ArrayList(); + String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + resolveBasePackage(basePackage) + "/" + "**/*.class"; + Resource[] resources = resourcePatternResolver.getResources(packageSearchPath); + for (Resource resource : resources) { + if (resource.isReadable()) { + MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource); + if (isCandidate(metadataReader)) { + candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName())); + } + } + } + return candidates; + } + + private static String resolveBasePackage(String basePackage) { + return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)); + } + + private static boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException { + try { + Class c = Class.forName(metadataReader.getClassMetadata().getClassName()); + if (c.getAnnotation(AggregatorMetadata.class) != null) { + return true; + } + } catch (Throwable e) { + } + return false; + } +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/MessageEndpointAggregator.java b/src/main/java/com/patternpedia/api/util/aggregator/MessageEndpointAggregator.java new file mode 100644 index 0000000..0e0a982 --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/MessageEndpointAggregator.java @@ -0,0 +1,40 @@ +package com.patternpedia.api.util.aggregator; + +import com.patternpedia.api.entities.designmodel.AggregationData; +import com.patternpedia.api.entities.designmodel.ConcreteSolution; +import com.patternpedia.api.entities.designmodel.DesignModelPatternInstance; +import com.patternpedia.api.rest.model.FileDTO; + +import java.util.Map; + + +@AggregatorMetadata(sourceTypes = {"MessageEndpoint"}, targetTypes = {"", "ActiveMQ-XML", "ActiveMQ-Java"}) +public class MessageEndpointAggregator extends ActiveMQAggregator { + + private static final String MIME_TYPE = "text/x-java"; + + + @Override + public void aggregate(AggregationData aggregationData) { + + DesignModelPatternInstance sourcePattern = aggregationData.getSource(); + ConcreteSolution concreteSolution = sourcePattern.getConcreteSolution(); + String patternInstanceId = sourcePattern.getPatternInstanceId().toString(); + Map templateContext = aggregationData.getTemplateContext(); + + String concreteSolutionTemplate = readFile(concreteSolution.getTemplateUri()); + concreteSolutionTemplate = extendVariables(concreteSolutionTemplate, patternInstanceId); + + boolean isProducer = aggregationData.getTarget() == null; + + String filename = isProducer ? "QueueProducer.java" : "QueueConsumer.java"; + templateContext.put("producer", isProducer); + + addInputOutputChannelContext(aggregationData); + + String renderedTemplate = renderTemplate(concreteSolutionTemplate, templateContext); + + FileDTO aggregationResult = new FileDTO(filename, MIME_TYPE, renderedTemplate); + aggregationData.setResult(aggregationResult); + } +} diff --git a/src/main/java/com/patternpedia/api/util/aggregator/MessageEndpointOnAWSAggregator.java b/src/main/java/com/patternpedia/api/util/aggregator/MessageEndpointOnAWSAggregator.java new file mode 100644 index 0000000..13bc9aa --- /dev/null +++ b/src/main/java/com/patternpedia/api/util/aggregator/MessageEndpointOnAWSAggregator.java @@ -0,0 +1,22 @@ +package com.patternpedia.api.util.aggregator; + +import com.patternpedia.api.entities.designmodel.AggregationData; +import com.patternpedia.api.rest.model.FileDTO; + + +@AggregatorMetadata(sourceTypes = {"MessageEndpoint"}, targetTypes = {"AWS-CloudFormation-JSON"}) +public class MessageEndpointOnAWSAggregator extends ActiveMQAggregator { + + @Override + public void aggregate(AggregationData aggregationData) { + + final String[] instructions = { + "Please perform the following steps for running your Messaging Endpoint on AWS:", + "1. upload your artefact on Amazon S3", + "2. replace the S3Bucket and S3Key values in the Cloud Formation template" + }; + + FileDTO aggregationResult = new FileDTO("Instructions for Message Endpoint deployment on AWS.txt", "plain/text", String.join("\n", instructions)); + aggregationData.setResult(aggregationResult); + } +}