diff --git a/microservices-modules/pom.xml b/microservices-modules/pom.xml index 4c7079f3cd..a9cd8d2cd9 100644 --- a/microservices-modules/pom.xml +++ b/microservices-modules/pom.xml @@ -19,6 +19,7 @@ microprofile msf4j open-liberty + rest-express \ No newline at end of file diff --git a/microservices-modules/rest-express/README.md b/microservices-modules/rest-express/README.md new file mode 100644 index 0000000000..0fdff99fb7 --- /dev/null +++ b/microservices-modules/rest-express/README.md @@ -0,0 +1,5 @@ +## RestExpress + +This module contains articles about RestExpress. + +### Relevant articles \ No newline at end of file diff --git a/microservices-modules/rest-express/pom.xml b/microservices-modules/rest-express/pom.xml new file mode 100644 index 0000000000..f222998340 --- /dev/null +++ b/microservices-modules/rest-express/pom.xml @@ -0,0 +1,153 @@ + + + + microservices-modules + com.baeldung + 1.0.0-SNAPSHOT + + 4.0.0 + + rest-express + + A Basic, MongoDB-backed Service Suite + https://github.com/RestExpress/RestExpress-Scaffold + 1.0.0-SNAPSHOT + rest-express + jar + + + 0.3.3 + 3.1.2 + 2.6 + 0.11.3 + 1.0 + 0.4.8 + 4.11 + + + + + com.strategicgains + RestExpress + ${RestExpress.version} + + + com.strategicgains + Syntaxe + ${Syntaxe.version} + + + com.strategicgains.repoexpress + repoexpress-mongodb + ${repoexpress-mongodb.version} + + + com.strategicgains.plugin-express + CacheControlPlugin + ${RestExpress.plugin.version} + + + com.strategicgains + HyperExpressPlugin + ${HyperExpressPlugin.version} + + + com.strategicgains.plugin-express + MetricsPlugin + ${RestExpress.plugin.version} + + + com.strategicgains.plugin-express + SwaggerPlugin + ${RestExpress.plugin.version} + + + com.strategicgains.plugin-express + CORSPlugin + ${RestExpress.plugin.version} + + + io.dropwizard.metrics + metrics-graphite + ${metrics-graphite.version} + + + junit + junit + ${junit4.version} + jar + test + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.0 + + 1.8 + 1.8 + UTF-8 + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + com.baeldung.restexpress.Main + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.1 + + false + + + *:* + + + + + + package + + shade + + + + + com.baeldung.restexpress.Main + + + + + + + + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.0 + + + + diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Configuration.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Configuration.java new file mode 100644 index 0000000000..22161a265b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Configuration.java @@ -0,0 +1,75 @@ +package com.baeldung.restexpress; + +import com.baeldung.restexpress.objectid.SampleOidEntityController; +import com.baeldung.restexpress.objectid.SampleOidEntityRepository; +import com.baeldung.restexpress.objectid.SampleOidEntityService; +import com.baeldung.restexpress.uuid.SampleUuidEntityController; +import com.baeldung.restexpress.uuid.SampleUuidEntityRepository; +import com.baeldung.restexpress.uuid.SampleUuidEntityService; +import com.strategicgains.repoexpress.mongodb.MongoConfig; +import com.strategicgains.restexpress.plugin.metrics.MetricsConfig; +import org.restexpress.RestExpress; +import org.restexpress.util.Environment; + +import java.util.Properties; + +public class Configuration + extends Environment { + private static final String DEFAULT_EXECUTOR_THREAD_POOL_SIZE = "20"; + + private static final String PORT_PROPERTY = "port"; + private static final String BASE_URL_PROPERTY = "base.url"; + private static final String EXECUTOR_THREAD_POOL_SIZE = "executor.threadPool.size"; + + private int port; + private String baseUrl; + private int executorThreadPoolSize; + private MetricsConfig metricsSettings; + + private SampleUuidEntityController sampleUuidController; + private SampleOidEntityController sampleOidController; + + @Override + protected void fillValues(Properties p) { + this.port = Integer.parseInt(p.getProperty(PORT_PROPERTY, String.valueOf(RestExpress.DEFAULT_PORT))); + this.baseUrl = p.getProperty(BASE_URL_PROPERTY, "http://localhost:" + String.valueOf(port)); + this.executorThreadPoolSize = Integer.parseInt(p.getProperty(EXECUTOR_THREAD_POOL_SIZE, DEFAULT_EXECUTOR_THREAD_POOL_SIZE)); + this.metricsSettings = new MetricsConfig(p); + MongoConfig mongo = new MongoConfig(p); + initialize(mongo); + } + + private void initialize(MongoConfig mongo) { + SampleUuidEntityRepository samplesUuidRepository = new SampleUuidEntityRepository(mongo.getClient(), mongo.getDbName()); + SampleUuidEntityService sampleUuidService = new SampleUuidEntityService(samplesUuidRepository); + sampleUuidController = new SampleUuidEntityController(sampleUuidService); + + SampleOidEntityRepository samplesOidRepository = new SampleOidEntityRepository(mongo.getClient(), mongo.getDbName()); + SampleOidEntityService sampleOidService = new SampleOidEntityService(samplesOidRepository); + sampleOidController = new SampleOidEntityController(sampleOidService); + } + + public int getPort() { + return port; + } + + public String getBaseUrl() { + return baseUrl; + } + + public int getExecutorThreadPoolSize() { + return executorThreadPoolSize; + } + + public MetricsConfig getMetricsConfig() { + return metricsSettings; + } + + public SampleUuidEntityController getSampleUuidEntityController() { + return sampleUuidController; + } + + public SampleOidEntityController getSampleOidEntityController() { + return sampleOidController; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Constants.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Constants.java new file mode 100644 index 0000000000..914c0a0a07 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Constants.java @@ -0,0 +1,23 @@ +package com.baeldung.restexpress; + +public class Constants { + /** + * These define the URL parmaeters used in the route definition strings (e.g. '{userId}'). + */ + public class Url { + //TODO: Your URL parameter names here... + public static final String SAMPLE_ID = "uuid"; + } + + /** + * These define the route names used in naming each route definitions. These names are used + * to retrieve URL patterns within the controllers by name to create links in responses. + */ + public class Routes { + //TODO: Your Route names here... + public static final String SINGLE_UUID_SAMPLE = "sample.single.route.uuid"; + public static final String SAMPLE_UUID_COLLECTION = "sample.collection.route.uuid"; + public static final String SINGLE_OID_SAMPLE = "sample.single.route.oid"; + public static final String SAMPLE_OID_COLLECTION = "sample.collection.route.oid"; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/LastModifiedHeaderPostprocessor.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/LastModifiedHeaderPostprocessor.java new file mode 100644 index 0000000000..81314679a4 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/LastModifiedHeaderPostprocessor.java @@ -0,0 +1,33 @@ +package com.baeldung.restexpress; + +import com.strategicgains.repoexpress.domain.Timestamped; +import com.strategicgains.util.date.DateAdapter; +import com.strategicgains.util.date.HttpHeaderTimestampAdapter; +import org.restexpress.Request; +import org.restexpress.Response; +import org.restexpress.pipeline.Postprocessor; + +import static io.netty.handler.codec.http.HttpHeaders.Names.LAST_MODIFIED; + +/** + * Assigns the Last-Modified HTTP header on the response for GET responses, if applicable. + * + * @author toddf + * @since May 15, 2012 + */ +public class LastModifiedHeaderPostprocessor + implements Postprocessor { + DateAdapter fmt = new HttpHeaderTimestampAdapter(); + + @Override + public void process(Request request, Response response) { + if (!request.isMethodGet()) return; + if (!response.hasBody()) return; + + Object body = response.getBody(); + + if (!response.hasHeader(LAST_MODIFIED) && body.getClass().isAssignableFrom(Timestamped.class)) { + response.addHeader(LAST_MODIFIED, fmt.format(((Timestamped) body).getUpdatedAt())); + } + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Main.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Main.java new file mode 100644 index 0000000000..1c02402d89 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Main.java @@ -0,0 +1,11 @@ +package com.baeldung.restexpress; + +import org.restexpress.util.Environment; + +public class Main { + public static void main(String[] args) throws Exception { + Configuration config = Environment.load(args, Configuration.class); + Server server = new Server(config); + server.start().awaitShutdown(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Relationships.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Relationships.java new file mode 100644 index 0000000000..4a94e96952 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Relationships.java @@ -0,0 +1,55 @@ +package com.baeldung.restexpress; + +import com.baeldung.restexpress.objectid.SampleOidEntity; +import com.baeldung.restexpress.uuid.SampleUuidEntity; +import com.strategicgains.hyperexpress.HyperExpress; +import com.strategicgains.hyperexpress.RelTypes; +import org.restexpress.RestExpress; +import org.restexpress.common.exception.ConfigurationException; + +import java.util.Map; + +public abstract class Relationships { + private static Map ROUTES; + + public static void define(RestExpress server) { + ROUTES = server.getRouteUrlsByName(); + + HyperExpress.relationships() + .forCollectionOf(SampleUuidEntity.class) + .rel(RelTypes.SELF, href(Constants.Routes.SAMPLE_UUID_COLLECTION)) + .withQuery("limit={limit}") + .withQuery("offset={offset}") + .rel(RelTypes.NEXT, href(Constants.Routes.SAMPLE_UUID_COLLECTION) + "?offset={nextOffset}") + .withQuery("limit={limit}") + .optional() + .rel(RelTypes.PREV, href(Constants.Routes.SAMPLE_UUID_COLLECTION) + "?offset={prevOffset}") + .withQuery("limit={limit}") + .optional() + + .forClass(SampleUuidEntity.class) + .rel(RelTypes.SELF, href(Constants.Routes.SINGLE_UUID_SAMPLE)) + .rel(RelTypes.UP, href(Constants.Routes.SAMPLE_UUID_COLLECTION)) + + .forCollectionOf(SampleOidEntity.class) + .rel(RelTypes.SELF, href(Constants.Routes.SAMPLE_OID_COLLECTION)) + .withQuery("limit={limit}") + .withQuery("offset={offset}") + .rel(RelTypes.NEXT, href(Constants.Routes.SAMPLE_OID_COLLECTION) + "?offset={nextOffset}") + .withQuery("limit={limit}") + .optional() + .rel(RelTypes.PREV, href(Constants.Routes.SAMPLE_OID_COLLECTION) + "?offset={prevOffset}") + .withQuery("limit={limit}") + .optional() + + .forClass(SampleOidEntity.class) + .rel(RelTypes.SELF, href(Constants.Routes.SINGLE_OID_SAMPLE)) + .rel(RelTypes.UP, href(Constants.Routes.SAMPLE_OID_COLLECTION)); + } + + private static String href(String name) { + String href = ROUTES.get(name); + if (href == null) throw new ConfigurationException("Route name not found: " + name); + return href; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Routes.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Routes.java new file mode 100644 index 0000000000..a510dd24fa --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Routes.java @@ -0,0 +1,30 @@ +package com.baeldung.restexpress; + +import io.netty.handler.codec.http.HttpMethod; +import org.restexpress.RestExpress; + +public abstract class Routes { + public static void define(Configuration config, RestExpress server) { + // TODO: Your routes here... + server.uri("/samples/uuid/{uuid}.{format}", config.getSampleUuidEntityController()) + .method(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE) + .name(Constants.Routes.SINGLE_UUID_SAMPLE); + + server.uri("/samples/uuid.{format}", config.getSampleUuidEntityController()) + .action("readAll", HttpMethod.GET) + .method(HttpMethod.POST) + .name(Constants.Routes.SAMPLE_UUID_COLLECTION); + + server.uri("/samples/oid/{uuid}.{format}", config.getSampleOidEntityController()) + .method(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE) + .name(Constants.Routes.SINGLE_OID_SAMPLE); + + server.uri("/samples/oid.{format}", config.getSampleOidEntityController()) + .action("readAll", HttpMethod.GET) + .method(HttpMethod.POST) + .name(Constants.Routes.SAMPLE_OID_COLLECTION); + + // or REGEX matching routes... + // server.regex("/some.regex", config.getRouteController()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Server.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Server.java new file mode 100644 index 0000000000..d5864e607d --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Server.java @@ -0,0 +1,129 @@ +package com.baeldung.restexpress; + +import com.baeldung.restexpress.serialization.SerializationProvider; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.graphite.Graphite; +import com.codahale.metrics.graphite.GraphiteReporter; +import com.strategicgains.repoexpress.adapter.Identifiers; +import com.strategicgains.repoexpress.exception.DuplicateItemException; +import com.strategicgains.repoexpress.exception.InvalidObjectIdException; +import com.strategicgains.repoexpress.exception.ItemNotFoundException; +import com.strategicgains.restexpress.plugin.cache.CacheControlPlugin; +import com.strategicgains.restexpress.plugin.cors.CorsHeaderPlugin; +import com.strategicgains.restexpress.plugin.metrics.MetricsConfig; +import com.strategicgains.restexpress.plugin.metrics.MetricsPlugin; +import com.strategicgains.restexpress.plugin.swagger.SwaggerPlugin; +import com.strategicgains.syntaxe.ValidationException; +import org.restexpress.Flags; +import org.restexpress.RestExpress; +import org.restexpress.exception.BadRequestException; +import org.restexpress.exception.ConflictException; +import org.restexpress.exception.NotFoundException; +import org.restexpress.pipeline.SimpleConsoleLogMessageObserver; +import org.restexpress.plugin.hyperexpress.HyperExpressPlugin; +import org.restexpress.plugin.hyperexpress.Linkable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +import static io.netty.handler.codec.http.HttpHeaders.Names.*; +import static org.restexpress.Flags.Auth.PUBLIC_ROUTE; + +public class Server { + private static final String SERVICE_NAME = "TODO: Enter service name"; + private static final Logger LOG = LoggerFactory.getLogger(SERVICE_NAME); + + private RestExpress server; + private Configuration config; + private boolean isStarted = false; + + public Server(Configuration config) { + this.config = config; + RestExpress.setDefaultSerializationProvider(new SerializationProvider()); + Identifiers.UUID.useShortUUID(true); + + this.server = new RestExpress() + .setName(SERVICE_NAME) + .setBaseUrl(config.getBaseUrl()) + .setExecutorThreadCount(config.getExecutorThreadPoolSize()) + .addMessageObserver(new SimpleConsoleLogMessageObserver()); + + Routes.define(config, server); + Relationships.define(server); + configurePlugins(config, server); + mapExceptions(server); + } + + public Server start() { + if (!isStarted) { + server.bind(config.getPort()); + isStarted = true; + } + + return this; + } + + public void awaitShutdown() { + if (isStarted) server.awaitShutdown(); + } + + public void shutdown() { + if (isStarted) server.shutdown(); + } + + private void configurePlugins(Configuration config, RestExpress server) { + configureMetrics(config, server); + + new SwaggerPlugin() + .flag(Flags.Auth.PUBLIC_ROUTE) + .register(server); + + new CacheControlPlugin() + .register(server); + + new HyperExpressPlugin(Linkable.class) + .register(server); + + new CorsHeaderPlugin("*") + .flag(PUBLIC_ROUTE) + .allowHeaders(CONTENT_TYPE, ACCEPT, AUTHORIZATION, REFERER, LOCATION) + .exposeHeaders(LOCATION) + .register(server); + } + + private void configureMetrics(Configuration config, RestExpress server) { + MetricsConfig mc = config.getMetricsConfig(); + + if (mc.isEnabled()) { + MetricRegistry registry = new MetricRegistry(); + new MetricsPlugin(registry) + .register(server); + + if (mc.isGraphiteEnabled()) { + final Graphite graphite = new Graphite(new InetSocketAddress(mc.getGraphiteHost(), mc.getGraphitePort())); + final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry) + .prefixedWith(mc.getPrefix()) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .filter(MetricFilter.ALL) + .build(graphite); + reporter.start(mc.getPublishSeconds(), TimeUnit.SECONDS); + } else { + LOG.warn("*** Graphite Metrics Publishing is Disabled ***"); + } + } else { + LOG.warn("*** Metrics Generation is Disabled ***"); + } + } + + private void mapExceptions(RestExpress server) { + server + .mapException(ItemNotFoundException.class, NotFoundException.class) + .mapException(DuplicateItemException.class, ConflictException.class) + .mapException(ValidationException.class, BadRequestException.class) + .mapException(InvalidObjectIdException.class, BadRequestException.class); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntity.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntity.java new file mode 100644 index 0000000000..f92e56889b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntity.java @@ -0,0 +1,32 @@ +package com.baeldung.restexpress.objectid; + +import com.baeldung.restexpress.Constants; +import com.strategicgains.hyperexpress.annotation.BindToken; +import com.strategicgains.hyperexpress.annotation.TokenBindings; +import com.strategicgains.repoexpress.mongodb.AbstractMongodbEntity; +import org.restexpress.plugin.hyperexpress.Linkable; + +/** + * This is a sample entity identified by a MongoDB ObjectID (instead of a UUID). + * It also contains createdAt and updatedAt properties that are automatically maintained + * by the persistence layer (SampleOidEntityRepository). + */ +@TokenBindings({ + @BindToken(value = Constants.Url.SAMPLE_ID, field = "id") +}) +public class SampleOidEntity + extends AbstractMongodbEntity + implements Linkable { + private String name; + + public SampleOidEntity() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityController.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityController.java new file mode 100644 index 0000000000..1997ad9e10 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityController.java @@ -0,0 +1,80 @@ +package com.baeldung.restexpress.objectid; + +import com.baeldung.restexpress.Constants; +import com.strategicgains.hyperexpress.builder.DefaultTokenResolver; +import com.strategicgains.hyperexpress.builder.DefaultUrlBuilder; +import com.strategicgains.hyperexpress.builder.UrlBuilder; +import com.strategicgains.repoexpress.mongodb.Identifiers; +import io.netty.handler.codec.http.HttpMethod; +import org.restexpress.Request; +import org.restexpress.Response; +import org.restexpress.common.query.QueryFilter; +import org.restexpress.common.query.QueryOrder; +import org.restexpress.common.query.QueryRange; +import org.restexpress.query.QueryFilters; +import org.restexpress.query.QueryOrders; +import org.restexpress.query.QueryRanges; + +import java.util.List; + +/** + * This is the 'controller' layer, where HTTP details are converted to domain concepts and passed to the service layer. + * Then service layer response information is enhanced with HTTP details, if applicable, for the response. + *

