Example #1
0
 /*package*/ void fillAttributes(XMLStreamReader in) {
   for (int i = in.getAttributeCount() - 1; i >= 0; i--) {
     String n = in.getAttributeLocalName(i);
     if (model.attributes.containsKey(n)) {
       if (attributes == null) attributes = new HashMap<String, String>();
       attributes.put(n, in.getAttributeValue(i));
     }
   }
   if (attributes == null) attributes = Collections.emptyMap();
 }
Example #2
0
 public List<Injectee> getInjectees() {
   return Collections.emptyList();
 }
Example #3
0
 public Set<Annotation> getQualifierAnnotations() {
   return Collections.emptySet();
 }
Example #4
0
/**
 * {@link Inhabitant} that loads configuration from XML.
 *
 * <p>This object also captures all the configuration values in a typeless way, so that the loading
 * of the actual classes can be deferred as much as possible.
 *
 * <p>This is the {@link ActiveDescriptor} that gets registered into {@link ServiceLocator}, so one
 * can access this object by {@link ServiceLocator#getServiceHandle(Class, String)} family of
 * methods.
 *
 * @author Kohsuke Kawaguchi
 */
public class Dom extends AbstractActiveDescriptor implements InvocationHandler, ObservableBean {
  /** Model drives the interpretation of this DOM. */
  public final ConfigModel model;

  private final Dom parent;

  private ActiveDescriptor<Dom> domDescriptor;

  private ServiceHandle<Dom> serviceHandle;
  /**
   * This flag indicates whether a Dom object should be written to domain.xml. By default everything
   * is written to domain.xml unless someone explicitly calls the skipFromXml method
   */
  private boolean writeToXml = true;

  /** This method should be invoked if this Dom should not be persisted to the domain.xml file. */
  public void skipFromXml() {
    writeToXml = false;
  }

  /** This method should be invoked if this Dom needs to be persisted to domain.xml file */
  public void writeToXml() {
    writeToXml = true;
  }

  abstract static class Child {
    final String name;

    Child(String name) {
      this.name = name;
    }

    /** Writes this node to XML. */
    protected abstract void writeTo(XMLStreamWriter w) throws XMLStreamException;

    /**
     * Returns a deep copy of itself.
     *
     * @return a deep copy of itself.
     */
    protected abstract Child deepCopy(Dom parent);

    /**
     * Returns true if it is an empty child, meaning all its attributes are either null or the
     * default value as well as for all the descendants.
     */
    protected abstract boolean isEmpty();

    @Override
    public String toString() {
      return "Dom.Child(" + name + "," + System.identityHashCode(this) + ")";
    }
  }

  static final class NodeChild extends Child {
    final Dom dom;

    NodeChild(String name, Dom dom) {
      super(name);
      this.dom = dom;
    }

    protected void writeTo(XMLStreamWriter w) throws XMLStreamException {
      dom.writeTo(name, w);
    }

    @Override
    protected Child deepCopy(Dom parent) {

      return new NodeChild(name, dom.copy(parent));
    }

    @Override
    protected boolean isEmpty() {
      return dom.isEmpty();
    }

    @Override
    public String toString() {
      return "Dom.NodeChild(" + dom.getImplementation() + "," + super.toString() + ")";
    }
  }

  static final class LeafChild extends Child {
    /** Raw element text value before {@link Translator} processing. */
    final String value;

    LeafChild(String name, String value) {
      super(name);
      this.value = value;
    }

    protected void writeTo(XMLStreamWriter w) throws XMLStreamException {
      w.writeStartElement(name);
      w.writeCharacters(value);
      w.writeEndElement();
    }

    @Override
    protected Child deepCopy(Dom parent) {
      return new LeafChild(name, value);
    }

    @Override
    protected boolean isEmpty() {
      return false;
    }
  }

  public void initializationCompleted() {}

  /* package */ @SuppressWarnings({"unchecked"})
  void register() {
    ServiceLocator locator = getServiceLocator();

    ActiveDescriptor<?> myselfReified = locator.reifyDescriptor(this);

    DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
    DynamicConfiguration dc = dcs.createDynamicConfiguration();

    //        habitat.add(this);
    HK2Loader loader = this.model.classLoaderHolder;

    Set<Type> ctrs = new HashSet<Type>();
    ctrs.add(myselfReified.getImplementationClass());

    if (ConfigBean.class.isAssignableFrom(this.getClass())) {
      ctrs.add(ConfigBean.class);
    }

    DomDescriptor<Dom> domDesc =
        new DomDescriptor<Dom>(
            this, ctrs, Singleton.class, getImplementation(), new HashSet<Annotation>());
    domDesc.setLoader(loader);
    domDescriptor = dc.addActiveDescriptor(domDesc, false);

    String key = getKey();
    for (String contract : model.contracts) {
      ActiveDescriptor<Dom> alias = new AliasDescriptor<Dom>(locator, domDescriptor, contract, key);
      dc.addActiveDescriptor(alias, false);
    }
    if (key != null) {
      ActiveDescriptor<Dom> alias =
          new AliasDescriptor<Dom>(locator, domDescriptor, model.targetTypeName, key);
      dc.addActiveDescriptor(alias, false);
    }

    dc.commit();

    serviceHandle = getHabitat().getServiceHandle(domDescriptor);
  }

