001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/component/tags/sakai_2-4-1/component-api/component/src/java/org/sakaiproject/component/impl/SpringCompMgr.java $
003: * $Id: SpringCompMgr.java 22830 2007-03-17 22:47:40Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2005, 2006, 2007 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.component.impl;
021:
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FilenameFilter;
025: import java.net.InetAddress;
026: import java.net.UnknownHostException;
027: import java.util.Arrays;
028: import java.util.HashSet;
029: import java.util.Iterator;
030: import java.util.Properties;
031: import java.util.Set;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035: import org.sakaiproject.component.api.ComponentManager;
036: import org.sakaiproject.component.api.ComponentsLoader;
037: import org.sakaiproject.util.PropertyOverrideConfigurer;
038: import org.springframework.beans.factory.NoSuchBeanDefinitionException;
039: import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
040: import org.springframework.context.ConfigurableApplicationContext;
041: import org.springframework.context.support.SakaiApplicationContext;
042: import org.springframework.core.io.ClassPathResource;
043:
044: /**
045: * <p>
046: * SpringCompMgr manages API implementation components using the Springframework ApplicationContext.
047: * </p>
048: * <p>
049: * See the {@link org.sakaiproject.api.kernel.component.ComponentManager}interface for details.
050: * </p>
051: */
052: public class SpringCompMgr implements ComponentManager {
053: /** Our log (commons). */
054: private static Log M_log = LogFactory.getLog(SpringCompMgr.class);
055:
056: /** System property to control if we close on jvm shutdown (if set) or on the loss of our last child (if not set). */
057: protected final static String CLOSE_ON_SHUTDOWN = "sakai.component.closeonshutdown";
058:
059: /** The Spring Application Context. */
060: protected ConfigurableApplicationContext m_ac = null;
061:
062: /** The already created components given to manage (their interface names). */
063: protected Set m_loadedComponents = new HashSet();
064:
065: /** A count of the # of child AC's that call us parent. */
066: protected int m_childCount = 0;
067:
068: /** A set of properties used when configuring components. */
069: protected Properties m_config = null;
070:
071: /** Records that close has been called. */
072: protected boolean m_hasBeenClosed = false;
073:
074: /**
075: * Initialize.
076: *
077: * @param parent
078: * A ComponentManager in which this one gets nested, or NULL if this is this top one.
079: */
080: public SpringCompMgr(ComponentManager parent) {
081: // Note: don't init here, init after it's fully constructed
082: // (and if it's being constructed by the cover, after the cover has set it's instance variable).
083: // othewise when singletons are instantiated, if they call a Cover or Discovery in the init(),
084: // the component manager cover will not yet have this object! -ggolden
085: }
086:
087: /**
088: * Initialize the component manager.
089: */
090: public void init() {
091: if (m_ac != null)
092: return;
093:
094: m_ac = new SakaiApplicationContext();
095:
096: // load component packages
097: loadComponents();
098:
099: // if configured (with the system property CLOSE_ON_SHUTDOWN set), create a shutdown task to close when the JVM closes
100: // (otherwise we will close in removeChildAc() when the last child is gone)
101: if (System.getProperty(CLOSE_ON_SHUTDOWN) != null) {
102: Runtime.getRuntime().addShutdownHook(new Thread() {
103: public void run() {
104: close();
105: }
106: });
107: }
108:
109: // find a path to sakai files on the app server - if not set, set it
110: String sakaiHomePath = System.getProperty("sakai.home");
111: if (sakaiHomePath == null) {
112: String catalina = getCatalina();
113: if (catalina != null) {
114: sakaiHomePath = catalina + File.separatorChar + "sakai"
115: + File.separatorChar;
116: }
117: }
118:
119: // strange case...
120: if (sakaiHomePath == null) {
121: sakaiHomePath = File.separatorChar + "usr"
122: + File.separatorChar + "local" + File.separatorChar
123: + "sakai" + File.separatorChar;
124: }
125: if (!sakaiHomePath.endsWith(File.separator))
126: sakaiHomePath = sakaiHomePath + File.separatorChar;
127:
128: // make sure it's set properly
129: System.setProperty("sakai.home", sakaiHomePath);
130:
131: // check for the security home
132: String securityPath = System.getProperty("sakai.security");
133: if (securityPath != null) {
134: // make sure it's properly slashed
135: if (!securityPath.endsWith(File.separator))
136: securityPath = securityPath + File.separatorChar;
137: System.setProperty("sakai.security", securityPath);
138: }
139:
140: // Collect values from all the properties files: the later ones loaded override settings from prior.
141: m_config = new Properties();
142:
143: // start with the distributed defaults from the classpath
144: try {
145: ClassPathResource rsrc = new ClassPathResource(
146: "org/sakaiproject/config/sakai.properties");
147: if (rsrc.exists()) {
148: m_config.load(rsrc.getInputStream());
149: }
150: } catch (Throwable t) {
151: M_log.warn(t.getMessage(), t);
152: }
153:
154: // read all the files from the home path that are properties files
155: // TODO: not quite yet -ggolden
156: // readDirectoryPropertiesFiles(sakaiHomePath);
157:
158: // TODO: deprecated placeholder.properties from sakai.home - remove in a later version of Sakai -ggolden
159: readPropertyFile(
160: sakaiHomePath,
161: "placeholder.properties",
162: "Deprecated use of placeholder.properties. This file will not be read in future versions of Sakai. Merge its content with the sakai.properties file.");
163:
164: // these are potentially re-reading, but later wins over earlier, so we assure the order is preserved
165: readPropertyFile(sakaiHomePath, "sakai.properties");
166: readPropertyFile(sakaiHomePath, "local.properties");
167:
168: // add last the security.properties
169: readPropertyFile(securityPath, "security.properties");
170:
171: // auto-set the server id if missing
172: if (!m_config.containsKey("serverId")) {
173: try {
174: String id = InetAddress.getLocalHost().getHostName();
175: m_config.put("serverId", id);
176: } catch (UnknownHostException e) { // empty catch block
177: M_log.trace("UnknownHostException expected: "
178: + e.getMessage(), e);
179: }
180: }
181:
182: // post process the definitions from components with overrides from these properties
183: // - these get injected into the beans
184: try {
185: PropertyOverrideConfigurer pushProcessor = new PropertyOverrideConfigurer();
186: pushProcessor.setProperties(m_config);
187: pushProcessor.setIgnoreInvalidKeys(true);
188: pushProcessor.postProcessBeanFactory(m_ac.getBeanFactory());
189: } catch (Throwable t) {
190: M_log.warn(t.getMessage(), t);
191: }
192:
193: // post process the definitions from components (now overridden with our property overrides) to satisfy any placeholder
194: // values
195: try {
196: PropertyPlaceholderConfigurer pullProcessor = new PropertyPlaceholderConfigurer();
197: pullProcessor.setProperties(m_config);
198: pullProcessor.postProcessBeanFactory(m_ac.getBeanFactory());
199: } catch (Throwable t) {
200: M_log.warn(t.getMessage(), t);
201: }
202:
203: // set some system properties from the configuration values
204: promotePropertiesToSystem(m_config);
205:
206: // get our special log handler started before the rest
207: try {
208: m_ac
209: .getBean("org.sakaiproject.log.api.LogConfigurationManager");
210: } catch (Throwable t) {
211: M_log.warn(t.getMessage(), t);
212: }
213:
214: try {
215: // get the singletons loaded
216: m_ac.refresh();
217: } catch (Throwable t) {
218: M_log.warn(t.getMessage(), t);
219: }
220: }
221:
222: /**
223: * Get a sorted list of the properties files in the directory.
224: *
225: * @param directoryPath
226: * The directory.
227: * @return A sorted list of the properties files in the directory.
228: */
229: protected String[] getPropertyFileList(String directoryPath) {
230: File f = new File(directoryPath);
231: String[] filelist = f.list(new FilenameFilter() {
232: public boolean accept(File f, String s) {
233: return s.endsWith(".properties");
234: }
235: });
236:
237: Arrays.sort(filelist);
238: return filelist;
239: }
240:
241: /**
242: * Read in the properties files from this directory.
243: *
244: * @param directoryPath
245: * The directory.
246: */
247: protected void readDirectoryPropertiesFiles(String directoryPath) {
248: for (String propertiesFile : getPropertyFileList(directoryPath)) {
249: readPropertyFile(directoryPath, propertiesFile);
250: }
251: }
252:
253: /**
254: * Read in a property file.
255: *
256: * @param fileDirectory
257: * The file's path.
258: * @param propertyFileName
259: * The file name.
260: */
261: protected void readPropertyFile(String fileDirectory,
262: String propertyFileName) {
263: readPropertyFile(fileDirectory, propertyFileName, null);
264: }
265:
266: /**
267: * Read in a property file.
268: *
269: * @param fileDirectory
270: * The file's path.
271: * @param propertyFileName
272: * The file name.
273: * @param loadMessage
274: * A message to show after loading.
275: */
276: protected void readPropertyFile(String fileDirectory,
277: String propertyFileName, String loadMessage) {
278: try {
279: File f = new File(fileDirectory + propertyFileName);
280: if (f.exists()) {
281: m_config.load(new FileInputStream(f));
282:
283: if (loadMessage != null) {
284: M_log.warn(loadMessage);
285: }
286:
287: M_log.info("loaded properties file: " + fileDirectory
288: + propertyFileName);
289: }
290: } catch (Throwable t) {
291: M_log.warn(t.getMessage(), t);
292: }
293: }
294:
295: /**
296: * Access the ApplicationContext
297: *
298: * @return the ApplicationContext
299: */
300: public ConfigurableApplicationContext getApplicationContext() {
301: return m_ac;
302: }
303:
304: /**
305: * Finalize.
306: */
307: protected void finalize() {
308: close();
309: }
310:
311: /**
312: * {@inheritDoc}
313: */
314: public Object get(Class iface) {
315: Object component = null;
316:
317: try {
318: component = m_ac.getBean(iface.getName(), iface);
319: } catch (NoSuchBeanDefinitionException e) {
320: // This is an expected outcome, we don't usually want logs
321: if (M_log.isDebugEnabled())
322: M_log.debug("get(" + iface.getName() + "): " + e, e);
323: } catch (Throwable t) {
324: M_log.warn("get(" + iface.getName() + "): ", t);
325: }
326:
327: return component;
328: }
329:
330: /**
331: * {@inheritDoc}
332: */
333: public Object get(String ifaceName) {
334: Object component = null;
335:
336: try {
337: component = m_ac.getBean(ifaceName);
338: } catch (NoSuchBeanDefinitionException e) {
339: // This is an expected outcome, we don't usually want logs
340: if (M_log.isDebugEnabled())
341: M_log.debug("get(" + ifaceName + "): " + e, e);
342: } catch (Throwable t) {
343: M_log.warn("get(" + ifaceName + "): ", t);
344: }
345:
346: return component;
347: }
348:
349: /**
350: * {@inheritDoc}
351: */
352: public boolean contains(Class iface) {
353: boolean found = m_ac.containsBeanDefinition(iface.getName());
354:
355: return found;
356: }
357:
358: /**
359: * {@inheritDoc}
360: */
361: public boolean contains(String ifaceName) {
362: boolean found = m_ac.containsBeanDefinition(ifaceName);
363:
364: return found;
365: }
366:
367: /**
368: * {@inheritDoc}
369: */
370: public Set getRegisteredInterfaces() {
371: Set rv = new HashSet();
372:
373: // get the registered ones
374: String[] names = m_ac.getBeanDefinitionNames();
375: for (int i = 0; i < names.length; i++) {
376: rv.add(names[i]);
377: }
378:
379: // add the loaded ones
380: for (Iterator iLoaded = m_loadedComponents.iterator(); iLoaded
381: .hasNext();) {
382: String loaded = (String) iLoaded.next();
383: rv.add(loaded);
384: }
385:
386: return rv;
387: }
388:
389: /**
390: * {@inheritDoc}
391: */
392: public void close() {
393: m_hasBeenClosed = true;
394: m_ac.close();
395: }
396:
397: /**
398: * {@inheritDoc}
399: */
400: public void loadComponent(Class iface, Object component) {
401: // Spring doesn't list these in getBeanDefinitionNames, so we keep track
402: m_loadedComponents.add(iface.getName());
403:
404: m_ac.getBeanFactory().registerSingleton(iface.getName(),
405: component);
406: }
407:
408: /**
409: * {@inheritDoc}
410: */
411: public void loadComponent(String ifaceName, Object component) {
412: // Spring doesn't list these in getBeanDefinitionNames, so we keep track
413: m_loadedComponents.add(ifaceName);
414:
415: m_ac.getBeanFactory().registerSingleton(ifaceName, component);
416: }
417:
418: /**
419: * Locate the component loader, and load any available components.
420: */
421: protected void loadComponents() {
422: // see if we can find a components loader
423: ComponentsLoader loader = null;
424:
425: // TODO: configure this?
426: String loaderClassName = "org.sakaiproject.util.ComponentsLoader";
427:
428: // first see if it can be located via the thread class loader
429: try {
430: loader = (ComponentsLoader) Thread.currentThread()
431: .getContextClassLoader().loadClass(loaderClassName)
432: .newInstance();
433: } catch (Throwable any) { // empty catch block
434: M_log.trace("Expected Throwable: " + any.getMessage(), any);
435: }
436:
437: // next try this class's loader
438: if (loader == null) {
439: try {
440: loader = (ComponentsLoader) getClass().getClassLoader()
441: .loadClass(loaderClassName).newInstance();
442: } catch (Throwable any) { // empty catch block
443: M_log.trace("Expected Throwable: " + any.getMessage(),
444: any);
445: }
446: }
447:
448: // if we found the class
449: if (loader != null) {
450: // locate the components root
451: // if we have our system property set, use it
452: String componentsRoot = System
453: .getProperty(SAKAI_COMPONENTS_ROOT_SYS_PROP);
454: if (componentsRoot == null) {
455: // if we are in Catalina, place it at ${catalina.home}/components/
456: String catalina = getCatalina();
457: if (catalina != null) {
458: componentsRoot = catalina + File.separatorChar
459: + "components" + File.separatorChar;
460: }
461: }
462:
463: if (componentsRoot == null) {
464: M_log
465: .warn("loadComponents: cannot estabish a root directory for the components packages");
466: return;
467: }
468:
469: // make sure this is set
470: System.setProperty(SAKAI_COMPONENTS_ROOT_SYS_PROP,
471: componentsRoot);
472:
473: // load components
474: loader.load(this , componentsRoot);
475: }
476:
477: else {
478: M_log
479: .warn("loadComponents: no component loader class found");
480: }
481: }
482:
483: /**
484: * Increment the count of ACs that call this one parent.
485: */
486: public synchronized void addChildAc() {
487: m_childCount++;
488: }
489:
490: /**
491: * Decrement the count of ACs that call this one parent.
492: */
493: public synchronized void removeChildAc() {
494: m_childCount--;
495:
496: // if we are not using the shutdown hook, close() when the m_childCount == 0
497: if ((m_childCount == 0)
498: && (System.getProperty(CLOSE_ON_SHUTDOWN) == null)) {
499: close();
500: }
501: }
502:
503: /**
504: * Check the environment for catalina's base or home directory.
505: *
506: * @return Catalina's base or home directory.
507: */
508: protected String getCatalina() {
509: String catalina = System.getProperty("catalina.base");
510: if (catalina == null) {
511: catalina = System.getProperty("catalina.home");
512: }
513:
514: return catalina;
515: }
516:
517: /**
518: * If the properties has any of the values we need to set as sakai system properties, set them.
519: *
520: * @param props
521: * The property override configurer with some override settings.
522: */
523: protected void promotePropertiesToSystem(Properties props) {
524: String serverId = props.getProperty("serverId");
525: if (serverId != null) {
526: System.setProperty("sakai.serverId", serverId);
527: }
528:
529: // for the request filter
530: String uploadMax = props.getProperty("content.upload.max");
531: if (uploadMax != null) {
532: System.setProperty("sakai.content.upload.max", uploadMax);
533: }
534:
535: // for the request filter
536: String uploadCeiling = props
537: .getProperty("content.upload.ceiling");
538: if (uploadCeiling != null) {
539: System.setProperty("sakai.content.upload.ceiling",
540: uploadCeiling);
541: }
542:
543: // for the request filter
544: String uploadDir = props.getProperty("content.upload.dir");
545: if (uploadDir != null) {
546: System.setProperty("sakai.content.upload.dir", uploadDir);
547: }
548:
549: if (props.getProperty("force.url.secure") != null) {
550: try {
551: // make sure it is an int
552: int port = Integer.parseInt(props
553: .getProperty("force.url.secure"));
554: System.setProperty("sakai.force.url.secure", props
555: .getProperty("force.url.secure"));
556: } catch (Throwable e) {
557: M_log
558: .warn(
559: "force.url.secure set to a non numeric value: "
560: + props
561: .getProperty("force.url.secure"),
562: e);
563: }
564: }
565: }
566:
567: /**
568: * @inheritDoc
569: */
570: public Properties getConfig() {
571: return m_config;
572: }
573:
574: /**
575: * @inheritDoc
576: */
577: public void waitTillConfigured() {
578: // Nothing really to do - the cover takes care of this -ggolden
579: }
580:
581: /**
582: * @inheritDoc
583: */
584: public boolean hasBeenClosed() {
585: return m_hasBeenClosed;
586: }
587: }
|