+ * This controller demonstrates how to process an entity that is identified by a MongoDB ObjectId. + */ +public class SampleOidEntityController { + private static final UrlBuilder LOCATION_BUILDER = new DefaultUrlBuilder(); + private SampleOidEntityService service; + + public SampleOidEntityController(SampleOidEntityService sampleService) { + super(); + this.service = sampleService; + } + + public SampleOidEntity create(Request request, Response response) { + SampleOidEntity entity = request.getBodyAs(SampleOidEntity.class, "Resource details not provided"); + SampleOidEntity saved = service.create(entity); + + // Construct the response for create... + response.setResponseCreated(); + + // Include the Location header... + String locationPattern = request.getNamedUrl(HttpMethod.GET, Constants.Routes.SINGLE_OID_SAMPLE); + response.addLocationHeader(LOCATION_BUILDER.build(locationPattern, new DefaultTokenResolver())); + + // Return the newly-created resource... + return saved; + } + + public SampleOidEntity read(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + SampleOidEntity entity = service.read(Identifiers.MONGOID.parse(id)); + + return entity; + } + + public List readAll(Request request, Response response) { + QueryFilter filter = QueryFilters.parseFrom(request); + QueryOrder order = QueryOrders.parseFrom(request); + QueryRange range = QueryRanges.parseFrom(request, 20); + List entities = service.readAll(filter, range, order); + long count = service.count(filter); + response.setCollectionResponse(range, entities.size(), count); + return entities; + } + + public void update(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + SampleOidEntity entity = request.getBodyAs(SampleOidEntity.class, "Resource details not provided"); + entity.setId(Identifiers.MONGOID.parse(id)); + service.update(entity); + response.setResponseNoContent(); + } + + public void delete(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + service.delete(Identifiers.MONGOID.parse(id)); + response.setResponseNoContent(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityRepository.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityRepository.java new file mode 100644 index 0000000000..d003e04254 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.restexpress.objectid; + +import com.mongodb.MongoClient; +import com.strategicgains.repoexpress.mongodb.MongodbEntityRepository; + +public class SampleOidEntityRepository + extends MongodbEntityRepository { + @SuppressWarnings("unchecked") + public SampleOidEntityRepository(MongoClient mongo, String dbName) { + super(mongo, dbName, SampleOidEntity.class); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityService.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityService.java new file mode 100644 index 0000000000..076fa57e6b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityService.java @@ -0,0 +1,48 @@ +package com.baeldung.restexpress.objectid; + +import com.strategicgains.repoexpress.domain.Identifier; +import com.strategicgains.syntaxe.ValidationEngine; +import org.restexpress.common.query.QueryFilter; +import org.restexpress.common.query.QueryOrder; +import org.restexpress.common.query.QueryRange; + +import java.util.List; + +/** + * This is the 'service' or 'business logic' layer, where business logic, syntactic and semantic + * domain validation occurs, along with calls to the persistence layer. + */ +public class SampleOidEntityService { + private SampleOidEntityRepository samples; + + public SampleOidEntityService(SampleOidEntityRepository samplesRepository) { + super(); + this.samples = samplesRepository; + } + + public SampleOidEntity create(SampleOidEntity entity) { + ValidationEngine.validateAndThrow(entity); + return samples.create(entity); + } + + public SampleOidEntity read(Identifier id) { + return samples.read(id); + } + + public void update(SampleOidEntity entity) { + ValidationEngine.validateAndThrow(entity); + samples.update(entity); + } + + public void delete(Identifier id) { + samples.delete(id); + } + + public List readAll(QueryFilter filter, QueryRange range, QueryOrder order) { + return samples.readAll(filter, range, order); + } + + public long count(QueryFilter filter) { + return samples.count(filter); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/JsonSerializationProcessor.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/JsonSerializationProcessor.java new file mode 100644 index 0000000000..e9487878f0 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/JsonSerializationProcessor.java @@ -0,0 +1,35 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.strategicgains.hyperexpress.domain.hal.HalResource; +import com.strategicgains.hyperexpress.serialization.jackson.HalResourceDeserializer; +import com.strategicgains.hyperexpress.serialization.jackson.HalResourceSerializer; +import org.bson.types.ObjectId; +import org.restexpress.ContentType; +import org.restexpress.serialization.json.JacksonJsonProcessor; + +import java.util.UUID; + +public class JsonSerializationProcessor + extends JacksonJsonProcessor { + public JsonSerializationProcessor() { + super(); + addSupportedMediaTypes(ContentType.HAL_JSON); + } + + @Override + protected void initializeModule(SimpleModule module) { + super.initializeModule(module); + // For UUID as entity identifiers... + module.addDeserializer(UUID.class, new UuidDeserializer()); + module.addSerializer(UUID.class, new UuidSerializer()); + + // For MongoDB ObjectId as entity identifiers... + module.addDeserializer(ObjectId.class, new ObjectIdDeserializer()); + module.addSerializer(ObjectId.class, new ObjectIdSerializer()); + + // Support HalResource (de)serialization. + module.addDeserializer(HalResource.class, new HalResourceDeserializer()); + module.addSerializer(HalResource.class, new HalResourceSerializer()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdDeserializer.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdDeserializer.java new file mode 100644 index 0000000000..84c1aca83b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdDeserializer.java @@ -0,0 +1,19 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.strategicgains.repoexpress.mongodb.Identifiers; +import org.bson.types.ObjectId; + +import java.io.IOException; + +public class ObjectIdDeserializer + extends JsonDeserializer { + @Override + public ObjectId deserialize(JsonParser json, DeserializationContext context) + throws IOException, JsonProcessingException { + return (ObjectId) Identifiers.MONGOID.parse(json.getText()).primaryKey(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdSerializer.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdSerializer.java new file mode 100644 index 0000000000..8c3e63b7f8 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdSerializer.java @@ -0,0 +1,18 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.bson.types.ObjectId; + +import java.io.IOException; + +public class ObjectIdSerializer + extends JsonSerializer { + @Override + public void serialize(ObjectId objectId, JsonGenerator json, SerializerProvider provider) + throws IOException, JsonProcessingException { + json.writeString(objectId.toString()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/SerializationProvider.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/SerializationProvider.java new file mode 100644 index 0000000000..cb619be84f --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/SerializationProvider.java @@ -0,0 +1,29 @@ +package com.baeldung.restexpress.serialization; + +import org.restexpress.response.ErrorResponseWrapper; +import org.restexpress.response.ResponseWrapper; +import org.restexpress.serialization.AbstractSerializationProvider; +import org.restexpress.serialization.SerializationProcessor; + +public class SerializationProvider + extends AbstractSerializationProvider { + // SECTION: CONSTANTS + + private static final SerializationProcessor JSON_SERIALIZER = new JsonSerializationProcessor(); + private static final SerializationProcessor XML_SERIALIZER = new XmlSerializationProcessor(); + private static final ResponseWrapper RESPONSE_WRAPPER = new ErrorResponseWrapper(); + + public SerializationProvider() { + super(); + add(JSON_SERIALIZER, RESPONSE_WRAPPER, true); + add(XML_SERIALIZER, RESPONSE_WRAPPER); + } + + public static SerializationProcessor json() { + return JSON_SERIALIZER; + } + + public static SerializationProcessor xml() { + return XML_SERIALIZER; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidDeserializer.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidDeserializer.java new file mode 100644 index 0000000000..ce676edff5 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidDeserializer.java @@ -0,0 +1,19 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.strategicgains.repoexpress.util.UuidConverter; + +import java.io.IOException; +import java.util.UUID; + +public class UuidDeserializer + extends JsonDeserializer { + @Override + public UUID deserialize(JsonParser json, DeserializationContext context) + throws IOException, JsonProcessingException { + return UuidConverter.parse(json.getText()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidFormatter.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidFormatter.java new file mode 100644 index 0000000000..c337dbeafd --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidFormatter.java @@ -0,0 +1,14 @@ +package com.baeldung.restexpress.serialization; + +import com.strategicgains.hyperexpress.annotation.TokenFormatter; +import com.strategicgains.repoexpress.util.UuidConverter; + +import java.util.UUID; + +public class UuidFormatter + implements TokenFormatter { + @Override + public String format(Object field) { + return UuidConverter.format((UUID) field); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidSerializer.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidSerializer.java new file mode 100644 index 0000000000..4349e6d144 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidSerializer.java @@ -0,0 +1,19 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.strategicgains.repoexpress.util.UuidConverter; + +import java.io.IOException; +import java.util.UUID; + +public class UuidSerializer + extends JsonSerializer { + @Override + public void serialize(UUID objectId, JsonGenerator json, SerializerProvider provider) + throws IOException, JsonProcessingException { + json.writeString(UuidConverter.format(objectId)); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XmlSerializationProcessor.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XmlSerializationProcessor.java new file mode 100644 index 0000000000..e1bdd229c1 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XmlSerializationProcessor.java @@ -0,0 +1,18 @@ +package com.baeldung.restexpress.serialization; + +import com.baeldung.restexpress.uuid.SampleUuidEntity; +import org.restexpress.serialization.xml.XstreamXmlProcessor; + +public class XmlSerializationProcessor + extends XstreamXmlProcessor { + public XmlSerializationProcessor() { + super(); + alias("sample", SampleUuidEntity.class); +// alias("element_name", Element.class); +// alias("element_name", Element.class); +// alias("element_name", Element.class); +// alias("element_name", Element.class); + registerConverter(new XstreamUuidConverter()); + registerConverter(new XstreamOidConverter()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamOidConverter.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamOidConverter.java new file mode 100644 index 0000000000..5eb53814fb --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamOidConverter.java @@ -0,0 +1,28 @@ +package com.baeldung.restexpress.serialization; + +import com.strategicgains.repoexpress.mongodb.Identifiers; +import com.thoughtworks.xstream.converters.SingleValueConverter; +import org.bson.types.ObjectId; + +/** + * @author toddf + * @since Feb 16, 2011 + */ +public class XstreamOidConverter + implements SingleValueConverter { + @SuppressWarnings("rawtypes") + @Override + public boolean canConvert(Class aClass) { + return ObjectId.class.isAssignableFrom(aClass); + } + + @Override + public Object fromString(String value) { + return (ObjectId) Identifiers.MONGOID.parse(value).primaryKey(); + } + + @Override + public String toString(Object objectId) { + return ((ObjectId) objectId).toString(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamUuidConverter.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamUuidConverter.java new file mode 100644 index 0000000000..82f34bc36d --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamUuidConverter.java @@ -0,0 +1,29 @@ +package com.baeldung.restexpress.serialization; + +import com.strategicgains.repoexpress.util.UuidConverter; +import com.thoughtworks.xstream.converters.SingleValueConverter; + +import java.util.UUID; + +/** + * @author toddf + * @since Feb 16, 2011 + */ +public class XstreamUuidConverter + implements SingleValueConverter { + @SuppressWarnings("rawtypes") + @Override + public boolean canConvert(Class aClass) { + return UUID.class.isAssignableFrom(aClass); + } + + @Override + public Object fromString(String value) { + return UuidConverter.parse(value); + } + + @Override + public String toString(Object objectId) { + return UuidConverter.format((UUID) objectId); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntity.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntity.java new file mode 100644 index 0000000000..724e681700 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntity.java @@ -0,0 +1,23 @@ +package com.baeldung.restexpress.uuid; + +import com.baeldung.restexpress.Constants; +import com.baeldung.restexpress.serialization.UuidFormatter; +import com.strategicgains.hyperexpress.annotation.BindToken; +import com.strategicgains.hyperexpress.annotation.TokenBindings; +import com.strategicgains.repoexpress.mongodb.AbstractUuidMongodbEntity; +import org.restexpress.plugin.hyperexpress.Linkable; + +/** + * This is a sample entity identified by a UUID (instead of a MongoDB ObjectID). + * It also contains createdAt and updatedAt properties that are automatically maintained + * by the persistence layer (SampleUuidEntityRepository). + */ +@TokenBindings({ + @BindToken(value = Constants.Url.SAMPLE_ID, field = "id", formatter = UuidFormatter.class) +}) +public class SampleUuidEntity + extends AbstractUuidMongodbEntity + implements Linkable { + public SampleUuidEntity() { + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityController.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityController.java new file mode 100644 index 0000000000..addd94ee3b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityController.java @@ -0,0 +1,79 @@ +package com.baeldung.restexpress.uuid; + +import com.baeldung.restexpress.Constants; +import com.strategicgains.hyperexpress.builder.DefaultTokenResolver; +import com.strategicgains.hyperexpress.builder.DefaultUrlBuilder; +import com.strategicgains.hyperexpress.builder.UrlBuilder; +import com.strategicgains.repoexpress.adapter.Identifiers; +import io.netty.handler.codec.http.HttpMethod; +import org.restexpress.Request; +import org.restexpress.Response; +import org.restexpress.common.query.QueryFilter; +import org.restexpress.common.query.QueryOrder; +import org.restexpress.common.query.QueryRange; +import org.restexpress.query.QueryFilters; +import org.restexpress.query.QueryOrders; +import org.restexpress.query.QueryRanges; + +import java.util.List; + +/** + * This is the 'controller' layer, where HTTP details are converted to domain concepts and passed to the service layer. + * Then service layer response information is enhanced with HTTP details, if applicable, for the response. + *

+ * This controller demonstrates how to process a MongoDB entity that is identified by a UUID. + */ +public class SampleUuidEntityController { + private static final UrlBuilder LOCATION_BUILDER = new DefaultUrlBuilder(); + private SampleUuidEntityService service; + + public SampleUuidEntityController(SampleUuidEntityService sampleService) { + super(); + this.service = sampleService; + } + + public SampleUuidEntity create(Request request, Response response) { + SampleUuidEntity entity = request.getBodyAs(SampleUuidEntity.class, "Resource details not provided"); + SampleUuidEntity saved = service.create(entity); + + // Construct the response for create... + response.setResponseCreated(); + + // Include the Location header... + String locationPattern = request.getNamedUrl(HttpMethod.GET, Constants.Routes.SINGLE_UUID_SAMPLE); + response.addLocationHeader(LOCATION_BUILDER.build(locationPattern, new DefaultTokenResolver())); + + // Return the newly-created resource... + return saved; + } + + public SampleUuidEntity read(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + SampleUuidEntity entity = service.read(Identifiers.UUID.parse(id)); + return entity; + } + + public List readAll(Request request, Response response) { + QueryFilter filter = QueryFilters.parseFrom(request); + QueryOrder order = QueryOrders.parseFrom(request); + QueryRange range = QueryRanges.parseFrom(request, 20); + List entities = service.readAll(filter, range, order); + long count = service.count(filter); + response.setCollectionResponse(range, entities.size(), count); + return entities; + } + + public void update(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + SampleUuidEntity entity = request.getBodyAs(SampleUuidEntity.class, "Resource details not provided"); + entity.setId(Identifiers.UUID.parse(id)); + service.update(entity); + response.setResponseNoContent(); + } + + public void delete(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + service.delete(Identifiers.UUID.parse(id)); + response.setResponseNoContent(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityRepository.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityRepository.java new file mode 100644 index 0000000000..0b5eef1ea6 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.restexpress.uuid; + +import com.mongodb.MongoClient; +import com.strategicgains.repoexpress.mongodb.MongodbUuidEntityRepository; + +public class SampleUuidEntityRepository + extends MongodbUuidEntityRepository { + @SuppressWarnings("unchecked") + public SampleUuidEntityRepository(MongoClient mongo, String dbName) { + super(mongo, dbName, SampleUuidEntity.class); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityService.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityService.java new file mode 100644 index 0000000000..57a1c18d05 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityService.java @@ -0,0 +1,48 @@ +package com.baeldung.restexpress.uuid; + +import com.strategicgains.repoexpress.domain.Identifier; +import com.strategicgains.syntaxe.ValidationEngine; +import org.restexpress.common.query.QueryFilter; +import org.restexpress.common.query.QueryOrder; +import org.restexpress.common.query.QueryRange; + +import java.util.List; + +/** + * This is the 'service' or 'business logic' layer, where business logic, syntactic and semantic + * domain validation occurs, along with calls to the persistence layer. + */ +public class SampleUuidEntityService { + private SampleUuidEntityRepository samples; + + public SampleUuidEntityService(SampleUuidEntityRepository samplesRepository) { + super(); + this.samples = samplesRepository; + } + + public SampleUuidEntity create(SampleUuidEntity entity) { + ValidationEngine.validateAndThrow(entity); + return samples.create(entity); + } + + public SampleUuidEntity read(Identifier id) { + return samples.read(id); + } + + public void update(SampleUuidEntity entity) { + ValidationEngine.validateAndThrow(entity); + samples.update(entity); + } + + public void delete(Identifier id) { + samples.delete(id); + } + + public List readAll(QueryFilter filter, QueryRange range, QueryOrder order) { + return samples.readAll(filter, range, order); + } + + public long count(QueryFilter filter) { + return samples.count(filter); + } +} diff --git a/microservices-modules/rest-express/src/main/resources/config/dev/environment.properties b/microservices-modules/rest-express/src/main/resources/config/dev/environment.properties new file mode 100644 index 0000000000..b81f9a84cb --- /dev/null +++ b/microservices-modules/rest-express/src/main/resources/config/dev/environment.properties @@ -0,0 +1,22 @@ +# Default is 8081 +port = 8081 + +# The size of the executor thread pool (that can handle blocking back-end processing). +executor.threadPool.size = 20 + +# A MongoDB URI/Connection string +# see: http://docs.mongodb.org/manual/reference/connection-string/ +mongodb.uri = mongodb://localhost:27017/scaffolding_mongodb + +# The base URL, used as a prefix for links returned in data +# default is http://localhost: +#base.url = http://localhost:8081 + +#Configuration for the MetricsPlugin/Graphite +metrics.isEnabled = true +#metrics.machineName = +metrics.prefix = web1.example.com +metrics.graphite.isEnabled = false +metrics.graphite.host = graphite.example.com +metrics.graphite.port = 2003 +metrics.graphite.publishSeconds = 60 \ No newline at end of file