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.spring.injection.annot;
018
019import javax.servlet.ServletContext;
020
021import org.apache.wicket.Application;
022import org.apache.wicket.Component;
023import org.apache.wicket.IBehaviorInstantiationListener;
024import org.apache.wicket.MetaDataKey;
025import org.apache.wicket.Session;
026import org.apache.wicket.application.IComponentInstantiationListener;
027import org.apache.wicket.behavior.Behavior;
028import org.apache.wicket.injection.IFieldValueFactory;
029import org.apache.wicket.injection.Injector;
030import org.apache.wicket.model.Model;
031import org.apache.wicket.protocol.http.WebApplication;
032import org.apache.wicket.spring.ISpringContextLocator;
033import org.apache.wicket.util.lang.Args;
034import org.springframework.context.ApplicationContext;
035import org.springframework.web.context.support.WebApplicationContextUtils;
036
037/**
038 * {@link IComponentInstantiationListener} that injects component and behavior properties
039 * annotated with {@link SpringBean} annotations.
040 * 
041 * To install in yourapplication.init() call
042 * <code>getComponentInstantiationListeners().add(new SpringComponentInjector(this));</code>
043 * <p>
044 * Only Wicket {@link Component}s and {@link Behavior}s are automatically injected, other classes
045 * such as {@link Session}, {@link Model}, and any other POJO can be injected by calling
046 * <code>Injector.get().inject(this)</code> in their constructor.
047 * </p>
048 * 
049 * @author Igor Vaynberg (ivaynberg)
050 * @author <a href="mailto:jlee@antwerkz.com">Justin Lee</a>
051 * 
052 */
053public class SpringComponentInjector extends Injector
054        implements
055                IComponentInstantiationListener,
056                IBehaviorInstantiationListener
057{
058        private final IFieldValueFactory fieldValueFactory;
059
060        /**
061         * Metadata key used to store application context in application's metadata
062         */
063        private static MetaDataKey<ApplicationContext> CONTEXT_KEY = new MetaDataKey<>()
064        {
065                private static final long serialVersionUID = 1L;
066
067        };
068
069        /**
070         * Constructor used when spring application context is declared in the spring standard way and
071         * can be located through
072         * {@link WebApplicationContextUtils#getRequiredWebApplicationContext(ServletContext)}.
073         * 
074         * @param webapp
075         *            wicket web application
076         */
077        public SpringComponentInjector(final WebApplication webapp)
078        {
079                this(webapp, getDefaultContext(webapp));
080        }
081
082        /**
083         * Constructor
084         * 
085         * @param webapp
086         *            wicket web application
087         * @param ctx
088         *            spring's application context
089         */
090        public SpringComponentInjector(final WebApplication webapp, final ApplicationContext ctx)
091        {
092                this(webapp, ctx, true);
093        }
094
095        /**
096         * Constructor
097         * 
098         * @param webapp
099         *            wicket web application
100         * @param ctx
101         *            spring's application context
102         * 
103         * @param wrapInProxies
104         *            whether or not wicket should wrap dependencies with specialized proxies that can
105         *            be safely serialized. in most cases this should be set to true.
106         */
107        public SpringComponentInjector(final WebApplication webapp, final ApplicationContext ctx,
108                final boolean wrapInProxies)
109        {
110                Args.notNull(webapp, "webapp");
111
112                Args.notNull(ctx, "ctx");
113
114                // store context in application's metadata ...
115                webapp.setMetaData(CONTEXT_KEY, ctx);
116                fieldValueFactory = new AnnotProxyFieldValueFactory(new ContextLocator(), wrapInProxies);
117                webapp.getBehaviorInstantiationListeners().add(this);
118                bind(webapp);
119        }
120
121        @Override
122        public void inject(final Object object)
123        {
124                inject(object, fieldValueFactory);
125        }
126
127        @Override
128        public void onInstantiation(final Component component)
129        {
130                inject(component);
131        }
132
133        @Override
134        public void onInstantiation(Behavior behavior)
135        {
136                inject(behavior);
137        }
138
139        /**
140         * A context locator that locates the context in application's metadata. This locator also keeps
141         * a transient cache of the lookup.
142         * 
143         * @author ivaynberg
144         */
145        private static class ContextLocator implements ISpringContextLocator
146        {
147                private transient ApplicationContext context;
148
149                private static final long serialVersionUID = 1L;
150
151                @Override
152                public ApplicationContext getSpringContext()
153                {
154                        if (context == null)
155                        {
156                                context = Application.get().getMetaData(CONTEXT_KEY);
157                        }
158                        return context;
159                }
160
161        }
162
163        /**
164         * Try to use an already pre-configured application context or locate it through Spring's default
165         * location mechanism.
166         * 
167         * @param webapp
168         * @return the application context to use for injection
169         */
170        private static ApplicationContext getDefaultContext(final WebApplication webapp)
171        {
172                ApplicationContext context = webapp.getMetaData(CONTEXT_KEY);
173                if (context == null)
174                {
175                        context = WebApplicationContextUtils.getRequiredWebApplicationContext(webapp.getServletContext());
176                }
177                return context;
178        }
179
180        /**
181         * Set the default context for the given webapp.
182         * 
183         * @param webapp
184         *            web application
185         * @param context
186         *            context to use as default if non is explicitely specified for the injector
187         */
188        public static void setDefaultContext(final WebApplication webapp, ApplicationContext context)
189        {
190                Args.notNull(context, "context");
191
192                if (webapp.getMetaData(CONTEXT_KEY) == null)
193                {
194                        webapp.setMetaData(CONTEXT_KEY, context);
195                }
196        }
197}