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}