portals-jetspeed-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rwat...@apache.org
Subject svn commit: r929218 [1/3] - in /portals/jetspeed-2/portal/trunk/openid-step2: ./ common/ common/src/ common/src/main/ common/src/main/java/ common/src/main/java/com/ common/src/main/java/com/google/ common/src/main/java/com/google/step2/ common/src/mai...
Date Tue, 30 Mar 2010 18:41:49 GMT
Author: rwatler
Date: Tue Mar 30 18:41:47 2010
New Revision: 929218

URL: http://svn.apache.org/viewvc?rev=929218&view=rev
Log:
JS2-1157: remove LGPL Step2/OpenXRI/JUG dependency and commit Step2 sources to Jetspeed SCM

Added:
    portals/jetspeed-2/portal/trunk/openid-step2/
    portals/jetspeed-2/portal/trunk/openid-step2/README.txt
    portals/jetspeed-2/portal/trunk/openid-step2/common/
    portals/jetspeed-2/portal/trunk/openid-step2/common/pom.xml
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/DefaultHostMetaFetcher.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Discovery2.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMeta.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaException.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaFetcher.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/IdpIdentifier.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LegacyXrdsResolver.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Link.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkBase.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkPattern.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkSyntaxException.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkValue.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/ParallelHostMetaFetcher.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/RelType.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/RelTypes.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/SecureDiscoveryInformation.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/SecureUrlIdentifier.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/UriTemplate.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/UrlHostMetaFetcher.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/XrdDiscoveryResolver.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/XrdLocationSelector.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/http/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/http/DefaultHttpFetcher.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/http/FetchException.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/http/FetchRequest.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/http/FetchResponse.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/http/HttpFetcher.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/util/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/util/EncodingUtil.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/util/ExpiringLruCache.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/util/TimeSource.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/util/XmlUtil.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/CachedCertPathValidator.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/CertUtil.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/CertValidator.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/CertValidatorException.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/CnConstraintCertValidator.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/Constants.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/DefaultCertValidator.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/DefaultTrustRootsProvider.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/TrustRootsProvider.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/VerificationResult.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/Verifier.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/xmlsimplesign/XmlSimpleSignException.java
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/org/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/org/doomdark/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/org/doomdark/uuid/
    portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/org/doomdark/uuid/UUIDGenerator.java
    portals/jetspeed-2/portal/trunk/openid-step2/pom.xml