  /**
   * When a new Dom object is created, ensures that all @NotNull annotated elements have a value.
   */
  public void addDefaultChildren() {
    List<Dom.Child> children = new ArrayList<Dom.Child>();
    ensureConstraints(children);
    if (!children.isEmpty()) {
      setChildren(children);
    }
  }

  /* package */ void ensureConstraints(List<Child> children) {
    Set<String> nullElements = new HashSet<String>(model.getElementNames());
    for (Child child : children) {
      nullElements.remove(child.name);
    }

    for (String s : nullElements) {
      ConfigModel.Property p = model.getElement(s);
      for (String annotation : p.getAnnotations()) {
        if (annotation.equals(NotNull.class.getName())) {
          if (p instanceof ConfigModel.Node) {
            ConfigModel childModel = ((ConfigModel.Node) p).model;
            Dom child = document.make(getHabitat(), null, this, childModel);
            child.register();

            children.add(new Dom.NodeChild(s, child));

            // recursive call to ensure the children constraints are also respected
            List<Child> grandChildren = new ArrayList<Child>();
            child.ensureConstraints(grandChildren);
            if (!grandChildren.isEmpty()) {
              child.setChildren(grandChildren);
            }

            child.initializationCompleted();
          }
        }
      }
    }
  }

  /** All attributes and their raw values before {@link Translator} processing. */
  private Map<String, String> attributes = new HashMap<String, String>();
  /**
   * List of all child elements, both leaves and nodes.
   *
   * <p>The list is read-only and copy-on-write to support concurrent access.
   */
  private volatile List<Child> children = Collections.emptyList();

  private final Location location;

  /** Owner of the DOM tree. */
  public final DomDocument document;

  private final ServiceLocator habitat;

  /**
   * @param in If provided, this is used to record the source location where this DOM object is
   *     loaded from. Otherwise this can be null.
   */
  public Dom(
      ServiceLocator habitat,
      DomDocument document,
      Dom parent,
      ConfigModel model,
      XMLStreamReader in) {
    super(
        createDescriptor(
            model.targetTypeName, model.injector.getLoader(), model.injector.getMetadata()));

    this.habitat = habitat;

    if (in != null) {
      this.location = new LocationImpl(in.getLocation());
    } else {
      this.location = null;
    }
    this.model = model;
    this.document = document;
    this.parent = parent;

    // TODO: This code is disabled as it does fail from time to time when assertions are enabled
    // assert (parent==null || parent.document==document); // all the nodes in the tree must belong
    // to the same document
  }

  public Dom(ServiceLocator habitat, DomDocument document, Dom parent, ConfigModel model) {
    this(habitat, document, parent, model, null);
  }

  public ServiceLocator getHabitat() {
    return habitat;
  }

  /**
   * Copy constructor, used to get a deep copy of the passed instance
   *
   * @param source the instance to copy
   */
  public Dom(Dom source, Dom parent) {
    this(source.getHabitat(), source.document, parent, source.model);
    List<Child> newChildren = new ArrayList<Child>();
    for (Child child : source.children) {
      newChildren.add(child.deepCopy(this));
    }
    setChildren(newChildren);
    attributes.putAll(source.attributes);
  }

  /**
   * Returns a copy of itself providing the parent for the new copy.
   *
   * @param parent the parent instance for the cloned copy
   * @return the cloned copy
   */
  protected <T extends Dom> T copy(T parent) {
    return (T) new Dom(this, parent);
  }

  /**
   * Unwraps the proxy and returns the underlying {@link Dom} object.
   *
   * @return null if the given instance is not actually a proxy to a DOM.
   */
  public static Dom unwrap(ConfigBeanProxy proxy) {
    ConfigBeanProxy configBeanProxy = ConfigSupport.revealProxy(proxy);
    InvocationHandler ih = Proxy.getInvocationHandler(configBeanProxy);
    if (ih instanceof Dom) return (Dom) ih;
    if (ih instanceof ConfigView) {
      return (Dom) ((ConfigView) ih).getMasterView();
    }
    return null;
  }

  /** Obtains the actual key value from this {@link Dom}. */
  public String getKey() {
    String k = model.key;
    if (k == null) return null;

    switch (k.charAt(0)) {
      case '@':
        return attribute(k.substring(1));
      case '<':
        return leafElement(k.substring(1, k.length() - 1));
      default:
        throw new IllegalStateException("Invalid key value:" + k);
    }
  }

  /** If this DOM is a child of another DOM, the parent pointer. Otherwise null. */
  public Dom parent() {
    return parent;
  }

  /*package*/ void fillAttributes(XMLStreamReader in) {
    for (int i = in.getAttributeCount() - 1; i >= 0; i--) {
      String n = in.getAttributeLocalName(i);
      if (model.attributes.containsKey(n)) {
        if (attributes == null) attributes = new HashMap<String, String>();
        attributes.put(n, in.getAttributeValue(i));
      }
    }
    if (attributes == null) attributes = Collections.emptyMap();
  }

