001/*
002 * Copyright 2002-2013 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jca.context;
018
019import javax.resource.NotSupportedException;
020import javax.resource.ResourceException;
021import javax.resource.spi.ActivationSpec;
022import javax.resource.spi.BootstrapContext;
023import javax.resource.spi.ResourceAdapter;
024import javax.resource.spi.ResourceAdapterInternalException;
025import javax.resource.spi.endpoint.MessageEndpointFactory;
026import javax.transaction.xa.XAResource;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.beans.factory.support.BeanDefinitionRegistry;
032import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
033import org.springframework.context.ConfigurableApplicationContext;
034import org.springframework.core.env.ConfigurableEnvironment;
035import org.springframework.core.env.StandardEnvironment;
036import org.springframework.util.ObjectUtils;
037import org.springframework.util.StringUtils;
038
039/**
040 * JCA 1.5 {@link javax.resource.spi.ResourceAdapter} implementation
041 * that loads a Spring {@link org.springframework.context.ApplicationContext},
042 * starting and stopping Spring-managed beans as part of the ResourceAdapter's
043 * lifecycle.
044 *
045 * <p>Ideal for application contexts that do not need any HTTP entry points
046 * but rather just consist of message endpoints and scheduled jobs etc.
047 * Beans in such a context may use application server resources such as the
048 * JTA transaction manager and JNDI-bound JDBC DataSources and JMS
049 * ConnectionFactory instances, and may also register with the platform's
050 * JMX server - all through Spring's standard transaction management and
051 * JNDI and JMX support facilities.
052 *
053 * <p>If the need for scheduling asynchronous work arises, consider using
054 * Spring's {@link org.springframework.jca.work.WorkManagerTaskExecutor}
055 * as a standard bean definition, to be injected into application beans
056 * through dependency injection. This WorkManagerTaskExecutor will automatically
057 * use the JCA WorkManager from the BootstrapContext that has been provided
058 * to this ResourceAdapter.
059 *
060 * <p>The JCA {@link javax.resource.spi.BootstrapContext} may also be
061 * accessed directly, through application components that implement the
062 * {@link BootstrapContextAware} interface. When deployed using this
063 * ResourceAdapter, the BootstrapContext is guaranteed to be passed on
064 * to such components.
065 *
066 * <p>This ResourceAdapter is to be defined in a "META-INF/ra.xml" file
067 * within a J2EE ".rar" deployment unit like as follows:
068 *
069 * <pre class="code">
070 * &lt;?xml version="1.0" encoding="UTF-8"?&gt;
071 * &lt;connector xmlns="http://java.sun.com/xml/ns/j2ee"
072 *               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
073 *               xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee https://java.sun.com/xml/ns/j2ee/connector_1_5.xsd"
074 *               version="1.5"&gt;
075 *       &lt;vendor-name&gt;Spring Framework&lt;/vendor-name&gt;
076 *       &lt;eis-type&gt;Spring Connector&lt;/eis-type&gt;
077 *       &lt;resourceadapter-version&gt;1.0&lt;/resourceadapter-version&gt;
078 *       &lt;resourceadapter&gt;
079 *               &lt;resourceadapter-class&gt;org.springframework.jca.context.SpringContextResourceAdapter&lt;/resourceadapter-class&gt;
080 *               &lt;config-property&gt;
081 *                       &lt;config-property-name&gt;ContextConfigLocation&lt;/config-property-name&gt;
082 *                       &lt;config-property-type&gt;java.lang.String&lt;/config-property-type&gt;
083 *                       &lt;config-property-value&gt;META-INF/applicationContext.xml&lt;/config-property-value&gt;
084 *               &lt;/config-property&gt;
085 *       &lt;/resourceadapter&gt;
086 * &lt;/connector&gt;</pre>
087 *
088 * Note that "META-INF/applicationContext.xml" is the default context config
089 * location, so it doesn't have to specified unless you intend to specify
090 * different/additional config files. So in the default case, you may remove
091 * the entire {@code config-property} section above.
092 *
093 * <p><b>For simple deployment needs, all you need to do is the following:</b>
094 * Package all application classes into a RAR file (which is just a standard
095 * JAR file with a different file extension), add all required library jars
096 * into the root of the RAR archive, add a "META-INF/ra.xml" deployment
097 * descriptor as shown above as well as the corresponding Spring XML bean
098 * definition file(s) (typically "META-INF/applicationContext.xml"),
099 * and drop the resulting RAR file into your application server's
100 * deployment directory!
101 *
102 * @author Juergen Hoeller
103 * @since 2.5
104 * @see #setContextConfigLocation
105 * @see #loadBeanDefinitions
106 * @see ResourceAdapterApplicationContext
107 */
108public class SpringContextResourceAdapter implements ResourceAdapter {
109
110        /**
111         * Any number of these characters are considered delimiters between
112         * multiple context config paths in a single String value.
113         * @see #setContextConfigLocation
114         */
115        public static final String CONFIG_LOCATION_DELIMITERS = ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS;
116
117        public static final String DEFAULT_CONTEXT_CONFIG_LOCATION = "META-INF/applicationContext.xml";
118
119
120        protected final Log logger = LogFactory.getLog(getClass());
121
122        private String contextConfigLocation = DEFAULT_CONTEXT_CONFIG_LOCATION;
123
124        private ConfigurableApplicationContext applicationContext;
125
126
127        /**
128         * Set the location of the context configuration files, within the
129         * resource adapter's deployment unit. This can be a delimited
130         * String that consists of multiple resource location, separated
131         * by commas, semicolons, whitespace, or line breaks.
132         * <p>This can be specified as "ContextConfigLocation" config
133         * property in the {@code ra.xml} deployment descriptor.
134         * <p>The default is "classpath:META-INF/applicationContext.xml".
135         */
136        public void setContextConfigLocation(String contextConfigLocation) {
137                this.contextConfigLocation = contextConfigLocation;
138        }
139
140        /**
141         * Return the specified context configuration files.
142         */
143        protected String getContextConfigLocation() {
144                return this.contextConfigLocation;
145        }
146
147        /**
148         * Return a new {@link StandardEnvironment}.
149         * <p>Subclasses may override this method in order to supply
150         * a custom {@link ConfigurableEnvironment} implementation.
151         */
152        protected ConfigurableEnvironment createEnvironment() {
153                return new StandardEnvironment();
154        }
155
156        /**
157         * This implementation loads a Spring ApplicationContext through the
158         * {@link #createApplicationContext} template method.
159         */
160        @Override
161        public void start(BootstrapContext bootstrapContext) throws ResourceAdapterInternalException {
162                if (logger.isInfoEnabled()) {
163                        logger.info("Starting SpringContextResourceAdapter with BootstrapContext: " + bootstrapContext);
164                }
165                this.applicationContext = createApplicationContext(bootstrapContext);
166        }
167
168        /**
169         * Build a Spring ApplicationContext for the given JCA BootstrapContext.
170         * <p>The default implementation builds a {@link ResourceAdapterApplicationContext}
171         * and delegates to {@link #loadBeanDefinitions} for actually parsing the
172         * specified configuration files.
173         * @param bootstrapContext this ResourceAdapter's BootstrapContext
174         * @return the Spring ApplicationContext instance
175         */
176        protected ConfigurableApplicationContext createApplicationContext(BootstrapContext bootstrapContext) {
177                ResourceAdapterApplicationContext applicationContext =
178                                new ResourceAdapterApplicationContext(bootstrapContext);
179                // Set ResourceAdapter's ClassLoader as bean class loader.
180                applicationContext.setClassLoader(getClass().getClassLoader());
181                // Extract individual config locations.
182                String[] configLocations =
183                                StringUtils.tokenizeToStringArray(getContextConfigLocation(), CONFIG_LOCATION_DELIMITERS);
184                if (configLocations != null) {
185                        loadBeanDefinitions(applicationContext, configLocations);
186                }
187                applicationContext.refresh();
188                return applicationContext;
189        }
190
191        /**
192         * Load the bean definitions into the given registry,
193         * based on the specified configuration files.
194         * @param registry the registry to load into
195         * @param configLocations the parsed config locations
196         * @see #setContextConfigLocation
197         */
198        protected void loadBeanDefinitions(BeanDefinitionRegistry registry, String[] configLocations) {
199                new XmlBeanDefinitionReader(registry).loadBeanDefinitions(configLocations);
200        }
201
202        /**
203         * This implementation closes the Spring ApplicationContext.
204         */
205        @Override
206        public void stop() {
207                logger.info("Stopping SpringContextResourceAdapter");
208                this.applicationContext.close();
209        }
210
211
212        /**
213         * This implementation always throws a NotSupportedException.
214         */
215        @Override
216        public void endpointActivation(MessageEndpointFactory messageEndpointFactory, ActivationSpec activationSpec)
217                        throws ResourceException {
218
219                throw new NotSupportedException("SpringContextResourceAdapter does not support message endpoints");
220        }
221
222        /**
223         * This implementation does nothing.
224         */
225        @Override
226        public void endpointDeactivation(MessageEndpointFactory messageEndpointFactory, ActivationSpec activationSpec) {
227        }
228
229        /**
230         * This implementation always returns {@code null}.
231         */
232        @Override
233        public XAResource[] getXAResources(ActivationSpec[] activationSpecs) throws ResourceException {
234                return null;
235        }
236
237
238        @Override
239        public boolean equals(Object obj) {
240                return (obj instanceof SpringContextResourceAdapter &&
241                                ObjectUtils.nullSafeEquals(getContextConfigLocation(),
242                                                ((SpringContextResourceAdapter) obj).getContextConfigLocation()));
243        }
244
245        @Override
246        public int hashCode() {
247                return ObjectUtils.nullSafeHashCode(getContextConfigLocation());
248        }
249
250}