Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save asufana/9987196 to your computer and use it in GitHub Desktop.
Save asufana/9987196 to your computer and use it in GitHub Desktop.
PlayFramework1 バインディング処理

PlayFramework1 バインディング

Playのバインディング処理とは、HTTPパラメータとして渡された文字列を適切なJavaの型へ変換する仕組み

すべての基本データ型と、そして、一般的な Java の型は自動的に紐付けられます:

int, long, boolean, char, byte, float, double, Integer, Long, Boolean, Char, String, Byte, Float, Double.

HTTP リクエスト中にパラメータが見つからないか、または自動変換に失敗した場合、オブジェクト型には null、基本データ型にはそれらのデフォルト値が設定されることに注意してください。

カスタムバインディング

組み込みのバインディング以外に、任意のクラスにバインドする仕組みも持つ

@Global
//日付文字列をDateTimeにバインドする
public class DateTimeBinder implements TypeBinder<DateTime> {
    
    @Override
    public Object bind(final String name,
                       final Annotation[] annotations,
                       final String value,
                       final Class actualClass,
                       final Type genericType) throws Exception {
        return DateUtil.toDateTime(value);
    }
}

バインディング処理クラス図

クラス図

Binder#bind()

バインド処理呼び出し

    /**
     * @param parentParamNode HTTPパラメータ値
     * @param name HTTPパラメータ名
     * @param clazz バインド対象のクラス(アクションメソッドの引数のひとつ)
     * @param type バインド対象クラスに指定されたジェネリクスクラス
     * @param annotations バインド対象クラスに指定されたアノテーション
     * @param methodAndParamInfo アクションメソッドとパラメータの情報
     * @return 指定型にバインドされたインスタンス
     */
    public static Object bind(RootParamNode parentParamNode, String name, Class<?> clazz, Type type, Annotation[] annotations, MethodAndParamInfo methodAndParamInfo) {

        final ParamNode paramNode = parentParamNode.getChild(name, true);
        Object result = null;
        if (paramNode == null) {
            result = MISSING;
        }

        final BindingAnnotations bindingAnnotations = new BindingAnnotations(annotations);
        if (bindingAnnotations.checkNoBinding()) {
            return NO_BINDING;
        }

        if (paramNode != null) {
            //プラグインで設定されたバインダがあればバインドする
            result = Play.pluginCollection.bind(parentParamNode, name, clazz, type, annotations);
            if (result != null) {
                return result;
            }

           //バインド処理する
           result = internalBind(paramNode, clazz, type, bindingAnnotations);
        }

        //バインドされなければ初期値を設定する
        if (result == MISSING) {
            if (clazz.equals(boolean.class)) {
                return false;
            }
            if (clazz.equals(int.class)) {
                return 0;
            }
            if (clazz.equals(long.class)) {
                return 0;
            }
            if (clazz.equals(double.class)) {
                return 0;
            }
            if (clazz.equals(short.class)) {
                return 0;
            }
            if (clazz.equals(byte.class)) {
                return 0;
            }
            if (clazz.equals(char.class)) {
                return ' ';
            }
            return null;
        }
        return result;
    }

Binder#internalBind()

