Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that implements properties methods for metacat
4
 *  Copyright: 2008 Regents of the University of California and the
5
 *             National Center for Ecological Analysis and Synthesis
6
 *    Authors: Michael Daigle
7
 *
8
 *   '$Author: daigle $'
9
 *     '$Date: 2009-08-14 17:38:05 -0700 (Fri, 14 Aug 2009) $'
10
 * '$Revision: 5028 $'
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 2 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License
23
 * along with this program; if not, write to the Free Software
24
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
 */
26

    
27
package edu.ucsb.nceas.metacat.service;
28

    
29
import java.io.IOException;
30
import java.util.Map;
31
import java.util.Set;
32
import java.util.Vector;
33

    
34
import javax.servlet.ServletContext;
35
import javax.servlet.http.HttpServletRequest;
36
import javax.xml.transform.TransformerException;
37

    
38
import org.apache.log4j.Logger;
39

    
40
import edu.ucsb.nceas.metacat.shared.BaseService;
41
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
42
import edu.ucsb.nceas.metacat.shared.ServiceException;
43
import edu.ucsb.nceas.metacat.util.SystemUtil;
44
import edu.ucsb.nceas.utilities.FileUtil;
45
import edu.ucsb.nceas.utilities.GeneralPropertyException;
46
import edu.ucsb.nceas.utilities.MetaDataProperty;
47
import edu.ucsb.nceas.utilities.PropertiesMetaData;
48
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
49
import edu.ucsb.nceas.utilities.SortedProperties;
50

    
51
/**
52
 * A suite of utility classes for the metadata configuration utility
53
 */