  /** Where was this {@link Dom} loaded from? */
  public Location getLocation() {
    return location;
  }

  /**
   * Returns the list of attributes with a value on this config instance. This is by definition a
   * subset of the attributes names as known to the model {@see ConfigModel.getAttributeNames}.
   *
   * @return list of attributes names which have values on this config instance
   */
  public Set<String> getAttributeNames() {
    return Collections.unmodifiableSet(attributes.keySet());
  }

  /**
   * Returns the children name associated with this config instance. This is by definition a subset
   * of the element names as known to the model {#see ConfigModel.getElementNames(). @Return list of
   * elements names associated with this config instance
   */
  public Set<String> getElementNames() {
    Set<String> names = new HashSet<String>();
    for (Child child : children) {
      names.add(child.name);
    }
    return names;
  }

  /** Performs translation with null pass-through. */
  private String t(String s) {
    if (s == null) return null;
    return document.getTranslator().translate(s);
  }

  /**
   * Obtains the attribute value, after variable expansion.
   *
   * @return null if the attribute is not found.
   */
  public String attribute(String name) {
    return t(rawAttribute(name));
  }

  /**
   * Obtians the attribute value without variable expansion.
   *
   * @return null if the attribute is not found.
   */
  public String rawAttribute(String name) {
    String value = attributes.get(name);
    if (value == null && model.attributes.containsKey(name)) {
      value = model.attributes.get(name).getDefaultValue();
    }
    return value;
  }

  /**
   * Obtains the plural attribute value. Values are separate by ',' and surrounding whitespaces are
   * ignored.
   *
   * @return null if the attribute doesn't exist. This is a distinct state from the empty list,
   *     which indicates that the attribute was there but no values were found.
   */
  public List<String> attributes(String name) {
    String v = attribute(name);
    if (v == null) return null;
    List<String> r = new ArrayList<String>();
    StringTokenizer tokens = new StringTokenizer(v, ",");
    while (tokens.hasMoreTokens()) r.add(tokens.nextToken().trim());
    return r;
  }

  /**
   * Updates the attribute value.
   *
   * <p>This would trigger the re-injection of the value.
   */
  public void attribute(String name, String value) {
    if (value == null) {
      attributes.remove(name);
    } else {
      attributes.put(name, value);
      // TODO:
      // this re-injection has two problems. First, it forces an instantiation
      // even if that hasn't happened yet. Second, if the component is scoped,
      // this won't work correctly (but then, there's no way to make that work,
      // since we can't enumerate all scope instances.)
      getInjector().injectAttribute(this, name, get());
    }
  }

  /**
   * Returns the child element by name
   *
   * @param name of the element
   * @return child element
   */
  public Dom element(String name) {

    List<Child> children = this.children; // fix the snapshot that we'll work with

    for (Child child : children) {
      if (child.name.equals(name)) {
        return ((NodeChild) child).dom;
      }
    }
    return null;
  }

  /** Picks up one leaf-element value. */
  public String leafElement(String name) {
    return t(rawLeafElement(name));
  }

  private ActiveDescriptor<Dom> addWithAlias(
      ServiceLocator locator,
      AbstractActiveDescriptor<?> descriptor,
      Class<?> contract,
      String name) {
    ActiveDescriptor<Dom> added = ServiceLocatorUtilities.findOneDescriptor(locator, descriptor);

    if (added == null) {
      if (ConfigBean.class.isAssignableFrom(this.getClass())) {
        if (!descriptor.getAdvertisedContracts().contains(ConfigBean.class.getName())) {
          descriptor.addContractType(ConfigBean.class);
        }
      }

      added = ServiceLocatorUtilities.addOneDescriptor(locator, descriptor);
    }

    AliasDescriptor<Dom> alias = new AliasDescriptor<Dom>(locator, added, contract.getName(), name);

    ServiceLocatorUtilities.addOneDescriptor(locator, alias);

    return added;
  }

  /**
   * Inserts a new {@link Dom} node right after the given DOM element.
   *
   * @param reference If null, the new element will be inserted at the very beginning.
   * @param name The element name of the newly inserted item. "*" to indicate that the element name
   *     be determined by the model of the new node.
   */
  public synchronized void insertAfter(Dom reference, String name, Dom newNode) {
    // TODO: reparent newNode
    if (name.equals("*")) name = newNode.model.tagName;
    NodeChild newChild = new NodeChild(name, newNode);

    if (children.size() == 0) {
      children = new ArrayList<Child>();
    }
    if (reference == null) {
      children.add(0, newChild);
      newNode.domDescriptor =
          addWithAlias(getHabitat(), newNode, newNode.getProxyType(), newNode.getKey());
      return;
    }

    ListIterator<Child> itr = children.listIterator();
    while (itr.hasNext()) {
      Child child = itr.next();
      if (child instanceof NodeChild) {
        NodeChild nc = (NodeChild) child;
        if (nc.dom == reference) {
          itr.add(newChild);
          newNode.domDescriptor =
              addWithAlias(getHabitat(), newNode, newNode.getProxyType(), newNode.getKey());

          return;
        }
      }
    }
    throw new IllegalArgumentException(
        reference + " is not a valid child of " + this + ". Children=" + children);
  }

