done GA12

This commit is contained in:
Claudio Maggioni 2023-05-23 14:07:42 +02:00
parent a22f8bb6cb
commit 2077cf0741
83 changed files with 368115 additions and 0 deletions

78
.gitignore vendored
View File

@ -2,3 +2,81 @@ artist.txt
.idea/
target/
*.iml
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

2
assignment-w13/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
testing-tools/
**/testing-tools/

View File

@ -0,0 +1 @@
comments.repository.MapCriticsRepository

View File

@ -0,0 +1,61 @@
---
title: Software Quality & Testing -- Graded Assignment 12
author: Claudio Maggioni
geometry: margin=2.5cm,bottom=3cm
---
# Exercise 1 and 2
The necessary edits have been performed in the test class `src/test/java/comments/CommentsTest.java`.
# Exercise 3
I use EvoMaster to generate test cases and find the bug in the source code. I run
EvoMaster with the following command from the project root folder `comments-api`:
```shell
java --add-opens java.base/java.net=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
-jar ./testing-tools/evomaster.jar --blackBox true \
--bbSwaggerUrl http://localhost:8080/docs/swagger.yaml \
--outputFormat JAVA_JUNIT_4 --maxTime 5m
```
EvoMaster correctly finds a bug in the `GET /comments` route,
causing a 500 error when the `limit` query parameter is set to `0` and
the `offset` query parameter is not given.
The generated tests are included in the directory `src/test/java/em`.
# Exercise 4
To run Randoop without a test oracle I run the following commands from the project root folder:
```shell
echo "comments.repository.MapCriticsRepository" > file.txt
java -classpath testing-tools/randoop-4.3.2/randoop-all-4.3.2.jar:target/classes \
randoop.main.Main gentests --classlist=file.txt --time-limit=120
```
To run Randoop with the test oracle `jdoctor-oracles.json` I run the following commands from the project root folder:
```shell
echo "comments.repository.MapCriticsRepository" > file.txt
java -classpath testing-tools/randoop-4.3.2/randoop-all-4.3.2.jar:target/classes \
randoop.main.Main gentests --classlist=file.txt --time-limit=120 \
--specifications=jdoctor-oracles.json
```
The generated tests without the oracle are included in the directory `src/test/java/randoopWithoutOracle`, while
the ones generated with the oracle are included in the directory `src/test/java/randoopWithOracle`.
The difference between the test generated with or without oracle lies in the assertions over the
`MapCriticsRepository.addComment(Comment)` method. The oracle file `jdoctor-oracles.json` specifies that this method
should throw a `IllegalArgumentException` when its parameter is null. In the tests without oracle, this exception throwing
is treated as a possible bug and tests that cause it are placed in `ErrorTest*` classes since by not expecting
the exception they fail. However, in the tests generated with oracles the exception throwing behaviour is correctly tested,
thus making tests fail when no exception is thrown and the parameter is indeed `null`. Even with the oracle the tests still fail,
since a `NullPointerException` is thrown instead of the `IllegalArgumentException` expected in the oracle (and the
documentation of the method), thus uncovering a new bug in `MapCriticsRepository`.

Binary file not shown.

View File

@ -0,0 +1,30 @@
[
{
"operation": {
"classname": "comments.repository.MapCriticsRepository",
"name": "addComment",
"parameterTypes": [
"comments.model.Comment"
]
},
"identifiers": {
"parameters": [
"c"
],
"receiverName": "receiverObjectID",
"returnName": "methodResultID"
},
"throws": [
{
"exception": "java.lang.IllegalArgumentException",
"description": "@throws java.lang.IllegalArgumentException the comment is null.",
"guard": {
"condition": "c==null",
"description": "the comment is null."
}
}
],
"post": [],
"pre": []
}
]

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>es.us.aiss</groupId>
<artifactId>comments</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.9</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-core-spi</artifactId>
<version>4.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-spring-boot-starter</artifactId>
<version>4.8.0.Final</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.3</version>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.3.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atlassian.oai</groupId>
<artifactId>swagger-request-validator-core</artifactId>
<version>2.18.0</version>
</dependency>
<dependency>
<groupId>com.atlassian.oai</groupId>
<artifactId>swagger-request-validator-restassured</artifactId>
<version>2.18.0</version>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>2.4.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>org.evosuite</groupId>
<artifactId>evosuite</artifactId>
<version>1.2.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/testing-tools/evosuite-1.2.0.jar</systemPath>
</dependency>
<dependency>
<groupId>org.evomaster</groupId>
<artifactId>evomaster-client-java-controller</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View File