組み込みバインド処理

    /**
     * @param paramNode パラメータ値
     * @param clazz バインド先クラス
     * @param type バインド先クラスのジェネリクス型
     * @param bindingAnnotations バインド先クラスのアノテーションコレクション
     * @return
     */
    protected static Object internalBind(final ParamNode paramNode,
                                         final Class<?> clazz,
                                         final Type type,
                                         final BindingAnnotations bindingAnnotations) {
        
        //パラメータが渡されなければMISSING
        if (paramNode == null) {
            return MISSING;
        }
        
        //パラメータ値がない、(構造化されていて)子パラメータもなければMISSING
        if (paramNode.getValues() == null
                && paramNode.getAllChildren().size() == 0) {
            return MISSING;
        }
        
        //アノテーション設定がなければNO_BINDING
        if (bindingAnnotations.checkNoBinding()) {
            return NO_BINDING;
        }
        
        try {
            //clazzがEnumであれば
            if (Enum.class.isAssignableFrom(clazz)) {
                return bindEnum(clazz, paramNode);
            }
            
            //clazzがMapであれば
            if (Map.class.isAssignableFrom(clazz)) {
                return bindMap(clazz, type, paramNode, bindingAnnotations);
            }
            
            //clazzがCollectionであれば
            if (Collection.class.isAssignableFrom(clazz)) {
                return bindCollection(clazz,
                                      type,
                                      paramNode,
                                      bindingAnnotations);
            }
            
            //クラスとしてバインドする
            final Object directBindResult = internalDirectBind(paramNode.getOriginalKey(),
                                                               bindingAnnotations.annotations,
                                                               paramNode.getFirstValue(clazz),
                                                               clazz,
                                                               type);
            if (directBindResult != DIRECTBINDING_NO_RESULT) {
                // we found a value/result when direct binding
                return directBindResult;
            }
            
            //clazzが配列であれば
            // Must do the default array-check after direct binding, since some custom-binders checks for specific arrays
            if (clazz.isArray()) {
                return bindArray(clazz, paramNode, bindingAnnotations);
            }
            
            //パラメータが構造化されていれば(POJOオブジェクトの紐付けのことか?)
            //http://www.playframework-ja.org/documentation/1.2.7/controllers#anamepojoPOJOa
            if (!paramNode.getAllChildren().isEmpty()) {
                return internalBindBean(clazz, paramNode, bindingAnnotations);
            }
            
            //諦める
            return null; // give up
        }
        catch (final Exception e) {
            //例外ばValidationエラーとして登録される
            Validation.addError(paramNode.getOriginalKey(), "validation.invalid");
        }
        return MISSING;
    }

Binder#internalDirectBind()

    //組み込みバインダーの登録
    static final Map<Class<?>, TypeBinder<?>> supportedTypes = new HashMap<Class<?>, TypeBinder<?>>();
    static {
        supportedTypes.put(Date.class, new DateBinder());
        supportedTypes.put(DateTime.class, new DateTimeBinder());
        supportedTypes.put(File.class, new FileBinder());
        supportedTypes.put(File[].class, new FileArrayBinder());
        supportedTypes.put(Model.BinaryField.class, new BinaryBinder());
        supportedTypes.put(Upload.class, new UploadBinder());
        supportedTypes.put(Upload[].class, new UploadArrayBinder());
        supportedTypes.put(Calendar.class, new CalendarBinder());
        supportedTypes.put(Locale.class, new LocaleBinder());
        supportedTypes.put(byte[].class, new ByteArrayBinder());
        supportedTypes.put(byte[][].class, new ByteArrayArrayBinder());
    }

    private static Object internalDirectBind(String name, Annotation[] annotations, String value, Class<?> clazz, Type type) throws Exception {
        boolean nullOrEmpty = value == null || value.trim().length() == 0;

        if (annotations != null) {
            for (Annotation annotation : annotations) {
                //@Asアノテーションが設定されていれば
                if (annotation.annotationType().equals(As.class)) {
                    //@Asアノテーションにbinderクラスが指定されていれば(カスタムバインダ)
                    //http://www.playframework-ja.org/documentation/1.2.7/controllers#anametypebinderplay.data.binding.TypeBindera
                    Class<? extends TypeBinder<?>> toInstanciate = ((As) annotation).binder();
                    if (!(toInstanciate.equals(As.DEFAULT.class))) {
                        //指定クラスでバインド返却する
                        TypeBinder<?> myInstance = toInstanciate.newInstance();
                        return myInstance.bind(name, annotations, value, clazz, type);
                    }
                }
            }
        }

        // application custom types have higher priority. If unable to bind proceed with the next one
        //TypeBinderクラスを継承したクラス一覧を取得
        for (Class<TypeBinder<?>> c : Play.classloader.getAssignableClasses(TypeBinder.class)) {
            //@Globalアノテーションが付加されていたら(カスタムバインダ)
            //http://www.playframework-ja.org/documentation/1.2.7/controllers#anameglobalplay.data.binding.Globala
            if (c.isAnnotationPresent(Global.class)) {
                //カスタムバインダのジェネリクス型とバインド先クラスが一致していれば
                Class<?> forType = (Class) ((ParameterizedType) c.getGenericInterfaces()[0]).getActualTypeArguments()[0];
                if (forType.isAssignableFrom(clazz)) {
                    //バインドする
                    Object result = c.newInstance().bind(name, annotations, value, clazz, type);
                    if (result != null) {
                        return result;
                    }
                }
            }
        }

        //組み込みバインダに含まれているか
        for (Class<?> c : supportedTypes.keySet()) {
            if (Logger.isTraceEnabled()) {
                Logger.trace("directBind: value [" + value + "] c [" + c + "] Class [" + clazz + "]");
            }

            //含まれていればバインド返却する
            if (c.isAssignableFrom(clazz)) {
                if (Logger.isTraceEnabled()) {
                    Logger.trace("directBind: isAssignableFrom is true");
                }
                return supportedTypes.get(c).bind(name, annotations, value, clazz, type);
            }
        }

        //Stringはそのまま
        if (clazz.equals(String.class)) {
            return value;
        }
        //Characterは一文字目のみ
        if (clazz.equals(Character.class)) {
            return value.charAt(0);
        }
        //このあとは、Enum, int, Integer, long, Long,,, 省略

        return DIRECTBINDING_NO_RESULT;
    }

