1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
76
77
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
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
95 for (Bundle bundle : osgiHost.getBundles().values()) {
96 String bundleName = bundle.getSymbolicName();
97 if (shouldProcessBundle(bundle) && !bundleNames.contains(bundleName)) {
98 bundleNames.add(bundleName);
99
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
118 ActionContext ctx = ActionContext.getContext();
119 if (ctx == null) {
120 ctx = new ActionContext(new HashMap());
121 ActionContext.setContext(ctx);
122 }
123
124 try {
125
126
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
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
141
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
155 for (String packageName : packagesAfterLoading)
156 bundleAccessor.addPackageFromBundle(bundle, packageName);
157 }
158
159 if (this.configuration.getRuntimeConfiguration() != null) {
160
161
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 }