http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableRangedProfileElement.java ---------------------------------------------------------------------- diff --git a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableRangedProfileElement.java b/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableRangedProfileElement.java deleted file mode 100644 index b2125f6..0000000 --- a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableRangedProfileElement.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.util.List; -import org.apache.oodt.profile.RangedProfileElement; - -/** - * Searchable profile element with a range of valid values. - * - * @author Kelly - * @version $Revision: 1.1.1.1 $ - */ -public class SearchableRangedProfileElement extends RangedProfileElement implements SearchableProfileElement { - public SearchableRangedProfileElement(SearchableProfile profile, String name, String id, String desc, String type, - String unit, List synonyms, boolean obligation, int maxOccurrence, String comment, String min, String max) { - super(profile, name, id, desc, type, unit, synonyms, obligation, maxOccurrence, comment, min, max); - } - - public Result result(String value, String operator) { - Result rc = FalseResult.INSTANCE; - double numeric = Double.parseDouble(value); - if (operator.equals("EQ") || operator.equals("LIKE")) { - if (Double.parseDouble(min) <= numeric && numeric <= Double.parseDouble(max)) { - rc = new MatchingResult(this); - } - } else if (operator.equals("NE") || operator.equals("NOTLIKE")) { - if (numeric < Double.parseDouble(min) || numeric > Double.parseDouble(max)) { - rc = new MatchingResult(this); - } - } else if (operator.equals("LT")) { - if (numeric > Double.parseDouble(min)) { - rc = new MatchingResult(this); - } - } else if (operator.equals("GT")) { - if (numeric < Double.parseDouble(max)) { - rc = new MatchingResult(this); - } - } else if (operator.equals("LE")) { - if (numeric >= Double.parseDouble(min)) { - rc = new MatchingResult(this); - } - } else { - if (numeric <= Double.parseDouble(max)) { - rc = new MatchingResult(this); - } - } - return rc; - } -}
http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableResourceAttributes.java ---------------------------------------------------------------------- diff --git a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableResourceAttributes.java b/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableResourceAttributes.java deleted file mode 100644 index 77786e1..0000000 --- a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableResourceAttributes.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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.util.HashSet; -import java.util.List; -import org.apache.oodt.profile.ResourceAttributes; -import org.w3c.dom.Element; - -/** - * Resource attributes that can be searched. - * - * @author Kelly - * @version $Revision: 1.1.1.1 $ - */ -public class SearchableResourceAttributes extends ResourceAttributes { - public SearchableResourceAttributes(SearchableProfile profile, Element node) { - super(profile, node); - } - - /** - * Produce a search result. - * - * @param value Desired value. - * @param operator What operator to use for comparison. - * @return a <code>Result</code> value. - */ - public Result result(String name, String value, String operator) { - if ("Identifier".equals(name)) { - return computeResult(identifier, value, operator); - } else if ("Title".equals(name)) { - return computeResult(title, value, operator); - } else if ("Format".equals(name)) { - return computeResult(formats, value, operator); - } else if ("Description".equals(name)) { - return computeResult(description, value, operator); - } else if ("Creator".equals(name)) { - return computeResult(creators, value, operator); - } else if ("Subject".equals(name)) { - return computeResult(subjects, value, operator); - } else if ("Publisher".equals(name)) { - return computeResult(publishers, value, operator); - } else if ("Contributor".equals(name)) { - return computeResult(contributors, value, operator); - } else if ("Date".equals(name)) { - return computeResult(dates, value, operator); - } else if ("Type".equals(name)) { - return computeResult(types, value, operator); - } else if ("Source".equals(name)) { - return computeResult(sources, value, operator); - } else if ("Language".equals(name)) { - return computeResult(languages, value, operator); - } else if ("Relation".equals(name)) { - return computeResult(relations, value, operator); - } else if ("Coverage".equals(name)) { - return computeResult(coverages, value, operator); - } else if ("Rights".equals(name)) { - return computeResult(rights, value, operator); - } else if ("resContext".equals(name)) { - return computeResult(contexts, value, operator); - } else if ("resClass".equals(name)) { - return computeResult(clazz, value, operator); - } else if ("resLocation".equals(name)) { - return computeResult(locations, value, operator); - } else { - throw new IllegalArgumentException("Unknown attribute \"" + name + "\""); - } - } - - private Result computeResult(String a, String b, String op) { - int c = a.compareTo(b); - boolean t; - if ("EQ".equals(op) || "LIKE".equals(op)) { - t = c == 0; - } else if ("GE".equals(op)) { - t = c >= 0; - } else if ("GT".equals(op)) { - t = c > 0; - } else if ("LE".equals(op)) { - t = c <= 0; - } else if ("LT".equals(op)) { - t = c < 0; - } else if ("NE".equals(op) || "NOTLIKE".equals(op)) { - t = c != 0; - } else { - throw new IllegalArgumentException("Unknown relational operator \"" + op + "\""); - } - if (t) { - return new MatchingResult(new HashSet(profile.getProfileElements().values())); - } else { - return FalseResult.INSTANCE; - } - } - - private Result computeResult(List a, String b, String op) { - if (a == null || a.isEmpty()) { - return FalseResult.INSTANCE; - } - - Result f = FalseResult.INSTANCE; - Result t = new MatchingResult(new HashSet(profile.getProfileElements().values())); - Result rc = f; - if ("EQ".equals(op) || "LIKE".equals(op)) { - if (a.contains(b)) { - rc = t; - } else if ("NE".equals(op) || "NOTLIKE".equals(op)) { - if (!a.contains(b)) { - rc = t; - } else if ("LT".equals(op) || "GT".equals(op) || "LE".equals(op) || "GE".equals(op)) { - for (Object anA : a) { - String value = (String) anA; - rc = computeResult(value, b, op); - if (rc != f) { - break; - } - } - } else { - throw new IllegalArgumentException("Unknown relational operator \"" + op + "\""); - } - } - } - return rc; - } - - -} http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableUnspecifiedProfileElement.java ---------------------------------------------------------------------- diff --git a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableUnspecifiedProfileElement.java b/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableUnspecifiedProfileElement.java deleted file mode 100644 index f480877..0000000 --- a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/SearchableUnspecifiedProfileElement.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.util.List; -import org.apache.oodt.profile.UnspecifiedProfileElement; - -/** - * Searchable profile element with unspecified values. - * - * @author Kelly - * @version $Revision: 1.1.1.1 $ - */ -public class SearchableUnspecifiedProfileElement extends UnspecifiedProfileElement implements SearchableProfileElement { - public SearchableUnspecifiedProfileElement(SearchableProfile profile, String name, String id, String desc, String type, - String unit, List synonyms, boolean obligation, int maxOccurrence, String comment) { - super(profile, name, id, desc, type, unit, synonyms, obligation, maxOccurrence, comment); - } - - public Result result(String value, String operator) { - return new MatchingResult(this); - } -} http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/Union.java ---------------------------------------------------------------------- diff --git a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/Union.java b/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/Union.java deleted file mode 100644 index 003670a..0000000 --- a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/Union.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.util.Set; -import org.apache.oodt.profile.ProfileElement; - -/** - * A union of matching elements. - * - * @author Kelly - */ -class Union implements Result { - /** - * Construct a union. - * - * @param lhs Left-hand side - * @param rhs Right-hand side. - */ - public Union(Result lhs, Result rhs) { - this.lhs = lhs; - this.rhs = rhs; - } - - public Set matchingElements() { - Set union = ProfileElement.profiles(lhs.matchingElements()); - union.addAll(ProfileElement.profiles(rhs.matchingElements())); - Set rc = ProfileElement.elements(union, lhs.matchingElements()); - rc.addAll(ProfileElement.elements(union, rhs.matchingElements())); - return rc; - } - - public String toString() { - return "union[" + lhs + "," + rhs + "]"; - } - - /** Left-hand side of the result. */ - private Result lhs; - - /** Right-hand side of the result. */ - private Result rhs; -} http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/WhereExpression.java ---------------------------------------------------------------------- diff --git a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/WhereExpression.java b/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/WhereExpression.java deleted file mode 100644 index cf488a6..0000000 --- a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/WhereExpression.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.util.Map; - -/** - * A where-expression. - * - * @author Kelly - */ -interface WhereExpression { - /** - * Compute the result tree of this where expression based on the map of profile elements. - * - * @param resAttr Resource attributes to check. - * @param elements Map from {@link String} element name to {@link ProfileElement}. - * @return A result tree that when evaluated yields matching {@link ProfileElement}s. - */ - Result result(SearchableResourceAttributes resAttr, Map elements); - - /** - * Return a simplified version of this expression. - * - * Our result generation can't handle "NOT" expressions (like "not (blah < 3)") - * since we can't do set inversion, so we simplify the expression by removing all - * "NOT" nodes. - * - * @return An equivalent expression without negative nodes. - */ - WhereExpression simplify(); - - /** - * Negate this expression. - * - * @return The negation of this expression. - */ - WhereExpression negate(); -} http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/package.html ---------------------------------------------------------------------- diff --git a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/package.html b/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/package.html deleted file mode 100644 index 56d73cd..0000000 --- a/profile/src/main/java/org/apache/oodt/profile/handlers/lightweight/package.html +++ /dev/null @@ -1,32 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"> -<!-- -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. ---> -<html> - <head> - <title>Lightweight Profile Handler. - </title> - </head> - <body> - <p>Lightweight profile handler. The lightweight profile handler - is lightweight because it doesn't rely on any external - search/retrieval mechanism. It's 100% pure Java, baby. - </p> - <p>To use this handler, create an XML file that contains the - profiles you want to serve. Set the system property - <code>profiles.url</code> - to the location of that file. Fire 'er up. - </p> - </body> -</html> http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/examples/src.jar ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/examples/src.jar b/profile/src/site/resources/examples/src.jar deleted file mode 100644 index 3edbb96..0000000 Binary files a/profile/src/site/resources/examples/src.jar and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/images/class.png ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/images/class.png b/profile/src/site/resources/images/class.png deleted file mode 100644 index d8a7ab4..0000000 Binary files a/profile/src/site/resources/images/class.png and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/images/class.psd ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/images/class.psd b/profile/src/site/resources/images/class.psd deleted file mode 100644 index 3a20fd4..0000000 Binary files a/profile/src/site/resources/images/class.psd and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/images/delegation.png ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/images/delegation.png b/profile/src/site/resources/images/delegation.png deleted file mode 100644 index fd8d5cf..0000000 Binary files a/profile/src/site/resources/images/delegation.png and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/images/delegation.psd ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/images/delegation.psd b/profile/src/site/resources/images/delegation.psd deleted file mode 100644 index 02a2fd9..0000000 Binary files a/profile/src/site/resources/images/delegation.psd and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/images/grid-profile.jpg ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/images/grid-profile.jpg b/profile/src/site/resources/images/grid-profile.jpg deleted file mode 100644 index de71567..0000000 Binary files a/profile/src/site/resources/images/grid-profile.jpg and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/images/stack.png ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/images/stack.png b/profile/src/site/resources/images/stack.png deleted file mode 100644 index 10856a5..0000000 Binary files a/profile/src/site/resources/images/stack.png and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/images/stack.psd ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/images/stack.psd b/profile/src/site/resources/images/stack.psd deleted file mode 100644 index 6d23e2d..0000000 Binary files a/profile/src/site/resources/images/stack.psd and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/images/tree.png ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/images/tree.png b/profile/src/site/resources/images/tree.png deleted file mode 100644 index 52cf31f..0000000 Binary files a/profile/src/site/resources/images/tree.png and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/resources/images/tree.psd ---------------------------------------------------------------------- diff --git a/profile/src/site/resources/images/tree.psd b/profile/src/site/resources/images/tree.psd deleted file mode 100644 index b51ce99..0000000 Binary files a/profile/src/site/resources/images/tree.psd and /dev/null differ http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/site.xml ---------------------------------------------------------------------- diff --git a/profile/src/site/site.xml b/profile/src/site/site.xml deleted file mode 100644 index 3cfecdf..0000000 --- a/profile/src/site/site.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -Licensed to the Apache Software Foundation (ASF) under one or more contributor -license agreements. See the NOTICE.txt 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. ---> - -<project name="Profile Service"> - - <body> - <links> - <item name="OODT" href="../oodt-site/"/> - <item name="Grid" href="../grid/"/> - </links> - - <menu ref="reports" inherit="bottom"/> - <menu name="User's Guide"> - <item name="Information Captured" href="info/"/> - <item name="Querying Profiles" href="querying/"/> - <item name="Profile Repr." href="rep/"/> - <item name="Basic Handler" href="handler/"/> - <item name="Advanced Handler" href="adv/"/> - </menu> - - </body> -</project> http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/xdoc/adv/index.xml ---------------------------------------------------------------------- diff --git a/profile/src/site/xdoc/adv/index.xml b/profile/src/site/xdoc/adv/index.xml deleted file mode 100755 index 3c8b913..0000000 --- a/profile/src/site/xdoc/adv/index.xml +++ /dev/null @@ -1,1072 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -Licensed to the Apache Software Foundation (ASF) under one or more contributor -license agreements. See the NOTICE.txt 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. ---> -<document> - <properties> - <title>Developing a Real Profile Handler</title> - <author email="[email protected]">Sean Kelly</author> - </properties> - <!-- Wizzy Noise - Haiku --> - <body> - <section name="Developing a Real Profile Handler"> - <p>In the <a href="../handler/">basic profile handler - tutorial</a>, we developed a profile handler that answered - every query with a list of exactly zero profile objects. It - also responded to requests to retrieve a profile by a - specific ID with <code>null</code>, meaning "not found by - this handler." Useful, huh? Not really. - </p> - - <p>But it did get our profile server ready for <em>this</em> - tutorial, where we'll write a <em>real</em> profile handler - that analyzes incoming queries, consults a local "database", - constructs a set of matching profile results, and responds to - requests to profiles by ID. Get some fresh coffee, because - this is going to be a tough one. - </p> - - <p>And yes, you'll need to have gone through <em>all</em> of the - following before proceeding: - </p> - - <ul> - <li><a href="../info/">Information Captured in a Profile</a></li> - <li><a href="../querying/">Querying Profiles</a></li> - <li><a href="../rep">Profile Representation</a></li> - <li><a href="../handler">Developing a Profile Handler</a></li> - </ul> - </section> - - <section name="The Music Database"> - <p>Let's say you've got an OODT <a href="/grid-product/">product - server</a> already running that serves up your favorite - music files. All you have to do is pass in the URI to a - track and it spits back the MP3 data which can run into your - favorite media player. You've set it up so your URIs are - all unique for each track, and you just have to pass in an - unparsed XMLQuery like <code>urn:sk:tr5B7E.mp3</code> and - you get the matching data. - </p> - - <p>But since you're not in the habit of memorizing hexadecimal - numbers inside of URIs, let's write a profile server who's job - it is to take queries for specific artists, genres, albums, - ratings, track titles, and so forth, and spit out the matching - profiles. The profiles have a places for a URI (in the - <code>Identifier</code> field) that you can then pass to the - hypothetical product server to get the track data. (In fact, - this is a common OODT pattern: profile query to do resource - location, product query to do resource retrieval.) While - you're listening to the track, you can read all sorts of other - juicy metadata about it by examining the returned profile. - </p> - - <subsection name="The Metadata"> - <p>For this demonstration, we'll just focus on three kinds of - metadata instead of going all-out, <a - href="http://www.apple.com/itunes/">iTunes</a> style: - </p> - - <ul> - <li>Artist name</li> - <li>Album name</li> - <li>Track name</li> - </ul> - - <p>Each profile will describe a single track. The resource - attributes will have: - </p> - - <ul> - <li>The URI of the track as the <code>Identifier</code>.</li> - <li>The name of the track as the <code>Title</code>.</li> - <li>The name of the artist as the <code>Creator</code>.</li> - </ul> - - <p>In addition, we'll put in two profile elements:</p> - - <ul> - <li>The name of the album as an - <code>EnumeratedProfileElement</code>. - </li> - <li>The name of the artist as an - <code>EnumeratedProfileElement</code>. Yes, this is - redundant with the artist named as the <code>Creator</code> - in the resource attributes; but one profile element by - itself would get too lonely!</li> - </ul> - </subsection> - - <subsection name="Query Style"> - <p>Both product handlers and profile handlers get to choose - whether they want unparsed query expressions in their - XMLQuery objects or if they want parsed query expressions. - Parsed query expressions generate the "where" boolean stack. - While the product server that this profile server is meant - to work with wants unparsed ones, we'll use parsed - expressions for this profile handler. Why? Well, having a - well-defined query language and a way to operate on it will - save a little trouble from us having to generate a parser. - </p> - - <p>The queries will use element names <code>artist</code>, - <code>album</code>, and <code>track</code> only, to match - what we'll save in our music database. Here are a couple example queries: - </p> - - <source>artist = Beatles AND album = Revolver -track = 'Blue Suede Shoes'</source> - </subsection> - </section> - - <section name="Developing the Handler"> - <p>We'll develop the handler in parts, so we can discuss each - section, and then show the entire source file. - </p> - - <subsection name='Making the "Database"'> - <p>Our music database will be nothing more than Java objects - kept in memory. We'll create separate objects of three - classes: - </p> - - <ul> - <li><code>Artist</code>. <code>Artist</code> objects represent - people or groups who create music. <code>Artist</code>s - will have zero or more <code>Track</code>s. - </li> - <li><code>Album</code>. <code>Album</code> objects are - collections of <code>Track</code>s. - </li> - <li><code>Track</code>. <code>Track</code> objects appear - on one <code>Album</code> and are made by one - <code>Artist</code>. They have the URN necessary to pass - to the hypothetical music product server in order to - actually play music. - </li> - </ul> - - <p>A better music model would probably separate out artists - and composers, account for remixes, compilation albums, - re-issues, multiple renditions, and so forth, but this is - government work, and it'll do. - </p> - - <p>Here's class <code>Artist</code>:</p> - - <source>class Artist { - public Artist(String name) { - this.name = name; - tracks = new ArrayList(); - } - public String getName() { - return name; - } - public List getTracks() { - return tracks; - } - public int hashCode() { - return name.hashCode(); - } - public boolean equals(Object obj) { - if (obj == this) return true; - if (!(obj instanceof Artist)) return false; - Artist rhs = (Artist) obj; - return name.equals(rhs.name); - } - private String name; - private List tracks; -}</source> - - <p>As you can see, <code>Artist</code>s have a name and a - <code>List</code> of <code>Track</code>s they've made. Now, - here's class <code>Album</code>: - </p> - - <source>class Album { - public Album(String name) { - this.name = name; - tracks = new ArrayList(); - } - public String getName() { - return name; - } - public List getTracks() { - return tracks; - } - public int hashCode() { - return name.hashCode(); - } - public boolean equals(Object obj) { - if (obj == this) return true; - if (!(obj instanceof Album)) return false; - Album rhs = (Album) obj; - return name.equals(rhs.name); - } - private String name; - private List tracks; -}</source> - - <p>As with <code>Artist</code>s, <code>Album</code>s (or - should that be <em>Alba</em>?) have names and collections of - <code>Track</code>s. Finally, here's class <code>Track</code>: - </p> - - <source>class Track { - public Track(String name, URI id, Artist artist, - Album album) { - this.name = name; - this.id = id; - this.artist = artist; - this.album = album; - artist.getTracks().add(this); - album.getTracks().add(this); - } - public String getName() { return name; } - public URI getID() { return id; } - public Artist getArtist() { return artist; } - public Album getAlbum() { return album; } - public int hashCode() { - return name.hashCode() ^ id.hashCode(); - } - public boolean equals(Object obj) { - if (obj == this) return true; - if (!(obj instanceof Track)) return false; - Track rhs = (Track) obj; - return id.equals(rhs.id); - } - private String name; - private URI id; - private Artist artist; - private Album album; -}</source> - - <p>As you can see from the code, a track belongs to an - <code>Artist</code> and to an <code>Album</code> and has a - URI which you can use to get to the track's MP3 data. - Finally, with these three "entity" classes in hand, we can - create a music database: - </p> - - <source>class DB { - public static Set ARTISTS = new HashSet(); - public static Set ALBUMS = new HashSet(); - public static Set TRACKS = new HashSet(); - static { - Artist bach = new Artist("Bach"); - Album brandenburg123 - = new Album("Brandenburg Concerti 1, 2, 3"); - Album brandenburg456 - = new Album("Brandenburg Concerti 4, 5, 6"); - Track brandenburg1 - = new Track("Brandenburg Concerto #1", - URI.create("urn:sk:tr91BC.mp3"), bach, - brandenburg123); - Track brandenburg2 - = new Track("Brandenburg Concerto #2", - URI.create("urn:sk:tr311E.mp3"), bach, - brandenburg123); - Track brandenburg3 - = new Track("Brandenburg Concerto #3", - URI.create("urn:sk:trA981.mp3"), bach, - brandenburg123); - Track brandenburg4 - = new Track("Brandenburg Concerto #4", - URI.create("urn:sk:tr233A.mp3"), bach, - brandenburg456); - Track brandenburg5 - = new Track("Brandenburg Concerto #5", - URI.create("urn:sk:trA6E5.mp3"), bach, - brandenburg456); - - Track brandenburg6 - = new Track("Brandenburg Concerto #6", - URI.create("urn:sk:tr01E9.mp3"), bach, - brandenburg456); - - Artist delerium = new Artist("Delerium"); - Album semantic = new Album("Semantic Spaces"); - Album poem = new Album("Poem"); - Track flowers - = new Track("Flowers Become Screens", - URI.create("urn:sk:tr3A5E.mp3"), delerium, - semantic); - Track metaphor = new Track("Metaphor", - URI.create("urn:sk:tr0E13.mp3"), delerium, - semantic); - Track innocente = new Track("Innocente", - URI.create("urn:sk:tr004A.mp3"), delerium, - poem); - Track aria = new Track("Aria", - URI.create("urn:sk:tr004A.mp3"), delerium, - poem); - - ARTISTS.add(bach); - ARTISTS.add(delerium); - ALBUMS.add(brandenburg123); - ALBUMS.add(brandenburg456); - ALBUMS.add(semantic); - ALBUMS.add(poem); - TRACKS.add(brandenburg1); - TRACKS.add(brandenburg2); - TRACKS.add(brandenburg3); - TRACKS.add(brandenburg4); - TRACKS.add(brandenburg5); - TRACKS.add(brandenburg6); - TRACKS.add(flowers); - TRACKS.add(metaphor); - TRACKS.add(innocente); - TRACKS.add(aria); - } -}</source> - - <p>(Please don't judge this limited collection as the breadth - of my listening tastes. It's actually much narrower now!) - In this small database, we've got two artists, Bach and - Delerium, with four albums: <i>Brandenburg Concerti 1, 2, - 3</i> and <i>4, 5, 6</i>; and <i>Semantic Spaces</i> and - <i>Poem</i>. And we've got 10 tracks: 3 belonging to one - album, 3 belonging to another, 2 belonging to yet another, - and the last 2 belonging to the last album. Six are by - Bach, and four by Delerium. Each track has - </p> - </subsection> - - <subsection name="Querying our Database"> - <p>Recall that the <a href="/edm-query/">XMLQuery</a>'s query - language uses triples of the form (element, relation, - literal) like <code>album != Poem</code>. The relations - include =, !=, <, >, <=, >=, LIKE, and NOTLIKE. - The triples are linked with AND, OR, and NOT. For this - tutorial, we'll do the = and != cases. The rest you can - fill in for your own edification. Our approach will be to - examine the postfix "where" boolean stack and convert it - into an infix boolean expression tree. We'll ask the tree - to evaluate itself into a matching set of - <code>Track</code>s. Then all we have to do is descibe the - matching <code>Track</code>s as <code>Profile</code> - objects. - </p> - - <p>Let's start by defining a node in our expression tree:</p> - - <source>interface Expr { - Set evaluate(); -}</source> - - <p>The <code>evaluate</code> method means "evaluate into a - <code>Set</code> of matching <code>Track</code> objects." - With this interface, we can then define classes that make up - different flavors of tree nodes. One of the easier ones is - a constant tree node that either matches <em>every</em> - track available (constant true) or <em>none</em> of them - (constant false): - </p> - - <source>class Constant implements Expr { - public Constant(boolean value) { - this.value = value; - } - public Set evaluate() { - return value? DB.TRACKS - : Collections.EMPTY_SET; - } - private boolean value; -}</source> - - <p>Next, let's do negation. This takes the set complement of - an existing tree node: - </p> - - <source>class Not implements Expr { - public Not(Expr expr) { - this.expr = expr; - } - public Set evaluate() { - Set matches = expr.evaluate(); - Set inverse = new HashSet(); - for (Iterator i = DB.TRACKS.iterator(); - i.hasNext();) { - Track t = (Track) i.next(); - if (!matches.contains(t)) - inverse.add(t); - } - return inverse; - } - private Expr expr; -}</source> - - <p>As you can see, this node is constructed with another tree - node expression. To evaluate this node, we evaluate the - expression passed in. Then we take its inverse by iterating - through each track in the database and adding it to the - matching set if it <em>doesn't</em> occur in the - expression's matching set. - </p> - - <p>The union tree node takes two expressions and adds the two - sets of matching tracks together: - </p> - - <source>class Or implements Expr { - public Or(Expr lhs, Expr rhs) { - this.lhs = lhs; - this.rhs = rhs; - } - public Set evaluate() { - Set left = lhs.evaluate(); - Set right = rhs.evaluate(); - left.addAll(right); - return left; - } - private Expr lhs; - private Expr rhs; -}</source> - - <p>The intersection tree node evaluates to <code>Track</code>s - that occur only in both expressions' tracks: - </p> - - <source>class And implements Expr { - public And(Expr lhs, Expr rhs) { - this.lhs = lhs; - this.rhs = rhs; - } - public Set evaluate() { - Set left = lhs.evaluate(); - Set right = rhs.evaluate(); - left.retainAll(right); - return left; - } - private Expr lhs; - private Expr rhs; -}</source> - - <p>With these nodes, we can cover the logical operators AND, - OR, and NOT that appear in a postfix "where" stack, as well - as an empty "where" stack, which, by convention, is meant to - be a constant "true", matching all available resources. Now - we just have to handle triples (element, relation, literal). - First up, comparisons against <code>Artist</code>s: - </p> - - <source>class ArtistExpr implements Expr { - public ArtistExpr(String op, String value) { - this.op = op; - this.value = value; - } - public Set evaluate() { - Set tracks = new HashSet(); - if ("EQ".equals(op)) { - for (Iterator i = DB.ARTISTS.iterator(); - i.hasNext();) { - Artist a = (Artist) i.next(); - if (a.getName().equals(value)) - tracks.addAll(a.getTracks()); - } - } else if ("NE".equals(op)) { - for (Iterator i = DB.ARTISTS.iterator(); - i.hasNext();) { - Artist a = (Artist) i.next(); - if (!a.getName().equals(value)) - tracks.addAll(a.getTracks()); - } - } else throw new - UnsupportedOperationException("NYI"); - return tracks; - } - private String op; - private String value; -}</source> - - <p>For an expression like <code>artist = Bach</code> or - <code>artist != Delerium</code> we use a expression node - object of the above class. When it's <code>EQ</code>, we - iterate through all the artists in the database and, when - the artist's name matches, add all of that artist's tracks - to the set of matches. When it's <code>NE</code>, we - instead add all of the artists' tracks whose name - <em>doesn't</em> match. (The other relational operators, - <code>LT</code>, <code>GT</code>, <code>LE</code>, - <code>GE</code>, <code>LIKE</code>, and <code>NOTLIKE</code> - currently throw an exception. You're welcome to try to - implement those.) - </p> - - <p>The <code>AlbumExpr</code> expression node is quite similar:</p> - - <source>class AlbumExpr implements Expr { - public AlbumExpr(String op, String value) { - this.op = op; - this.value = value; - } - public Set evaluate() { - Set tracks = new HashSet(); - if ("EQ".equals(op)) { - for (Iterator i = DB.ALBUMS.iterator(); - i.hasNext();) { - Album a = (Album) i.next(); - if (a.getName().equals(value)) - tracks.addAll(a.getTracks()); - } - } else if ("NE".equals(op)) { - for (Iterator i = DB.ALBUMS.iterator(); - i.hasNext();) { - Album a = (Album) i.next(); - if (!a.getName().equals(value)) - tracks.addAll(a.getTracks()); - } - } else throw new - UnsupportedOperationException("NYI"); - return tracks; - } - private String op; - private String value; -}</source> - - <p>(Another exercise for the reader: refactor out common code - between these two classes.) Finally, the - <code>TrackExpr</code> node is for expressions like - <code>track = Poem</code>: - </p> - - <source>class TrackExpr implements Expr { - public TrackExpr(String op, String value) { - this.op = op; - this.value = value; - } - public Set evaluate() { - Set tracks = new HashSet(); - if ("EQ".equals(op)) { - for (Iterator i = DB.TRACKS.iterator(); - i.hasNext();) { - Track t = (Track) i.next(); - if (t.getName().equals(value)) - tracks.add(t); - } - } else if ("NE".equals(op)) { - for (Iterator i = DB.TRACKS.iterator(); - i.hasNext();) { - Track t = (Track) i.next(); - if (!t.getName().equals(value)) - tracks.add(t); - } - } else throw new - UnsupportedOperationException("NYI"); - return tracks; - } - private String op; - private String value; -}</source> - - <p>For <code>EQ</code>, we just iterate through every track in - the database and add it to the set of matching tracks if the - names match the name passed into the user's query. For - <code>NE</code>, we add them if their names <em>don't</em> match. - </p> - - <p>That completes all the code for the expression tree. Now - we can start working on the class that implements the - <code>ProfileHandler</code> interface, - <code>MusicHandler</code>. Here, we'll build that - expression tree with the incoming <code>XMLQuery</code>, - which provides its "where" element stack as a postfix - boolean expression. Here's the approach: - </p> - - <ol> - <li>Make a new, empty stack.</li> - <li>For each element in the "where" stack: - <ol> - <li>If it's an element name (<code>artist</code>, - <code>album</code>, <code>track</code>), push the name - onto the stack. - </li> - <li>If it's a literal value (<code>Bach</code>, - <code>Poem</code>, etc.), push it onto the stack. - </li> - <li>If it's a relational operator (<code>EQ</code>, <code>NE</code>, etc.): - <ol> - <li>Pop two values off.</li> - <li>Push an <code>ArtistExpr</code>, <code>AlbumExpr</code>, or <code>TrackExpr</code>.</li> - </ol> - </li> - <li>It it's a logical operator: - <ul> - <li>For <code>AND</code>, pop two values off and push an <code>And</code> node.</li> - <li>For <code>OR</code>, pop two values off and push an <code>Or</code> node.</li> - <li>For <code>NOT</code>, pop one value off and push a <code>Not</code> node.</li> - </ul> - </li> - </ol> - </li> - </ol> - <p>In the end, there will be one element left on the stack, an - <code>Expr</code> node representing the root of the - expression tree. Here's the method of - <code>MusicHandler</code> that implements the algorithm: - </p> - - <source>private static Expr transform(XMLQuery q) { - Stack stack = new Stack(); - for (Iterator i = q.getWhereElementSet() - .iterator(); i.hasNext();) { - QueryElement e = (QueryElement) i.next(); - String keyword = e.getValue(); - String type = e.getRole(); - if ("elemName".equals(type)) - stack.push(keyword); - else if ("LITERAL".equals(type)) - stack.push(keyword); - else if ("RELOP".equals(type)) - addRelational(keyword, (String)stack.pop(), - (String)stack.pop(), stack); - else if ("LOGOP".equals(type)) - addLogical(keyword, stack); - else throw new - IllegalArgumentException("Unknown query " - + type + " type"); - } - if (stack.size() == 0) - return new Constant(true); - else if (stack.size() > 1) - throw new IllegalArgumentException("Unbalanced" - + " query"); - else return (Expr) stack.pop(); -}</source> - - <p>For relational and logical operators, this method defers to - two other utility methods, which we'll see shortly. After - iterating through the entire "where" set, we check to see if - there's an empty stack. That's the case where the user - passes in an empty expression, which by convention we'll - take to mean they want everything. Otherwise, there should - be just one <code>Expr</code> node on the stack, the root of - the expression tree. - </p> - - <p>To handle adding a <code>RELOP</code>, we pop two values - off, the element name (<code>artist</code>, - <code>album</code>, or <code>track</code>), and the literal - value the user wants (<code>Bach</code>, <code>Poem</code>, - etc.), along with the operator and the stack: - </p> - - <source>private static void addRelational(String op, - String value, String kind, Stack stack) { - if ("artist".equals(kind)) - stack.push(new ArtistExpr(op, value)); - else if ("album".equals(kind)) - stack.push(new AlbumExpr(op, value)); - else if ("track".equals(kind)) - stack.push(new TrackExpr(op, value)); - else throw new - IllegalArgumentException("Unknown profile" - + " element " + kind); -}</source> - - <p>This method then replaces the popped off values with the - matching <code>Expr</code> class for artists, albums, or - tracks. - </p> - - <p>To handle adding a <code>LOGOP</code>, we pass the logical - operator and the entire stack to this method: - </p> - - <source>private static void addLogical(String op, - Stack stack) { - if ("AND".equals(op)) - stack.push(new And((Expr)stack.pop(), - (Expr) stack.pop())); - else if ("OR".equals(op)) - stack.push(new Or((Expr)stack.pop(), - (Expr) stack.pop())); - else if ("NOT".equals(op)) - stack.push(new Not((Expr)stack.pop())); - else throw new - IllegalArgumentException("Illegal operator " - + op); -}</source> - - <p>With all this code in place we can generate the expression - tree. Let's look at an example. Suppose when constructing the - <code>XMLQuery</code>, the user passed in</p> - - <source>artist = Bach and not album = Poem or track != Aria</source> - - <p>The XMLQuery query language generates a postfix stack of - <code>QueryElement</code> objects in the "where" list: - </p> - - <img src="../images/stack.png" alt="Stack" /> - - <p>And we then create this tree:</p> - - <img src="../images/tree.png" alt="Tree" /> - - <p>Calling the root's <code>evaluate</code> method then yields - a <code>java.util.Set</code> of <code>Track</code> objects - that match that expression. - </p> - - <p>OK, we've got a set of <code>Track</code>s. But what we - want are a set of <em><code>Profile</code>s</em>. The next - step is to describe those tracks using the profile metadata - model. - </p> - </subsection> - - <subsection name="Describing Tracks"> - <p>Query handlers serve up <code>List</code>s of - <code>Profile</code> objects, where <code>Profile</code>s - contain metadata descriptions of resources. For this - tutorial, the resources we're describing are music tracks, - represented by instances of <code>Track</code> objects. - When the handler's <code>findProfiles</code> and - <code>get</code> methods are called by the OODT framework to - service a request, all we have to do is find the matching - <code>Track</code> (or <code>Track</code>s) and create - matching <code>Profile</code>s. - </p> - - <p>Recall that we're setting up the resource attributes of the - profile so that - </p> - <ul> - <li>The URI of the track appears in the <code>Identifier</code>.</li> - <li>The name of the track appears in the <code>Title</code>.</li> - <li>The name of the artist appears the <code>Creator</code>.</li> - </ul> - - <p>In addition, we'll put in two profile elements:</p> - - <ul> - <li>The name of the album as an - <code>EnumeratedProfileElement</code>. - </li> - <li>The name of the artist redundantly as an - <code>EnumeratedProfileElement</code>. - </li> - </ul> - - <p>Now, let's create a utility method <code>describe</code> - which takes a <code>java.util.Set</code> of matching - <code>Track</code>s and yields a <code>java.util.List</code> - of corresponding <code>Profile</code>s: - </p> - - <source>private static List describe(Set tracks) { - List profiles = new ArrayList(); - for (Iterator i = tracks.iterator(); - i.hasNext();) { - Track t = (Track) i.next(); - String id = t.getID().toString(); - String trackName = t.getName(); - String albumName = t.getAlbum().getName(); - String artistName = t.getArtist().getName(); - Profile p = createProfile(id, trackName, - albumName, artistName); - profiles.add(p); - } - return profiles; -}</source> - - <p>We build a list of <code>Profile</code>s by calling another - method, <code>createProfile</code>. It takes the track's - URI, its name, the name of the album on which it appears, - and the name of the artist who created it, and yields a - <code>Profile</code>: - </p> - - <source>private static Profile createProfile(String id, - String trackName, String albumName, - String artistName) { - Profile p = new Profile(); - ProfileAttributes pa=new ProfileAttributes(id, - "1.0", "profile", "active", "unclassified", - /*parent*/null, /*children*/EL, - "1.3.6.1.4.1.7655", /*revNotes*/EL); - p.setProfileAttributes(pa); - ResourceAttributes ra=new ResourceAttributes(p, - id, trackName, - Collections.singletonList("audio/mpeg"), - /*desc*/null, - Collections.singletonList(artistName), - /*subjects*/EL, /*pubs*/EL, /*contrib*/EL, - /*dates*/EL, /*types*/EL, /*sources*/EL, - /*langs*/EL, /*relations*/EL, /*covs*/EL, - /*rights*/EL, - Collections.singletonList("SK.Music"), - "granule", "system.productServer", - Collections.singletonList("urn:eda:rmi:" - + "MyProductServer")); - p.setResourceAttributes(ra); - EnumeratedProfileElement artistElem = - new EnumeratedProfileElement(p, "artist", - "artist", "Name of the artist of a work", - "string", "name", /*syns*/EL, /*ob*/true, - /*maxOccur*/1, /*comment*/null, - Collections.singletonList(artistName)); - p.getProfileElements().put("artist", - artistElem); - EnumeratedProfileElement albumElem = - new EnumeratedProfileElement(p, "album", - "album", "Name of album where track occurs", - "string", "name", /*syns*/EL, /*ob*/true, - /*maxOccur*/1, /*comment*/null, - Collections.singletonList(albumName)); - p.getProfileElements().put("album", - albumElem); - return p; -}</source> - - <p>The profile attributes say that</p> - <ul> - <li>The ID of the profile itself is the same as the track's URI.</li> - <li>The version of the profile is 1.0.</li> - <li>The type is "profile".</li> - <li>It's currently active.</li> - <li>It's not top-secret, it's "unclassified".</li> - <li>It has no parent profile.</li> - <li>It has no child profiles.</li> - <li>The registration authority has OID 1.3.6.1.4.1.7655</li> - <li>There are no revision notes.</li> - </ul> - - <p>The resource attributes say that</p> - <ul> - <li>The Identifier is the track's URI.</li> <li>The Title is - the track's title.</li> <li>The sole Format in which the - track is available is <code>audio/mpeg</code>.</li> - <li>There's no description.</li> <li>The sole Creator is the - name of the artist.</li> <li>There are no subject keywords, - publishers, contributors, dates, types, sources, languages, - relations, coverages, nor rights.</li> - <li>The sole resource context is "Tutorial.Music".</li> - <li>The resource's aggregation is "granule", meaning this profile is describing a single, discrete resource.</li> - <li>The resource's class is "system.productServer", meaning you need to contact a product server at the resource location to retrieve the resource.</li> - <li>The resource location is <code>urn:eda:rmi:MyProductServer</code>.</li> - </ul> - - <p>Finally, the two profile elements tell (again) who the - artist was and also on what album the track appears. - </p> - - <p>What's with all the <code>EL</code>s? It's just to save on typing:</p> - - <source>private static final List EL - = Collections.EMPTY_LIST;</source> - </subsection> - - <subsection name="Implementng the Interface"> - <p>The <code>ProfileHandler</code> interface stipulates two - methods, one for finding profiles given an - <code>XMLQuery</code> and another for retrieving a single - profile given its ID. With all of these utility methods in - place, these are both easy to write. First, the - <code>findProfiles</code> method: - </p> - - <source>public List findProfiles(XMLQuery q) { - Expr expr = transform(q); - Set matches = expr.evaluate(); - List profiles = describe(matches); - return profiles; -}</source> - - <p>The algorithm should be painfully obvious by now: transform - the query to a tree, evaluate the tree into a set of - matching tracks, and describe the tracks. - </p> - - <p>The <code>get</code> method takes a profile's ID and - returns the matching profile, or <code>null</code> if it's - not found. Since we're using the track's ID as the - profile's ID as well, we can just iterate through our - tracks, find the one with the matching ID, and - <code>describe</code> it: - </p> - - <source>public Profile get(String id) { - URI uri = URI.create(id); - for (Iterator i = DB.TRACKS.iterator(); - i.hasNext();) { - Track t = (Track) i.next(); - if (t.getID().equals(uri)) - return createProfile(t.getID().toString(), - t.getName(), t.getAlbum().getName(), - t.getArtist().getName()); - } - return null; -}</source> - - </subsection> - - <subsection name="Complete Source Code"> - <p>Don't feel like cutting and pasting all of those code - fragments? No problem. All of the source files are - available <a href="../examples/src.jar">in a jar</a>. - </p> - </subsection> - </section> - - <section name="Compiling the Handler"> - <p>As with the <a - href="../handler/"><code>NullHandler</code></a>, we'll use the - J2SDK command-line tools. And if you've gone through the <a - href="../handler/"><code>NullHandler</code> tutorial</a>, you've - got all the dependent jars in place already. Just put the - <code>MusicHandler.java</code> and all the related source files - under <code>$PS_HOME/src</code>, compile, and build the jar: - </p> - - <source>% <b>ls src</b> -Album.java Expr.java -AlbumExpr.java MusicHandler.java -And.java Not.java -Artist.java NullHandler.java -ArtistExpr.java Or.java -Constant.java Track.java -DB.java TrackExpr.java -% <b>javac -extdirs lib -d classes src/*.java</b> -% <b>ls classes</b> -Album.class Expr.class -AlbumExpr.class MusicHandler.class -And.class Not.class -Artist.class NullHandler.class -ArtistExpr.class Or.class -Constant.class Track.class -DB.class TrackExpr.class -% <b>cd classes</b> -% <b>jar -uf ../lib/my-handler.jar *.class</b> -% <b>cd ..</b> -% <b>jar -tf lib/my-handler.jar</b> -META-INF/ -META-INF/MANIFEST.MF -NullHandler.class -Album.class -AlbumExpr.class -And.class -Artist.class -ArtistExpr.class -Constant.class -DB.class -Expr.class -MusicHandler.class -Not.class -Or.class -Track.class -TrackExpr.class</source> - - <p>We also need to update the <code>$PS_HOME/bin/ps</code> - script. Currently, it's instantiating just the - <code>NullHandler</code>; we need it to instantiate the - <code>MusicHandler</code> too. Stop any currently running - profile server by pressing CTRL+C (or whatever your interrupt - key is) in the window running the server. Then edit the - script so it reads as follows: - </p> - - <source>#!/bin/sh -exec java -Djava.ext.dirs=$PS_HOME/lib \ - -Dhandlers=NullHandler,MusicHandler \ - jpl.eda.ExecServer \ - jpl.eda.profile.rmi.ProfileServiceImpl \ - urn:eda:rmi:MyProfileService</source> - - <p>Now the profile server will delegate to <em>two</em> - handlers: the <code>NullHandler</code> and the - <code>MusicHandler</code>. With more than one handler, the - OODT framework calls each one in turn and collects all of the - matching profiles together to return to the - <code>ProfileClient</code>. (Of course, the - <code>NullHandler</code> never actually generates any matching - profiles.) - </p> - </section> - - <section name="Querying the Profile Server"> - <p>Start the profile server by running - <code>$PS_HOME/bin/ps</code> in one window (presumably the RMI - registry is still running in another window). In yet another - window, we'll run our <code>$PS_HOME/bin/pc</code> script to - query the profile server: - </p> - - <source>% <b>$PS_HOME/bin/pc 'artist = Delerium - AND album != Poem OR artist = Bach'</b><![CDATA[ -Object context ready; delegating to: -[jpl.eda.object.jndi.RMIContext@dec8b3] -[<?xml version="1.0" encoding="UTF-8"?> -<profile><profAttributes><profId>urn:sk:tr91BC.mp3</profId>...]]></source> - - <p>Whoa! There's a huge load of XML! In fact what the - <code>ProfileClient</code> is printing is a - <code>java.util.List</code> of profiles in XML format, each - separated by a comma, and the whole list in square brackets. - If you search this output carefully, though, you can pick out - the <code><Title></code> elements and see indeed that - we've got six matching tracks: - </p> - - <ul> - <li>Brandenburg Concerto #1</li> - <li>Brandenburg Concerto #2</li> - <li>Brandenburg Concerto #3</li> - <li>Brandenburg Concerto #4</li> - <li>Brandenburg Concerto #5</li> - <li>Brandenburg Concerto #6</li> - <li>Flowers Become Screens</li> - <li>Metaphor</li> - </ul> - - <p>Sure enough, this matches the XMLQuery query language - expression we passed in: There are tracks by Delerium but - <em>not</em> from the Poem album, and there are all the tracks - by Bach. - </p> - </section> - - <section name='Conclusion'> - <p>In this long tutorial we developed a real profile handler - that answered queries by transforming them from postfix stacks - into expression trees and using those trees to query an - in-memory database made of Java objects. We then described - matching data by creating <code>Profile</code>s. - </p> - - <p>You might be thinking that this seems like a lot of work, and - there might be some easier ways to go. You could use the - <code>LightweightProfileHandler</code> for resources that - never change, but only if you don't have too many of them and - don't mind managing potentially large XML documents. You - could choose to use unparsed XMLQuery expressions and instead - make the user query in the same language as your data system, - obviating the need for complex expression trees. - </p> - - <p>However, with the tools presented in this tutorial, you could - adapt the expression tree code to generating system-specific - queries, and describe those results with as much or as little - detail as necessary. - </p> - - <p>Happy profiling!</p> - </section> - </body> -</document> http://git-wip-us.apache.org/repos/asf/oodt/blob/098cc4fa/profile/src/site/xdoc/handler/index.xml ---------------------------------------------------------------------- diff --git a/profile/src/site/xdoc/handler/index.xml b/profile/src/site/xdoc/handler/index.xml deleted file mode 100755 index ba02b14..0000000 --- a/profile/src/site/xdoc/handler/index.xml +++ /dev/null @@ -1,595 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -Licensed to the Apache Software Foundation (ASF) under one or more contributor -license agreements. See the NOTICE.txt 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. ---> -<document> - <properties> - <title>Developing a Profile Handler</title> - <author email="[email protected]">Sean Kelly</author> - </properties> - <!-- Shift - Nexus Overload --> - <body> - <section name="Developing a Profile Handler"> - <p>Profiles describe resources. But where do profiles come from? - From profile handlers. Profile handlers are interchangeable - components of a profile server that accept queries for - profiles and return matching profiles. Profile handlers take a - static or a dynamic collection of information and serve an - equivalent set of profiles. Developing a new profile handler - is as simple as writing a Java class that implements a - specific interface or two. - </p> - - <p>If you're not already familiar with the <code>XMLQuery</code> - class, go ahead and <a href="/edm-query/tutorial/">take its - tutorial now</a>. We'll wait! - </p> - </section> - - <section name="Introduction"> - <p>Profile handlers handle queries for profiles. They're the - interchangeable part of a profile server that you can develop - for special needs. Profile servers delegate all incoming - requests to zero or more profile handlers, as shown in the - following class diagram: - </p> - - <img src="../images/delegation.png" alt="Delegation model" /> - - <p>Developing, testing, and deploying a new profile handler - involves: - </p> - <ol> - <li>Creating a class that implements (however indirectly) - the <code>jpl.eda.profile.handlers.ProfileHandler</code> interface. - </li> - <li>Creating a new process that runs either the RMI or - CORBA <code>ProfileServiceImpl</code> class, - specifying the name of your handler class. - </li> - <li>Starting the server and sending in queries. - </li> - </ol> - <p>This document describes each of these steps in detail.</p> - </section> - - <section name="Writing the Handler Class"> - <p>Writing the class that handles profile queries and delivers - profile results is easily the hardest part in developing a new - kind of profile server. Profile servers' handlers can serve - profiles describing static resources, can synthesize profile - on the fly to describe resources, and can create profile - metadata for resources that change all the time. - </p> - - <p>Understanding the resources you're trying to describe with - profiles is the most important thing you can do before - beginning to write your profile handler: - </p> - - <ul> - <li>Do you always have the same set of static resources? - If so, you can write a static profile document to describe - them and use the - <code>LightweightProfileHandler</code>, thus - avoiding having to write a new handler at all. - </li> - <li>Do you have resources that never change, but may add to or - remove from that set? If so, you can use the - <code>OracleProfileImpl</code> handler which uses an Oracle - database to store a set of profiles that you can update. - </li> - <li>Do you have resources that do change, or that come - from a dynamic set of data? If so, you'll have to write a - handler. - </li> - </ul> - - <subsection name="Choosing the Handler Interface to Implement"> - <p>The OODT Framework provides two handler interfaces (one is - an extension of the other): - </p> - <ul> - <li><code>jpl.eda.profile.handlers.ProfileHandler</code> - is the basic profile handler. It defines methods for - <em>handling</em> searches for profiles. - </li> - - <li><code>jpl.eda.profile.handlers.ProfileManager</code> is an - extension that not just <em>handles</em> profile queries - but also <em>manages</em> the set of profiles maintained - by the server, by providing methods for adding to, - removing from, and updating the set of managed profiles. - </li> - </ul> - - <p>For nearly all applications, the <code>ProfileHandler</code> - interface is sufficient. If you need to provide profile - management capabilities, it still may be handy to start with - the <code>ProfileHandler</code> interface, implement and test - its methods, and <em>then</em> change to the - <code>ProfileManager</code>. - </p> - </subsection> - - <subsection name="The ProfileHandler Interface"> - <p>The <code>ProfileHandler</code> interface is - as follows: - </p> - - <source>package jpl.eda.profile.handlers; - -import java.util.List; -import jpl.eda.profile.Profile; -import jpl.eda.profile.ProfileException; -import jpl.eda.xmlquery.XMLQuery; - -public interface ProfileHandler { - List findProfiles(XMLQuery query) throws ProfileException; - Profile get(String profID) throws ProfileException; -}</source> - - <p>The two methods are described in detail below.</p> - - <dl> - <dt><code>findProfiles</code></dt> - <dd>This method accepts a query in the form of an - <code>XMLQuery</code> object and returns a - Java <code>List</code> of - <code>Profile</code> objects that match. If - there are no matches, this method must return an empty - list. If an error occurs, it should throw the - <code>ProfileException</code>. - - <p>This is by far the most used and most important - method, and really is the <i>raison - d'etre</i> for profile servers. It's - what the OODT Framework uses to allow clients to ask - your server for resources based on metadata . - </p> - </dd> - <dt><code>get</code></dt> - <dd>This method accepts the ID of a profile in the form - of a Java <code>String</code> and returns - either a <code>Profile</code> object with that - ID or null if the ID is unknown. This method enables a - client to retrieve a profile using a priori knowledge of - the profile's ID (perhaps from a previous search). - </dd> - </dl> - </subsection> - - <subsection name="The ProfileManager Interface"> - <p>The <code>ProfileManager</code> interface - builds on the <code>ProfileHandler</code>, and is - listed below: - </p> - <source>package jpl.eda.profile.handlers; - -import java.util.Collection; -import java.util.Iterator; -import jpl.eda.profile.Profile; -import jpl.eda.profile.ProfileException; -import jpl.eda.xmlquery.XMLQuery; - -public interface ProfileManager extends ProfileHandler { - void add(Profile profile) throws ProfileException; - void addAll(Collection collection) throws ProfileException; - void clear() throws ProfileException; - boolean contains(Profile profile) throws ProfileException; - boolean containsAll(Collection collection) throws ProfileException; - Collection getAll() throws ProfileException; - boolean isEmpty() throws ProfileException; - Iterator iterator() throws ProfileException; - boolean remove(String profID, String version) throws ProfileException; - boolean remove(String profID) throws ProfileException; - int size() throws ProfileException; - void replace(Profile profile) throws ProfileException; -}</source> - - <p>If you choose to implement a profile manager, please see - the API documentation for the - <code>ProfileManager</code> class for the - expectations of each method. - </p> - </subsection> - </section> - - <section name="Our First Profile Handler"> - <p>Although not terribly useful, a “null” profile - handler is a good example to start with because it is small and - will make sure your environment is in good working order before - proceeding to a real profile handler. - </p> - - <p>What's a “null” profile handler? It's one that - serves no profiles. That is, for any query with - <code>findProfiles</code> and any retrieval with - <code>get</code> it never returns any profiles. - </p> - - <subsection name="Directory Layout"> - <p>For these examples, we'll work on a kind of Unix system - with a <code>csh</code> shell. Other shell users or Windows - users will need to adjust. We'll also use the J2SDK - command-line tools. If you're using an Integrated - Development Environment of some sort, please adjust - accordingly. - </p> - - <p>We'll create a "home" directory for our profile servers - with subdirectories to hold specific components like source - code, jar files, and scripts. We'll call this home - directory by an environment variable, <code>PS_HOME</code> - (PS for Profile Server), so that scripts won't have to refer - to things by relative paths: - </p> - - <source>% <b>mkdir ps</b> -% <b>cd ps</b> -% <b>setenv PS_HOME `pwd`</b> -% <b>mkdir bin classes lib src</b></source> - </subsection> - - <subsection name="Source File"> - <p>One of the easier parts is the source itself for the - ‘null’ profile handler. Here it is: - </p> - <source>import java.util.Collections; -import java.util.List; -import jpl.eda.profile.Profile; -import jpl.eda.profile.handlers.ProfileHandler; -import jpl.eda.xmlquery.XMLQuery; - -public class NullHandler implements ProfileHandler { - public List findProfiles(XMLQuery query) { - return Collections.EMPTY_LIST; - } - public Profile get(String id) { - return null; - } -}</source> - - <p>Note that for every query, the - <code>findProfiles</code> method returns an empty list - (meaning that no profiles matched), and that for any retrieval - the <code>get</code> method returns null, meaning that - the handler believes there's no such profile. - </p> - <p>This class should be compiled into a file named - <code>$PS_HOME/src/NullHandler.java</code> - since it is a public class. - </p> - - <p><em>Note:</em> Profile handler classes <em>must</em> be - public <em>and</em> provide a no-arguments - constructor. You should retrieve any initialization - settings through the System Properties or by other means - specific to your profile handler. - </p> - </subsection> - - <subsection name="Compiling the Handler"> - <p>Compiling this profile handler requires the following - dependent components: - </p> - - <ul> - <li><a href="/grid-profile/">Profile Service</a>. This - defines the entire profile model, handler interfaces, - servers, clients, and so forth. - </li> - <li><a href="/edm-query/">Query Expression</a>. This - defines the <code>XMLQuery</code> and related classes. - </li> - </ul> - - <p>Download the binary distributions of the above two packages - and copy the jar file from each into the - <code>$PS_HOME/lib</code> directory. Then you can compile - the <code>NullHandler.java</code> file. - </p> - - <source>% <b>ls</b> -bin classes lib src -% <b>ls -l lib</b> -total 244 --rw-r--r-- 1 kelly kelly 43879 28 Feb 07:05 edm-query-2.0.2.jar --rw-r--r-- 1 kelly kelly 201453 28 Feb 07:01 grid-profile-3.0.2.jar -% <b>javac -extdirs lib -d classes src/NullHandler.java</b> -% <b>ls -l classes</b> -total 4 --rw-r--r-- 1 kelly kelly 511 28 Feb 07:07 NullHandler.class -% <b>jar -cf lib/my-handler.jar -C classes NullHandler.class</b> -% <b>jar -tf lib/my-handler.jar</b> -META-INF/ -META-INF/MANIFEST.MF -NullHandler.class</source> - - <p>We now have a new jar file, <code>my-handler.jar</code> - which contains our ‘null’ profile handler, - compiled and ready to go. - </p> - </subsection> - - <subsection name="Starting an RMI Registry"> - <p>Clients access profile servers with an open-ended set of - network protocols. We currently have implementations for - RMI and CORBA. For this tutorial, we'll use RMI, since it's - enormously less complex. Clients of RMI systems first - contact an RMI registry and look up a server object's - network address. The registry maintains mappings from a - server object's name to its network address. When servers - start up, they register with the RMI registry so clients can - later find them. - </p> - - <p>To start an RMI Registry, you'll need the following components:</p> - - <ul> - <li><a href="/edm-commons/">EDM Common Components</a>. - These are common utilities used by every OODT - service. - </li> - <li><a href="/rmi-registry/">OODT RMI Registry</a>. This is the - actual RMI registry.</li> - </ul> - - <p>Download each component's binary distribution, unpack each - one, and take collect the jar files into the - <code>lib</code> directory. The RMI Registry will also need - the <code>grid-profile</code> jar file, which we've already - got. - </p> - - <source>% <b>ls -l $PS_HOME/lib</b> -total 404 --rw-r--r-- 1 kelly kelly 149503 28 Feb 07:28 edm-commons-2.2.5.jar --rw-r--r-- 1 kelly kelly 43879 28 Feb 07:05 edm-query-2.0.2.jar --rw-r--r-- 1 kelly kelly 201453 28 Feb 07:01 grid-profile-3.0.2.jar --rw-r--r-- 1 kelly kelly 796 28 Feb 07:07 my-handler.jar --rw-r--r-- 1 kelly kelly 8055 28 Feb 07:28 rmi-registry-1.0.0.jar</source> - - <p>Now all we need is a convenient script to start the RMI - registry. We'll call it <code>rmi-reg</code> and stick it - in the <code>bin</code> directory. Here's the - <code>rmi-reg</code> script:</p> - - <source>#!/bin/sh -exec java -Djava.ext.dirs=$PS_HOME/lib \ - gov.nasa.jpl.oodt.rmi.RMIRegistry</source> - - <p>This script tells the Java virtual machine to find - extension jars in the directory <code>$PS_HOME/lib</code>. It - then says that the main class to execute is - <code>gov.nasa.jpl.oodt.rmi.RMIRegistry</code>. - </p> - - <p>Go ahead and make this script executable and start the RMI - Registry. In another window (with the appropriate setting of - <code>PS_HOME</code>), run - <code>$PS_HOME/bin/rmi-reg</code>. You should see output - similar to the following: - </p> - - <source>% <b>chmod 755 $PS_HOME/bin/rmi-reg</b> -% <b>$PS_HOME/bin/rmi-reg</b> -Mon Feb 28 07:30:13 CST 2005: no objects registered</source> - - <p>The RMI Registry is now running. Every two minutes it will - display an update of all registered objects. Naturally, we - don't have any profile service running right now, so it will - say <code>no objects registered</code>. Go ahead and ignore - this window for now. It's time to start our profile server. - </p> - </subsection> - - <subsection name="Starting the Profile Server"> - <p>With our handler compiled and our RMI registry running, - we're ready to start our profile server. As said before, - profile servers delegate to zero or more profile handlers to - actually handle all incoming requests. You tell the profile - server what handlers to instantiate by naming their classes - in a system property. That property is called - <code>handlers</code>, and its value is a comma-separated - list of fully qualified class names, including the package - name prefixes. Since our <code>NullHandler</code> is just - in the default package, <code>NullHandler</code> <em>is</em> - its fully-qualified class name. - </p> - - <p>Profile server processes require the following components - in addition to the ones we've downloaded so far:</p> - - <ul> - <li><a href="http://ws.apache.org/xmlrpc">Apache - XML-RPC</a>. This is used internally by OODT services. - Download version 1.1, not a later version! If you prefer, - you can <a - href="http://ibiblio.org/maven/xmlrpc/jars/xmlrpc-1.1.jar">fetch - the jar file directly</a>. - </li> - - <li><a href="http://jena.sourceforge.net/">Jena Semantic Web - Framework for Java</a>. This is used by the classes - that represent profiles. You'll need version 1.6.1 You - can also <a - href="http://oodt.jpl.nasa.gov/download/public/Jena/jars/jena-1.6.1.jar">fetch - the jar file directly</a>. - </li> - </ul> - - <p>Copy these two other jars to the <code>$PS_HOME/lib</code> - directory. You should now have seven jars there: - </p> - - <source>% <b>ls -l $PS_HOME/lib</b> -total 1580 --rw-r--r-- 1 kelly kelly 149503 28 Feb 07:28 edm-commons-2.2.5.jar --rw-r--r-- 1 kelly kelly 43879 28 Feb 07:05 edm-query-2.0.2.jar --rw-r--r-- 1 kelly kelly 201453 28 Feb 07:01 grid-profile-3.0.2.jar --rw-r--r-- 1 kelly kelly 1144107 28 Feb 09:23 jena-1.6.1.jar --rw-r--r-- 1 kelly kelly 796 28 Feb 07:07 my-handler.jar --rw-r--r-- 1 kelly kelly 8055 28 Feb 07:28 rmi-registry-1.0.0.jar --rw-r--r-- 1 kelly kelly 53978 28 Feb 09:20 xmlrpc-1.1.jar</source> - - <p>Now, create a second shell script to make starting the - profile server convenient and call it - <code>$PS_HOME/bin/ps</code>. It should look like this: - </p> - - <source>#!/bin/sh -exec java -Djava.ext.dirs=$PS_HOME/lib \ - -Dhandlers=NullHandler \ - jpl.eda.ExecServer \ - jpl.eda.profile.rmi.ProfileServiceImpl \ - urn:eda:rmi:MyProfileService</source> - - <p>Make the script executable and start the profile server:</p> - - <source>% <b>chmod 755 $PS_HOME/bin/ps</b> -% <b>$PS_HOME/bin/ps</b> -Object context ready; delegating to: [jpl.eda.object.jndi.RMIContext@dec8b3]</source> - - <p>The profile server will start, check its - <code>handlers</code> property, and create an object of each - class named by it. Then it'll register itself with the RMI - registry and wait for requests to come in from profile clients. - </p> - - </subsection> - - <subsection name="What's in a Name?"> - <p>The profile server registers itself using a name provided - on the command-line, in this case, - <code>urn:eda:rmi:MyProfileService</code>. Let's take apart - the name and see how it works. - </p> - - <p>If you're familiar with web standards, you can see that the - name is a Uniform Resource Name (URN), since it starts with - <code>urn:</code>. The OODT Framework uses URNs to identify - services and other objects. The <code>eda:</code> tells - that the name is part of the Enterprise Data Architecture - (EDA) namespace. (EDA was the name of a project related to - OODT that was merged with OODT. For now, just always use - <code>eda:</code> in your URNs.) - </p> - - <p>Next comes <code>rmi:</code>. This is a special flag for - the OODT services that tells that we're using a name of an - RMI-accessible object. The OODT framework will know to use - an RMI registry to register the server. - </p> - - <p>Finally is <code>MyProfileService</code>. This is the - actual name used in the RMI registry. You can call your - profile server anything you want. For example, suppose you - have three profile servers; one in the US, one in Canada, - and one in Australia. You might name them: - </p> - - <ul> - <li><code>urn:eda:rmi:US</code></li> - <li><code>urn:eda:rmi:Canada</code></li> - <li><code>urn:eda:rmi:Australia</code></li> - </ul> - - <p>Or you might prefer to use ISO country codes. Or you might - name them according to the kinds of profiles they serve, - such as <code>urn:eda:rmi:BiomarkerMetadata</code> or - <code>urn:eda:rmi:BusniessForecastMetadata</code>. - </p> - - <p>The RMI registry will happily re-assign a name if one's - already in use, so when deploying your own profile (and - other) servers, be sure to give each one a unique name. - </p> - </subsection> - - <subsection name="Querying the Profile Server"> - <p>To query a profile server, you use the - <code>ProfileClient</code> class. It provides methods to - contact a named profile server, performing the lookup in the - RMI registry, contacting the profile server, and passing in - queries and profile retrievals. The - <code>ProfileClient</code> class is also an - <em>executable</em> class, making it perfect for testing a - new profile server from the command-line. - </p> - - <p>Still, we'll make a script, called - <code>$PS_HOME/bin/pc</code> (for "profile client") to - execute it, though, to save from having to type hugely long - Java command-lines: - </p> - - <source>#!/bin/sh -if [ $# -ne 1 ]; then - echo "Usage: `basename $0` <query-expression>" 1>&2 - exit 1 -fi -exec java -Djava.ext.dirs=$PS_HOME/lib \ - jpl.eda.profile.ProfileClient \ - urn:eda:rmi:MyProfileService \ - "$1"</source> - - <p>Make this script executable and then run it:</p> - - <source>% <b>chmod 755 $PS_HOME/bin/pc</b> -% <b>$PS_HOME/bin/pc "temperature = 37"</b> -Object context ready; delegating to: [jpl.eda.object.jndi.RMIContext@dec8b3] -[]</source> - - <p>Although it may not look spetacular, this is a success! - The two square brackets, <code>[]</code>, indicates the list - of matching profiles to our query expression, - <code>temperature = 37</code>. In this case, there were no - matches, which is exactly what we wanted. - </p> - </subsection> - </section> - - <section name="Conclusion"> - <p>The Null Profile Server made sure our development - environment worked from end to end for creating, deploying, and - testing a profile handler. Now you're ready to implement a real - profile handler: - </p> - - <ul> - <li>Instead of returning an empty list, create a - <code>Profile</code> object and return it as a - singleton list. See the API documentation for - <code>Profile</code> as well as other articles - for manipulating this class. - </li> - <li>Analyze the methods of the - <code>XMLQuery</code> class to determine the - query passed in from the user. Use that information to - synthesize the correct <code>Profile</code> - object. See the API documentation for class - <code>XMLQuery</code> for more information. - </li> - <li>Connecting to an external data source (such as the - local filesystem or a database), synthesize appropriate - profiles in response to queries and profile retrieval with - the <code>get</code> method. - </li> - </ul> - </section> - </body> -</document>
