| /* |
| * 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); |
| } |
| } |