diff --git a/pippo-core/src/main/java/ro/pippo/core/Application.java b/pippo-core/src/main/java/ro/pippo/core/Application.java index de5e8d6f..421af8be 100644 --- a/pippo-core/src/main/java/ro/pippo/core/Application.java +++ b/pippo-core/src/main/java/ro/pippo/core/Application.java @@ -17,6 +17,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ro.pippo.core.entity.DefaultEntityRequestEngine; +import ro.pippo.core.entity.EntityRequestEngine; import ro.pippo.core.gzip.GZipRequestResponseFactory; import ro.pippo.core.route.DefaultRouter; import ro.pippo.core.route.ResourceRouting; @@ -59,6 +61,7 @@ public class Application implements ResourceRouting { private HttpCacheToolkit httpCacheToolkit; private TemplateEngine templateEngine; private ContentTypeEngines engines; + private EntityRequestEngine entityRequestEngine; protected Router router; private ErrorHandler errorHandler; private RequestResponseFactory requestResponseFactory; @@ -218,6 +221,18 @@ public ContentTypeEngine getContentTypeEngine(String contentType) { return engines.getContentTypeEngine(contentType); } + public EntityRequestEngine getEntityRequestEngine() { + if (entityRequestEngine == null) { + entityRequestEngine = new DefaultEntityRequestEngine(getContentTypeEngines()); + } + + return entityRequestEngine; + } + + public void setEntityRequestEngine(EntityRequestEngine entityRequestEngine) { + this.entityRequestEngine = entityRequestEngine; + } + public Router getRouter() { if (router == null) { router = new DefaultRouter(); diff --git a/pippo-core/src/main/java/ro/pippo/core/Request.java b/pippo-core/src/main/java/ro/pippo/core/Request.java index c331b210..daa7514b 100644 --- a/pippo-core/src/main/java/ro/pippo/core/Request.java +++ b/pippo-core/src/main/java/ro/pippo/core/Request.java @@ -17,11 +17,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import ro.pippo.core.converters.Converter; +import ro.pippo.core.entity.EntityRequestEngine; import ro.pippo.core.route.RouteContext; import ro.pippo.core.route.RouteDispatcher; -import ro.pippo.core.util.ClassUtils; import ro.pippo.core.util.CookieUtils; import ro.pippo.core.util.IoUtils; import ro.pippo.core.util.StringUtils; @@ -30,9 +28,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.servlet.http.Part; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.net.URI; import java.util.Collection; import java.util.Collections; @@ -56,6 +51,7 @@ public final class Request { private HttpServletRequest httpServletRequest; private ContentTypeEngines contentTypeEngines; + private EntityRequestEngine entityRequestEngine; private Map parameters; // query&post parameters private Map pathParameters; // path parameters private Map allParameters; // parameters + pathParameters @@ -72,6 +68,7 @@ public final class Request { public Request(HttpServletRequest servletRequest, Application application) { this.httpServletRequest = servletRequest; this.contentTypeEngines = application.getContentTypeEngines(); + this.entityRequestEngine = application.getEntityRequestEngine(); applicationPath = application.getRouter().getApplicationPath(); @@ -227,121 +224,28 @@ public void internalSetPathParameters(Map pathParameters) { initAllParameters(); } + /** + * Forward to {@link EntityRequestEngine::createEntityFromParameters}. + */ + @Deprecated public T createEntityFromParameters(Class entityClass) { - T entity; - try { - entity = entityClass.newInstance(); - } catch (Exception e) { - log.error("Cannot create new instance of class '{}'", entityClass.getName(), e); - return null; - } - - updateEntityFromParameters(entity); - - return entity; + return entityRequestEngine.createEntityFromParameters(entityClass, this); } - @SuppressWarnings("unchecked") + /** + * Forward to {@link EntityRequestEngine::updateEntityFromParameters}. + */ + @Deprecated public T updateEntityFromParameters(T entity) { - for (Field field : ClassUtils.getAllFields(entity.getClass())) { - String parameterName = field.getName(); - ParamField parameter = field.getAnnotation(ParamField.class); - if (parameter != null) { - parameterName = parameter.value(); - } - - if (getParameters().containsKey(parameterName)) { - if (!field.isAccessible()) { - field.setAccessible(true); - } - - String pattern = (parameter != null) ? parameter.pattern() : null; - Class converterClass = (parameter != null && void.class != parameter.converterClass()) ? parameter.converterClass() : null; - - try { - Class fieldClass = field.getType(); - Object value; - if (converterClass == null) { - if (Collection.class.isAssignableFrom(fieldClass)) { - Type parameterType = field.getGenericType(); - if (!ParameterizedType.class.isAssignableFrom(parameterType.getClass())) { - throw new PippoRuntimeException("Please specify a generic parameter type for field '{}' {}", - field.getName(), fieldClass.getName()); - } - ParameterizedType parameterizedType = (ParameterizedType) parameterType; - Class genericClass; - try { - genericClass = (Class) parameterizedType.getActualTypeArguments()[0]; - } catch (ClassCastException e) { - throw new PippoRuntimeException("Please specify a generic parameter type for field '{}' {}", - field.getName(), fieldClass.getName()); - } - - if (Set.class == fieldClass) { - value = getParameters().get(parameterName).toSet(genericClass, pattern); - } else if (List.class == fieldClass) { - value = getParameters().get(parameterName).toList(genericClass, pattern); - } else if (fieldClass.isInterface()) { - throw new PippoRuntimeException("Field '{}' collection '{}' is not a supported type!", - field.getName(), fieldClass.getName()); - } else { - Class collectionClass = (Class) fieldClass; - value = getParameters().get(parameterName).toCollection(collectionClass, genericClass, pattern); - } - } else { - value = getParameters().get(parameterName).to(fieldClass, pattern); - } - } else { - value = getParameters().get(parameterName).convert(converterClass, pattern); - } - field.set(entity, value); - } catch (IllegalAccessException e) { - log.error("Cannot set value for field '{}' from parameter '{}'", field.getName(), parameterName, e); - } catch (PippoRuntimeException e) { - log.error(e.getMessage(), e); - } - } - } - - return entity; + return entityRequestEngine.updateEntityFromParameters(entity, this); } + /** + * Forward to {@link EntityRequestEngine::createEntityFromBody}. + */ + @Deprecated public T createEntityFromBody(Class entityClass) { - try { - String body = getBody(); - if (StringUtils.isNullOrEmpty(body)) { - log.warn("Can not create entity '{}' from null or empty request body!", entityClass.getName()); - return null; - } - - // try to determine the body content-type - String contentType = getContentType(); - if (StringUtils.isNullOrEmpty(contentType)) { - // sloppy client, try the accept header - contentType = getAcceptType(); - } - - if (StringUtils.isNullOrEmpty(contentType)) { - throw new PippoRuntimeException( - "Failed to create entity '{}' from request body because 'content-type' is not specified!", - entityClass.getName()); - } - - ContentTypeEngine engine = contentTypeEngines.getContentTypeEngine(contentType); - if (engine == null) { - throw new PippoRuntimeException( - "Failed to create entity '{}' from request body because a content engine for '{}' could not be found!", - entityClass.getName(), contentType); - } - - return engine.fromString(body, entityClass); - } catch (PippoRuntimeException e) { - // pass-through PippoRuntimeExceptions - throw e; - } catch (Exception e) { - // capture and re-throw all other exceptions - throw new PippoRuntimeException(e, "Failed to create entity '{}' from request body!", entityClass.getName()); - } + return entityRequestEngine.createEntityFromBody(entityClass, this); } public String getHost() { @@ -371,7 +275,7 @@ public String getScheme() { public String getAcceptType() { if (acceptType == null) { acceptType = httpServletRequest.getHeader(HttpConstants.Header.ACCEPT); - // try to specify an AcceptType from an registered ContentType suffix + // try to specify an AcceptType from a registered ContentType suffix String suffix = StringUtils.getFileExtension(getPath()); if (!StringUtils.isNullOrEmpty(suffix)) { ContentTypeEngine engine = contentTypeEngines.getContentTypeEngine(suffix); diff --git a/pippo-core/src/main/java/ro/pippo/core/entity/DefaultEntityRequestEngine.java b/pippo-core/src/main/java/ro/pippo/core/entity/DefaultEntityRequestEngine.java new file mode 100644 index 00000000..39e0c554 --- /dev/null +++ b/pippo-core/src/main/java/ro/pippo/core/entity/DefaultEntityRequestEngine.java @@ -0,0 +1,157 @@ +package ro.pippo.core.entity; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ro.pippo.core.ContentTypeEngine; +import ro.pippo.core.ContentTypeEngines; +import ro.pippo.core.ParamField; +import ro.pippo.core.ParameterValue; +import ro.pippo.core.PippoRuntimeException; +import ro.pippo.core.Request; +import ro.pippo.core.converters.Converter; +import ro.pippo.core.util.ClassUtils; +import ro.pippo.core.util.StringUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Decebal Suiu + */ +public class DefaultEntityRequestEngine implements EntityRequestEngine { + + private static final Logger log = LoggerFactory.getLogger(DefaultEntityRequestEngine.class); + + private final ContentTypeEngines contentTypeEngines; + + public DefaultEntityRequestEngine(ContentTypeEngines contentTypeEngines) { + this.contentTypeEngines = contentTypeEngines; + } + + @Override + public T createEntityFromParameters(Class entityClass, Request request) { + T entity; + try { + entity = entityClass.newInstance(); + } catch (Exception e) { + log.error("Cannot create new instance of class '{}'", entityClass.getName(), e); + return null; + } + + updateEntityFromParameters(entity, request); + + return entity; + } + + @Override + public T createEntityFromBody(Class entityClass, Request request) { + try { + String body = request.getBody(); + if (StringUtils.isNullOrEmpty(body)) { + log.warn("Can not create entity '{}' from null or empty request body!", entityClass.getName()); + return null; + } + + // try to determine the body content-type + String contentType = request.getContentType(); + if (StringUtils.isNullOrEmpty(contentType)) { + // sloppy client, try to accept header + contentType = request.getAcceptType(); + } + + if (StringUtils.isNullOrEmpty(contentType)) { + throw new PippoRuntimeException( + "Failed to create entity '{}' from request body because 'content-type' is not specified!", + entityClass.getName()); + } + + ContentTypeEngine engine = contentTypeEngines.getContentTypeEngine(contentType); + if (engine == null) { + throw new PippoRuntimeException( + "Failed to create entity '{}' from request body because a content engine for '{}' could not be found!", + entityClass.getName(), contentType); + } + + return engine.fromString(body, entityClass); + } catch (PippoRuntimeException e) { + // pass-through PippoRuntimeExceptions + throw e; + } catch (Exception e) { + // capture and re-throw all exceptions + throw new PippoRuntimeException(e, "Failed to create entity '{}' from request body!", entityClass.getName()); + } + } + + @Override + @SuppressWarnings("unchecked") + public T updateEntityFromParameters(T entity, Request request) { + Map parameters = request.getParameters(); + for (Field field : ClassUtils.getAllFields(entity.getClass())) { + String parameterName = field.getName(); + ParamField parameter = field.getAnnotation(ParamField.class); + if (parameter != null) { + parameterName = parameter.value(); + } + + if (parameters.containsKey(parameterName)) { + if (!field.isAccessible()) { + field.setAccessible(true); + } + + String pattern = (parameter != null) ? parameter.pattern() : null; + Class> converterClass = (parameter != null && void.class != parameter.converterClass()) ? parameter.converterClass() : null; + + try { + Class fieldClass = field.getType(); + Object value; + if (converterClass == null) { + if (Collection.class.isAssignableFrom(fieldClass)) { + Type parameterType = field.getGenericType(); + if (!ParameterizedType.class.isAssignableFrom(parameterType.getClass())) { + throw new PippoRuntimeException("Please specify a generic parameter type for field '{}' {}", + field.getName(), fieldClass.getName()); + } + ParameterizedType parameterizedType = (ParameterizedType) parameterType; + Class genericClass; + try { + genericClass = (Class) parameterizedType.getActualTypeArguments()[0]; + } catch (ClassCastException e) { + throw new PippoRuntimeException("Please specify a generic parameter type for field '{}' {}", + field.getName(), fieldClass.getName()); + } + + if (Set.class == fieldClass) { + value = parameters.get(parameterName).toSet(genericClass, pattern); + } else if (List.class == fieldClass) { + value = parameters.get(parameterName).toList(genericClass, pattern); + } else if (fieldClass.isInterface()) { + throw new PippoRuntimeException("Field '{}' collection '{}' is not a supported type!", + field.getName(), fieldClass.getName()); + } else { + Class> collectionClass = (Class>) fieldClass; + value = parameters.get(parameterName).toCollection(collectionClass, genericClass, pattern); + } + } else { + value = parameters.get(parameterName).to(fieldClass, pattern); + } + } else { + value = parameters.get(parameterName).convert(converterClass, pattern); + } + field.set(entity, value); + } catch (IllegalAccessException e) { + log.error("Cannot set value for field '{}' from parameter '{}'", field.getName(), parameterName, e); + } catch (PippoRuntimeException e) { + log.error(e.getMessage(), e); + } + } + } + + return entity; + } + +} diff --git a/pippo-core/src/main/java/ro/pippo/core/entity/EntityRequestEngine.java b/pippo-core/src/main/java/ro/pippo/core/entity/EntityRequestEngine.java new file mode 100644 index 00000000..8e362b59 --- /dev/null +++ b/pippo-core/src/main/java/ro/pippo/core/entity/EntityRequestEngine.java @@ -0,0 +1,18 @@ +package ro.pippo.core.entity; + +import ro.pippo.core.Request; + +/** + * Contains methods that create/update an entity from a request. + * + * @author Decebal Suiu + */ +public interface EntityRequestEngine { + + T createEntityFromParameters(Class entityClass, Request request); + + T createEntityFromBody(Class entityClass, Request request); + + T updateEntityFromParameters(T entity, Request request); + +}