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.guice;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.Field;
021import java.lang.reflect.Modifier;
022import java.util.concurrent.ConcurrentMap;
023
024import javax.inject.Qualifier;
025
026import org.apache.wicket.injection.IFieldValueFactory;
027import org.apache.wicket.proxy.LazyInitProxyFactory;
028
029import com.google.inject.BindingAnnotation;
030import com.google.inject.Inject;
031import org.apache.wicket.util.lang.Generics;
032
033/**
034 *
035 */
036public class GuiceFieldValueFactory implements IFieldValueFactory
037{
038        private final ConcurrentMap<GuiceProxyTargetLocator, Object> cache = Generics.newConcurrentHashMap();
039        private static final Object NULL_SENTINEL = new Object();
040
041        private final boolean wrapInProxies;
042
043        /**
044         * Construct.
045         *
046         * @param wrapInProxies
047         */
048        GuiceFieldValueFactory(final boolean wrapInProxies)
049        {
050                this.wrapInProxies = wrapInProxies;
051        }
052
053        /**
054         * {@inheritDoc}
055         */
056        @Override
057        public Object getFieldValue(final Field field, final Object fieldOwner)
058        {
059                Object target = null;
060
061                if (supportsField(field))
062                {
063                        Inject injectAnnotation = field.getAnnotation(Inject.class);
064                        javax.inject.Inject javaxInjectAnnotation = field.getAnnotation(javax.inject.Inject.class);
065                        if (!Modifier.isStatic(field.getModifiers()) && (injectAnnotation != null || javaxInjectAnnotation != null))
066                        {
067                                try
068                                {
069                                        boolean optional = injectAnnotation != null && injectAnnotation.optional();
070                                        Annotation bindingAnnotation = findBindingAnnotation(field.getAnnotations());
071                                        final GuiceProxyTargetLocator locator = new GuiceProxyTargetLocator(field, bindingAnnotation, optional);
072
073                                        Object cachedValue = cache.get(locator);
074                                        if (cachedValue != null)
075                                        {
076                                                return cachedValue == NULL_SENTINEL ? null : cachedValue;
077                                        }
078
079                                        target = locator.locateProxyTarget();
080                                        if (target == null)
081                                        {
082                                                // Optional without a binding, return null
083                                        }
084                                        else
085                                        {
086                                                if (wrapInProxies)
087                                                {
088                                                        target = LazyInitProxyFactory.createProxy(field.getType(), locator);
089                                                }
090                                        }
091
092                                        if (locator.isSingletonScope())
093                                        {
094                                                Object tmpTarget = cache.putIfAbsent(locator, target == null ? NULL_SENTINEL : target);
095                                                if (tmpTarget != null)
096                                                {
097                                                        target = tmpTarget;
098                                                }
099                                        }
100
101                                        if (!field.canAccess(fieldOwner))
102                                        {
103                                                field.setAccessible(true);
104                                        }
105                                }
106                                catch (MoreThanOneBindingException e)
107                                {
108                                        throw new RuntimeException(
109                                                        "Can't have more than one BindingAnnotation on field " + field.getName() +
110                                                                        " of class " + fieldOwner.getClass().getName());
111                                }
112                        }
113                }
114
115                return target == NULL_SENTINEL ? null : target;
116        }
117
118        /**
119         * {@inheritDoc}
120         */
121        @Override
122        public boolean supportsField(final Field field)
123        {
124                return field.isAnnotationPresent(Inject.class) || field.isAnnotationPresent(javax.inject.Inject.class);
125        }
126
127        /**
128         *
129         * @param annotations
130         * @return Annotation
131         * @throws MoreThanOneBindingException
132         */
133        private Annotation findBindingAnnotation(final Annotation[] annotations)
134                        throws MoreThanOneBindingException
135        {
136                Annotation bindingAnnotation = null;
137
138                // Work out if we have a BindingAnnotation on this parameter.
139                for (Annotation annotation : annotations)
140                {
141                        if (annotation.annotationType().getAnnotation(BindingAnnotation.class) != null ||
142                                        annotation.annotationType().getAnnotation(Qualifier.class) != null)
143                        {
144                                if (bindingAnnotation != null)
145                                {
146                                        throw new MoreThanOneBindingException();
147                                }
148                                bindingAnnotation = annotation;
149                        }
150                }
151                return bindingAnnotation;
152        }
153
154        /**
155         *
156         */
157        public static class MoreThanOneBindingException extends Exception
158        {
159                private static final long serialVersionUID = 1L;
160        }
161}