diff --git a/.gitignore b/.gitignore index 8ba30af0..5b5b6619 100644 --- a/.gitignore +++ b/.gitignore @@ -16,14 +16,11 @@ gradle-app.setting out/ *.iml .DS_Store -.project/ +.project .settings/org.eclipse.buildship.core.prefs -common/src/main/java/io/swagger/api/CheckApiController.java -team/.classpath -team/.project/ -team/.settings/org.eclipse.buildship.core.prefs -team/.settings/org.eclipse.jdt.core.prefs -team/bin/main/urlshortener/team/Application.class +.classpath +.settings/ .vscode - +demo/bin +bin/ app.properties \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 602fb55f..0ff0325c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,9 @@ addons: token: secure: yUN8YFkxwRsLb53i+J/G3huQHEg190cvTcslKyP/m7GBpjwcrcXcvI4/z32leAQkXWnLAsjCIsoQuNHtAdFZptSMFxV9nvLeAgSQdlv8WcPO3tTKSj7nR6vFgnhpIiS/FqRCKp1X3tPCtOI5JbGjxpJ8nKjkI7ow+MxaDIATLEELcEu6XNX8lwKnFlsfAwVBGcFBx3lfZLjTnbBe+PSjFy3oLrOZyfhKXIs8RF8c7xdPN1E8Au7mml5YqYsAev5IU4GkCd7GQRWTi9k/cnC4Y9T+FlbMZp5SjebQh4cNh8jagcVSLBDfTH9dRvdw2xiniOFqQzg2SQKjf3rn2UqMnKhMqYo2kGwQZKNrHj37RYpZtaNYvQTcnoQCPk4Bk0LwxCaxG7AYyMaMO8LbCJIoCYmsaXIIzHQ79UdttVsNN+vI0qtWB0uHPToyWoB3DcvoT8ocZRSYARcQ6g53BWnmpQu5gSxgNAHhnCL7eSLhvAZfeHbSf2ObGcaHpRhEI8GiBvK4jX66nuV0H+6CODcDgniBM99Urknu24V4jJjD079psX4lWKBOiuMfrtPSXAIOCEWpSgXX10lyD/rTTasBmb111novg8JNlzB+MwnLIoAte96+AyCoYh41A2HQUh+0EOOrcgxO3kYUiaaBUbUJ4w5ex11o3BQBxsq2dX2BCdM= script: -- bash sonar.sh + - ./gradlew test jacocoTestReport + - bash sonar.sh before_install: -- openssl aes-256-cbc -K $encrypted_be44edb6a80d_key -iv $encrypted_be44edb6a80d_iv - -in app.properties.enc -out app.properties -d + # Our tests inspect some of *our* git history + - openssl aes-256-cbc -K $encrypted_be44edb6a80d_key -iv $encrypted_be44edb6a80d_iv + -in app.properties.enc -out app.properties -d diff --git a/build.gradle b/build.gradle index 4e996fef..fb7d9665 100644 --- a/build.gradle +++ b/build.gradle @@ -16,15 +16,28 @@ plugins { id 'jacoco' } +sonarqube { + properties { + property 'sonar.host.url', 'https://sonarcloud.io' + property 'sonar.organization', 'blue-bash' + } +} + +jacoco { + toolVersion = "0.8.2" + reportsDir = file("$buildDir/customJacocoReportDir") +} + + allprojects { apply plugin: 'idea' apply plugin: 'eclipse' + apply plugin: 'jacoco' + apply plugin: 'java' } // Instructions for each sub project subprojects { - - apply plugin: 'java' apply plugin: 'io.spring.dependency-management' sourceCompatibility = 1.8 @@ -56,9 +69,6 @@ project(':common') { compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.7.0' // https://mvnrepository.com/artifact/com.github.joschi.jackson/jackson-datatype-threetenbp compile group: 'com.github.joschi.jackson', name: 'jackson-datatype-threetenbp', version: '2.6.4' - compile 'org.hibernate:hibernate-core:5.3.7.Final' - // https://mvnrepository.com/artifact/com.zaxxer/HikariCP - compile group: 'com.zaxxer', name: 'HikariCP', version: '3.2.0' // Required for QR generation compile "com.google.zxing:core:3.3.0" @@ -84,19 +94,39 @@ project(':demo') { } apply plugin: 'org.springframework.boot' + apply plugin: 'org.springframework.boot' + apply plugin: 'io.spring.dependency-management' + dependencies { compile project(':common') runtime 'org.hsqldb:hsqldb' + compile("org.webjars:sockjs-client:1.0.2") compile 'org.webjars:bootstrap:3.3.5' compile 'org.webjars:jquery:2.1.4' + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator + compile 'org.springframework.boot:spring-boot-starter-actuator' + compile("org.springframework.boot:spring-boot-starter-websocket") + compile("org.webjars:webjars-locator-core") + + compile("org.webjars:sockjs-client:1.0.2") + compile("org.webjars:stomp-websocket:2.3.3") + compile 'org.glassfish.tyrus:tyrus-container-grizzly-server:1.14' + compile 'org.springframework.boot:spring-boot-starter-web' // Testing framework testCompile 'org.springframework.boot:spring-boot-starter-test' testCompile 'org.apache.httpcomponents:httpclient' } + + jacocoTestReport { + reports { + xml.enabled false + csv.enabled false + } + } } project ('team') { diff --git a/demo/src/main/java/urlshortener/demo/Application.java b/demo/src/main/java/urlshortener/demo/Application.java index 63ff12cb..b3bc6a1f 100755 --- a/demo/src/main/java/urlshortener/demo/Application.java +++ b/demo/src/main/java/urlshortener/demo/Application.java @@ -4,10 +4,13 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.scheduling.annotation.EnableScheduling; import springfox.documentation.swagger2.annotations.EnableSwagger2; @SpringBootApplication @EnableSwagger2 +@EnableScheduling +//@EnableWebSocket public class Application extends SpringBootServletInitializer { public static void main(String[] args) { diff --git a/demo/src/main/java/urlshortener/demo/config/HomeController.java b/demo/src/main/java/urlshortener/demo/config/HomeController.java deleted file mode 100644 index 4b6bbd5b..00000000 --- a/demo/src/main/java/urlshortener/demo/config/HomeController.java +++ /dev/null @@ -1,15 +0,0 @@ -package urlshortener.demo.config; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; - -/** - * Home redirection to swagger api documentation - */ -@Controller -public class HomeController { - @RequestMapping(value = "/swagger") - public String index() { - return "redirect:swagger-ui.html"; - } -} diff --git a/demo/src/main/java/urlshortener/demo/config/LiveStatsConfig.java b/demo/src/main/java/urlshortener/demo/config/LiveStatsConfig.java new file mode 100644 index 00000000..8aa1c8fb --- /dev/null +++ b/demo/src/main/java/urlshortener/demo/config/LiveStatsConfig.java @@ -0,0 +1,23 @@ +package urlshortener.demo.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; + +@Configuration +@EnableWebSocketMessageBroker +public class LiveStatsConfig extends AbstractWebSocketMessageBrokerConfigurer { + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/stats/live"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/live-stats").withSockJS(); + } + +} diff --git a/demo/src/main/java/urlshortener/demo/config/PersistenceContext.java b/demo/src/main/java/urlshortener/demo/config/PersistenceContext.java index 40780e55..0f442759 100755 --- a/demo/src/main/java/urlshortener/demo/config/PersistenceContext.java +++ b/demo/src/main/java/urlshortener/demo/config/PersistenceContext.java @@ -1,33 +1,10 @@ package urlshortener.demo.config; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.jdbc.core.JdbcTemplate; - -import urlshortener.demo.repository.ClickRepository; -import urlshortener.demo.repository.ClickRepositoryImpl; -import urlshortener.demo.repository.ShortURLRepository; -import urlshortener.demo.repository.ShortURLRepositoryImpl; @Configuration -@ComponentScan(basePackages = {"urlshortener.demo" }) @Import({JacksonConfiguration.class, SwaggerDocumentationConfig.class}) public class PersistenceContext { - @Autowired - protected JdbcTemplate jdbc; - - @Bean - ShortURLRepository shortURLRepository() { - return new ShortURLRepositoryImpl(jdbc); - } - - @Bean - ClickRepository clickRepository() { - return new ClickRepositoryImpl(jdbc); - } - } diff --git a/demo/src/main/java/urlshortener/demo/config/RepositoryConfiguration.java b/demo/src/main/java/urlshortener/demo/config/RepositoryConfiguration.java new file mode 100644 index 00000000..60961542 --- /dev/null +++ b/demo/src/main/java/urlshortener/demo/config/RepositoryConfiguration.java @@ -0,0 +1,35 @@ +package urlshortener.demo.config; + +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import urlshortener.demo.domain.URIItem; +import urlshortener.demo.repository.QRRepository; +import urlshortener.demo.repository.URIRepository; +import urlshortener.demo.repository.impl.QRRepositoryImpl; +import urlshortener.demo.repository.impl.StatsRepositoryHook; +import urlshortener.demo.repository.impl.URIRepositoryImpl; + +@Configuration +public class RepositoryConfiguration { + + private final MeterRegistry metrics; + + @Autowired + public RepositoryConfiguration(MeterRegistry metrics) { + this.metrics = metrics; + } + + @Bean("uriRepository") + public URIRepository getURIRepository(){ + StatsRepositoryHook statsHook = new StatsRepositoryHook<>(null, metrics, "uri"); + return new URIRepositoryImpl(statsHook); + } + + @Bean("qrRepository") + public QRRepository getQRRepository(){ + return new QRRepositoryImpl(); + } + +} diff --git a/demo/src/main/java/urlshortener/demo/controller/CheckApi.java b/demo/src/main/java/urlshortener/demo/controller/CheckApi.java index 71e05c20..0f1ad235 100644 --- a/demo/src/main/java/urlshortener/demo/controller/CheckApi.java +++ b/demo/src/main/java/urlshortener/demo/controller/CheckApi.java @@ -1,8 +1,3 @@ -/** - * NOTE: This class is auto generated by the swagger code generator program (3.0.2). - * https://github.com/swagger-api/swagger-codegen - * Do not edit the class manually. - */ package urlshortener.demo.controller; import io.swagger.annotations.*; @@ -18,8 +13,9 @@ public interface CheckApi { @ApiOperation(value = "Checks the state of the original URI", nickname = "checkURI", notes = "Checks the state of the original URI", tags={ "F5", }) @ApiResponses(value = { - @ApiResponse(code = 200, message = "The original URI is alive"), - @ApiResponse(code = 404, message = "The original URI is dead or the shortened URI doesn't exist") }) + @ApiResponse(code = 200, message = "The original URI is alive"), + @ApiResponse(code = 404, message = "The original URI is dead or the shortened URI doesn't exist") + }) @RequestMapping(value = "/check/{id}", method = RequestMethod.GET) ResponseEntity checkURI(@ApiParam(value = "",required=true) @PathVariable("id") String id); diff --git a/demo/src/main/java/urlshortener/demo/controller/StatsApi.java b/demo/src/main/java/urlshortener/demo/controller/StatsApi.java index 491bdbdd..b42b1397 100644 --- a/demo/src/main/java/urlshortener/demo/controller/StatsApi.java +++ b/demo/src/main/java/urlshortener/demo/controller/StatsApi.java @@ -10,6 +10,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import urlshortener.demo.domain.URIStats; @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-23T14:33:33.583Z[GMT]") @@ -24,4 +25,14 @@ public interface StatsApi { method = RequestMethod.GET) ResponseEntity getStats(); + @ApiOperation(value = "Returns stats from server for a certain URI", nickname = "getUriStats", notes = "Get uri stats info ", response = URIStats.class, tags={ "F1 - The app will return system stats and info. (WS)", }) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "URIStats from server", response = URIStats.class) }) + @RequestMapping(value = "/stats/{id}", + produces = { "application/json" }, + method = RequestMethod.GET) + ResponseEntity getUriStats(String id); + + + } diff --git a/demo/src/main/java/urlshortener/demo/controller/UriApi.java b/demo/src/main/java/urlshortener/demo/controller/UriApi.java index d17bf037..5da840b2 100644 --- a/demo/src/main/java/urlshortener/demo/controller/UriApi.java +++ b/demo/src/main/java/urlshortener/demo/controller/UriApi.java @@ -1,8 +1,3 @@ -/** - * NOTE: This class is auto generated by the swagger code generator program (3.0.2). - * https://github.com/swagger-api/swagger-codegen - * Do not edit the class manually. - */ package urlshortener.demo.controller; import io.swagger.annotations.*; @@ -10,6 +5,7 @@ import org.springframework.web.bind.annotation.*; import urlshortener.demo.domain.URICreate; import urlshortener.demo.domain.URIItem; +import urlshortener.demo.domain.URIUpdate; import javax.validation.Valid; @@ -26,19 +22,32 @@ public interface UriApi { produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.PUT) - ResponseEntity changeURI(@ApiParam(value = "Optional description in *Markdown*" ,required=true ) @Valid @RequestBody URICreate body,@ApiParam(value = "",required=true) @PathVariable("name") String name); + public ResponseEntity changeUri(@ApiParam(value = "update info", required=true ) @Valid @RequestBody URIUpdate body, + @ApiParam(value = "actual name", required=true) @PathVariable("name") String name); @ApiOperation(value = "Creates a new redirection", nickname = "createURI", notes = "Create a new URI redirection ", response = URIItem.class, tags={ "F0 - The app will short, storage and get URI's", }) @ApiResponses(value = { - @ApiResponse(code = 201, message = "The URI redirection has been successfully created", response = URIItem.class), - @ApiResponse(code = 400, message = "Error creating resource") }) + @ApiResponse(code = 201, message = "The URI redirection has been successfully created", response = URIItem.class), + @ApiResponse(code = 400, message = "The URI was not reachable", response = URIItem.class) + }) @RequestMapping(value = "/uri", produces = { "application/json" }, consumes = { "application/json" }, - method = RequestMethod.PUT) + method = RequestMethod.POST) ResponseEntity createURI(@ApiParam(value = "URI" ,required=true ) @Valid @RequestBody URICreate body); + @ApiOperation(value = "Creates a new redirection with a custom name", nickname = "createURIWithName", notes = "Create a new URI redirection with a custom name", response = URIItem.class, tags={ "F0 - The app will short, storage and get URI's", }) + @ApiResponses(value = { + @ApiResponse(code = 201, message = "The URI redirection has been successfully created", response = URIItem.class), + @ApiResponse(code = 400, message = "The URI was not reachable", response = URIItem.class) + }) + @RequestMapping(value = "/uri", + produces = { "application/json" }, + consumes = { "application/json" }, + method = RequestMethod.PUT) + ResponseEntity createURIwithName(@ApiParam(value = "URI" ,required=true ) @Valid @RequestBody URICreate body); + @ApiOperation(value = "Deletes an existing URI and its content.", nickname = "deleteURI", notes = "Remove a URI redirection ", tags={ "F0 - The app will short, storage and get URI's", }) @ApiResponses(value = { @@ -51,8 +60,9 @@ public interface UriApi { @ApiOperation(value = "Returns the data of a redirection", nickname = "getURI", notes = "Get a URI redirection ", tags={ "F0 - The app will short, storage and get URI's", }) @ApiResponses(value = { - @ApiResponse(code = 307, message = "Redirect to the real URI"), - @ApiResponse(code = 404, message = "The given URI couldn't be found") }) + @ApiResponse(code = 307, message = "Redirect to the real URI"), + @ApiResponse(code = 404, message = "The given URI couldn't be found") + }) @RequestMapping(value = "/uri/{id}", method = RequestMethod.GET) ResponseEntity getURI(@ApiParam(value = "",required=true) @PathVariable("id") String id); diff --git a/demo/src/main/java/urlshortener/demo/controller/impl/CheckApiController.java b/demo/src/main/java/urlshortener/demo/controller/impl/CheckApiController.java index bc3dc7c1..d913f4db 100644 --- a/demo/src/main/java/urlshortener/demo/controller/impl/CheckApiController.java +++ b/demo/src/main/java/urlshortener/demo/controller/impl/CheckApiController.java @@ -9,8 +9,13 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import urlshortener.demo.controller.CheckApi; +import urlshortener.demo.domain.URIItem; +import urlshortener.demo.exception.InvalidRequestParametersException; +import urlshortener.demo.repository.URIRepository; +import urlshortener.demo.utils.CheckAlive; import javax.servlet.http.HttpServletRequest; +import java.io.IOException; @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-21T05:15:43.072Z[GMT]") @@ -23,16 +28,30 @@ public class CheckApiController implements CheckApi { private final HttpServletRequest request; + private final URIRepository uriService; + @org.springframework.beans.factory.annotation.Autowired - public CheckApiController(ObjectMapper objectMapper, HttpServletRequest request) { + public CheckApiController(ObjectMapper objectMapper, HttpServletRequest request, URIRepository uriService) { this.objectMapper = objectMapper; this.request = request; + this.uriService = uriService; } public ResponseEntity checkURI(@ApiParam(value = "",required=true) @PathVariable("id") String id) { String accept = request.getHeader("Accept"); + URIItem uri = uriService.get(id); - return new ResponseEntity(HttpStatus.OK); - } + CheckAlive c = new CheckAlive(); + try { + HttpStatus httpStatus = HttpStatus.valueOf(c.makeRequest(uri.getRedirection())); + + if (httpStatus != HttpStatus.OK) { + return new ResponseEntity(HttpStatus.NOT_FOUND); + } + return new ResponseEntity(httpStatus); + } catch (IOException e) { + throw new InvalidRequestParametersException(HttpStatus.BAD_REQUEST.value(), "There was a problem with the parameters."); + } + } } diff --git a/demo/src/main/java/urlshortener/demo/controller/impl/QrApiController.java b/demo/src/main/java/urlshortener/demo/controller/impl/QrApiController.java index 14792544..bf5ab852 100644 --- a/demo/src/main/java/urlshortener/demo/controller/impl/QrApiController.java +++ b/demo/src/main/java/urlshortener/demo/controller/impl/QrApiController.java @@ -7,17 +7,18 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PathVariable; import urlshortener.demo.controller.QrApi; import urlshortener.demo.domain.QRItem; -import urlshortener.demo.utils.*; +import urlshortener.demo.domain.URIItem; +import urlshortener.demo.repository.QRRepository; +import urlshortener.demo.repository.URIRepository; +import urlshortener.demo.utils.StringChecker; import javax.servlet.http.HttpServletRequest; @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-21T05:15:43.072Z[GMT]") @Controller -@Transactional public class QrApiController implements QrApi { private static final Logger log = LoggerFactory.getLogger(QrApiController.class); @@ -26,10 +27,16 @@ public class QrApiController implements QrApi { private final HttpServletRequest request; + private final QRRepository qrRepository; + + private final URIRepository uriRepository; + @org.springframework.beans.factory.annotation.Autowired - public QrApiController(ObjectMapper objectMapper, HttpServletRequest request) { + public QrApiController(ObjectMapper objectMapper, HttpServletRequest request, QRRepository qrRepository, URIRepository uriRepository) { this.objectMapper = objectMapper; this.request = request; + this.qrRepository = qrRepository; + this.uriRepository = uriRepository; } public ResponseEntity getQR(@ApiParam(value = "",required=true) @PathVariable("id") String id) { @@ -37,15 +44,30 @@ public ResponseEntity getQR(@ApiParam(value = "",required=true) @PathVar String accept = request.getHeader("Accept"); String width = request.getParameter("width"); String height = request.getParameter("height"); + + QRItem qr; // Check that width and heigth params are not null or negative // and return int int w = StringChecker.checkString2Int(width); int h = StringChecker.checkString2Int(height); + + // If uri is not in the URIRepository return 404 + if (!this.uriRepository.contains(id)){ + return new ResponseEntity(HttpStatus.NOT_FOUND); + } + + // Default qr is required, so it's saved if the uri is shorthed + if(w==500 && h==500){ + URIItem uriItem = this.uriRepository.get(id); + qr = this.qrRepository.get(uriItem.getRedirection()); + }else{ + URIItem uriItem = this.uriRepository.get(id); + qr = new QRItem(); + qr.setUri(uriItem.getRedirection()); + qr.convertBase64(w, h); + } - QRItem qr = new QRItem(); - qr.setUri(id); - qr.convertBase64(w, h); return new ResponseEntity(qr, HttpStatus.OK); } diff --git a/demo/src/main/java/urlshortener/demo/controller/impl/StatsApiController.java b/demo/src/main/java/urlshortener/demo/controller/impl/StatsApiController.java index f57cd21d..b42a542a 100644 --- a/demo/src/main/java/urlshortener/demo/controller/impl/StatsApiController.java +++ b/demo/src/main/java/urlshortener/demo/controller/impl/StatsApiController.java @@ -1,19 +1,23 @@ package urlshortener.demo.controller.impl; import com.fasterxml.jackson.databind.ObjectMapper; -import urlshortener.demo.controller.StatsApi; -import urlshortener.demo.domain.Stats; +import io.swagger.annotations.ApiParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import urlshortener.demo.controller.StatsApi; +import urlshortener.demo.domain.Stats; +import urlshortener.demo.domain.URIStats; +import urlshortener.demo.repository.StatsRepository; import javax.servlet.http.HttpServletRequest; -import java.math.BigDecimal; + @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-21T05:15:43.072Z[GMT]") -@RestController +@Controller public class StatsApiController implements StatsApi { private static final Logger log = LoggerFactory.getLogger(StatsApiController.class); @@ -22,19 +26,23 @@ public class StatsApiController implements StatsApi { private final HttpServletRequest request; + private final StatsRepository statsRepository; + @org.springframework.beans.factory.annotation.Autowired - public StatsApiController(ObjectMapper objectMapper, HttpServletRequest request) { + public StatsApiController(ObjectMapper objectMapper, HttpServletRequest request, StatsRepository statsRepository) { this.objectMapper = objectMapper; this.request = request; + this.statsRepository = statsRepository; } public ResponseEntity getStats() { String accept = request.getHeader("Accept"); - Stats stats = new Stats(); - stats.setRedirectedUris(745); - stats.setGeneratedQr(452); - stats.setServerLoad(new BigDecimal(0.23)); - return new ResponseEntity(stats, HttpStatus.OK); + return new ResponseEntity(statsRepository.getStats(), HttpStatus.OK); + } + + public ResponseEntity getUriStats(@ApiParam(value = "",required=true) @PathVariable("id") String keyID) { + String accept = request.getHeader("Accept"); + return new ResponseEntity(statsRepository.getUriStats(keyID), HttpStatus.OK); } } diff --git a/demo/src/main/java/urlshortener/demo/controller/impl/UriApiController.java b/demo/src/main/java/urlshortener/demo/controller/impl/UriApiController.java index da2e2aef..cda3d7db 100644 --- a/demo/src/main/java/urlshortener/demo/controller/impl/UriApiController.java +++ b/demo/src/main/java/urlshortener/demo/controller/impl/UriApiController.java @@ -12,24 +12,42 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import urlshortener.demo.controller.UriApi; +import urlshortener.demo.domain.QRItem; import urlshortener.demo.domain.URICreate; import urlshortener.demo.domain.URIItem; +import urlshortener.demo.domain.URIUpdate; import urlshortener.demo.exception.IncorrectHashPassException; +import urlshortener.demo.exception.InvalidRequestParametersException; import urlshortener.demo.exception.UnknownEntityException; +import urlshortener.demo.repository.QRRepository; +import urlshortener.demo.repository.StatsRepository; import urlshortener.demo.repository.URIRepository; +import urlshortener.demo.utils.CheckAlive; import urlshortener.demo.utils.ParameterUtils; import urlshortener.demo.utils.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.Date; +import java.util.Map; @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-21T05:15:43.072Z[GMT]") @Controller public class UriApiController implements UriApi { + // Limit to K redirections in one hour + private static final long MAX_REDIRECTION_TIME = 3600000L; + + // Limit to 100 redirections in X ms. + private static final long MAX_REDIRECTIONS = 100; + + // Default size of QR code + private static final int QR_SIZE = 500; + private static final Logger log = LoggerFactory.getLogger(UriApiController.class); private final ObjectMapper objectMapper; @@ -38,26 +56,70 @@ public class UriApiController implements UriApi { private final URIRepository uriService; + private final StatsRepository statsRepository; + + private final QRRepository qrService; + @org.springframework.beans.factory.annotation.Autowired - public UriApiController(ObjectMapper objectMapper, HttpServletRequest request, URIRepository uriService) { + public UriApiController(ObjectMapper objectMapper, HttpServletRequest request, URIRepository uriService, StatsRepository statsRepository, QRRepository qrService) { this.objectMapper = objectMapper; this.request = request; this.uriService = uriService; + this.statsRepository = statsRepository; + this.qrService = qrService; } - public ResponseEntity changeURI(@ApiParam(value = "Optional description in *Markdown*" ,required=true ) @Valid @RequestBody URICreate body,@ApiParam(value = "",required=true) @PathVariable("name") String name) { + public ResponseEntity changeUri(@ApiParam(value = "update info", required=true ) @Valid @RequestBody URIUpdate body, + @ApiParam(value = "actual name", required=true) @PathVariable("name") String name) { String accept = request.getHeader("Accept"); + + URIItem actualUri = uriService.get(name); + URICreate uri = new URICreate().uri(actualUri.getRedirection()); + + if(!actualUri.checkHashPass(body.getHashpass())) { + throw new IncorrectHashPassException(HttpStatus.BAD_REQUEST.value(), "Given hashpass doesn't match " + actualUri.getId() + " hashpass."); + } + + return createNewUri(uri,body.getNewName()); + + } + + private ResponseEntity createNewUri(URICreate uri, String name) { + CheckAlive c = new CheckAlive(); + ParameterUtils.checkParameter(name); - ParameterUtils.checkParameter(body.getUri()); + ParameterUtils.checkParameter(uri.getUri()); - URIItem item = (URIItem) new URIItem().id(name).redirection(body.getUri()).hashpass(StringUtils.randomHash()); - uriService.add(item); - return new ResponseEntity(item, HttpStatus.CREATED); + URIItem item = (URIItem) new URIItem().id(name).redirection(uri.getUri()).hashpass(StringUtils.randomHash()); + + try { + if (Integer.valueOf(c.makeRequest(item.getRedirection())) == 200) { + uriService.add(item); + // Save default QR to QRRepository if it doesn't already exist + if(!qrService.contains(uri.getUri())){ + QRItem qrItem = new QRItem(); + qrItem.setUri(uri.getUri()); + qrItem.convertBase64(QR_SIZE, QR_SIZE); + + this.qrService.add(qrItem); + } + return new ResponseEntity(item, HttpStatus.CREATED); + } else { + return new ResponseEntity(HttpStatus.BAD_REQUEST); + } + } catch (IOException e) { + throw new InvalidRequestParametersException(HttpStatus.BAD_REQUEST.value(), "There was a problem with the parameters."); + } } public ResponseEntity createURI(@ApiParam(value = "URI" ,required=true ) @Valid @RequestBody URICreate body) { String accept = request.getHeader("Accept"); - return changeURI(body, Long.toHexString(uriService.getNextID())); + return createNewUri(body, Long.toHexString(uriService.getNextID())); + } + + public ResponseEntity createURIwithName(@ApiParam(value = "URI" ,required=true ) @Valid @RequestBody URICreate body) { + String accept = request.getHeader("Accept"); + return createNewUri(body, body.getName()); } public ResponseEntity deleteURI(@ApiParam(value = "",required=true) @PathVariable("id") String id, @RequestHeader("URIHashPass") String hashpass) { @@ -76,24 +138,55 @@ public ResponseEntity deleteURI(@ApiParam(value = "",required=true) @PathV public ResponseEntity getURI(@ApiParam(value = "",required=true) @PathVariable("id") String id) { String accept = request.getHeader("Accept"); + CheckAlive c = new CheckAlive(); + URI location = null; + Map fechas = uriService.getAllFechas(); String redirection; URIItem item = uriService.get(id); if(item == null){ throw new UnknownEntityException(1, "Unknown URI: " + id); } + redirection = item.getRedirection(); - URI location = null; try { - location = new URI(redirection); - } catch (URISyntaxException e) { - e.printStackTrace(); + if (c.makeRequest(redirection) == 200){ + //OK + //Para esa URI, se registra la fecha actual como útima fecha en la que estuvo viva + location = new URI(redirection); + uriService.removeFecha(item.getId()); + uriService.addFecha(item.getId(), new Date()); + } + else { + //Cualquier otra cosa aparte de un código 200 significará que la URI está muerta + //Se obtiene la última vez que la URI estuvo viva + // -Si la diferencia entre la fecha actual y la fecha recuperada es >= K, entonces la URI se borra + long actual = System.currentTimeMillis(); + long fechaUri = fechas.get(item.getId()).getTime(); + long diff = actual - fechaUri; + long days = diff / (604800000); + if (days >= 7.0){ + uriService.remove(item.getId()); + } + + return new ResponseEntity(HttpStatus.NOT_FOUND); + } + + } catch (URISyntaxException | IOException e) { + throw new InvalidRequestParametersException(HttpStatus.BAD_REQUEST.value(), ""); + } + + if(uriService.getRedirectionAmount(id, MAX_REDIRECTION_TIME) > MAX_REDIRECTIONS){ + return new ResponseEntity<>(HttpStatus.TOO_MANY_REQUESTS); } + HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.setLocation(location); + statsRepository.incrementAccessStats(id); + return new ResponseEntity(responseHeaders, HttpStatus.TEMPORARY_REDIRECT); } diff --git a/demo/src/main/java/urlshortener/demo/domain/Click.java b/demo/src/main/java/urlshortener/demo/domain/Click.java deleted file mode 100644 index 41417404..00000000 --- a/demo/src/main/java/urlshortener/demo/domain/Click.java +++ /dev/null @@ -1,88 +0,0 @@ -package urlshortener.demo.domain; - -import java.sql.Date; - -public class Click { - - private Long id; - private String hash; - private Date created; - private String referrer; - private String browser; - private String platform; - private String ip; - private String country; - - - public Long getId() { - return id; - } - - public String getHash() { - return hash; - } - - public Date getCreated() { - return created; - } - - public String getReferrer() { - return referrer; - } - - public String getBrowser() { - return browser; - } - - public String getPlatform() { - return platform; - } - - public String getIp() { - return ip; - } - - public String getCountry() { - return country; - } - - public Click id(Long id) { - this.id = id; - return this; - } - - public Click hash(String hash) { - this.hash = hash; - return this; - } - - public Click created(Date created) { - this.created = created; - return this; - } - - public Click referrer(String referrer) { - this.referrer = referrer; - return this; - } - - public Click browser(String browser) { - this.browser = browser; - return this; - } - - public Click platform(String platform) { - this.platform = platform; - return this; - } - - public Click ip(String ip) { - this.ip = ip; - return this; - } - - public Click country(String country) { - this.country = country; - return this; - } -} diff --git a/demo/src/main/java/urlshortener/demo/domain/QRItem.java b/demo/src/main/java/urlshortener/demo/domain/QRItem.java index 8c68ab63..8577ee7f 100644 --- a/demo/src/main/java/urlshortener/demo/domain/QRItem.java +++ b/demo/src/main/java/urlshortener/demo/domain/QRItem.java @@ -1,31 +1,28 @@ package urlshortener.demo.domain; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.client.j2se.MatrixToImageConfig; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; import io.swagger.annotations.ApiModelProperty; -import org.springframework.validation.annotation.Validated; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; -import javax.persistence.Entity; -import javax.persistence.Id; import javax.validation.constraints.NotNull; +import java.io.ByteArrayOutputStream; +import java.util.Base64; import java.util.Objects; // Libraries for QR Generation -import com.google.zxing.*; -import com.google.zxing.common.BitMatrix; - -import com.google.zxing.client.j2se.*; -import java.util.Base64; -import java.io.ByteArrayOutputStream; /** * QRItem */ @Validated @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-23T14:33:33.583Z[GMT]") -@Entity public class QRItem implements BaseEntity{ - @Id @JsonProperty("uri") private String uri = null; diff --git a/demo/src/main/java/urlshortener/demo/domain/ShortURL.java b/demo/src/main/java/urlshortener/demo/domain/ShortURL.java deleted file mode 100644 index 4b579ae0..00000000 --- a/demo/src/main/java/urlshortener/demo/domain/ShortURL.java +++ /dev/null @@ -1,108 +0,0 @@ -package urlshortener.demo.domain; - -import java.net.URI; -import java.sql.Date; - -public class ShortURL { - - private String hash; - private String target; - private URI uri; - private String sponsor; - private Date created; - private String owner; - private Integer mode; - private Boolean safe; - private String ip; - private String country; - - public String getHash() { - return hash; - } - - public String getTarget() { - return target; - } - - public URI getUri() { - return uri; - } - - public Date getCreated() { - return created; - } - - public String getOwner() { - return owner; - } - - public Integer getMode() { - return mode; - } - - public String getSponsor() { - return sponsor; - } - - public Boolean getSafe() { - return safe; - } - - public String getIP() { - return ip; - } - - public String getCountry() { - return country; - } - - public ShortURL hash(String hash) { - this.hash = hash; - return this; - } - - public ShortURL target(String target) { - this.target = target; - return this; - } - - public ShortURL uri(URI uri) { - this.uri = uri; - return this; - } - - public ShortURL sponsor(String sponsor) { - this.sponsor = sponsor; - return this; - } - - public ShortURL created(Date created) { - this.created = created; - return this; - } - - public ShortURL owner(String owner) { - this.owner = owner; - return this; - } - - public ShortURL mode(Integer mode) { - this.mode = mode; - return this; - } - - public ShortURL safe(Boolean safe) { - this.safe = safe; - return this; - } - - public ShortURL ip(String ip) { - this.ip = ip; - return this; - } - - public ShortURL country(String country) { - this.country = country; - return this; - } -} diff --git a/demo/src/main/java/urlshortener/demo/domain/Stats.java b/demo/src/main/java/urlshortener/demo/domain/Stats.java index c01f7f71..f51c86db 100644 --- a/demo/src/main/java/urlshortener/demo/domain/Stats.java +++ b/demo/src/main/java/urlshortener/demo/domain/Stats.java @@ -1,12 +1,10 @@ package urlshortener.demo.domain; -import java.util.Objects; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; -import java.math.BigDecimal; import org.springframework.validation.annotation.Validated; -import javax.validation.Valid; -import javax.validation.constraints.*; + +import java.math.BigDecimal; +import java.util.Objects; /** * Stats @@ -15,79 +13,127 @@ @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-26T14:20:22.002Z[GMT]") public class Stats { - @JsonProperty("redirected-uris") - private Integer redirectedUris = null; + @JsonProperty("cpu.usage") + private BigDecimal cpuUsage = BigDecimal.ZERO; + + @JsonProperty("system.cpu.usage") + private BigDecimal serverUsage = BigDecimal.ZERO; + + @JsonProperty("system.memory.usage") + private Integer memoryUsage = 0; + + @JsonProperty("uris.created") + private BigDecimal urisCreated = BigDecimal.ZERO; + + @JsonProperty("uris.accessed") + private BigDecimal urisAccessed = BigDecimal.ZERO; - @JsonProperty("server-load") - private BigDecimal serverLoad = null; + @JsonProperty("uris.removed") + private BigDecimal urisRemoved = BigDecimal.ZERO; - @JsonProperty("generated-qr") - private Integer generatedQr = null; + @JsonProperty("uris.now") + private BigDecimal urisNow = BigDecimal.ZERO; - public Stats redirectedUris(Integer redirectedUris) { - this.redirectedUris = redirectedUris; - return this; + @JsonProperty("qr.created") + private BigDecimal qrCreated = BigDecimal.ZERO; + + @JsonProperty("qr.removed") + private BigDecimal qrRemoved = BigDecimal.ZERO; + + @JsonProperty("qr.now") + private BigDecimal qrNow = BigDecimal.ZERO; + + @JsonProperty("qr.accessed") + private BigDecimal qrAccessed = BigDecimal.ZERO; + + + public void setCpuUsage(BigDecimal cpuUsage) { + this.cpuUsage = cpuUsage; } - /** - * Get redirectedUris - * @return redirectedUris - **/ - @ApiModelProperty(example = "3457", required = true, value = "") - @NotNull + public void setServerUsage(BigDecimal serverUsage) { + this.serverUsage = serverUsage; + } + public void setMemoryUsage(Integer memoryUsage) { + this.memoryUsage = memoryUsage; + } - public Integer getRedirectedUris() { - return redirectedUris; + public void setUrisCreated(BigDecimal urisCreated) { + this.urisCreated = urisCreated; } - public void setRedirectedUris(Integer redirectedUris) { - this.redirectedUris = redirectedUris; + public void setUrisRemoved(BigDecimal urisRemoved) { + this.urisRemoved = urisRemoved; } - public Stats serverLoad(BigDecimal serverLoad) { - this.serverLoad = serverLoad; - return this; + public void setUrisNow(BigDecimal urisNow) { + this.urisNow = urisNow; } - /** - * Get serverLoad - * @return serverLoad - **/ - @ApiModelProperty(example = "0.79", required = true, value = "") - @NotNull + public void setQrCreated(BigDecimal qrCreated) { + this.qrCreated = qrCreated; + } - @Valid + public void setQrRemoved(BigDecimal qrRemoved) { + this.qrRemoved = qrRemoved; + } - public BigDecimal getServerLoad() { - return serverLoad; + public void setQrNow(BigDecimal qrNow) { + this.qrNow = qrNow; } - public void setServerLoad(BigDecimal serverLoad) { - this.serverLoad = serverLoad; + public BigDecimal getQrAccessed() { + return qrAccessed; } - public Stats generatedQr(Integer generatedQr) { - this.generatedQr = generatedQr; - return this; + public void setQrAccessed(BigDecimal qrAccessed) { + this.qrAccessed = qrAccessed; } - /** - * Get generatedQr - * @return generatedQr - **/ - @ApiModelProperty(example = "1243", required = true, value = "") - @NotNull + public BigDecimal getUrisAccessed() { + return urisAccessed; + } + + public void setUrisAccessed(BigDecimal urisAccessed) { + this.urisAccessed = urisAccessed; + } + + public BigDecimal getCpuUsage() { + return cpuUsage; + } + + public BigDecimal getServerUsage() { + return serverUsage; + } + public Integer getMemoryUsage() { + return memoryUsage; + } - public Integer getGeneratedQr() { - return generatedQr; + public BigDecimal getUrisCreated() { + return urisCreated; } - public void setGeneratedQr(Integer generatedQr) { - this.generatedQr = generatedQr; + public BigDecimal getUrisRemoved() { + return urisRemoved; } + public BigDecimal getUrisNow() { + return urisNow; + } + + public BigDecimal getQrCreated() { + return qrCreated; + } + + public BigDecimal getQrRemoved() { + return qrRemoved; + } + + public BigDecimal getQrNow() { + return qrNow; + } @Override public boolean equals(java.lang.Object o) { @@ -98,14 +144,20 @@ public boolean equals(java.lang.Object o) { return false; } Stats stats = (Stats) o; - return Objects.equals(this.redirectedUris, stats.redirectedUris) && - Objects.equals(this.serverLoad, stats.serverLoad) && - Objects.equals(this.generatedQr, stats.generatedQr); + return Objects.equals(this.cpuUsage, stats.cpuUsage) && + Objects.equals(this.serverUsage, stats.serverUsage) && + Objects.equals(this.urisCreated, stats.urisCreated) && + Objects.equals(this.urisRemoved, stats.urisRemoved) && + Objects.equals(this.urisNow, stats.urisNow) && + Objects.equals(this.qrCreated, stats.qrCreated) && + Objects.equals(this.qrRemoved, stats.qrRemoved) && + Objects.equals(this.qrNow, stats.qrNow) && + Objects.equals(this.memoryUsage, stats.memoryUsage); } @Override public int hashCode() { - return Objects.hash(redirectedUris, serverLoad, generatedQr); + return Objects.hash(cpuUsage, serverUsage, memoryUsage, urisCreated, urisNow, urisRemoved, qrCreated, qrRemoved, qrNow); } @Override @@ -113,9 +165,15 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class Stats {\n"); - sb.append(" redirectedUris: ").append(toIndentedString(redirectedUris)).append("\n"); - sb.append(" serverLoad: ").append(toIndentedString(serverLoad)).append("\n"); - sb.append(" generatedQr: ").append(toIndentedString(generatedQr)).append("\n"); + sb.append(" cpuUsage: ").append(toIndentedString(cpuUsage)).append("\n"); + sb.append(" serverUsage: ").append(toIndentedString(serverUsage)).append("\n"); + sb.append(" memoryUsage: ").append(toIndentedString(memoryUsage)).append("\n"); + sb.append(" urisCreated: ").append(toIndentedString(urisCreated)).append("\n"); + sb.append(" urisRemoved: ").append(toIndentedString(urisRemoved)).append("\n"); + sb.append(" urisNow: ").append(toIndentedString(urisNow)).append("\n"); + sb.append(" qrCreated: ").append(toIndentedString(qrCreated)).append("\n"); + sb.append(" qrRemoved: ").append(toIndentedString(qrRemoved)).append("\n"); + sb.append(" qrNow: ").append(toIndentedString(qrNow)).append("\n"); sb.append("}"); return sb.toString(); } diff --git a/demo/src/main/java/urlshortener/demo/domain/URIBase.java b/demo/src/main/java/urlshortener/demo/domain/URIBase.java index f8404fd5..988d1470 100644 --- a/demo/src/main/java/urlshortener/demo/domain/URIBase.java +++ b/demo/src/main/java/urlshortener/demo/domain/URIBase.java @@ -40,7 +40,7 @@ public boolean equals(java.lang.Object o) { if (o == null || getClass() != o.getClass()) { return false; } - URIBase urIItem = (URIItem) o; + URIBase urIItem = (URIBase) o; return Objects.equals(this.hashpass, urIItem.hashpass); } diff --git a/demo/src/main/java/urlshortener/demo/domain/URICreate.java b/demo/src/main/java/urlshortener/demo/domain/URICreate.java index 2ac07925..b5a35661 100644 --- a/demo/src/main/java/urlshortener/demo/domain/URICreate.java +++ b/demo/src/main/java/urlshortener/demo/domain/URICreate.java @@ -17,6 +17,9 @@ public class URICreate { @JsonProperty("uri") private String uri = null; + @JsonProperty("name") + private String name = ""; + public URICreate uri(String uri) { this.uri = uri; return this; @@ -28,8 +31,6 @@ public URICreate uri(String uri) { **/ @ApiModelProperty(example = "https://google.es/", required = true, value = "") @NotNull - - public String getUri() { return uri; } @@ -38,6 +39,25 @@ public void setUri(String uri) { this.uri = uri; } + /** + * Get uri + * @return uri + **/ + @ApiModelProperty(example = "https://google.es/", required = true, value = "") + @NotNull + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public URICreate name(String name) { + this.name = name; + return this; + } + @Override public boolean equals(java.lang.Object o) { @@ -48,20 +68,21 @@ public boolean equals(java.lang.Object o) { return false; } URICreate urICreate = (URICreate) o; - return Objects.equals(this.uri, urICreate.uri); + return Objects.equals(this.uri, urICreate.uri) && Objects.equals(this.name, urICreate.name); } @Override public int hashCode() { - return Objects.hash(uri); + return Objects.hash(uri,name); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class URICreate {\n"); - + sb.append(" uri: ").append(toIndentedString(uri)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); sb.append("}"); return sb.toString(); } diff --git a/demo/src/main/java/urlshortener/demo/domain/URIStats.java b/demo/src/main/java/urlshortener/demo/domain/URIStats.java new file mode 100644 index 00000000..5866fb01 --- /dev/null +++ b/demo/src/main/java/urlshortener/demo/domain/URIStats.java @@ -0,0 +1,65 @@ +package urlshortener.demo.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.validation.annotation.Validated; + +import java.math.BigDecimal; +import java.util.Objects; + +/** + * Stats + */ +@Validated +@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-26T14:20:22.002Z[GMT]") + +public class URIStats { + + @JsonProperty("uri.accesses") + private BigDecimal uriAccesses = BigDecimal.ZERO; + + public BigDecimal getUriAccesses() { + return uriAccesses; + } + + public void setUriAccesses(BigDecimal uriAccesses) { + this.uriAccesses = uriAccesses; + } + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + URIStats stats = (URIStats) o; + return Objects.equals(this.uriAccesses, stats.uriAccesses); + } + + @Override + public int hashCode() { + return Objects.hash(uriAccesses); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class Stats {\n"); + + sb.append(" cpuUsage: ").append(toIndentedString(uriAccesses)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/demo/src/main/java/urlshortener/demo/exception/CannotAddEntityException.java b/demo/src/main/java/urlshortener/demo/exception/CannotAddEntityException.java index 7e7b5342..3594124d 100644 --- a/demo/src/main/java/urlshortener/demo/exception/CannotAddEntityException.java +++ b/demo/src/main/java/urlshortener/demo/exception/CannotAddEntityException.java @@ -3,9 +3,7 @@ @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-26T14:20:22.002Z[GMT]") public class CannotAddEntityException extends RuntimeApiException { - private int code; public CannotAddEntityException(int code, String msg) { super(code, msg); - this.code = code; } } diff --git a/demo/src/main/java/urlshortener/demo/exception/IncorrectHashPassException.java b/demo/src/main/java/urlshortener/demo/exception/IncorrectHashPassException.java index eb7afbbf..89aaf821 100644 --- a/demo/src/main/java/urlshortener/demo/exception/IncorrectHashPassException.java +++ b/demo/src/main/java/urlshortener/demo/exception/IncorrectHashPassException.java @@ -3,9 +3,7 @@ @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-26T14:20:22.002Z[GMT]") public class IncorrectHashPassException extends RuntimeApiException { - private int code; public IncorrectHashPassException(int code, String msg) { super(code, msg); - this.code = code; } } diff --git a/demo/src/main/java/urlshortener/demo/exception/InvalidRequestParametersException.java b/demo/src/main/java/urlshortener/demo/exception/InvalidRequestParametersException.java index 9131f2ac..36d32a2e 100644 --- a/demo/src/main/java/urlshortener/demo/exception/InvalidRequestParametersException.java +++ b/demo/src/main/java/urlshortener/demo/exception/InvalidRequestParametersException.java @@ -3,9 +3,7 @@ @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-26T14:20:22.002Z[GMT]") public class InvalidRequestParametersException extends RuntimeApiException { - private int code; public InvalidRequestParametersException(int code, String msg) { super(code, msg); - this.code = code; } } diff --git a/demo/src/main/java/urlshortener/demo/exception/NotFoundException.java b/demo/src/main/java/urlshortener/demo/exception/NotFoundException.java index 44cd56c6..4a14eb77 100644 --- a/demo/src/main/java/urlshortener/demo/exception/NotFoundException.java +++ b/demo/src/main/java/urlshortener/demo/exception/NotFoundException.java @@ -3,9 +3,7 @@ @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-26T14:20:22.002Z[GMT]") public class NotFoundException extends ApiException { - private int code; public NotFoundException (int code, String msg) { super(code, msg); - this.code = code; } } diff --git a/demo/src/main/java/urlshortener/demo/exception/UnknownEntityException.java b/demo/src/main/java/urlshortener/demo/exception/UnknownEntityException.java index 3416d8cc..075f80c9 100644 --- a/demo/src/main/java/urlshortener/demo/exception/UnknownEntityException.java +++ b/demo/src/main/java/urlshortener/demo/exception/UnknownEntityException.java @@ -3,9 +3,7 @@ @javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-26T14:20:22.002Z[GMT]") public class UnknownEntityException extends RuntimeApiException{ - private int code; public UnknownEntityException(int code, String msg) { super(code, msg); - this.code = code; } } diff --git a/demo/src/main/java/urlshortener/demo/repository/AbstractRepository.java b/demo/src/main/java/urlshortener/demo/repository/AbstractRepository.java index 17060bb7..1ee5f321 100644 --- a/demo/src/main/java/urlshortener/demo/repository/AbstractRepository.java +++ b/demo/src/main/java/urlshortener/demo/repository/AbstractRepository.java @@ -5,6 +5,7 @@ import urlshortener.demo.exception.CannotAddEntityException; import urlshortener.demo.exception.UnknownEntityException; +import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -12,19 +13,43 @@ public class AbstractRepository> implements IReposito private long nextID = 0; private Map uris = new HashMap<>(); + private Map fechas = new HashMap<>(); + + public Map getAllUris(){ + return uris; + } + + public Map getAllFechas() { + return fechas; + } + + public void removeFecha(K hash){ + if(!fechas.containsKey(hash)){ + throw new UnknownEntityException(HttpStatus.NOT_FOUND.value(), "Cannot fetch date with uri hash " + hash); + } + fechas.remove(hash); + } + + public void addFecha(K hash, Date fecha){ + if(fechas.containsKey(hash)){ + throw new UnknownEntityException(HttpStatus.NOT_FOUND.value(), "Cannot add date with uri hash " + hash); + } + fechas.put(hash, fecha); + } @Override - public void add(V uri) throws CannotAddEntityException { + public void add(V uri) { if(uris.containsKey(uri.getId())){ throw new CannotAddEntityException(HttpStatus.BAD_REQUEST.value(), "Cannot add uri with hash " + uri.getId()); } uris.put(uri.getId(), uri); + fechas.put(uri.getId(), new Date()); nextID++; } @Override - public V get(K hash) throws UnknownEntityException { + public V get(K hash) { V item = uris.get(hash); if(item == null){ throw new UnknownEntityException(HttpStatus.NOT_FOUND.value(), "Cannot fetch uri with hash " + hash); @@ -33,11 +58,12 @@ public V get(K hash) throws UnknownEntityException { } @Override - public void remove(K hash) throws UnknownEntityException { + public void remove(K hash) { if(!uris.containsKey(hash)){ throw new UnknownEntityException(HttpStatus.NOT_FOUND.value(), "Cannot fetch uri with hash " + hash); } uris.remove(hash); + fechas.remove(hash); } @Override @@ -55,4 +81,8 @@ public boolean contains(K key){ return uris.containsKey(key); } + @Override + public int getCount() { + return uris.size(); + } } diff --git a/demo/src/main/java/urlshortener/demo/repository/ClickRepository.java b/demo/src/main/java/urlshortener/demo/repository/ClickRepository.java deleted file mode 100644 index 3dec2ff9..00000000 --- a/demo/src/main/java/urlshortener/demo/repository/ClickRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package urlshortener.demo.repository; - -import java.util.List; - -import urlshortener.demo.domain.Click; - -public interface ClickRepository { - - List findByHash(String hash); - - Long clicksByHash(String hash); - - Click save(Click cl); - - void update(Click cl); - - void delete(Long id); - - void deleteAll(); - - Long count(); - - List list(Long limit, Long offset); -} diff --git a/demo/src/main/java/urlshortener/demo/repository/ClickRepositoryImpl.java b/demo/src/main/java/urlshortener/demo/repository/ClickRepositoryImpl.java deleted file mode 100644 index 2886db2d..00000000 --- a/demo/src/main/java/urlshortener/demo/repository/ClickRepositoryImpl.java +++ /dev/null @@ -1,151 +0,0 @@ -package urlshortener.demo.repository; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.dao.DuplicateKeyException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; -import urlshortener.demo.domain.Click; - -import java.sql.PreparedStatement; -import java.sql.Statement; -import java.sql.Types; -import java.util.Collections; -import java.util.List; - - -@Repository -public class ClickRepositoryImpl implements ClickRepository { - - private static final Logger log = LoggerFactory - .getLogger(urlshortener.demo.repository.ClickRepositoryImpl.class); - - private static final RowMapper rowMapper = (rs, rowNum) -> new Click() - .id(rs.getLong("id")).hash(rs.getString("hash")) - .created(rs.getDate("created")).referrer(rs.getString("referrer")).browser(rs.getString("browser")).platform(rs.getString("platform")) - .ip(rs.getString("ip")).country(rs.getString("country")); - - private JdbcTemplate jdbc; - - public ClickRepositoryImpl(JdbcTemplate jdbc) { - this.jdbc = jdbc; - } - - @Override - public List findByHash(String hash) { - try { - return jdbc.query("SELECT * FROM click WHERE hash=?", - new Object[] { hash }, rowMapper); - } catch (Exception e) { - log.debug("When select for hash " + hash, e); - return Collections.emptyList(); - } - } - - @Override - public Click save(final Click cl) { - try { - KeyHolder holder = new GeneratedKeyHolder(); - jdbc.update(conn -> { - PreparedStatement ps = conn - .prepareStatement( - "INSERT INTO CLICK VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - Statement.RETURN_GENERATED_KEYS); - ps.setNull(1, Types.BIGINT); - ps.setString(2, cl.getHash()); - ps.setDate(3, cl.getCreated()); - ps.setString(4, cl.getReferrer()); - ps.setString(5, cl.getBrowser()); - ps.setString(6, cl.getPlatform()); - ps.setString(7, cl.getIp()); - ps.setString(8, cl.getCountry()); - return ps; - }, holder); - if (holder.getKey() != null) { - new DirectFieldAccessor(cl).setPropertyValue("id", holder.getKey() - .longValue()); - } else { - log.debug("Key from database is null"); - } - } catch (DuplicateKeyException e) { - log.debug("When insert for click with id " + cl.getId(), e); - return cl; - } catch (Exception e) { - log.debug("When insert a click", e); - return null; - } - return cl; - } - - @Override - public void update(Click cl) { - log.info("ID2: {} navegador: {} SO: {} Date: {}", cl.getId(), cl.getBrowser(), cl.getPlatform(), cl.getCreated()); - try { - jdbc.update( - "update click set hash=?, created=?, referrer=?, browser=?, platform=?, ip=?, country=? where id=?", - cl.getHash(), cl.getCreated(), cl.getReferrer(), - cl.getBrowser(), cl.getPlatform(), cl.getIp(), - cl.getCountry(), cl.getId()); - - } catch (Exception e) { - log.info("When update for id " + cl.getId(), e); - } - } - - @Override - public void delete(Long id) { - try { - jdbc.update("delete from click where id=?", id); - } catch (Exception e) { - log.debug("When delete for id " + id, e); - } - } - - @Override - public void deleteAll() { - try { - jdbc.update("delete from click"); - } catch (Exception e) { - log.debug("When delete all", e); - } - } - - @Override - public Long count() { - try { - return jdbc - .queryForObject("select count(*) from click", Long.class); - } catch (Exception e) { - log.debug("When counting", e); - } - return -1L; - } - - @Override - public List list(Long limit, Long offset) { - try { - return jdbc.query("SELECT * FROM click LIMIT ? OFFSET ?", - new Object[] { limit, offset }, rowMapper); - } catch (Exception e) { - log.debug("When select for limit " + limit + " and offset " - + offset, e); - return Collections.emptyList(); - } - } - - @Override - public Long clicksByHash(String hash) { - try { - return jdbc - .queryForObject("select count(*) from click where hash = ?", new Object[]{hash}, Long.class); - } catch (Exception e) { - log.debug("When counting hash "+hash, e); - } - return -1L; - } - -} diff --git a/demo/src/main/java/urlshortener/demo/repository/IRepository.java b/demo/src/main/java/urlshortener/demo/repository/IRepository.java index a0c5eba2..e65eed7e 100644 --- a/demo/src/main/java/urlshortener/demo/repository/IRepository.java +++ b/demo/src/main/java/urlshortener/demo/repository/IRepository.java @@ -1,20 +1,20 @@ package urlshortener.demo.repository; import urlshortener.demo.domain.BaseEntity; -import urlshortener.demo.exception.CannotAddEntityException; -import urlshortener.demo.exception.UnknownEntityException; public interface IRepository> { - void add(V value) throws CannotAddEntityException; + void add(V value); - V get(K key) throws UnknownEntityException; + V get(K key); - void remove(K key) throws UnknownEntityException; + void remove(K key); void removeAll(); long getNextID(); boolean contains(K key); + + int getCount(); } diff --git a/demo/src/main/java/urlshortener/demo/repository/ShortURLRepository.java b/demo/src/main/java/urlshortener/demo/repository/ShortURLRepository.java deleted file mode 100644 index d06877e9..00000000 --- a/demo/src/main/java/urlshortener/demo/repository/ShortURLRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -package urlshortener.demo.repository; - -import java.util.List; - -import urlshortener.demo.domain.ShortURL; - -public interface ShortURLRepository { - - ShortURL findByKey(String id); - - List findByTarget(String target); - - ShortURL save(ShortURL su); - - ShortURL mark(ShortURL urlSafe, boolean safeness); - - void update(ShortURL su); - - void delete(String id); - - Long count(); - - List list(Long limit, Long offset); - -} diff --git a/demo/src/main/java/urlshortener/demo/repository/ShortURLRepositoryImpl.java b/demo/src/main/java/urlshortener/demo/repository/ShortURLRepositoryImpl.java deleted file mode 100644 index 3fe315a5..00000000 --- a/demo/src/main/java/urlshortener/demo/repository/ShortURLRepositoryImpl.java +++ /dev/null @@ -1,131 +0,0 @@ -package urlshortener.demo.repository; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.dao.DuplicateKeyException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Repository; -import urlshortener.demo.domain.ShortURL; - -import java.util.Collections; -import java.util.List; - -@Repository -public class ShortURLRepositoryImpl implements ShortURLRepository { - - private static final Logger log = LoggerFactory - .getLogger(ShortURLRepositoryImpl.class); - - private static final RowMapper rowMapper = (rs, rowNum) -> new ShortURL() - .hash(rs.getString("hash")).target(rs.getString("target")) - .sponsor(rs.getString("sponsor")).created(rs.getDate("created")) - .owner(rs.getString("owner")).mode(rs.getInt("mode")) - .safe(rs.getBoolean("safe")).ip(rs.getString("ip")) - .country(rs.getString("country")); - private JdbcTemplate jdbc; - - public ShortURLRepositoryImpl(JdbcTemplate jdbc) { - this.jdbc = jdbc; - } - - @Override - public ShortURL findByKey(String id) { - try { - return jdbc.queryForObject("SELECT * FROM shorturl WHERE hash=?", - rowMapper, id); - } catch (Exception e) { - log.debug("When select for key {}", id, e); - return null; - } - } - - @Override - public ShortURL save(ShortURL su) { - try { - jdbc.update("INSERT INTO shorturl VALUES (?,?,?,?,?,?,?,?,?)", - su.getHash(), su.getTarget(), su.getSponsor(), - su.getCreated(), su.getOwner(), su.getMode(), su.getSafe(), - su.getIP(), su.getCountry()); - } catch (DuplicateKeyException e) { - log.debug("When insert for key {}", su.getHash(), e); - return su; - } catch (Exception e) { - log.debug("When insert", e); - return null; - } - return su; - } - - @Override - public ShortURL mark(ShortURL su, boolean safeness) { - try { - jdbc.update("UPDATE shorturl SET safe=? WHERE hash=?", safeness, - su.getHash()); - ShortURL res = new ShortURL(); - BeanUtils.copyProperties(su, res); - new DirectFieldAccessor(res).setPropertyValue("safe", safeness); - return res; - } catch (Exception e) { - log.debug("When update", e); - return null; - } - } - - @Override - public void update(ShortURL su) { - try { - jdbc.update( - "update shorturl set target=?, sponsor=?, created=?, owner=?, mode=?, safe=?, ip=?, country=? where hash=?", - su.getTarget(), su.getSponsor(), su.getCreated(), - su.getOwner(), su.getMode(), su.getSafe(), su.getIP(), - su.getCountry(), su.getHash()); - } catch (Exception e) { - log.debug("When update for hash {}", su.getHash(), e); - } - } - - @Override - public void delete(String hash) { - try { - jdbc.update("delete from shorturl where hash=?", hash); - } catch (Exception e) { - log.debug("When delete for hash {}", hash, e); - } - } - - @Override - public Long count() { - try { - return jdbc.queryForObject("select count(*) from shorturl", - Long.class); - } catch (Exception e) { - log.debug("When counting", e); - } - return -1L; - } - - @Override - public List list(Long limit, Long offset) { - try { - return jdbc.query("SELECT * FROM shorturl LIMIT ? OFFSET ?", - new Object[] { limit, offset }, rowMapper); - } catch (Exception e) { - log.debug("When select for limit {} and offset {}", limit, offset, e); - return Collections.emptyList(); - } - } - - @Override - public List findByTarget(String target) { - try { - return jdbc.query("SELECT * FROM shorturl WHERE target = ?", - new Object[] { target }, rowMapper); - } catch (Exception e) { - log.debug("When select for target " + target , e); - return Collections.emptyList(); - } - } -} diff --git a/demo/src/main/java/urlshortener/demo/repository/StatsRepository.java b/demo/src/main/java/urlshortener/demo/repository/StatsRepository.java new file mode 100644 index 00000000..056099e7 --- /dev/null +++ b/demo/src/main/java/urlshortener/demo/repository/StatsRepository.java @@ -0,0 +1,14 @@ +package urlshortener.demo.repository; + +import urlshortener.demo.domain.Stats; +import urlshortener.demo.domain.URIStats; + +public interface StatsRepository { + + Stats getStats(); + + URIStats getUriStats(String keyID); + + void incrementAccessStats(String id); + +} diff --git a/demo/src/main/java/urlshortener/demo/repository/URIRepository.java b/demo/src/main/java/urlshortener/demo/repository/URIRepository.java index 8304d201..2896b413 100644 --- a/demo/src/main/java/urlshortener/demo/repository/URIRepository.java +++ b/demo/src/main/java/urlshortener/demo/repository/URIRepository.java @@ -2,6 +2,25 @@ import urlshortener.demo.domain.URIItem; +import java.util.Date; +import java.util.Map; + public interface URIRepository extends IRepository { + Map getAllURIS(); + + Map getAllFechas(); + + void removeFecha(String id); + + void addFecha(String id, Date fecha); + + long getRedirectionAmount(String hash, long timeFromNow); + + void add(URIItem uri); + + URIItem get(String hash); + + void remove(String hash); + void removeAll(); } diff --git a/demo/src/main/java/urlshortener/demo/repository/impl/QRRepositoryImpl.java b/demo/src/main/java/urlshortener/demo/repository/impl/QRRepositoryImpl.java index 71e6d098..058b54f9 100644 --- a/demo/src/main/java/urlshortener/demo/repository/impl/QRRepositoryImpl.java +++ b/demo/src/main/java/urlshortener/demo/repository/impl/QRRepositoryImpl.java @@ -1,10 +1,8 @@ package urlshortener.demo.repository.impl; -import org.springframework.stereotype.Repository; import urlshortener.demo.domain.QRItem; import urlshortener.demo.repository.AbstractRepository; import urlshortener.demo.repository.QRRepository; -@Repository public class QRRepositoryImpl extends AbstractRepository implements QRRepository { } diff --git a/demo/src/main/java/urlshortener/demo/repository/impl/StatsRepositoryHook.java b/demo/src/main/java/urlshortener/demo/repository/impl/StatsRepositoryHook.java new file mode 100644 index 00000000..8adfca76 --- /dev/null +++ b/demo/src/main/java/urlshortener/demo/repository/impl/StatsRepositoryHook.java @@ -0,0 +1,60 @@ +package urlshortener.demo.repository.impl; + +import io.micrometer.core.instrument.MeterRegistry; +import urlshortener.demo.domain.BaseEntity; +import urlshortener.demo.repository.IRepository; + +public class StatsRepositoryHook> implements IRepository { + + private final IRepository repo; + private final MeterRegistry metrics; + private final String key; + + public StatsRepositoryHook(IRepository repo, MeterRegistry metrics, String key) { + this.repo = repo; + this.metrics = metrics; + this.key = key; + } + + + @Override + public void add(V value) { + if(repo != null) repo.add(value); + metrics.counter(key + ".created").increment(); + metrics.counter(key + ".now").increment(); + } + + @Override + public V get(K key) { + return null; + } + + @Override + public void remove(K key) { + if(repo != null) repo.remove(key); + metrics.counter(this.key + ".removed").increment(); + metrics.counter(this.key + ".now").increment(-1.0); + } + + @Override + public void removeAll() { + metrics.counter(key + ".removed").increment(getCount()); + metrics.counter(key + ".now").increment(getCount()); + if(repo != null) repo.removeAll(); + } + + @Override + public long getNextID() { + return repo != null ? repo.getNextID() : -1; + } + + @Override + public boolean contains(K key) { + return repo != null && repo.contains(key); + } + + @Override + public int getCount() { + return repo != null ? repo.getCount() : 0; + } +} diff --git a/demo/src/main/java/urlshortener/demo/repository/impl/StatsRepositoryImpl.java b/demo/src/main/java/urlshortener/demo/repository/impl/StatsRepositoryImpl.java new file mode 100644 index 00000000..a5c0db36 --- /dev/null +++ b/demo/src/main/java/urlshortener/demo/repository/impl/StatsRepositoryImpl.java @@ -0,0 +1,74 @@ +package urlshortener.demo.repository.impl; + +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.metrics.MetricsEndpoint; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Repository; +import urlshortener.demo.domain.Stats; +import urlshortener.demo.domain.URIItem; +import urlshortener.demo.domain.URIStats; +import urlshortener.demo.exception.UnknownEntityException; +import urlshortener.demo.repository.StatsRepository; +import urlshortener.demo.repository.URIRepository; + +import java.math.BigDecimal; +import java.util.Collections; + +@Repository +public class StatsRepositoryImpl implements StatsRepository { + + private final MeterRegistry metrics; + + private final MetricsEndpoint metricsEndpoint; + + private final URIRepository repository; + + @Autowired + public StatsRepositoryImpl(MeterRegistry metrics, MetricsEndpoint metricsEndpoint, URIRepository repository) { + this.metrics = metrics; + this.metricsEndpoint = metricsEndpoint; + this.repository = repository; + } + + public Stats getStats() { + Stats stats = new Stats(); + stats.setCpuUsage(BigDecimal.valueOf(getMetric("process.cpu.usage"))); + stats.setMemoryUsage((int) getMetric("jvm.memory.used")); + stats.setServerUsage(BigDecimal.valueOf(getMetric("system.cpu.usage"))); + + stats.setUrisCreated(BigDecimal.valueOf(getCustomMetric("uri.created"))); + stats.setUrisRemoved(BigDecimal.valueOf(getCustomMetric("uri.removed"))); + stats.setUrisNow(BigDecimal.valueOf(getCustomMetric("uri.now"))); + stats.setUrisAccessed(BigDecimal.valueOf(getCustomMetric("uri.accessed"))); + stats.setQrCreated(BigDecimal.valueOf(getCustomMetric("qr.created"))); + stats.setQrRemoved(BigDecimal.valueOf(getCustomMetric("qr.removed"))); + stats.setQrNow(BigDecimal.valueOf(getCustomMetric("qr.now"))); + stats.setQrAccessed(BigDecimal.valueOf(getCustomMetric("qr.accessed"))); + return stats; + } + + public URIStats getUriStats(String keyID) { + URIItem uri = repository.get(keyID); + if(uri == null) throw new UnknownEntityException(HttpStatus.NOT_FOUND.value(), "Cannot find stats for an unknown URI."); + URIStats stats = new URIStats(); + + stats.setUriAccesses(BigDecimal.valueOf(getCustomMetric("uri."+keyID+".accessed"))); + return stats; + } + + @Override + public void incrementAccessStats(String id) { + metrics.counter("uri." + id + ".accessed").increment(); + metrics.counter("uri.accessed").increment(); + } + + private double getMetric(String s) { + return metricsEndpoint.metric(s, Collections.emptyList()).getMeasurements().stream().mapToDouble(MetricsEndpoint.Sample::getValue).average().orElse(0.0); + } + + private double getCustomMetric(String s){ + return metrics.counter(s).count(); + } + +} diff --git a/demo/src/main/java/urlshortener/demo/repository/impl/URIRepositoryImpl.java b/demo/src/main/java/urlshortener/demo/repository/impl/URIRepositoryImpl.java index e41444e4..c8a3d85b 100644 --- a/demo/src/main/java/urlshortener/demo/repository/impl/URIRepositoryImpl.java +++ b/demo/src/main/java/urlshortener/demo/repository/impl/URIRepositoryImpl.java @@ -1,10 +1,77 @@ package urlshortener.demo.repository.impl; -import org.springframework.stereotype.Repository; +import org.springframework.http.HttpStatus; import urlshortener.demo.domain.URIItem; +import urlshortener.demo.exception.UnknownEntityException; import urlshortener.demo.repository.AbstractRepository; +import urlshortener.demo.repository.IRepository; import urlshortener.demo.repository.URIRepository; -@Repository +import java.util.*; + public class URIRepositoryImpl extends AbstractRepository implements URIRepository { + + private final IRepository repo; + private Map stats = new HashMap<>(); + + public URIRepositoryImpl(IRepository repo) { + this.repo = repo; + } + + @Override + public Map getAllURIS() { + return super.getAllUris(); + } + + @Override + public long getRedirectionAmount(String hash, long timeFromNow) { + if(repo != null) repo.get(hash); + URIData statsData = this.stats.get(hash); + if(statsData == null) throw new UnknownEntityException(HttpStatus.BAD_REQUEST.value(), "Unknown URI " + hash); + + return statsData.getAccesssesAfter(System.currentTimeMillis() - timeFromNow); + } + + @Override + public void add(URIItem uri) { + if(repo != null) repo.add(uri); + stats.putIfAbsent(uri.getId(), new URIData()); + super.add(uri); + } + + @Override + public URIItem get(String hash) { + if(repo != null) repo.get(hash); + stats.putIfAbsent(hash, new URIData()); + stats.get(hash).addAccess(); + + return super.get(hash); + } + + @Override + public void remove(String hash) { + if(repo != null) repo.remove(hash); + stats.remove(hash); + super.remove(hash); + } + + @Override + public void removeAll() { + if(repo != null) repo.removeAll(); + stats.clear(); + super.removeAll(); + } + + //THIS ARE NOT STATS, ONLY URI DATA USED TO BLOCK DDoS + private static class URIData { + private List lastAccesses = new ArrayList<>(); + + private void addAccess(){ + this.lastAccesses.add(System.currentTimeMillis()); + } + + private long getAccesssesAfter(long time){ + return lastAccesses.stream().filter(t -> t > time).count(); + } + } } diff --git a/demo/src/main/java/urlshortener/demo/schedule/checkSchedule.java b/demo/src/main/java/urlshortener/demo/schedule/checkSchedule.java new file mode 100644 index 00000000..be6d5775 --- /dev/null +++ b/demo/src/main/java/urlshortener/demo/schedule/checkSchedule.java @@ -0,0 +1,66 @@ +package urlshortener.demo.schedule; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import urlshortener.demo.domain.URIItem; +import urlshortener.demo.repository.URIRepository; +import urlshortener.demo.utils.CheckAlive; + +import java.io.IOException; +import java.util.Collection; +import java.util.Date; +import java.util.Map; + +@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2018-11-21T05:15:43.072Z[GMT]") + +@Component +public class checkSchedule { + private final int CHECK_RATE = 86400000; + + private static final Logger log = LoggerFactory.getLogger(checkSchedule.class); + + private final URIRepository uriService; + + @org.springframework.beans.factory.annotation.Autowired + public checkSchedule(URIRepository uriService) { + this.uriService = uriService; + } + + @Scheduled(fixedRate = CHECK_RATE) + public void check() throws IOException, InterruptedException { + CheckAlive c = new CheckAlive(); + + Map uris = uriService.getAllURIS(); + Collection soloUris = uris.values(); + Map fechas = uriService.getAllFechas(); + + log.info("Comienza el checkeo de las URI..."); + for (URIItem i: soloUris){ + log.info("COMPROBANDO URI " + i.getRedirection()); + if (c.makeRequest(i.getRedirection()) == 200){ + log.info("La URI " + i.getRedirection() + " está viva."); + //OK + //Para esa URI, se registra la fecha actual como útima fecha en la que estuvo viva + uriService.removeFecha(i.getId()); + uriService.addFecha(i.getId(), new Date()); + } + else { + log.info("La URI " + i.getRedirection() + " no responde."); + //Cualquier otra cosa aparte de un código 200 significará que la URI está muerta + //Se obtiene la última vez que la URI estuvo viva + // -Si la diferencia entre la fecha actual y la fecha recuperada es >= K, entonces la URI se borra + long actual = System.currentTimeMillis(); + long fechaUri = fechas.get(i.getId()).getTime(); + long diff = actual - fechaUri; + long days = diff / (604800000); + if (days >= 7.0){ + uriService.remove(i.getId()); + } + } + } + } + +} + diff --git a/demo/src/main/java/urlshortener/demo/tasks/LiveStatsTasks.java b/demo/src/main/java/urlshortener/demo/tasks/LiveStatsTasks.java new file mode 100644 index 00000000..98e62c6a --- /dev/null +++ b/demo/src/main/java/urlshortener/demo/tasks/LiveStatsTasks.java @@ -0,0 +1,27 @@ +package urlshortener.demo.tasks; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import urlshortener.demo.repository.StatsRepository; + +@Component +public class LiveStatsTasks { + + private final SimpMessagingTemplate messager; + + private final StatsRepository statsApi; + + @Autowired + public LiveStatsTasks(SimpMessagingTemplate messager, StatsRepository statsApi) { + this.messager = messager; + this.statsApi = statsApi; + } + + @Scheduled(fixedRate = 1000) + public void runTask(){ + messager.convertAndSend("/stats/live", statsApi.getStats()); + } + +} diff --git a/demo/src/main/java/urlshortener/demo/utils/CheckAlive.java b/demo/src/main/java/urlshortener/demo/utils/CheckAlive.java new file mode 100644 index 00000000..fe468095 --- /dev/null +++ b/demo/src/main/java/urlshortener/demo/utils/CheckAlive.java @@ -0,0 +1,16 @@ +package urlshortener.demo.utils; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +public class CheckAlive { + public int makeRequest(String s) throws IOException { + URL url = new URL(s); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + + return con.getResponseCode(); + } + +} \ No newline at end of file diff --git a/demo/src/main/java/urlshortener/demo/utils/StringChecker.java b/demo/src/main/java/urlshortener/demo/utils/StringChecker.java index 4aeae62b..1237f858 100644 --- a/demo/src/main/java/urlshortener/demo/utils/StringChecker.java +++ b/demo/src/main/java/urlshortener/demo/utils/StringChecker.java @@ -2,16 +2,16 @@ public class StringChecker { - private StringChecker() { - throw new IllegalStateException("Utility class"); - } - public static int checkString2Int(String string){ - + if (string == null){ return 500; } + if (string == ""){ + return 500; + } + int num = Integer.parseInt(string); if(num < 30){ @@ -25,4 +25,7 @@ public static int checkString2Int(String string){ return num; } + private StringChecker() { + //Let's hide the implicit public constructor as this class should never be instantiated. + } } \ No newline at end of file diff --git a/demo/src/main/java/urlshortener/demo/web/UrlShortenerController.java b/demo/src/main/java/urlshortener/demo/web/UrlShortenerController.java deleted file mode 100755 index d64ced63..00000000 --- a/demo/src/main/java/urlshortener/demo/web/UrlShortenerController.java +++ /dev/null @@ -1,101 +0,0 @@ -package urlshortener.demo.web; - -import com.google.common.hash.Hashing; -import org.apache.commons.validator.routines.UrlValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import urlshortener.demo.domain.Click; -import urlshortener.demo.domain.ShortURL; -import urlshortener.demo.repository.ClickRepository; -import urlshortener.demo.repository.ShortURLRepository; - -import javax.servlet.http.HttpServletRequest; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.sql.Date; -import java.util.UUID; - -import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; -import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; - -@RestController -public class UrlShortenerController { - private static final Logger LOG = LoggerFactory - .getLogger(UrlShortenerController.class); - @Autowired - protected ShortURLRepository shortURLRepository; - - @Autowired - protected ClickRepository clickRepository; - - @RequestMapping(value = "/{id:(?!link|index).*}", method = RequestMethod.GET) - public ResponseEntity redirectTo(@PathVariable String id, - HttpServletRequest request) { - ShortURL l = shortURLRepository.findByKey(id); - if (l != null) { - createAndSaveClick(id, extractIP(request)); - return createSuccessfulRedirectToResponse(l); - } else { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } - } - - private void createAndSaveClick(String hash, String ip) { - Click cl = new Click().hash(hash).created(new Date(System.currentTimeMillis())).ip(ip); - cl=clickRepository.save(cl); - LOG.info(cl!=null?"["+hash+"] saved with id ["+cl.getId()+"]":"["+hash+"] was not saved"); - } - - private String extractIP(HttpServletRequest request) { - return request.getRemoteAddr(); - } - - private ResponseEntity createSuccessfulRedirectToResponse(ShortURL l) { - HttpHeaders h = new HttpHeaders(); - h.setLocation(URI.create(l.getTarget())); - return new ResponseEntity<>(h, HttpStatus.valueOf(l.getMode())); - } - - @RequestMapping(value = "/link", method = RequestMethod.POST) - public ResponseEntity shortener(@RequestParam("url") String url, - @RequestParam(value = "sponsor", required = false) String sponsor, - HttpServletRequest request) { - ShortURL su = createAndSaveIfValid(url, sponsor, UUID - .randomUUID().toString(), extractIP(request)); - if (su != null) { - HttpHeaders h = new HttpHeaders(); - h.setLocation(su.getUri()); - return new ResponseEntity<>(su, h, HttpStatus.CREATED); - } else { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - } - } - - private ShortURL createAndSaveIfValid(String url, String sponsor, - String owner, String ip) { - UrlValidator urlValidator = new UrlValidator(new String[] { "http", - "https" }); - if (urlValidator.isValid(url)) { - String id = Hashing.murmur3_32() - .hashString(url, StandardCharsets.UTF_8).toString(); - ShortURL su = new ShortURL().hash(id).target(url) - .uri(linkTo( - methodOn(UrlShortenerController.class).redirectTo( - id, null)).toUri()) - .sponsor(sponsor) - .created(new Date(System.currentTimeMillis())) - .owner(owner) - .mode(HttpStatus.TEMPORARY_REDIRECT.value()) - .safe(true) - .ip(ip); - return shortURLRepository.save(su); - } else { - return null; - } - } -} diff --git a/demo/src/main/resources/application.properties b/demo/src/main/resources/application.properties index ca4b8b41..9e76e560 100755 --- a/demo/src/main/resources/application.properties +++ b/demo/src/main/resources/application.properties @@ -1,6 +1,4 @@ -# Spring Datasource JDBC -spring.datasource.platform=hsqldb -spring.datasource.url=jdbc:hsqldb:mem:. -spring.datasource.username=sa -spring.datasource.password= -spring.datasource.driverClassName=org.hsqldb.jdbcDriver +server.error.whitelabel.enabled=false +management.endpoints.web.exposure.include=* +logging.level.org.springframework.web=INFO +logging.level.org.hibernate=ERROR \ No newline at end of file diff --git a/demo/src/main/resources/public/error/404.html b/demo/src/main/resources/public/error/404.html new file mode 100644 index 00000000..57388072 --- /dev/null +++ b/demo/src/main/resources/public/error/404.html @@ -0,0 +1,19 @@ + + + + TinyURI 404 Error + + + + +

