Skip to content

Commit

Permalink
Body Parsing Improvement
Browse files Browse the repository at this point in the history
Documentation for Validation enhanced. SwissKnife converters replaced
by usage of ParamParsers in BodyPostEngine. Support for enumeration
value (registration deprecated), custom types parsing and POST objects
parsing.
  • Loading branch information
jlannoy committed Apr 15, 2016
1 parent dc993ff commit 6c84f6d
Show file tree
Hide file tree
Showing 26 changed files with 869 additions and 402 deletions.
6 changes: 4 additions & 2 deletions ninja-core/src/main/java/ninja/Bootstrap.java
Expand Up @@ -20,9 +20,8 @@
import java.util.List; import java.util.List;


import ninja.application.ApplicationRoutes; import ninja.application.ApplicationRoutes;
import ninja.lifecycle.LifecycleSupport;
import ninja.logging.LogbackConfigurator; import ninja.logging.LogbackConfigurator;
import ninja.scheduler.SchedulerSupport; import ninja.params.ParamParser;
import ninja.utils.NinjaConstant; import ninja.utils.NinjaConstant;
import ninja.utils.NinjaProperties; import ninja.utils.NinjaProperties;
import ninja.utils.NinjaPropertiesImpl; import ninja.utils.NinjaPropertiesImpl;
Expand All @@ -38,6 +37,8 @@
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.Stage; import com.google.inject.Stage;
import com.google.inject.multibindings.Multibinder;

import ninja.conf.FrameworkModule; import ninja.conf.FrameworkModule;
import ninja.conf.NinjaBaseModule; import ninja.conf.NinjaBaseModule;
import ninja.conf.NinjaClassicModule; import ninja.conf.NinjaClassicModule;
Expand Down Expand Up @@ -208,6 +209,7 @@ protected void configure() throws Exception {
@Override @Override
protected void configure() { protected void configure() {
bind(Ninja.class).to(ninjaClass).in(Singleton.class); bind(Ninja.class).to(ninjaClass).in(Singleton.class);
Multibinder.newSetBinder(binder(), ParamParser.class);
} }
}); });
} }
Expand Down
95 changes: 66 additions & 29 deletions ninja-core/src/main/java/ninja/bodyparser/BodyParserEnginePost.java
Expand Up @@ -19,26 +19,42 @@
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.util.Collection; import java.util.Collection;
import java.util.Map.Entry; import java.util.List;
import java.util.Set; import java.util.Set;


import ninja.ContentTypes; import ninja.ContentTypes;
import ninja.Context; import ninja.Context;
import ninja.utils.SwissKnife; import ninja.params.ParamParser;
import ninja.params.ParamParsers;
import ninja.params.ParamParsers.ArrayParamParser;
import ninja.params.ParamParsers.ListParamParser;


import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;


import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;


