Source code: er/extensions/ERXConfigurationManager.java
1 /*
2 * Copyright (C) NetStruxr, Inc. All rights reserved.
3 *
4 * This software is published under the terms of the NetStruxr
5 * Public Software License version 0.5, a copy of which has been
6 * included with this distribution in the LICENSE.NPL file. */
7 package er.extensions;
8
9 import com.webobjects.foundation.*;
10 import com.webobjects.eocontrol.*;
11 import com.webobjects.eoaccess.*;
12 import com.webobjects.appserver.*;
13 import java.util.*;
14 import java.io.*;
15 import java.lang.reflect.*;
16
17 /**
18 * <code>Configuration Manager</code> handles rapid turnaround for
19 * system configuration as well as swizzling of the EOModel connection
20 * dictionaries.
21 * <p>
22 * <strong>Placing configuration parameters</strong>
23 * <p>
24 * You can provide the system configuration by the following ways:<br/>
25 * Note: This is the standard way for WebObjects 5.x applications.
26 * <ul>
27 * <li><code>Properties</code> file under the Resources group of the
28 * application and framework project.
29 * It's a {@link java.util.Properties} file and Project Wonder's
30 * standard project templates include it. (The templates won't
31 * be available on some platforms at this moment.)</li>
32 *
33 * <li><code>WebObjects.properties</code> under the user home directory;
34 * same format to Properties. <br/>
35 * Note that the user home directory depends on the user who
36 * launch the application. They may change between the
37 * developent and deployment time.</li>
38 *
39 * <li>Command line arguments<br/>
40 * For example: <code>-WOCachingEnabled false -com.webobjects.pid $$</code></br>
41 * Don't forget to put a dash "-" before the key.</li>
42 * </ul>
43 * <p>
44 * <strong>Loading order of the configuration parameters</strong>
45 * <p>
46 * When the application launches, configuration parameters will
47 * be loaded by the following order. ERXConfigurationManager trys
48 * to reload them by the exactly same order when one of those
49 * configuration files changes.
50 * <p>
51 * 1. Properties in frameworks that the application links to<br/>
52 * 2. Properties in the application<br/>
53 * 3. WebObjects.properties under the home directory<br/>
54 * 4. Command line arguments<br/>
55 * <p>
56 * If there is a conflicting parameter between the files and
57 * arguments, the latter one overrides the earlier one.
58 * <p>
59 * Note that the order between frameworks does not seems
60 * to be specified. You should not put conflicting parameters
61 * between framework Properties files. On the other hand,
62 * the application Properties should be always loaded after
63 * all framework Properties are loaded. You can safely
64 * override parameters on the frameworks from the applications
65 * Properties.
66 *
67 *
68 *
69 * <p>
70 * <strong>Changing the connection dictionary</strong>
71 * <p>
72 * To do this for Oracle you can either specify on a per model basis
73 * or on a global basis.
74 * <pre>
75 * <strong>Global:</strong>
76 * dbConnectServerGLOBAL = myDatabaseServer
77 * dbConnectUserGLOBAL = me
78 * dbConnectPasswordGLOBAL = secret
79 * <strong>Per Model for say model ER:</strong>
80 * ER.DBServer = myDatabaseServer
81 * ER.DBUser = me
82 * ER.DBPassword = secret
83 *
84 * <strong>Openbase:</strong> same, with DBDatabase and DBHostname
85 *
86 * <strong>JDBC:</strong> same with urlGlobal, or db.url
87 *
88 * </pre>
89 * <p>
90 * Prototypes can be swapped globally or per model either by
91 * hydrating an archived prototype entity for a file or from
92 * another entity.
93 */
94 public class ERXConfigurationManager {
95
96 /** logging support */
97 public static final ERXLogger log = ERXLogger.getERXLogger(ERXConfigurationManager.class);
98
99 /**
100 * Notification posted when the configuration is updated.
101 * The Java system properties is the part of the configuration.
102 */
103 public static final String ConfigurationDidChangeNotification = "ConfigurationDidChangeNotification";
104
105 /** Configuration manager singleton */
106 static ERXConfigurationManager defaultManager = null;
107
108 private String[] _commandLineArguments;
109 private NSMutableArray _monitoredProperties;
110 private boolean _isInitialized = false;
111 private boolean _isRapidTurnAroundInitialized = false;
112
113 /** Private constructor to prevent instantiation from outside the class */
114 private ERXConfigurationManager() {
115 /* empty */
116 }
117
118 /**
119 * Returns the single instance of this class
120 *
121 * @return the configuration manager
122 */
123 public static ERXConfigurationManager defaultManager() {
124 if (defaultManager == null)
125 defaultManager = new ERXConfigurationManager();
126 return defaultManager;
127 }
128
129 /**
130 * Returns the command line arguments.
131 * {@link ERXApplication#main} sets this value.
132 *
133 * @return the command line arguments as a String[]
134 * @see #setCommandLineArguments
135 */
136 public String[] commandLineArguments() {
137 return _commandLineArguments;
138 }
139
140 /**
141 * Sets the command line arguments.
142 * {@link ERXApplication#main} will call this method
143 * when the application starts up.
144 *
145 * @see #commandLineArguments
146 */
147 public void setCommandLineArguments(String [] newCommandLineArguments) {
148 _commandLineArguments = newCommandLineArguments;
149 }
150
151 /**
152 * Initializes the configuration manager.
153 * The framework principal {@link ERXExtensions} calles
154 * this method when the ERExtensions framework is loaded.
155 */
156 public void initialize() {
157 if (! _isInitialized) {
158 _isInitialized = true;
159 NSNotificationCenter.defaultCenter().addObserver(this,
160 new NSSelector("modelAddedHandler", ERXConstant.NotificationClassArray),
161 EOModelGroup.ModelAddedNotification,
162 null);
163 }
164 }
165
166 /**
167 * Sets up the system for rapid turnaround mode. It will watch the
168 * changes on Properties files in application and framework bundles
169 * and WebObjects.properties under the home directory.
170 * Rapid turnaround mode will only be enabled if there are such
171 * files available and system has WOCaching disabled.
172 */
173 public void configureRapidTurnAround() {
174 if (_isRapidTurnAroundInitialized) return;
175
176 _isRapidTurnAroundInitialized = true;
177
178 if (WOApplication.application()!=null && WOApplication.application().isCachingEnabled()) {
179 log.info("WOCachingEnabled is true. Disabling the rapid turnaround for Properties files");
180 return;
181 }
182
183 NSArray propertyPaths = ERXProperties.pathsForUserAndBundleProperties(/* logging */ true);
184 _monitoredProperties = new NSMutableArray();
185
186 Enumeration e = propertyPaths.objectEnumerator();
187 while (e.hasMoreElements()) {
188 String path = (String) e.nextElement();
189 try {
190 ERXFileNotificationCenter.defaultCenter().addObserver(this,
191 new NSSelector("updateSystemProperties", ERXConstant.NotificationClassArray),
192 path);
193 _monitoredProperties.addObject(path);
194 log.debug("Registered: " + path);
195 } catch (Exception ex) {
196 log.error("An exception occured while registering the observer for the "
197 + "logging configuration file: "
198 + ex.getClass().getName() + " " + ex.getMessage());
199 }
200 }
201 }
202
203 /**
204 * Updates the configuration from the current configuration and
205 * posts {@link #ConfigurationDidChangeNotification}. It also
206 * calls {@link ERXLogger#configureLogging} to reconfigure
207 * the logging system.
208 * <p>
209 * The configuration files: Properties and WebObjects.properties
210 * files are reloaded to the Java system properties by the same
211 * order to the when the system starts up. Then the command line
212 * arguments will be applied to the properties again so that
213 * the configuration will be consistent during the application
214 * lifespan.
215 * <p>
216 * This method is called when rapid turnaround is enabled and one
217 * of the configuration files changes.
218 *
219 * @param n NSNotification object for the event
220 */
221 public synchronized void updateSystemProperties(NSNotification n) {
222 _updateSystemPropertiesFromMonitoredProperties((File)n.object(), _monitoredProperties);
223 if (_commandLineArguments != null && _commandLineArguments.length > 0)
224 _reinsertCommandLineArgumentsToSystemProperties(_commandLineArguments);
225 ERXLogger.configureLogging(System.getProperties());
226
227 NSNotificationCenter.defaultCenter().postNotification(ConfigurationDidChangeNotification, null);
228 }
229
230 private void _updateSystemPropertiesFromMonitoredProperties(File updatedFile, NSArray monitoredProperties) {
231 if (monitoredProperties == null || monitoredProperties.count() == 0) return;
232
233 String updatedFilePath = null;
234 try {
235 updatedFilePath = updatedFile.getCanonicalPath();
236 } catch (IOException ex) {
237 log.error(ex.toString());
238 return;
239 }
240
241 Properties systemProperties = System.getProperties();
242 // Find the position of the updatedFile in the monitoredProperties list,
243 // then reload it and everything after it on the list.
244 for (int i = monitoredProperties.indexOfObject(updatedFilePath);
245 0 <= i && i < _monitoredProperties.count(); i++) {
246 String monitoredPropertiesPath = (String) _monitoredProperties.objectAtIndex(i);
247 Properties loadedProperty = ERXProperties.propertiesFromPath(monitoredPropertiesPath);
248 ERXProperties.transferPropertiesFromSourceToDest(loadedProperty, systemProperties);
249 }
250 }
251
252 private void _reinsertCommandLineArgumentsToSystemProperties(String[] commandLineArguments) {
253 Properties commandLineProperties = ERXProperties.propertiesFromArgv(commandLineArguments);
254 Properties systemProperties = System.getProperties();
255 ERXProperties.transferPropertiesFromSourceToDest(commandLineProperties, systemProperties);
256 log.info("Reinserted the command line arguments to the system properties.");
257 }
258
259 //CHECKME: shouldn't this use ERXProperties.stringForKey?
260 private String stringForKey(String key) { return System.getProperty(key); }
261
262 public void modelAddedHandler(NSNotification n) {
263 resetConnectionDictionaryInModel((EOModel)n.object());
264 }
265
266 /* reset the connection dictionary to the specified values that are in the defaults.
267 This method will look for defaults in the form
268 <MODELNAME>.DBServer
269 <MODELNAME>.DBUser
270 <MODELNAME>.DBPassword
271 <MODELNAME>.URL (for JDBC)
272 if the serverName and username both exists, we overwrite the connection dict
273 (password is optional). Otherwise we fall back to what's in the model.
274 */
275 public void resetConnectionDictionaryInModel(EOModel aModel) {
276 if(aModel!=null) {
277 String aModelName=aModel.name();
278 log.debug("Adjusting "+aModelName);
279 NSMutableDictionary newConnectionDictionary=null;
280 if (aModel.adaptorName().indexOf("Oracle")!=-1) {
281 String serverName= stringForKey(aModelName + ".DBServer");
282 serverName=serverName==null ? stringForKey("dbConnectServerGLOBAL") : serverName;
283 String userName= stringForKey(aModelName + ".DBUser");
284 userName= userName ==null ? stringForKey("dbConnectUserGLOBAL") : userName;
285 String passwd= stringForKey(aModelName + ".DBPassword");
286 passwd= passwd ==null ? stringForKey("dbConnectPasswordGLOBAL") : passwd;
287
288 if((serverName!=null) || (userName!=null) || (passwd!=null)) {
289 newConnectionDictionary=new NSMutableDictionary(aModel.connectionDictionary());
290 if (serverName!=null) newConnectionDictionary.setObjectForKey(serverName,"serverId");
291 if (userName!=null) newConnectionDictionary.setObjectForKey(userName,"userName");
292 if (passwd!=null) newConnectionDictionary.setObjectForKey(passwd,"password");
293 aModel.setConnectionDictionary(newConnectionDictionary);
294 }
295
296 } else if (aModel.adaptorName().indexOf("Flat")!=-1) {
297 String path= stringForKey(aModelName + ".DBPath");
298 path = path ==null ? stringForKey("dbConnectPathGLOBAL") : path;
299 if (path!=null) {
300 if (path.indexOf(" ")!=-1) {
301 NSArray a=NSArray.componentsSeparatedByString(path," ");
302 //System.out.println("found "+a);
303 if (a.count()==2) {
304 path =WOApplication.application().resourceManager().pathForResourceNamed((String)a.objectAtIndex(0),
305 (String)a.objectAtIndex(1),
306 null);
307 //System.out.println("path= "+path);
308 }
309 }
310 } else {
311 // by default we take <modelName>.db in the directory we found the model
312 path=aModel.path();
313 path=NSPathUtilities.stringByDeletingLastPathComponent(path);
314 path=NSPathUtilities.stringByAppendingPathComponent(path,aModel.name()+".db");
315 }
316 newConnectionDictionary=new NSMutableDictionary(aModel.connectionDictionary());
317 if (path!=null) newConnectionDictionary.setObjectForKey(path,"path");
318 if (operatingSystem()==WindowsOperatingSystem)
319 newConnectionDictionary.setObjectForKey("\r\n","rowSeparator");
320 aModel.setConnectionDictionary(newConnectionDictionary);
321 } else if (aModel.adaptorName().indexOf("OpenBase")!=-1) {
322 String db= stringForKey(aModelName + ".DBDatabase");
323 db = db ==null ? stringForKey("dbConnectDatabaseGLOBAL") : db;
324 String h= stringForKey(aModelName + ".DBHostName");
325 h = h ==null ? stringForKey("dbConnectHostNameGLOBAL") : h;
326 if (h!=null || db!=null) {
327 newConnectionDictionary=new NSMutableDictionary(aModel.connectionDictionary());
328 if (db!=null) newConnectionDictionary.setObjectForKey(db, "databaseName");
329 if (h!=null) newConnectionDictionary.setObjectForKey(h, "hostName");
330 aModel.setConnectionDictionary(newConnectionDictionary);
331 }
332 } else if (aModel.adaptorName().indexOf("JDBC")!=-1) {
333 String url= stringForKey(aModelName + ".URL");
334 url = url ==null ? stringForKey("dbConnectURLGLOBAL") : url;
335 String userName= stringForKey(aModelName + ".DBUser");
336 userName= userName ==null ? stringForKey("dbConnectUserGLOBAL") : userName;
337 String passwd= stringForKey(aModelName + ".DBPassword");
338 passwd= passwd ==null ? stringForKey("dbConnectPasswordGLOBAL") : passwd;
339 String driver= stringForKey(aModelName + ".DBDriver");
340 driver= driver ==null ? stringForKey("dbConnectDriverGLOBAL") : driver;
341 String jdbcInfo= stringForKey(aModelName + ".DBJDBCInfo");
342 jdbcInfo= jdbcInfo ==null ? stringForKey("dbConnectJDBCInfoGLOBAL") : jdbcInfo;
343 String plugin= stringForKey(aModelName + ".DBPlugin");
344 plugin= plugin ==null ? stringForKey("dbConnectPluginGLOBAL") : plugin;
345 if (url!=null || userName!=null || passwd!=null || driver!=null || jdbcInfo!=null || plugin!=null) {
346 newConnectionDictionary=new NSMutableDictionary(aModel.connectionDictionary());
347 if (url!=null) newConnectionDictionary.setObjectForKey(url, "URL");
348 if (userName!=null) newConnectionDictionary.setObjectForKey(userName,"username");
349 if (passwd!=null) newConnectionDictionary.setObjectForKey(passwd,"password");
350 if (driver!=null) newConnectionDictionary.setObjectForKey(driver,"driver");
351 if (jdbcInfo!=null) {
352 NSDictionary d=(NSDictionary)NSPropertyListSerialization.propertyListFromString(jdbcInfo);
353 if (d!=null)
354 newConnectionDictionary.setObjectForKey(d,"jdbc2Info");
355 else
356 newConnectionDictionary.removeObjectForKey("jdbc2Info");
357 }
358 if (plugin!=null) newConnectionDictionary.setObjectForKey(plugin,"plugin");
359 aModel.setConnectionDictionary(newConnectionDictionary);
360 }
361 }
362
363 if (newConnectionDictionary!=null && log.isDebugEnabled()) {
364 if (newConnectionDictionary.objectForKey("password")!=null)
365 newConnectionDictionary.setObjectForKey("<deleted for log>", "password");
366 log.debug("New Connection Dictionary for "+aModelName+": "+newConnectionDictionary);
367 }
368
369
370 // based on an idea from Stefan Apelt <stefan@tetlabors.de>
371 String f = stringForKey(aModelName + ".EOPrototypesFile");
372 f = f ==null ? stringForKey("EOPrototypesFileGLOBAL") : f;
373 if(f != null) {
374 NSDictionary dict = (NSDictionary)NSPropertyListSerialization.propertyListFromString(ERXStringUtilities.stringFromResource(f, "", null));
375 if(dict != null) {
376 if (log.isDebugEnabled()) log.debug("Adjusting prototypes from " + f);
377 EOEntity proto = aModel.entityNamed("EOPrototypes");
378 if (proto == null) {
379 log.warn("No prototypes found in model named \"" + aModelName + "\", although the EOPrototypesFile default was set!");
380 } else {
381 aModel.removeEntity(proto);
382 proto = new EOEntity(dict, aModel);
383 proto.awakeWithPropertyList(dict);
384 aModel.addEntity(proto);
385 }
386 }
387 }
388 String e = stringForKey(aModelName + ".EOPrototypesEntity");
389 // global prototype setting not supported yet
390 //e = e ==null ? stringForKey("EOPrototypesEntityGLOBAL") : e;
391 if(e != null) {
392 // we look for the entity globally so we can have one prototype entity
393 EOEntity newPrototypeEntity = aModel.modelGroup().entityNamed(e);
394 if (newPrototypeEntity == null) {
395 log.warn("Prototype Entity named "+e+" not found in model "+aModel.name());
396 } else {
397 if (log.isDebugEnabled()) log.debug("Adjusting prototypes to those from entity " + e);
398
399 EOEntity proto = aModel.entityNamed("EOPrototypes");
400 if(proto != null) aModel.removeEntity(proto);
401
402 aModel.removeEntity(newPrototypeEntity);
403 newPrototypeEntity.setName("EOPrototypes");
404 aModel.addEntity(newPrototypeEntity);
405 }
406 }
407 }
408
409 }
410
411
412 public final static int WindowsOperatingSystem=1;
413 public final static int MacOSXOperatingSystem=2;
414 public final static int SolarisOperatingSystem=3;
415 public final static int UnknownOperatingSystem=3;
416
417 private int _operatingSystem=0;
418 public int operatingSystem() {
419 if (_operatingSystem==0) {
420 String osName=System.getProperty("os.name").toLowerCase();
421 if (osName.indexOf("windows")!=-1) _operatingSystem=WindowsOperatingSystem;
422 else if (osName.indexOf("solaris")!=-1) _operatingSystem=SolarisOperatingSystem;
423 else if (osName.indexOf("macos")!=-1) _operatingSystem=MacOSXOperatingSystem;
424 else _operatingSystem=UnknownOperatingSystem;
425 }
426 return _operatingSystem;
427 }
428
429 /**
430 * Path to the web server's document root.
431 * This implementation tries first to resolve the
432 * <code>application.name()+ "DocumentRoot"</code> property value,
433 * then the <code>ERXDocumentRoot</> property before
434 * getting the <code>DocumentRoot</code> key in your WebServerConfig.plist in the
435 * JavaWebObjects bundle.
436 * @returns to the web server's document root.
437 */
438 protected String documentRoot;
439 public String documentRoot() {
440 if (documentRoot == null) {
441 // for WebObjects.properties
442 documentRoot = ERXProperties.stringForKey(WOApplication.application().name() + "DocumentRoot");
443 if(documentRoot == null) {
444 // for command line and Properties
445 documentRoot = ERXProperties.stringForKey("ERXDocumentRoot");
446 if(documentRoot == null) {
447 // default value
448 NSDictionary dict = ERXDictionaryUtilities.dictionaryFromPropertyList("WebServerConfig", NSBundle.bundleForName("JavaWebObjects"));
449 if(dict != null)
450 documentRoot = (String)dict.objectForKey("DocumentRoot");
451 }
452 }
453 }
454 return documentRoot;
455 }
456
457 /** holds the host name */
458 protected String _hostName;
459
460 /**
461 * Gets the default host name for the current local host.
462 * @return host name or UnknownHost if the host is unknown.
463 */
464 public String hostName() {
465 if (_hostName == null) {
466 try {
467 _hostName = java.net.InetAddress.getLocalHost().getHostName();
468 } catch (java.net.UnknownHostException ehe) {
469 log.warn("Caught unknown host exception: " + ehe.getMessage());
470 _hostName = "UnknownHost";
471 }
472 }
473 return _hostName;
474 }
475
476 /**
477 * Checks if the application is deployed as a servlet.
478 * <p>
479 * The current implementation only checks if the application
480 * is linked against <code>JavaWOJSPServlet.framework</code>.
481 *
482 * @return true if the application is deployed as a servlet
483 */
484 public boolean isDeployedAsServlet() {
485 NSArray frameworkNames = (NSArray)NSBundle.frameworkBundles().valueForKey("name");
486 return frameworkNames.containsObject("JavaWOJSPServlet");
487 }
488
489 }