001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.injection; 018 019import java.lang.reflect.Field; 020import java.util.ArrayList; 021import java.util.List; 022 023import org.apache.wicket.Application; 024import org.apache.wicket.MetaDataKey; 025import org.apache.wicket.util.collections.ClassMetaCache; 026 027/** 028 * Injector scans fields of an object instance and checks if the specified 029 * {@link IFieldValueFactory} can provide a value for a field; if it can, the field is set to that 030 * value. Injector will ignore all non-null fields. 031 * 032 * @author Igor Vaynberg (ivaynberg) 033 * 034 */ 035public abstract class Injector 036{ 037 private static final MetaDataKey<Injector> KEY = new MetaDataKey<>() 038 { 039 private static final long serialVersionUID = 1L; 040 }; 041 042 private final ClassMetaCache<Field[]> cache = new ClassMetaCache<>(); 043 044 /** 045 * Binds current instance of the injector to the Application. After this method is called this 046 * instance of injector will be returned from subsequent calls to {@link #get()} whenever the 047 * specified application object is active in the thread. 048 * 049 * @param application 050 */ 051 public void bind(final Application application) 052 { 053 application.setMetaData(KEY, this); 054 } 055 056 /** 057 * @return Injector associated with the application instance 058 */ 059 public static Injector get() 060 { 061 return Application.get().getMetaData(KEY); 062 } 063 064 /** 065 * Injects the specified object. This method is usually implemented by delegating to 066 * {@link #inject(Object, IFieldValueFactory)} with some {@link IFieldValueFactory} 067 * 068 * @param object 069 * 070 * @see #inject(Object, IFieldValueFactory) 071 */ 072 public abstract void inject(Object object); 073 074 /** 075 * traverse fields in the class hierarchy of the object and set their value with a locator 076 * provided by the locator factory. 077 * 078 * @param object 079 * @param factory 080 */ 081 protected void inject(final Object object, final IFieldValueFactory factory) 082 { 083 final Class<?> clazz = object.getClass(); 084 085 Field[] fields = null; 086 087 // try cache 088 fields = cache.get(clazz); 089 090 if (fields == null) 091 { 092 // cache miss, discover fields 093 fields = findFields(clazz, factory); 094 095 // write to cache 096 cache.put(clazz, fields); 097 } 098 099 for (final Field field : fields) 100 { 101 if (!field.canAccess(object)) 102 { 103 field.setAccessible(true); 104 } 105 try 106 { 107 108 if (field.get(object) == null) 109 { 110 111 Object value = factory.getFieldValue(field, object); 112 113 if (value != null) 114 { 115 field.set(object, value); 116 } 117 } 118 } 119 catch (IllegalArgumentException e) 120 { 121 throw new RuntimeException("error while injecting object [" + object.toString() + 122 "] of type [" + object.getClass().getName() + "]", e); 123 } 124 catch (IllegalAccessException e) 125 { 126 throw new RuntimeException("error while injecting object [" + object.toString() + 127 "] of type [" + object.getClass().getName() + "]", e); 128 } 129 } 130 } 131 132 /** 133 * Returns an array of fields that can be injected using the given field value factory 134 * 135 * @param clazz 136 * @param factory 137 * @return an array of fields that can be injected using the given field value factory 138 */ 139 private Field[] findFields(Class<?> clazz, final IFieldValueFactory factory) 140 { 141 List<Field> matched = new ArrayList<>(); 142 143 while (clazz != null) 144 { 145 Field[] fields = clazz.getDeclaredFields(); 146 for (final Field field : fields) 147 { 148 if (factory.supportsField(field)) 149 { 150 matched.add(field); 151 } 152 } 153 clazz = clazz.getSuperclass(); 154 } 155 156 return matched.toArray(new Field[matched.size()]); 157 } 158 159}