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 9CAE7F590 for ; Tue, 2 Apr 2013 14:54:40 +0000 (UTC) Received: (qmail 55650 invoked by uid 500); 2 Apr 2013 14:54:40 -0000 Delivered-To: apmail-ace-commits-archive@ace.apache.org Received: (qmail 55620 invoked by uid 500); 2 Apr 2013 14:54:40 -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 55604 invoked by uid 99); 2 Apr 2013 14:54:39 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 02 Apr 2013 14:54:39 +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; Tue, 02 Apr 2013 14:54:28 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 9F98F2388A9B; Tue, 2 Apr 2013 14:53:41 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1463576 [7/8] - in /ace/trunk: org.apache.ace.client.repository.api/ org.apache.ace.client.repository.helper.base/ org.apache.ace.client.repository.helper.bundle/ org.apache.ace.client.repository.helper.configuration/ org.apache.ace.client... Date: Tue, 02 Apr 2013 14:53:35 -0000 To: commits@ace.apache.org From: marrs@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20130402145341.9F98F2388A9B@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/stateful/impl/StatefulTargetRepositoryImpl.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/stateful/impl/StatefulTargetRepositoryImpl.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/stateful/impl/StatefulTargetRepositoryImpl.java (added) +++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/stateful/impl/StatefulTargetRepositoryImpl.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,754 @@ +/* + * 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.Collections; +import java.util.Comparator; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.ace.client.repository.RepositoryAdmin; +import org.apache.ace.client.repository.RepositoryObject; +import org.apache.ace.client.repository.RepositoryUtil; +import org.apache.ace.client.repository.SessionFactory; +import org.apache.ace.client.repository.helper.bundle.BundleHelper; +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.FeatureObject; +import org.apache.ace.client.repository.object.DistributionObject; +import org.apache.ace.client.repository.repository.ArtifactRepository; +import org.apache.ace.client.repository.repository.DeploymentVersionRepository; +import org.apache.ace.client.repository.repository.TargetRepository; +import org.apache.ace.client.repository.stateful.StatefulTargetObject; +import org.apache.ace.client.repository.stateful.StatefulTargetRepository; +import org.apache.ace.log.LogDescriptor; +import org.apache.ace.log.LogEvent; +import org.apache.ace.server.log.store.LogStore; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.Version; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventAdmin; +import org.osgi.service.event.EventHandler; +import org.osgi.service.log.LogService; + +/** + * Implements the StatefulTargetRepository. If an AuditLogStore is present, + * it will be used; it is assumed that the auditlog store is up to date. + */ +public class StatefulTargetRepositoryImpl implements StatefulTargetRepository, EventHandler { + private BundleContext m_context; /* Injected by dependency manager */ + private ArtifactRepository m_artifactRepository; /* Injected by dependency manager */ + private TargetRepository m_targetRepository; /* Injected by dependency manager */ + private DeploymentVersionRepository m_deploymentRepository; /* Injected by dependency manager */ + private LogStore m_auditLogStore; /* Injected by dependency manager */ + private EventAdmin m_eventAdmin; /* Injected by dependency manager */ + private LogService m_log; /* Injected by dependency manager */ + private BundleHelper m_bundleHelper; /* Injected by dependency manager */ + // TODO: Make the concurrencyLevel of this concurrent hashmap settable? + private Map m_repository = new ConcurrentHashMap(); + private Map m_index = new ConcurrentHashMap(); + private final String m_sessionID; + + public StatefulTargetRepositoryImpl(String sessionID) { + m_sessionID = sessionID; + } + + public StatefulTargetObject create(Map attributes, Map tags) + throws IllegalArgumentException { + throw new UnsupportedOperationException("Creating StatefulTargetObjects is not supported."); + } + + public List get() { + synchronized (m_repository) { + List result = new ArrayList(); + for (StatefulTargetObjectImpl sgoi : m_repository.values()) { + result.add(sgoi); + } + return result; + } + } + + public List get(Filter filter) { + synchronized (m_repository) { + List result = new ArrayList(); + for (StatefulTargetObject entry : m_repository.values()) { + if (filter.match(entry.getDictionary())) { + result.add(entry); + } + } + return result; + } + } + + public StatefulTargetObject get(String definition) { + return m_index.get(definition); + } + + public void remove(StatefulTargetObject entity) { + synchronized (m_repository) { + StatefulTargetObjectImpl statefulTarget = (StatefulTargetObjectImpl) entity; + unregister(statefulTarget.getID()); + removeStateful(statefulTarget); + // Ensure the external side sees the changes we've made... + statefulTarget.updateTargetObject(false); + } + } + + public StatefulTargetObject preregister(Map attributes, Map tags) { + synchronized (m_repository) { + TargetObject to = m_targetRepository.create(attributes, tags); + return createStateful(to.getID()); + } + } + + public void unregister(String targetID) { + synchronized (m_repository) { + TargetObject to = getTargetObject(targetID); + if (to == null) { + throw new IllegalArgumentException(targetID + " does not represent a TargetObject."); + } + else { + m_targetRepository.remove(to); + // No need to inform the stateful representation; this will be done by the event handler. + } + } + } + + public void refresh() { + populate(); + } + + /** + * Gets the TargetObject which is identified by the targetID. + * + * @param targetID A string representing a target ID. + * @return The TargetObject from the TargetRepository which has the given + * ID, or null if none can be found. + */ + TargetObject getTargetObject(String targetID) { +// synchronized(m_repository) { + try { + List targets = + m_targetRepository.get(m_context.createFilter("(" + TargetObject.KEY_ID + "=" + + RepositoryUtil.escapeFilterValue(targetID) + ")")); + if ((targets != null) && (targets.size() == 1)) { + return targets.get(0); + } + else { + return null; + } + } + catch (InvalidSyntaxException e) { + // The filter syntax is illegal, probably a bad target ID. + return null; + } +// } + } + + /** + * Gets the stateful representation of the given target ID. + * + * @param targetID A string representing a target ID. + * @return The StatefulTargetyObjectImpl which handles the given ID, + * or null if none can be found. + */ + StatefulTargetObjectImpl getStatefulTargetObject(String targetID) { + synchronized (m_repository) { + return m_repository.get(targetID); + } + } + + /** + * Creates and registers a new stateful target object based on the given ID. + * + * @param targetID A string representing a target ID. + * @return The newly created and registered StatefulTargetObjectImpl. + */ + private StatefulTargetObjectImpl createStateful(String targetID) { + synchronized (m_repository) { + StatefulTargetObjectImpl result = new StatefulTargetObjectImpl(this, targetID); + if (add(result)) { + return result; + } + else { + throw new IllegalArgumentException("The StateTargetObject " + targetID + " already exists."); + } + } + } + + /** + * Removes the given entity from this object's repository, and notifies + * interested parties of this. + * + * @param entity The StatefulTargetObjectImpl to be removed. + */ + void removeStateful(StatefulTargetObjectImpl entity) { + synchronized (m_repository) { + m_repository.remove(entity.getID()); + notifyChanged(entity, StatefulTargetObject.TOPIC_REMOVED); + } + } + + /** + * Adds the given stateful object to this object's repository, and notifies + * interested parties of this change. + * + * @param stoi A StatefulTargetObjectImpl to be registered. + * @return true when this object has been added to the repository + * and listeners have been notified, false otherwise. + */ + boolean add(StatefulTargetObjectImpl stoi) { + if (!m_repository.containsKey(stoi)) { + m_repository.put(stoi.getID(), stoi); + m_index.put(stoi.getDefinition(), stoi); + notifyChanged(stoi, StatefulTargetObject.TOPIC_ADDED); + return true; + } + return false; + } + + private Comparator m_auditEventComparator = new LogEventComparator(); + + /** + * Gets all auditlog events which are related to a given target ID. + * + * @param targetID A string representing a target ID. + * @return a list of AuditEvents related to this target ID, + * ordered in the order they happened. If no events can be found, and empty list will be returned. + */ + List getAuditEvents(String targetID) { + return getAuditEvents(getAllDescriptors(targetID)); + } + + /** + * Gets all auditlog descriptors which are related to a given target. + * + * @param targetID The target ID + * @return A list of LogDescriptors, in no particular order. + */ + List getAllDescriptors(String targetID) { + List result = new ArrayList(); + try { + List descriptors = m_auditLogStore.getDescriptors(targetID); + if (descriptors != null) { + result = descriptors; + } + } + catch (IOException e) { + // Too bad, but not much we can do. + m_log.log(LogService.LOG_INFO, "Error getting descriptors from auditlog store: ", e); + } + return result; + } + + /** + * Gets all audit log events for a target is has not yet 'seen'. + * + * @param all A list of all LogDescriptor from which to filter + * the new ones. + * @param seen A list of LogDescriptor objects, which indicate + * the items the target has already processed. + * @return All AuditLog events that are in the audit store, but are not identified + * by oldDescriptors, ordered by 'happened-before'. + */ + List getAuditEvents(List events) { + // Get all events from the audit log store, if possible. + List result = new ArrayList(); + for (LogDescriptor l : events) { + try { + result.addAll(m_auditLogStore.get(l)); + } + catch (IOException e) { + // too bad, but not much to do. + m_log.log(LogService.LOG_INFO, "Error getting contents from auditlog store: ", e); + } + } + + Collections.sort(result, m_auditEventComparator); + return result; + } + + List diffLogDescriptorLists(List all, List seen) { + List descriptors = new ArrayList(); + + // Find out what events should be returned + for (LogDescriptor s : all) { + LogDescriptor diffs = s; + for (LogDescriptor d : seen) { + if ((s.getLogID() == d.getLogID()) && (s.getTargetID().equals(d.getTargetID()))) { + diffs = new LogDescriptor(s.getTargetID(), s.getLogID(), d.getRangeSet().diffDest(s.getRangeSet())); + } + } + descriptors.add(diffs); + } + return descriptors; + } + + /** + * See {@link DeploymentRepository#getDeploymentVersion(java.lang.String)}. + */ + DeploymentVersionObject getMostRecentDeploymentVersion(String targetID) { + return m_deploymentRepository.getMostRecentDeploymentVersion(targetID); + } + + /** + * Based on the information in this stateful object, creates a TargetObject + * in the TargetRepository. + * This function is intended to be used for targets which are not yet represented + * in the TargetRepository; if they already are, an IllegalArgumentException + * will be thrown. + * + * @param targetID A string representing the ID of the new target. + */ + void register(String targetID) { + Map attr = new HashMap(); + attr.put(TargetObject.KEY_ID, targetID); + Map tags = new HashMap(); + m_targetRepository.create(attr, tags); + getStatefulTargetObject(targetID).updateTargetObject(false); + } + + /** + * Notifies interested parties of a change to a StatefulTargetObject. + * + * @param stoi The StatefulTargetObject which has changed. + * @param topic A topic string for posting the event. + * @param additionalProperties A Properties event, already containing some extra properties. If + * RepositoryObject.EVENT_ENTITY is used, it will be overwritten. + */ + void notifyChanged(StatefulTargetObject stoi, String topic, Properties additionalProperties) { + additionalProperties.put(RepositoryObject.EVENT_ENTITY, stoi); + additionalProperties.put(SessionFactory.SERVICE_SID, m_sessionID); + m_eventAdmin.postEvent(new Event(topic, (Dictionary) additionalProperties)); + } + + /** + * Notifies interested parties of a change to a StatefulTargetObject. + * + * @param stoi The StatefulTargetObject which has changed. + * @param topic A topic string for posting the event. + */ + void notifyChanged(StatefulTargetObject stoi, String topic) { + notifyChanged(stoi, topic, new Properties()); + } + + /** + * Reads the information sources to generate the stateful objects. + */ + private void populate() { + synchronized (m_repository) { + List touched = new ArrayList(); + touched.addAll(parseTargetRepository()); + touched.addAll(parseAuditLog()); + + // Now, it is possible we have not touched all objects. Find out which these are, and make + // them check whether they should still exist. + List all = new ArrayList(m_repository.values()); + all.removeAll(touched); + for (StatefulTargetObjectImpl stoi : all) { + stoi.updateTargetObject(false); + stoi.updateDeploymentVersions(null); + stoi.updateAuditEvents(true); + } + // Furthermore, for all those we _did_ see, we need to make sure their deployment versions + // are up to date. + for (StatefulTargetObjectImpl stoi : touched) { + stoi.updateDeploymentVersions(null); + stoi.updateTargetObject(true); + } + } + } + + /** + * Checks all inhabitants of the TargetRepository to see + * whether we already have a stateful representation of them. + * + * @param needsVerify states whether the objects which are 'touched' by this + * actions should verify their existence. + * @return A list of all the target objects that have been touched by this action. + */ + private List parseTargetRepository() { + List result = new ArrayList(); + for (TargetObject to : m_targetRepository.get()) { + StatefulTargetObjectImpl stoi = getStatefulTargetObject(to.getID()); + if (stoi == null) { + result.add(createStateful(to.getID())); + } + else { + result.add(stoi); + stoi.updateTargetObject(false); + } + } + return result; + } + + /** + * Checks the audit log to see whether we already have a + * stateful object for all targets mentioned there. + * + * @param needsVerify states whether the objects which are 'touched' by this + * actions should verify their existence. + */ + private List parseAuditLog() { + List result = new ArrayList(); + List descriptors = null; + try { + descriptors = m_auditLogStore.getDescriptors(); + } + catch (IOException e) { + // Not much to do. + } + if (descriptors == null) { + // There is no audit log available, or it failed getting the log descriptors. + return result; + } + + Set targetIDs = new HashSet(); + for (LogDescriptor l : descriptors) { + targetIDs.add(l.getTargetID()); + } + + /* + * Note: the parsing of the audit log and the creation/notification of the + * stateful objects has been separated, to prevent calling updateAuditEvents() + * multiple times on targets which have more than one log. + */ + synchronized (m_repository) { + for (String targetID : targetIDs) { + StatefulTargetObjectImpl stoi = getStatefulTargetObject(targetID); + if (stoi == null) { + result.add(createStateful(targetID)); + } + else { + result.add(stoi); + stoi.updateAuditEvents(false); + } + } + } + return result; + } + + /** + * Approves the changes that will happen to the target based on the + * changes in the shop by generating a new deployment version. + * + * @param targetID A string representing a target ID. + * @return The version identifier of the new deployment package. + * @throws java.io.IOException When there is a problem generating the deployment version. + */ + String approve(String targetID) throws IOException { + return generateDeploymentVersion(targetID).getVersion(); + } + + /** + * Generates an array of bundle URLs which have to be deployed on + * the target, given the current state of the shop. + * TODO: In the future, we want to add support for multiple shops. + * TODO: Is this prone to concurrency issues with changes distribution- and + * feature objects? + * + * @param targetID A string representing a target. + * @return An array of artifact URLs. + * @throws java.io.IOException When there is a problem processing an artifact for deployment. + */ + DeploymentArtifact[] getNecessaryDeploymentArtifacts(String targetID, String version) throws IOException { + TargetObject to = getTargetObject(targetID); + + Map bundles = new HashMap(); + Map artifacts = new HashMap(); + Map>> path = + new HashMap>>(); + + // First, find all basic bundles and artifacts. An while we're traversing the + // tree of objects, build the tree of properties. + if (to != null) { + for (DistributionObject distribution : to.getDistributions()) { + for (FeatureObject feature : distribution.getFeatures()) { + for (ArtifactObject artifact : feature.getArtifacts()) { + if (m_bundleHelper.canUse(artifact)) { + bundles.put(artifact, m_bundleHelper.getResourceProcessorPIDs(artifact)); + } + else { + artifacts.put(artifact, artifact.getProcessorPID()); + } + Map> featureToDistribution = path.get(artifact); + if (featureToDistribution == null) { + featureToDistribution = new HashMap>(); + path.put(artifact, featureToDistribution); + } + List distributions = featureToDistribution.get(feature); + if (distributions == null) { + distributions = new ArrayList(); + featureToDistribution.put(feature, distributions); + } + distributions.add(distribution); + } + } + } + } + + // Find all processors + Map allProcessors = new HashMap(); + for (ArtifactObject bundle : m_artifactRepository.getResourceProcessors()) { + allProcessors.put(m_bundleHelper.getResourceProcessorPIDs(bundle), bundle); + } + + // Determine all resource processors we need + for (String processor : artifacts.values()) { + if (!bundles.containsValue(processor)) { + ArtifactObject bundle = allProcessors.get(processor); + if (bundle == null) { + m_log.log(LogService.LOG_ERROR, "Unable to create deployment version: there is no resource processing bundle available that publishes " + processor); + throw new IllegalStateException("Unable to create deployment version: there is no resource processing bundle available that publishes " + processor); + } + bundles.put(bundle, processor); + } + } + + List result = new ArrayList(); + + for (ArtifactObject bundle : bundles.keySet()) { + Map directives = new HashMap(); + if (m_bundleHelper.isResourceProcessor(bundle)) { + // it's a resource processor, mark it as such. + directives.put(DeploymentArtifact.DIRECTIVE_ISCUSTOMIZER, "true"); + } + directives.put(BundleHelper.KEY_SYMBOLICNAME, m_bundleHelper.getSymbolicName(bundle)); + String bundleVersion = m_bundleHelper.getVersion(bundle); + if (bundleVersion != null) { + directives.put(BundleHelper.KEY_VERSION, bundleVersion); + } + + directives.put(DeploymentArtifact.DIRECTIVE_KEY_BASEURL, bundle.getURL()); + + String repositoryPath = getRepositoryPath(bundle, path); + if (repositoryPath != null) { + directives.put(DeploymentArtifact.REPOSITORY_PATH, repositoryPath); + } + + result.add(m_deploymentRepository.createDeploymentArtifact(bundle.getURL(), directives)); + } + + for (ArtifactObject artifact : artifacts.keySet()) { + Map directives = new HashMap(); + directives.put(DeploymentArtifact.DIRECTIVE_KEY_PROCESSORID, artifact.getProcessorPID()); + directives.put(DeploymentArtifact.DIRECTIVE_KEY_BASEURL, artifact.getURL()); + if (artifact.getResourceId() != null) { + directives.put(DeploymentArtifact.DIRECTIVE_KEY_RESOURCE_ID, artifact.getResourceId()); + } + + String repositoryPath = getRepositoryPath(artifact, path); + if (repositoryPath != null) { + directives.put(DeploymentArtifact.REPOSITORY_PATH, repositoryPath); + } + result.add(m_deploymentRepository.createDeploymentArtifact( + m_artifactRepository.preprocessArtifact(artifact, to, targetID, version), directives)); + } + + return result.toArray(new DeploymentArtifact[result.size()]); + } + + private String getRepositoryPath(ArtifactObject artifact, + Map>> path) { + StringBuilder builder = new StringBuilder(); + Map> featureToDistribution = path.get(artifact); + if (featureToDistribution != null) { + for (Entry> entry : featureToDistribution.entrySet()) { + for (DistributionObject distribution : entry.getValue()) { + builder.append(entry.getKey().getName()).append(';').append(distribution.getName()).append(','); + } + } + } + else { + return null; + } + builder.setLength(builder.length() - 1); + return builder.toString(); + } + + /** + * Quick method to find all artifacts that need to be deployed to a target. + */ + ArtifactObject[] getNecessaryArtifacts(String targetID) { + List result = new ArrayList(); + TargetObject to = getTargetObject(targetID); + + Map allProcessors = new HashMap(); + for (ArtifactObject bundle : m_artifactRepository.getResourceProcessors()) { + allProcessors.put(m_bundleHelper.getResourceProcessorPIDs(bundle), bundle); + } + + if (to != null) { + for (DistributionObject distribution : to.getDistributions()) { + for (FeatureObject feature : distribution.getFeatures()) { + for (ArtifactObject artifact : feature.getArtifacts()) { + result.add(artifact); + if (!m_bundleHelper.canUse(artifact)) { + String processorPID = artifact.getProcessorPID(); + if (processorPID == null) { + m_log.log(LogService.LOG_ERROR, "Cannot gather necessary artifacts: no processor PID defined for " + artifact.getName()); + return null; + } + ArtifactObject processor = allProcessors.get(processorPID); + if (processor == null) { + // this means we cannot create a useful version; return null. + m_log.log(LogService.LOG_ERROR, "Cannot gather necessary artifacts: failed to find resource processor named '" + artifact.getProcessorPID() + "' for artifact '" + artifact.getName() + "'!"); + return null; + } + result.add(processor); + } + } + } + } + } + + return result.toArray(new ArtifactObject[result.size()]); + } + + /** + * Generates a new deployment version for the the given target, + * based on the artifacts it is linked to by the distributions it is + * associated to. + * + * @param targetID A string representing a target. + * @return A new DeploymentVersionObject, representing this new version for the target. + * @throws java.io.IOException When there is a problem determining the artifacts to be deployed. + */ + DeploymentVersionObject generateDeploymentVersion(String targetID) throws IOException { + Map attr = new HashMap(); + attr.put(DeploymentVersionObject.KEY_TARGETID, targetID); + Map tags = new HashMap(); + + DeploymentVersionObject mostRecentDeploymentVersion = getMostRecentDeploymentVersion(targetID); + String nextVersion; + if (mostRecentDeploymentVersion == null) { + nextVersion = nextVersion(null); + } + else { + nextVersion = nextVersion(mostRecentDeploymentVersion.getVersion()); + } + attr.put(DeploymentVersionObject.KEY_VERSION, nextVersion); + + synchronized (m_repository) { + DeploymentVersionObject result = m_deploymentRepository.create(attr, tags, getNecessaryDeploymentArtifacts(targetID, nextVersion)); + + StatefulTargetObjectImpl stoi = getStatefulTargetObject(targetID); + if (stoi == null) { + createStateful(targetID); + } + else { + stoi.updateDeploymentVersions(result); + } + + return result; + } + } + + /** + * Generates the next version, based on the version passed in. + * The version is assumed to be an OSGi-version; for now, the next + * 'major' version is generated. In the future, we might want to do + * 'smarter' things here, like checking the impact of a new version + * and use the minor and micro versions, or attach some qualifier. + * + * @param version A string representing a deployment version's version. + * @return A string representing the next version. + */ + private static String nextVersion(String version) { + try { + Version v = new Version(version); + Version result = new Version(v.getMajor() + 1, 0, 0); + return result.toString(); + } + catch (Exception iae) { + // Basically, if anything goes wrong, we assume we want to start a new version at 1. + return "1.0.0"; + } + } + + public void handleEvent(Event event) { + String topic = event.getTopic(); + if (TargetObject.TOPIC_ADDED.equals(topic)) { + synchronized (m_repository) { + String id = ((TargetObject) event.getProperty(RepositoryObject.EVENT_ENTITY)).getID(); + StatefulTargetObjectImpl stoi = getStatefulTargetObject(id); + if (stoi == null) { + createStateful(id); + } + else { + stoi.updateTargetObject(true); + } + } + } + else if (TargetObject.TOPIC_REMOVED.equals(topic)) { + synchronized (m_repository) { + String id = ((TargetObject) event.getProperty(RepositoryObject.EVENT_ENTITY)).getID(); + StatefulTargetObjectImpl stoi = getStatefulTargetObject(id); + // if the stateful target is already gone; we don't have to do anything... + if (stoi != null) { + stoi.updateTargetObject(true); + } + } + } + else if (DeploymentVersionObject.TOPIC_ADDED.equals(topic) || DeploymentVersionObject.TOPIC_REMOVED.equals(topic)) { + synchronized (m_repository) { + DeploymentVersionObject deploymentVersionObject = ((DeploymentVersionObject) event.getProperty(RepositoryObject.EVENT_ENTITY)); + String id = deploymentVersionObject.getTargetID(); + StatefulTargetObjectImpl stoi = getStatefulTargetObject(id); + if (stoi == null) { + createStateful(id); + } + else { + stoi.updateDeploymentVersions(deploymentVersionObject); + } + } + } + else if (RepositoryAdmin.TOPIC_LOGIN.equals(topic) || RepositoryAdmin.TOPIC_REFRESH.equals(topic)) { + synchronized (m_repository) { + populate(); + } + } + else { + // Something else has changed; however, the entire shop may have an influence on + // any target, so recheck everything. + synchronized (m_repository) { + for (StatefulTargetObjectImpl stoi : m_repository.values()) { + stoi.determineStatus(); + } + } + } + } + + boolean needsNewVersion(ArtifactObject artifact, String targetID, String version) { + return m_artifactRepository.needsNewVersion(artifact, getTargetObject(targetID), targetID, version); + } +} \ No newline at end of file Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/stateful/packageinfo URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/stateful/packageinfo?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/stateful/packageinfo (added) +++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/stateful/packageinfo Tue Apr 2 14:53:33 2013 @@ -0,0 +1 @@ +version 1.0 \ No newline at end of file Added: ace/trunk/org.apache.ace.client.repository/test/invalid13.xml URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/invalid13.xml?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/invalid13.xml (added) +++ ace/trunk/org.apache.ace.client.repository/test/invalid13.xml Tue Apr 2 14:53:33 2013 @@ -0,0 +1,13 @@ + + + + + + + + + 8080 + + + + Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessorTest.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessorTest.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessorTest.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessorTest.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,202 @@ +/* + * 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.helper.base; + +import static org.apache.ace.test.utils.TestUtils.UNIT; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; + +import org.apache.ace.client.repository.helper.PropertyResolver; +import org.apache.ace.connectionfactory.ConnectionFactory; +import org.osgi.service.useradmin.User; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +/** + * Test cases for {@link VelocityArtifactPreprocessor}. + */ +public class VelocityArtifactPreprocessorTest { + + private static final String TARGET = "target"; + private static final String VERSION1 = "1.0.0"; + + private URL m_obrUrl; + private PropertyResolver m_resolver; + + @BeforeTest + public void setUp() throws Exception { + m_obrUrl = new URL("file://" + System.getProperty("java.io.tmpdir") + "/"); + + m_resolver = new PropertyResolver() { + public String get(String key) { + return "msg".equals(key) ? "Hello World!" : null; + } + }; + } + + /** + * Test case for {@link VelocityArtifactPreprocessor#needsNewVersion(String, PropertyResolver, String, String)} + */ + @Test(groups = { UNIT }) + public void testNeedsNewVersionChangedTemplateOk() throws Exception { + final VelocityArtifactPreprocessor vap = createProcessor(); + + String url = createArtifact("Message: [$context.msg]"); + + // "upload" a new version... + vap.preprocess(url, m_resolver, TARGET, VERSION1, m_obrUrl); + + boolean result = vap.needsNewVersion(url, m_resolver, TARGET, VERSION1); + assertFalse(result); // no new version is needed... + + updateArtifact(url, "Another message: [$context.msg2]"); + + result = vap.needsNewVersion(url, m_resolver, TARGET, VERSION1); + assertFalse(result); // no new version is needed; original artifact is cached indefinitely... + } + + /** + * Test case for {@link VelocityArtifactPreprocessor#needsNewVersion(String, PropertyResolver, String, String)} + */ + @Test(groups = { UNIT }) + public void testNeedsNewVersionEmptyTemplateOk() throws Exception { + final VelocityArtifactPreprocessor vap = createProcessor(); + + String url = createArtifact(""); + + // "upload" a new version... + vap.preprocess(url, m_resolver, TARGET, VERSION1, m_obrUrl); + + boolean result = vap.needsNewVersion(url, m_resolver, TARGET, VERSION1); + assertFalse(result); // no new version is needed... + } + + /** + * Test case for {@link VelocityArtifactPreprocessor#needsNewVersion(String, PropertyResolver, String, String)} + */ + @Test(groups = { UNIT }) + public void testNeedsNewVersionNonExistingTemplateOk() throws Exception { + final VelocityArtifactPreprocessor vap = createProcessor(); + + // Should be something that really doesn't exist somehow... + String url = "file:///path/to/nowhere-" + System.currentTimeMillis(); + + boolean result = vap.needsNewVersion(url, m_resolver, TARGET, VERSION1); + assertTrue(result); // always true for non-existing templates... + } + + /** + * Test case for {@link VelocityArtifactPreprocessor#needsNewVersion(String, PropertyResolver, String, String)} + */ + @Test(groups = { UNIT }) + public void testNeedsNewVersionUnchangedTemplateOk() throws Exception { + final VelocityArtifactPreprocessor vap = createProcessor(); + + String url = createArtifact("Message: [$context.msg]"); + + boolean result = vap.needsNewVersion(url, m_resolver, TARGET, VERSION1); + assertTrue(result); // nothing uploaded yet; new version is needed... + + // "upload" a new version... + vap.preprocess(url, m_resolver, TARGET, VERSION1, m_obrUrl); + + result = vap.needsNewVersion(url, m_resolver, TARGET, VERSION1); + assertFalse(result); // no new version is needed... + } + + /** + * Test case for {@link VelocityArtifactPreprocessor#preprocess(String, PropertyResolver, String, String, java.net.URL)} + */ + @Test(groups = { UNIT }) + public void testPreprocessExistingNoTemplateOk() throws Exception { + String url = createArtifact("Message: [context.msg]"); + + String newUrl = createProcessor().preprocess(url, m_resolver, TARGET, VERSION1, m_obrUrl); + assertNotNull(newUrl); + // Verify that it is *not* uploaded... + assertEquals(url, newUrl); + } + + /** + * Test case for {@link VelocityArtifactPreprocessor#preprocess(String, PropertyResolver, String, String, java.net.URL)} + */ + @Test(groups = { UNIT }) + public void testPreprocessExistingRealTemplateOk() throws Exception { + String url = createArtifact("Message: [$context.msg]"); + + String newUrl = createProcessor().preprocess(url, m_resolver, TARGET, VERSION1, m_obrUrl); + assertNotNull(newUrl); + // Verify that it is actually uploaded... + assertFalse(newUrl.equals(url)); + // Verify that it is actually uploaded to our (fake) OBR... + assertTrue(newUrl.startsWith(m_obrUrl.toExternalForm()), "newUrl (" + newUrl + ") should start with: " + m_obrUrl.toExternalForm()); + } + + /** + * Test case for {@link VelocityArtifactPreprocessor#preprocess(String, PropertyResolver, String, String, java.net.URL)} + */ + @Test(groups = { UNIT }, expectedExceptions = { IOException.class }) + public void testPreprocessNonExistingTemplateOk() throws Exception { + // Should be something that really doesn't exist somehow... + String url = "file:///path/to/nowhere-" + System.currentTimeMillis(); + + createProcessor().preprocess(url, m_resolver, TARGET, VERSION1, m_obrUrl); + } + + private String createArtifact(String string) throws IOException { + File tmpFile = File.createTempFile("vap", "vm"); + tmpFile.delete(); + tmpFile.deleteOnExit(); + + FileWriter writer = new FileWriter(tmpFile); + writer.write(string); + writer.flush(); + writer.close(); + + return tmpFile.toURI().toURL().toExternalForm(); + } + + private VelocityArtifactPreprocessor createProcessor() { + return new VelocityArtifactPreprocessor(new ConnectionFactory() { + public URLConnection createConnection(URL url, User user) throws IOException { + return createConnection(url); + } + + public URLConnection createConnection(URL url) throws IOException { + return url.openConnection(); + } + }); + } + + private String updateArtifact(String url, String string) throws IOException { + URL uri = new URL(url); + + FileWriter writer = new FileWriter(uri.getFile()); + writer.write(string); + writer.flush(); + writer.close(); + + return url; + } +} Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/helper/configuration/impl/ConfigurationHelperImplTest.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/helper/configuration/impl/ConfigurationHelperImplTest.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/helper/configuration/impl/ConfigurationHelperImplTest.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/helper/configuration/impl/ConfigurationHelperImplTest.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,91 @@ +/* + * 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.helper.configuration.impl; + +import static org.apache.ace.test.utils.TestUtils.UNIT; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.apache.ace.client.repository.helper.ArtifactResource; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ConfigurationHelperImplTest { + + // ACE-259 Basic recognizer tests + + @Test(groups = { UNIT }) + public void testNamespace10Recognized() throws Exception { + ConfigurationHelperImpl c = new ConfigurationHelperImpl(); + String mime = c.recognize(convertToArtifactResource("valid10.xml")); + Assert.assertNotNull(mime); + } + + @Test(groups = { UNIT }) + public void testNamespace11Recognized() throws Exception { + ConfigurationHelperImpl c = new ConfigurationHelperImpl(); + String mime = c.recognize(convertToArtifactResource("valid11.xml")); + Assert.assertNotNull(mime); + } + + @Test(groups = { UNIT }) + public void testNamespace12Recognized() throws Exception { + ConfigurationHelperImpl c = new ConfigurationHelperImpl(); + String mime = c.recognize(convertToArtifactResource("valid12.xml")); + Assert.assertNotNull(mime); + } + + @Test(groups = { UNIT }) + public void testNamespace13NotRecognized() throws Exception { + ConfigurationHelperImpl c = new ConfigurationHelperImpl(); + String mime = c.recognize(convertToArtifactResource("invalid13.xml")); + Assert.assertNull(mime); + } + + @Test(groups = { UNIT }) + public void testCanHandleCommentBeforeRoot() throws Exception { + ConfigurationHelperImpl c = new ConfigurationHelperImpl(); + String mime = c.recognize(convertToArtifactResource("validWithComment.xml")); + Assert.assertNotNull(mime); + } + + /** + * @param url + * @return + */ + private ArtifactResource convertToArtifactResource(final String res) { + if (res == null) { + return null; + } + + final URL url = getClass().getClassLoader().getResource("./" + res); + + return new ArtifactResource() { + public URL getURL() { + return url; + } + + public InputStream openStream() throws IOException { + return getURL().openStream(); + } + }; + } +} \ No newline at end of file Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/ACE308Test.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/ACE308Test.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/ACE308Test.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/ACE308Test.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,42 @@ +/* + * 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 org.apache.ace.client.repository.stateful.impl.LogEventComparator; +import org.apache.ace.log.LogEvent; +import org.apache.ace.test.utils.TestUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + + +/** + * Before fixing ACE-308 the comparator could "overflow" when casting a long to an + * int, changing the sign of the result. For this specific case, it would fail. After + * the fix, it no longer fails. + */ +public class ACE308Test { + @Test( groups = { TestUtils.UNIT } ) + public void testLogEvents() { + LogEventComparator c = new LogEventComparator(); + LogEvent left = new LogEvent("t", 1, 1, -1000000000000000000L, 0, null); + LogEvent right = new LogEvent("t", 1, 1, 1, 0, null); + Assert.assertTrue((left.getTime() - right.getTime()) < 0L); + Assert.assertTrue(c.compare(left, right) < 0L); + } +} Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/AdminTestUtil.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/AdminTestUtil.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/AdminTestUtil.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/AdminTestUtil.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,51 @@ +/* + * 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; + +public class AdminTestUtil { + + public static byte[] copy(InputStream in) throws IOException { + byte[] result = new byte[in.available()]; + in.read(result); + return result; + } + + public static boolean byteArraysEqual(byte[] left, byte[] right) { + if (left.length != right.length) { + return false; + } + for (int i = 0; i < right.length; i++) { + if (left[i] != right[i]) { + return false; + } + } + return true; + } + + public static byte[] copy(byte[] input) { + byte[] result = new byte[input.length]; + for (int i = 0; i < input.length; i++) { + result[i] = input[i]; + } + return result; + } +} Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/ArtifactTest.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/ArtifactTest.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/ArtifactTest.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/ArtifactTest.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,178 @@ +/* + * 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.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.ace.client.repository.helper.ArtifactHelper; +import org.apache.ace.client.repository.helper.ArtifactPreprocessor; +import org.apache.ace.client.repository.helper.bundle.BundleHelper; +import org.apache.ace.client.repository.helper.bundle.impl.BundleHelperImpl; +import org.apache.ace.client.repository.object.ArtifactObject; +import org.apache.ace.test.utils.TestUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.log.LogService; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Tests the behavior of the ArtifactObject class, most prominently, checking whether + * delegation to the Helpers is done at the right moments. + */ +public class ArtifactTest { + + private ArtifactRepositoryImpl m_artifactRepository; + + @BeforeMethod(alwaysRun = true) + public void init() { + BundleContext bc = TestUtils.createMockObjectAdapter(BundleContext.class, new Object() { + @SuppressWarnings("unused") + public Filter createFilter(String filter) throws InvalidSyntaxException { + return FrameworkUtil.createFilter(filter); + } + }); + + m_artifactRepository = new ArtifactRepositoryImpl(TestUtils.createNullObject(ChangeNotifier.class)); + TestUtils.configureObject(m_artifactRepository, LogService.class); + TestUtils.configureObject(m_artifactRepository, BundleContext.class, bc); + } + + @Test( groups = { TestUtils.UNIT } ) + public void testAttributeChecking() { + ArtifactHelper helper = new MockHelper("yourURL"); + + try { + createArtifact("myMime", "myUrl", null, null); + assert false : "There is no helper for this type of artifact."; + } + catch (IllegalArgumentException iae) { + // expected + } + + m_artifactRepository.addHelper("myMime", helper); + + ArtifactObject obj = createArtifact("myMime", "myUrl", null, null); + + assert obj.getURL().equals("yourURL"); + + try { + m_artifactRepository.getHelper("yourMime"); + assert false : "We have not registered this helper."; + } + catch (IllegalArgumentException iae) { + // expected + } + } + + @Test( groups = { TestUtils.UNIT } ) + public void testResourceProcessorFiltering() throws InvalidSyntaxException { + m_artifactRepository.addHelper("myMime", new MockHelper()); + m_artifactRepository.addHelper(BundleHelper.MIMETYPE, new BundleHelperImpl()); + + createArtifact(BundleHelper.MIMETYPE, "normalBundle", "normalBundle", null); + + ArtifactObject resourceProcessor1 = createArtifact(BundleHelper.MIMETYPE, "resourceProcessor1", "resourceProcessor1", "somePID"); + ArtifactObject resourceProcessor2 = createArtifact(BundleHelper.MIMETYPE, "resourceProcessor2", "resourceProcessor2", "someOtherPID"); + + ArtifactObject myArtifact = createArtifact("myMime", "myArtifact", null, null); + + assert m_artifactRepository.get().size() == 2 : "We expect to find two artifacts, but we find " + m_artifactRepository.get().size(); + + List list = m_artifactRepository.get(m_artifactRepository.createFilter("(!(" + BundleHelper.KEY_SYMBOLICNAME + "=normalBundle))")); + assert (list.size() == 1) && list.contains(myArtifact) : "We expect to find one artifact when filtering, but we find " + list.size(); + + list = m_artifactRepository.getResourceProcessors(); + assert (list.size() == 2) && list.contains(resourceProcessor1) && list.contains(resourceProcessor2) : "We expect to find both our resource processors when asking for them; we find " + list.size() + " artifacts."; + + m_artifactRepository.get(m_artifactRepository.createFilter("(" + BundleHelper.MIMETYPE + "=my\\(Mi\\*me)")); + } + + private ArtifactObject createArtifact(String mimetype, String URL, String symbolicName, String processorPID) { + Map attributes = new HashMap(); + attributes.put(ArtifactObject.KEY_MIMETYPE, mimetype); + attributes.put(ArtifactObject.KEY_URL, URL); + Map tags = new HashMap(); + + if (symbolicName != null) { + attributes.put(BundleHelper.KEY_SYMBOLICNAME, symbolicName); + } + if (processorPID != null) { + attributes.put(BundleHelper.KEY_RESOURCE_PROCESSOR_PID, processorPID); + } + + return m_artifactRepository.create(attributes, tags); + } +} + +/** + * Helper for testing the ArtifactObject. In the constructor, a replaceURL can + * be passed in to test the attribute normalization. + */ +class MockHelper implements ArtifactHelper { + private final String m_replaceURL; + + MockHelper() { + this(null); + } + + MockHelper(String replaceURL) { + m_replaceURL = replaceURL; + } + + public Map checkAttributes(Map attributes) { + if ((m_replaceURL != null) && attributes.containsKey(ArtifactObject.KEY_URL)) { + attributes.put(ArtifactObject.KEY_URL, m_replaceURL); + } + return attributes; + } + + public Comparator getComparator() { + return null; + } + + public String[] getDefiningKeys() { + return new String[]{ArtifactObject.KEY_URL}; + } + + public String[] getMandatoryAttributes() { + return new String[]{ArtifactObject.KEY_URL}; + } + + public boolean canUse(ArtifactObject object) { + return false; + } + + public String getAssociationFilter(TYPE obj, Map properties) { + return null; + } + + public int getCardinality(TYPE obj, Map properties) { + return 0; + } + + public ArtifactPreprocessor getPreprocessor() { + return null; + } +} Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/CachedRepositoryImplTest.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/CachedRepositoryImplTest.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/CachedRepositoryImplTest.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/CachedRepositoryImplTest.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,124 @@ +/* + * 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.ace.repository.Repository; +import org.apache.ace.repository.ext.BackupRepository; +import org.apache.ace.repository.ext.CachedRepository; +import org.apache.ace.repository.ext.impl.CachedRepositoryImpl; +import org.apache.ace.test.utils.TestUtils; +import org.testng.annotations.Test; + +public class CachedRepositoryImplTest { + + /** + * Initial checkout: the remote repository contains some data for a given version, + * we make the cached repository do a checkout, and check whether all data arrives at the + * right places: in getLocal, and in the backup repository. + */ + @Test( groups = { TestUtils.UNIT } ) + public void testInitialCheckout() throws IllegalArgumentException, IOException { + Repository m_repository = new MockRepository(); + byte[] testContent = new byte[] {'i', 'n', 'i', 't', 'i', 'a', 'l'}; + m_repository.commit(new ByteArrayInputStream(testContent), 0); + BackupRepository m_backupRepository = new MockBackupRepository(); + + CachedRepository m_cachedRepository = new CachedRepositoryImpl(m_repository, m_backupRepository, 0); + + InputStream input = m_cachedRepository.checkout(1); + byte[] inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, testContent) : "We got something different than 'initial' from checkout: " + new String(inputBytes); + input = m_cachedRepository.getLocal(false); + inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, testContent) : "We got something different than 'initial' from getLocal: " + new String(inputBytes); + input = m_backupRepository.read(); + inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, testContent) : "We got something different than 'initial' from the backup repository: " + new String(inputBytes); + } + + /** + * There are two types of commit, one that takes an input stream, and one that + * simply flushes whatever is in the current to the remote repository. + * After each commit, we should be able to renew the cached repository, + * and checkout the data we put in before. + */ + @Test( groups = { TestUtils.UNIT } ) + public void testCommit() throws IllegalArgumentException, IOException { + Repository m_repository = new MockRepository(); + BackupRepository m_backupRepository = new MockBackupRepository(); + + CachedRepository m_cachedRepository = new CachedRepositoryImpl(m_repository, m_backupRepository, 0); + byte[] testContent = new byte[] {'i', 'n', 'i', 't', 'i', 'a', 'l'}; + + InputStream input = new ByteArrayInputStream(testContent); + m_cachedRepository.commit(input, 0); + + m_cachedRepository = new CachedRepositoryImpl(m_repository, m_backupRepository, 0); + input = m_cachedRepository.checkout(1); + byte[] inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, testContent) : "We got something different than 'initial' from checkout: " + new String(inputBytes); + + byte[] newTestContent = new byte[] {'n', 'e', 'w'}; + + m_cachedRepository.writeLocal(new ByteArrayInputStream(newTestContent)); + + m_cachedRepository.commit(); + + m_cachedRepository = new CachedRepositoryImpl(m_repository, m_backupRepository, 0); + input = m_cachedRepository.checkout(2); + inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, newTestContent) : "We got something different than 'new' from checkout: " + new String(inputBytes); + } + + /** + * After checking out and changing stuff, we want to revert to the old version. + * @throws IOException + * @throws IllegalArgumentException + */ + @Test( groups = { TestUtils.UNIT } ) + public void testRevert() throws IllegalArgumentException, IOException { + Repository m_repository = new MockRepository(); + BackupRepository m_backupRepository = new MockBackupRepository(); + + CachedRepository m_cachedRepository = new CachedRepositoryImpl(m_repository, m_backupRepository, 0); + byte[] testContent = new byte[] {'i', 'n', 'i', 't', 'i', 'a', 'l'}; + + InputStream input = new ByteArrayInputStream(testContent); + m_cachedRepository.commit(input, 0); + + m_cachedRepository = new CachedRepositoryImpl(m_repository, m_backupRepository, 0); + input = m_cachedRepository.checkout(1); + + byte[] newTestContent = new byte[] {'n', 'e', 'w'}; + + m_cachedRepository.writeLocal(new ByteArrayInputStream(newTestContent)); + input = m_cachedRepository.getLocal(false); + byte[] inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, newTestContent) : "We got something different than 'new' from getLocal: " + new String(inputBytes); + + m_cachedRepository.revert(); + input = m_cachedRepository.getLocal(false); + inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, testContent) : "We got something different than 'initial' from getLocal: " + new String(inputBytes); + } +} Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/FilebasedBackupRepositoryTest.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/FilebasedBackupRepositoryTest.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/FilebasedBackupRepositoryTest.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/FilebasedBackupRepositoryTest.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,75 @@ +/* + * 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.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.ace.repository.ext.impl.FilebasedBackupRepository; +import org.apache.ace.test.utils.TestUtils; +import org.testng.annotations.Test; + +public class FilebasedBackupRepositoryTest { + + /** + * A basic scenario: we write, backup, write again, and revert. + */ + @Test( groups = { TestUtils.UNIT } ) + public void testFilebasedBackupRepository() throws IOException { + File current = File.createTempFile("testFilebasedBackupRepository", null); + File backup = File.createTempFile("testFilebasedBackupRepository", null); + current.deleteOnExit(); + backup.deleteOnExit(); + + FilebasedBackupRepository rep = new FilebasedBackupRepository(current, backup); + + byte[] testContent = new byte[] {'i', 'n', 'i', 't', 'i', 'a', 'l'}; + + // write initial content + rep.write(new ByteArrayInputStream(testContent)); + + // read initial content + InputStream input = rep.read(); + byte[] inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, testContent) : "We got something different than 'initial' from read: " + new String(inputBytes); + + // backup what's in the repository + rep.backup(); + + // write new content + byte[] newTestContent = new byte[] {'n', 'e', 'w'}; + rep.write(new ByteArrayInputStream(newTestContent)); + + // read current content + input = rep.read(); + inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, newTestContent) : "We got something different than 'new' from read: " + new String(inputBytes); + + // revert to previous (initial) content + rep.restore(); + + // read current content + input = rep.read(); + inputBytes = AdminTestUtil.copy(input); + assert AdminTestUtil.byteArraysEqual(inputBytes, testContent) : "We got something different than 'initial' from read: " + new String(inputBytes); + } + +} Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockBackupRepository.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockBackupRepository.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockBackupRepository.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockBackupRepository.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,60 @@ +/* + * 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.ace.repository.ext.BackupRepository; + + +public class MockBackupRepository implements BackupRepository { + private byte[] m_current; + private byte[] m_backup; + + public boolean backup() throws IOException { + if (m_current == null) { + return false; + } + m_backup = AdminTestUtil.copy(m_current); + return true; + } + + public InputStream read() throws IOException { + return new ByteArrayInputStream(m_current); + } + + public boolean restore() throws IOException { + if (m_backup == null) { + return false; + } + m_current = AdminTestUtil.copy(m_backup); + return true; + } + + public void write(InputStream data) throws IOException { + m_current = AdminTestUtil.copy(data); + } + + public void delete() throws IOException { + m_current = null; + m_backup = null; + } +} Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockCachedRepository.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockCachedRepository.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockCachedRepository.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockCachedRepository.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,89 @@ +/* + * 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 org.apache.ace.range.SortedRangeSet; +import org.apache.ace.repository.ext.CachedRepository; + + +public class MockCachedRepository implements CachedRepository { + + public InputStream checkout(boolean fail) throws IOException { + // TODO Auto-generated method stub + return null; + } + + public boolean commit() throws IOException { + // TODO Auto-generated method stub + return false; + } + + public InputStream getLocal(boolean fail) throws IOException { + // TODO Auto-generated method stub + return null; + } + + public boolean revert() throws IOException { + // TODO Auto-generated method stub + return false; + } + + public void writeLocal(InputStream data) throws IOException { + // TODO Auto-generated method stub + + } + + public InputStream checkout(long version) throws IOException, IllegalArgumentException { + // TODO Auto-generated method stub + return null; + } + + public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException { + // TODO Auto-generated method stub + return false; + } + + public SortedRangeSet getRange() throws IOException { + // TODO Auto-generated method stub + return null; + } + + public boolean isCurrent() throws IOException { + // TODO Auto-generated method stub + return false; + } + + public long getHighestRemoteVersion() throws IOException { + // TODO Auto-generated method stub + return 0; + } + + public long getMostRecentVersion() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void deleteLocal() throws IOException { + // TODO Auto-generated method stub + } +} Added: ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockRepository.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockRepository.java?rev=1463576&view=auto ============================================================================== --- ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockRepository.java (added) +++ ace/trunk/org.apache.ace.client.repository/test/org/apache/ace/client/repository/impl/MockRepository.java Tue Apr 2 14:53:33 2013 @@ -0,0 +1,52 @@ +/* + * 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.ace.range.SortedRangeSet; +import org.apache.ace.repository.Repository; + + +public class MockRepository implements Repository { + private byte[] m_repo; + private int currentVersion = 0; + + public InputStream checkout(long version) throws IOException, IllegalArgumentException { + if (version == currentVersion) { + return new ByteArrayInputStream(m_repo); + } + return null; + } + + public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException { + if (fromVersion == currentVersion) { + currentVersion++; + m_repo = AdminTestUtil.copy(data); + return true; + } + return false; + } + + public SortedRangeSet getRange() throws IOException { + return new SortedRangeSet(new long[] {currentVersion}); + } +}