diff --git a/conf/logback.xml b/conf/logback.xml index 4a4eb58..3e04985 100644 --- a/conf/logback.xml +++ b/conf/logback.xml @@ -20,7 +20,7 @@ - + diff --git a/pom.xml b/pom.xml index 7688b90..a6e6df6 100644 --- a/pom.xml +++ b/pom.xml @@ -131,6 +131,11 @@ org.msgpack jackson-dataformat-msgpack 0.8.12 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + [2.8.8,) com.fasterxml.jackson.module @@ -236,5 +241,11 @@ graphql-java 2.3.0 + + io.github.lukehutch + fast-classpath-scanner + LATEST + + \ No newline at end of file diff --git a/src/main/java/com/wurrly/Application.java b/src/main/java/com/wurrly/Application.java index b9abed9..0d9247f 100644 --- a/src/main/java/com/wurrly/Application.java +++ b/src/main/java/com/wurrly/Application.java @@ -28,6 +28,7 @@ import com.wurrly.server.endpoints.EndpointInfo; import com.wurrly.server.handlers.HandlerGenerator; import com.wurrly.server.handlers.benchmark.BenchmarkHandlers; +import com.wurrly.services.AssetsService; import com.wurrly.services.SwaggerService; import io.undertow.Undertow; @@ -93,7 +94,9 @@ public void start() - Set services = registeredServices.stream().map( sc -> injector.getInstance(sc)).collect(Collectors.toSet()); + Set services = registeredServices.stream() + .map( sc -> injector.getInstance(sc)) + .collect(Collectors.toSet()); this.serviceManager = new ServiceManager(services); @@ -214,6 +217,8 @@ public static void main(String[] args) app.useService(SwaggerService.class); + app.useService(AssetsService.class); + app.useController(Users.class); app.start(); diff --git a/src/main/java/com/wurrly/controllers/Users.java b/src/main/java/com/wurrly/controllers/Users.java index beb862a..25967ee 100644 --- a/src/main/java/com/wurrly/controllers/Users.java +++ b/src/main/java/com/wurrly/controllers/Users.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -179,10 +181,11 @@ public ServerResponse createUser( final ServerRequest serverRequest, @QueryPara @PUT @Path("/username") - @Consumes(("application/json")) + @Consumes("application/json,application/xml") + @Produces("application/json,application/xml") // @ApiImplicitParams({ @ApiImplicitParam(dataType = "com.wurrly.models.User", name = "user", paramType = "body", required = false, allowMultiple = false) }) @ApiOperation(value = "Update a user's name", httpMethod = "PUT", response = User.class) - public ServerResponse updateUsername(@ApiParam(hidden=true)final ServerRequest serverRequest, @QueryParam("context") Optional context, final User user ) + public CompletableFuture updateUsername(@ApiParam(hidden=true)final ServerRequest serverRequest, @QueryParam("context") Optional context, final User user ) { // log.debug("esIndexName: " + esIndexName); @@ -191,7 +194,7 @@ public ServerResponse updateUsername(@ApiParam(hidden=true)final ServerRequest s log.debug("file: " + user); - return response().entity(Any.wrap(user)); + return CompletableFuture.completedFuture(response().entity(user)); } diff --git a/src/main/java/com/wurrly/modules/RoutingModule.java b/src/main/java/com/wurrly/modules/RoutingModule.java index 37f8ad4..4cc7b3c 100644 --- a/src/main/java/com/wurrly/modules/RoutingModule.java +++ b/src/main/java/com/wurrly/modules/RoutingModule.java @@ -11,6 +11,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.google.inject.AbstractModule; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; @@ -21,7 +22,7 @@ import io.undertow.predicate.TruePredicate; import io.undertow.server.DefaultResponseListener; -import io.undertow.server.HttpServerExchange; +import io.undertow.server.HttpHandler; import io.undertow.server.RoutingHandler; import io.undertow.server.handlers.resource.FileResourceManager; import io.undertow.server.handlers.resource.ResourceHandler; @@ -57,24 +58,15 @@ protected void configure() RoutingHandler router = new RoutingHandler() .setFallbackHandler(BaseHandlers::notFoundHandler); - - final String assetsPath = config.getString("assets.path"); - final String assetsDirectoryName = config.getString("assets.dir") ; - final Integer assetsCacheTime = config.getInt("assets.cache.time"); - - final FileResourceManager fileResourceManager = new FileResourceManager(Paths.get(assetsDirectoryName).toFile()); - + + this.bind(XmlMapper.class).toInstance(new XmlMapper()); - router.add(Methods.GET, assetsPath + "/*", io.undertow.Handlers.rewrite("regex['" + assetsPath + "/(.*)']", "/$1", getClass().getClassLoader(), new ResourceHandler(fileResourceManager) - .setCachable(TruePredicate.instance()) - .setCacheTime(assetsCacheTime) - )); - this.bind(RoutingHandler.class).toInstance(router); this.bind(RoutingModule.class).toInstance(this); + try { String defaultResponseListenerClassName = config.getString("application.defaultResponseListener"); @@ -84,6 +76,8 @@ protected void configure() { log.error(e.getMessage(),e); } + + this.bind(new TypeLiteral>>() {}).annotatedWith(Names.named("registeredControllers")).toInstance(registeredControllers); this.bind(new TypeLiteral>() {}).annotatedWith(Names.named("registeredEndpoints")).toInstance(registeredEndpoints); diff --git a/src/main/java/com/wurrly/server/Extractors.java b/src/main/java/com/wurrly/server/Extractors.java index 3df3990..5aae615 100644 --- a/src/main/java/com/wurrly/server/Extractors.java +++ b/src/main/java/com/wurrly/server/Extractors.java @@ -11,6 +11,7 @@ import java.nio.file.StandardOpenOption; import java.time.ZonedDateTime; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.Deque; import java.util.Objects; @@ -19,13 +20,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.google.inject.Inject; import com.jsoniter.JsonIterator; import com.jsoniter.any.Any; import com.jsoniter.spi.TypeLiteral; +import com.wurrly.server.handlers.predicates.MaxRequestContentLengthPredicate; +import io.undertow.attribute.ExchangeAttributes; +import io.undertow.predicate.Predicate; +import io.undertow.predicate.Predicates; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormData.FormValue; import io.undertow.server.handlers.form.FormDataParser; +import io.undertow.server.handlers.form.FormEncodedDataDefinition; +import io.undertow.server.handlers.form.MultiPartParserDefinition; +import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; @@ -36,15 +46,47 @@ public class Extractors { private static Logger log = LoggerFactory.getLogger(Extractors.class.getCanonicalName()); + + + public static final Predicate JSON_PREDICATE = Predicates.contains(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), MimeTypes.APPLICATION_JSON_TYPE); + public static final Predicate XML_PREDICATE = io.undertow.predicate.Predicates.contains(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), MimeTypes.APPLICATION_XML_TYPE); + + protected static final XmlMapper XML_MAPPER = new XmlMapper(); + public static class Optional { public static java.util.Optional jsonIterator(final HttpServerExchange exchange) { - return java.util.Optional.ofNullable( JsonIterator.parse(exchange.getAttachment(ServerRequest.JSON_DATA).array())); + return java.util.Optional.ofNullable(exchange.getAttachment(ServerRequest.BYTE_BUFFER_KEY)).map(ByteBuffer::array).map(JsonIterator::parse); } - public static java.util.Optional typed(final HttpServerExchange exchange, final TypeLiteral type ) + public static java.util.Optional model(final HttpServerExchange exchange, final TypeLiteral type ) + { + if( JSON_PREDICATE.resolve(exchange) ) + { + return jsonModel(exchange,type); + } + else + { + return xmlModel(exchange,type); + } + } + + public static java.util.Optional model(final HttpServerExchange exchange, final Class type ) + { + if( JSON_PREDICATE.resolve(exchange) ) + { + return jsonModel(exchange,type); + } + else + { + return xmlModel(exchange,type); + } + } + + + public static java.util.Optional jsonModel(final HttpServerExchange exchange, final TypeLiteral type ) { return jsonIterator(exchange).map(i -> { try @@ -57,7 +99,7 @@ public static java.util.Optional typed(final HttpServerExchange exchange }); } - public static java.util.Optional typed(final HttpServerExchange exchange, final Class type ) + public static java.util.Optional jsonModel(final HttpServerExchange exchange, final Class type ) { return jsonIterator(exchange).map(i -> { try @@ -69,6 +111,35 @@ public static java.util.Optional typed(final HttpServerExchange exchange } }); } + + public static java.util.Optional xmlModel(final HttpServerExchange exchange, final TypeLiteral type ) + { + return java.util.Optional.ofNullable(exchange.getAttachment(ServerRequest.BYTE_BUFFER_KEY)).map(ByteBuffer::array).map( b -> { + try + { + return XML_MAPPER.readValue(b,XML_MAPPER.getTypeFactory().constructType(type.getType())); + } catch (Exception e) + { + return null; + } + }); + } + + public static java.util.Optional xmlModel(final HttpServerExchange exchange, final Class type ) + { + return java.util.Optional.ofNullable(exchange.getAttachment(ServerRequest.BYTE_BUFFER_KEY)).map(ByteBuffer::array).map( b -> { + try + { + return XML_MAPPER.readValue(b,type); + } catch (Exception e) + { + return null; + } + }); + + } + + public static java.util.Optional date(final HttpServerExchange exchange,final String name) { @@ -79,7 +150,7 @@ public static java.util.Optional date(final HttpServerExchange exchange,fi public static java.util.Optional any(final HttpServerExchange exchange ) { - return java.util.Optional.ofNullable(exchange.getAttachment(ServerRequest.JSON_DATA)).map(t -> JsonIterator.deserialize(t.array())); + return java.util.Optional.ofNullable(exchange.getAttachment(ServerRequest.BYTE_BUFFER_KEY)).map(t -> JsonIterator.deserialize(t.array())); } public static java.util.Optional integerValue(final HttpServerExchange exchange, final String name) @@ -108,10 +179,10 @@ public static java.util.Optional booleanValue(final HttpServerExchange return string(exchange, name).map(Boolean::parseBoolean); } - public static > java.util.Optional enumValue(final HttpServerExchange exchange, final Class clazz, final String name) - { - return string(exchange, name).map(e -> Enum.valueOf(clazz, name)); - } +// public static > java.util.Optional enumValue(final HttpServerExchange exchange, final Class clazz, final String name) +// { +// return string(exchange, name).map(e -> Enum.valueOf(clazz, name)); +// } public static java.util.Optional string(final HttpServerExchange exchange, final String name) { @@ -150,7 +221,7 @@ public static Date date(final HttpServerExchange exchange,final String name) { } - public static T typed(final HttpServerExchange exchange, final TypeLiteral type ) throws Exception + public static T jsonModel(final HttpServerExchange exchange, final TypeLiteral type ) throws IllegalArgumentException { try { @@ -162,7 +233,7 @@ public static T typed(final HttpServerExchange exchange, final TypeLiteral< } } - public static T typed(final HttpServerExchange exchange, final Class type ) throws Exception + public static T jsonModel(final HttpServerExchange exchange, final Class type ) throws IllegalArgumentException { try { @@ -174,12 +245,39 @@ public static T typed(final HttpServerExchange exchange, final Class typ } } + + + public static T xmlModel(final HttpServerExchange exchange, final Class type ) throws IllegalArgumentException + { + try + { + return XML_MAPPER.readValue(exchange.getAttachment(ServerRequest.BYTE_BUFFER_KEY).array(), type); + } + catch( Exception e ) + { + throw new IllegalArgumentException("Invalid XML"); + + } + } + + public static T xmlModel(final HttpServerExchange exchange, final TypeLiteral type ) throws IllegalArgumentException + { + try + { + return XML_MAPPER.readValue(exchange.getAttachment(ServerRequest.BYTE_BUFFER_KEY).array(), XML_MAPPER.getTypeFactory().constructType(type.getType())); + } + catch( Exception e ) + { + throw new IllegalArgumentException("Invalid XML"); + + } + } public static Any any(final HttpServerExchange exchange ) { try { - return JsonIterator.parse( exchange.getAttachment(ServerRequest.JSON_DATA).array() ).readAny(); + return JsonIterator.parse( exchange.getAttachment(ServerRequest.BYTE_BUFFER_KEY).array() ).readAny(); } catch (IOException e) { return Any.wrapNull(); @@ -188,7 +286,7 @@ public static Any any(final HttpServerExchange exchange ) public static JsonIterator jsonIterator(final HttpServerExchange exchange ) { - return JsonIterator.parse(exchange.getAttachment(ServerRequest.JSON_DATA).array()); + return JsonIterator.parse(exchange.getAttachment(ServerRequest.BYTE_BUFFER_KEY).array()); } public static Path filePath(final HttpServerExchange exchange, final String name) @@ -242,13 +340,36 @@ public static Boolean booleanValue(final HttpServerExchange exchange, final Str { return Boolean.parseBoolean(string(exchange, name)); } + + public static T model(final HttpServerExchange exchange, final TypeLiteral type ) throws IllegalArgumentException + { + if( JSON_PREDICATE.resolve(exchange) ) + { + return jsonModel(exchange,type); + } + else + { + return xmlModel(exchange,type); + } + } - - - public static > E enumValue(final HttpServerExchange exchange, Class clazz,final String name) + public static T model(final HttpServerExchange exchange, final Class type ) throws IllegalArgumentException { - return Enum.valueOf(clazz, string(exchange, name)); + if( JSON_PREDICATE.resolve(exchange) ) + { + return jsonModel(exchange,type); + } + else + { + return xmlModel(exchange,type); + } } + + +// public static > E enumValue(final HttpServerExchange exchange, Class clazz,final String name) +// { +// return Enum.valueOf(clazz, string(exchange, name)); +// } diff --git a/src/main/java/com/wurrly/server/MimeTypes.java b/src/main/java/com/wurrly/server/MimeTypes.java new file mode 100644 index 0000000..e7f70fd --- /dev/null +++ b/src/main/java/com/wurrly/server/MimeTypes.java @@ -0,0 +1,19 @@ +/** + * + */ +package com.wurrly.server; + +/** + * @author jbauer + * + */ +public class MimeTypes +{ + public static final String OCTET_STREAM_TYPE = com.j256.simplemagic.ContentType.EMPTY.getMimeType(); + public static final String APPLICATION_JSON_TYPE = com.j256.simplemagic.ContentType.JSON.getMimeType(); + public static final String APPLICATION_XML_TYPE = com.j256.simplemagic.ContentType.XML.getMimeType(); + public static final String TEXT_HTML_TYPE = com.j256.simplemagic.ContentType.HTML.getMimeType(); + public static final String TEXT_PLAIN_TYPE = com.j256.simplemagic.ContentType.TEXT.getMimeType(); + + +} diff --git a/src/main/java/com/wurrly/server/ServerPredicates.java b/src/main/java/com/wurrly/server/ServerPredicates.java new file mode 100644 index 0000000..53b482d --- /dev/null +++ b/src/main/java/com/wurrly/server/ServerPredicates.java @@ -0,0 +1,31 @@ +/** + * + */ +package com.wurrly.server; + +import java.util.Collections; + +import com.wurrly.server.handlers.predicates.MaxRequestContentLengthPredicate; + +import io.undertow.attribute.ExchangeAttributes; +import io.undertow.predicate.Predicate; +import io.undertow.predicate.Predicates; +import io.undertow.server.handlers.form.FormEncodedDataDefinition; +import io.undertow.server.handlers.form.MultiPartParserDefinition; +import io.undertow.util.Headers; + +/** + * @author jbauer + * + */ +public class ServerPredicates +{ + + public static final Predicate ACCEPT_JSON_PREDICATE = Predicates.contains(ExchangeAttributes.requestHeader(Headers.ACCEPT), MimeTypes.APPLICATION_JSON_TYPE); + public static final Predicate ACCEPT_XML_PREDICATE = io.undertow.predicate.Predicates.contains(ExchangeAttributes.requestHeader(Headers.ACCEPT), MimeTypes.APPLICATION_XML_TYPE); + public static final Predicate MAX_CONTENT_SIZE_PREDICATE = new MaxRequestContentLengthPredicate.Builder().build(Collections.singletonMap("value", 0L)); + public static final Predicate STRING_BODY_PREDICATE = io.undertow.predicate.Predicates.and(io.undertow.predicate.Predicates.contains(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), MimeTypes.APPLICATION_JSON_TYPE, MimeTypes.APPLICATION_XML_TYPE), MAX_CONTENT_SIZE_PREDICATE ); + public static final Predicate MULTIPART_PREDICATE = Predicates.contains(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), MimeTypes.OCTET_STREAM_TYPE, MultiPartParserDefinition.MULTIPART_FORM_DATA ); + public static final Predicate URL_ENCODED_FORM_PREDICATE = io.undertow.predicate.Predicates.contains(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), FormEncodedDataDefinition.APPLICATION_X_WWW_FORM_URLENCODED ); + +} diff --git a/src/main/java/com/wurrly/server/ServerRequest.java b/src/main/java/com/wurrly/server/ServerRequest.java index e37fe57..0a461c7 100644 --- a/src/main/java/com/wurrly/server/ServerRequest.java +++ b/src/main/java/com/wurrly/server/ServerRequest.java @@ -5,24 +5,17 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.net.InetSocketAddress; import java.net.URLDecoder; import java.nio.ByteBuffer; import java.util.Deque; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Scanner; import java.util.concurrent.Executor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xnio.channels.StreamSourceChannel; -import com.jsoniter.JsonIterator; - -import io.undertow.UndertowMessages; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormData; @@ -34,17 +27,13 @@ import io.undertow.util.Headers; import io.undertow.util.MalformedMessageException; -import java.util.stream.Collectors; - public class ServerRequest { private static Logger log = LoggerFactory.getLogger(ServerRequest.class.getCanonicalName()); - - public static final AttachmentKey JSON_DATA = AttachmentKey.create(ByteBuffer.class); - - - private static final String OCTET_STREAM_TYPE = org.apache.http.entity.ContentType.APPLICATION_OCTET_STREAM.getMimeType(); - private static final String APPLICATION_JSON = org.apache.http.entity.ContentType.APPLICATION_JSON.getMimeType(); + + + public static final AttachmentKey BYTE_BUFFER_KEY = AttachmentKey.create(ByteBuffer.class); + private static final String CHARSET = "UTF-8"; public final HttpServerExchange exchange; @@ -52,7 +41,8 @@ public class ServerRequest private final String path; private FormData form; private final String contentType; - private final String method; + private final String method; + private static final String TMP_DIR = System.getProperty("java.io.tmpdir"); @@ -62,7 +52,7 @@ public ServerRequest() this.path = null; this.exchange = null; this.contentType = null; - + } public ServerRequest(HttpServerExchange exchange) throws IOException @@ -70,23 +60,22 @@ public ServerRequest(HttpServerExchange exchange) throws IOException this.method = exchange.getRequestMethod().toString(); this.path = URLDecoder.decode(exchange.getRequestPath(), CHARSET); this.exchange = exchange; - this.contentType = exchange.getRequestHeaders().getFirst("Content-Type"); - - + this.contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); + if (this.contentType != null ) { - if (this.contentType.contains(FormEncodedDataDefinition.APPLICATION_X_WWW_FORM_URLENCODED) ) + if ( ServerPredicates.URL_ENCODED_FORM_PREDICATE.resolve(exchange) ) { this.parseEncodedForm(); } - else if (this.contentType.contains(MultiPartParserDefinition.MULTIPART_FORM_DATA) || this.contentType.contains(OCTET_STREAM_TYPE)) + else if ( ServerPredicates.MULTIPART_PREDICATE.resolve(exchange) ) { this.parseMultipartForm(); } - else if (this.contentType.contains(APPLICATION_JSON) && this.exchange.getRequestContentLength() != -1) - { - this.parseJson(); - } + else if ( ServerPredicates.STRING_BODY_PREDICATE.resolve(exchange) ) + { + this.extractBytes(); + } } @@ -151,18 +140,11 @@ public Map> getPathParameters() - private void parseJson() throws IOException - { - - if(this.exchange.getRequestContentLength() != -1) - { + private void extractBytes() throws IOException + { + this.exchange.startBlocking(); - -// ByteBuffer buffer = ByteBuffer.allocate((int) this.exchange.getRequestContentLength()); -// this.exchange.getRequestChannel().read(buffer); -// JsonIterator iterator = JsonIterator.parse(buffer.array()); -// this.exchange.putAttachment(JSON_DATA, iterator); - + try (PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate()){ ByteBuffer buf = pooled.getBuffer(); @@ -173,20 +155,17 @@ private void parseJson() throws IOException buf.clear(); int c = channel.read(buf); - - - + if (c == -1) { - -// JsonIterator iterator = JsonIterator.parse(buf.array()); - + int pos = buf.limit(); ByteBuffer buffer = ByteBuffer.allocate(pos); System.arraycopy(buf.array(), 0, buffer.array(), 0, pos); - exchange.putAttachment(JSON_DATA, buffer); + exchange.putAttachment(BYTE_BUFFER_KEY, buffer); + break; } else if (c != 0) { @@ -195,71 +174,34 @@ private void parseJson() throws IOException } } catch (MalformedMessageException e) { throw new IOException(e); - } -// try(PooledByteBuffer resource = this.exchange.getConnection().getByteBufferPool().allocate()) -// { -// final ByteBuffer buffer = resource.getBuffer(); - -// final UTF8Output string = new UTF8Output(); + } + +// else +// { +// this.exchange.startBlocking(); // -// final StreamSourceChannel channel = this.exchange.getRequestChannel(); -// -// try { -// int r = 0; -// do { -// r = channel.read(buffer); -// if (r == 0) { -// //channel.getReadSetter().set(this); -// channel.resumeReads(); -// } else if (r == -1) { -// JsonIterator iterator = JsonIterator.parse(string.extract()); -// this.exchange.putAttachment(REQUEST_JSON_BODY, iterator); -// IoUtils.safeClose(channel); -// } else { -// buffer.flip(); -// string.write(buffer); -// } -// } while (r > 0); - - - - -// } catch (IOException e) { -// throw e; -// } -// } -// Logger.debug("iterator " + iterator); - - } - else - { - this.exchange.startBlocking(); - - InputStream is = exchange.getInputStream(); - if (is != null) { - - try - { - if (is.available() != -1) { - - try(Scanner scanner = new Scanner(is, "UTF-8")) - { - String s = scanner.useDelimiter("\\A").next(); - s = s.trim(); - // JsonIterator iterator = JsonIterator.parse(s); - // log.debug("iterator " + iterator); - - this.exchange.putAttachment(JSON_DATA, ByteBuffer.wrap(s.getBytes())); - } - - } - - } catch (IOException e) { - log.error("IOException: ", e); - } - - } - } +// InputStream is = exchange.getInputStream(); +// if (is != null) { +// +// try +// { +// if (is.available() != -1) { +// +// try(Scanner scanner = new Scanner(is, "UTF-8")) +// { +// String s = scanner.useDelimiter("\\A").next(); +// s = s.trim(); +// this.exchange.putAttachment(BYTE_BUFFER_KEY, ByteBuffer.wrap(s.getBytes())); +// } +// +// } +// +// } catch (IOException e) { +// log.error("IOException: ", e); +// } +// +// } +// } } @@ -272,28 +214,22 @@ private void parseMultipartForm() throws IOException .setDefaultEncoding(CHARSET) .create(this.exchange); - log.debug(this.exchange+"\nmime: " + this.contentType); - - log.debug("boundary: " + Headers.extractQuotedValueFromHeader(this.contentType, "boundary")); - - + if(formDataParser != null) { - final FormData formData = formDataParser.parseBlocking(); - - log.debug("formData: " + formData); - + final FormData formData = formDataParser.parseBlocking(); this.exchange.putAttachment(FormDataParser.FORM_DATA, formData); extractFormParameters(formData); } } private void parseEncodedForm() throws IOException - { - + { this.exchange.startBlocking(); - final FormData formData = new FormEncodedDataDefinition().setDefaultEncoding(this.exchange.getRequestCharset()).create(exchange).parseBlocking(); + final FormData formData = new FormEncodedDataDefinition() + .setDefaultEncoding(this.exchange.getRequestCharset()) + .create(exchange).parseBlocking(); this.exchange.putAttachment(FormDataParser.FORM_DATA, formData); @@ -305,8 +241,11 @@ private void extractFormParameters(final FormData formData) if (formData != null) { for (String key : formData) { - Deque formValues = formData.get(key); - Deque values = formValues.stream().filter(fv -> !fv.isFile()).map(FormData.FormValue::getValue).collect(java.util.stream.Collectors.toCollection(FastConcurrentDirectDeque::new)); + final Deque formValues = formData.get(key); + final Deque values = formValues.stream() + .filter(fv -> !fv.isFile()) + .map(FormData.FormValue::getValue) + .collect(java.util.stream.Collectors.toCollection(FastConcurrentDirectDeque::new)); exchange.getQueryParameters().put(key, values); } } @@ -332,6 +271,7 @@ public String rawPath() return exchange.getRequestURI(); } + public void startAsync(final Executor executor, final Runnable runnable) { exchange.dispatch(executor, runnable); diff --git a/src/main/java/com/wurrly/server/ServerResponse.java b/src/main/java/com/wurrly/server/ServerResponse.java index d9075aa..d8334ac 100644 --- a/src/main/java/com/wurrly/server/ServerResponse.java +++ b/src/main/java/com/wurrly/server/ServerResponse.java @@ -3,7 +3,6 @@ */ package com.wurrly.server; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; @@ -11,10 +10,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.jsoniter.any.Any; import com.jsoniter.output.JsonStream; +import io.undertow.attribute.ExchangeAttributes; import io.undertow.io.IoCallback; +import io.undertow.predicate.Predicate; +import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.Cookie; @@ -29,14 +32,11 @@ * */ public class ServerResponse -{ +{ private static Logger log = LoggerFactory.getLogger(ServerResponse.class.getCanonicalName()); - - private static final String APPLICATION_JSON_CONTENT_TYPE = org.apache.http.entity.ContentType.APPLICATION_JSON.getMimeType(); - private static final String TEXT_PLAIN_CONTENT_TYPE = org.apache.http.entity.ContentType.TEXT_PLAIN.getMimeType(); - private static final String TEXT_XML_CONTENT_TYPE = org.apache.http.entity.ContentType.TEXT_XML.getMimeType(); - private static final String TEXT_HTML_CONTENT_TYPE = org.apache.http.entity.ContentType.TEXT_HTML.getMimeType(); - + + protected static final XmlMapper XML_MAPPER = new XmlMapper(); + protected ByteBuffer body; protected int status = StatusCodes.OK; @@ -48,7 +48,9 @@ public class ServerResponse protected boolean hasCookies = false; protected boolean hasHeaders = false; protected boolean hasIoCallback = false; - + protected boolean isXml = false; + protected boolean isJson = false; + public ServerResponse() { @@ -120,6 +122,15 @@ public void setStatus(int status) public void setContentType(String contentType) { this.contentType = contentType; + + if(this.contentType.equals(MimeTypes.APPLICATION_JSON_TYPE)) + { + this.isJson = true; + } + else if(this.contentType.equals(MimeTypes.APPLICATION_XML_TYPE)) + { + this.isXml = true; + } } public ServerResponse body(ByteBuffer body) @@ -130,8 +141,7 @@ public ServerResponse body(ByteBuffer body) public ServerResponse entity(Object entity) { - this.entity = entity; - applicationJson(); + this.entity = entity; return this; } @@ -165,32 +175,35 @@ public ServerResponse cookie(String cookieName, Cookie cookie) public ServerResponse contentType(String contentType) { - this.contentType = contentType; + this.setContentType(contentType); return this; } public ServerResponse applicationJson() { - this.contentType = APPLICATION_JSON_CONTENT_TYPE; + this.contentType = MimeTypes.APPLICATION_JSON_TYPE; + this.isJson = true; + return this; } public ServerResponse textHtml() { - this.contentType = TEXT_HTML_CONTENT_TYPE; + this.contentType = MimeTypes.TEXT_HTML_TYPE; return this; } - public ServerResponse textXml() + public ServerResponse applicationXml() { - this.contentType = TEXT_XML_CONTENT_TYPE; + this.contentType = MimeTypes.APPLICATION_XML_TYPE; + this.isXml = true; return this; } public ServerResponse textPlain() { - this.contentType = TEXT_PLAIN_CONTENT_TYPE; + this.contentType = MimeTypes.TEXT_PLAIN_TYPE; return this; } @@ -265,8 +278,9 @@ public ServerResponse exception(Throwable t) return this.entity(Any.wrap(t)); } - public void send( final HttpHandler handler, final HttpServerExchange exchange ) + public void send( final HttpHandler handler, final HttpServerExchange exchange ) throws RuntimeException { + if( this.hasHeaders ) { long itr = this.headers.fastIterateNonEmpty(); @@ -292,8 +306,19 @@ public void send( final HttpHandler handler, final HttpServerExchange exchange ) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, this.contentType); } - - + else if( !this.isJson && !this.isXml ) + { + if( ServerPredicates.ACCEPT_JSON_PREDICATE.resolve(exchange) ) + { + this.applicationJson(); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, this.contentType); + } + else if( ServerPredicates.ACCEPT_XML_PREDICATE.resolve(exchange) ) + { + this.applicationXml(); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, this.contentType); + } + } if( this.body != null) { @@ -303,41 +328,49 @@ public void send( final HttpHandler handler, final HttpServerExchange exchange ) } else { - exchange.getResponseSender().send(this.body,this.ioCallback); - + exchange.getResponseSender().send(this.body,this.ioCallback); } } else if( this.entity != null) { - if(exchange.isInIoThread()) { - exchange.dispatch(handler); - return; - } - - - exchange.startBlocking(); - - final int bufferSize = exchange.getConnection().getBufferSize(); - - final JsonStream stream = new JsonStream(exchange.getOutputStream(), bufferSize); - - try + try { - stream.writeVal(this.entity); - stream.close(); - } catch (IOException e) + + if( this.isXml ) + { + exchange.getResponseSender().send(ByteBuffer.wrap(XML_MAPPER.writeValueAsBytes(this.entity))); + } + else + { + if(exchange.isInIoThread()) { + exchange.dispatch(handler); + return; + } + + + exchange.startBlocking(); + + final int bufferSize = exchange.getConnection().getBufferSize(); + + try(final JsonStream stream = new JsonStream(exchange.getOutputStream(), bufferSize)) + { + stream.writeVal(this.entity); + } + + exchange.endExchange(); + + } + + } catch (Exception e) { - - log.error(e.getMessage(),e); - exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); - exchange.getResponseSender().send(e.getMessage()); + log.error(e.getMessage() + " for entity " + this.entity,e); + + throw new IllegalArgumentException(e); } - exchange.endExchange(); - } + } else { exchange.endExchange(); diff --git a/src/main/java/com/wurrly/server/endpoints/EndpointInfo.java b/src/main/java/com/wurrly/server/endpoints/EndpointInfo.java index 26da81e..679812f 100644 --- a/src/main/java/com/wurrly/server/endpoints/EndpointInfo.java +++ b/src/main/java/com/wurrly/server/endpoints/EndpointInfo.java @@ -131,6 +131,10 @@ public String getControllerName() public void setControllerName(String controllerName) { this.controllerName = controllerName; + if(this.controllerName == null) + { + this.controllerName = ""; + } } @@ -162,7 +166,7 @@ public int compareTo(EndpointInfo other) { @Override public String toString() { - return String.format("%-8s %-30s %-26s %-26s %s", this.method, this.pathTemplate, "[" + this.consumes + "]", "[" + this.produces+ "]", "("+this.controllerMethod+ ")"); + return String.format("%-8s %-30s %-26s %-26s %s", this.method, this.pathTemplate, "[" + this.consumes + "]", "[" + this.produces+ "]", "("+this.controllerName+"."+this.controllerMethod+ ")"); } /** diff --git a/src/main/java/com/wurrly/server/handlers/HandlerGenerator.java b/src/main/java/com/wurrly/server/handlers/HandlerGenerator.java index 7c650ef..ab89bf8 100644 --- a/src/main/java/com/wurrly/server/handlers/HandlerGenerator.java +++ b/src/main/java/com/wurrly/server/handlers/HandlerGenerator.java @@ -11,9 +11,11 @@ import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletionException; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Matcher; @@ -45,7 +47,6 @@ import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; -import com.wurrly.modules.RoutingModule; import com.wurrly.server.Extractors; import com.wurrly.server.ServerRequest; import com.wurrly.server.ServerResponse; @@ -66,7 +67,7 @@ public class HandlerGenerator private static Logger log = LoggerFactory.getLogger(HandlerGenerator.class.getCanonicalName()); - private static final Pattern TYPE_NAME_PATTERN = Pattern.compile("(java\\.util\\.[A-Za-z]+)<([^>]+)", Pattern.DOTALL | Pattern.UNIX_LINES); + private static final Pattern TYPE_NAME_PATTERN = Pattern.compile("(java\\.util\\.(concurrent\\.)?[A-Za-z]+)<([^>]+)", Pattern.DOTALL | Pattern.UNIX_LINES); public enum StatementParameterType { @@ -83,8 +84,9 @@ public enum TypeHandler FilePathType("$T $L = $T.filePath(exchange,$S)", true, java.nio.file.Path.class, StatementParameterType.LITERAL,com.wurrly.server.Extractors.class, StatementParameterType.STRING), AnyType("$T $L = $T.any(exchange)", true, com.jsoniter.any.Any.class, StatementParameterType.LITERAL,com.wurrly.server.Extractors.class), JsonIteratorType("$T $L = $T.jsonIterator(exchange)", true, com.jsoniter.JsonIterator.class, StatementParameterType.LITERAL,com.wurrly.server.Extractors.class), - ModelType("$T $L = com.wurrly.server.Extractors.typed(exchange,$L)", true, StatementParameterType.TYPE, StatementParameterType.LITERAL, StatementParameterType.LITERAL), - EnumType("$T $L = $T.enumValue(exchange,$T.class,$S)", true, StatementParameterType.TYPE, StatementParameterType.LITERAL,com.wurrly.server.Extractors.class, StatementParameterType.TYPE, StatementParameterType.STRING), + ModelType("$T $L = com.wurrly.server.Extractors.model(exchange,$L)", true, StatementParameterType.TYPE, StatementParameterType.LITERAL, StatementParameterType.LITERAL), + + //EnumType("$T $L = $T.enumValue(exchange,$T.class,$S)", true, StatementParameterType.TYPE, StatementParameterType.LITERAL,com.wurrly.server.Extractors.class, StatementParameterType.TYPE, StatementParameterType.STRING), ByteBufferType("$T $L = $T.fileBytes(exchange,$S)", false, java.nio.ByteBuffer.class, StatementParameterType.LITERAL,com.wurrly.server.Extractors.class, StatementParameterType.STRING), DateType("$T $L = $T.date(exchange,$S)", false, java.util.Date.class, StatementParameterType.LITERAL, com.wurrly.server.Extractors.class, StatementParameterType.STRING), FloatType("Integer $L = $T.floatValue(exchange,$S)", false, StatementParameterType.LITERAL, com.wurrly.server.Extractors.class, StatementParameterType.STRING), @@ -117,7 +119,7 @@ public enum TypeHandler OptionalDateType("$T<$T> $L = $T.date(exchange,$S)", false, Optional.class, java.util.Date.class, StatementParameterType.LITERAL, com.wurrly.server.Extractors.Optional.class, StatementParameterType.STRING), - OptionalModelType("java.util.Optional<$L> $L = $T.typed(exchange,$L)", true, StatementParameterType.LITERAL, StatementParameterType.LITERAL, com.wurrly.server.Extractors.Optional.class, StatementParameterType.LITERAL), + OptionalModelType("java.util.Optional<$L> $L = $T.model(exchange,$L)", true, StatementParameterType.LITERAL, StatementParameterType.LITERAL, com.wurrly.server.Extractors.Optional.class, StatementParameterType.LITERAL), OptionalValueOfType("$T<$T> $L = $T.string(exchange,$S).map($T::valueOf)", false, Optional.class, StatementParameterType.RAW, StatementParameterType.LITERAL,com.wurrly.server.Extractors.Optional.class, StatementParameterType.STRING, StatementParameterType.RAW), OptionalFromStringType("$T<$T> $L = $T.string(exchange,$S).map($T::fromString)", false, Optional.class, StatementParameterType.RAW, StatementParameterType.LITERAL, com.wurrly.server.Extractors.Optional.class, StatementParameterType.STRING, StatementParameterType.RAW), @@ -230,6 +232,8 @@ public static void addStatement(MethodSpec.Builder builder, Parameter parameter) public static TypeHandler forType(Type type) { + log.debug("forType " + type); + boolean hasValueOf = false; boolean hasFromString = false; boolean isOptional = type.getTypeName().contains("java.util.Optional"); @@ -343,7 +347,7 @@ else if (type.getTypeName().contains("java.nio.file.Path")) Class erasedType = extractErasedType(type); - //log.debug("erasedType: " + erasedType.getTypeName() + " valueOf: " + hasValueOfMethod(erasedType) + " fromString: " + hasFromStringMethod(erasedType)); + log.debug("erasedType: " + erasedType + " for " + type); if( hasValueOfMethod(erasedType) ) { @@ -490,19 +494,21 @@ protected void generateRoutes() } } - public void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class clazz) + public void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class clazz) throws Exception { ClassName httpHandlerClass = ClassName.get("io.undertow.server", "HttpHandler"); String controllerName = clazz.getSimpleName().toLowerCase(); + + MethodSpec.Builder initBuilder = MethodSpec.methodBuilder("get").addModifiers(Modifier.PUBLIC).returns(RoutingHandler.class).addStatement("final $T router = new $T()", io.undertow.server.RoutingHandler.class, io.undertow.server.RoutingHandler.class); final Map typeLiteralsMap = Arrays.stream(clazz.getDeclaredMethods()) .flatMap(m -> Arrays.stream(m.getParameters()).map(Parameter::getParameterizedType) - .filter(t -> t.getTypeName().contains("<"))) + .filter(t -> t.getTypeName().contains("<") && !t.getTypeName().contains("concurrent"))) .distinct().filter(t -> { - TypeHandler handler = TypeHandler.forType(t); + TypeHandler handler = TypeHandler.forType(t); return (handler.equals(TypeHandler.ModelType) || handler.equals(TypeHandler.OptionalModelType)); }).collect(Collectors.toMap(java.util.function.Function.identity(), HandlerGenerator::typeLiteralNameForType)); @@ -511,12 +517,65 @@ public void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class clazz) typeLiteralsMap.forEach((t, n) -> initBuilder.addStatement("final $T<$L> $LType = new $T<$L>(){}", TypeLiteral.class, t, n, TypeLiteral.class, t)); initBuilder.addCode("$L", "\n"); + + + List consumesContentTypes = new ArrayList<>(); + List producesContentTypes = new ArrayList<>(); for (Method m : clazz.getDeclaredMethods()) { - EndpointInfo route = new EndpointInfo(); + EndpointInfo endpointInfo = new EndpointInfo(); + + String producesContentType = "*/*"; - route.setControllerName(clazz.getSimpleName()); + Optional producesAnnotation = Optional.ofNullable(m.getAnnotation(javax.ws.rs.Produces.class)); + + if (!producesAnnotation.isPresent()) + { + producesAnnotation = Optional.ofNullable(clazz.getAnnotation(javax.ws.rs.Produces.class)); + + if (producesAnnotation.isPresent()) + { + + producesContentTypes = Arrays.stream(producesAnnotation.get().value()).flatMap( v -> Arrays.stream((v.split(",")))).collect(Collectors.toList()); + + producesContentType = producesContentTypes.stream().collect(Collectors.joining(",")); + } + + } + else + { + producesContentTypes = Arrays.stream(producesAnnotation.get().value()).flatMap( v -> Arrays.stream((v.split(",")))).collect(Collectors.toList()); + + producesContentType = producesContentTypes.stream().collect(Collectors.joining(",")); + } + + endpointInfo.setProduces(producesContentType); + + String consumesContentType = "*/*"; + + Optional consumesAnnotation = Optional.ofNullable(m.getAnnotation(javax.ws.rs.Consumes.class)); + + if (!consumesAnnotation.isPresent()) + { + consumesAnnotation = Optional.ofNullable(clazz.getAnnotation(javax.ws.rs.Consumes.class)); + + if (consumesAnnotation.isPresent()) + { + consumesContentTypes = Arrays.stream(consumesAnnotation.get().value()).flatMap( v -> Arrays.stream((v.split(",")))).collect(Collectors.toList()); + + consumesContentType = consumesContentTypes.stream().collect(Collectors.joining(",")); + } + } + else + { + consumesContentTypes = Arrays.stream(consumesAnnotation.get().value()).flatMap( v -> Arrays.stream((v.split(",")))).collect(Collectors.toList()); + + consumesContentType = consumesContentTypes.stream().collect(Collectors.joining(",")); + } + + + endpointInfo.setControllerName(clazz.getSimpleName()); String methodPath = Extractors.pathTemplateFromMethod.apply(m).replaceAll("\\/\\/", "\\/"); @@ -525,11 +584,11 @@ public void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class clazz) HttpString httpMethod = Extractors.httpMethodFromMethod.apply(m); - route.setMethod(httpMethod); + endpointInfo.setMethod(httpMethod); - route.setPathTemplate(methodPath); + endpointInfo.setPathTemplate(methodPath); - route.setControllerMethod(clazz.getSimpleName() + "." + m.getName()); + endpointInfo.setControllerMethod( m.getName()); String methodName = String.format("%c%s%sHandler", Character.toLowerCase(clazz.getSimpleName().charAt(0)), clazz.getSimpleName().substring(1, clazz.getSimpleName().length()), StringUtils.capitalize(m.getName())); @@ -590,13 +649,10 @@ public void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class clazz) { if (p.isAnnotationPresent(HeaderParam.class)) { - - log.debug("header class: " + type); TypeHandler handler = TypeHandler.forType(type); - - log.debug("header typehandler: " + handler); + if( handler.equals(TypeHandler.OptionalStringType) ) { @@ -608,41 +664,31 @@ public void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class clazz) else if( handler.equals(TypeHandler.OptionalValueOfType) ) { handler = TypeHandler.OptionalHeaderValueOfType; - - log.debug("header typehandler: " + handler.statement); - + TypeHandler.addStatement(methodBuilder, p,handler); } else if( handler.equals(TypeHandler.OptionalFromStringType) ) { - handler = TypeHandler.OptionalHeaderFromStringType; - log.debug("header typehandler: " + handler.statement); - + handler = TypeHandler.OptionalHeaderFromStringType; TypeHandler.addStatement(methodBuilder, p,handler); } else if( handler.equals(TypeHandler.StringType) ) { - handler = TypeHandler.HeaderStringType; - log.debug("header typehandler: " + handler.statement); - + handler = TypeHandler.HeaderStringType; TypeHandler.addStatement(methodBuilder, p,handler); } else if( handler.equals(TypeHandler.ValueOfType) ) { - handler = TypeHandler.HeaderValueOfType; - log.debug("header typehandler: " + handler.statement); - + handler = TypeHandler.HeaderValueOfType; TypeHandler.addStatement(methodBuilder, p,handler); } else if( handler.equals(TypeHandler.FromStringType) ) { - handler = TypeHandler.HeaderFromStringType; - log.debug("header typehandler: " + handler.statement); - + handler = TypeHandler.HeaderFromStringType; TypeHandler.addStatement(methodBuilder, p,handler); } @@ -671,7 +717,6 @@ else if( handler.equals(TypeHandler.FromStringType) ) } else if (t.equals(TypeHandler.OptionalFromStringType) || t.equals(TypeHandler.OptionalValueOfType)) { - Class erasedType = extractErasedType(p.getParameterizedType()); TypeHandler.addStatement(methodBuilder,p); } @@ -697,54 +742,26 @@ else if (t.equals(TypeHandler.OptionalFromStringType) || t.equals(TypeHandler.Op if (!m.getReturnType().equals(Void.class)) { - // log.debug("return : " + m.getReturnType()); - functionBlockBuilder.add("$T $L = $L.$L($L);", m.getReturnType(), "response", controllerName, m.getName(), controllerMethodArgs); + if (m.getReturnType().getTypeName().contains("java.util.concurrent.CompletionStage") || m.getReturnType().getTypeName().contains("java.util.concurrent.CompletableFuture")) + { + Type futureType = m.getGenericReturnType(); + + functionBlockBuilder.add("$T $L = $L.$L($L);", futureType, "response", controllerName, m.getName(), controllerMethodArgs); + } + else + { + functionBlockBuilder.add("$T $L = $L.$L($L);", m.getReturnType(), "response", controllerName, m.getName(), controllerMethodArgs); + } } methodBuilder.addCode(functionBlockBuilder.build()); methodBuilder.addCode("$L", "\n"); - String producesContentType = "*/*"; - - Optional producesAnnotation = Optional.ofNullable(m.getAnnotation(javax.ws.rs.Produces.class)); - - if (!producesAnnotation.isPresent()) - { - producesAnnotation = Optional.ofNullable(clazz.getAnnotation(javax.ws.rs.Produces.class)); - - if (producesAnnotation.isPresent()) - { - producesContentType = producesAnnotation.get().value()[0]; - } - } - else - { - producesContentType = producesAnnotation.get().value()[0]; - } - - route.setProduces(producesContentType); - - String consumesContentType = "*/*"; - - Optional consumesAnnotation = Optional.ofNullable(m.getAnnotation(javax.ws.rs.Consumes.class)); - - if (!consumesAnnotation.isPresent()) - { - consumesAnnotation = Optional.ofNullable(clazz.getAnnotation(javax.ws.rs.Consumes.class)); - - if (consumesAnnotation.isPresent()) - { - consumesContentType = consumesAnnotation.get().value()[0]; - } - } - else - { - consumesContentType = consumesAnnotation.get().value()[0]; - } + - route.setConsumes(consumesContentType); + endpointInfo.setConsumes(consumesContentType); methodBuilder.addCode("$L", "\n"); @@ -753,6 +770,11 @@ else if (t.equals(TypeHandler.OptionalFromStringType) || t.equals(TypeHandler.Op methodBuilder.addStatement("$L.send(this,$L)","response","exchange"); } + else if( m.getReturnType().getTypeName().contains("java.util.concurrent.CompletionStage") || m.getReturnType().getTypeName().contains("java.util.concurrent.CompletableFuture") ) + { + + methodBuilder.addStatement("$L.thenAccept( r -> r.send(this,$L) ).exceptionally( ex -> { throw new java.util.concurrent.CompletionException(ex); } );","response","exchange"); + } else { @@ -782,7 +804,7 @@ else if (t.equals(TypeHandler.OptionalFromStringType) || t.equals(TypeHandler.Op initBuilder.addCode("$L", "\n"); - registeredEndpoints.add(route); + registeredEndpoints.add(endpointInfo); } @@ -900,6 +922,30 @@ public static Class extractErasedType(Type type) throws Exception String clearDollarType = erasedType.replaceAll("\\$", "."); + log.debug("erasedType: " + erasedType); + log.debug("clearDollarType: " + clearDollarType); + + try + { + return Class.forName(clearDollarType); + + } catch (Exception e) + { + return Class.forName(erasedType); + } + + + } + else if (matches > 2) + { + + String erasedType = matcher.group(3); + + String clearDollarType = erasedType.replaceAll("\\$", "."); + +// log.debug("erasedType: " + erasedType); +// log.debug("clearDollarType: " + clearDollarType); + try { return Class.forName(clearDollarType); @@ -913,6 +959,10 @@ public static Class extractErasedType(Type type) throws Exception } } + else + { + log.warn("No type found for " + typeName); + } return null; } @@ -932,6 +982,9 @@ public static String typeLiteralNameForType(Type type) { String genericInterface = matcher.group(1); String erasedType = matcher.group(2).replaceAll("\\$", "."); + +// log.debug("genericInterface: " + genericInterface); +// log.debug("erasedType: " + erasedType); String[] genericParts = genericInterface.split("\\."); String[] erasedParts = erasedType.split("\\."); @@ -972,6 +1025,7 @@ public static void generateParameterReference(MethodSpec.Builder builder, Class< public static boolean hasValueOfMethod(Class clazz) { + log.debug("valueMethod: " + clazz); return Arrays.stream(clazz.getMethods()).filter( m -> m.getName().equals("valueOf")).findFirst().isPresent(); } diff --git a/src/main/java/com/wurrly/server/handlers/ServerDefaultResponseListener.java b/src/main/java/com/wurrly/server/handlers/ServerDefaultResponseListener.java new file mode 100644 index 0000000..56e35a9 --- /dev/null +++ b/src/main/java/com/wurrly/server/handlers/ServerDefaultResponseListener.java @@ -0,0 +1,95 @@ +/** + * + */ +package com.wurrly.server.handlers; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.jsoniter.output.JsonStream; +import com.wurrly.server.MimeTypes; +import com.wurrly.server.ServerPredicates; + +import io.undertow.server.DefaultResponseListener; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; + +/** + * @author jbauer + * + */ +@Singleton +public class ServerDefaultResponseListener implements DefaultResponseListener +{ + private static Logger log = LoggerFactory.getLogger(ServerDefaultResponseListener.class.getCanonicalName()); + + @Inject + protected XmlMapper xmlMapper; + + @Override + public boolean handleDefaultResponse(HttpServerExchange exchange) + { + if (!exchange.isResponseChannelAvailable()) { + return false; + } + + if (exchange.getStatusCode() == 500) { + + Throwable throwable = exchange.getAttachment(DefaultResponseListener.EXCEPTION); + + if( throwable == null ) + { + throwable = new Exception("An unknown error occured"); + } + + Map errorMap = new HashMap<>(); + + errorMap.put("message", throwable.getMessage()); + + if( throwable.getStackTrace() != null ) + { + if( throwable.getStackTrace().length > 0 ) + { + errorMap.put("exceptionClass", throwable.getStackTrace()[0].getClassName()); + } + } + + if( ServerPredicates.ACCEPT_XML_PREDICATE.resolve(exchange) ) + { + try + { + + final String xmlBody = xmlMapper.writeValueAsString(errorMap); + exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, xmlBody.length()); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MimeTypes.APPLICATION_XML_TYPE); + exchange.getResponseSender().send(xmlBody); + + } catch (JsonProcessingException e) + { + log.warn("Unable to create XML from error..."); + } + + } + else + { + final String jsonBody = JsonStream.serialize(errorMap); + + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MimeTypes.APPLICATION_JSON_TYPE); + exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, jsonBody.length()); + exchange.getResponseSender().send(jsonBody); + } + + return true; + } + return false; + } + +} diff --git a/src/main/java/com/wurrly/server/handlers/benchmark/BenchmarkHandlers.java b/src/main/java/com/wurrly/server/handlers/benchmark/BenchmarkHandlers.java index 898dec6..fa37337 100644 --- a/src/main/java/com/wurrly/server/handlers/benchmark/BenchmarkHandlers.java +++ b/src/main/java/com/wurrly/server/handlers/benchmark/BenchmarkHandlers.java @@ -21,6 +21,8 @@ import com.jsoniter.output.JsonStream; import com.wurrly.server.ServerResponse; +import io.undertow.attribute.ExchangeAttributes; +import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.RoutingHandler; @@ -52,7 +54,7 @@ public RoutingHandler get() final RoutingHandler handler = new RoutingHandler(); - handler.add(Methods.GET, "/string".intern(), new HttpHandler(){ + handler.add(Methods.GET, "/".intern(), new HttpHandler(){ @Override public void handleRequest(HttpServerExchange exchange) throws Exception @@ -76,21 +78,7 @@ public void handleRequest(HttpServerExchange exchange) throws Exception } } ); - handler.add(Methods.GET, "/string3".intern(), new HttpHandler(){ - - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception - { - // TODO Auto-generated method stub - - exchange.setStatusCode( 200 ); - - //exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, PLAIN_TEXT); - - exchange.getResponseSender().send(msgBuffer); - - } - } ); + handler.add(Methods.GET, "/string4".intern(), new HttpHandler(){ @@ -293,6 +281,24 @@ public void handleRequest(HttpServerExchange exchange) throws Exception } } ); + + + + handler.add(Methods.GET, "/string3".intern(), new HttpHandler(){ + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception + { + // TODO Auto-generated method stub + + exchange.setStatusCode( 200 ); + + //exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, PLAIN_TEXT); + + exchange.getResponseSender().send(msgBuffer); + + } + } ); diff --git a/src/main/java/com/wurrly/server/handlers/predicates/MaxRequestContentLengthPredicate.java b/src/main/java/com/wurrly/server/handlers/predicates/MaxRequestContentLengthPredicate.java new file mode 100644 index 0000000..2234a60 --- /dev/null +++ b/src/main/java/com/wurrly/server/handlers/predicates/MaxRequestContentLengthPredicate.java @@ -0,0 +1,65 @@ +/** + * + */ +package com.wurrly.server.handlers.predicates; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import io.undertow.predicate.Predicate; +import io.undertow.predicate.PredicateBuilder; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; + +/** + * @author jbauer + * + */ +public class MaxRequestContentLengthPredicate implements Predicate +{ + private final long maxSize; + + MaxRequestContentLengthPredicate(final long maxSize) { + this.maxSize = maxSize; + } + + @Override + public boolean resolve(final HttpServerExchange value) { + final String length = value.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); + if (length == null) { + return false; + } + return Long.parseLong(length) > maxSize; + } + + public static class Builder implements PredicateBuilder { + + @Override + public String name() { + return "max-content-size"; + } + + @Override + public Map> parameters() { + return Collections.>singletonMap("value", Long.class); + } + + + @Override + public Set requiredParameters() { + return Collections.singleton("value"); + } + + @Override + public String defaultParameter() { + return "value"; + } + + @Override + public Predicate build(final Map config) { + Long max = (Long) config.get("value"); + return new MaxRequestContentLengthPredicate(max); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/wurrly/server/listeners/ServerDefaultResponseListener.java b/src/main/java/com/wurrly/server/listeners/ServerDefaultResponseListener.java deleted file mode 100644 index 4bf1f1d..0000000 --- a/src/main/java/com/wurrly/server/listeners/ServerDefaultResponseListener.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * - */ -package com.wurrly.server.listeners; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.ImmutableMap; -import com.jsoniter.output.JsonStream; - -import io.undertow.server.DefaultResponseListener; -import io.undertow.server.HttpServerExchange; -import io.undertow.util.Headers; - -/** - * @author jbauer - * - */ -public class ServerDefaultResponseListener implements DefaultResponseListener -{ - - - @Override - public boolean handleDefaultResponse(HttpServerExchange exchange) - { - if (!exchange.isResponseChannelAvailable()) { - return false; - } - - if (exchange.getStatusCode() == 500) { - - - Throwable throwable = exchange.getAttachment(DefaultResponseListener.EXCEPTION); - - if( throwable == null ) - { - throwable = new Exception("An unknown error occured"); - } - - final String jsonBody = JsonStream.serialize(throwable); - - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, org.apache.http.entity.ContentType.APPLICATION_JSON.getMimeType()); - exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, jsonBody.length()); - exchange.getResponseSender().send(jsonBody); - - return true; - } - return false; - } - -} diff --git a/src/main/java/com/wurrly/server/swagger/Reader.java b/src/main/java/com/wurrly/server/swagger/Reader.java index f847139..6d08a4f 100644 --- a/src/main/java/com/wurrly/server/swagger/Reader.java +++ b/src/main/java/com/wurrly/server/swagger/Reader.java @@ -35,6 +35,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; +import com.fasterxml.jackson.databind.type.SimpleType; import com.fasterxml.jackson.databind.type.TypeFactory; import com.wurrly.server.ServerRequest; @@ -1062,9 +1063,13 @@ private List getParameters(Type type, List annotations, j LOGGER.debug("getParameters for {}", type); Set typesToSkip = new HashSet<>(); - // typesToSkip.add(ServerRequest.class); + typesToSkip.add(TypeFactory.defaultInstance().constructType(ServerRequest.class)); final SwaggerExtension extension = chain.next(); + if (typesToSkip.contains(type)) { + return Collections.emptyList(); + } + annotations = new ArrayList<>(annotations); diff --git a/src/main/java/com/wurrly/server/swagger/ServerParameterExtension.java b/src/main/java/com/wurrly/server/swagger/ServerParameterExtension.java index 5856806..10c1520 100644 --- a/src/main/java/com/wurrly/server/swagger/ServerParameterExtension.java +++ b/src/main/java/com/wurrly/server/swagger/ServerParameterExtension.java @@ -37,8 +37,7 @@ public ServerParameterExtension() @Override public List extractParameters(List annotations, Type type, Set typesToSkip, Iterator chain) { - log.debug("extractParameters: " + type); - + if(type.getTypeName().contains("java.nio.ByteBuffer") || type.getTypeName().contains("java.nio.file.Path")) { type = java.io.File.class; @@ -53,8 +52,8 @@ public List extractParameters(List annotations, Type type @Override protected boolean shouldIgnoreType(Type type, Set typesToSkip) { - - if( type.getTypeName().contains("com.wurrly.server.ServerRequest")) + + if( type.getTypeName().contains("ServerRequest")) { return true; } diff --git a/src/main/java/com/wurrly/services/AssetsService.java b/src/main/java/com/wurrly/services/AssetsService.java new file mode 100644 index 0000000..4f84742 --- /dev/null +++ b/src/main/java/com/wurrly/services/AssetsService.java @@ -0,0 +1,75 @@ +/** + * + */ +package com.wurrly.services; + +import java.nio.file.Paths; +import java.util.Set; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import com.typesafe.config.Config; +import com.wurrly.server.endpoints.EndpointInfo; + +import io.undertow.predicate.TruePredicate; +import io.undertow.server.RoutingHandler; +import io.undertow.server.handlers.resource.FileResourceManager; +import io.undertow.server.handlers.resource.ResourceHandler; +import io.undertow.util.Methods; + +/** + * @author jbauer + * + */ +public class AssetsService extends BaseService +{ + + @Inject + @Named("registeredEndpoints") + protected Set registeredEndpoints; + + @Inject + protected RoutingHandler rootHandler; + + @Inject + @Named("assets") + protected Config serviceConfig; + /** + * + */ + public AssetsService() + { + // TODO Auto-generated constructor stub + } + + + @Override + protected void startUp() throws Exception + { + super.startUp(); + + final String assetsPath = serviceConfig.getString("path"); + final String assetsDirectoryName = serviceConfig.getString("dir") ; + final Integer assetsCacheTime = serviceConfig.getInt("cache.time"); + + final FileResourceManager fileResourceManager = new FileResourceManager(Paths.get(assetsDirectoryName).toFile()); + + + rootHandler.add(Methods.GET, assetsPath + "/*", io.undertow.Handlers.rewrite("regex['" + assetsPath + "/(.*)']", "/$1", getClass().getClassLoader(), new ResourceHandler(fileResourceManager) + .setCachable(TruePredicate.instance()) + .setCacheTime(assetsCacheTime) + )); + + this.registeredEndpoints.add(EndpointInfo.builder().withConsumes("*/*").withProduces("*/*").withPathTemplate(assetsPath).withControllerName("Assets").withMethod(Methods.GET).build()); + + } + + + @Override + protected void shutDown() throws Exception + { + super.shutDown(); + + } + +} diff --git a/src/main/java/com/wurrly/services/BaseService.java b/src/main/java/com/wurrly/services/BaseService.java new file mode 100644 index 0000000..192985f --- /dev/null +++ b/src/main/java/com/wurrly/services/BaseService.java @@ -0,0 +1,56 @@ +/** + * + */ +package com.wurrly.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.util.concurrent.AbstractIdleService; +import com.google.inject.Inject; +import com.typesafe.config.Config; +import com.wurrly.modules.ConfigModule; + +/** + * @author jbauer + * + */ +public class BaseService extends AbstractIdleService +{ + private static Logger log = LoggerFactory.getLogger(BaseService.class.getCanonicalName()); + + /* (non-Javadoc) + * @see com.google.inject.AbstractModule#configure() + */ + + @Inject + protected Config config; + + + public BaseService() + { + + } + + + /* (non-Javadoc) + * @see com.google.common.util.concurrent.AbstractIdleService#startUp() + */ + @Override + protected void startUp() throws Exception + { + log.info("Starting " + this.getClass().getSimpleName() ); + } + + + /* (non-Javadoc) + * @see com.google.common.util.concurrent.AbstractIdleService#shutDown() + */ + @Override + protected void shutDown() throws Exception + { + log.info("Stopping " + this.getClass().getSimpleName() ); + } + + +} diff --git a/src/main/java/com/wurrly/services/ConfigurableService.java b/src/main/java/com/wurrly/services/ConfigurableService.java deleted file mode 100644 index cd1d906..0000000 --- a/src/main/java/com/wurrly/services/ConfigurableService.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * - */ -package com.wurrly.services; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.util.concurrent.AbstractIdleService; -import com.google.inject.Inject; -import com.wurrly.modules.ConfigModule; - -/** - * @author jbauer - * - */ -public abstract class ConfigurableService extends AbstractIdleService -{ - private static Logger log = LoggerFactory.getLogger(ConfigurableService.class.getCanonicalName()); - - /* (non-Javadoc) - * @see com.google.inject.AbstractModule#configure() - */ - - protected String configFile; - - @Inject - protected ConfigModule configModule; - - public ConfigurableService() - { - - } - - - protected void configure() - { - log.debug("Configuring : " + this.getClass().getSimpleName()); - - if( this.configFile != null ) - { - configModule.bindFileConfig(this.configFile); - } - - } - - - -} diff --git a/src/main/java/com/wurrly/services/SwaggerService.java b/src/main/java/com/wurrly/services/SwaggerService.java index ff90aac..dbef59b 100644 --- a/src/main/java/com/wurrly/services/SwaggerService.java +++ b/src/main/java/com/wurrly/services/SwaggerService.java @@ -1,10 +1,8 @@ package com.wurrly.services; -import java.io.File; import java.io.StringWriter; import java.io.Writer; -import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -19,13 +17,11 @@ import com.google.inject.Inject; import com.google.inject.name.Named; -import com.j256.simplemagic.ContentType; import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.template.PebbleTemplate; import com.typesafe.config.Config; -import com.wurrly.modules.RoutingModule; +import com.wurrly.server.MimeTypes; import com.wurrly.server.endpoints.EndpointInfo; -import com.wurrly.server.handlers.HandlerGenerator; import com.wurrly.server.swagger.ServerParameterExtension; import com.wurrly.utilities.JsonMapper; @@ -37,17 +33,17 @@ import io.undertow.server.HttpServerExchange; import io.undertow.server.RoutingHandler; import io.undertow.server.handlers.resource.ClassPathResourceManager; -import io.undertow.server.handlers.resource.Resource; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.util.CanonicalPathUtils; import io.undertow.util.Headers; import io.undertow.util.Methods; -import com.j256.simplemagic.ContentType; - -public class SwaggerService extends ConfigurableService implements Supplier +public class SwaggerService extends BaseService implements Supplier { + + + private static Logger log = LoggerFactory.getLogger(SwaggerService.class.getCanonicalName()); protected com.wurrly.server.swagger.Reader reader = null; @@ -58,9 +54,9 @@ public class SwaggerService extends ConfigurableService implements Supplier> registeredControllers; - public SwaggerService() - { - this.configFile = "swagger.conf"; - } - + /** + * @param config + */ + public SwaggerService( ) + { + + } + + public void generateSwaggerSpec() { @@ -165,14 +165,7 @@ public void generateSwaggerSpec() } - @Override - protected void configure() - { - // log.debug("Configuring : " + this.getClass().getSimpleName() + " swaggerBasePath: " + swaggerBasePath); - - - super.configure(); - } + /** * @return the swagger @@ -242,7 +235,7 @@ public RoutingHandler get() public void handleRequest(HttpServerExchange exchange) throws Exception { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ContentType.JSON.getMimeType()); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MimeTypes.APPLICATION_JSON_TYPE); exchange.getResponseSender().send(swaggerSpec); @@ -252,7 +245,7 @@ public void handleRequest(HttpServerExchange exchange) throws Exception }); - this.registeredEndpoints.add(EndpointInfo.builder().withConsumes("*/*").withPathTemplate(pathTemplate).withControllerName("Swagger").withMethod(Methods.GET).withProduces(ContentType.JSON.getMimeType()).build()); + this.registeredEndpoints.add(EndpointInfo.builder().withConsumes("*/*").withPathTemplate(pathTemplate).withControllerName("Swagger").withMethod(Methods.GET).withProduces(MimeTypes.APPLICATION_JSON_TYPE).build()); pathTemplate = this.swaggerBasePath; @@ -264,7 +257,7 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ContentType.HTML.getMimeType()); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MimeTypes.TEXT_HTML_TYPE); exchange.getResponseSender().send(swaggerIndexHTML); } diff --git a/src/main/java/com/wurrly/tests/TestTypes.java b/src/main/java/com/wurrly/tests/TestTypes.java new file mode 100644 index 0000000..f4e69e6 --- /dev/null +++ b/src/main/java/com/wurrly/tests/TestTypes.java @@ -0,0 +1,127 @@ +/** + * + */ +package com.wurrly.tests; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +import com.wurrly.server.ServerResponse; + +/** + * @author jbauer + * + */ +public class TestTypes +{ + + interface ServiceCall + { + CompletableFuture invoke(T request); + } + + public static interface FutureSupplier extends ServiceCall + { + default CompletableFuture invoke() + { + return completedFutureSupplier.get(); //CompletableFuture.completedFuture(new ServerResponse()); + } + + public CompletableFuture invoke( CompletableFuture response ); + } + + + + public static class RestCaller implements FutureSupplier + { + + public RestCaller( UnaryOperator> initializer ) + { + initializer.apply(this.invoke()); + } + + @Override + public CompletableFuture invoke(CompletableFuture response) + { + // TODO Auto-generated method stub + return response; + } + + /* (non-Javadoc) + * @see com.wurrly.tests.TestTypes.ServiceCall#invoke(java.lang.Object) + */ + @Override + public CompletableFuture invoke(ServerResponse request) + { + // TODO Auto-generated method stub + return null; + } + + } + + private static Supplier> completedFutureSupplier = () -> + { + return CompletableFuture.completedFuture(new ServerResponse()); + }; + /** + * @param args + */ + public static void main(String[] args) + { + // TODO Auto-generated method stub + try + { + + + ServiceCall res = processRequest(); + + + + + CompletableFuture response = (res.invoke(new ServerResponse())); + + response.thenAccept( r -> + { + + r.send(null, null); + + + }).exceptionally( ex -> { throw new CompletionException(ex); }); + + + CompletableFuture future3 = CompletableFuture.supplyAsync(() -> "test"); + future3.thenAccept(t -> { + throw new RuntimeException(); + }).exceptionally(t -> { + t.printStackTrace(); + throw new CompletionException(t); + }); + + //FutureSupplier supplier = processRequest2(); + + + + } catch (Exception e) + { + e.printStackTrace(); + } + + } + + public static ServiceCall processRequest() + { + return (response) -> { + + System.out.println(response); + return CompletableFuture.completedFuture(response); + + }; + } + + + +}