54
public class PropertyService extends BaseService {
55
	
56
	private static PropertyService propertyService = null;
57
	
58
	// system is configured
59
	public static final String CONFIGURED = "true"; 
60
	// system has never been configured
61
	public static final String UNCONFIGURED = "false"; 
62
	public static final String BYPASSED = "bypassed";
63
	
64
	private static String MAIN_CONFIG_NAME = "";
65
	private static final String AUTH_CONFIG_NAME = "auth.properties";
66
	
67
	private static boolean bypassAlreadyChecked = false;
68
	
69
	private static String recommendedExternalDir = null;
70
	
71
	private static String mainPropertiesFilePath = null;
72
	private static SortedProperties mainProperties = null;
73
	
74
	private static String mainBackupPropertiesFilePath = null;
75
	private static SortedProperties mainBackupProperties = null;
76
	
77
	private static String propertiesMetaDataFilePath = null;
78
	private static PropertiesMetaData mainMetaData = null;
79
	
80
	private static String authBackupPropertiesFilePath = null;
81
	private static SortedProperties authBackupProperties = null;
82
	
83
	private static String authMetaDataFilePath = null;
84
	private static PropertiesMetaData authMetaData = null;
85
	
86
	private static boolean testInstance = false;
87
	private static String testConfigDir = null;
88
	
89
	private static Logger logMetacat = Logger.getLogger(PropertyService.class);
90

    
91
	/**
92
	 * private constructor since this is a singleton
93
	 * 
94
	 * @param servletContext the context we will use to get relative paths
95
	 */
96
	private PropertyService() throws ServiceException {		
97
		_serviceName = "PropertyService";
98
		
99
		MAIN_CONFIG_NAME = ServiceService.CONFIG_FILE_NAME;
100
		
101
		initialize();		
102
	}
103
	
104
	/**
105
	 * Get the single instance of PropertyService.
106
	 * 
107
	 * @return the single instance of PropertyService
108
	 */
109
	public static PropertyService getInstance() throws ServiceException {
110
		if (propertyService == null) {
111
			
112
			propertyService = new PropertyService();
113
		}
114
		return propertyService;
115
	}
116
	
117
	/**
118
	 * Get the single instance of PropertyService for test purposes.  In this
119
	 * case, we allow the configuration directory to be passed in.
120
	 * 
121
	 * @param configDir the configuration directory we need to look in
122
	 * @return the single instance of PropertyService
123
	 */
124
	public static PropertyService getTestInstance(String configDir) throws ServiceException {
125
		if (propertyService == null) {
126
			
127
			testInstance = true;
128
			testConfigDir = configDir;
129
			
130
			propertyService = new PropertyService();
131
		}
132
		return propertyService;
133
	}
134
	
135
	public boolean refreshable() {
136
		return true;
137
	}
138
	
139
	public void doRefresh() throws ServiceException {
140
		initialize();
141
	}
142
	
143
	public void stop() throws ServiceException {
144
		return;
145
	}
146
	
147
	/**
148
	 * Initialize the singleton.
149
	 * 
150
	 * @param servletContext the context we will use to get relative paths
151
	 */
152
	private void initialize() throws ServiceException {
153
		
154
		logMetacat.debug("Initializing PropertyService");
155
		
156
		String configDir;
157
		if (testInstance) {
158
			configDir = testConfigDir;
159
		} else {
160
			configDir = ServiceService.getRealConfigDir();
161
		}
162
		
163
		try {
164
			// mainProperties will hold the primary configuration values for
165
			// metacat.
166
			mainPropertiesFilePath = configDir + FileUtil.getFS() + MAIN_CONFIG_NAME;
167
			mainProperties = new SortedProperties(mainPropertiesFilePath);
168
			mainProperties.load();
169

    
170
			// mainMetaData holds configuration information about main
171
			// properties. This is primarily used to display input fields on 
172
			// the configuration page. The information is retrieved from an 
173
			// xml metadata file
174
			propertiesMetaDataFilePath = 
175
				configDir + FileUtil.getFS() + MAIN_CONFIG_NAME + ".metadata.xml";
176
			mainMetaData = new PropertiesMetaData(propertiesMetaDataFilePath);
177

    
178
			// authMetaData holds configuration information about organization
179
			// level
180
			// properties. This is primarily used to display input fields on
181
			// the auth configuration page. The information is retrieved
182
			// from an xml metadata file dedicated just to auth properties.
183
			authMetaDataFilePath = 
184
				configDir + FileUtil.getFS() + AUTH_CONFIG_NAME + ".metadata.xml";
185
			authMetaData = new PropertiesMetaData(authMetaDataFilePath);
186

    
187
			String recommendedExternalDir = SystemUtil.discoverExternalDir();
188
			setRecommendedExternalDir(recommendedExternalDir);
189
			
190
			String backupPath = getProperty("application.backupDir");
191
			if (backupPath == null || backupPath.equals("")) {
192
				backupPath = SystemUtil.getStoredBackupDir();
193
			}
194
			if (backupPath == null || backupPath.equals("") && recommendedExternalDir != null) {
195
				backupPath = 
196
					recommendedExternalDir + FileUtil.getFS() + "." + ServiceService.getRealApplicationContext();
197
			}
198

    
199
			// if backupPath is still null, no reason to initialize the
200
			// backup properties. The system will need to prompt the user for 
201
			// the backup properties and reinitialize PropertyService.
202
			if (backupPath != null && !backupPath.equals("")) {		
203
				setProperty("application.backupDir", backupPath);
204
				SystemUtil.writeStoredBackupFile(backupPath);
205

    
206
				// The mainBackupProperties hold properties that were backed up
207
				// the last time the application was configured. On disk, the 
208
				// file will look like a smaller version of metacat.properties. 
209
				// It is stored in the data storage directory outside the 
210
				// application directories.
211
				mainBackupPropertiesFilePath =
212
					backupPath + FileUtil.getFS() + MAIN_CONFIG_NAME + ".backup";
213
				mainBackupProperties = new SortedProperties(mainBackupPropertiesFilePath);
214
				mainBackupProperties.load();
215

    
216
				// The authBackupProperties hold properties that were backed up
217
				// the last time the auth was configured. On disk, the file 
218
				// will look like a smaller version of metacat.properties. It 
219
				// is stored in the data storage directory outside the 
220
				// application directories.
221
				authBackupPropertiesFilePath = 
222
					backupPath + FileUtil.getFS() + AUTH_CONFIG_NAME + ".backup";
223
				authBackupProperties = new SortedProperties(authBackupPropertiesFilePath);
224
				authBackupProperties.load();
225
			}
226
		} catch (TransformerException te) {
227
			throw new ServiceException("Transform problem while loading properties: "
228
					+ te.getMessage());
229
		} catch (IOException ioe) {
230
			throw new ServiceException("I/O problem while loading properties: "
231
					+ ioe.getMessage());
232
		} catch (GeneralPropertyException gpe) {
233
			throw new ServiceException("General properties problem while loading properties: "
234
					+ gpe.getMessage());
235
		} catch (MetacatUtilException ue) {
236
			throw new ServiceException("Utilities problem while loading properties: "
237
					+ ue.getMessage());
238
		} 
239
	}
240

    
241
	/**
242
	 * Utility method to get a property value from the properties file
243
	 * 
244
	 * @param propertyName
245
	 *            the name of the property requested
246
	 * @return the String value for the property
247
	 */
248
	public static String getProperty(String propertyName)
249
			throws PropertyNotFoundException {
250
		return mainProperties.getProperty(propertyName);
251
	}
252
	
253
	/**
254
     * Get a set of all property names.
255
     * 
256
     * @return Set of property names  
257
     */
258
    public static Vector<String> getPropertyNames() {   	
259
    	return mainProperties.getPropertyNames();
260
    }
261
    
262

    
263
	/**
264
	 * Get a Set of all property names that start with the groupName prefix.
265
	 * 
266
	 * @param groupName
267
	 *            the prefix of the keys to search for.
268
	 * @return enumeration of property names
269
	 */
270
    public static Vector<String> getPropertyNamesByGroup(String groupName) {   	
271
    	return mainProperties.getPropertyNamesByGroup(groupName);
272
    }
273
    
274
	/**
275
	 * Get a Map of all properties that start with the groupName prefix.
276
	 * 
277
	 * @param groupName
278
	 *            the prefix of the keys to search for.
279
	 * @return Map of property names
280
	 */
281
    public static Map<String, String> getPropertiesByGroup(String groupName) throws PropertyNotFoundException {   	
282
    	return mainProperties.getPropertiesByGroup(groupName);
283
    }
284

    
285
	/**
286
	 * Utility method to set a property value both in memory and to the
287
	 * properties file
288
	 * 
289
	 * @param propertyName
290
	 *            the name of the property requested
291
	 * @param newValue
292
	 *            the new value for the property
293
	 */
294
	public static void setProperty(String propertyName, String newValue) throws GeneralPropertyException {
295
			mainProperties.setProperty(propertyName, newValue);
296
			mainProperties.store();
297
	}
298

    
299
	/**
300
	 * Utility method to set a property value in memory. This will NOT cause the
301
	 * property to be written to disk. Use this method to set multiple
302
	 * properties in a row without causing excessive I/O. You must call
303
	 * persistProperties() once you're done setting properties to have them
304
	 * written to disk.
305
	 * 
306
	 * @param propertyName
307
	 *            the name of the property requested
308
	 * @param newValue
309
	 *            the new value for the property
310
	 */
311
	public static void setPropertyNoPersist(String propertyName, String newValue) throws GeneralPropertyException {
312
		mainProperties.setPropertyNoPersist(propertyName, newValue);
313
	}
314

    
315
	/**
316
	 * Save the properties to a properties file. Note, the 
317
	 * order and comments will be preserved.
318
	 */
319
	public static void persistProperties() throws GeneralPropertyException {
320
		mainProperties.store();
321
	}
322
	
323
	/**
324
	 * Get the main backup properties file. These are configurable properties that
325
	 * are stored outside the metacat install directories so the user does not
326
	 * need to re-enter all the configuration information every time they do an
327
	 * upgrade.
328
	 * 
329
	 * @return a SortedProperties object with the backup properties
330
	 */
331
	public static SortedProperties getMainBackupProperties() {
332
		return mainBackupProperties;
333
	}
334
	
335
	/**
336
	 * Get the auth backup properties file. These are configurable 
337
	 * properties that are stored outside the metacat install directories so 
338
	 * the user does not need to re-enter all the configuration information 
339
	 * every time they do an upgrade.
340
	 * 
341
	 * @return a SortedProperties object with the backup properties
342
	 */
343
	public static SortedProperties getAuthBackupProperties() {
344
		return authBackupProperties;
345
	}
346
	
347
	/**
348
	 * Get the main properties metadata. This is retrieved from an xml file that
349
	 * describes the attributes of configurable properties.
350
	 * 
351
	 * @return a PropertiesMetaData object with the main properties metadata
352
	 */
353
	public static PropertiesMetaData getMainMetaData() {
354
		return mainMetaData;
355
	}
356
	
357
	/**
358
	 * Get the auth properties metadata. This is retrieved from an xml
359
	 * file that describes the attributes of configurable properties.
360
	 * 
361
	 * @return a PropertiesMetaData object with the organization properties
362
	 *         metadata
363
	 */
364
	public static PropertiesMetaData getAuthMetaData() {
365
		return authMetaData;
366
	}
367
	
368
	/**
369
	 * Writes out backup configurable properties to a file.
370
	 */
371
	public static void persistMainBackupProperties()
372
			throws GeneralPropertyException {
373

    
374
		// Use the metadata to extract configurable properties from the 
375
		// overall properties list, and store those properties.
376
		try {
377
			SortedProperties backupProperties = new SortedProperties(mainBackupPropertiesFilePath);
378
			
379
			// Populate the backup properties for main metacat properties using
380
			// the associated metadata file
381
			PropertiesMetaData mainMetadata = new PropertiesMetaData(propertiesMetaDataFilePath);
382

    
383
			Map<String, MetaDataProperty> mainKeyMap = mainMetadata.getProperties();
384
			Set<String> mainKeySet = mainKeyMap.keySet();
385
			for (String propertyKey : mainKeySet) {
386
				// don't backup passwords
387
				MetaDataProperty metaData = mainKeyMap.get(propertyKey);
388
				if (!metaData.getFieldType().equals(MetaDataProperty.PASSWORD_TYPE)) {
389
					backupProperties.addProperty(propertyKey, getProperty(propertyKey));
390
				}
391
			}
392
			
393
			// store the properties to file
394
			backupProperties.store();
395
			mainBackupProperties = 
396
				new SortedProperties(mainBackupPropertiesFilePath);
397
			mainBackupProperties.load();
398

    
399
		} catch (TransformerException te) {
400
			throw new GeneralPropertyException("Could not transform backup properties xml: "
401
					+ te.getMessage());
402
		} catch (IOException ioe) {
403
			throw new GeneralPropertyException("Could not backup configurable properties: "
404
					+ ioe.getMessage());
405
		}
406
	}
407
	
408
	/**
409
	 * Writes out backup configurable properties to a file.
410
	 */
411
	public static void persistAuthBackupProperties(ServletContext servletContext)
412
			throws GeneralPropertyException {
413

    
414
		// Use the metadata to extract configurable properties from the 
415
		// overall properties list, and store those properties.
416
		try {
417
			SortedProperties backupProperties = 
418
				new SortedProperties(authBackupPropertiesFilePath);
419
			
420
			// Populate the backup properties for auth properties using
421
			// the associated metadata file
422
			PropertiesMetaData authMetadata = new PropertiesMetaData(authMetaDataFilePath);
423
			
424
			Map<String, MetaDataProperty> authKeyMap = authMetadata.getProperties();
425
			Set<String> authKeySet = authKeyMap.keySet();
426
			for (String propertyKey : authKeySet) {
427
				// don't backup passwords
428
				MetaDataProperty metaData = authKeyMap.get(propertyKey);
429
				if (!metaData.getFieldType().equals(MetaDataProperty.PASSWORD_TYPE)) {
430
					backupProperties.addProperty(propertyKey, getProperty(propertyKey));
431
				}
432
			}
433
			
434
			// store the properties to file
435
			backupProperties.store();
436
			authBackupProperties = 
437
				new SortedProperties(authBackupPropertiesFilePath);
438
			authBackupProperties.load();
439

    
440
		} catch (TransformerException te) {
441
			throw new GeneralPropertyException("Could not transform backup properties xml: "
442
					+ te.getMessage());
443
		} catch (IOException ioe) {
444
			throw new GeneralPropertyException("Could not backup configurable properties: "
445
					+ ioe.getMessage());
446
		} 
447
	}
448
	
449
	/**
450
	 * Reports whether properties are fully configured.
451
	 * 
452
	 * @return a boolean that is true if properties are not unconfigured and
453
	 *         false otherwise
454
	 */
455
	public static boolean arePropertiesConfigured() throws MetacatUtilException {		
456
		try {
457
			String propertiesConfigured = PropertyService.getProperty("configutil.propertiesConfigured");
458
			if (propertiesConfigured != null && !propertiesConfigured.equals(UNCONFIGURED)) {
459
				return true;
460
			}			
461
			return false;
462
		} catch (PropertyNotFoundException pnfe) {
463
			throw new MetacatUtilException("Could not determine if properties are configured: "
464
					+ pnfe.getMessage());
465
		}
466
	}
467
	
468
	/**
469
	 * Determine if the system is configured to bypass configuration. If so, the
470
	 * system will look for backup configuration files at startup time and use
471
	 * those to configure metacat. The bypass options should only be set by
472
	 * developers. Production code should never bypass confguration.
473
	 * 
474
	 * @return true if dev.runConfiguration is set to true in metacat.properties
475
	 *         and we have not already checked for bypass, false otherwise.
476
	 */
477
	public static boolean doBypass() throws PropertyNotFoundException {
478
		// We only want to go through the check once to see if we want to
479
		// bypass the configuration.  We don't want to run through all of
480
		// this every time  we hit metacat. 
481
		if (bypassAlreadyChecked) {
482
			logMetacat.debug("bypassConfiguration not performing full bypass check.  Bypass set to false");
483
			return false;
484
		}
485
		
486
		// check how dev.runConfiguration is set in metacat.properties
487
		String strRunConfiguration = PropertyService.getProperty("dev.runConfiguration");
488
		boolean runConfiguration = Boolean.parseBoolean(strRunConfiguration);
489
		logMetacat.debug("bypassConfiguration: dev.runConfiguration property set to: " + strRunConfiguration);
490
		
491
		// if the dev.runConfiguration is true, return false here.
492
		if (runConfiguration) {
493
			bypassAlreadyChecked = true;
494
			return false;
495
		} 
496
		
497
		return true;
498
	}
499
	
500
	/**
501
	 * Reports whether the metacat configuration utility should be run.  
502
	 * Returns false if  
503
	 *   -- dev.runConfiguration=false and
504
	 *   -- backup properties file exists
505
	 * Note that dev.runConfiguration should only be set to false when
506
	 * reinstalling the same version of the application in developement.
507
	 * 
508
	 * @return a boolean that is false if dev.runConfiguration is false and 
509
	 * the backup properties file exists.  
510
	 */
511
	public static void bypassConfiguration() throws ServiceException {
512
		try {
513
			boolean doBypass = doBypass();
514

    
515
			if (!doBypass) {
516
				throw new ServiceException(
517
						"Attempting to do bypass when system is not configured for it.");
518
			}			
519

    
520
			// The system is bypassing the configuration utility. We need to
521
			// get the backup properties and replace existing properties with
522
			// backup values.  We do this for main and org properties.		
523
			logMetacat.debug("bypassConfiguration: setting main backup properties.");
524
			SortedProperties mainBackupProperties = getMainBackupProperties();
525
			Vector<String> backupPropertyNames = 
526
				mainBackupProperties.getPropertyNames();
527
			for (String backupPropertyName : backupPropertyNames) {
528
				String value = mainBackupProperties.getProperty(backupPropertyName);
529
				setPropertyNoPersist(backupPropertyName, value);
530
			}
531

    
532
			logMetacat.debug("bypassConfiguration: setting auth backup properties.");
533
			SortedProperties authBackupProperties = getAuthBackupProperties();
534
			Vector<String> authBackupPropertyNames = 
535
				authBackupProperties.getPropertyNames();
536
			for (String authBackupPropertyName : authBackupPropertyNames) {
537
				String value = authBackupProperties.getProperty(authBackupPropertyName);
538
				setPropertyNoPersist(authBackupPropertyName, value);
539
			}
540

    
541
			logMetacat.debug("bypassConfiguration: setting configutil sections to true.");
542
			setPropertyNoPersist("configutil.propertiesConfigured", "true");
543
			setPropertyNoPersist("configutil.authConfigured", "true");
544
			setPropertyNoPersist("configutil.skinsConfigured", "true");
545
			setPropertyNoPersist("configutil.databaseConfigured", "true");
546
			setPropertyNoPersist("configutil.geoserverConfigured", "bypassed");
547
				
548
			persistProperties();
549

    
550
		} catch (PropertyNotFoundException pnfe) {
551
			logMetacat.error("bypassConfiguration: Could not find property: " + pnfe.getMessage());
552
		} catch (GeneralPropertyException gpe) {
553
			logMetacat.error("bypassConfiguration: General property error: " + gpe.getMessage());
554
		}
555

    
556
		bypassAlreadyChecked = true;
557
	}
558
	
559
	/**
560
	 * Take input from the user in an HTTP request about an property to be changed
561
	 * and update the metacat property file with that new value if it has
562
	 * changed from the value that was originally set.
563
	 * 
564
	 * @param request
565
	 *            that was generated by the user
566
	 * @param response
567
	 *            to send output back to the user
568
	 * @param propertyName
569
	 *            the name of the property to be checked and set
570
	 */
571
	public static void checkAndSetProperty(HttpServletRequest request, String propertyName) 
572
			throws GeneralPropertyException {
573
		String value = PropertyService.getProperty(propertyName);
574
		String newValue = request.getParameter(propertyName);
575
		if (newValue != null && !newValue.trim().equals(value)) {
576
			PropertyService.setPropertyNoPersist(propertyName, newValue.trim());
577
		}
578
	}
579
	
580
	/**
581
	 * Sets the recommended external directory.  This is populated during initialization 
582
	 * time using the SystemUtil.discoverExternalDir() method.  This directory will be used to
583
	 * suggest external user directories when the user configures metacat for the first time.
584
	 * 
585
	 */
586
	public static void setRecommendedExternalDir(String extBaseDir) {
587
		recommendedExternalDir = extBaseDir;
588
	}
589
	
590
	/**
591
	 * Returns the recommended external base directory.  This is populated during initialization 
592
	 * time using the SystemUtil.discoverExternalBaseDir() method.  This directory will be used to
593
	 * suggest external user directories when the user configures metacat for the first time.
594
	 * 
595
	 * @return a String holding the recommended external directory
596
	 */
597
	public static String getRecommendedExternalDir() {
598
		return recommendedExternalDir;
599
	}
600

    
601
}
(1-1/6)