  /**
   * Replaces an existing {@link NodeChild} with another one.
   *
   * @see #insertAfter(Dom, String, Dom)
   */
  public synchronized void replaceChild(Dom reference, String name, Dom newNode) {
    ListIterator<Child> itr = children.listIterator();
    while (itr.hasNext()) {
      Child child = itr.next();
      if (child instanceof NodeChild) {
        NodeChild nc = (NodeChild) child;
        if (nc.dom == reference) {
          reference.release();
          newNode.domDescriptor =
              addWithAlias(getHabitat(), newNode, newNode.getProxyType(), newNode.getKey());

          itr.set(new NodeChild(name, newNode));
          return;
        }
      }
    }
    throw new IllegalArgumentException(
        reference + " is not a valid child of " + this + ". Children=" + children);
  }

  /** Removes an existing {@link NodeChild} */
  public synchronized void removeChild(final Dom reference) {
    ListIterator<Child> itr = children.listIterator();
    while (itr.hasNext()) {
      Child child = itr.next();
      if (child instanceof NodeChild) {
        NodeChild nc = (NodeChild) child;
        if (nc.dom == reference) {
          itr.remove();
          reference.release();
          return;
        }
      }
    }
    throw new IllegalArgumentException(
        reference + " is not a valid child of " + this + ". Children=" + children);
  }

  public synchronized boolean addLeafElement(String xmlName, String value) {
    if (children.size() == 0) {
      children = new ArrayList<Child>();
    }
    return children.add(new LeafChild(xmlName, value));
  }

  public synchronized boolean removeLeafElement(String xmlName, String element) {
    List<Child> children = this.children; // fix the snapshot that we'll work with

    for (Child child : children) {
      if (child.name.equals(xmlName) && ((LeafChild) child).value.equals(element)) {
        return children.remove(child);
      }
    }
    return false;
  }

  public synchronized boolean changeLeafElement(String xmlName, String oldValue, String newValue) {
    List<Child> children = this.children; // fix the snapshot that we'll work with

    int len = children.size();
    for (int i = 0; i < len; i++) {
      Child child = children.get(i);
      if (child.name.equals(xmlName) && ((LeafChild) child).value.equals(oldValue)) {
        return (children.set(i, new LeafChild(xmlName, newValue)) != null);
      }
    }
    return false;
  }

  /** Picks up one leaf-element value without variable expansion. */
  public String rawLeafElement(String name) {
    List<Child> children = this.children; // fix the snapshot that we'll work with

    int len = children.size();
    for (int i = 0; i < len; i++) {
      Child child = children.get(i);
      if (child.name.equals(name)) {
        // error check on model guarantees that this works.
        return ((LeafChild) child).value;
      }
    }
    return null;
  }

  /**
   * Given a master list and the new sub list, replace the items in the master list with the
   * matching items from the new sub list. This process works even if the length of the new sublist
   * is different.
   *
   * <p>For example, givn:
   *
   * <pre>
   * replace A by A':
   *   M=[A,B,C], S=[A'] => [A',B,C]
   *   M=[A,B,A,B,C], S=[A',A'] => [A',B,A',B,C]
   *
   * when list length is different:
   *   M=[A,A,B,C], S=[] => [B,C]
   *   M=[A,B,C], S=[A',A'] => [A',A',B,C]
   *   M=[B,C], S=[A',A'] => [B,C,A',A']
   * </pre>
   */
  private static List<Child> stitchList(
      List<Child> list, String name, List<? extends Child> newSubList) {
    List<Child> removed = new LinkedList<Child>();
    // to preserve order, try to put new itesm where old items are found.
    // if the new list is longer than the current list, we put all the extra
    // after the last item in the sequence. That is,
    // given [A,A,B,C] and [A',A',A'], we'll update the list to [A',A',A',B,C]
    // The 'last' variable remembers the insertion position.
    int last = list.size();

    ListIterator<Child> itr = list.listIterator();
    ListIterator<? extends Child> jtr = newSubList.listIterator();
    while (itr.hasNext()) {
      Child child = itr.next();
      if (child.name.equals(name)) {
        if (jtr.hasNext()) {
          itr.set(jtr.next()); // replace
          last = itr.nextIndex();
          removed.add(child);
        } else {
          itr.remove(); // remove
          removed.add(child);
        }
      }
    }

    // new list is longer than the current one
    if (jtr.hasNext()) list.addAll(last, newSubList.subList(jtr.nextIndex(), newSubList.size()));

    return removed;
  }

  /**
   * Updates leaf-element values.
   *
   * <p>Synchronized so that concurrenct modifications will work correctly.
   */
  public synchronized void setLeafElements(final String name, String... values) {
    List<Child> newChildren = new ArrayList<Child>(children);

    LeafChild[] leaves = new LeafChild[values.length];
    for (int i = 0; i < values.length; i++) leaves[i] = new LeafChild(name, values[i]);

    stitchList(newChildren, name, Arrays.asList(leaves));
    children = newChildren;

    // see attribute(String,String) for the issue with this
    getInjector().injectElement(this, name, get());
  }

