blob: 29977ea128634833a733426c61044b254abbc2f7 [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.profile.handlers.lightweight;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import org.apache.oodt.commons.Configuration;
import org.apache.oodt.commons.ExecServerConfig;
import org.apache.oodt.profile.Profile;
import org.apache.oodt.profile.ProfileException;
import org.apache.oodt.profile.handlers.ProfileHandler;
import org.apache.oodt.commons.util.DOMParser;
import org.apache.oodt.commons.util.XML;
import org.apache.oodt.xmlquery.QueryElement;
import org.apache.oodt.xmlquery.XMLQuery;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* A lightweight profile server.
*
* A lightweight profile server is lightweight because it doesn't rely on any external
* database or other search/retrieval mechanism.
*
* @author Kelly
*/
final public class LightweightProfileServer implements ProfileHandler {
/**
* Create a lightweight profile server using defaults.
*
* @throws IOException If an I/O error occurs.
* @throws SAXException If an error occurs parsing the profile file.
* @throws MalformedURLException If the default profile URL is malformed.
*/
public LightweightProfileServer() throws IOException, SAXException, MalformedURLException {
this(System.getProperties());
}
/**
* Create a lightweight profile server using the given properties.
*
* The property we use is
* <code>profiles.url</code>,
* which is the URL of the file containing profile definitions that this server
* will read and serve.
*
* @param props Properties.
* @throws IOException If an I/O error occurs.
* @throws SAXException If an error occurs parsing the profile file.
* @throws MalformedURLException If the URL to the profile file is malformed.
*/
public LightweightProfileServer(Properties props) throws IOException, SAXException, MalformedURLException {
this(new URL(props.getProperty("org.apache.oodt.profile.handlers.LightweightProfileServer.profiles.url",
props.getProperty("org.apache.oodt.profile.webServer.baseURL", "http://eda.jpl.nasa.gov")
+ "/profiles.xml")),
props.getProperty("org.apache.oodt.profile.handlers.LightweightProfileServer.id", "lightweight"));
}
/**
* Create a lightweight profile server using the given URL.
*
* @param url URL of the file containing profile definitions that this server will read and serve.
* @param id Identifier to report for when this handler is queried by name.
* @throws IOException If an I/O error occurs.
* @throws SAXException If an error occurs parsing the profile file.
* @throws MalformedURLException If <var>url</var> is malformed.
*/
public LightweightProfileServer(URL url, String id) throws IOException, SAXException, MalformedURLException {
this.id = id;
// Get the list of profiles from the cache, if it's there.
profiles = (List) cache.get(url);
if (profiles != null) return;
// It wasn't in the cache, so create a parser to parse the file. We only
// deal with correct files, so turn on validation and install an error
// handler that will throw an exception.
profiles = new ArrayList();
DOMParser parser = XML.createDOMParser();
parser.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException ex) throws SAXParseException {
System.err.println("Parse error line " + ex.getLineNumber() + " column "
+ ex.getColumnNumber() + ": " + ex.getMessage());
throw ex;
}
public void warning(SAXParseException ex) {
System.err.println("Parse warning: " + ex.getMessage());
}
public void fatalError(SAXParseException ex) throws SAXParseException {
throw ex;
}
});
// Parse the file.
InputSource inputSource = new InputSource(url.toString());
parser.parse(inputSource);
Document doc = parser.getDocument();
// Normalize it and get the document element, and from that, create a list
// of profiles, and add it to the cache.
doc.normalize();
Element root = doc.getDocumentElement();
profiles = Profile.createProfiles(root, new SearchableObjectFactory());
cache.put(url, profiles);
System.err.println("LightweightProfileServer ready");
}
public List findProfiles(XMLQuery query) throws ProfileException {
// Compute the where-expression based on the query string, and start off
// with an empty set of results.
Set matchingProfiles = new HashSet();
WhereExpression whereExpression = createWhereExpression(query);
// Search each profile, and add the results of the search to the set of results.
for (Iterator i = profiles.iterator(); i.hasNext();) {
SearchableProfile profile = (SearchableProfile) i.next();
// Search the profile with the where-expression.
Result result = profile.search(whereExpression);
// If there are any matching elements, add the profile to the set.
if (!result.matchingElements().isEmpty())
matchingProfiles.add(profile);
}
// Convert from set to list.
return new ArrayList(matchingProfiles);
}
/**
* Get a single profile matching the given ID.
*
* @param profID a {@link String} value.
* @return a {@link Profile} value.
*/
public Profile get(String profID) {
if (profID == null) return null;
Profile rc = null;
for (Iterator i = profiles.iterator(); i.hasNext();) {
Profile p = (Profile) i.next();
if (p.getProfileAttributes().getID().equals(profID)) {
rc = p;
break;
}
}
return rc;
}
/**
* Convert the XML query to a where expression.
*
* @param query The query to convert
* @return The equivalent, simplified where expression.
*/
private static WhereExpression createWhereExpression(XMLQuery query) {
Stack stack = new Stack();
// Collect together the where- and from-sets, joined with an "and".
List allElements = new ArrayList(query.getWhereElementSet());
List fromElements = query.getFromElementSet();
if (!fromElements.isEmpty()) {
allElements.addAll(fromElements);
allElements.add(new QueryElement("LOGOP", "AND"));
}
// For each item in the where-set
for (Iterator i = allElements.iterator(); i.hasNext();) {
QueryElement queryElement = (QueryElement) i.next();
// Get the keyword and its type.
String keyword = queryElement.getValue();
String type = queryElement.getRole();
if (type.equals("elemName")) {
// It's an element name, so push the element name.
stack.push(keyword);
} else if (type.equals("LITERAL")) {
// It's a literal value, so push the value.
stack.push(keyword);
} else if (type.equals("LOGOP")) {
// It's a logical operator. Pop the operands off the
// stack and push the appropriate operator back on.
if (keyword.equals("AND")) {
stack.push(new AndExpression((WhereExpression) stack.pop(), (WhereExpression)stack.pop()));
} else if (keyword.equals("OR")) {
stack.push(new OrExpression((WhereExpression) stack.pop(), (WhereExpression)stack.pop()));
} else if (keyword.equals("NOT")) {
stack.push(new NotExpression((WhereExpression) stack.pop()));
} else throw new IllegalArgumentException("Illegal operator \"" + keyword + "\" in query");
} else if (type.equals("RELOP")) {
// It's a relational operator. Pop the element name and
// literal value off the stack, and push the operator
// expression on with the given operator.
stack.push(new OperatorExpression((String) stack.pop(), (String) stack.pop(), keyword));
}
}
// If there's nothing on the stack, we're given nothing, so give back everything.
if (stack.size() == 0)
return new ConstantExpression(true);
else if (stack.size() > 1)
throw new IllegalStateException("Imbalanced expression in query");
// Simplify/optimize the where-expression and return it.
return ((WhereExpression) stack.pop()).simplify();
}
/** {@inheritDoc} */
public String getID() {
return id;
}
/** Profiles I serve. */
private List profiles;
/**
* Cache of profiles.
*
* This is a mapping from {@link java.net.URL} of the profile source to the {@link
* List} of profiles. We do this so we don't have to keep rereading and reparsing
* the possibly huge profile file each time an object of this class is
* instantiated.
*
* <p><em>Question:</em> Since when are multiple LightweightProfileServers being
* constructed anyway? There's just one per profile server process, and it's
* using just the one file, so there should be no need for this cache. Who added
* this? And if it were me, what was I smoking?
*/
private static Map cache = new HashMap();
/** My ID. */
private String id;
/**
* Application execution entry point.
*
* This lets you try out a query to the Lightweight Profile server.
*
* @param argv Command-line arguments.
* @throws Exception Should any error occur.
*/
public static void main(String[] argv) throws Exception {
if (argv.length == 0) {
System.err.println("Usage: <query>...");
System.exit(1);
}
// Create the profile
LightweightProfileServer lp = new LightweightProfileServer();
// Gather together the command-line arguments into a single long string.
StringBuffer b = new StringBuffer();
for (int i = 0; i < argv.length; ++i)
b.append(argv[i]).append(' ');
// Create the query object from the expression.
XMLQuery query = new XMLQuery(b.toString().trim(), /*id*/"cli1", /*title*/"CmdLine-1",
/*desc*/"This is a query entered on the command-line", /*ddId*/null, /*resultModeId*/null,
/*propType*/null, /*propLevels*/null, XMLQuery.DEFAULT_MAX_RESULTS);
// Display the results.
System.out.println(lp.findProfiles(query));
// All done.
System.exit(0);
}
}