blob: 8ea537593c8fb9d9ead5f02b5f9cd0bb4b032838 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.oodt.grid;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;
import org.apache.oodt.commons.util.Base64;
/**
* web-grid configuration. This holds all the runtime configuration of profile servers,
* product servers, properties, and other settings for the web-grid container.
*
*/
public class Configuration implements Serializable {
/**
* Creates a new <code>Configuration</code> instance.
*
* @param file File containing the serialized configuration.
* @throws IOException if an I/O error occurs.
* @throws SAXException if the file can't be parsed.
*/
public Configuration(File file) throws IOException, SAXException {
this.file = file;
if (file.isFile() && file.length() > 0)
parse(file);
}
/**
* Convert the configuration to XML.
*
* @param owner Owning document.
* @return XML representation of this configuration.
*/
public Node toXML(Document owner) {
Element elem = owner.createElement("configuration"); // <configuration>
elem.setAttribute("xmlns", NS); // Set default namespace
elem.setAttribute("https", httpsRequired? "true" : "false"); // Add https attribute
elem.setAttribute("localhost", localhostRequired? "true" : "false"); // Add localhost attribute
if (password != null && password.length > 0) // If we have a password
elem.setAttribute("password", encode(password)); // Add passowrd attribute
for (Iterator i = productServers.iterator(); i.hasNext();) { // For each product server
ProductServer ps = (ProductServer) i.next(); // Get the product server
elem.appendChild(ps.toXML(owner)); // And add it under <configuration>
}
for (Iterator i = profileServers.iterator(); i.hasNext();) { // For each profile server
ProfileServer ps = (ProfileServer) i.next(); // Get the profile server
elem.appendChild(ps.toXML(owner)); // And add it under the <configuration>
}
if (!codeBases.isEmpty()) { // Got any code bases?
Element cbs = owner.createElement("codeBases"); // Boo yah. Make a parent for 'em
elem.appendChild(cbs); // Add parent
for (Iterator i = codeBases.iterator(); i.hasNext();) { // Then, for each code base
URL url = (URL) i.next(); // Get the URL to it
Element cb = owner.createElement("codeBase"); // And make a <codeBase> for it
cb.setAttribute("url", url.toString()); // And an "url" attribute
cbs.appendChild(cb); // Add it
}
}
if (!properties.isEmpty()) { // If we have any properties>
Element props = owner.createElement("properties"); // Add <properties> under <configuration>
props.setAttribute("xml:space", "preserve"); // And make sure space is properly preserved
elem.appendChild(props); // Add the space attribute
for (Iterator i = properties.entrySet().iterator(); i.hasNext();) { // For each property
Map.Entry entry = (Map.Entry) i.next(); // Get the property key/value pair
String key = (String) entry.getKey(); // Key is always a String
String value = (String) entry.getValue(); // So is the value
Element prop = owner.createElement("property"); // Create a <property> element
props.appendChild(prop); // Add it under the <properties>
prop.setAttribute("key", key); // Set the key as an attribute
Text text = owner.createTextNode(value); // Make text to hold the value
prop.appendChild(text); // Add it under the <property>
}
}
return elem;
}
/**
* Get the properties.
*
* @return a <code>Properties</code> value.
*/
public Properties getProperties() {
return properties;
}
/**
* Is localhost access required for the configuration?
*
* @return True if the configuration must be accessed from the localhost, false otherwise.
*/
public boolean isLocalhostRequired() {
return localhostRequired;
}
/**
* Is https access required for the configuration?
*
* @return True if the configuration must be accessed via https, false if http is OK.
*/
public boolean isHTTPSrequired() {
return httpsRequired;
}
/**
* Set if https is required to access the configuration.
*
* @param required True if the configuration must be accessed via https, false if http is OK.
*/
public void setHTTPSrequired(boolean required) {
httpsRequired = required;
}
/**
* Set if localhost is required to access the configuration.
*
* @param required True if the configuration must be accessed from the localhost, false otherwise.
*/
public void setLocalhostRequired(boolean required) {
localhostRequired = required;
}
/**
* Return the code bases.
*
* @return a <code>List</code> of {@link URL}s.
*/
public List getCodeBases() {
return codeBases;
}
/**
* Get the product servers.
*
* @return a <code>List</code> of {@link ProductServer}s.
*/
public List getProductServers() {
return productServers;
}
/**
* Get the profile servers.
*
* @return a <code>List</code> of {@link ProfileServer}s.
*/
public List getProfileServers() {
return profileServers;
}
/**
* Get the administrator password.
*
* @return Administrator password.
*/
public byte[] getPassword() {
return password;
}
/**
* Set the administrator password.
*
* @param password Administrator password.
*/
public void setPassword(byte[] password) {
if (password == null)
throw new IllegalArgumentException("Non-null passwords not allowed");
this.password = password;
}
/**
* Save the configuration.
*
* @throws IOException if an I/O error occurs.
*/
public synchronized void save() throws IOException {
BufferedWriter writer = null; // Start w/no writer
try { // Then try ...
writer = new BufferedWriter(new FileWriter(file)); // Create a writer
Document doc; // As for the doc...
synchronized (DOCUMENT_BUILDER) { // Using the document builder...
doc = DOCUMENT_BUILDER.newDocument(); // Create an empty document
}
Node root = toXML(doc); // Convert this config to XML
doc.appendChild(root); // Add it to the doc
DOMSource source = new DOMSource(doc); // Use the source Luke
StreamResult result = new StreamResult(writer); // And serialize it to the writer
TRANSFORMER.transform(source, result); // Serialize
} catch (TransformerException ex) {
throw new IllegalStateException("Unexpected TransformerException: " + ex.getMessage());
} finally {
if (writer != null) try { // And if we got a writer, try ...
writer.close(); // to close it
} catch (IOException ignore) {} // Ignoring any error
}
}
/**
* Parse a serialized configuration document.
*
* @param file File to parse
* @throws IOException if an I/O error occurs.
* @throws SAXException if a parse error occurs.
*/
private void parse(File file) throws IOException, SAXException {
Document doc; // Start with a doc ...
synchronized (DOCUMENT_BUILDER) { // And using the DOCUMENT_BUILDER
doc = DOCUMENT_BUILDER.parse(file); // Try to parse the file
}
Element root = doc.getDocumentElement(); // Assume the root element is <configuration>
String httpsAttr = root.getAttribute("https"); // Get the https attribute
httpsRequired = httpsAttr != null && "true".equals(httpsAttr); // See if it's "true"
String localhostAttr = root.getAttribute("localhost"); // Get the localhost attribute
localhostRequired = localhostAttr != null && "true".equals(localhostAttr); // See if it's "true"
String passwordAttr = root.getAttribute("password"); // Get the password attribute
if (passwordAttr != null && passwordAttr.length() > 0) // If it's there, and non-empty
password = decode(passwordAttr); // Then decode it
NodeList children = root.getChildNodes(); // Get the child nodes
for (int i = 0; i < children.getLength(); ++i) { // For each child node
Node child = children.item(i); // Get the child
if (child.getNodeType() == Node.ELEMENT_NODE) { // An element?
if ("server".equals(child.getNodeName())) { // A <server>?
Server server = Server.create(this, (Element) child); // Create the correct server
// Keep these in separate sets?
if (server instanceof ProductServer) // Is a product server?
productServers.add(server); // Add to product servers
else if (server instanceof ProfileServer) // Is a profile server?
profileServers.add(server); // Add to profile servers
else throw new IllegalArgumentException("Unexpected server type " + server + " in " + file);
} else if ("properties".equals(child.getNodeName())) { // Is it a <properties>?
NodeList props = child.getChildNodes(); // Get its children
for (int j = 0; j < props.getLength(); ++j) { // For each child
Node node = props.item(j); // Get the chld
if (node.getNodeType() == Node.ELEMENT_NODE // And element?
&& "property".equals(node.getNodeName())) { // And it's <property>?
Element propNode = (Element) node; // Great, use it as an element
String key = propNode.getAttribute("key"); // Get its key attribute
if (key == null || key.length() == 0) // Make sure it's there
throw new SAXException("Required 'key' attribute missing from "
+ "<property> element");
properties.setProperty(key, text(propNode)); // And set it
}
}
} else if ("codeBases".equals(child.getNodeName())) { // And yadda
NodeList cbs = child.getChildNodes(); // yadda
for (int j = 0; j < cbs.getLength(); ++j) { // yadda.
Node node = cbs.item(j);
if (node.getNodeType() == Node.ELEMENT_NODE
&& "codeBase".equals(node.getNodeName())) {
Element cbNode = (Element) node;
String u = cbNode.getAttribute("url");
if (u == null || u.length() == 0)
throw new SAXException("Required 'url' attribute missing from "
+ "<codeBase> element");
try {
codeBases.add(new URL(u));
} catch (MalformedURLException ex) {
throw new SAXException("url attribute " + u + " isn't a valid URL");
}
}
}
}
}
}
}
/**
* Encode a password. This just hides it so it's not plain text, but it's just as
* easy to decode it if you have a base-64 decoder handy.
*
* @param password a <code>byte[]</code> value.
* @return a <code>String</code> value.
*/
static String encode(byte[] password) {
return new String(Base64.encode(password));
}
/**
* Decode a password.
*
* @param password a <code>String</code> value.
* @return a <code>byte[]</code> value.
*/
static byte[] decode(String password) {
return Base64.decode(password.getBytes());
}
/**
* Get the text under an XML node.
*
* @param node a <code>Node</code> value.
* @return a <code>String</code> value.
*/
private static String text(Node node) {
StringBuffer b = new StringBuffer();
text0(node, b);
return b.toString();
}
/**
* Get the text from an XML node into a StringBuffer.
*
* @param node a <code>Node</code> value.
* @param b a <code>StringBuffer</code> value.
*/
private static void text0(Node node, StringBuffer b) {
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); ++i)
text0(children.item(i), b);
short type = node.getNodeType();
if (type == Node.CDATA_SECTION_NODE || type == Node.TEXT_NODE)
b.append(node.getNodeValue());
}
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj instanceof Configuration) {
Configuration rhs = (Configuration) obj;
return codeBases.equals(rhs.codeBases) && productServers.equals(rhs.productServers)
&& profileServers.equals(rhs.profileServers) && Arrays.equals(password, rhs.password)
&& httpsRequired == rhs.httpsRequired && localhostRequired == rhs.localhostRequired
&& properties.equals(rhs.properties);
}
return false;
}
public int hashCode() {
return codeBases.hashCode() ^ productServers.hashCode() ^ profileServers.hashCode();
}
/** List of {@link URL}s to code bases. */
private List codeBases = new ArrayList();
/** List of {@link ProductServer}s. */
private List productServers = new ArrayList();
/** List of {@link ProfileServer}s. */
private List profileServers = new ArrayList();
/** Admin password. */
private byte[] password = DEFAULT_PASSWORD;
/** True if https is requried. */
private boolean httpsRequired;
/** True if localhost access is required. */
private boolean localhostRequired;
/** Properties to set. */
private Properties properties = new Properties();
/** Where to save the file. */
private transient File file;
/** Default password. */
static final byte[] DEFAULT_PASSWORD = { (byte)'h', (byte)'a', (byte)'n', (byte)'a', (byte)'l', (byte)'e', (byte)'i' };
/** XML namespace */
public static final String NS = "http://oodt.jpl.nasa.gov/web-grid/ns/";
/** Sole document builder we'll need. */
private static final DocumentBuilder DOCUMENT_BUILDER;
/** Sole transfomer we'll need. */
private static final Transformer TRANSFORMER;
static {
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setValidating(false);
documentBuilderFactory.setIgnoringElementContentWhitespace(false);
documentBuilderFactory.setExpandEntityReferences(true);
documentBuilderFactory.setIgnoringComments(true);
documentBuilderFactory.setCoalescing(true);
DOCUMENT_BUILDER = documentBuilderFactory.newDocumentBuilder();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
TRANSFORMER = transformerFactory.newTransformer();
TRANSFORMER.setOutputProperty(OutputKeys.METHOD, "xml");
TRANSFORMER.setOutputProperty(OutputKeys.VERSION, "1.0");
TRANSFORMER.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
TRANSFORMER.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
TRANSFORMER.setOutputProperty(OutputKeys.INDENT, "yes");
TRANSFORMER.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
} catch (ParserConfigurationException ex) {
throw new IllegalStateException("Cannot create document builder");
} catch (TransformerConfigurationException ex) {
throw new IllegalStateException("Cannot create transformer");
}
}
}