Added: portals/jetspeed-2/portal/trunk/openid-step2/README.txt
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/README.txt?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/README.txt (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/README.txt Tue Mar 30 18:41:47 2010
@@ -0,0 +1,39 @@
+Copyright:
+-----------------------------------------------------------------------
+Copyright 2009 Google Inc.
+
+Licensed 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.
+
+Step2 Implementation adapted from release 1-SNAPSHOT here:
+-----------------------------------------------------------------------
+URL: http://step2.googlecode.com/svn/code/java/trunk
+Repository Root: http://step2.googlecode.com/svn
+Repository UUID: c427a029-b451-0410-ae65-13123b152969
+Revision: 478
+Node Kind: directory
+Schedule: normal
+Last Changed Author: dirk.balfanz
+Last Changed Rev: 478
+Last Changed Date: 2010-01-26 14:29:33 -0700 (Tue, 26 Jan 2010)
+
+Minor Source Adaptations:
+-----------------------------------------------------------------------
+1. Strip unused classes and deps from common module project.
+2. Remove Guice assembly annotations.
+3. Utilize standard Java collections API in place of Google collections.
+4. Make compatible with released openid4java version 0.9.5.
+5. Set release version to '0'.
+6. Simplify Maven2 POMs.
+7. Compile with Java 1.5 compatibility.
+8. Strip LGPL JUG 1.1.2 library from dependencies, (apparently used
+   only for XRI, SAML, and test cases), stub JUG UUIDGenerator.

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/pom.xml
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/pom.xml?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/pom.xml (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/pom.xml Tue Mar 30 18:41:47 2010
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <name>STEP2 Common</name>
+
+  <parent>
+    <artifactId>step2-parent</artifactId>
+    <groupId>com.google.step2</groupId>
+    <version>0</version>
+  </parent>
+
+  <groupId>com.google.step2</groupId>
+  <artifactId>step2-common</artifactId>
+  <version>0</version>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.openid4java</groupId>
+      <artifactId>openid4java-nodeps</artifactId>
+      <version>0.9.5</version>
+    </dependency>
+    <dependency>
+      <groupId>org.openxri</groupId>
+      <artifactId>openxri-syntax</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.openxri</groupId>
+      <artifactId>openxri-client</artifactId>
+      <version>1.2.0</version>
+      <exclusions>
+        <exclusion>
+          <groupId>jug</groupId>
+          <artifactId>jug</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>4.0.1</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>1.4</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+      <version>2.4</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jdom</groupId>
+      <artifactId>jdom</artifactId>
+      <version>1.1</version>
+    </dependency>
+  </dependencies>
+
+</project>

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/DefaultHostMetaFetcher.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/DefaultHostMetaFetcher.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/DefaultHostMetaFetcher.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/DefaultHostMetaFetcher.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+import com.google.step2.http.HttpFetcher;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Implements the default strategy to fetch host-meta files: from the
+ * /host-meta path of the site in question.
+ */
+public class DefaultHostMetaFetcher extends UrlHostMetaFetcher {
+
+  private final static String HOST_META_PATH = "/host-meta";
+
+  public DefaultHostMetaFetcher(HttpFetcher fetcher) {
+    super(fetcher);
+  }
+
+  @Override
+  protected URI getHostMetaUriForHost(String host) throws URISyntaxException {
+    return new URI("http", host, HOST_META_PATH, null);
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Discovery2.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Discovery2.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Discovery2.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Discovery2.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,438 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+import org.openid4java.discovery.Discovery;
+import org.openid4java.discovery.DiscoveryException;
+import org.openid4java.discovery.DiscoveryInformation;
+import org.openid4java.discovery.Identifier;
+import org.openid4java.discovery.UrlIdentifier;
+import org.openid4java.discovery.html.HtmlResolver;
+import org.openid4java.discovery.xri.XriResolver;
+import org.openid4java.discovery.yadis.YadisResolver;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implements Next-Generation OpenID discovery, based on Link-headers,
+ * link-elements, and host-meta. There are two different discovery
+ * operations available:
+ *
+ * (1) discover the OP endpoint(s) for a "site", and
+ * (2) discover the OP endpoint(s) for an OpenID (aka claimed id, which is the
+ *     id of a user)
+ *
+ * Case (1) is for when users merely indicate the IdP to the RP, case (2) is
+ * for when users actually submit their OpenID (claimed id) to the RP, and also
+ * used during the validation of an authentication response from an IdP.
+ *
+ * The strategy for case (1) is as follows:
+ *
+ * - find the host-meta file for the site.
+ * - find a link in the host-meta that points to an XRD(S) metadata
+ *   document for the site
+ * - follow the links in the XRD(S) to find the OP endpoint.
+ *
+ * The strategy for case (2) is as follows:
+ *
+ * - Try host-meta strategy (2a)
+ * - If that fails, link-header strategy (2b)
+ * - If that fails, try link-element strategy (2c)
+ *
+ * Strategy (2a) works as follows:
+ *
+ * - find the host-meta file for the host identified in the claimed id.
+ * - in the host-meta file, check whether a Link-Pattern in the host-meta
+ *   points to an XRD(S) for the claimed id, and if so, skip the next two steps.
+ * - if not, find a Link in the host-meta that points to an XRD(S) metadata
+ *   document for the site.
+ * - follow the URITemplate link in the site XRD(S) to find the XRD(S) for the
+ *   the claimed id.
+ * - follow the links in the XRD(S) to find the OP endpoint.
+ *
+ * Strategy (2b) works as follow:
+ *
+ * - Find a link in the HTTP headers in the HTTP response that points
+ *   to an XRD(S) metadata document for the claimed id.
+ * - follow the links in the XRD(S) to find the OP endpoint.
+ *
+ * Strategy (2c) works as follow:
+ *
+ * - Find an HTML link element in the document returned from
+ *   the claimed id that points to an XRD(S) metadata document for the
+ *   claimed id.
+ * - follow the links in the XRD(S) to find the OP endpoint.
+ *
+ *
+ * For backwards compatibility, we also provide a generic discover() method,
+ * which decides whether to use site-discovery or user-id-discovery based on the
+ * type of the provided identifier. This allows us to use this class inside the
+ * openid4java library.
+ *
+ * When calling this class through the legacy generic discover() method, we also
+ * employ a "fallback" strategy, i.e. we first try the strategy described above,
+ * and then fall back to OpenID 2.0-style discovery, if the strategy above
+ * doesn't yield any results.
+ */
+public class Discovery2 extends Discovery {
+
+  private static final Logger logger =
+      Logger.getLogger(Discovery2.class.getName());
+
+  private final HostMetaFetcher hostMetaFetcher;
+  private final XrdDiscoveryResolver xrdResolver;
+  private final XrdLocationSelector xrdLocationSelector;
+
+  // Strategy for site discovery: First, we try discoverOpEndpointsForSite,
+  // and as a fallback try legacy discovery for an identifier derived from
+  // the site identifier
+  private final FallbackDiscovery<IdpIdentifier> siteFallbackDiscoverer =
+    new FallbackDiscovery<IdpIdentifier>() {
+      @Override
+      public List<SecureDiscoveryInformation> newStyleDiscovery(
+          IdpIdentifier idp) throws DiscoveryException {
+        // how new-style discovery is performed
+        return discoverOpEndpointsForSite(idp);
+      }
+
+      @Override
+      public Identifier getLegacyIdentifier(IdpIdentifier idp)
+          throws DiscoveryException {
+        // which identifier is to be used for the fallback old-style discovery
+        try {
+          String openIdAsString = idp.getIdentifier().trim();
+
+          // since we're going to convert this to a URL anyway, we might as
+          // well make sure that the identifier starts with http[s]://
+          // Otherwise, the URL classes used inside UrlIdentifier tend to
+          // get huffy.
+          URI openIdAsUri = URI.create(openIdAsString);
+          if (openIdAsUri.getScheme() == null) {
+            openIdAsString = "http://" + openIdAsString;
+          }
+
+          return new UrlIdentifier(openIdAsString);
+        } catch (IllegalArgumentException e) {
+          // thrown if the identifier doesn't look like a host name
+          throw new DiscoveryException(e);
+        }
+      }
+    };
+
+  // Strategy for claimed_id discovery: first, we'll try
+  // discoverOpEndpointsForUser, and if that fails simply perform legacy
+  // discovery on the same UrlIdentifier
+  private final FallbackDiscovery<UrlIdentifier> userFallbackDiscoverer =
+    new FallbackDiscovery<UrlIdentifier>() {
+      @Override
+      public List<SecureDiscoveryInformation> newStyleDiscovery(
+          UrlIdentifier url) throws DiscoveryException {
+        // how new-style discovery is performed
+        return discoverOpEndpointsForUser(url);
+      }
+
+      @Override
+      public Identifier getLegacyIdentifier(UrlIdentifier url) {
+        // which identifier is to be used for the fallback old-style discovery
+        return url;
+      }
+    };
+
+  public Discovery2(HostMetaFetcher hostMetaFetcher,
+      XrdDiscoveryResolver xrdResolver,
+      HtmlResolver htmlResolver, YadisResolver yadisResolver,
+      XriResolver xriResolver) {
+    super();
+    setHtmlResolver(htmlResolver);
+    setYadisResolver(yadisResolver);
+    setXriResolver(xriResolver);
+    this.hostMetaFetcher = hostMetaFetcher;
+    this.xrdResolver = xrdResolver;
+    this.xrdLocationSelector = new XrdLocationSelector();
+  }
+
+  /**
+   * Returns list of likely OpenID endpoints for a site, ordered by
+   * preference as listed by the site. The host-meta points to an XRD(S)
+   * document, which contains a (prioritized) list of endpoints, and which
+   * will be returned.
+   *
+   * @param site the {@link IdpIdentifier} identifying the site for which
+   *   OpenID endpoints are being sought.
+   */
+  public List<SecureDiscoveryInformation> discoverOpEndpointsForSite(
+      IdpIdentifier site) throws DiscoveryException {
+
+    String host = site.getIdentifier();
+
+    // get host-meta for that host
+    HostMeta hostMeta;
+    try {
+      hostMeta = hostMetaFetcher.getHostMeta(host);
+    } catch (HostMetaException e) {
+      throw new DiscoveryException("could not get host-meta for " + host, e);
+    }
+
+    // Find XRD that host-meta is pointing to. In the case of site-discovery,
+    // this will point to the site's XRD.
+    URI xrdUri = xrdLocationSelector.findSiteXrdUriForOp(hostMeta,
+        xrdResolver.getDiscoveryDocumentType());
+
+    if (xrdUri == null) {
+      return Collections.emptyList();
+    }
+
+    // now that we have the location of the XRD, perform the actual
+    // discovery based on the XRD.
+    return xrdResolver.findOpEndpointsForSite(site, xrdUri);
+  }
+
+  /**
+   * Returns list of OpenID endpoints declared by a claimed id (aka user id).
+   * There are a variety of ways to discover OP endpoints from a claimed id.
+   * We're doing it in the following order: first, try host-meta-based
+   * discovery. Next, try link-header-based discovery. Last, try
+   * link-element (in HTML)-based discovery.
+   *
+   * In the latter two cases, the link will point directly to the XRD(S) of the
+   * user (claimed id). In the first case, there will either be a Link-Pattern:
+   * pointing to the XRD(S) of the user, or a Link: pointing to a site-wide
+   * XRD(S) (which in turn will have URITemplates that point to the XRD(S) of
+   * the user).
+   *
+   * @param claimedId the {@link UrlIdentifier} identifying the user's claimed
+   *   id
+   */
+  public List<SecureDiscoveryInformation> discoverOpEndpointsForUser(
+      UrlIdentifier claimedId) throws DiscoveryException {
+
+    List<SecureDiscoveryInformation> result;
+
+    try {
+      result = tryHostMetaBasedDiscoveryForUser(claimedId);
+    } catch (DiscoveryException e) {
+      result = null;
+    }
+
+    if (result != null) {
+      return result;
+    }
+
+    try {
+      result = tryLinkHeaderBasedDiscoveryForUser(claimedId);
+    } catch (DiscoveryException e) {
+      result = null;
+    }
+
+    if (result != null) {
+      return result;
+    }
+
+    return tryLinkElementBasedDiscoveryForUser(claimedId);
+  }
+
+  /**
+   * Returns list of likely OpenID endpoints for a user, ordered by
+   * preference. If there is a link-pattern in the host-meta that points to the
+   * user's XRD(S), we base XRD(S) discovery on that document.
+   * Otherwise, the host-meta can point to a site's XRD(S) document,
+   * which may contain URITemplates, which in turn point to the user's XRD(S).
+   * The latter should include a list of OpenID endpoints.
+   *
+   * @param claimedId the {@link IdpIdentifier} identifying the site for which
+   *   OpenID endpoints are being sought.
+   */
+  /* visible for testing */
+  List<SecureDiscoveryInformation> tryHostMetaBasedDiscoveryForUser(
+      UrlIdentifier claimedId) throws DiscoveryException {
+
+    // extract the host from the claimed id
+    String host = claimedId.getUrl().getHost();
+
+    // get host-meta for that host
+    HostMeta hostMeta;
+    try {
+      hostMeta = hostMetaFetcher.getHostMeta(host);
+    } catch (HostMetaException e) {
+      throw new DiscoveryException("could not get host-meta for " + host, e);
+    }
+
+    // First, let's check whether there are link-patterns in the host-meta that
+    // point directly to the user's XRD(S).
+    URI xrdUri = xrdLocationSelector.findUserXrdUriForOp(hostMeta,
+        xrdResolver.getDiscoveryDocumentType(), claimedId);
+
+    if (xrdUri != null) {
+
+      // xrdUri points to user's XRD
+      return xrdResolver.findOpEndpointsForUser(claimedId, xrdUri);
+    }
+
+    // There were no link-patterns, i.e.,  we'll have to go with the
+    // site-wide XRD(S)
+    xrdUri = xrdLocationSelector.findSiteXrdUriForOp(hostMeta,
+        xrdResolver.getDiscoveryDocumentType());
+
+    if (xrdUri != null) {
+
+      // xrdUri points to site-wide XRD
+      return xrdResolver.findOpEndpointsForUserThroughSiteXrd(claimedId,
+          xrdUri);
+    }
+
+    // xrdUri == null
+    return Collections.emptyList();
+  }
+
+  /**
+   * Link-element based discovery.
+   */
+  private List<SecureDiscoveryInformation> tryLinkElementBasedDiscoveryForUser(
+      UrlIdentifier claimedId) throws DiscoveryException {
+    // TODO: implement this
+    throw new DiscoveryException("link-element-based discovery is not " +
+        "implemented yet");
+  }
+
+  /**
+   * Link-header based discovery.
+   */
+  private List<SecureDiscoveryInformation> tryLinkHeaderBasedDiscoveryForUser(
+      UrlIdentifier claimedId) throws DiscoveryException {
+    // TODO: implement this
+    throw new DiscoveryException("link-header-based discovery is not " +
+        "implemented yet");
+  }
+
+  /**
+   * Legacy generic discovery method. Checks the type of identifier provided,
+   * and dispatches to the appropriate discovery method. Also employs a
+   * fallback strategy to use 2.0-style discovery in case the new-style
+   * discovery doesn't yield any results.
+   */
+  @Override
+  public List<SecureDiscoveryInformation> discover(Identifier identifier)
+      throws DiscoveryException {
+
+    /*
+     * The old API doesn't distinguish between discovery of an IdP endpoint
+     * and discovery of a user id. We introduce a new type of Identifier
+     * to be able to distinguish between these two cases.
+     */
+
+    if (identifier instanceof IdpIdentifier) {
+
+      IdpIdentifier site = (IdpIdentifier) identifier;
+      return siteFallbackDiscoverer.get(site);
+
+    } else if (identifier instanceof UrlIdentifier) {
+
+      UrlIdentifier url = (UrlIdentifier)identifier;
+      return userFallbackDiscoverer.get(url);
+
+    } else {
+
+      // for all other types of identifiers, use old-style discovery
+      @SuppressWarnings("unchecked")
+      List<DiscoveryInformation> result = super.discover(identifier);
+      return convertToNewDiscoveryInfo(result);
+    }
+  }
+
+  /**
+   * Converts {@link DiscoveryInformation} objects into
+   * {@link SecureDiscoveryInformation} object (which will have the isSecure
+   * bit set to false).
+   */
+  /* visible for testing */
+  static List<SecureDiscoveryInformation> convertToNewDiscoveryInfo(
+      List<DiscoveryInformation> infos) throws DiscoveryException {
+
+    if (infos == null) {
+      return null;
+    }
+
+    List<SecureDiscoveryInformation> result = new ArrayList<SecureDiscoveryInformation>(infos.size());
+
+    for (DiscoveryInformation info : infos) {
+      result.add(new SecureDiscoveryInformation(info));
+    }
+    return result;
+  }
+
+  /**
+   * Implements fallback discovery: First, we try new-style discovery (to
+   * be implemented by a subclass), and if that doesn't work, we'll give the
+   * implementing subclass a chance to provide a different identifier for the
+   * legacy discovery. For example, a subclass implementing discovery for an
+   * IdPIdentifier identifying a site (which doesn't exist in 2.0-style
+   * discovery) could convert the IdPIdentifier "example.com" into an
+   * UrlIdentifier "http://www.example.com/" and have legacy discovery performed
+   * on that UrlIdentifier.
+   *
+   * @param <T> the type of identifier discovery is performed on.
+   */
+  /* visible for testing */
+  abstract class FallbackDiscovery<T extends Identifier> {
+
+    public abstract List<SecureDiscoveryInformation> newStyleDiscovery(T id)
+        throws DiscoveryException;
+
+    public abstract Identifier getLegacyIdentifier(T id)
+        throws DiscoveryException;
+
+    public List<SecureDiscoveryInformation> get(T id)
+        throws DiscoveryException {
+
+      List<SecureDiscoveryInformation> result;
+
+      // first, try new-style discovery
+      try {
+        result = newStyleDiscovery(id);
+        if (result != null && result.size() == 0) {
+          logger.log(Level.WARNING, "could not perform new-style discovery on "
+              + id.getIdentifier() + ". discovery returned null");
+          result = null;
+        }
+      } catch (DiscoveryException e) {
+        logger.log(Level.WARNING, "could not perform new-style discovery on "
+            + id.getIdentifier(), e);
+        result = null;
+      }
+
+      if (result != null) {
+        return result;
+      }
+
+      // if that doesn't work, try old-style discovery
+      return convertToNewDiscoveryInfo(
+          oldStyleDiscovery(getLegacyIdentifier(id)));
+    }
+
+    @SuppressWarnings("unchecked")
+    /* visible for testing */
+    List<DiscoveryInformation> oldStyleDiscovery(Identifier id)
+        throws DiscoveryException {
+      return Discovery2.super.discover(id);
+    }
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMeta.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMeta.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMeta.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMeta.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,121 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+/**
+ * Represents a host-meta file for a certain site (host).
+ */
+public class HostMeta {
+
+  private final static Logger log = Logger.getLogger(HostMeta.class.getName());
+
+  // links found in the host-meta
+  private final ArrayList<Link> links;
+
+  // link-patterns found in the host-meta
+  private final ArrayList<LinkPattern> linkPatterns;
+
+  /**
+   * Returns a host-meta, as read and parsed from a stream.
+   * @throws IOException if we can't read from the stream.
+   */
+  public static HostMeta parseFromStream(InputStream content)
+      throws IOException {
+
+    HostMeta result = new HostMeta();
+    BufferedReader reader =
+        new BufferedReader(new InputStreamReader(content, "UTF-8"));
+
+    int i = 0;
+    String line;
+
+    while ((line = reader.readLine()) != null) {
+      i++;
+      line = line.trim();
+
+      // read over comments
+      if (line.startsWith("#")) {
+        continue;
+      }
+
+      // read over empty lines
+      if (line.length() == 0) {
+        continue;
+      }
+
+      try {
+
+        if (line.toLowerCase().startsWith("link:")) {
+          result.addLink(Link.fromString(line));
+        } else if (line.toLowerCase().startsWith("link-pattern:")) {
+          result.addLinkPattern(LinkPattern.fromString(line));
+        } else {
+          log.info("ignoring line in host-meta: " + line);
+        }
+
+      } catch (LinkSyntaxException e) {
+        // couldn't parse line. Maybe it wasn't a _link_ line?
+        log.warning("could not parse line " + i + " in host-meta: " +
+            line);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Parses a host-meta from a byte array.
+   */
+  public static HostMeta parseFromBytes(byte[] bytes) {
+    try {
+      return parseFromStream(new ByteArrayInputStream(bytes));
+    } catch (IOException e) {
+      // this should never happen
+      throw new RuntimeException(e);
+    }
+  }
+
+  public HostMeta() {
+    links = new ArrayList<Link>();
+    linkPatterns = new ArrayList<LinkPattern>();
+  }
+
+  public Collection<Link> getLinks() {
+    return links;
+  }
+
+  public Collection<LinkPattern> getLinkPatterns() {
+    return linkPatterns;
+  }
+
+  public void addLink(Link link) {
+    links.add(link);
+  }
+
+  public void addLinkPattern(LinkPattern linkPattern) {
+    linkPatterns.add(linkPattern);
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaException.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaException.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaException.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaException.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+/**
+ * Exception thrown when host-meta cannnot be fetched.
+ */
+public class HostMetaException extends Exception {
+
+  public HostMetaException() {
+  }
+
+  public HostMetaException(String message) {
+    super(message);
+  }
+
+  public HostMetaException(Throwable cause) {
+    super(cause);
+  }
+
+  public HostMetaException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaFetcher.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaFetcher.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaFetcher.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/HostMetaFetcher.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,35 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+/**
+ * Interface for classes that implement various host-meta fetching
+ * strategies. The default implementation will just attempto to fetch the
+ * host-meta from the /host-meta path of a site.
+ */
+public interface HostMetaFetcher {
+
+  /**
+   * Returns the host-meta for a given site.
+   * @param host the name of the host, including port (e.g. "foo.com", or
+   *   "bar.com:9080", or "www.foo.com").
+   * @return the host-meta
+   * @throws HostMetaException if the host-meta cannot be fetched.
+   */
+  public HostMeta getHostMeta(String host) throws HostMetaException;
+
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/IdpIdentifier.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/IdpIdentifier.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/IdpIdentifier.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/IdpIdentifier.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,37 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+import org.openid4java.discovery.Identifier;
+
+/**
+ * A new type of identifier, indicating the identity of a site (IdP) as opposed
+ * to that of a user. The identifier will typically not be a URL, but simply
+ * the name of the host (e.g. "example.com", as opposed to "http://example.com")
+ */
+public class IdpIdentifier implements Identifier {
+
+  private final String idp;
+
+  public IdpIdentifier(String idp) {
+    this.idp = idp;
+  }
+
+  public String getIdentifier() {
+    return idp;
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LegacyXrdsResolver.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LegacyXrdsResolver.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LegacyXrdsResolver.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LegacyXrdsResolver.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,567 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+import com.google.step2.http.FetchException;
+import com.google.step2.http.FetchRequest;
+import com.google.step2.http.FetchResponse;
+import com.google.step2.http.HttpFetcher;
+import com.google.step2.util.XmlUtil;
+import com.google.step2.xmlsimplesign.CertValidator;
+import com.google.step2.xmlsimplesign.VerificationResult;
+import com.google.step2.xmlsimplesign.Verifier;
+import com.google.step2.xmlsimplesign.XmlSimpleSignException;
+
+import org.openid4java.discovery.DiscoveryException;
+import org.openid4java.discovery.DiscoveryInformation;
+import org.openid4java.discovery.Identifier;
+import org.openid4java.discovery.UrlIdentifier;
+import org.openxri.xml.Service;
+import org.openxri.xml.XRD;
+import org.openxri.xml.XRDS;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Vector;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Implements XRDS-based discovery.
+ */
+public class LegacyXrdsResolver implements XrdDiscoveryResolver {
+
+  private static final Logger logger =
+      Logger.getLogger(LegacyXrdsResolver.class.getName());
+
+  // the type of meta-data document this resolver understands
+  private static final String XRDS_TYPE = "application/xrds+xml";
+
+  // specifies a link that points to a document that includes meta data.
+  private static final String URI_TEMPLATE_TYPE =
+    "http://www.iana.org/assignments/relation/describedby";
+
+  // used to generate URIs pointing to user-specific XRDS documents
+  private static final String URI_TEMPLATE_TAG = "URITemplate";
+
+  // used to delegate to new signer in next XRDS document
+  private static final String NEXT_AUTHORITY_TAG = "NextAuthority";
+
+  // injected fetcher
+  private final HttpFetcher httpFetcher;
+
+  // injected XRD signature verifier
+  private final Verifier verifier;
+
+  // the object that will validate the signing cert, i.e., decide whether
+  // the signing cert belongs to an authority appropriate for the given XRD
+  private final CertValidator certValidator;
+
+  public LegacyXrdsResolver(HttpFetcher httpFetcher, Verifier verifier,
+      CertValidator validator) {
+    this.httpFetcher = httpFetcher;
+    this.verifier = verifier;
+    this.certValidator = validator;
+  }
+
+  public String getDiscoveryDocumentType() {
+    return XRDS_TYPE;
+  }
+
+  /**
+   * Finds OP endpoints in a site's XRDS.
+   * @param siteXrdsUri the URI from which to load the site's XRDS.
+   * @return a list of discovery infos.
+   * @throws DiscoveryException
+   */
+  public List<SecureDiscoveryInformation> findOpEndpointsForSite(
+      IdpIdentifier site, URI siteXrdsUri) throws DiscoveryException {
+    return resolveXrds(getXrd(siteXrdsUri), DiscoveryInformation.OPENID2_OP,
+        site, null);
+  }
+
+  /**
+   * Returns a list of discovery info objects from a user's XRDS document.
+   * The document's canonical ID is expected to be equal to the claimedID of
+   * the user.
+   * @param claimedId the claimedId of the user
+   * @param userXrdsUri the URI from which to download the user's XRDS document.
+   */
+  public List<SecureDiscoveryInformation> findOpEndpointsForUser(
+      UrlIdentifier claimedId, URI userXrdsUri) throws DiscoveryException {
+
+      return resolveXrds(getXrd(userXrdsUri), DiscoveryInformation.OPENID2,
+          claimedId, null);
+  }
+
+  /**
+   * Returns a list of discovery info objects from a user's XRDS document, but
+   * starts discovery at the site's XRDS document. The site's XRDS document
+   * (whose canonical ID is expected to match the host in the claimed ID) is
+   * expected to contain URITemplate elements which will point to the user's
+   * XRDS document. The latter document's canonical ID is expected to be equal
+   * to the claimedID of the user.
+   * @param claimedId the claimedId of the user
+   * @param siteXrdsUri the URI from which to download the user's XRDS document.
+   */
+  public List<SecureDiscoveryInformation> findOpEndpointsForUserThroughSiteXrd(
+      UrlIdentifier claimedId, URI siteXrdsUri) throws DiscoveryException {
+
+    // We're given the XRDS for the site of the claimedID.
+    // Perform mapping to extract user's XRDS location.
+    NextXrdLocation userXrdsLocation =
+        mapClaimedIdToUserXrdsUri(getXrd(siteXrdsUri), claimedId);
+
+    // now that we have the user XRDS URI, we fetch the XRDS
+    // and return the list of OP endpoints found in there.
+    return resolveXrds(getXrd(userXrdsLocation.getUri()),
+        DiscoveryInformation.OPENID2,
+        claimedId,
+        userXrdsLocation.getNextAuthority());
+  }
+
+  /**
+   * Looks for a URITemplate in the XRD, and applies the claimed id to it in
+   * order to generate the user's XRDS endpoint.
+   *
+   * @param siteXrd the XRD for the site (host) identified in the claimedId
+   *
+   * @return A {@link NextXrdLocation}, which is a struct containing the URI
+   *   obtained by mapping the claimedId onto the URITemplate found in the XRD,
+   *   and also a String identifying the next authority expected to sign the
+   *   XRD that the URI points to. This authority string might be null, which
+   *   means that the XRD that the URI points to should be signed by an
+   *   authority that matches the claimedId.
+   *
+   * @throws DiscoveryException
+   */
+  /* visible for testing */
+  NextXrdLocation mapClaimedIdToUserXrdsUri(XrdRepresentations siteXrd,
+      UrlIdentifier claimedId) throws DiscoveryException {
+
+    // extract the host from the claimed id - this is the canonicalID
+    // we expect in the site's XRD
+    IdpIdentifier host = new IdpIdentifier(claimedId.getUrl().getHost());
+
+    // find the <Service> element with type '.../describedby'
+    Service service = getServiceForType(siteXrd.getXrd(), URI_TEMPLATE_TYPE);
+    if (service == null) {
+      throw new DiscoveryException("could not find service of type " +
+          URI_TEMPLATE_TYPE + " in XRDS at location " +
+          claimedId.getIdentifier());
+    }
+
+    // is there a NextAuthority? We only trust the next authority element
+    // if the document is properly signed.
+    String nextAuthority = checkSecurity(siteXrd, host, null)
+        ? getTagValue(service, NEXT_AUTHORITY_TAG)  // might still be null
+        : null;                                     // must be null if unsigned
+
+    // find the <URITemplate> tag inside the <Service> element
+    String uriTemplate = getTagValue(service, URI_TEMPLATE_TAG);
+    if (uriTemplate == null) {
+      throw new DiscoveryException("missing " + URI_TEMPLATE_TAG + " in " +
+          "service specification in XRDS at location " +
+          claimedId.getIdentifier());
+    }
+
+    // now, apply the mapping:
+    UriTemplate template = new UriTemplate(uriTemplate);
+    URI newUri = template.map(URI.create(claimedId.getIdentifier()));
+
+    return new NextXrdLocation(newUri, nextAuthority);
+  }
+
+  /**
+   * Returns the first value of a tag inside a Service element.
+   * @param service the Service element inside which we're looking for a tag.
+   * @param tagName the name of the tag
+   * @return the value of the tag, or null if no such tag exists.
+   */
+  private String getTagValue(Service service, String tagName) {
+    @SuppressWarnings("unchecked")
+    Vector<Element> tags = service.getOtherTagValues(tagName);
+    if (tags == null || tags.size() == 0) {
+      return null;
+    }
+
+    // we're just looking at the first tag
+    return tags.get(0).getTextContent();
+  }
+
+  /**
+   * Finds OP-endpoints in an XRDS document.
+   * @param xrd The XRD in which we're looking for OP endpoints.
+   * @param version the type of <Service> element we're looking for, can be
+   *   either http://specs.openid.net/auth/2.0/signon or
+   *   http://specs.openid.net/auth/2.0/server
+   * @param id the identifier (UrlIdentifier for claimedId, or IdPIdentifier
+   *   for site discovery)
+   * @param authority who we expect to be signing this XRD. If this is null,
+   *   then the XRD must be signed by an authority that matches the
+   *   canonicalID in the document.
+   * @return a list of discovery info objects.
+   * @throws DiscoveryException
+   */
+  private List<SecureDiscoveryInformation> resolveXrds(XrdRepresentations xrd,
+      String version, Identifier id, String authority)
+      throws DiscoveryException {
+
+    boolean isSecure = checkSecurity(xrd, id, authority);
+
+    List<Service> services = getServicesForType(xrd.getXrd(), version);
+
+    if (services == null) {
+      throw new DiscoveryException("could not find <Service> of type " +
+          version + " in XRDS for " + xrd.getSource());
+    }
+
+    List<SecureDiscoveryInformation> result = new ArrayList<SecureDiscoveryInformation>(services.size());
+
+    for (Service service : services) {
+      try {
+        if (version.equals(DiscoveryInformation.OPENID2)) {
+          // look for LocalID and use claimedID, if given.
+          result.add(createDiscoveryInfoForSignon(service, id, isSecure));
+        } else if (version.equals(DiscoveryInformation.OPENID2_OP)) {
+          // for site discovery, just return the URI
+          result.add(createDiscoveryInfoForServer(service, isSecure));
+        } else {
+          throw new DiscoveryException("unkown OpenID version : " + version);
+        }
+      } catch (MalformedURLException e) {
+        logger.log(Level.WARNING, "found malformed URL in discovery document " +
+            "at " + xrd.getSource(), e);
+        continue;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks whether the XRD is properly signed.
+   * @param xrd the XRD in question.
+   * @param id the id that we expect this XRD to be about.
+   * @param authority the authority that we expect this document to have signed.
+   *   If null, the document should be signed by an authority matching the
+   *   CanonicalId.
+   * @return true if the signature could be validated, false otherwise
+   */
+  private boolean checkSecurity(XrdRepresentations xrd, Identifier id,
+      String authority) {
+
+    // first, we make sure that the canonicalID in this XRD matches
+    // the given identifier
+    String canonicalId = getCanonicalId(xrd.getXrd());
+    if (canonicalId == null) {
+      logger.warning("XRD from " + xrd.getSource() +
+          "did not have canonical Id");
+      return false;
+    }
+
+    if (!canonicalId.equals(id.getIdentifier())) {
+      logger.warning("Canonical ID " + canonicalId + " in XRD from " +
+          xrd.getSource() + " did not equal identifier " +
+          id.getIdentifier());
+      return false;
+    }
+
+    // now, check the signature:
+    VerificationResult verificatioResult;
+    try {
+      verificatioResult = verifier.verify(xrd.getDocument(), xrd.getSignature());
+    } catch (XmlSimpleSignException e) {
+      logger.log(Level.WARNING, "signature on XRD from " + xrd.getSource() +
+          "did not verify", e);
+      return false;
+    }
+
+    // finally, validate the signing cert (make sure it belongs to the authority
+    // that is supposed to have signed this XRD). If we're not given an
+    // authority, the XRD should be signed by the entity identified in the
+    // canonical id.
+    authority = (authority == null) ? canonicalId : authority;
+    return certValidator.matches(verificatioResult.getCerts().get(0), authority);
+  }
+
+  /**
+   * Returns CanonicalId of this document. There should be exactly one
+   * CanonicalId in the document for us to consider the document secure.
+   * @param xrd
+   */
+  @SuppressWarnings("deprecation")
+  private String getCanonicalId(XRD xrd) {
+    // The new API doesn't tell us if there are more than one canonical ID
+    // in the document, but we need to know.
+    if (xrd.getNumCanonicalids() != 1) {
+      return null;
+    }
+    return xrd.getCanonicalidAt(0).getValue();
+  }
+
+  /**
+   * Returns a simple {@link SecureDiscoveryInformation} object pointing to an
+   * OP endpoint.
+   * @param service The <Service> element that has the OP endpoint information.
+   * @param isSecure whether to mark the {@link SecureDiscoveryInformation}
+   *   object as secure.
+   * @return a {@link SecureDiscoveryInformation} object.
+   * @throws DiscoveryException
+   * @throws MalformedURLException
+   */
+  private SecureDiscoveryInformation createDiscoveryInfoForServer(
+      Service service, boolean isSecure) throws DiscoveryException, MalformedURLException {
+    SecureDiscoveryInformation result =
+        new SecureDiscoveryInformation(service.getURIAt(0).getURI().toURL());
+    result.setSecure(isSecure);
+    return result;
+  }
+
+  /**
+   * Returns a {@link SecureDiscoveryInformation} object pointing to an
+   * OP endpoint, and possibly containing other information such as the
+   * claimedId and the OP-local id.
+   * @param service The <Service> element that has the OP endpoint information.
+   * @param claimedId the claimedId we currently performing discovery on.
+   * @param isSecure whether to mark the {@link SecureDiscoveryInformation}
+   *   object as secure.
+   * @return a {@link SecureDiscoveryInformation} object.
+   * @throws DiscoveryException
+   * @throws MalformedURLException
+   */
+  private SecureDiscoveryInformation createDiscoveryInfoForSignon(
+      Service service, Identifier claimedId, boolean isSecure)
+      throws DiscoveryException, MalformedURLException {
+
+    // could be null
+    String localId = getLocalId(service);
+
+    SecureDiscoveryInformation result = new SecureDiscoveryInformation(
+        service.getURIAt(0).getURI().toURL(),
+        claimedId,
+        localId,
+        DiscoveryInformation.OPENID2);
+
+    result.setSecure(isSecure);
+    return result;
+  }
+
+  /**
+   * Returns LocalID from a <Service> element.
+   * @param service
+   * @return null if there is no LocalID specified.
+   */
+  private String getLocalId(Service service) {
+    int numLocalIds = service.getNumLocalIDs();
+
+    if (numLocalIds == 0) {
+      return null;
+    }
+
+    return service.getLocalIDAt(0).getValue();
+  }
+
+  /**
+   * Fetches an XRD from a URI and returns it, or throws if the XRD can't be
+   * fetched/found.
+   * @param uri from where to fetch the XRDS.
+   * @throws DiscoveryException
+   */
+  private XrdRepresentations getXrd(URI uri) throws DiscoveryException {
+    XrdRepresentations result;
+    try {
+      result = fetchXrd(uri);
+    } catch (FetchException e) {
+      throw new DiscoveryException("could not fetch XRDS from "
+          + uri.toASCIIString(), e);
+    }
+    if (result == null) {
+      throw new DiscoveryException("XRDS at " + uri.toASCIIString() + " did " +
+          "not contain an XRD");
+    }
+    return result;
+  }
+
+  /**
+   * Fetches an OpenID 2.0-style XRDS document and returns the "final" XRD
+   * from it.
+   *
+   * @return an {@link XrdRepresentations} object, which not only contains the
+   *   parsed XRD, but also the document as a byte array, the URI from which
+   *   the XRD was fetched, and the Signature that we might have see in the
+   *   HTTP response's Signature header.
+   *
+   * @throws FetchException
+   */
+  private XrdRepresentations fetchXrd(URI uri) throws FetchException {
+
+    FetchRequest request = FetchRequest.createGetRequest(uri);
+
+    XRDS xrds;
+    byte[] documentBytes;
+    String signature;
+
+    try {
+      FetchResponse response = httpFetcher.fetch(request);
+
+      documentBytes = response.getContentAsBytes();
+      signature = response.getFirstHeader("Signature"); // could be null
+
+      Document document =
+          XmlUtil.getDocument(new ByteArrayInputStream(documentBytes));
+
+      xrds = new XRDS(document.getDocumentElement(), false);
+
+    } catch (ParserConfigurationException e) {
+      throw new FetchException(e);
+    } catch (SAXException e) {
+      throw new FetchException(e);
+    } catch (IOException e) {
+      throw new FetchException(e);
+    } catch (URISyntaxException e) {
+      throw new FetchException(e);
+    } catch (ParseException e) {
+      throw new FetchException(e);
+    }
+
+    return new XrdRepresentations(xrds.getFinalXRD(), uri.toASCIIString(),
+        documentBytes, signature);
+  }
+
+  /**
+   * Returns highest-priority service for given type from an XRD
+   */
+  private Service getServiceForType(XRD xrd, String type) {
+
+    @SuppressWarnings("unchecked")
+    List<Service> allServices = xrd.getPrioritizedServices();
+
+    if (allServices == null) {
+      return null;
+    }
+
+    for (Service service : allServices) {
+      if (service.matchType(type)) {
+        return service;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Returns services (highest-priority first) for given type from an XRD
+   */
+  private List<Service> getServicesForType(XRD xrd, String type) {
+
+    @SuppressWarnings("unchecked")
+    List<Service> allServices = xrd.getPrioritizedServices();
+    List<Service> result = new ArrayList<Service>();
+
+    if (allServices == null) {
+      return null;
+    }
+
+    for (Service service : allServices) {
+      if (service.matchType(type)) {
+        result.add(service);
+      }
+    }
+
+    if (result.size() == 0) {
+      return null;
+    }
+
+    return result;
+  }
+
+  /**
+   * Helper class that bundles the location of the next XRD document in the
+   * discovery chain, together with the authority that should sign that next
+   * XRD document.
+   */
+  private static class NextXrdLocation {
+
+    private final URI uri;
+    private final String nextAuthority;
+
+    public NextXrdLocation(URI uri, String nextAuthority) {
+      this.uri = uri;
+      this.nextAuthority = nextAuthority;
+    }
+
+    public URI getUri() {
+      return uri;
+    }
+
+    public String getNextAuthority() {
+      return nextAuthority;
+    }
+  }
+
+  /**
+   * Helper class that hold two different representations of the XRD: the
+   * parsed version (useful for extracting information from it), and the
+   * raw bytes (useful for verifying the signature). Also holds the value
+   * of the Signature: header, if it was present when fetching the XRD, and
+   * the location (source) from which the the XRD was fetched.
+   */
+  private static class XrdRepresentations {
+
+    private final XRD xrd;
+    private final byte[] document;
+    private final String source;
+    private final String signature;
+
+    public XrdRepresentations(XRD xrd, String source, byte[] document, String signature) {
+      this.xrd = xrd;
+      this.source = source;
+      this.document = document;
+      this.signature = signature;
+    }
+
+    public XRD getXrd() {
+      return xrd;
+    }
+
+    public byte[] getDocument() {
+      return document;
+    }
+
+    public String getSignature() {
+      return signature;
+    }
+
+    public String getSource() {
+      return source;
+    }
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Link.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Link.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Link.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/Link.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+import java.net.URI;
+
+/**
+ * Represents a link header
+ */
+public class Link extends LinkBase {
+
+  public static Link fromString(String input) throws LinkSyntaxException {
+    return new Link(input);
+  }
+
+  private final URI uri;
+
+  public Link(String input) throws LinkSyntaxException {
+    super(input, "Link");
+
+    // this particular subclass of LinkBase knows that the URI string
+    // returned by the superclass is, in fact, a URI and not some sort
+    // of URI template pattern, so we're parsing it right here and now
+    // as a URI.
+    try {
+      uri = URI.create(getLinkValue().getUriString());
+    } catch (IllegalArgumentException e) {
+      throw new LinkSyntaxException(e);
+    }
+  }
+
+  /**
+   * Returns the URI that the Link points to.
+   */
+  public URI getUri() {
+    return uri;
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkBase.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkBase.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkBase.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkBase.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,125 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+/**
+ * Super class for Link and Link-Pattern. This is basically a wrapper around
+ * {@link LinkValue}, which is a class that can parse the link values (i.e.,
+ * the stuff to the right of the colon in Link: <...>; ...).
+ */
+public abstract class LinkBase {
+
+  private final LinkValue value;
+  private final String prefix;
+
+  protected LinkBase(String input, String prefix) throws LinkSyntaxException {
+    this.value = getLinkValue(input, prefix);
+
+    // we keep track of the prefix only for our equals() and hashCode() methods
+    this.prefix = prefix.toLowerCase();
+  }
+
+  /**
+   * Returns the LinkValue that this instance represents. LinkValues know
+   * about rel-types, mime-types, and general link parameters.
+   */
+  public LinkValue getLinkValue() {
+    return value;
+  }
+
+  /**
+   * Returns the list of rel-types specified in this link or link-pattern.
+   */
+  public RelTypes getRelationships() {
+    return getLinkValue().getRelationships();
+  }
+
+  /**
+   * Returns the mime-type of this link or link-pattern.
+   */
+  public String getMimeType() {
+    return getLinkValue().getMimeType();
+  }
+
+  /**
+   * Returns the value of a link parameter. For example, in
+   *
+   * Link: <http://example.com/path>; foo=bar
+   *
+   * this method, when called with name = "foo", would return "bar".
+   *
+   * @param name the name of the parameter.
+   */
+  public String getParamater(String name) {
+    return getLinkValue().getParameter(name);
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((prefix == null) ? 0 : prefix.hashCode());
+    result = prime * result + ((value == null) ? 0 : value.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+    LinkBase other = (LinkBase) obj;
+    if (prefix == null) {
+      if (other.prefix != null) return false;
+    } else if (!prefix.equals(other.prefix)) return false;
+    if (value == null) {
+      if (other.value != null) return false;
+    } else if (!value.equals(other.value)) return false;
+    return true;
+  }
+
+  /**
+   * Helper method to parse the link value (i.e., the stuff to the right of
+   * the colon), given the whole line, and the prefix (i.e., the stuff to the
+   * left of the colon).
+   * @param input the complete line of text
+   * @param prefix the stuff to the left of the colon (e.g. "Link" or
+   *   "Link-Pattern")
+   * @return the parsed link-value
+   * @throws LinkSyntaxException if the stuff to the right of the colon couldn't
+   *   be parsed, or if the line of text doesn't follow the pattern
+   *   prefix: link-value.
+   */
+  private static LinkValue getLinkValue(String input, String prefix)
+      throws LinkSyntaxException {
+
+    int colon = input.indexOf(':');
+
+    if (colon < 0) {
+      throw new LinkSyntaxException("missing colon:" + input);
+    }
+
+    String left = input.substring(0, colon).trim();
+    String right = input.substring(colon + 1).trim();
+
+    if (!left.equalsIgnoreCase(prefix)) {
+      throw new LinkSyntaxException("missing " + prefix + " prefix: " + input);
+    }
+
+    return LinkValue.fromString(right);
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkPattern.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkPattern.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkPattern.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkPattern.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+/**
+ * Represents a link-pattern header
+ */
+public class LinkPattern extends LinkBase {
+
+  public static LinkPattern fromString(String input) throws LinkSyntaxException {
+    return new LinkPattern(input);
+  }
+
+  public LinkPattern(String input) throws LinkSyntaxException {
+    super(input, "Link-Pattern");
+  }
+
+  /**
+   * Returns the string between the '<' and '>' in a Link-Pattern. Since that
+   * is not necessarily a syntactically correct URI, we're returning it as a
+   * string.
+   */
+  public String getUriPattern() {
+    return getLinkValue().getUriString();
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkSyntaxException.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkSyntaxException.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkSyntaxException.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkSyntaxException.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+/**
+ * Gets thrown when we can't parse a Link: or Link-Pattern: line in a
+ * host-meta file
+ */
+public class LinkSyntaxException extends Exception {
+
+  public LinkSyntaxException() {
+    super();
+  }
+
+  public LinkSyntaxException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public LinkSyntaxException(String message) {
+    super(message);
+  }
+
+  public LinkSyntaxException(Throwable cause) {
+    super(cause);
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkValue.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkValue.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkValue.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/LinkValue.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,359 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Class that represents a link-value, i.e., the stuff to the right of the
+ * colon in a Link: or Link-Pattern: HTTP header (also used in host-meta files).
+ *
+ * Link: <http://example.com>; rel=rel-param; other-param=other-value
+ *        |---uri-string---|   |-----------parameters---------------|
+ *       |---------------------link-value---------------------------|
+ *
+ * We parse out the uri-string and the link parameters. Special attention
+ * is given to the "rel" parameter, which in turn is parsed into a
+ * RelTypes datastructure.
+ */
+public class LinkValue {
+
+  public static LinkValue fromString(String link) throws LinkSyntaxException {
+    return new Parser(link).link_value();
+  }
+
+  private static Pattern WHITESPACE = Pattern.compile("\\s");
+
+  private final String uri;
+  private final RelTypes relTypes;
+  private final Map<String, String> params;
+
+  private LinkValue(String uri, RelTypes relTypes, Map<String, String> params) {
+    this.uri = uri;
+    this.relTypes = relTypes;
+    this.params = params;
+  }
+
+  public RelTypes getRelationships() {
+    return relTypes;
+  }
+
+  protected String getUriString() {
+    return uri;
+  }
+
+  public String getMimeType() {
+    return params.get("type");
+  }
+
+  public String getParameter(String name) {
+    return params.get(name);
+  }
+
+  /**
+   * Hashcode and equals are only based on params and uri.
+   */
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((params == null) ? 0 : params.hashCode());
+    result = prime * result + ((uri == null) ? 0 : uri.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+    LinkValue other = (LinkValue) obj;
+    if (params == null) {
+      if (other.params != null) return false;
+    } else if (!params.equals(other.params)) return false;
+    if (uri == null) {
+      if (other.uri != null) return false;
+    } else if (!uri.equals(other.uri)) return false;
+    return true;
+  }
+
+  /**
+   * Allows you to slowly build up a link from its constituent parts.
+   * This class is used by the parser.
+   */
+  private static class Builder {
+
+    private final String uri;
+    private final Collection<RelType> relTypes = new ArrayList<RelType>();
+    private final Map<String, String> params = new HashMap<String, String>();
+
+    public Builder(String uri) {
+      this.uri = uri;
+    }
+
+    public Builder addRelType(RelType relType) {
+      relTypes.add(relType);
+      return this;
+    }
+
+    public LinkValue create() {
+      return new LinkValue(uri, RelTypes.setOf(relTypes), params);
+    }
+
+    public Builder addLinkParameter(String name, String value) {
+      params.put(name, value);
+      return this;
+    }
+  }
+
+  /**
+   * Parsers that can parse link-values.
+   */
+  private static class Parser {
+
+    // the line of text we're parsing
+    private final String input;
+
+    public Parser(String input) {
+      this.input = input.trim();
+    }
+
+    // the main entry method. We're using non-standard method name
+    // capitalization b/c the method names match the BNF-definition
+    // in the spec.
+    public LinkValue link_value() throws LinkSyntaxException {
+
+      String s = input;
+
+      if (!s.startsWith("<")) {
+        throw new LinkSyntaxException("missing '<': " + input);
+      }
+
+      int greaterThan = s.indexOf('>', 1);
+
+      if (greaterThan < 0) {
+        throw new LinkSyntaxException("missing '>':" + input);
+      }
+
+      String left = s.substring(1, greaterThan).trim();
+      String right = s.substring(greaterThan + 1).trim();
+
+      // get the uri string out from between the '<' and '>'
+      String uri_reference = uri_reference(left);
+
+      // init a builder with that uri string[
+      Builder builder = new Builder(uri_reference);
+
+      // parse the rest of the line (link params), passing the builder
+      // along so that we can add params to it as we encounter them.
+      link_params(right, builder);
+
+      return builder.create();
+    }
+
+    private String uri_reference(String s) throws LinkSyntaxException {
+      if (s == null || s.length() == 0) {
+        throw new LinkSyntaxException("got empty uri-reference: " + input);
+      }
+      return s;
+    }
+
+    // parses a list of link-params (e.g. "; foo=bar; bla=baz")
+    private void link_params(String s, Builder builder)
+        throws LinkSyntaxException {
+
+      if (s == null || s.length() == 0) {
+        // that's fine - link params are optional;
+        return;
+      }
+
+      if (!s.startsWith(";")) {
+        throw new LinkSyntaxException(
+            "link-params must start with ';': " + input);
+      }
+
+      // skip over the semicolon;
+      String remainder = s.substring(1).trim();
+
+      // parse the first (left-most) link parameter
+      link_param(remainder, builder);
+    }
+
+    // parses a single link-param out of a list of link-params. In particular,
+    // it will parse the left-most link-param and then recursively call
+    // link-params (not the plural) to parse the rest of the parameters.
+    private void link_param(String s, Builder builder)
+        throws LinkSyntaxException {
+
+      int eq = s.indexOf('=');
+
+      if (eq < 0) {
+        throw new LinkSyntaxException("missing '=' in link-param:" + input);
+      }
+
+      String left = s.substring(0, eq).trim();
+      String right = s.substring(eq + 1).trim();
+
+      // we'll be calling param_value to parse the parameter value, which will
+      // tell us how much of the remaining input it consumed.
+      int consumedUntil;
+
+      // we treat "rel" params special, since we parse their internal
+      // structure
+      if (left.equalsIgnoreCase("rel")) {
+        consumedUntil = relation_type(right, builder);
+      } else {
+        consumedUntil = param_value(right, builder, left);
+      }
+
+      // now, parse the rest of the remaining input agains as a list of params
+      String remainder = right.substring(consumedUntil).trim();
+      link_params(remainder, builder);
+    }
+
+    // returns the length of the prefix that turns out to be a relation-type
+    private int relation_type(String s, Builder builder)
+        throws LinkSyntaxException {
+      if (s.startsWith("\"")) {
+        return quoted_relation_types(s, builder);
+      } else {
+        return unquoted_relation_type(s, builder);
+      }
+    }
+
+    private int unquoted_relation_type(String s, Builder builder)
+        throws LinkSyntaxException {
+      if (s == null || s.length() == 0) {
+        throw new LinkSyntaxException("missing value in: " + input);
+      }
+
+      // this is ended by a semicolon, or by end-of-line
+      String[] parts = s.split(";"); // split by semicolon
+
+      // let's also add it as a param, in case people are interested in the
+      // unparsed rel param
+      builder.addLinkParameter("rel", parts[0].trim());
+
+      RelType relType;
+      try {
+        relType = new RelType(parts[0].trim());
+      } catch (IllegalArgumentException e) {
+        // thrown when relType is not a valid URI
+        throw new LinkSyntaxException(parts[0].trim() + " is not a valid URI",
+            e);
+      }
+      builder.addRelType(relType);
+      return parts[0].length();
+    }
+
+    private int quoted_relation_types(String s, Builder builder)
+        throws LinkSyntaxException {
+      if (!s.startsWith("\"")) {
+        throw new LinkSyntaxException("expected \" in relation-type: " + input);
+      }
+
+      int secondQuote = s.indexOf('"', 1);
+
+      if (secondQuote < 0) {
+        throw new LinkSyntaxException("could not find closing quote in: "
+            + input);
+      }
+
+      int result = secondQuote + 1; // that's how long the whole thing was
+
+      String sWithoutQuotes = s.substring(1, secondQuote);
+
+      // let's also add it as a param, in case people want to see the
+      // unparsed rel parameter
+      builder.addLinkParameter("rel", sWithoutQuotes);
+
+      String[] relTypes = sWithoutQuotes.split("\\s"); // split by whitespace
+
+      for (String relTypeString : relTypes) {
+        RelType relType;
+        try {
+          relType = new RelType(relTypeString.trim());
+        } catch (IllegalArgumentException e) {
+          // thrown when relTypeString is not a valid URI
+          throw new LinkSyntaxException(relTypeString.trim() +
+              "is not a valid URI", e);
+        }
+        builder.addRelType(relType);
+      }
+
+      return result;
+    }
+
+    // returns the length of the prefix that turns out to be a parameter value
+    // (the stuff following the prefix are different link parameters)
+    private int param_value(String s, Builder builder, String paramName)
+        throws LinkSyntaxException {
+      if (s.startsWith("\"")) {
+        return quoted_param_value(s, builder, paramName);
+      } else {
+        return unquoted_param_value(s, builder, paramName);
+      }
+    }
+
+    private int quoted_param_value(String s, Builder builder, String paramName)
+        throws LinkSyntaxException {
+
+      if (!s.startsWith("\"")) {
+        throw new LinkSyntaxException("expected \" in: " + input);
+      }
+
+      int secondQuote = s.indexOf('"', 1);
+
+      if (secondQuote < 0) {
+        throw new LinkSyntaxException("could not find closing quote in: "
+            + input);
+      }
+
+      int result = secondQuote + 1; // that's how long the whole thing was
+
+      String sWithoutQuotes = s.substring(1, secondQuote);
+
+      builder.addLinkParameter(paramName, sWithoutQuotes);
+
+      return result;
+    }
+
+    private int unquoted_param_value(String s, Builder builder, String paramName)
+        throws LinkSyntaxException {
+      if (s == null || s.length() == 0) {
+        throw new LinkSyntaxException("missing value in: " + input);
+      }
+
+      // this is ended by semicolo, or by end-of-line
+      String[] parts = s.split(";"); // split by semicolon
+
+      String paramValue = parts[0].trim();
+
+      if (WHITESPACE.matcher(paramValue).find()) {
+        throw new LinkSyntaxException("unexpected whitespace in unqoted param " +
+            paramName + " in link value " + input);
+      }
+
+      builder.addLinkParameter(paramName, paramValue);
+      return parts[0].length();
+    }
+  }
+}

Added: portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/ParallelHostMetaFetcher.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/ParallelHostMetaFetcher.java?rev=929218&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/ParallelHostMetaFetcher.java (added)
+++ portals/jetspeed-2/portal/trunk/openid-step2/common/src/main/java/com/google/step2/discovery/ParallelHostMetaFetcher.java Tue Mar 30 18:41:47 2010
@@ -0,0 +1,114 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed 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 com.google.step2.discovery;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Uses multiple fetchers in parallel to obtain a host-meta for a given site.
+ * For example, one of the fetchers can look for the host-meta in its standard
+ * location (http://host/host-meta), while other fetchers try other strategies
+ * (getting host-metas from a database, or fetching them from a hosting service
+ * to which the host may have outsourced the hosting of host-metas).
+ *
+ * If more than one fetchers succeed, it is undefined which result this fetcher
+ * returns. Likewise, if none of them succeed, it is undefined which fetcher's
+ * exception is propagated to the caller. We _do_ guarantee that if at least one
+ * fetcher succeeds, the parallel fetcher will also succeed.
+ */
+public class ParallelHostMetaFetcher implements HostMetaFetcher {
+
+  private final List<HostMetaFetcher> fetchers;
+  private final ExecutorService executorService;
+  private final long timeout; // in seconds
+
+  /**
+   * Public constructor.
+   * @param executorService the ExecutorService that will run the various
+   *   threads in which we'll attempt the parallel fetching.
+   * @param timeout timeout, in seconds, of how long we're willing to wait for
+   *   the parallel host-meta fetchers to fetch a host-meta
+   * @param fetchers the fetchers that we will try in parallel.
+   */
+  public ParallelHostMetaFetcher(ExecutorService executorService,
+      Long timeout, HostMetaFetcher... fetchers) {
+    if (fetchers.length == 0) {
+      throw new IllegalArgumentException("need to supply at least one " +
+          "HostMetaFetcher to ParallelHostMetaFetcher");
+    }
+    this.fetchers = Arrays.asList(fetchers);
+    this.executorService = executorService;
+    this.timeout = timeout.longValue();
+  }
+
+  public HostMeta getHostMeta(String host) throws HostMetaException {
+    List<Callable<HostMeta>> threads =
+        new ArrayList<Callable<HostMeta>>(fetchers.size());
+    for (HostMetaFetcher fetcher : fetchers) {
+      threads.add(new FetcherThread(fetcher, host));
+    }
+
+    try {
+      return executorService.invokeAny(threads, timeout, TimeUnit.SECONDS);
+
+    } catch (InterruptedException e) {
+      throw new HostMetaException(e);
+    } catch (ExecutionException e) {
+      throw new HostMetaException("no fetcher found a host-meta for " + host, e);
+    } catch (RejectedExecutionException e) {
+      throw new HostMetaException("could not schedule threads for parallel " +
+          "fetching of host-meta for " + host, e);
+    } catch (TimeoutException e) {
+      throw new HostMetaException("none of the host-meta fetchers completed " +
+          "within " + timeout + " seconds for host " + host, e);
+    }
+  }
+
+  /**
+   * Thread in which we execute one particular fetch.
+   */
+  private class FetcherThread implements Callable<HostMeta> {
+
+    private final String host;
+    private final HostMetaFetcher fetcher;
+
+    public FetcherThread(HostMetaFetcher fetcher, String host) {
+      this.fetcher = fetcher;
+      this.host = host;
+    }
+
+    public HostMeta call() throws HostMetaException {
+      HostMeta hostMeta = fetcher.getHostMeta(host);
+      if ((hostMeta == null)
+          || (0 == (hostMeta.getLinks().size() + hostMeta.getLinkPatterns().size()))) {
+        throw new HostMetaException("fetcher " +
+            fetcher.getClass().getName() +
+            " returned empty host-meta for " + host);
+      } else {
+        return hostMeta;
+      }
+    }
+  }
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: jetspeed-dev-unsubscribe@portals.apache.org
For additional commands, e-mail: jetspeed-dev-help@portals.apache.org


Mime
View raw message