/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jooby.internal.parser; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import org.jooby.Err; import org.jooby.Mutant; import org.jooby.Parser; import org.jooby.Request; import org.jooby.Response; import org.jooby.internal.ParameterNameProvider; import org.jooby.internal.mvc.RequestParam; import org.jooby.internal.parser.bean.BeanPlan; import com.google.common.primitives.Primitives; import com.google.common.reflect.Reflection; import com.google.inject.TypeLiteral; import javaslang.control.Try; public class BeanParser implements Parser { private Function<? super Throwable, Try<? extends Object>> MISSING = x -> { return x instanceof Err.Missing ? Try.success(null) : Try.failure(x); }; private Function<? super Throwable, Try<? extends Object>> RETHROW = Try::failure; private Function<? super Throwable, Try<? extends Object>> recoverMissing; @SuppressWarnings("rawtypes") private final Map<TypeLiteral, BeanPlan> forms; public BeanParser(final boolean allowNulls) { this.recoverMissing = allowNulls ? MISSING : RETHROW; this.forms = new ConcurrentHashMap<>(); } @Override public Object parse(final TypeLiteral<?> type, final Context ctx) throws Throwable { Class<?> beanType = type.getRawType(); if (Primitives.isWrapperType(Primitives.wrap(beanType)) || CharSequence.class.isAssignableFrom(beanType)) { return ctx.next(); } return ctx.ifparams(map -> { final Object bean; if (List.class.isAssignableFrom(beanType)) { bean = newBean(ctx.require(Request.class), ctx.require(Response.class), map, type); } else if (beanType.isInterface()) { bean = newBeanInterface(ctx.require(Request.class), ctx.require(Response.class), beanType); } else { bean = newBean(ctx.require(Request.class), ctx.require(Response.class), map, type); } return bean; }); } @Override public String toString() { return "bean"; } @SuppressWarnings("rawtypes") private Object newBean(final Request req, final Response rsp, final Map<String, Mutant> params, final TypeLiteral type) throws Throwable { BeanPlan form = forms.get(type); if (form == null) { form = new BeanPlan(req.require(ParameterNameProvider.class), type); forms.put(type, form); } return form.newBean(p -> value(p, req, rsp), params.keySet()); } private Object newBeanInterface(final Request req, final Response rsp, final Class<?> beanType) { return Reflection.newProxy(beanType, (proxy, method, args) -> { StringBuilder name = new StringBuilder(method.getName() .replace("get", "") .replace("is", "")); name.setCharAt(0, Character.toLowerCase(name.charAt(0))); return value(new RequestParam(method, name.toString(), method.getGenericReturnType()), req, rsp); }); } private Object value(final RequestParam param, final Request req, final Response rsp) throws Throwable { return Try.of(() -> param.value(req, rsp)) .recoverWith(recoverMissing) .getOrElseThrow(Function.identity()); } }