Return-Path: X-Original-To: apmail-ace-commits-archive@www.apache.org Delivered-To: apmail-ace-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B7A66DF21 for ; Thu, 5 Jul 2012 12:12:09 +0000 (UTC) Received: (qmail 99685 invoked by uid 500); 5 Jul 2012 12:12:09 -0000 Delivered-To: apmail-ace-commits-archive@ace.apache.org Received: (qmail 99621 invoked by uid 500); 5 Jul 2012 12:12:09 -0000 Mailing-List: contact commits-help@ace.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ace.apache.org Delivered-To: mailing list commits@ace.apache.org Received: (qmail 99502 invoked by uid 99); 5 Jul 2012 12:12:08 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Jul 2012 12:12:08 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Jul 2012 12:12:01 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 8B5992388B42 for ; Thu, 5 Jul 2012 12:10:36 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1357570 [18/34] - in /ace/sandbox/marrs: cnf/ cnf/ext/ cnf/lib/ cnf/releaserepo/ cnf/repo/ cnf/repo/.obrcache/ cnf/repo/.obrcache/http%3A%2F%2Fbundles.bndtools.org.s3.amazonaws.com%2Fcom.jcraft.jsch/ cnf/repo/.obrcache/http%3A%2F%2Fbundles... Date: Thu, 05 Jul 2012 12:10:06 -0000 To: commits@ace.apache.org From: marrs@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120705121036.8B5992388B42@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositoryObjectImpl.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositoryObjectImpl.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositoryObjectImpl.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositoryObjectImpl.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,588 @@ +package org.apache.ace.client.repository.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.apache.ace.client.repository.Associatable; +import org.apache.ace.client.repository.Association; +import org.apache.ace.client.repository.RepositoryObject; +import org.apache.ace.client.repository.RepositoryUtil; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventHandler; + +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * Represents a value-object as is part of the repository.
+ * It stores the 'member' values of the repository object, and allows putting tags on this object by using put() + * and remove(). It 'looks' like a dictionary to allow filtering of it, using an ldap filter. + */ +public class RepositoryObjectImpl extends Dictionary implements RepositoryObject, EventHandler { + private final Map m_attributes = new HashMap(); + private final Map m_tags = new HashMap(); + @SuppressWarnings("unchecked") + private final Map> m_associations = new HashMap>(); + private final ChangeNotifier m_notifier; + private final String m_xmlNode; + + private volatile boolean m_deleted = false; + private volatile boolean m_busy = false; + + public RepositoryObjectImpl(ChangeNotifier notifier, String xmlNode) { + this((Map) null, null, notifier, xmlNode); + } + + public RepositoryObjectImpl(Map attributes, ChangeNotifier notifier, String xmlNode) { + this(attributes, null, notifier, xmlNode); + } + + public RepositoryObjectImpl(HierarchicalStreamReader reader, ChangeNotifier notifier, String xmlNode) { + this(readMap(reader), readMap(reader), notifier, xmlNode); + readCustom(reader); + } + + public RepositoryObjectImpl(Map attributes, Map tags, ChangeNotifier notifier, String xmlNode) { + m_xmlNode = xmlNode; + if (attributes != null) { + m_attributes.putAll(attributes); + } + if (tags != null) { + m_tags.putAll(tags); + } + if (notifier == null) { + throw new IllegalArgumentException(); + } + m_notifier = notifier; + } + + protected void notifyChanged(Properties props) { + if (props == null) { + props = new Properties(); + } + props.put(EVENT_ENTITY, this); + m_notifier.notifyChanged(TOPIC_CHANGED_SUFFIX, props, m_busy); + } + + /** + * Returns an enumeration of the values in this object's dictionary. + */ + @Override + public Enumeration elements() { + synchronized (m_attributes) { + return new ValuesEnumeration(); + } + } + + /** + * Gets the object associated with this key. Will return null when the key is not used; if the key is available for both the + * tags and the object's basic information, an array of two Strings will be returned. + */ + @Override + public Object get(Object key) { + synchronized (m_attributes) { + String manifest = m_attributes.get(key); + String tag = m_tags.get(key); + + if (manifest == null) { + return tag; + } + else if (tag == null) { + return manifest; + } + else { + return new String[] { tag, manifest }; + } + } + } + + /** + * Return whether the dictionary is empty. + */ + @Override + public boolean isEmpty() { + synchronized (m_attributes) { + return m_attributes.isEmpty() && m_tags.isEmpty(); + } + } + + /** + * Returns an enumeration of the keys in this object. + */ + @Override + public Enumeration keys() { + synchronized (m_attributes) { + Set keys = new HashSet(); + keys.addAll(m_attributes.keySet()); + keys.addAll(m_tags.keySet()); + return new KeysEnumeration(keys.iterator()); + } + } + + /** + * Unsupported operation. + * + * @throws UnsupportedOperationException always + */ + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(); + } + + /** + * Unsupported operation. + * + * @throws UnsupportedOperationException always + */ + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the number of keys in both this object's tags, and its 'member' keys. + */ + @Override + public int size() { + synchronized (m_attributes) { + Set keys = new HashSet(); + keys.addAll(m_attributes.keySet()); + keys.addAll(m_tags.keySet()); + return keys.size(); + } + } + + public String addAttribute(String key, String value) { + for (String s : getDefiningKeys()) { + if (s.equals(key)) { + throw new UnsupportedOperationException("The defining attribute " + key + " is not allowed to be changed."); + } + } + synchronized (m_attributes) { + ensureCurrent(); + notifyChanged(null); + return m_attributes.put(key, value); + } + } + + public String addTag(String key, String value) { + synchronized (m_attributes) { + ensureCurrent(); + notifyChanged(null); + return m_tags.put(key, value); + } + } + + public String getAttribute(String key) { + synchronized (m_attributes) { + return m_attributes.get(key); + } + } + + public String getTag(String key) { + synchronized (m_attributes) { + return m_tags.get(key); + } + } + + public Enumeration getAttributeKeys() { + synchronized (m_attributes) { + return new KeysEnumeration(new HashSet(m_attributes.keySet()).iterator()); + } + } + + public Dictionary getDictionary() { + return this; + } + + public Enumeration getTagKeys() { + synchronized (m_attributes) { + return new KeysEnumeration(new HashSet(m_tags.keySet()).iterator()); + } + } + + @SuppressWarnings("unchecked") + public void add(Association association, Class clazz) { + synchronized (m_associations) { + List associations = m_associations.get(clazz); + if (associations == null) { + associations = new ArrayList(); + m_associations.put(clazz, associations); + } + associations.add(association); + } + } + + @SuppressWarnings("unchecked") + public void remove(Association association, Class clazz) { + synchronized (m_associations) { + List associations = m_associations.get(clazz); + if (associations != null) { + associations.remove(association); + } + } + } + + @SuppressWarnings("unchecked") + public List getAssociations(Class clazz) { + synchronized (m_associations) { + List result = new ArrayList(); + List associations = m_associations.get(clazz); + if (associations != null) { + for (Association association : associations) { + List otherSide = association.getTo(this); + if (otherSide != null) { + // If the other side is null, the association + // is not satisfied. + result.addAll(otherSide); + } + } + } + return result; + } + } + + @SuppressWarnings("unchecked") + public boolean isAssociated(Object obj, Class clazz) { + synchronized (m_associations) { + if (obj == null) { + return false; + } + List associations = m_associations.get(clazz); + if (associations != null) { + for (Association association : associations) { + if (association.getTo(this).contains(obj)) { + return true; + } + } + } + return false; + } + } + + @SuppressWarnings("unchecked") + public List getAssociationsWith(Associatable other, Class clazz, Class associationType) { + List result = new ArrayList(); + synchronized (m_associations) { + if (other == null) { + return result; + } + List associations = m_associations.get(clazz); + if (associations != null) { + for (Association association : associations) { + if (association.getTo(this).contains(other)) { + result.add((A) association); + } + } + } + return result; + } + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object o) { + synchronized(m_attributes) { + if ((o == null) || !(getClass().isInstance(o))) { + return false; + } + if (m_attributes.size() == 0) { + return this == o; + } + for (String s : getDefiningKeys()) { + String ourAttribute = m_attributes.get(s); + String otherAttribute = (String) ((RepositoryObjectImpl) o).m_attributes.get(s); + if ((ourAttribute == null) && (otherAttribute != null)) { + return false; + } + else if ((ourAttribute != null) && (otherAttribute == null)) { + return false; + } + else if ((ourAttribute == null) && (otherAttribute == null)) { + continue; + } + else if (!otherAttribute.equals(ourAttribute)) { + return false; + } + } + return true; + } + } + + /** + * Returns an array of keys which are considered to be defining for this object's + * attributes. The basic implementation just uses every key that is used; this + * function is intended to be overridden by deriving classes, providing a real + * set. + * + * Note that the array returned from this function should be read from only; + * writing to it will change the state of the object. + */ + String[] getDefiningKeys() { + return m_attributes.keySet().toArray(new String[m_attributes.size()]); + } + + @Override + public int hashCode() { + synchronized(m_attributes) { + return m_attributes.hashCode(); + } + } + + void marshal(HierarchicalStreamWriter writer) { + synchronized (m_attributes) { + writer.startNode(m_xmlNode); + writeMap(writer, m_attributes, "attributes"); + writeMap(writer, m_tags, "tags"); + writeCustom(writer); + writer.endNode(); + } + } + + /** + * This method is intended to be overridden by deriving classes, to read custom information + * from the XML representation of this object. This method should end with the writer at + * the same 'level' as before, that is, using equally many moveDown() and moveUp() calls. + * @param reader A reader to read from the XML stream. + */ + protected void readCustom(HierarchicalStreamReader reader) { + // do nothing + } + + /** + * This method is intended to be overridden by deriving classes, to write custom information + * to the XML representation of this object. This method should end with the writer at + * the same 'level' as before, that is, using equally many moveDown() and moveUp() calls. + * @param writer A writer to write to the XML stream. + */ + protected void writeCustom(HierarchicalStreamWriter writer) { + // do nothing + } + + static void writeMap(HierarchicalStreamWriter writer, Map entries, String name) { + writer.startNode(name); + for (Map.Entry entry : entries.entrySet()) { + if (entry.getKey() != null && entry.getValue() != null) { + // we only write values that have non null keys and values + writer.startNode(entry.getKey()); + writer.setValue(entry.getValue()); + writer.endNode(); + } + } + writer.endNode(); + } + + static Map readMap(HierarchicalStreamReader reader) { + reader.moveDown(); + Map result = new HashMap(); + while (reader.hasMoreChildren()) { + reader.moveDown(); + result.put(reader.getNodeName(), reader.getValue()); + reader.moveUp(); + } + reader.moveUp(); + return result; + } + + /** + * Helper function to check the existence of keys in a map. Each attribute is required + * to be non-empty. + * @param attributes A map of attribute-value combinations. + * @param mandatory An array of attributes which have to be present in the map. + * @return attributes if this map meets the requirements. If not, IllegalArgumentException + * will be thrown. + */ + static Map checkAttributes(Map attributes, String... mandatory) { + boolean[] booleans = new boolean[mandatory.length]; + Arrays.fill(booleans, false); + return checkAttributes(attributes, mandatory, booleans); + } + + /** + * Helper function to check the existence of keys in a map. + * @param attributes A map of attribute-value combinations. + * @param mandatory An array of attributes which have to be present in the map. + * @param emptyAttributeAllowed An array of booleans, indicating which of the attributes is allowed + * to be equal. Items in this array are matched by index on the elements in mandatory, so + * the length should be equal. + * @return attributes if this map meets the requirements. If not, IllegalArgumentException + * will be thrown. + */ + static Map checkAttributes(Map attributes, String[] mandatory, boolean[] emptyAttributeAllowed) { + if (!(mandatory.length == emptyAttributeAllowed.length)) { + throw new IllegalArgumentException("The length of the mandatory- and the emptyAttributeAllow-array should be equal."); + } + for (int i = 0; i < mandatory.length; i++) { + String attribute = mandatory[i]; + boolean emptyAllowed = emptyAttributeAllowed[i]; + if (!attributes.containsKey(attribute)) { + throw new IllegalArgumentException(attribute + " is a mandatory attribute."); + } + else if ((!emptyAllowed) && (attributes.get(attribute).length() == 0)) { + throw new IllegalArgumentException(attribute + " is not allowed to be empty."); + } + } + return attributes; + } + + public String getXmlNode() { + return m_xmlNode; + } + + public void handleEvent(Event e) { + } + + void setDeleted() { + m_deleted = true; + } + + public boolean isDeleted() { + return m_deleted; + } + + void ensureNotDeleted() { + if (isDeleted()) { + throw new IllegalStateException("This object is deleted, and should no longer be used."); + } + } + + void setBusy(boolean busy) { + // setBusy should 'wait' until all altering operations have passed. To do so, + // it gets the locks for the other 'set' objects. Once it has all these locks, + // we are sure no thread is performing a set-action. + synchronized(m_associations) { + synchronized(m_attributes) { + if (m_busy && !busy) { + m_associations.notifyAll(); + m_attributes.notifyAll(); + } + m_busy = busy; + } + } + } + + public boolean getBusy() { + return m_busy; + } + + // NEVER CALL WITHOUT m_attributes lock + private void ensureNotBusy() { + boolean interrupted = false; + while (m_busy) { + try { + m_attributes.wait(); + } + catch (InterruptedException e) { + interrupted = true; + } + } + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + + void ensureCurrent() { + ensureNotDeleted(); + ensureNotBusy(); + } + + public String getDefinition() { + StringBuilder result = new StringBuilder(); + + result.append(getXmlNode().replaceAll("\\\\", "\\\\\\\\").replaceAll("-", "\\-")); + + for (String key : getDefiningKeys()) { + String value = getAttribute(key); + if (value != null) { + result.append("-") + .append(key.replaceAll("\\\\", "\\\\\\\\").replaceAll("-", "\\-").replaceAll("\\/", "/")) + .append("-") + .append(value.replaceAll("\\\\", "\\\\\\\\").replaceAll("-", "\\-").replaceAll("\\/", "/")); + // About the /: the forward slash will be used by the preference admin, but we don't want that. + } + } + + return result.toString(); + } + + /** + * Creates a filter string for use in associations, optionally with some + * additional properties. The basic implementation will use all getDefiningKeys. + * @param properties Properties indicating specifics of the filter to be created. + * @return A string representation of a filter, for use in Associations. + */ + public String getAssociationFilter(Map properties) { + StringBuilder filter = new StringBuilder("(&"); + + for (String key : getDefiningKeys()) { + filter.append("(" + key + "=" + RepositoryUtil.escapeFilterValue(getAttribute(key)) + ")"); + } + + filter.append(")"); + + return filter.toString(); + } + + /** + * Determines the cardinality of this endpoint of an association, given + * the passed properties. + * @param properties Properties indicating specifics of this endpoint. + * @return The necessary cardinality. + */ + public int getCardinality(Map properties) { + return Integer.MAX_VALUE; + } + + /** + * Returns a Comparator for this type of object, suitable + * for the endpoint properties that are passed. + * @return A Comparator for this type of object + */ + public Comparator getComparator() { + return null; + } + + /** + * Helper class that implements an enumeration for use in keys(). + */ + private static class KeysEnumeration implements Enumeration { + private final Iterator m_iter; + + public KeysEnumeration(Iterator iter) { + m_iter = iter; + } + + public boolean hasMoreElements() { + return m_iter.hasNext(); + } + + public String nextElement() { + return m_iter.next(); + } + } + + /** + * Helper class that implements an enumeration for use in elements(). + */ + private class ValuesEnumeration implements Enumeration { + private final Enumeration m_iter = keys(); + + public boolean hasMoreElements() { + return m_iter.hasMoreElements(); + } + + public Object nextElement() { + return get(m_iter.nextElement()); + } + } +} Added: ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositoryPropertyResolver.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositoryPropertyResolver.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositoryPropertyResolver.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositoryPropertyResolver.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,82 @@ +package org.apache.ace.client.repository.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.ace.client.repository.RepositoryObject; +import org.apache.ace.client.repository.helper.PropertyResolver; +import org.apache.ace.client.repository.object.TargetObject; +import org.apache.ace.client.repository.object.FeatureObject; +import org.apache.ace.client.repository.object.DistributionObject; + +/** + * This PropertyResolver first tries to resolve the key in the + * current repository object. If not found, it looks for the key + * in its children. + */ +public class RepositoryPropertyResolver implements PropertyResolver { + + private final RepositoryObject m_repositoryObject; + + public RepositoryPropertyResolver(RepositoryObject repositoryObject) { + m_repositoryObject = repositoryObject; + } + + public String get(String key) { + return get(key, m_repositoryObject); + } + + private String get(String key, RepositoryObject ro) { + // Is it in this object? + String result = findKeyInObject(ro, key); + if (result != null) { + return result; + } + + // Is it in one of the children? + List children = getChildren(ro); + for (RepositoryObject child : children) { + result = findKeyInObject(child, key); + if (result != null) { + return result; + } + } + + // Not found yet? then continue to the next level recursively. + for (RepositoryObject child : children) { + result = get(key, child); + if (result != null) { + return result; + } + } + return result; + } + + protected List getChildren() { + return getChildren(m_repositoryObject); + } + + protected List getChildren(RepositoryObject ob) { + if (ob instanceof TargetObject) { + return ((TargetObject) ob).getDistributions(); + } + else if (ob instanceof DistributionObject) { + return ((DistributionObject) ob).getFeatures(); + } + else if (ob instanceof FeatureObject) { + return ((FeatureObject) ob).getArtifacts(); + } + return new ArrayList(); + } + + private String findKeyInObject(RepositoryObject ro, String key) { + String result; + if ((result = ro.getAttribute(key)) != null) { + return result; + } + if ((result = ro.getTag(key)) != null) { + return result; + } + return null; + } +} Added: ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositorySerializer.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositorySerializer.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositorySerializer.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositorySerializer.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ace.client.repository.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * Helper class that takes a RepositorySet
+ * TODO We might move out xstream at some time in the future; before that + * time, it could be a smart idea to wrap xstream's writer in a delegate + * object, so this will not require changes to the repositories + * and objects. + */ +class RepositorySerializer implements Converter { + @SuppressWarnings("unchecked") + private final Map m_tagToRepo = new HashMap(); + + private final RepositorySet m_set; + + private final XStream m_stream; + + @SuppressWarnings("unchecked") + RepositorySerializer(RepositorySet set) { + m_set = set; + for (ObjectRepositoryImpl repo : m_set.getRepos()) { + m_tagToRepo.put(repo.getXmlNode(), repo); + } + m_stream = new XStream(); + m_stream.alias("repository", getClass()); + m_stream.registerConverter(this); + } + + @SuppressWarnings("unchecked") + public void marshal(Object target, HierarchicalStreamWriter writer, MarshallingContext context) { + for (ObjectRepositoryImpl repo : m_set.getRepos()) { + repo.marshal(writer); + } + } + + @SuppressWarnings("unchecked") + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + while(reader.hasMoreChildren()) { + reader.moveDown(); + String nodeName = reader.getNodeName(); + ObjectRepositoryImpl o = m_tagToRepo.get(nodeName); + o.unmarshal(reader); + reader.moveUp(); + } + return this; + } + + @SuppressWarnings("unchecked") + public boolean canConvert(Class target) { + return target == getClass(); + } + + @SuppressWarnings("unchecked") + public void toXML(OutputStream out) { + for (ObjectRepositoryImpl repo : m_set.getRepos()) { + repo.setBusy(true); + } + try { + m_stream.toXML(this, out); + } + finally { + // Ensure all busy flags are reset at all times... + for (ObjectRepositoryImpl repo : m_set.getRepos()) { + repo.setBusy(false); + } + } + } + + /** + * Reads the repositories with which this RepositoryRoot had been initialized with from the + * given XML file. + * @param in The input stream. + */ + @SuppressWarnings("unchecked") + public void fromXML(InputStream in) { + // The repositories get cleared, since a user *could* add stuff before + // checking out. + for (ObjectRepositoryImpl repo : m_set.getRepos()) { + repo.setBusy(true); + repo.removeAll(); + } + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + if (in.available() > 0) { + m_stream.fromXML(in, this); + } + } + catch (IOException e) { + // This means the stream has been closed before we got it. + // Since the repository is now in a consistent state, just move on. + e.printStackTrace(); + } + finally { + Thread.currentThread().setContextClassLoader(cl); + // Ensure all busy flags are reset at all times... + for (ObjectRepositoryImpl repo : m_set.getRepos()) { + repo.setBusy(false); + } + } + } +} Added: ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositorySet.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositorySet.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositorySet.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/RepositorySet.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,359 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ace.client.repository.impl; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.ace.client.repository.ObjectRepository; +import org.apache.ace.client.repository.RepositoryAdmin; +import org.apache.ace.client.repository.RepositoryObject; +import org.apache.ace.client.repository.RepositoryObject.WorkingState; +import org.apache.ace.client.repository.SessionFactory; +import org.apache.ace.repository.ext.CachedRepository; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventConstants; +import org.osgi.service.event.EventHandler; +import org.osgi.service.log.LogService; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; +import org.osgi.service.useradmin.User; + +/** + * This class encapsulates a set of ObjectRepositoryImpls, and manages + * auxiliary information and functionality that is linked to that set. + */ +class RepositorySet { + private final static String PREFS_LOCAL_WORKING_STATE = "workingState"; + private final static String PREFS_LOCAL_WORKING_STATE_VALUE = "workingStateValue"; + private final static String PREFS_LOCAL_FILE_VERSION = "version"; + + private final User m_user; + private final Preferences m_prefs; + @SuppressWarnings("unchecked") + private final ObjectRepositoryImpl[] m_repos; + private final CachedRepository m_repository; + private final String m_name; + private final boolean m_writeAccess; + private final ConcurrentMap m_workingState; + private final ChangeNotifier m_notifier; + private final LogService m_log; + + private volatile ServiceRegistration m_modifiedHandler; + + /* ******** + * Basics + * ********/ + + @SuppressWarnings("unchecked") + /** + * Creates a new RepositorySet. Notes: + *
    + *
  • When storing association repositories in repos, it is wise to + * put these in last. This has to do with the method of deserialization, which assumes + * that endpoints of an association are available at the time of deserialization.
  • + *
+ */ + RepositorySet(ChangeNotifier notifier, LogService log, User user, Preferences prefs, ObjectRepositoryImpl[] repos, CachedRepository repository, String name, boolean writeAccess) { + m_workingState = new ConcurrentHashMap(); + m_notifier = notifier; + m_log = log; + m_user = user; + m_prefs = prefs; + m_repos = repos; + m_repository = repository; + m_name = name; + m_writeAccess = writeAccess; + } + + void unregisterHandler() { + m_modifiedHandler.unregister(); + m_modifiedHandler = null; + } + + boolean writeAccess() { + return m_writeAccess; + } + + boolean isModified() { + for (WorkingState workingState : m_workingState.values()) { + if (!WorkingState.Unchanged.equals(workingState)) { + return true; + } + } + return false; + } + + User getUser() { + return m_user; + } + + @SuppressWarnings("unchecked") + ObjectRepositoryImpl[] getRepos() { + return m_repos; + } + + String getName() { + return m_name; + } + + /* ******** + * Preferences + * ********/ + + void savePreferences() { + Preferences workingNode = m_prefs.node(PREFS_LOCAL_WORKING_STATE); + try { + workingNode.clear(); + } + catch (BackingStoreException e) { + // Something went wrong clearing the node... Too bad, this means we + // cannot store the properties. + m_log.log(LogService.LOG_WARNING, "Could not store all preferences for " + workingNode.absolutePath()); + e.printStackTrace(); + } + + for (Map.Entry entry : m_workingState.entrySet()) { + workingNode.node(entry.getKey().getDefinition()).put(PREFS_LOCAL_WORKING_STATE_VALUE, entry.getValue().toString()); + } + + m_prefs.putLong(PREFS_LOCAL_FILE_VERSION, m_repository.getMostRecentVersion()); + } + + /** + * Only call this after the repository has been deserialized. + */ + void loadPreferences() { + Preferences workingNode = m_prefs.node(PREFS_LOCAL_WORKING_STATE); + Map entries = new HashMap(); + // First, get all nodes and their workingstate. + try { + String defaultWorkingState = WorkingState.Unchanged.toString(); + + for (String node : workingNode.childrenNames()) { + String state = workingNode.node(node).get(PREFS_LOCAL_WORKING_STATE_VALUE, defaultWorkingState); + entries.put(node, WorkingState.valueOf(state)); + } + } + catch (BackingStoreException e) { + // Something went wrong reading from the store, just work with whatever we have in the map. + m_log.log(LogService.LOG_WARNING, "Could not load all preferences for " + workingNode.absolutePath()); + e.printStackTrace(); + } + // Then, go through all objects and check whether they match a definition we know. + // This prevents calling getDefinition more than once per object. + for (ObjectRepository repo : m_repos) { + for (RepositoryObject o : repo.get()) { + WorkingState state = entries.get(o.getDefinition()); + if (state != null) { + m_workingState.put(o, state); + } + } + } + } + + /* ******** + * Persistence + * ********/ + + boolean readLocal() throws IOException { + InputStream input = m_repository.getLocal(false /* fail */); + if (input.available() > 0) { + read(input); + return true; + } + else { + closeSafely(input); + return false; + } + } + + void read(InputStream input) { + new RepositorySerializer(this).fromXML(input); + closeSafely(input); + } + + void writeLocal() throws IOException { + final PipedInputStream input = new PipedInputStream(); + final PipedOutputStream output = new PipedOutputStream(input); + new Thread(new Runnable() { + public void run() { + new RepositorySerializer(RepositorySet.this).toXML(output); + try { + output.flush(); + output.close(); + } + catch (IOException e) { + // There is no way to tell this to the user, but the other side will + // notice that the pipe is broken. + } + } + }, "write(" + m_name + ")").start(); + m_repository.writeLocal(input); + input.close(); + } + + void commit() throws IOException { + if (!isCurrent()) { + throw new IllegalStateException("When committing the " + m_name + ", it should be current."); + } + writeLocal(); + if (m_writeAccess) { + m_repository.commit(); + } + resetModified(false); + } + + void checkout() throws IOException { + read(m_repository.checkout(false)); + resetModified(true); + } + + void revert() throws IOException { + m_repository.revert(); + read(m_repository.getLocal(false)); + resetModified(false); + } + + boolean isCurrent() throws IOException { + return m_repository.isCurrent(); + } + + @SuppressWarnings("unchecked") + void clearRepositories() { + for (ObjectRepositoryImpl repo : getRepos()) { + repo.setBusy(true); + } + + try { + for (ObjectRepositoryImpl repo : getRepos()) { + repo.removeAll(); + } + } + finally { + // Ensure all busy flags are reset at all times... + for (ObjectRepositoryImpl repo : getRepos()) { + repo.setBusy(false); + } + } + } + + /* ******** + * Event handling + * ********/ + + void registerHandler(BundleContext context, String sessionID, String... topics) { + if (m_modifiedHandler != null) { + throw new IllegalStateException("A handler is already registered; only one can be used at a time."); + } + Dictionary topic = new Hashtable(); + topic.put(EventConstants.EVENT_TOPIC, topics); + topic.put(EventConstants.EVENT_FILTER, "(" + SessionFactory.SERVICE_SID + "=" + sessionID + ")"); + m_modifiedHandler = context.registerService(EventHandler.class.getName(), new ModifiedHandler(), topic); + } + + WorkingState getWorkingState(RepositoryObject object) { + return m_workingState.get(object); + } + + int getNumberWithWorkingState(Class clazz, WorkingState state) { + int result = 0; + for (Map.Entry entry : m_workingState.entrySet()) { + if (clazz.isInstance(entry.getKey()) && state.equals(entry.getValue())) { + result++; + } + } + return result; + } + + private void resetModified(boolean fill) { + m_workingState.clear(); + if (fill) { + for (ObjectRepository repo : m_repos) { + for (RepositoryObject object : repo.get()) { + m_workingState.put(object, WorkingState.Unchanged); + } + } + } + m_notifier.notifyChanged(RepositoryAdmin.TOPIC_STATUSCHANGED_SUFFIX, null); + } + + private void closeSafely(Closeable resource) { + if (resource != null) { + try { + resource.close(); + } + catch (IOException e) { + // Ignored; not much we can do about this... + m_log.log(LogService.LOG_DEBUG, "Closing resource failed!", e); + } + } + } + + final class ModifiedHandler implements EventHandler { + + public void handleEvent(Event event) { + /* + * NOTE: if recalculating the state for every event turns out to be + * too expensive, we can cache the 'modified' state and not recalculate + * it every time. + */ + + boolean wasModified = isModified(); + + RepositoryObject object = (RepositoryObject) event.getProperty(RepositoryObject.EVENT_ENTITY); + String topic = event.getTopic(); + + WorkingState newState = WorkingState.Unchanged; + if (topic.endsWith("/ADDED")) { + newState = WorkingState.New; + } + else if (topic.endsWith("/CHANGED")) { + newState = WorkingState.Changed; + } + else if (topic.endsWith("/REMOVED")) { + newState = WorkingState.Removed; + } + + if (!newState.equals(m_workingState.get(object))) { + m_workingState.put(object, newState); + + Properties props = new Properties(); + props.put(RepositoryObject.EVENT_ENTITY, object); + m_notifier.notifyChanged(RepositoryAdmin.TOPIC_STATUSCHANGED_SUFFIX, props); + } + + if (!wasModified) { + m_notifier.notifyChanged(RepositoryAdmin.TOPIC_STATUSCHANGED_SUFFIX, null); + } + } + } +} Added: ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetObjectImpl.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetObjectImpl.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetObjectImpl.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetObjectImpl.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ace.client.repository.impl; + +import java.util.List; +import java.util.Map; + +import org.apache.ace.client.repository.object.TargetObject; +import org.apache.ace.client.repository.object.Distribution2TargetAssociation; +import org.apache.ace.client.repository.object.DistributionObject; + +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +/** + * Implementation class for the TargetObject. For 'what it does', see TargetObject, + * for 'how it works', see RepositoryObjectImpl. + */ +public class TargetObjectImpl extends RepositoryObjectImpl implements TargetObject { + private final static String XML_NODE = "target"; + + TargetObjectImpl(Map attributes, ChangeNotifier notifier) { + super(checkAttributes(attributes, KEY_ID), notifier, XML_NODE); + } + + TargetObjectImpl(Map attributes, Map tags, ChangeNotifier notifier) { + super(checkAttributes(attributes, KEY_ID), tags, notifier, XML_NODE); + } + + TargetObjectImpl(HierarchicalStreamReader reader, ChangeNotifier notifier) { + super(reader, notifier, XML_NODE); + if(getAttribute(KEY_AUTO_APPROVE) == null) { + addAttribute(KEY_AUTO_APPROVE, String.valueOf(false)); + } + } + + public List getDistributions() { + return getAssociations(DistributionObject.class); + } + + public String getID() { + return getAttribute(KEY_ID); + } + + public List getAssociationsWith(DistributionObject distribution) { + return getAssociationsWith(distribution, DistributionObject.class, Distribution2TargetAssociation.class); + } + + private static String[] DEFINING_KEYS = new String[] {KEY_ID}; + @Override + String[] getDefiningKeys() { + return DEFINING_KEYS; + } + + public boolean getAutoApprove() { + String value = getAttribute(KEY_AUTO_APPROVE); + if (value == null) { + return false; + } + else { + return Boolean.parseBoolean(value); + } + } + + public void setAutoApprove(boolean approve) { + addAttribute(KEY_AUTO_APPROVE, String.valueOf(approve)); + } +} Added: ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetPropertyResolver.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetPropertyResolver.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetPropertyResolver.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetPropertyResolver.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ace.client.repository.impl; + +import java.util.*; + +import org.apache.ace.client.repository.RepositoryObject; +import org.apache.ace.client.repository.helper.PropertyResolver; +import org.apache.ace.client.repository.object.TargetObject; + +/** + * Top-level property resolver, also able to return collections + * of distributions, features and artifacts linked to this target + * repository object. + */ +public class TargetPropertyResolver extends RepositoryPropertyResolver { + + public TargetPropertyResolver(TargetObject to) { + super(to); + } + + public Collection getDistributions() { + List list = new ArrayList(); + + List distributions = (List)getChildren(); + + for (RepositoryObject repo : distributions) { + list.add(new RepositoryPropertyResolver(repo)); + } + + return list; + } + + public Collection getFeatures() { + List list = new ArrayList(); + + Set features = new HashSet(); + + for (RepositoryObject repositoryObject : getChildren()) { + features.addAll(getChildren(repositoryObject)); + } + + for (RepositoryObject repo : features) { + list.add(new RepositoryPropertyResolver(repo)); + } + return list; + } + + public Collection getArtifacts() { + List list = new ArrayList(); + + Set artifacts = new HashSet(); + Set features = new HashSet(); + + for (RepositoryObject repositoryObject : getChildren()) { + features.addAll(getChildren(repositoryObject)); + } + + for (RepositoryObject repositoryObject : features) { + artifacts.addAll(getChildren(repositoryObject)); + } + + for (RepositoryObject repo : artifacts) { + list.add(new RepositoryPropertyResolver(repo)); + } + return list; + } +} Added: ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetRepositoryImpl.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetRepositoryImpl.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetRepositoryImpl.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/impl/TargetRepositoryImpl.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ace.client.repository.impl; + +import java.util.Map; + +import org.apache.ace.client.repository.object.TargetObject; +import org.apache.ace.client.repository.repository.TargetRepository; + +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +/** + * Implementation class for the TargetRepository. For 'what it does', see TargetRepository, + * for 'how it works', see ObjectRepositoryImpl. + */ +public class TargetRepositoryImpl extends ObjectRepositoryImpl implements TargetRepository { + private final static String XML_NODE = "targets"; + + public TargetRepositoryImpl(ChangeNotifier notifier) { + super(notifier, XML_NODE); + } + + @Override + TargetObjectImpl createNewInhabitant(Map attributes, Map tags) { + return new TargetObjectImpl(attributes, tags, this); + } + + @Override + TargetObjectImpl createNewInhabitant(Map attributes) { + return new TargetObjectImpl(attributes, this); + } + + @Override + TargetObjectImpl createNewInhabitant(HierarchicalStreamReader reader) { + return new TargetObjectImpl(reader, this); + } +} Added: ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/stateful/impl/StatefulTargetObjectImpl.java URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/stateful/impl/StatefulTargetObjectImpl.java?rev=1357570&view=auto ============================================================================== --- ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/stateful/impl/StatefulTargetObjectImpl.java (added) +++ ace/sandbox/marrs/org.apache.ace.client.repository.impl/src/org/apache/ace/client/repository/stateful/impl/StatefulTargetObjectImpl.java Thu Jul 5 12:09:30 2012 @@ -0,0 +1,717 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ace.client.repository.stateful.impl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Properties; + +import org.apache.ace.client.repository.Associatable; +import org.apache.ace.client.repository.Association; +import org.apache.ace.client.repository.object.ArtifactObject; +import org.apache.ace.client.repository.object.DeploymentArtifact; +import org.apache.ace.client.repository.object.DeploymentVersionObject; +import org.apache.ace.client.repository.object.TargetObject; +import org.apache.ace.client.repository.object.Distribution2TargetAssociation; +import org.apache.ace.client.repository.object.DistributionObject; +import org.apache.ace.client.repository.stateful.StatefulTargetObject; +import org.apache.ace.log.AuditEvent; +import org.apache.ace.log.LogDescriptor; +import org.apache.ace.log.LogEvent; + +/** + * A StatefulTargetObjectImpl uses the interface of a StatefulTargetObject, + * but delegates most of its calls to either an embedded TargetObject, or to its + * parent StatefulTargetRepository. Once created, it will handle its own lifecycle + * and remove itself once is existence is no longer necessary. + */ +public class StatefulTargetObjectImpl implements StatefulTargetObject { + private final StatefulTargetRepositoryImpl m_repository; + private final Object m_lock = new Object(); + private TargetObject m_targetObject; + private List m_processedAuditEvents = new ArrayList(); + private Map m_attributes = new HashMap(); + /** This boolean is used to suppress STATUS_CHANGED events during the creation of the object.*/ + private boolean m_inConstructor = true; + + /** + * Creates a new StatefulTargetObjectImpl. After creation, it will have the + * most recent data available, and has verified its own reasons for existence. + * @param repository The parent repository of this object. + * @param targetID A string representing a target ID. + */ + StatefulTargetObjectImpl(StatefulTargetRepositoryImpl repository, String targetID) { + m_repository = repository; + addStatusAttribute(KEY_ID, targetID); + updateTargetObject(false); + updateAuditEvents(false); + updateDeploymentVersions(null); + verifyExistence(); + m_inConstructor = false; + } + + public String approve() throws IllegalStateException { + try { + String version = m_repository.approve(getID()); + setStoreState(StoreState.Approved); + return version; + } + catch (IOException e) { + throw new IllegalStateException("Problem generating new deployment version: " + e.getMessage(), e); + } + } + + public List getAuditEvents() { + return m_repository.getAuditEvents(getID()); + } + + public String getCurrentVersion() { + DeploymentVersionObject version = m_repository.getMostRecentDeploymentVersion(getID()); + if (version == null) { + return StatefulTargetObject.UNKNOWN_VERSION; + } + else { + return version.getVersion(); + } + } + + public void register() throws IllegalStateException { + m_repository.register(getID()); + } + + public boolean isRegistered() { + synchronized(m_lock) { + return (m_targetObject != null); + } + } + + public TargetObject getTargetObject() { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject; + } + } + + public DeploymentArtifact[] getArtifactsFromDeployment() { + synchronized(m_lock) { + DeploymentVersionObject mostRecentDeploymentVersion = m_repository.getMostRecentDeploymentVersion(getID()); + if (mostRecentDeploymentVersion != null) { + return mostRecentDeploymentVersion.getDeploymentArtifacts(); + } + return new DeploymentArtifact[0]; + } + } + + public ArtifactObject[] getArtifactsFromShop() { + return m_repository.getNecessaryArtifacts(getID()); + } + + public boolean getLastInstallSuccess() { + synchronized(m_lock) { + return Boolean.parseBoolean(getStatusAttribute(KEY_LAST_INSTALL_SUCCESS)); + } + } + + public String getLastInstallVersion() { + synchronized(m_lock) { + return getStatusAttribute(KEY_LAST_INSTALL_VERSION); + } + } + + public void acknowledgeInstallVersion(String version) { + synchronized(m_lock) { + addStatusAttribute(KEY_ACKNOWLEDGED_INSTALL_VERSION, version); + if (version.equals(getStatusAttribute(KEY_LAST_INSTALL_VERSION))) { + setProvisioningState(ProvisioningState.Idle); + } + } + } + + public boolean needsApprove() { + return getStoreState() == StoreState.Unapproved; + } + + public ProvisioningState getProvisioningState() { + return ProvisioningState.valueOf(getStatusAttribute(KEY_PROVISIONING_STATE)); + } + + public RegistrationState getRegistrationState() { + return RegistrationState.valueOf(getStatusAttribute(KEY_REGISTRATION_STATE)); + } + + public StoreState getStoreState() { + String statusAttribute = getStatusAttribute(KEY_STORE_STATE); + if (statusAttribute != null) { + return StoreState.valueOf(statusAttribute); + } + return StoreState.New; + } + + /** + * Signals this object that there has been a change to the TargetObject it represents. + * @param needsVerify States whether this update should make the object check for its + * reasons for existence. + */ + void updateTargetObject(boolean needsVerify) { + synchronized(m_lock) { + m_targetObject = m_repository.getTargetObject(getID()); + determineRegistrationState(); + if (needsVerify) { + verifyExistence(); + } + } + } + + /** + * Signals this object that there has been a change to the auditlog which may interest + * this object. + * @param needsVerify States whether this update should make the object check for its + * reasons for existence. + */ + void updateAuditEvents(boolean needsVerify) { + synchronized(m_lock) { + determineProvisioningState(); + if (needsVerify) { + verifyExistence(); + } + } + } + + /** + * Signals this object that a new deployment version has been created in relation + * to the targetID this object manages. + */ + void updateDeploymentVersions(DeploymentVersionObject deploymentVersionObject) { + synchronized(m_lock) { + determineProvisioningState(); + determineStoreState(deploymentVersionObject); + } + } + + /** + * Based on the information about a TargetObject, the + * AuditEvents available, and the deployment information that + * the parent repository can give, determines the status of this target. + */ + void determineStatus() { + determineRegistrationState(); + determineProvisioningState(); + determineStoreState(null); + verifyExistence(); + } + + private void determineRegistrationState() { + synchronized(m_lock) { + if (!isRegistered()) { + setRegistrationState(RegistrationState.Unregistered); + } + else { + setRegistrationState(RegistrationState.Registered); + } + } + } + + private void determineStoreState(DeploymentVersionObject deploymentVersionObject) { + synchronized(m_lock) { + List fromShop = new ArrayList(); + ArtifactObject[] artifactsFromShop = m_repository.getNecessaryArtifacts(getID()); + DeploymentVersionObject mostRecentVersion; + if (deploymentVersionObject == null) { + mostRecentVersion = m_repository.getMostRecentDeploymentVersion(getID()); + } + else { + mostRecentVersion = deploymentVersionObject; + } + if (artifactsFromShop == null) { + if (mostRecentVersion == null) { + setStoreState(StoreState.New); + } + else { + setStoreState(StoreState.Unapproved); + } + return; + } + + for (ArtifactObject ao : artifactsFromShop) { + fromShop.add(ao.getURL()); + } + + List fromDeployment = new ArrayList(); + for (DeploymentArtifact da : getArtifactsFromDeployment()) { + fromDeployment.add(da.getDirective(DeploymentArtifact.DIRECTIVE_KEY_BASEURL)); + } + + if ((mostRecentVersion == null) && fromShop.isEmpty()) { + setStoreState(StoreState.New); + } + else if (fromShop.containsAll(fromDeployment) && fromDeployment.containsAll(fromShop)) { + // great, we have the same artifacts. But... do they need to be reprocessed? + for (ArtifactObject ao : artifactsFromShop) { + if (m_repository.needsNewVersion(ao, getID(), mostRecentVersion.getVersion())) { + setStoreState(StoreState.Unapproved); + return; + } + } + setStoreState(StoreState.Approved); + } + else { + setStoreState(StoreState.Unapproved); + } + } + } + + private void determineProvisioningState() { + /* + * This method gets all audit events it has not yet seen, and goes through them, backward + * in time, to find either and INSTALL or a COMPLETE event. A INSTALL event gives us a version, + * and tells us we're in InProgress. A COMPLETE tells gives us a version, and a success. The success + * will be stored, and also sets the state to OK or Failed, unless the version we found has already been + * acknowledged, the the state is set to Idle. Also, if there is no information whatsoever, we assume Idle. + */ + synchronized(m_lock) { + List allDescriptors = m_repository.getAllDescriptors(getID()); + List newDescriptors = m_repository.diffLogDescriptorLists(allDescriptors, m_processedAuditEvents); + + List newEvents = m_repository.getAuditEvents(newDescriptors); + for (int position = newEvents.size() - 1; position >= 0; position--) { + LogEvent event = newEvents.get(position); + + // TODO we need to check here if the deployment package is actually the right one + + String currentVersion = (String) event.getProperties().get(AuditEvent.KEY_VERSION); + if (event.getType() == AuditEvent.DEPLOYMENTCONTROL_INSTALL) { + addStatusAttribute(KEY_LAST_INSTALL_VERSION, currentVersion); + setProvisioningState(ProvisioningState.InProgress); + sendNewAuditlog(newDescriptors); + m_processedAuditEvents = allDescriptors; + return; + } + else if (event.getType() == AuditEvent.DEPLOYMENTADMIN_COMPLETE) { + addStatusAttribute(KEY_LAST_INSTALL_VERSION, currentVersion); + if ((currentVersion != null) && currentVersion.equals(getStatusAttribute(KEY_ACKNOWLEDGED_INSTALL_VERSION))) { + setProvisioningState(ProvisioningState.Idle); + sendNewAuditlog(newDescriptors); + m_processedAuditEvents = allDescriptors; + return; + } + else { + String value = (String) event.getProperties().get(AuditEvent.KEY_SUCCESS); + addStatusAttribute(KEY_LAST_INSTALL_SUCCESS, value); + if (Boolean.parseBoolean(value)) { + setProvisioningState(ProvisioningState.OK); + sendNewAuditlog(newDescriptors); + m_processedAuditEvents = allDescriptors; + return; + } + else { + setProvisioningState(ProvisioningState.Failed); + sendNewAuditlog(newDescriptors); + m_processedAuditEvents = allDescriptors; + return; + } + } + } + } + + if (m_processedAuditEvents.isEmpty()) { + setProvisioningState(ProvisioningState.Idle); + } + sendNewAuditlog(newDescriptors); + m_processedAuditEvents = allDescriptors; + } + } + + private void sendNewAuditlog(List events) { + // Check whether there are actually events in the list. + boolean containsData = false; + for (LogDescriptor l : events) { + containsData |= (l.getRangeSet().getHigh() != 0); + } + + if (containsData) { + Properties props = new Properties(); + props.put(StatefulTargetObject.KEY_AUDITEVENTS, events); + m_repository.notifyChanged(this, TOPIC_AUDITEVENTS_CHANGED, props); + } + } + + private void setRegistrationState(RegistrationState state) { + setStatus(KEY_REGISTRATION_STATE, state.toString()); + } + + private void setStoreState(StoreState state) { + setStatus(KEY_STORE_STATE, state.toString()); + } + + private void setProvisioningState(ProvisioningState state) { + setStatus(KEY_PROVISIONING_STATE, state.toString()); + } + + private void setStatus(String key, String status) { + if (!status.equals(getStatusAttribute(key))) { + addStatusAttribute(key, status); + handleStatechangeAutomation(); + if (!m_inConstructor) { + m_repository.notifyChanged(this, TOPIC_STATUS_CHANGED); + } + } + } + + private void handleStatechangeAutomation() { + if (getStoreState().equals(StoreState.Unapproved) && isRegistered() && getAutoApprove()) { + approve(); + } + } + + /** + * Verifies that this object should still be around. If the target is represents + * shows up in at least the target repository or the auditlog, it has a reason + * to exists; if not, it doesn't. When it is no longer necessary, it will remove itself + * from the parent repository. + * @return Whether or not this object should still exist. + */ + boolean verifyExistence() { + synchronized(m_lock) { + if ((m_targetObject == null) && ((m_processedAuditEvents == null) || m_processedAuditEvents.isEmpty())) { + m_repository.removeStateful(this); + return false; + } + return true; + } + } + + /** + * Helper method for the delegate methods below: most of these delegate their calls to a + * TargetObject, but in order to do so, one must be present. + */ + private void ensureTargetPresent() { + if ((m_targetObject == null)) { + throw new IllegalStateException("This StatefulTargetObject is not backed by a TargetObject."); + // NOTE: we do not check the isDeleted state; the TargetObject itself will notify the user of this. + } + } + + @Override + public boolean equals(Object o) { + if ((o == null) || !(o instanceof StatefulTargetObject)) { + return false; + } + return getID().equals(((StatefulTargetObject) o).getID()); + } + + private void addStatusAttribute(String key, String value) { + m_attributes.put(key, value); + } + + private String getStatusAttribute(String key) { + return m_attributes.get(key); + } + + /* ****************** + * Delegates to TargetObject + */ + + public String getID() { + return getStatusAttribute(KEY_ID); + } + + public boolean isDeleted() { + return !verifyExistence(); + } + + public List getAssociationsWith(DistributionObject distribution) { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject.getAssociationsWith(distribution); + } + } + + public List getDistributions() { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject.getDistributions(); + } + } + + public String addAttribute(String key, String value) { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject.addAttribute(key, value); + } + } + + public String addTag(String key, String value) { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject.addTag(key, value); + } + } + + public String getAttribute(String key) { + // retrieve from both + synchronized(m_lock) { + if (Arrays.binarySearch(KEYS_ALL, key) >= 0) { + return getStatusAttribute(key); + } + ensureTargetPresent(); + return m_targetObject.getAttribute(key); + } + } + + public Enumeration getAttributeKeys() { + synchronized(m_lock) { + List statusKeys = new ArrayList(); + for (String s : KEYS_ALL) { + statusKeys.add(s); + } + Enumeration attributeKeys = null; + if (m_targetObject != null) { + attributeKeys = m_targetObject.getAttributeKeys(); + } + return new ExtendedEnumeration(attributeKeys, statusKeys, true); + } + } + + public Dictionary getDictionary() { + // build our own dictionary + synchronized(m_lock) { + return new StatefulTargetObjectDictionary(); + } + } + + public String getTag(String key) { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject.getTag(key); + } + } + + public Enumeration getTagKeys() { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject.getTagKeys(); + } + } + + public boolean getAutoApprove() { + synchronized(m_lock) { + if (m_targetObject != null) { + return m_targetObject.getAutoApprove(); + } + else { + return false; + } + + } + } + + public void setAutoApprove(boolean approve) { + synchronized(m_lock) { + ensureTargetPresent(); + m_targetObject.setAutoApprove(approve); + } + } + + @SuppressWarnings("unchecked") + public void add(Association association, Class clazz) { + synchronized(m_lock) { + ensureTargetPresent(); + m_targetObject.add(association, clazz); + } + } + + public List getAssociations(Class clazz) { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject.getAssociations(clazz); + } + } + + @SuppressWarnings("unchecked") + public List getAssociationsWith(Associatable other, Class clazz, Class associationType) { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject.getAssociationsWith(other, clazz, associationType); + } + } + + public boolean isAssociated(Object obj, Class clazz) { + synchronized(m_lock) { + ensureTargetPresent(); + return m_targetObject.isAssociated(obj, clazz); + } + } + + @SuppressWarnings("unchecked") + public void remove(Association association, Class clazz) { + synchronized(m_lock) { + ensureTargetPresent(); + m_targetObject.remove(association, clazz); + } + } + + public String getDefinition() { + return "target-" + KEY_ID + "-" + getID(); + } + + private class ExtendedEnumeration implements Enumeration { + private Enumeration m_source; + private List m_extra; + private final boolean m_allowDuplicates; + + ExtendedEnumeration(Enumeration source, List extra, boolean allowDuplicates) { + m_source = source; + m_extra = extra; + m_allowDuplicates = allowDuplicates; + } + + public boolean hasMoreElements() { + boolean inSource = (m_source != null); + boolean inExtra = false; + if (m_extra != null) { + inExtra = !m_extra.isEmpty(); + } + return inSource || inExtra; + } + + public T nextElement() { + if (m_source != null) { + T result = m_source.nextElement(); + if (!m_source.hasMoreElements()) { + m_source = null; + } + if (!m_allowDuplicates) { + m_extra.remove(result); + } + return result; + } + else if (!m_extra.isEmpty()) { + return m_extra.remove(0); + } + throw new NoSuchElementException(); + } + } + + private class StatefulTargetObjectDictionary extends Dictionary { + private final Dictionary m_dict; + + StatefulTargetObjectDictionary() { + if (m_targetObject != null) { + m_dict = m_targetObject.getDictionary(); + } + else { + m_dict = null; + } + } + + @Override + public Enumeration elements() { + List statusVals = new ArrayList(); + for (String key : KEYS_ALL) { + statusVals.add(getStatusAttribute(key)); + } + Enumeration attributeVals = null; + if (m_dict != null) { + attributeVals = m_dict.elements(); + } + return new ExtendedEnumeration(attributeVals, statusVals, true); + } + + @Override + public Object get(Object key) { + for (String s : KEYS_ALL) { + if (s.equals(key)) { + return getStatusAttribute((String) key); + } + } + String tag = m_targetObject.getTag((String)key); + String attr = m_targetObject.getAttribute((String)key); + if (tag == null) { + return attr; + } + else if (attr == null) { + return tag; + } + else { + return new String[] {attr, tag}; + } + } + + @Override + public boolean isEmpty() { + // This is always false, since we always have the status attributes. + return false; + } + + @Override + public Enumeration keys() { + List statusKeys = new ArrayList(); + for (String key : KEYS_ALL) { + statusKeys.add(key); + } + Enumeration attributeKeys = null; + if (m_dict != null) { + attributeKeys = m_dict.keys(); + } + return new ExtendedEnumeration(attributeKeys, statusKeys, false); + } + + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + int result = 0; + Enumeration keys = keys(); + while (keys.hasMoreElements()) { + result++; + keys.nextElement(); + } + return result; + } + } + + public String getAssociationFilter(Map properties) { + throw new UnsupportedOperationException("A StatefulTargetObject cannot return a filter; use the underlying TargetObject instead."); + } + + public int getCardinality(Map properties) { + throw new UnsupportedOperationException("A StatefulTargetObject cannot return a cardinality; use the underlying TargetObject instead."); + } + + @SuppressWarnings("unchecked") + public Comparator getComparator() { + throw new UnsupportedOperationException("A StatefulTargetObject cannot return a comparator; use the underlying TargetObject instead."); + } +} \ No newline at end of file