NOT FOUND

+

You shouldn't be here

+Return to TinyURI + + + \ No newline at end of file diff --git a/demo/src/main/resources/public/error/429.html b/demo/src/main/resources/public/error/429.html new file mode 100644 index 00000000..8de9392f --- /dev/null +++ b/demo/src/main/resources/public/error/429.html @@ -0,0 +1,19 @@ + + + + TinyURI 429 Error + + + + +

TOO MANY REQUEST

+

Relax and try again later

+Return to TinyURI + + + \ No newline at end of file diff --git a/demo/src/main/resources/public/error/500.html b/demo/src/main/resources/public/error/500.html new file mode 100644 index 00000000..ea162d8a --- /dev/null +++ b/demo/src/main/resources/public/error/500.html @@ -0,0 +1,19 @@ + + + + TinyURI 500 Error + + + + +

SERVER ERROR

+

Ops! This is our fault

+Return to TinyURI + + + \ No newline at end of file diff --git a/demo/src/main/resources/static/css/landing-page.css b/demo/src/main/resources/static/css/landing-page.css index 4a122296..35a29256 100644 --- a/demo/src/main/resources/static/css/landing-page.css +++ b/demo/src/main/resources/static/css/landing-page.css @@ -4,8 +4,14 @@ * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap-landing-page/blob/master/LICENSE) */ -body { +html,body +{ font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif; + width: 100%; + height: 100%; + margin: 0px; + padding: 0px; + overflow-x: hidden; } h1, @@ -18,10 +24,12 @@ h6 { font-weight: 700; } + + header.masthead { position: relative; - background-color: #343a40; - background: url("../img/bg-masthead.jpg") no-repeat center center; + background-color: #294028; + /*background: url("../img/bg-masthead.jpg") no-repeat center center;*/ background-size: cover; padding-top: 8rem; padding-bottom: 8rem; @@ -104,8 +112,7 @@ header.masthead h1 { .call-to-action { position: relative; - background-color: #343a40; - background: url("../img/bg-masthead.jpg") no-repeat center center; + background-color: #3f4024; background-size: cover; padding-top: 7rem; padding-bottom: 7rem; @@ -121,7 +128,100 @@ header.masthead h1 { opacity: 0.3; } +.qrViewer { + position: relative; + background-color: #263940; + background-size: cover; + padding-top: 7rem; + padding-bottom: 7rem; +} + +.qrViewer .overlay { + position: absolute; + background-color: #212529; + height: 100%; + width: 100%; + top: 0; + left: 0; + opacity: 0.3; +} + +.delete-uri { + position: relative; + background-color: #402722; + background-size: cover; + padding-top: 7rem; + padding-bottom: 7rem; +} + +.delete-uri .overlay { + position: absolute; + background-color: #212529; + height: 100%; + width: 100%; + top: 0; + left: 0; + opacity: 0.3; +} + +.error { + position: absolute; + height: 100%; + width: 100%; + background-color: #712c2b; + background-size: cover; + padding-top: 7rem; + padding-bottom: 7rem; +} + +.error .overlay { + position: absolute; + background-color: #212529; + height: 100%; + width: 100%; + top: 0; + left: 0; + opacity: 0.3; +} + + footer.footer { - padding-top: 4rem; - padding-bottom: 4rem; + padding-top: 1rem; + padding-bottom: 1rem; +} + +.stats { + position: relative; + background-color: #366344; + background-size: cover; + padding-top: 7rem; + padding-bottom: 0rem; +} + +.stats .overlay { + position: absolute; + background-color: #212529; + height: 100%; + width: 100%; + top: 0; + left: 0; + opacity: 0.3; +} + +.check-uri { + position: relative; + background-color: #454645; + background-size: cover; + padding-top: 7rem; + padding-bottom: 7rem; +} + +.check-uri .overlay { + position: absolute; + background-color: #212529; + height: 100%; + width: 100%; + top: 0; + left: 0; + opacity: 0.3; } diff --git a/demo/src/main/resources/static/css/landing-page.min.css b/demo/src/main/resources/static/css/landing-page.min.css index be98144a..ad36fd11 100644 --- a/demo/src/main/resources/static/css/landing-page.min.css +++ b/demo/src/main/resources/static/css/landing-page.min.css @@ -2,4 +2,4 @@ * Start Bootstrap - Landing Page v5.0.0 (https://startbootstrap.com/template-overviews/landing-page) * Copyright 2013-2018 Start Bootstrap * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap-landing-page/blob/master/LICENSE) - */body{font-family:Lato,'Helvetica Neue',Helvetica,Arial,sans-serif}h1,h2,h3,h4,h5,h6{font-family:Lato,'Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:700}header.masthead{position:relative;background-color:#343a40;background:url(../img/bg-masthead.jpg) no-repeat center center;background-size:cover;padding-top:8rem;padding-bottom:8rem}header.masthead .overlay{position:absolute;background-color:#212529;height:100%;width:100%;top:0;left:0;opacity:.3}header.masthead h1{font-size:2rem}@media (min-width:768px){header.masthead{padding-top:12rem;padding-bottom:12rem}header.masthead h1{font-size:3rem}}.showcase .showcase-text{padding:3rem}.showcase .showcase-img{min-height:30rem;background-size:cover}@media (min-width:768px){.showcase .showcase-text{padding:7rem}}.features-icons{padding-top:7rem;padding-bottom:7rem}.features-icons .features-icons-item{max-width:20rem}.features-icons .features-icons-item .features-icons-icon{height:7rem}.features-icons .features-icons-item .features-icons-icon i{font-size:4.5rem}.features-icons .features-icons-item:hover .features-icons-icon i{font-size:5rem}.testimonials{padding-top:7rem;padding-bottom:7rem}.testimonials .testimonial-item{max-width:18rem}.testimonials .testimonial-item img{max-width:12rem;-webkit-box-shadow:0 5px 5px 0 #adb5bd;box-shadow:0 5px 5px 0 #adb5bd}.call-to-action{position:relative;background-color:#343a40;background:url(../img/bg-masthead.jpg) no-repeat center center;background-size:cover;padding-top:7rem;padding-bottom:7rem}.call-to-action .overlay{position:absolute;background-color:#212529;height:100%;width:100%;top:0;left:0;opacity:.3}footer.footer{padding-top:4rem;padding-bottom:4rem} \ No newline at end of file + */body{font-family:Lato,'Helvetica Neue',Helvetica,Arial,sans-serif}h1,h2,h3,h4,h5,h6{font-family:Lato,'Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:700}header.masthead{position:relative;background-color:#343a40;background:url(../img/bg-masthead.jpg) no-repeat center center;background-size:cover;padding-top:8rem;padding-bottom:8rem}header.masthead .overlay{position:absolute;background-color:#212529;height:100%;width:100%;top:0;left:0;opacity:.3}header.masthead h1{font-size:2rem}@media (min-width:768px){header.masthead{padding-top:12rem;padding-bottom:12rem}header.masthead h1{font-size:3rem}}.showcase .showcase-text{padding:3rem}.showcase .showcase-img{min-height:30rem;background-size:cover}@media (min-width:768px){.showcase .showcase-text{padding:7rem}}.features-icons{padding-top:7rem;padding-bottom:7rem}.features-icons .features-icons-item{max-width:20rem}.features-icons .features-icons-item .features-icons-icon{height:7rem}.features-icons .features-icons-item .features-icons-icon i{font-size:4.5rem}.features-icons .features-icons-item:hover .features-icons-icon i{font-size:5rem}.testimonials{padding-top:7rem;padding-bottom:7rem}.testimonials .testimonial-item{max-width:18rem}.testimonials .testimonial-item img{max-width:12rem;-webkit-box-shadow:0 5px 5px 0 #adb5bd;box-shadow:0 5px 5px 0 #adb5bd}.call-to-action{position:relative;background-color:#343a40;background-size:cover;padding-top:7rem;padding-bottom:7rem}.call-to-action .overlay{position:absolute;background-color:#212529;height:100%;width:100%;top:0;left:0;opacity:.3}footer.footer{padding-top:4rem;padding-bottom:4rem} \ No newline at end of file diff --git a/demo/src/main/resources/static/index.html b/demo/src/main/resources/static/index.html index 20640275..58116ac2 100755 --- a/demo/src/main/resources/static/index.html +++ b/demo/src/main/resources/static/index.html @@ -8,7 +8,7 @@ - URL Shortener + TinyURI @@ -19,215 +19,211 @@ - + + + - + -