@Singleton @Singleton
public class BodyParserEnginePost implements BodyParserEngine { public class BodyParserEnginePost implements BodyParserEngine {


private final Logger logger = LoggerFactory.getLogger(BodyParserEnginePost.class); private final Logger logger = LoggerFactory.getLogger(BodyParserEnginePost.class);


private final ParamParsers paramParsers;

@Inject
public BodyParserEnginePost(ParamParsers paramParsers) {
this.paramParsers = paramParsers;
}

@Override @Override
public <T> T invoke(Context context, Class<T> classOfT) { public <T> T invoke(Context context, Class<T> classOfT) {
return invoke(context, classOfT, "");
}

// Allows to instantiate inner objects with a prefix for each parameter key
private <T> T invoke(Context context, Class<T> classOfT, String paramPrefix) {


T t = null; T t = null;


Expand All @@ -49,46 +65,67 @@ public <T> T invoke(Context context, Class<T> classOfT) {
return null; return null;
} }


Set<String> declaredFields = getAllDeclaredFieldsAsStringSet(classOfT); for(String declaredField : getAllDeclaredFieldsAsStringSet(classOfT)) {


for (Entry<String, String[]> ent : context.getParameters().entrySet()) { try {


if (declaredFields.contains(ent.getKey())) { Field field = classOfT.getDeclaredField(declaredField);
Class<?> fieldType = field.getType();
field.setAccessible(true);


try { if (context.getParameters().containsKey(paramPrefix + declaredField)) {

String[] values = context.getParameters().get(paramPrefix + declaredField);


Field field = classOfT.getDeclaredField(ent.getKey()); if (Collection.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fieldType)) {
Class<?> fieldType = field.getType();
field.setAccessible(true);


String[] values = ent.getValue(); ListParamParser<?> parser = (ListParamParser<?>) paramParsers.getListParser(getGenericType(field));
Object convertedValue; if (parser == null) {
logger.warn("No parser defined for a collection of type {}", getGenericType(field).getCanonicalName());
} else {
field.set(t, parser.parseParameter(field.getName(), values, context.getValidation()));
}


// convert the values based on the field type,
// supported types are collections, arrays and boxed primities

if (Collection.class.isAssignableFrom(fieldType)) {
convertedValue = SwissKnife.convertCollection(values, getGenericType(field));
} else if (fieldType.isArray()) { } else if (fieldType.isArray()) {
convertedValue = SwissKnife.convertArray(values, fieldType.getComponentType());
ArrayParamParser<?> parser = paramParsers.getArrayParser(fieldType);
if (parser == null) {
logger.warn("No parser defined for an array of type {}", fieldType.getComponentType().getCanonicalName());
} else {
field.set(t, parser.parseParameter(field.getName(), values, context.getValidation()));
}

} else { } else {
convertedValue = SwissKnife.convert(values[0], fieldType);
}


if (convertedValue != null) { ParamParser<?> parser = (ParamParser<?>) paramParsers.getParamParser(fieldType);
field.set(t, convertedValue); if (parser == null) {
} logger.warn("No parser defined for type {}", fieldType.getCanonicalName());
} else {
field.set(t, parser.parseParameter(field.getName(), values[0], context.getValidation()));
}


} catch (NoSuchFieldException }
| SecurityException
| IllegalArgumentException
| IllegalAccessException e) {


logger.warn( } else {
"Error parsing incoming Post request into class {}. Key {} and value {}.",
classOfT.getName(), ent.getKey(), ent.getValue(), e); // Check if we have a parameter key corresponding to inner attributes of this field
for(String parameter : context.getParameters().keySet()) {
if(parameter.startsWith(paramPrefix + declaredField + ".")) {
field.set(t, invoke(context, fieldType, paramPrefix + declaredField + "."));
break;
}
}

} }


} catch (NoSuchFieldException
| SecurityException
| IllegalArgumentException
| IllegalAccessException e) {

logger.warn(
"Error parsing incoming Post request into class {}. Key {} and value {}.",
classOfT.getName(), paramPrefix + declaredField, context.getParameters().get(paramPrefix + declaredField), e);
} }


} }
Expand Down
4 changes: 4 additions & 0 deletions ninja-core/src/main/java/ninja/conf/NinjaBaseModule.java
Expand Up @@ -25,7 +25,10 @@


import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.multibindings.Multibinder;

import ninja.lifecycle.LifecycleSupport; import ninja.lifecycle.LifecycleSupport;
import ninja.params.ParamParser;
import ninja.scheduler.SchedulerSupport; import ninja.scheduler.SchedulerSupport;


/** /**
Expand All @@ -50,6 +53,7 @@ public void configure() {
install(SchedulerSupport.getModule()); install(SchedulerSupport.getModule());


// Routing // Routing
Multibinder.newSetBinder(binder(), ParamParser.class);
bind(RouteBuilder.class).to(RouteBuilderImpl.class); bind(RouteBuilder.class).to(RouteBuilderImpl.class);
bind(Router.class).to(RouterImpl.class).in(Singleton.class); bind(Router.class).to(RouterImpl.class).in(Singleton.class);


Expand Down
Expand Up @@ -200,7 +200,7 @@ private static ArgumentExtractor<?> validateArgumentWithExtractor(Class<?> param
if (extractor.getFieldName() != null) { if (extractor.getFieldName() != null) {
if (String.class.isAssignableFrom(extractor.getExtractedType())) { if (String.class.isAssignableFrom(extractor.getExtractedType())) {
// Look up a parser for a single-valued parameter // Look up a parser for a single-valued parameter
ParamParser<?> parser = ParamParsers.getParamParser(paramType); ParamParser<?> parser = injector.getInstance(ParamParsers.class).getParamParser(paramType);
if (parser == null) { if (parser == null) {
throw new RoutingException("Can't find parameter parser for type " throw new RoutingException("Can't find parameter parser for type "
+ extractor.getExtractedType() + " on field " + extractor.getExtractedType() + " on field "
Expand All @@ -211,7 +211,7 @@ private static ArgumentExtractor<?> validateArgumentWithExtractor(Class<?> param
} }
} else if (String[].class.isAssignableFrom(extractor.getExtractedType())) { } else if (String[].class.isAssignableFrom(extractor.getExtractedType())) {
// Look up a parser for a multi-valued parameter // Look up a parser for a multi-valued parameter
ArrayParamParser<?> parser = ParamParsers.getArrayParser(paramType); ArrayParamParser<?> parser = injector.getInstance(ParamParsers.class).getArrayParser(paramType);
if (parser == null) { if (parser == null) {
throw new RoutingException("Can't find parameter array parser for type " throw new RoutingException("Can't find parameter array parser for type "
+ extractor.getExtractedType() + " on field " + extractor.getExtractedType() + " on field "
Expand Down

0 comments on commit 6c84f6d

Please sign in to comment.