View Javadoc

1   /*
2    * ConfigurationToolkit.java
3    *
4    * Created on June 4, 2004, 10:51 AM
5    */
6   
7   package gov.bnl.gums.configuration;
8   
9   import gov.bnl.gums.GUMS;
10  import gov.bnl.gums.GridUser;
11  import gov.bnl.gums.SiteUser;
12  import gov.bnl.gums.account.AccountMapper;
13  import gov.bnl.gums.account.ManualAccountMapper;
14  import gov.bnl.gums.admin.CertCache;
15  import gov.bnl.gums.groupToAccount.GroupToAccountMapping;
16  import gov.bnl.gums.hostToGroup.CertificateHostToGroupMapping;
17  import gov.bnl.gums.hostToGroup.HostToGroupMapping;
18  import gov.bnl.gums.userGroup.UserGroup;
19  import gov.bnl.gums.userGroup.VOMSUserGroup;
20  import gov.bnl.gums.userGroup.ManualUserGroup;
21  import gov.bnl.gums.util.StringUtil;
22  import gov.bnl.gums.util.URIToolkit;
23  
24  import java.io.ByteArrayInputStream;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.IOException;
28  import java.io.OutputStream;
29  import java.io.PrintStream;
30  import java.net.MalformedURLException;
31  import java.net.URL;
32  import java.util.*;
33  import java.util.jar.JarFile;
34  import java.util.regex.Matcher;
35  import java.util.regex.Pattern;
36  
37  import javax.xml.parsers.DocumentBuilder;
38  import javax.xml.parsers.DocumentBuilderFactory;
39  import javax.xml.parsers.ParserConfigurationException;
40  import javax.xml.transform.Source;
41  import javax.xml.transform.Transformer;
42  import javax.xml.transform.TransformerFactory;
43  import javax.xml.transform.sax.SAXSource;
44  import javax.xml.transform.stream.StreamResult;
45  import javax.xml.transform.stream.StreamSource;
46  
47  import org.xml.sax.ErrorHandler;
48  import org.xml.sax.InputSource;
49  import org.xml.sax.SAXParseException;
50  import org.xml.sax.SAXException;
51  import org.xml.sax.XMLReader;
52  import org.xml.sax.helpers.XMLReaderFactory;
53  import org.apache.commons.beanutils.MethodUtils;
54  import org.apache.commons.digester.*;
55  import org.apache.log4j.Logger;
56  
57  /**
58   * Contains the logic on how to parse an XML configuration file to create a
59   * correctly built Configuration object.
60   * 
61   * @author Gabriele Carcassi, Jay Packard
62   */
63  public class ConfigurationToolkit {
64  	/**
65  	 * Simple error handler that logs errors
66  	 * 
67  	 * @author jpackard
68  	 */
69  	public class SimpleErrorHandler implements ErrorHandler {
70  		public String error = null;
71  
72  		public void error(SAXParseException exception) {
73  			log.error(exception.getMessage());
74  			adminLog.error(exception.getMessage());
75  			error = exception.getMessage();
76  		}
77  
78  		public void fatalError(SAXParseException exception) {
79  			log.fatal(exception.getMessage());
80  			adminLog.fatal(exception.getMessage());
81  			error = exception.getMessage();
82  		}
83  
84  		public void warning(SAXParseException exception) {
85  			log.warn(exception.getMessage());
86  			adminLog.warn(exception.getMessage());
87  		}
88  	}
89  	// Config Element Rule
90  	private static class ConfElementRule extends Rule {
91  		public void begin(String str, String str1, org.xml.sax.Attributes attributes) throws java.lang.Exception {
92  			Object configElement = getDigester().peek();
93  			for (int i = 0; i < attributes.getLength(); i++) {
94  				String attribute = attributes.getQName(i);
95  				String value = attributes.getValue(i);
96  				String setMethod = "set" + attribute.substring(0, 1).toUpperCase() + attribute.substring(1);
97  				try {
98  					try {
99  						// Try set method
100 						MethodUtils.invokeMethod(configElement, setMethod, value);
101 					} catch (NoSuchMethodException e) {
102 						if (value.length()==0)
103 							// For case when transform results in '' for boolean - too difficult to change in transform file
104 							continue;
105 						// Try interpreting value as boolean
106 						Boolean boolVal;
107 						try {
108 							boolVal = new Boolean(value);
109 							MethodUtils.invokeMethod(configElement, setMethod, boolVal);
110 						}
111 						catch (Exception e2) {
112 							throw new NoSuchMethodException();
113 						}
114 					}
115 				} catch (NoSuchMethodException e) {
116 					// Try using add method
117 					try {
118 						StringTokenizer tokens = new StringTokenizer(value, ",");
119 						while (tokens.hasMoreTokens()) {
120 							value = tokens.nextToken().trim();
121 							String addMethod = "add" + attribute.substring(0, 1).toUpperCase() + attribute.substring(1, attribute.length() - 1);
122 							MethodUtils.invokeMethod(configElement, addMethod, value);
123 						}
124 					} catch (NoSuchMethodException e1) {
125 						throw new NoSuchMethodException("Could not find setter function for "+attribute);
126 					}
127 				}
128 			}
129 		}
130 	}
131 	static public String testHostDN = "/DC=com/DC=example/OU=Services/CN=example.site.com";
132 	static public Pattern testHostDNPattern = (Pattern) Pattern.compile(testHostDN);
133 	static public String testUserDN = "/DC=com/DC=example/OU=People/CN=Example User 12345";
134 	static private Logger adminLog = Logger.getLogger(GUMS.gumsAdminLogName);
135 	static private Logger log = Logger.getLogger(ConfigurationToolkit.class);
136 	static private String schemaPath = null;
137 	static private String templateSchemaPath = null;
138 	static private String transformDir = null;
139 	static String gumsJarPath = null;
140 	static public Set<Class<ConfigElement>> userGroupSet = null;
141 	static public Set<Class<ConfigElement>> accountMapperSet = null;
142 	static public Set<Class<ConfigElement>> groupToAccountSet = null;
143 	static public Set<Class<ConfigElement>> hostToGroupSet = null;
144 	
145 	static {
146 		String resourceDir = null;
147 		try {
148 			resourceDir = CertCache.getResourceDir();
149 		} catch (Exception e) {
150 			// For testing or command line
151 			URL resource = new ConfigurationToolkit().getClass().getClassLoader().getResource("gums.config");
152 			if (resource == null) {
153 				String temp = System.getProperty("user.dir");
154 				if (temp.endsWith("/WEB-INF/scripts"))
155 					resourceDir = temp + "/..";
156 			}
157 
158 			if (resource != null)
159 				resourceDir = resource.getPath().replace("/gums.config", "");
160 		}
161 		if (resourceDir != null) {
162 			schemaPath = resourceDir + "/gums.config.schema";
163 			templateSchemaPath = resourceDir + "/gums.config.template.schema";
164 			transformDir = resourceDir + "/";
165 		}
166 		String[] classPaths = System.getProperty("java.class.path").split(File.pathSeparator);
167 		for (int i=0; i<classPaths.length; i++) {
168 			if (classPaths[i].contains("gums-core") && classPaths[i].contains(".jar")) {
169 				gumsJarPath = classPaths[i];
170 				break;
171 			}
172 		}
173 		try {
174 			userGroupSet = findClasses(Thread.currentThread().getContextClassLoader(), null, Collections.singleton("gov.bnl.gums.userGroup"), gumsJarPath!=null?Collections.singleton(gumsJarPath):null);
175 			userGroupSet.remove(UserGroup.class);
176 			accountMapperSet = findClasses(Thread.currentThread().getContextClassLoader(), null, Collections.singleton("gov.bnl.gums.account"), gumsJarPath!=null?Collections.singleton(gumsJarPath):null);
177 			accountMapperSet.remove(AccountMapper.class);
178 			groupToAccountSet = findClasses(Thread.currentThread().getContextClassLoader(), null, Collections.singleton("gov.bnl.gums.groupToAccount"), gumsJarPath!=null?Collections.singleton(gumsJarPath):null);
179 			hostToGroupSet = findClasses(Thread.currentThread().getContextClassLoader(), null, Collections.singleton("gov.bnl.gums.hostToGroup"), gumsJarPath!=null?Collections.singleton(gumsJarPath):null);
180 			hostToGroupSet.remove(HostToGroupMapping.class);
181 		} catch (ClassNotFoundException e) {
182 			log.fatal(e);
183 			throw new RuntimeException(e.getMessage());
184 		}
185 	}
186 
187 	static public Configuration do1_1_to_1_4Transform(String configText,
188 			String transformPath) {
189 		try {
190 			log.trace("Transforming configuration file using transform '" + transformPath);
191 
192 			File configFileTemp = File.createTempFile("gums", "config");
193 
194 			XMLReader reader = XMLReaderFactory.createXMLReader();
195 			Source source = new SAXSource(reader, new InputSource(new ByteArrayInputStream(configText.getBytes())));
196 
197 			StreamResult result = new StreamResult(configFileTemp);
198 			Source style = new StreamSource(transformPath);
199 
200 			TransformerFactory transFactory = TransformerFactory.newInstance();
201 			Transformer trans = transFactory.newTransformer(style);
202 
203 			trans.transform(source, result);
204 
205 			// Reload it to get rid of duplicates that the transform 
206 			// couldn't handle as well as to clean up the formatting
207 			Digester digester = ConfigurationToolkit.retrieveDigester();
208 			Configuration configuration = new Configuration(true);
209 			digester.push(configuration);
210 			digester.parse("file://" + configFileTemp.getAbsolutePath());
211 
212 			configFileTemp.delete();
213 
214 			// Clean up account mapper names
215 			Iterator<String> it = new ArrayList<String>(configuration.getAccountMappers().keySet()).iterator();
216 			while (it.hasNext()) {
217 				String name = it.next();
218 				String origName = new String(name);
219 				if (name.indexOf("://") != -1) {
220 					name = name.replaceAll(".*://", "");
221 					name = name.replaceAll(".*/dc=", "");
222 					name = name.replaceAll("dc=", "");
223 					name = name.toLowerCase();
224 					if (configuration.getAccountMapper(name) != null) {
225 						int count = 1;
226 						while (configuration.getAccountMapper(name + Integer.toString(count)) != null)
227 							count++;
228 						name += Integer.toString(count);
229 					}
230 					AccountMapper accountMapper = configuration.getAccountMapper(origName);
231 					if (accountMapper != null) {
232 						accountMapper.setName(name);
233 						configuration.removeAccountMapper(origName, false);
234 						configuration.addAccountMapper(accountMapper);
235 						Iterator<GroupToAccountMapping> it2 = configuration.getGroupToAccountMappings().values().iterator();
236 						while (it2.hasNext()) {
237 							GroupToAccountMapping groupToAccountMapping = it2.next();
238 							List<String> accountMappers = StringUtil.toList(groupToAccountMapping.getAccountMappers());
239 							Iterator<String> it3 = accountMappers.iterator();
240 							int index = 0;
241 							while (it3.hasNext()) {
242 								String str = it3.next();
243 								if (str.equals(origName)) {
244 									groupToAccountMapping.removeAccountMapper(index);
245 									groupToAccountMapping.addAccountMapper(index, name);
246 									it3 = accountMappers.iterator();
247 									index = 0;
248 								} else
249 									index++;
250 							}
251 						}
252 					}
253 				}
254 			}
255 
256 			if (configuration.getUserGroup("osg") != null) {
257 				// Assume this is OSG template
258 				configuration.setSource("OSG");
259 				configuration.setIsTemplate(true);
260 			}
261 
262 			return configuration;
263 		} catch (Exception e) {
264 			String message = "Could not convert older version of gums.config";
265 			log.error(message, e);
266 			adminLog.error(message);
267 			throw new RuntimeException(message);
268 		}
269 	}
270 
271 	static public Configuration do1_3_to_1_4Transform(String configText) {
272 		try {
273 			configText = configText.replaceAll("\n", " ");
274 			
275 			// Get information from VomsServers
276 			Map<String, String> vsMap = new HashMap<String, String>();
277 			String vomsServerName = null;
278 			String vomsUrl = null;
279 			int i = configText.indexOf("<vomsServers", 0) + 1;
280 			while (i != -1) {
281 				if ((i = configText.indexOf("<vomsServer", i)) != -1) {
282 					int j = -1;
283 					if ((j = configText.indexOf("name", i)) != -1) {
284 						int startingIndex = -1, endingIndex = -1;
285 						for (; j < configText.length() && configText.charAt(j) != '\n'; j++) {
286 							if (configText.charAt(j) == '\'' || configText.charAt(j) == '"') {
287 								startingIndex = j + 1;
288 								break;
289 							}
290 						}
291 						j++;
292 						for (; j < configText.length() && configText.charAt(j) != '\n'; j++) {
293 							if (configText.charAt(j) == '\'' || configText.charAt(j) == '"') {
294 								endingIndex = j;
295 								break;
296 							}
297 						}
298 						if (startingIndex != -1 && endingIndex != -1)
299 							vomsServerName = configText.substring(startingIndex, endingIndex);
300 					}
301 					if ((j = configText.indexOf("baseUrl", i)) != -1) {
302 						int startingIndex = -1, endingIndex = -1;
303 						for (; j < configText.length() && configText.charAt(j) != '\n'; j++) {
304 							if (configText.charAt(j) == '\'' || configText.charAt(j) == '"') {
305 								startingIndex = j + 1;
306 								break;
307 							}
308 						}
309 						j++;
310 						for (; j < configText.length() && configText.charAt(j) != '\n'; j++) {
311 							if (configText.charAt(j) == '\'' || configText.charAt(j) == '"') {
312 								endingIndex = j;
313 								break;
314 							}
315 						}
316 						if (startingIndex != -1 && endingIndex != -1)
317 							vomsUrl = configText.substring(startingIndex, endingIndex);
318 					}
319 					i++;
320 				}
321 				if (vomsServerName != null && vomsUrl != null) {
322 					vsMap.put(vomsServerName, vomsUrl);
323 					vomsServerName = null;
324 					vomsUrl = null;
325 				}
326 			}
327 
328 			// Get information from VomsUserGroups
329 			Map<String, String> ugMap = new HashMap<String, String>();
330 			String userGroupName = null;
331 			String vomsServer = null;
332 			i = configText.indexOf("<vomsUserGroups", 0) + 1;
333 			while (i != -1) {
334 				if ((i = configText.indexOf("<vomsUserGroup", i)) != -1) {
335 					int j = -1;
336 					if ((j = configText.indexOf("name", i)) != -1) {
337 						int startingIndex = -1, endingIndex = -1;
338 						for (; j < configText.length() && configText.charAt(j) != '\n'; j++) {
339 							if (configText.charAt(j) == '\'' || configText.charAt(j) == '"') {
340 								startingIndex = j + 1;
341 								break;
342 							}
343 						}
344 						j++;
345 						for (; j < configText.length() && configText.charAt(j) != '\n'; j++) {
346 							if (configText.charAt(j) == '\'' || configText.charAt(j) == '"') {
347 								endingIndex = j;
348 								break;
349 							}
350 						}
351 						if (startingIndex != -1 && endingIndex != -1)
352 							userGroupName = configText.substring(startingIndex, endingIndex);
353 					}
354 					if ((j = configText.indexOf("vomsServer", i)) != -1) {
355 						int startingIndex = -1, endingIndex = -1;
356 						for (; j < configText.length() && configText.charAt(j) != '\n'; j++) {
357 							if (configText.charAt(j) == '\'' || configText.charAt(j) == '"') {
358 								startingIndex = j + 1;
359 								break;
360 							}
361 						}
362 						j++;
363 						for (; j < configText.length() && configText.charAt(j) != '\n'; j++) {
364 							if (configText.charAt(j) == '\'' || configText.charAt(j) == '"') {
365 								endingIndex = j;
366 								break;
367 							}
368 						}
369 						if (startingIndex != -1 && endingIndex != -1)
370 							vomsServer = configText.substring(startingIndex, endingIndex);
371 					}
372 					i++;
373 				}
374 				if (userGroupName != null && vomsServer != null) {
375 					ugMap.put(userGroupName, vomsServer);
376 					userGroupName = null;
377 					vomsServer = null;
378 				}
379 			}
380 
381 			// Get SSL fields from first VomsServer and set in global properties
382 			String sslKey = "";
383 			Pattern p = Pattern.compile(".*sslKey[\\s]*=[\\s]*['|\"](.*?)['|\"].*");
384 			Matcher m = p.matcher(configText);
385 			if (m.matches())
386 				sslKey = m.group(1);
387 			String sslCertfile = "";
388 			p = Pattern.compile(".*sslCertfile[\\s]*=[\\s]*['|\"](.*?)['|\"].*");
389 			m = p.matcher(configText);
390 			if (m.matches())
391 				sslCertfile = m.group(1);
392 			String sslCaFiles = "";
393 			p = Pattern.compile(".*sslCAFiles[\\s]*=[\\s]*['|\"](.*?)['|\"].*");
394 			m = p.matcher(configText);
395 			if (m.matches())
396 				sslCaFiles = m.group(1);
397 			String sslKeyPasswd = "";
398 			p = Pattern.compile(".*sslKeyPasswd[\\s]*=[\\s]*['|\"](.*?)['|\"].*");
399 			m = p.matcher(configText);
400 			if (m.matches())
401 				sslKeyPasswd = m.group(1);
402 
403 			// replacements
404 			configText = configText.replaceAll("<vomsServers>.*</vomsServers>", "");
405 			configText = configText.replaceAll("remainderUrl", "vomsServerUrls");
406 			configText = configText.replaceAll("vomsServer=['|\"].*?['|\"]", "");
407 			configText = configText.replaceAll("persistenceFactory=['|\"].*?['|\"]", "");
408 			configText = configText.replaceAll("<hostToGroupMapping[\\s]", "<certificateHostToGroupMapping");
409 
410 			// parse
411 			Configuration configuration = new Configuration();
412 			Digester digester = retrieveDigester();
413 			digester.push(configuration);
414 			digester.parse(new ByteArrayInputStream(configText.getBytes()));
415 
416 			// set ssl key stuff extracted earlier
417 			configuration.setSslKey(sslKey);
418 			configuration.setSslCertfile(sslCertfile);
419 			configuration.setSslCAFiles(sslCaFiles);
420 			configuration.setSslKeyPasswd(sslKeyPasswd);
421 
422 			// set voms stuff extracted earlier
423 			for (UserGroup u : configuration.getUserGroups().values()) {
424 				if (u instanceof VOMSUserGroup) {
425 					VOMSUserGroup vug = (VOMSUserGroup) u;
426 					String remainderUrl = vug.getVomsServerUrls();
427 					String temp = ugMap.get(vug.getName());
428 					if (temp != null) {
429 						String baseUrl = vsMap.get(temp);
430 						vug.setVomsServerUrls(baseUrl + remainderUrl);
431 					}
432 				}
433 			}
434 
435 			return configuration;
436 		} catch (Exception e) {
437 			String message = "Could not convert older version of gums.config";
438 			log.error(message, e);
439 			adminLog.error(message);
440 			throw new RuntimeException(message);
441 		}
442 	}
443 
444 	public static Set<Class<ConfigElement>> getConfigElementTypes(Class<?> configElementClass) {
445 		if (configElementClass.equals(AccountMapper.class))
446 			return accountMapperSet;
447 		else if (configElementClass.equals(UserGroup.class))
448 			return userGroupSet;
449 		else if (configElementClass.equals(GroupToAccountMapping.class))
450 			return groupToAccountSet;
451 		else if (configElementClass.equals(HostToGroupMapping.class))
452 			return hostToGroupSet;
453 		else
454 			assert(false);
455 		return null;
456 	}
457 
458 	static public void insertTest(Configuration configuration) {
459 		// Add test user to manual user group
460 		ManualUserGroup manualUserGroup;
461 		UserGroup userGroup = (ManualUserGroup) configuration.getUserGroup("_test");
462 		if (userGroup != null && userGroup instanceof ManualUserGroup)
463 			manualUserGroup = (ManualUserGroup) userGroup;
464 		else {
465 			String name = "_test";
466 			while (configuration.getUserGroup(name) != null)
467 				name = name + "_";
468 			manualUserGroup = new ManualUserGroup(configuration, name);
469 		}
470 		GridUser testUser = new GridUser(testUserDN);
471 		if (!manualUserGroup.isMember(testUser))
472 			manualUserGroup.addMember(testUser);
473 
474 		// Add test account mapper
475 		ManualAccountMapper manualAccountMapper;
476 		AccountMapper accountMapper = configuration.getAccountMapper("_test");
477 		if (accountMapper != null && accountMapper instanceof ManualAccountMapper)
478 			manualAccountMapper = (ManualAccountMapper) accountMapper;
479 		else {
480 			String name = "_test";
481 			while (configuration.getAccountMapper(name) != null)
482 				name = name + "_";
483 			manualAccountMapper = new ManualAccountMapper(configuration, name);
484 		}
485 		if (manualAccountMapper.mapDn(testUser.getDn(), false) == null)
486 			manualAccountMapper.createMapping(testUser.getDn(),
487 					new SiteUser("test"));
488 
489 		// Add test groupToAccountMapping
490 		GroupToAccountMapping g2AMapping = configuration.getGroupToAccountMapping("_test");
491 		if (g2AMapping == null) {
492 			g2AMapping = new GroupToAccountMapping(configuration, "_test");
493 			g2AMapping.setName("_test");
494 		}
495 		if (!StringUtil.toList(g2AMapping.getUserGroups()).contains(manualUserGroup.getName()))
496 			g2AMapping.addUserGroup(manualUserGroup.getName());
497 		if (!StringUtil.toList(g2AMapping.getAccountMappers()).contains(manualAccountMapper.getName()))
498 			g2AMapping.addAccountMapper(manualAccountMapper.getName());
499 
500 		// Add test hostToGroupMapping
501 		HostToGroupMapping h2GMapping = configuration.getHostToGroupMapping(testHostDN);
502 		if (h2GMapping == null) {
503 			h2GMapping = new CertificateHostToGroupMapping(configuration);
504 			((CertificateHostToGroupMapping) h2GMapping).setDn(testHostDN);
505 		}
506 		if (!StringUtil.toList(h2GMapping.getGroupToAccountMappings()).contains(g2AMapping.getName()))
507 			h2GMapping.addGroupToAccountMapping(g2AMapping.getName());
508 	}
509 
510 	public static void main(String args[]) {
511 		if (args.length != 1) {
512 			System.out.println("Must specify arguments: {configuration path}");
513 			return;
514 		}
515 
516 		// Disable system error output since getBuilderFactory is outputting a
517 		// bunch of information messages to it
518 		PrintStream errStreamOriginal = System.err;
519 		System.setErr(new PrintStream(new OutputStream() {
520 			public void write(int b) {
521 			}
522 		}));
523 		PrintStream outStreamOriginal = System.out;
524 		System.setOut(new PrintStream(new OutputStream() {
525 			public void write(int b) {
526 			}
527 		}));
528 
529 		try {
530 			FileInputStream fileInputStream = new FileInputStream(args[0]);
531 			try {
532 				StringBuffer configBuffer = new StringBuffer();
533 				int ch;
534 				while ((ch = fileInputStream.read()) != -1)
535 					configBuffer.append((char) ch);
536 				ConfigurationToolkit.parseConfiguration(
537 						configBuffer.toString(), false);
538 			} finally {
539 				fileInputStream.close();
540 			}
541 		} catch (Exception e) {
542 			System.setErr(errStreamOriginal);
543 			System.setOut(outStreamOriginal);
544 			System.out.println("Configuration is NOT valid:");
545 			System.out.println(e.getMessage());
546 			System.exit(1);
547 		}
548 
549 		System.setErr(errStreamOriginal);
550 		System.setOut(outStreamOriginal);
551 
552 		System.out.println("Configuration is valid");
553 	}
554 
555 	/**
556 	 * Get the gums.config version for the given file
557 	 * 
558 	 * @param filename
559 	 * @return
560 	 * @throws IOException
561 	 * @throws SAXException
562 	 */
563 	public static Configuration metaParse(ByteArrayInputStream inputStream)
564 			throws IOException, SAXException {
565 		Digester digester = new Digester();
566 		digester.setValidating(false);
567 		digester.addObjectCreate("gums", Configuration.class);
568 		digester.addSetProperties("gums");
569 		digester.parse(inputStream);
570 		Configuration conf = (Configuration) digester.getRoot();
571 		conf.setMetaOnly(true);
572 		return conf;
573 	}
574 
575 	/**
576 	 * new StringBufferInputStream(configText) Load gums.config
577 	 * 
578 	 * @param configFile
579 	 * @param configText
580 	 * @param schemaPath
581 	 * @return
582 	 * @throws ParserConfigurationException
583 	 * @throws IOException
584 	 * @throws SAXException
585 	 * 
586 	 *             Either set configPath or configText
587 	 */
588 	public static Configuration parseConfiguration(String configText,
589 			boolean insertTest) throws ParserConfigurationException,
590 			IOException, SAXException, ClassNotFoundException {
591 		// Parse if current version, otherwise perform one or more
592 		// transforms to get to the right version
593 		Configuration configuration = metaParse(new ByteArrayInputStream(
594 				configText.getBytes()));
595 		boolean isTemplate = configuration.isTemplate();
596 		if (!configuration.versionWasSet)
597 			configuration.setVersion("1.1");
598 		if (configuration.getVersion().startsWith(GUMS.getMajorMinorVersion())) {
599 			// Parse normally
600 			log.trace("Loading configuration");
601 			validate(configText, isTemplate);
602 			configuration = new Configuration();
603 			Digester digester = retrieveDigester();
604 			digester.push(configuration);
605 			digester.parse(new ByteArrayInputStream(configText.getBytes()));
606 		} else {
607 			// Transform
608 			if (configuration.getVersion().equals("1.1")) {
609 				configuration = do1_1_to_1_4Transform(configuration.isMetaOnly() ? configText : configuration.toXml(), transformDir + "gums.config.transform1_1-1_4");
610 				configuration.setSource("OSG");
611 			} else if (configuration.getVersion().equals("1.3")) {
612 				log.trace("Loading configuration");
613 				configuration = do1_3_to_1_4Transform(configuration.isMetaOnly() ? configText : configuration.toXml());
614 			} else {
615 				String message = "Cannot parse configurations with version '" + configuration.getVersion() + "'";
616 				adminLog.error(message);
617 				throw new RuntimeException(message);
618 			}
619 
620 			configuration.setVersion(GUMS.getMajorMinorVersion());
621 		}
622 
623 		return configuration;
624 	}
625 
626 	static public Configuration parseConfigurationTemplates(String uris,
627 			String tempFile) throws Exception {
628 		Configuration conf = null;
629 		StringTokenizer st = new StringTokenizer(uris, ",");
630 		while (st.hasMoreTokens()) {
631 			String uri = st.nextToken().trim();
632 			String configText = URIToolkit.parseUri(uri, tempFile);
633 			if (conf == null) {
634 				conf = parseConfiguration(configText, false);
635 				if (!conf.isTemplate())
636 					throw new RuntimeException(uri
637 							+ " is not a configuration template");
638 			} else {
639 				Configuration tempConf = parseConfiguration(configText, false);
640 				if (!tempConf.isTemplate())
641 					throw new RuntimeException(uri
642 							+ " is not a configuration template");
643 				conf.mergeConfigurationTemplate(tempConf, null, false);
644 			}
645 		}
646 
647 		// clean up old temp files
648 		if (tempFile != null) {
649 			File tempDir = new File(new File(tempFile).getParent());
650 			String[] tempFiles = tempDir.list();
651 			for (String tfName : tempFiles) {
652 				File tf = new File(tfName);
653 				Calendar cal = Calendar.getInstance();
654 				cal.add(Calendar.HOUR, -1);
655 				if (new Date(tf.lastModified()).before(cal.getTime()))
656 					tf.delete();
657 			}
658 		}
659 
660 		return conf;
661 	}
662 
663 	/**
664 	 * @return a Digestor object for parsing gums.config
665 	 */
666 	public static Digester retrieveDigester() throws ClassNotFoundException,
667 			IOException {
668 		Digester digester = new Digester();
669 		digester.setValidating(false);
670 
671 		digester.addSetProperties("gums");
672 		
673 		addRules(digester, userGroupSet, "gums/userGroups", "addUserGroup");
674 		addRules(digester, accountMapperSet, "gums/accountMappers", "addAccountMapper");
675 		addRules(digester, groupToAccountSet, "gums/groupToAccountMappings", "addGroupToAccountMapping");
676 		addRules(digester, hostToGroupSet, "gums/hostToGroupMappings", "addHostToGroupMapping");
677 		
678 		return digester;
679 	}
680 
681 	/**
682 	 * Validate gums.config given a config file and a schema file
683 	 * 
684 	 * @param configFile
685 	 * @param schemaFile
686 	 * @throws ParserConfigurationException
687 	 * @throws SAXException
688 	 * @throws IOException
689 	 */
690 	public static void validate(String configText, boolean isTemplate)
691 			throws ParserConfigurationException, SAXException, IOException {
692 		System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
693 				"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
694 		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
695 		log.trace("DocumentBuilderFactory: " + factory.getClass().getName());
696 
697 		factory.setNamespaceAware(true);
698 		factory.setValidating(true);
699 		factory.setAttribute(
700 				"http://java.sun.com/xml/jaxp/properties/schemaLanguage",
701 				"http://www.w3.org/2001/XMLSchema");
702 		if (isTemplate)
703 			factory.setAttribute(
704 					"http://java.sun.com/xml/jaxp/properties/schemaSource",
705 					"file:" + templateSchemaPath);
706 		else
707 			factory.setAttribute(
708 					"http://java.sun.com/xml/jaxp/properties/schemaSource",
709 					"file:" + schemaPath);
710 
711 		DocumentBuilder builder = factory.newDocumentBuilder();
712 		SimpleErrorHandler errorHandler = new ConfigurationToolkit().new SimpleErrorHandler();
713 		builder.setErrorHandler(errorHandler);
714 
715 		builder.parse(new ByteArrayInputStream(configText.getBytes()));
716 
717 		if (errorHandler.error != null)
718 			throw new ParserConfigurationException(errorHandler.error);
719 	}
720 	
721 	@SuppressWarnings("unchecked")
722 	protected static void addRules(Digester digester, Set<Class<ConfigElement>> configElementMap, String xmlGroup, String addMethodName) throws ClassNotFoundException, IOException {
723 		for (Class c: configElementMap) {
724 			char[] xmlElementName = c.getSimpleName().replace("VOMS", "voms").replace("LDAP", "ldap").toCharArray();
725 			xmlElementName[0] = Character.toLowerCase(xmlElementName[0]);
726 			String xmlPattern = xmlGroup + "/" + new String(xmlElementName);
727 			digester.addObjectCreate(xmlPattern, c);
728 			digester.addRule(xmlPattern, new ConfElementRule());
729 			digester.addSetNext(xmlPattern, addMethodName, c.getName());
730 		}
731 	}
732 
733 	/**
734 	   * Searches the classpath for all classes matching a specified search criteria, 
735 	   * returning them in a map keyed with the interfaces they implement or null if they
736 	   * have no interfaces. The search criteria can be specified via interface, package
737 	   * and jar name filter arguments
738 	   * <p>
739 	   * @param classLoader       The classloader whose classpath will be traversed
740 	   * @param interfaceFilter   A Set of fully qualified interface names to search for
741 	   *                          or null to return classes implementing all interfaces
742 	   * @param packageFilter     A Set of fully qualified package names to search for or
743 	   *                          or null to return classes in all packages
744 	   * @param jarFilter         A Set of jar file names to search for or null to return
745 	   *                          classes from all jars
746 	   * @return A Map of a Set of Classes keyed to their interface names
747 	   *
748 	   * @throws ClassNotFoundException if the current thread's classloader cannot load
749 	   *                                a requested class for any reason
750 	   */
751 	@SuppressWarnings("unchecked")
752 	protected static Set<Class<ConfigElement>> findClasses(ClassLoader classLoader,
753 			Set<String> interfaceFilter, Set<String> packageFilter,
754 			Set<String> jarFilter) throws ClassNotFoundException {
755 		Map<String, Set<Class<ConfigElement>>> classTable = new HashMap();
756 		Object[] classPaths;
757 		try {
758 			// get a list of all classpaths
759 			classPaths = ((java.net.URLClassLoader) classLoader).getURLs();
760 			if (classPaths.length<=1)
761 				// somethings wrong with the class load so get it from properties
762 				classPaths = System.getProperty("java.class.path", "").split(File.pathSeparator);
763 		} catch (ClassCastException cce) {
764 			// or cast failed; tokenize the system classpath
765 			classPaths = System.getProperty("java.class.path", "").split(File.pathSeparator);
766 		}
767 
768 		for (int h = 0; h < classPaths.length; h++) {
769 			Enumeration files = null;
770 			JarFile module = null;
771 			// for each classpath ...
772 			File classPath = new File((URL.class).isInstance(classPaths[h]) ? ((URL) classPaths[h]).getFile() : classPaths[h].toString());
773 			if (classPath.isDirectory() && jarFilter == null) { 
774 				List<String> dirListing = new ArrayList();
775 				// get a recursive listing of this classpath
776 				recursivelyListDir(dirListing, classPath, new StringBuffer());
777 				// an enumeration wrapping our list of files
778 				files = Collections.enumeration(dirListing);
779 			} else if (classPath.getName().endsWith(".jar")) { 
780 				// skip any jars not list in the filter
781 				if (jarFilter != null && !jarFilter.contains(classPath.getName())) {
782 					continue;
783 				}
784 				try {
785 					// if our resource is a jar, instantiate a jarfile using the
786 					// full path to resource
787 					module = new JarFile(classPath);
788 				} catch (MalformedURLException mue) {
789 					throw new ClassNotFoundException("Bad classpath. Error: "
790 							+ mue.getMessage());
791 				} catch (IOException io) {
792 					throw new ClassNotFoundException(
793 							"jar file '"
794 							+ classPath.getName()
795 							+ "' could not be instantiate from file path. Error: "
796 							+ io.getMessage());
797 				}
798 				// get an enumeration of the files in this jar
799 				files = module.entries();
800 			}
801 			
802 			// for each file path in our directory or jar
803 			while (files != null && files.hasMoreElements()) {
804 				// get each fileName
805 				String fileName = files.nextElement().toString();
806 				// we only want the class files
807 				if (fileName.endsWith(".class")) {
808 					// convert our full filename to a fully qualified class name
809 					String className = fileName.replaceAll("/", ".").substring(0, fileName.length() - 6);
810 					// debug class list
811 					// System.out.println(className);
812 					// skip any classes in packages not explicitly requested in
813 					// our package filter
814 					if (packageFilter != null && !packageFilter.contains(className.substring(0, className.lastIndexOf(".")))) {
815 						continue;
816 					}
817 					// get the class for our class name
818 					Class theClass = null;
819 					try {
820 						theClass = Class.forName(className, false, classLoader);
821 					} catch (NoClassDefFoundError e) {
822 						log.trace("Skipping class '" + className + "' for reason " + e.getMessage());
823 						continue;
824 					}
825 					// skip interfaces
826 					if (theClass.isInterface()) {
827 						continue;
828 					}
829 					// then get an array of all the interfaces in our class
830 					Class[] classInterfaces = theClass.getInterfaces();
831 
832 					// for each interface in this class, add both class and
833 					// interface into the map
834 					String interfaceName = null;
835 					for (int i = 0; i < classInterfaces.length
836 							|| (i == 0 && interfaceFilter == null); i++) {
837 						if (i < classInterfaces.length) {
838 							interfaceName = classInterfaces[i].getName();
839 							// was this interface requested?
840 							if (interfaceFilter != null
841 									&& !interfaceFilter.contains(interfaceName)) {
842 								continue;
843 							}
844 						}
845 						// is this interface already in the map?
846 						if (classTable.containsKey(interfaceName)) {
847 							// if so then just add this class to the end of the
848 							// list of classes implementing this interface
849 							classTable.get(interfaceName).add(theClass);
850 						} else {
851 							// else create a new list initialised with our first
852 							// class and put the list into the map
853 							Set<Class<ConfigElement>> allClasses = new HashSet();
854 							allClasses.add(theClass);
855 							classTable.put(interfaceName, allClasses);
856 						}
857 					}
858 
859 				}
860 			}
861 
862 			// close the jar if it was used
863 			if (module != null) {
864 				try {
865 					module.close();
866 				} catch (IOException ioe) {
867 					throw new ClassNotFoundException("The module jar file '"
868 							+ classPath.getName()
869 							+ "' could not be closed. Error: "
870 							+ ioe.getMessage());
871 				}
872 			}
873 
874 		} // end for loop
875 
876 		return classTable.values().iterator().next();
877 	} // end method
878 
879 	/**
880 	 * Recursively lists a directory while generating relative paths. This is a
881 	 * helper function for findClasses. Note: Uses a StringBuffer to avoid the
882 	 * excessive overhead of multiple String concatentation
883 	 * 
884 	 * @param dirListing
885 	 *            A list variable for storing the directory listing as a list of
886 	 *            Strings
887 	 * @param dir
888 	 *            A File for the directory to be listed
889 	 * @param relativePath
890 	 *            A StringBuffer used for building the relative paths
891 	 */
892 	protected static void recursivelyListDir(List<String> dirListing, File dir,
893 			StringBuffer relativePath) {
894 		int prevLen; // used to undo append operations to the StringBuffer
895 
896 		// if the dir is really a directory
897 		if (dir.isDirectory()) {
898 			// get a list of the files in this directory
899 			File[] files = dir.listFiles();
900 			// for each file in the present dir
901 			for (int i = 0; i < files.length; i++) {
902 				// store our original relative path string length
903 				prevLen = relativePath.length();
904 				// call this function recursively with file list from present
905 				// dir and relateveto appended with present dir
906 				recursivelyListDir(dirListing, files[i], relativePath.append(
907 						prevLen == 0 ? "" : "/").append(files[i].getName()));
908 				// delete subdirectory previously appended to our relative path
909 				relativePath.delete(prevLen, relativePath.length());
910 			}
911 		} else {
912 			// this dir is a file; append it to the relativeto path and add it
913 			// to the directory listing
914 			dirListing.add(relativePath.toString());
915 		}
916 	}
917 
918 }