  /**
   * Picks up all leaf-element values of the given name.
   *
   * @return Can be empty but never null.
   */
  public List<String> leafElements(String name) {
    List<Child> children = this.children; // fix the snapshot that we'll work with

    final List<String> r = new ArrayList<String>();
    for (Child child : children) {
      if (child.name.equals(name)) {
        // error check on model guarantees that this cast works.
        r.add(t(((LeafChild) child).value));
      }
    }
    return r;
  }

  /**
   * Picks up all leaf-element values of the given name, without variable expansion.
   *
   * @return can be empty, but never null (even if such element name is not defined in the model.)
   */
  public List<String> rawLeafElements(String name) {
    List<Child> children = this.children; // fix the snapshot that we'll work with

    final List<String> r = new ArrayList<String>();
    for (Child child : children) {
      if (child.name.equals(name)) {
        // error check on model guarantees that this cast works.
        r.add(((LeafChild) child).value);
      }
    }
    return r;
  }

  /** Picks up one node-element value. */
  public Dom nodeElement(String name) {
    List<Child> children = this.children; // fix the snapshot that we'll work with

    int len = children.size();
    for (int i = 0; i < len; i++) {
      Child child = children.get(i);
      if (child.name.equals(name)) {
        // error check on model guarantees that this works.
        return ((NodeChild) child).dom;
      }
    }
    return null;
  }

  /**
   * Updates node-element values.
   *
   * <p>Synchronized so that concurrenct modifications will work correctly.
   */
  public synchronized void setNodeElements(final String name, Dom... values) {
    List<Child> newChildren = new ArrayList<Child>(children);

    NodeChild[] leaves = new NodeChild[values.length];
    for (int i = 0; i < values.length; i++) leaves[i] = new NodeChild(name, values[i]);

    List<Child> removed = stitchList(newChildren, name, Arrays.asList(leaves));
    children = newChildren;

    for (Child c : removed) {
      ((NodeChild) c).dom.release();
    }

    // see attribute(String,String) for the issue with this
    getInjector().injectElement(this, name, get());
  }

  /** Picks up all node-elements that have the given element name. */
  public List<Dom> nodeElements(String elementName) {
    List<Child> children = this.children; // fix the snapshot that we'll work with

    final List<Dom> r = new ArrayList<Dom>();
    int len = children.size();
    for (int i = 0; i < len; i++) {
      Child child = children.get(i);
      if (child.name.equals(elementName)) {
        // error check on model guarantees that this works.
        r.add(((NodeChild) child).dom);
      }
    }
    return r;
  }

  /**
   * Picks up all node elements that are assignable to the given type, except those who are matched
   * by other named elements in the model.
   *
   * <p>Used to implement {@code FromElement("*")}.
   */
  public List<Dom> domNodeByTypeElements(Class baseType) {
    List<Dom> r = new ArrayList<Dom>();

    int len = children.size();
    for (int i = 0; i < len; i++) {
      Child child = children.get(i);
      if (child instanceof NodeChild) {
        NodeChild nc = (NodeChild) child;
        if (model.elements.containsKey(nc.name)) continue; // match with named
        if (baseType.isAssignableFrom(nc.dom.getImplementationClass())) r.add(nc.dom);
      }
    }
    return r;
  }

  public <T> List<T> nodeByTypeElements(final Class<T> baseType) {
    final List<Dom> elements = domNodeByTypeElements(baseType);
    return new AbstractList<T>() {
      public T get(int index) {
        return baseType.cast(elements.get(index).get());
      }

      public int size() {
        return elements.size();
      }
    };
  }

  public <T> T nodeByTypeElement(Class<T> baseType) {
    int len = children.size();
    for (int i = 0; i < len; i++) {
      Child child = children.get(i);
      if (child instanceof NodeChild) {
        NodeChild nc = (NodeChild) child;
        if (model.elements.containsKey(nc.name)) continue; // match with named
        if (baseType.isAssignableFrom(nc.dom.getImplementationClass()))
          return baseType.cast(nc.dom.get());
      }
    }
    return null;
  }

  /** Performs injection to the given object. */
  public void inject(Object target) {
    model.inject(this, target);
  }

  /** Gets the {@link ConfigInjector} instance that can be used to inject this DOM to a bean. */
  public ConfigInjector getInjector() {
    return ServiceLocatorUtilities.getService(habitat, model.injector);
  }

  /**
   * Locates the DOM that serves as the symbol space root.
   *
   * @return always non-null.
   */
  public Dom getSymbolSpaceRoot(String typeName) {
    Dom dom = this;
    while (!dom.model.symbolSpaces.contains(typeName)) {
      Dom p = dom.parent();
      if (p == null) return dom; // root
      dom = p;
    }
    return dom;
  }