@Asによるバインドパラメータの提供

@play.data.binding.As

文脈的にバインディングを構成する新しい @play.data.binding.As アノテーションです。これは例えば、 DateBinder によって使用される日付のフォーマットを指定するために使います:

DateBinder#bind()

日付文字列をファイルをDateクラスにバインドする

public class DateBinder implements TypeBinder<Date> {

    public static final String ISO8601 = "'ISO8601:'yyyy-MM-dd'T'HH:mm:ssZ";

    public Date bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) throws Exception {
        if (value == null || value.trim().length() == 0) {
            return null;
        }

        //AnnotationHelperにバインド依頼(@Asでのバインド)
        Date date = AnnotationHelper.getDateAs(annotations, value);
        if (date != null) {
            return date;
        }

        //I18N.getDateFormat()日付文字列で想定してバインド
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(I18N.getDateFormat());
            sdf.setLenient(false);
            return sdf.parse(value);
        } catch (ParseException e) {
            // Ignore
        }

        //駄目ならISO8601日付文字列想定で再バインド
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(ISO8601);
            sdf.setLenient(false);
            return sdf.parse(value);
        } catch (Exception e) {
            throw new IllegalArgumentException("Cannot convert [" + value + "] to a Date: " + e.toString());
        }
    }

AnnotationHelper#getDateAs()

@Asアトリビュート値から日付フォーマットを取得してDateクラスにバインドする

public class AnnotationHelper {

    //こんな指定を想定しておるぞ
    // It can be something like As(lang={"fr,de","*"}, value={"dd-MM-yyyy","MM-dd-yyyy"})

    public static Date getDateAs(Annotation[] annotations, String value) throws ParseException {
        // Look up for the BindAs annotation
        if (annotations == null) {
            return null;
        }
        for (Annotation annotation : annotations) {
            //@Asが指定されていれば、ロケール情報と日付フォーマットを取得する
            if (annotation.annotationType().equals(As.class)) {
                As as = (As) annotation;
                Locale locale = Lang.getLocale();
                //日付フォーマット取得
                String format = as.value()[0];
                if (!StringUtils.isEmpty(format)) {
                    //ロケール取得
                    Tuple tuple = getLocale(as.lang());
                    if (tuple != null) {
                        format = as.value()[tuple.index < as.value().length ? tuple.index : as.value().length - 1];
                        locale = tuple.locale;
                    }
                }
                if (StringUtils.isEmpty(format)) {
                    format = I18N.getDateFormat();
                }
                SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
                sdf.setLenient(false);
                //Dateオブジェクトを返却
                return sdf.parse(value);
            }
        }
        return null;
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment