Package com.google.gdata.data

Source Code of com.google.gdata.data.ExtensionProfile$ExtensionPointHandler

/* Copyright (c) 2008 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.gdata.data;

import com.google.gdata.util.common.base.Pair;
import com.google.gdata.util.common.xml.XmlNamespace;
import com.google.gdata.util.common.xml.XmlWriter;
import com.google.gdata.util.common.xml.XmlWriter.Attribute;
import com.google.gdata.client.CoreErrorDomain;
import com.google.gdata.util.Namespaces;
import com.google.gdata.util.ParseException;
import com.google.gdata.util.XmlParser;

import org.xml.sax.Attributes;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.TreeSet;

/**
* Specifies a complete extension profile for an extended GData schema.
* A profile is a set of allowed extensions for each type together with
* additional properties.
* <p>
* For example, Calendar might allow {@code <gd:who>} within {@code
* <atom:feed>}, and {@code <gd:when>}, {@code <gd:who>}, and {@code
* <gd:where>} within {@code <atom:entry>}.
*
*
*
*/
public class ExtensionProfile {

  /** Set of previously declared Kind.Adaptor classes. */
  private HashSet<Class<? extends Kind.Adaptor>> declared =
      new HashSet<Class<? extends Kind.Adaptor>>();

  /**
   * Adds the extension declarations associated with an {@link Kind.Adaptor}
   * instance, if the declaring class has not already added to this
   * profile.  The method is optimized to reduce the overhead of declaring
   * the same adaptor type multiple times within the same profile.
   */
  public void addDeclarations(Kind.Adaptor adaptor) {
    Class<? extends Kind.Adaptor> adaptorClass = adaptor.getClass();
    if (declared.add(adaptorClass)) {
      adaptor.declareExtensions(this);
    }
  }

  // Simple helper method to avoid cast warnings in very specific cases
  // where we know the cast is safe.
  @SuppressWarnings("unchecked")
  private Class<? extends ExtensionPoint> extensionPointClass(Class clazz) {
    return clazz;
  }

  /**
   * Specifies that type {@code extendedType} can contain an extension
   * described by {@code extDescription}.
   */
  public synchronized void declare(Class<? extends ExtensionPoint> extendedType,
                                   ExtensionDescription extDescription) {

    // When configuring an extension profile that is auto-extensible, remap
    // th extension point assocations from the specific type down to any
    // base adaptable type.  This ensures that extensions will be parseable
    // on a more generic base type.   As an example, this would map extensions
    // that are normally associated with EventEntry "up" to BaseEntry.
    while (isAutoExtending &&
        Kind.Adaptable.class.isAssignableFrom(extendedType.getSuperclass())) {
        extendedType = extensionPointClass(extendedType.getSuperclass());
    }

    ExtensionManifest manifest = getOrCreateManifest(extendedType);

    Pair<String, String> extensionQName =
        new Pair<String,String>(extDescription.getNamespace().getUri(),
            extDescription.getLocalName());

    manifest.supportedExtensions.put(extensionQName, extDescription);

    // Propagate the declarations down to any profiled subtypes.
    for(ExtensionManifest subclassManifest : manifest.subclassManifests) {
      subclassManifest.supportedExtensions.put(extensionQName, extDescription);
    }

    profile.put(extendedType, manifest);

    nsDecls = null;
  }


  /**
   * Specifies that type {@code extendedType} can contain an extension described
   * by {@code extClass}, as determined by
   * {@link ExtensionDescription#getDefaultDescription(Class)}.
   */
  public synchronized void declare(Class<? extends ExtensionPoint> extendedType,
      Class<? extends Extension> extClass) {
    declare(extendedType, ExtensionDescription.getDefaultDescription(extClass));
  }


  /**
   * Declares that {@code extDesc} defines a feed extension.
   *
   * @deprecated Calls to this API should be replaced with calls to
   * {@link ExtensionProfile#declare(Class,ExtensionDescription)} where
   * the first argument is a specific {@link BaseFeed} subtype. The
   * {@link BaseFeed} class should only be used for mix-in types that
   * might appear in multiple feed types.  Its use for all feed declarations
   * can result in conflicts when mutiple feed types are declared into a
   * single extension profile, a common practice in client library service
   * initialization for services that return multiple feed types.
   */
  @Deprecated
  public synchronized void declareFeedExtension(ExtensionDescription extDesc) {
    declare(BaseFeed.class, extDesc);
  }


  /**
   * Declares that {@code extClass} defines a feed extension.
   *
   * @deprecated Calls to this API should be replaced with calls to
   * {@link ExtensionProfile#declare(Class,ExtensionDescription)} where
   * the first argument is a specific {@link BaseFeed} subtype. The
   * {@link BaseFeed} class should only be used for mix-in types that
   * might appear in multiple feed types.  Its use for all feed declarations
   * can result in conflicts when mutiple feed types are declared into a
   * single extension profile, a common practice in client library service
   * initialization for services that return multiple feed types.
   */
  @Deprecated
  public synchronized void declareFeedExtension(
      Class<? extends Extension> extClass) {
    declare(BaseFeed.class, extClass);
  }


  /**
   * Declares that {@code extDesc} defines an entry extension.
   *
   * @deprecated Calls to this API should be replaced with calls to
   * {@link ExtensionProfile#declare(Class,ExtensionDescription)} where
   * the first argument is a specific {@link BaseEntry} subtype. The
   * {@link BaseEntry} class should only be used for mix-in types that
   * might appear in multiple entry types.  Its use for all entry declarations
   * can result in conflicts when mutiple feed types are declared into a
   * single extension profile, a common practice in client library service
   * initialization for services that return multiple entry types.
   */
  @Deprecated
  public synchronized void declareEntryExtension(ExtensionDescription extDesc) {
    declare(BaseEntry.class, extDesc);
  }


  /**
   * Declares that {@code extClass} defines an entry extension.
   *
   * @deprecated Calls to this API should be replaced with calls to
   * {@link ExtensionProfile#declare(Class,ExtensionDescription)} where
   * the first argument is a specific {@link BaseEntry} subtype. The
   * {@link BaseEntry} class should only be used for mix-in types that
   * might appear in multiple entry types.  Its use for all entry declarations
   * can result in conflicts when mutiple feed types are declared into a
   * single extension profile, a common practice in client library service
   * initialization for services that return multiple entry types.
   */
  @Deprecated
  public synchronized void declareEntryExtension(
      Class<? extends Extension> extClass) {
    declare(BaseEntry.class, extClass);
  }


  /** Specifies that type {@code extendedType} can contain arbitrary XML. */
  public synchronized void declareArbitraryXmlExtension(
      Class<? extends ExtensionPoint> extendedType) {

    ExtensionManifest manifest = getOrCreateManifest(extendedType);
    manifest.arbitraryXml = true;

    // Propagate the arbitrary xml declaration to any profiled subtypes.
    for(ExtensionManifest subclassManifest : manifest.subclassManifests) {
      subclassManifest.arbitraryXml = true;
    }

    profile.put(extendedType, manifest);
    nsDecls = null;
  }


  /** Specifies additional top-level namespace declarations. */
  public synchronized void declareAdditionalNamespace(XmlNamespace ns) {
    additionalNamespaces.add(ns);
  }


  /** Specifies the type of feeds nested within {@code <gd:feedLink>}. */
  public synchronized void declareFeedLinkProfile(ExtensionProfile profile) {
    feedLinkProfile = profile;
    nsDecls = null;
  }


  /** Retrieves the type of feeds nested within {@code <gd:feedLink>}. */
  public synchronized ExtensionProfile getFeedLinkProfile() {
    return feedLinkProfile;
  }


  /** Specifies the type of entries nested within {@code <gd:entryLink>}. */
  public synchronized void declareEntryLinkProfile(ExtensionProfile profile) {
    entryLinkProfile = profile;
    nsDecls = null;
  }


  /** Retrieves the type of entries nested within {@code <gd:entryLink>}. */
  public synchronized ExtensionProfile getEntryLinkProfile() {
    return entryLinkProfile;
  }


  /**
   * Retrieves an extension manifest for a specific class (or one of
   * its superclasses) or {@code null} if not specified.
   */
  public ExtensionManifest getManifest(Class<?> extendedType) {
    ExtensionManifest manifest = null;
    while (extendedType != null) {
      manifest = profile.get(extendedType);
      if (manifest != null)
        return manifest;
      extendedType = extendedType.getSuperclass();
    }
    return null;
  }


  /**
   * Returns whether the given extended type has already been declared.  Note
   * that unlike {@link #getManifest(Class)}, it does not check the super
   * classes.
   */
  public boolean isDeclared(Class<?> extendedType) {
    return profile.containsKey(extendedType);
  }


  /** Retrieves a collection of all namespaces used by this profile. */
  public synchronized Collection<XmlNamespace> getNamespaceDecls() {

    if (nsDecls == null) {
      nsDecls = computeNamespaceDecls();
    }

    return nsDecls;
  }


  /** Internal storage for the profile. */
  private final Map<Class<?>, ExtensionManifest> profile =
    new HashMap<Class<?>, ExtensionManifest>();


  /** Additional namespaces. */
  private Collection<XmlNamespace> additionalNamespaces =
    new LinkedHashSet<XmlNamespace>();


  /** Nested feed link profile. */
  private ExtensionProfile feedLinkProfile;


  /** Nested entry link profile. */
  private ExtensionProfile entryLinkProfile;


  /** Namespace declarations cache. */
  private Collection<XmlNamespace> nsDecls = null;


  /** Profile supports auto-extension declaration */
  private boolean isAutoExtending = false;

  public void setAutoExtending(boolean v) { isAutoExtending = v; }
  public boolean isAutoExtending() { return isAutoExtending; }

  /**
   * When {@code true}, indicates that arbitrary XML is acceptable on any
   * {@link ExtensionPoint} when parsing using this profile.  The default
   * value is {@code true} to provide compliance with sections 6.3 of
   * RFC4287 (Atom Syntax) and section 6.2 of the AtomPub spec.
   */
  private boolean allowsArbitraryXml = true;

  /**
   * Configures the extension profile to specify whether any foreign XML
   * elements found when parsing within an {@link ExtensionPoint} should
   * be preserved.  If {@code false}, the presence of foreign XML will result
   * in parsing errors.  Arbitrary XML support is enabled by default in a
   * newly created profile.
   *
   * @param v {@code true} to enable foreign XML preservation, {@code false}
   *          otherwise.
   *
   * #see ExtensionPoint.getXmlBlob()
   */
  public void setArbitraryXml(boolean v) { allowsArbitraryXml = v; }

  /**
   * Returns whether foreign XML elements will be preserved within any
   * {@link ExtensionPoint}.
   *
   * @return {@code true} if foreign XML elements are preserved, {@code false}
   * otherwise.
   */
  public boolean allowsArbitraryXml() { return allowsArbitraryXml; }

  /** Internal helper routine. */
  private ExtensionManifest getOrCreateManifest(
      Class<? extends ExtensionPoint> extendedType) {

    // Look for a manifest associated with the extend type, and if it is
    // a precise match then return it.
    ExtensionManifest manifest = getManifest(extendedType);
    if (manifest != null && manifest.extendedType == extendedType) {
        return manifest;
    }

    ExtensionManifest newManifest = new ExtensionManifest(extendedType);

    // Compute the list of manifests for supertypes.  Do this using a stack,
    // so we can process them in reverse order (from the deepest superclass
    // to the closest).
    Stack<ExtensionManifest> superManifests = new Stack<ExtensionManifest>();
    while (manifest != null) {
      superManifests.push(manifest);
      manifest = getManifest(manifest.extendedType.getSuperclass());
    }

    // Propagate declarations from any superclass that is already in the
    // extension profile, and set up an association from the super manifest
    // to the subclass one so future declarations will propagate.
    while (!superManifests.empty()) {
      ExtensionManifest superManifest = superManifests.pop();
      newManifest.supportedExtensions.putAll(
          superManifest.supportedExtensions);
      newManifest.arbitraryXml = superManifest.arbitraryXml;
      superManifest.subclassManifests.add(newManifest);
    }

    // Look for any existing profile types that extend the newly added
    // one and set up a relationship mapping so future declarations on this
    // manifest will be propagated
    for(Map.Entry<Class<?>, ExtensionManifest> profileMapping :
        profile.entrySet()) {

      if (extendedType.isAssignableFrom(profileMapping.getKey())) {
        newManifest.subclassManifests.add(profileMapping.getValue());
      }
    }

    return newManifest;
  }


  private synchronized Collection<XmlNamespace> computeNamespaceDecls() {

    HashSet<XmlNamespace> result = new HashSet<XmlNamespace>();

    result.addAll(additionalNamespaces);

    for (ExtensionManifest manifest: profile.values()) {
      result.addAll(manifest.getNamespaceDecls());
    }

    if (feedLinkProfile != null) {
      result.addAll(feedLinkProfile.computeNamespaceDecls());
    }

    if (entryLinkProfile != null) {
      result.addAll(entryLinkProfile.computeNamespaceDecls());
    }

    return Collections.unmodifiableSet(result);
  }

  /**
   * Reads the ExtensionProfile XML format.
   */
  public class Handler extends XmlParser.ElementHandler {

    private ExtensionProfile configProfile;
    private ClassLoader configLoader;
    private List<XmlNamespace> namespaces =
              new ArrayList<XmlNamespace>();

    public Handler(ExtensionProfile configProfile, ClassLoader configLoader,
                   Attributes attrs) throws ParseException {
      this.configProfile = configProfile;
      this.configLoader = configLoader;

      if (attrs != null) {
        String arbitraryXmlAttr = attrs.getValue("", "arbitraryXml");
        if (arbitraryXmlAttr != null) {
          if (arbitraryXmlAttr.equals("true") || arbitraryXmlAttr.equals("1")) {
            allowsArbitraryXml = true;
          } else if (arbitraryXmlAttr.equals("false") ||
                     arbitraryXmlAttr.equals("0")) {
            allowsArbitraryXml = false;
          } else {
            ParseException pe = new ParseException(
                CoreErrorDomain.ERR.invalidArbitraryXml);
            pe.setInternalReason("Invalid value for arbitaryXml: " +
                                     arbitraryXmlAttr);
            throw pe;
          }
        }
      }
    }

    public void validate() {
    }


    @Override
    public void processEndElement() {
      validate();
    }


    @Override
    public XmlParser.ElementHandler getChildHandler(String namespace,
                                                    String localName,
                                                    Attributes attrs)
        throws ParseException, IOException {

      if (namespace.equals(Namespaces.gdataConfig)) {

        if (localName.equals("namespaceDescription")) {

          String alias = attrs.getValue("", "alias");
          if (alias == null) {
            ParseException pe = new ParseException(
                CoreErrorDomain.ERR.missingAttribute);
            pe.setInternalReason(
                      "NamespaceDescription alias attribute is missing");
            throw pe;
          }
          String uri = attrs.getValue("", "uri");
          if (uri == null) {
            ParseException pe = new ParseException(
                CoreErrorDomain.ERR.missingAttribute);
            pe.setInternalReason(
                      "NamespaceDescription uri attribute is missing");
            throw pe;
          }

          XmlNamespace declaredNs = new XmlNamespace(alias, uri);
          namespaces.add(declaredNs);
          declareAdditionalNamespace(declaredNs);
          return new XmlParser.ElementHandler();

        } else if (localName.equals("extensionPoint")) {

          return new ExtensionPointHandler(configProfile, configLoader,
                                           namespaces, attrs);
        }
      }

      return super.getChildHandler(namespace, localName, attrs);
    }
  }

  /**
   * Reads the ExtensionPoint XML format
   */
  public class ExtensionPointHandler extends XmlParser.ElementHandler {

    private ExtensionProfile configProfile;
    private ClassLoader configLoader;

    private Class<? extends ExtensionPoint> extensionPoint;
    private boolean arbitraryXml;
    private List<ExtensionDescription> extDescriptions =
      new ArrayList<ExtensionDescription>();
    private List<XmlNamespace> namespaces;

    public ExtensionPointHandler(ExtensionProfile configProfile,
                                 ClassLoader configLoader,
                                 List<XmlNamespace> namespaces,
                                 Attributes attrs)
        throws ParseException {

      this.configProfile = configProfile;
      this.configLoader = configLoader;
      this.namespaces = namespaces;

      String extendedClassName = attrs.getValue("", "extendedClass");
      if (extendedClassName == null) {
        ParseException pe = new ParseException(
            CoreErrorDomain.ERR.missingAttribute);
        pe.setInternalReason(
            "ExtensionPoint extendedClass attribute is missing");
        throw pe;
      }

      Class<?> loadedClass;
      try {
        loadedClass = configLoader.loadClass(extendedClassName);
      } catch (ClassNotFoundException e) {
        throw new ParseException(
            CoreErrorDomain.ERR.cantLoadExtensionPoint, e);
      }
      if (!ExtensionPoint.class.isAssignableFrom(loadedClass)) {
        throw new ParseException(
            CoreErrorDomain.ERR.mustExtendExtensionPoint);
      }
      extensionPoint = extensionPointClass(loadedClass);

      String arbitraryXmlAttr = attrs.getValue("", "arbitraryXml");
      if (arbitraryXmlAttr != null) {
        if (arbitraryXmlAttr.equals("true") || arbitraryXmlAttr.equals("1")) {
          arbitraryXml = true;
        } else if (arbitraryXmlAttr.equals("false") ||
                   arbitraryXmlAttr.equals("0")) {
          arbitraryXml = false;
        } else {
          ParseException pe = new ParseException(
              CoreErrorDomain.ERR.invalidArbitraryXml);
          pe.setInternalReason("Invalid value for arbitaryXml: " +
                                   arbitraryXmlAttr);
          throw pe;
        }
      }
    }

    @Override
    public void processEndElement() {

      if (arbitraryXml) {
        declareArbitraryXmlExtension(extensionPoint);
      }

      for (ExtensionDescription extDescription: extDescriptions) {
        declare(extensionPoint, extDescription);
      }
    }

    @Override
    public XmlParser.ElementHandler getChildHandler(String namespace,
                                                    String localName,
                                                    Attributes attrs)
        throws ParseException, IOException {

      if (namespace.equals(Namespaces.gdataConfig)) {
        if (localName.equals("extensionDescription")) {

          ExtensionDescription extDescription = new ExtensionDescription();
          extDescriptions.add(extDescription);
          return extDescription.new Handler(configProfile, configLoader,
                                            namespaces, attrs);
        }
      }

      return super.getChildHandler(namespace, localName, attrs);
    }
  }


  /**
   * Parses XML in the ExtensionProfile format.
   *
   * @param   configProfile
   *            Extension profile description configuration extensions.
   *
   * @param   classLoader
   *            ClassLoader to load extension classes
   *
   * @param   stream
   *            InputStream from which to read the description
   */
  public void parseConfig(ExtensionProfile configProfile,
                          ClassLoader classLoader,
                          InputStream stream) throws IOException,
                                                   ParseException {

    Handler handler = new Handler(configProfile, classLoader, null);
    new XmlParser().parse(stream, handler, Namespaces.gdataConfig,
                          "extensionProfile");
  }

  /**
   * Generates XML in the external config format.
   *
   * @param   w
   *          Output writer.
   *
   * @param   extProfile
   *          Extension profile.
   *
   * @throws  IOException
   */
  public void generateConfig(XmlWriter w,
                             ExtensionProfile extProfile) throws IOException {


    List<Attribute> epAttrs = new ArrayList<Attribute>();
    epAttrs.add(new Attribute("arbitraryXml", allowsArbitraryXml));
    w.startElement(Namespaces.gdataConfigNs, "extensionProfile", epAttrs,
        nsDecls);

    for (XmlNamespace namespace : additionalNamespaces) {

      List<Attribute> nsAttrs = new ArrayList<Attribute>();
      nsAttrs.add(new Attribute("alias", namespace.getAlias()));
      nsAttrs.add(new Attribute("uri", namespace.getUri()));
      w.simpleElement(Namespaces.gdataConfigNs, "namespaceDescription",
                      nsAttrs, null);
    }

    //
    // Get a list of the extended classes sorted by class name
    //
    TreeSet<Class<?>> extensionSet = new TreeSet<Class<?>>(
        new Comparator<Class<?>>() {
          public int compare(Class<?> c1, Class<?> c2) {
            return c1.getName().compareTo(c2.getName());
          }
         
          @Override
          public boolean equals(Object c) {
            return this.getClass().equals(c.getClass());
          }
        });

    for (Class<?> extensionPoint : profile.keySet()) {
      extensionSet.add(extensionPoint);
    }

    for (Class<?> extensionPoint : extensionSet) {

      ExtensionManifest  manifest = profile.get(extensionPoint);

      List<Attribute> ptAttrs = new ArrayList<Attribute>();
      ptAttrs.add(new Attribute("extendedClass", extensionPoint.getName()));
      ptAttrs.add(new Attribute("arbitraryXml", manifest.arbitraryXml));
      w.startElement(Namespaces.gdataConfigNs, "extensionPoint", ptAttrs, null);

      // Create an ordered list of the descriptions in this profile
      TreeSet<ExtensionDescription> descSet =
        new TreeSet<ExtensionDescription>();

      for (ExtensionDescription extDescription :
           manifest.getSupportedExtensions().values()) {
        descSet.add(extDescription);
      }

      // Now output based upon the ordered list
      for (ExtensionDescription extDescription : descSet) {
        extDescription.generateConfig(w, extProfile);
      }

      w.endElement(Namespaces.gdataConfigNs, "extensionPoint");
    }

    w.endElement(Namespaces.gdataConfigNs, "extensionProfile");
  }
}
TOP

Related Classes of com.google.gdata.data.ExtensionProfile$ExtensionPointHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.