  /**
   * Recursively decends the DOM tree and finds a DOM that has the given key and the type name.
   *
   * <p>TODO: the current algorithm does a full tree scan. Expand the model so that we can detect
   * deadends that are statically known not to contain the kind we are looking for, and use that to
   * cut the search space.
   */
  public Dom resolveReference(String key, String typeName) {
    String keyedAs = model.keyedAs;
    if (keyedAs != null && keyedAs.equals(typeName) && getKey().equals(key))
      return this; // found it

    for (Child child : children) {
      if (child instanceof NodeChild) {
        NodeChild n = (NodeChild) child;
        Dom found = n.dom.resolveReference(key, typeName);
        if (found != null) return found;
      }
    }

    return null;
  }

  private final WeakCARCache<Class<?>, ConfigBeanProxy> proxyCache =
      CacheUtilities.createWeakCARCache(new DomProxyComputable(this), 200, false);

  /**
   * Creates a strongly-typed proxy to access values in this {@link Dom} object, by using the
   * specified interface type as the proxy type.
   */
  public <T extends ConfigBeanProxy> T createProxy(final Class<T> proxyType) {
    ConfigBeanProxy retVal = proxyCache.compute(proxyType);
    return proxyType.cast(retVal);
  }

  /** Creates a strongly-typed proxy to access values in this {@link Dom} object, */
  public <T extends ConfigBeanProxy> T createProxy() {
    return createProxy(this.<T>getProxyType());
  }

  /**
   * Returns the proxy type for this configuration object
   *
   * @param <T> the proxy type
   * @return the class object for the proxy type
   */
  public <T extends ConfigBeanProxy> Class<T> getProxyType() {
    return model.getProxyType();
  }

  /**
   * This ensures no-one tried to reify this descriptor, which has an impl class the interface
   *
   * @return always true
   */
  public boolean isReified() {
    return true;
  }

  public Class<?> getImplementationClass() {
    Class<?> retVal = (Class<?>) model.getProxyType();
    return retVal;
  }

  public Type getImplementationType() {
    return getImplementationClass();
  }

  public void setImplementationType(Type t) {
    throw new AssertionError("Can not set type of Dom descriptor");
  }

  public Set<Type> getContractTypes() {
    HashSet<Type> retVal = new HashSet<Type>();

    retVal.add(model.getProxyType());

    return retVal;
  }

  public Class<? extends Annotation> getScopeAnnotation() {
    String scope = getScope();
    if (scope != null && scope.equals(Singleton.class.getName())) {
      return Singleton.class;
    }

    return PerLookup.class;
  }

  public Set<Annotation> getQualifierAnnotations() {
    return Collections.emptySet();
  }

  public List<Injectee> getInjectees() {
    return Collections.emptyList();
  }

  public Long getFactoryServiceId() {
    return null;
  }

  public Long getFactoryLocatorId() {
    return null;
  }

  /**
   * {@link InvocationHandler} implementation that allows strongly-typed access to the
   * configuration.
   *
   * <p>TODO: it might be a great performance improvement to have APT generate code that does this
   * during the development time by looking at the interface.
   */
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // serve java.lang.Object methods by ourselves
    Class<?> clazz = method.getDeclaringClass();
    if (clazz == Object.class) {
      try {
        return method.invoke(this, args);
      } catch (InvocationTargetException e) {
        throw e.getTargetException();
      }
    }

    if (method.getAnnotation(DuckTyped.class) != null) {
      return invokeDuckMethod(method, proxy, args);
    }
    if (method.getAnnotation(ConfigExtensionMethod.class) != null) {
      ConfigExtensionMethod cem = method.getAnnotation(ConfigExtensionMethod.class);
      ConfigExtensionHandler handler =
          (ConfigExtensionHandler)
              ((cem.value() != null)
                  ? getServiceLocator().getService(ConfigExtensionHandler.class, cem.value())
                  : getServiceLocator().getService(ConfigExtensionHandler.class));
      return invokeConfigExtensionMethod(handler, this, model.getProxyType(), args);
    }

    ConfigModel.Property p = model.toProperty(method);
    if (p == null)
      throw new IllegalArgumentException("No corresponding property found for method: " + method);

