View Javadoc

1   /*
2    * $Id$
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.osgi;
23  
24  import com.opensymphony.xwork2.ActionContext;
25  import com.opensymphony.xwork2.ObjectFactory;
26  import com.opensymphony.xwork2.config.Configuration;
27  import com.opensymphony.xwork2.config.ConfigurationException;
28  import com.opensymphony.xwork2.config.PackageProvider;
29  import com.opensymphony.xwork2.config.entities.PackageConfig;
30  import com.opensymphony.xwork2.inject.Container;
31  import com.opensymphony.xwork2.inject.Inject;
32  import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
33  import com.opensymphony.xwork2.util.logging.Logger;
34  import com.opensymphony.xwork2.util.logging.LoggerFactory;
35  import org.apache.struts2.osgi.loaders.VelocityBundleResourceLoader;
36  import org.apache.struts2.views.velocity.VelocityManager;
37  import org.apache.velocity.app.Velocity;
38  import org.apache.commons.lang.xwork.StringUtils;
39  import org.osgi.framework.Bundle;
40  import org.osgi.framework.BundleContext;
41  import org.osgi.framework.BundleListener;
42  import org.osgi.framework.BundleEvent;
43  
44  import javax.servlet.ServletContext;
45  import java.util.ArrayList;
46  import java.util.HashMap;
47  import java.util.HashSet;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.Properties;
51  import java.util.Set;
52  
53  /***
54   * Struts package provider that starts the OSGi container and deelgates package loading
55   */
56  public class OsgiConfigurationProvider implements PackageProvider, BundleListener {
57      private static final Logger LOG = LoggerFactory.getLogger(OsgiConfigurationProvider.class);
58  
59      private Configuration configuration;
60      private ObjectFactory objectFactory;
61  
62      private OsgiHost osgiHost;
63      private BundleContext bundleContext;
64      private BundleAccessor bundleAccessor;
65      private boolean bundlesChanged = false;
66      private ServletContext servletContext;
67  
68      public void init(Configuration configuration) throws ConfigurationException {
69          osgiHost = (OsgiHost) servletContext.getAttribute(StrutsOsgiListener.OSGI_HOST);
70          bundleContext = osgiHost.getBundleContext();
71          bundleAccessor.setBundleContext(bundleContext);
72          bundleAccessor.setOsgiHost(osgiHost);
73          this.configuration = configuration;
74  
75          //this class loader interface can be used by other plugins to lookup resources
76          //from the bundles. A temporary class loader interface is set during other configuration
77          //loading as well
78          servletContext.setAttribute(ClassLoaderInterface.CLASS_LOADER_INTERFACE, new BundleClassLoaderInterface());
79      }
80  
81      public synchronized void loadPackages() throws ConfigurationException {
82          if (LOG.isTraceEnabled())
83              LOG.trace("Loading packages from XML and Convention on startup");                
84  
85          //init action contect
86          ActionContext ctx = ActionContext.getContext();
87          if (ctx == null) {
88              ctx = new ActionContext(new HashMap());
89              ActionContext.setContext(ctx);
90          }
91  
92          Set<String> bundleNames = new HashSet<String>();
93  
94          //iterate over the bundles and load packages from them
95          for (Bundle bundle : osgiHost.getBundles().values()) {
96              String bundleName = bundle.getSymbolicName();
97              if (shouldProcessBundle(bundle) && !bundleNames.contains(bundleName)) {
98                  bundleNames.add(bundleName);
99                  //load XML and COnvention config
100                 loadConfigFromBundle(bundle);
101             }
102         }
103 
104         bundlesChanged = false;
105         bundleContext.addBundleListener(this);
106     }
107 
108     /***
109      * Loads XML config as well as Convention config from a bundle
110      * Limitation: Constants and Beans are ignored on XML config
111      */
112     protected void loadConfigFromBundle(Bundle bundle) {
113         String bundleName = bundle.getSymbolicName();
114         if (LOG.isDebugEnabled())
115             LOG.debug("Loading packages from bundle [#0]", bundleName);
116 
117         //init action context
118         ActionContext ctx = ActionContext.getContext();
119         if (ctx == null) {
120             ctx = new ActionContext(new HashMap());
121             ActionContext.setContext(ctx);
122         }
123 
124         try {
125             //the Convention plugin will use BundleClassLoaderInterface from the ActionContext to find resources
126             //and load classes
127             ctx.put(ClassLoaderInterface.CLASS_LOADER_INTERFACE, new BundleClassLoaderInterface());
128             ctx.put(BundleAccessor.CURRENT_BUNDLE_NAME, bundleName);
129 
130             if (LOG.isTraceEnabled())
131                 LOG.trace("Loading XML config from bundle [#0]", bundleName);
132 
133             //XML config
134             PackageLoader loader = new BundlePackageLoader();
135             for (PackageConfig pkg : loader.loadPackages(bundle, bundleContext, objectFactory, configuration.getPackageConfigs())) {
136                 configuration.addPackageConfig(pkg.getName(), pkg);
137                 bundleAccessor.addPackageFromBundle(bundle, pkg.getName());
138             }
139 
140             //Convention
141             //get the existing packages before reloading the provider (se we can figure out what are the new packages)
142             Set<String> packagesBeforeLoading = new HashSet(configuration.getPackageConfigNames());
143 
144             PackageProvider conventionPackageProvider = configuration.getContainer().getInstance(PackageProvider.class, "convention.packageProvider");
145             if (conventionPackageProvider != null) {
146                 if (LOG.isTraceEnabled())
147                     LOG.trace("Loading Convention config from bundle [#0]", bundleName);
148                 conventionPackageProvider.loadPackages();
149             }
150 
151             Set<String> packagesAfterLoading = new HashSet(configuration.getPackageConfigNames());
152             packagesAfterLoading.removeAll(packagesBeforeLoading);
153             if (!packagesAfterLoading.isEmpty()) {
154                 //add the new packages to the map of bundle -> package
155                 for (String packageName : packagesAfterLoading)
156                     bundleAccessor.addPackageFromBundle(bundle, packageName);
157             }
158 
159             if (this.configuration.getRuntimeConfiguration() != null) {
160                 //if there is a runtime config, it meas that this method was called froma bundle start event
161                 //instead of the initial load, in that case, reload the config
162                 this.configuration.rebuildRuntimeConfiguration();
163             }
164         } finally {
165             ctx.put(BundleAccessor.CURRENT_BUNDLE_NAME, null);
166             ctx.put(ClassLoaderInterface.CLASS_LOADER_INTERFACE, null);
167         }
168     }
169 
170     /***
171      * Checks for "Struts2-Enabled" header in the bundle
172      */
173     protected boolean shouldProcessBundle(Bundle bundle) {
174         String strutsEnabled = (String) bundle.getHeaders().get(OsgiHost.OSGI_HEADER_STRUTS_ENABLED);
175 
176         return "true".equalsIgnoreCase(strutsEnabled);
177     }
178 
179     public synchronized boolean needsReload() {
180         return bundlesChanged;
181     }
182 
183     @Inject
184     public void setObjectFactory(ObjectFactory factory) {
185         this.objectFactory = factory;
186     }
187 
188     @Inject
189     public void setBundleAccessor(BundleAccessor acc) {
190         this.bundleAccessor = acc;
191     }
192 
193     @Inject
194     public void setVelocityManager(VelocityManager vm) {
195         Properties props = new Properties();
196         props.setProperty("osgi.resource.loader.description", "OSGI bundle loader");
197         props.setProperty("osgi.resource.loader.class", VelocityBundleResourceLoader.class.getName());
198         props.setProperty(Velocity.RESOURCE_LOADER, "strutsfile,strutsclass,osgi");
199         vm.setVelocityProperties(props);
200     }
201 
202     @Inject
203     public void setServletContext(ServletContext servletContext) {
204         this.servletContext = servletContext;
205     }
206 
207     public void destroy() {
208         try {
209             osgiHost.destroy();
210         } catch (Exception e) {
211             if (LOG.isErrorEnabled())
212                 LOG.error("Failed to stop OSGi container", e);
213         }
214     }
215 
216     /***
217      * Listens to bundle event to load/unload config
218      */
219     public void bundleChanged(BundleEvent bundleEvent) {
220         Bundle bundle = bundleEvent.getBundle();
221         String bundleName = bundle.getSymbolicName();
222         if (bundleName != null && shouldProcessBundle(bundle)) {
223             switch (bundleEvent.getType()) {
224                 case BundleEvent.STARTED:
225                     if (LOG.isTraceEnabled())
226                         LOG.trace("The bundlde [#0] has been activated and will be scanned for struts configuration", bundleName);
227                     loadConfigFromBundle(bundle);
228                     break;
229                 case BundleEvent.STOPPED:
230                     onBundleStopped(bundle);
231                     break;
232             }
233         }
234     }
235 
236     /***
237      * This method is called when a bundle is stopped, so the config that is related to it is removed
238      *
239      * @param bundle the bundle that stopped
240      */
241     protected void onBundleStopped(Bundle bundle) {
242         Set<String> packages = bundleAccessor.getPackagesByBundle(bundle);
243         if (!packages.isEmpty()) {
244             if (LOG.isTraceEnabled())
245                 LOG.trace("The bundle [#0] has been stopped. The packages [#1] will be disabled", bundle.getSymbolicName(), StringUtils.join(packages, ","));
246             for (String packageName : packages)
247                 configuration.removePackageConfig(packageName);
248         }
249     }
250 }