@ -0,0 +1,12 @@
package comments.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,32 @@
package comments.main;
import comments.resources.CommentResource;
import org.springframework.stereotype.Component;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
@Component
@ApplicationPath("/api")
public class CommentsAPIApplication extends Application {
private Set<Object> singletons = new HashSet<Object>();
private Set<Class<?>> classes = new HashSet<Class<?>>();
public CommentsAPIApplication() {
singletons.add(CommentResource.getInstance());
}
@Override
public Set<Class<?>> getClasses() {
return classes;
}
@Override
public Set<Object> getSingletons() {
return singletons;
}
}

View File

@ -0,0 +1,113 @@
package comments.model;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import java.util.Objects;
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class Comment {
private String id;
private String userName;
private String text;
private String date;
private String type; // Enum: Review, Request, Complain
public Comment(){}
public Comment(String userName, String text, String date, String type) {
this.userName=userName;
this.text = text;
this.date = date;
this.type = type;
}
public Comment(String id, String userName, String text, String date, String type) {
this.id=id;
this.userName=userName;
this.text = text;
this.date = date;
this.type = type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public int hashCode() {
int hash = 7;
hash = 29 * hash + Objects.hashCode(this.id);
hash = 29 * hash + Objects.hashCode(this.userName);
hash = 29 * hash + Objects.hashCode(this.text);
hash = 29 * hash + Objects.hashCode(this.date);
hash = 29 * hash + Objects.hashCode(this.type);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Comment other = (Comment) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
if (!Objects.equals(this.userName, other.userName)) {
return false;
}
if (!Objects.equals(this.text, other.text)) {
return false;
}
if (!Objects.equals(this.date, other.date)) {
return false;
}
if (!Objects.equals(this.type, other.type)) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,71 @@
package comments.model;
import java.util.ArrayList;
import java.util.List;
public class Profile {
private String userName;
private List<Comment> comments;
public Profile(){}
public Profile(String userName){
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public List<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> list){
this.comments=list;
}
public Comment getComment(String id){
if (comments==null)
return null;
Comment comment =null;
for(Comment c: comments)
if (c.getId().equals(id))
{
comment=c;
break;
}
return comment;
}
public void addComment(Comment c){
if(comments==null){
comments = new ArrayList<Comment>();
}
comments.add(c);
}
public void deleteComment(Comment c){
comments.remove(c);
}
public void deleteComment(String id){
Comment c = getComment(id);
if(c != null){
comments.remove(c);
}
}
public void deleteAllComments(){
comments.removeAll(comments);
}
}

View File

@ -0,0 +1,24 @@
package comments.repository;
import comments.model.Comment;
import comments.model.Profile;
import java.util.List;
public interface CriticsRepository {
/*Comments*/
public List<Comment> getAllComments();
public void addComment(Comment c);
public Comment getComment(String commentId);
public void deleteComment(String commentId);
public void updateComment(Comment c);
/*Profiles*/
public List<Profile> getAllProfiles();
public void addProfile(Profile p);
public Profile getProfile(String username);
public void deleteProfile(String username);
}

View File

@ -0,0 +1,208 @@
package comments.repository;
import comments.model.Comment;
import comments.model.Profile;
import java.time.LocalDateTime;
import java.util.*;
public class MapCriticsRepository implements CriticsRepository {
Map<String, Comment> commentMap;
Map<String, Profile> profileMap;
private static MapCriticsRepository instance = null;
private int index = 0;
public static MapCriticsRepository getInstance() {
if (instance == null) {
instance = new MapCriticsRepository();
instance.init();
}
return instance;
}
private void init() {
commentMap = new HashMap<String, Comment>();
profileMap = new HashMap<String, Profile>();
// Create Comments
Comment c1 = new Comment();
c1.setUserName("johnSmith");
c1.setText("Great book!");
c1.setType("Review");
c1.setDate("2017-02-16T20:44:53.950");
addComment(c1);
Comment c2 = new Comment();
c2.setUserName("johnSmith");
c2.setText("Original product but the quality is too low for the price");
c2.setType("Review");
c2.setDate("2017-06-16T20:44:53.950");
addComment(c2);
Comment c3 = new Comment();
c3.setUserName("johnSmith");
c3.setText("The product was delivered three days after the expected date.");
c3.setType("Complain");
c3.setDate("2017-06-16T20:48:53.950");
addComment(c3);
Comment c4 = new Comment();
c4.setUserName("oliviaClark");
c4.setText("I would love to see this product in pocket size");
c4.setType("Request");
c4.setDate("2015-01-16T20:44:53.950");
addComment(c4);
Comment c5 = new Comment();
c5.setUserName("oliviaClark");
c5.setText("Please, give me a call before the delivery");
c5.setType("Request");
c5.setDate("2015-06-22T20:12:45.950");
addComment(c5);
Comment c6 = new Comment();
c6.setUserName("gloriaCavanni");
c6.setText("I love the color and the shape of the cover");
c6.setType("Review");
c6.setDate("2020-11-16T20:44:53.950");
addComment(c6);
Comment c7 = new Comment();
c7.setUserName("markSpecter");
c7.setText("Adding a sim card slot would be a winner point!");
c7.setType("Request");
c7.setDate("1998-01-16T20:44:53.950");
addComment(c7);
Comment c8 = new Comment();
c8.setUserName("markSpecter");
c8.setText("Nice product, my kids love it.");
c8.setType("Review");
c8.setDate("2013-04-16T20:44:53.950");
addComment(c8);
Comment c9 = new Comment();
c9.setUserName("markSpecter");
c9.setText("Too bad, too late.");
c9.setType("Complain");
c9.setDate("2018-04-16T20:44:53.950");
addComment(c9);
Comment c10 = new Comment();
c10.setUserName("markSpecter");
c10.setText("It is bigger than it look in the image, but it does the work");
c10.setType("Review");
c10.setDate("2019-08-02T12:31:53.950");
addComment(c10);
Comment c11 = new Comment();
c11.setUserName("testUser");
c11.setText("This is a test comment");
c11.setType(null);
c11.setDate("2020-08-02T12:31:53.950");
addComment(c11);
}
/* Comments */
@Override
public List<Comment> getAllComments() {
List<Comment> comments = new ArrayList<Comment>(commentMap.values());
return comments;
}
/**
*
* @throws IllegalArgumentException if the comment is null
*/
@Override
public void addComment(Comment c) {
String id = "c" + index++;
if (profileMap.containsKey(c.getUserName())) {
profileMap.get(c.getUserName()).addComment(c);
} else {
Profile p = new Profile();
p.setUserName(c.getUserName());
p.addComment(c);
profileMap.put(p.getUserName(), p);
}
c.setId(id);
if (c.getDate() != null) {
c.setDate(c.getDate());
} else {
c.setDate(LocalDateTime.now().toString());
}
commentMap.put(id, c);
}
@Override
public Comment getComment(String commentId) {
return commentMap.get(commentId);
}
@Override
public void deleteComment(String commentId) {
Comment c = commentMap.get(commentId);
commentMap.remove(commentId);
profileMap.get(c.getUserName()).deleteComment(commentId);
}
@Override
public void updateComment(Comment c) {
Comment comment = commentMap.get(c.getId());
comment.setText(c.getText());
if (c.getDate() != null) {
c.setDate(c.getDate());
} else {
c.setDate(LocalDateTime.now().toString());
}
}
/* Profiles */
@Override
public List<Profile> getAllProfiles() {
List<Profile> profiles = new ArrayList<Profile>(profileMap.values());
return profiles;
}
@Override
public void addProfile(Profile p) {
p.setComments(new ArrayList<Comment>());
for (Comment e : commentMap.values()) {
if (e.getUserName().equals(p.getUserName())) {
p.addComment(e);
}
}
profileMap.put(p.getUserName(), p);
}
@Override
public Profile getProfile(String userName) {
return profileMap.get(userName);
}
@Override
public void deleteProfile(String userName) {
Set<Comment> comments = new HashSet<Comment>(commentMap.values());
for (Comment e : comments) {
if (e.getUserName().equals(userName)) {
deleteComment(e.getId());
}
}
profileMap.remove(userName);
}
}

View File

@ -0,0 +1,199 @@
package comments.resources;
import comments.model.Comment;
import comments.repository.CriticsRepository;
import comments.repository.MapCriticsRepository;
//import org.jboss.resteasy.spi.BadRequestException;
//import org.jboss.resteasy.spi.NotFoundException;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.ResponseBuilder;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@Path("/comments")
public class CommentResource {
public static CommentResource _instance = null;
CriticsRepository repository;
private CommentResource() {
repository = MapCriticsRepository.getInstance();
}
public static CommentResource getInstance() {
if (_instance == null) {
_instance = new CommentResource();
}
return _instance;
}
public void order(List<Comment> list) {
// Comparator<Comment> c = Comparator.comparing(x -> x.getDate());
Collections.sort(list, new ComparatorDateComment());
}
public List<Comment> length(List<Comment> list, Integer index) {
List<Comment> sublist = list.subList(0, index);
return sublist;
}
@GET
@Produces("application/json")
public Response getAll(@QueryParam("contains") String word, @QueryParam("type") String type, @QueryParam("order") String order,
@QueryParam("offset") String offset, @QueryParam("limit") String limit) {
if(type != null && !type.equals("Review") && !type.equals("Request") && !type.equals("Complain") && !type.equals("All"))
return Response.status(400).type(MediaType.TEXT_PLAIN).entity("type must be one of Review, Request, Complain or All").build();
// return Response.status(400).build();
if(order != null && !order.equals("date") && !order.equals("-date") && !order.equals("+date"))
return Response.status(400).type(MediaType.TEXT_PLAIN).entity("order must be one of date, -date or +date").build();
// return Response.status(400).build();
List<Comment> comments = new ArrayList<Comment>();
Comment c = null;
Iterator<Comment> iterator = repository.getAllComments().iterator();
while (iterator.hasNext()) {
c = iterator.next();
if (wordFilter(c, word) && typeFilter(c, type)) {
comments.add(c);
}
}
if (order != null && (order.equals("date") || order.equals("+date"))) {
Collections.sort(comments, new ComparatorDateComment());
}
if (order != null && order.equals("-date")) {
Collections.sort(comments, new ComparatorDateCommentReversed());
}
Integer off = 0;
Integer lim = comments.size();
if (offset != null) {
try {
off = new Integer(offset);
if (off < 0 || off > comments.size()) {
off = 0;
}
} catch(NumberFormatException e) {
return Response.status(400).type(MediaType.TEXT_PLAIN).entity("The offset must be a number").build();
}
}
if (limit != null) {
try {
lim = new Integer(limit);
if (lim < 0 || lim + off > comments.size()) {
lim = comments.size();
} else {
lim--;
}
} catch(NumberFormatException e) {
return Response.status(400).type(MediaType.TEXT_PLAIN).entity("The limit must be a number").build();
}
}
comments = comments.subList(off, lim + off > comments.size() ? lim : lim + off);
return Response.ok(comments).build();
}
@GET
@Path("{id}")
@Produces("application/json")
public Response getComment(@PathParam("id") String commentId) {
Comment comment = repository.getComment(commentId);
if (comment == null) {
return Response.status(404).type(MediaType.TEXT_PLAIN).entity("The comment with id" + commentId + " was not found").type(MediaType.TEXT_PLAIN).build();
// return Response.status(404).build();
}
return Response.ok(comment).build();
}
@DELETE
@Path("{id}")
@Produces("application/json")
public Response deleteComment(@PathParam("id") String commentId) {
Comment comment = repository.getComment(commentId);
if (comment == null) {
return Response.status(404).type(MediaType.TEXT_PLAIN).entity("The comment with id" + commentId + " was not found").build();
// return Response.status(404).build();
}
return Response.noContent().build();
}
@POST
@Consumes("application/json")
@Produces("application/json")
public Response addComment(@Context UriInfo uriInfo, Comment c) {
if (!validateBodyPost(c)) {
return Response.status(400).type(MediaType.TEXT_PLAIN).entity("You must complete the username and text fields").build();
// return Response.status(400).build();
}
if (c.getType() != null && !(c.getType().equals("Review") || c.getType().equals("Request") || c.getType().equals("Complain"))) {
return Response.status(400).type(MediaType.TEXT_PLAIN).entity("Invalid value for type parameter").build();
// return Response.status(400).build();
}
repository.addComment(c);
UriBuilder ub = uriInfo.getAbsolutePathBuilder().path(this.getClass(), "getComment");
URI uri = ub.build(c.getId());
ResponseBuilder resp = Response.created(uri);
resp.entity(c);
return resp.build();
}
@PUT
@Consumes("application/json")
@Produces("application/json")
public Response updateComment(Comment c) {
if(!validateBodyPut(c))
return Response.status(400).type(MediaType.TEXT_PLAIN).entity("You must complete all fields").build();
// return Response.status(400).build();
Comment oldComment = repository.getComment(c.getId());
if (oldComment == null) {
return Response.status(404).type(MediaType.TEXT_PLAIN).entity("The comment with id " + c.getId() + " was not found").build();
// return Response.status(404).build();
}
repository.updateComment(c);
return Response.ok(repository.getComment(c.getId())).build();
}
private boolean validateBodyPost(Comment c) {
return c != null && c.getUserName() != null && !c.getUserName().equals("") && c.getText() != null
&& !c.getText().equals("") && !(c.getType() != null && !c.getType().equals("Review")
&& !c.getType().equals("Request") && !c.getType().equals("Complain"));
}
private boolean validateBodyPut(Comment c) {
return c != null && c.getId() != null && !c.getId().equals("") && c.getUserName() != null
&& !c.getUserName().equals("") && c.getText() != null && !c.getText().equals("")
&& c.getId() != null && !c.getId().equals("") && c.getDate() != null && !c.getDate().equals("")
&& c.getType() != null && (c.getType().equals("Review") || c.getType().equals("Request")
|| c.getType().equals("Complain"));
}
// Returns true if the comment include word, or if the word is null or empty.
private boolean wordFilter(Comment c, String word) {
return (word == null || word.equals("") || c.getText().contains(word));
}
// Returns true if the comment matches the input type.
private boolean typeFilter(Comment c, String type) {
// return (type == null || type.equals("") || type.equals("All") || c.getType().equals(type));
return type == null || type.equals("") || type.equals("All") || (c.getType() != null && c.getType().equals(type));
}
}

View File

@ -0,0 +1,15 @@
package comments.resources;
import comments.model.Comment;
import java.util.Comparator;
public class ComparatorDateComment implements Comparator<Comment> {
@Override
public int compare(Comment c1, Comment c2) {
// TODO Auto-generated method stub
return c1.getDate().compareTo(c2.getDate());
}
}

View File

@ -0,0 +1,15 @@
package comments.resources;
import comments.model.Comment;
import java.util.Comparator;
public class ComparatorDateCommentReversed implements Comparator<Comment> {
@Override
public int compare(Comment c1, Comment c2) {
// TODO Auto-generated method stub
return c2.getDate().compareTo(c1.getDate());
}
}

View File

@ -0,0 +1,19 @@
resteasy:
jaxrs:
app:
registration: property
classes: comments.main.CommentsAPIApplication
management:
endpoints:
web:
exposure:
include:
- health
- shutdown
endpoint:
shutdown:
enabled: true
logging:
level:
org:
springframework: info

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Class-Path:

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<threadsafe>true</threadsafe>
<sessions-enabled>false</sessions-enabled>
<runtime>java8</runtime>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
</appengine-web-app>

View File

@ -0,0 +1,4 @@
# https://cloud.google.com/appengine/docs/standard/java/logs/
# Set the default logging level for all loggers to WARNING
.level = WARNING

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<!-- All REST resources will be prefixed by /api -->
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/api</param-value>
</context-param>
<!-- Servlets -->
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>comments.main.CommentsAPIApplication</param-value>
</init-param>
</servlet>
<!-- Servlet mappings -->
<!-- All calls to /api/xxx will be sent to the resteasy servlet -->
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

View File

@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "http://localhost:8080/docs/swagger.yaml",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>

View File

@ -0,0 +1,68 @@
<!doctype html>
<html lang="en-US">
<title>Swagger UI: OAuth2 Redirect</title>
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,271 @@
---
swagger: "2.0"
info:
description: This is a simple Comments API
version: 1.0.0
title: Comments
host: localhost:8080
basePath: /api
tags:
- name: Comments
description: music comments
schemes:
- http
paths:
/comments:
get:
operationId: getComments
tags:
- Comments
summary: searches comments
description: |
Returns all comments made by all users
produces:
- application/json
- text/html;charset=utf-8
parameters:
- in: query
name: contains
description: filter comments by string
required: false
type: string
- in: query
name: type
description: filter comments by type
required: false
type: string
enum:
- Review
- Request
- Complain
- All
- in: query
name: order
description: order comments by date
required: false
type: string
enum:
- date
- +date
- -date
- in: query
name: offset
description: get comments from certain position
required: false
type: integer
- in: query
name: limit
description: limit comments retrieved
required: false
type: integer
responses:
"200":
description: searchs all comments
schema:
type: array
items:
$ref: '#/definitions/CommentReturned'
"400":
description: bad input parameter
post:
operationId: postComment
tags:
- Comments
summary: adds a comment
description: Adds a comment to the system
consumes:
- application/json
produces:
- application/json
- text/html;charset=utf-8
parameters:
- in: body
name: comment
description: comment to add
required: true
schema:
$ref: '#/definitions/CommentPost'
responses:
"201":
description: comment created
schema:
$ref: '#/definitions/CommentReturned'
"400":
description: invalid input, object invalid
put:
operationId: putComment
tags:
- Comments
summary: adds a comment
description: Adds an comment to the system
consumes:
- application/json
produces:
- application/json
- text/html;charset=utf-8
parameters:
- in: body
name: comment
description: Comment to update
required: true
schema:
$ref: '#/definitions/CommentPut'
responses:
"200":
description: comment created
schema:
$ref: '#/definitions/CommentReturned'
"400":
description: invalid input, object invalid
"404":
description: not found
/comments/{id}:
get:
operationId: getComment
tags:
- Comments
summary: search a comments
description: "Returns a comment \n"
produces:
- application/json
- text/html;charset=utf-8
parameters:
- name: id
in: path
description: id of the comment
required: true
type: string
responses:
"200":
description: search results matching criteria
schema:
$ref: '#/definitions/CommentReturned'
"404":
description: not found
delete:
operationId: deleteComment
tags:
- Comments
summary: delete a comment
description: "Deletes a comment \n"
produces:
- application/json
- text/html;charset=utf-8
parameters:
- name: id
in: path
description: id of the comment
required: true
type: string
responses:
"204":
description: comment deleted
"404":
description: not found
definitions:
CommentReturned:
type: object
required:
- id
- text
- userName
properties:
id:
type: string
minLength: 1
example: c1
userName:
type: string
minLength: 1