    if (args == null || args.length == 0) {
      // getter
      return getter(p, method.getGenericReturnType());
    } else {
      throw new PropertyVetoException(
          "Instance of "
              + getImplementation()
              + " named '"
              + getKey()
              + "' is not locked for writing when invoking method "
              + method.getName()
              + " you must use transaction semantics to access it.",
          null);
    }
  }

  /**
   * Another version of the {@link #invoke(Object, Method, Object[])}, but instead of {@link Method}
   * object, it takes the method name and argument types.
   */
  public Object invoke(Object proxy, String methodName, Class[] argTypes, Object[] args)
      throws Throwable {
    return invoke(proxy, getProxyType().getMethod(methodName, argTypes), args);
  }

  /**
   * Invoke the user defined static method in the nested "Duck" class so that the user can define
   * convenience methods on the config beans.
   */
  Object invokeDuckMethod(Method method, Object proxy, Object[] args) throws Exception {
    Method duckMethod = model.getDuckMethod(method);

    Object[] duckArgs;
    if (args == null) {
      duckArgs = new Object[] {proxy};
    } else {
      duckArgs = new Object[args.length + 1];
      duckArgs[0] = proxy;
      System.arraycopy(args, 0, duckArgs, 1, args.length);
    }

    try {
      return duckMethod.invoke(null, duckArgs);
    } catch (InvocationTargetException e) {
      Throwable t = e.getTargetException();
      if (t instanceof Exception) throw (Exception) t;
      if (t instanceof Error) throw (Error) t;
      throw e;
    }
  }
  /**
   * Invoke the user defined static method in the nested "Duck" class so that the user can define
   * convenience methods on the config beans.
   */
  <T extends ConfigBeanProxy> T invokeConfigExtensionMethod(
      ConfigExtensionHandler<T> handler, Dom dom, Class<T> clazz, Object[] args) throws Exception {

    return handler.handleExtension(dom, clazz, args);
  }

  protected Object getter(ConfigModel.Property target, Type t) {
    return target.get(this, t);
  }

  protected void setter(ConfigModel.Property target, Object value) throws Exception {
    target.set(this, value);
  }

  public static String convertName(String name) {
    // first, trim off the prefix
    for (String p : PROPERTY_PREFIX) {
      if (name.startsWith(p)) {
        name = name.substring(p.length());
        break;
      }
    }

    // tokenize by finding 'x|X' and 'X|Xx' then insert '-'.
    StringBuilder buf = new StringBuilder(name.length() + 5);
    for (String t : TOKENIZER.split(name)) {
      if (buf.length() > 0) buf.append('-');
      buf.append(t.toLowerCase());
    }
    return buf.toString();
  }

  /** Used to tokenize the property name into XML name. */
  static final Pattern TOKENIZER;

  private static String split(String lookback, String lookahead) {
    return "((?<=" + lookback + ")(?=" + lookahead + "))";
  }

  private static String or(String... tokens) {
    StringBuilder buf = new StringBuilder();
    for (String t : tokens) {
      if (buf.length() > 0) buf.append('|');
      buf.append(t);
    }
    return buf.toString();
  }

  static {
    String pattern =
        or(
            split("x", "X"), // AbcDef -> Abc|Def
            split("X", "Xx"), // USArmy -> US|Army
            // split("\\D","\\d"), // SSL2 -> SSL|2
            split("\\d", "\\D") // SSL2Connector -> SSL|2|Connector
            );
    pattern = pattern.replace("x", "\\p{Lower}").replace("X", "\\p{Upper}");
    TOKENIZER = Pattern.compile(pattern);
  }

  static final String[] PROPERTY_PREFIX = new String[] {"get", "set", "is", "has"};

  /**
   * This is how we inject the configuration into the created object.
   *
   * <p>There are two kinds &mdash; one where @{@link Configured} is put on a bean and that is
   * placedinto Habitat, and the other is where @{@link Configured} is on {@link ConfigBeanProxy}
   * subtype, in which case the proxy to {@link Dom} will be placed into the habitat.
   */
  @SuppressWarnings("unchecked")
  protected Creator createCreator(Class c) {
    Map<String, List<String>> metadata = getMetadata();
    Creator creator = new CreatorImpl(c, getServiceLocator());

    return (ConfigBeanProxy.class.isAssignableFrom(c)
        ? new DomProxyCreator(c, this)
        : new ConfiguredCreator(creator, this));
  }

  public static <T extends Annotation> T digAnnotation(Class<?> target, Class<T> annotationType) {
    return digAnnotation(target, annotationType, new ArrayList<Class<? extends Annotation>>());
  }

  public static <T extends Annotation> T digAnnotation(
      Class<?> target, Class<T> annotationType, List<Class<? extends Annotation>> visited) {
    T result = target.getAnnotation(annotationType);
    if (result == null) {
      for (Annotation a : target.getAnnotations()) {
        if (!visited.contains(a.annotationType())) {
          visited.add(a.annotationType());
          result = digAnnotation(a.annotationType(), annotationType, visited);
          if (result != null) {
            return result;
          }
        }
      }
    }
    return result;
  }

  /** Used by the parser to set a list of children. */
  /*package*/ void setChildren(List<Child> children) {
    this.children = children;
  }

  /**
   * Returns the map of attributes names and values for attributes which value is neither null or
   * the default value. These attributes are considered having a non default value and must be
   * written out.
   *
   * @return map of attributes indexed by name that must be persisted
   */
  private Map<String, String> attributesToWrite() {

    Map<String, String> attributesToWrite = new HashMap<String, String>();
    Map<String, String> localAttr = new HashMap<String, String>(attributes);
    for (Map.Entry<String, String> a : localAttr.entrySet()) {
      ConfigModel.AttributeLeaf am = model.attributes.get(a.getKey());
      String dv = am.getDefaultValue();
      if (dv == null || !dv.equals(a.getValue())) {
        attributesToWrite.put(a.getKey(), a.getValue());
      }
    }
    return attributesToWrite;
  }

  /**
   * Returns true if this element is empty meaning all their attributes have default values and it
   * has no descendants.
   *
   * @return true if the element is empty, false otherwise
   */
  private boolean isEmpty() {
    Map<String, String> attributesToWrite = attributesToWrite();

    if (!attributesToWrite.isEmpty()) {
      return false;
    }

    // if we have children, we are not empty.
    return children.isEmpty();
  }

  /**
   * Writes back this element.
   *
   * @param tagName The tag name of this element to be written. If null, this DOM node must be a
   *     global element and its tag name will be used.
   * @param w Receives XML infoset stream.
   */
  public void writeTo(String tagName, XMLStreamWriter w) throws XMLStreamException {
    if (tagName == null) tagName = model.tagName;
    if (tagName == null)
      throw new IllegalArgumentException(
          "Trying t write a local element " + this + " w/o a tag name");

    /**
     * If someone has explicitly called the skipFromXml then dont write the element to domain.xml
     */
    if (!writeToXml) {
      return;
    }
    w.writeStartElement(tagName);

    for (Map.Entry<String, String> attributeToWrite : attributesToWrite().entrySet()) {
      w.writeAttribute(attributeToWrite.getKey(), attributeToWrite.getValue());
    }

    List<Child> localChildren = new ArrayList<Child>(children);
    for (Child c : localChildren) c.writeTo(w);

    w.writeEndElement();
  }

  public void release() {
    if (domDescriptor
        != null) { // children added via createProxy are not registered in serviceLocator
      ServiceLocatorUtilities.removeOneDescriptor(getHabitat(), domDescriptor, true);
    }
    listeners.clear();
  }

  Set<ConfigListener> listeners = new HashSet<ConfigListener>();

  public void addListener(ConfigListener listener) {
    if (listener == null) {
      throw new IllegalArgumentException("Listener cannot be null");
    }
    listeners.add(listener);
  }

  public boolean removeListener(ConfigListener listener) {
    return listeners.remove(listener);
  }

  Collection<ConfigListener> getListeners() {
    return listeners;
  }

  private boolean isCacheSet = false;
  private Object cache;

  /* (non-Javadoc)
   * @see org.glassfish.hk2.api.SingleCache#getCache()
   */
  @Override
  public Object getCache() {
    return cache;
  }

  /* (non-Javadoc)
   * @see org.glassfish.hk2.api.SingleCache#isCacheSet()
   */
  @Override
  public boolean isCacheSet() {
    return isCacheSet;
  }

  /* (non-Javadoc)
   * @see org.glassfish.hk2.api.SingleCache#setCache(java.lang.Object)
   */
  @Override
  public void setCache(Object cacheMe) {
    cache = cacheMe;
    isCacheSet = true;
  }

  /* (non-Javadoc)
   * @see org.glassfish.hk2.api.SingleCache#releaseCache()
   */
  @Override
  public void releaseCache() {
    isCacheSet = false;
    cache = null;
  }

  /* (non-Javadoc)
   * @see org.glassfish.hk2.api.ActiveDescriptor#create(org.glassfish.hk2.api.ServiceHandle)
   */
  @Override
  public Object create(ServiceHandle root) {
    return createProxy();
  }

  public Object get() {
    if (serviceHandle == null) {
      return null;
    }

    Object result = serviceHandle.getService();

    return result;
  }

  public ServiceLocator getServiceLocator() {
    return habitat;
  }

  private static DescriptorImpl createDescriptor(
      String typeName, HK2Loader cl, Map<String, List<String>> metadata) {
    DescriptorImpl retVal = new DescriptorImpl();

    retVal.setImplementation(typeName);
    retVal.addAdvertisedContract(typeName);
    retVal.setLoader(cl);
    retVal.setMetadata(metadata);

    return retVal;
  }

  public int hashCode() {
    return System.identityHashCode(this);
  }

  public boolean equals(Object o) {
    return this == o;
  }

  private static class DomProxyComputable implements Computable<Class<?>, ConfigBeanProxy> {
    private final Dom dom;

    private DomProxyComputable(Dom dom) {
      this.dom = dom;
    }

    @Override
    public ConfigBeanProxy compute(final Class<?> proxyType) throws ComputationErrorException {

      ClassLoader cl;
      if (System.getSecurityManager() != null) {
        cl =
            AccessController.doPrivileged(
                new PrivilegedAction<ClassLoader>() {
                  @Override
                  public ClassLoader run() {
                    return proxyType.getClassLoader();
                  }
                });
      } else {
        cl = proxyType.getClassLoader();
      }

      ConfigBeanProxy retVal =
          (ConfigBeanProxy) Proxy.newProxyInstance(cl, new Class[] {proxyType}, dom);

      return retVal;
    }
  }
}
Example #5
0
 /**
  * Returns the list of attributes with a value on this config instance. This is by definition a
  * subset of the attributes names as known to the model {@see ConfigModel.getAttributeNames}.
  *
  * @return list of attributes names which have values on this config instance
  */
 public Set<String> getAttributeNames() {
   return Collections.unmodifiableSet(attributes.keySet());
 }