blob: a2d85b2874a41c6f19259fcacb519d12713ecb44 [file] [log] [blame]
/*
* 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.it.repositoryadmin;
import static org.apache.ace.client.repository.stateful.StatefulTargetObject.TOPIC_ADDED;
import static org.apache.ace.client.repository.stateful.StatefulTargetObject.TOPIC_STATUS_CHANGED;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import org.apache.ace.client.repository.RepositoryAdminLoginContext;
import org.apache.ace.client.repository.helper.ArtifactHelper;
import org.apache.ace.client.repository.helper.ArtifactPreprocessor;
import org.apache.ace.client.repository.helper.PropertyResolver;
import org.apache.ace.client.repository.helper.bundle.BundleHelper;
import org.apache.ace.client.repository.object.Artifact2FeatureAssociation;
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.DistributionObject;
import org.apache.ace.client.repository.object.FeatureObject;
import org.apache.ace.client.repository.object.TargetObject;
import org.apache.ace.client.repository.stateful.StatefulTargetObject;
import org.apache.ace.client.repository.stateful.StatefulTargetObject.StoreState;
import org.apache.felix.dm.Component;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.useradmin.User;
/**
* Test cases for the template processing functionality.
*/
public class TemplateProcessorTest extends BaseRepositoryAdminTest {
static class MockArtifactHelper implements ArtifactHelper {
private final String m_mimetype;
private final ArtifactPreprocessor m_preprocessor;
MockArtifactHelper(String mimetype) {
this(mimetype, null);
}
MockArtifactHelper(String mimetype, ArtifactPreprocessor preprocessor) {
m_mimetype = mimetype;
m_preprocessor = preprocessor;
}
public boolean canUse(ArtifactObject object) {
return object.getMimetype().equals(m_mimetype);
}
public Map<String, String> checkAttributes(Map<String, String> attributes) {
return attributes;
}
public <TYPE extends ArtifactObject> String getAssociationFilter(TYPE obj, Map<String, String> properties) {
return ("(" + ArtifactObject.KEY_URL + "=" + obj.getURL() + ")");
}
public <TYPE extends ArtifactObject> int getCardinality(TYPE obj, Map<String, String> properties) {
return 1;
}
public Comparator<ArtifactObject> getComparator() {
return null;
}
public String[] getDefiningKeys() {
return new String[] { ArtifactObject.KEY_URL };
}
public String[] getMandatoryAttributes() {
return new String[] { ArtifactObject.KEY_URL };
}
public ArtifactPreprocessor getPreprocessor() {
return m_preprocessor;
}
};
static class MockArtifactPreprocessor implements ArtifactPreprocessor {
private PropertyResolver m_props;
public boolean needsNewVersion(String url, PropertyResolver props, String targetID, String fromVersion) {
return false;
}
public String preprocess(String url, PropertyResolver props, String targetID, String version, URL obrBase)
throws IOException {
m_props = props;
return url;
}
PropertyResolver getProps() {
return m_props;
}
}
public void testStatefulApprovalWithArtifacts() throws Exception {
setupRepository();
// some setup: we need a helper.
ArtifactHelper myHelper = new MockArtifactHelper("mymime");
Properties serviceProps = new Properties();
serviceProps.put(ArtifactHelper.KEY_MIMETYPE, "mymime");
Component myHelperService = m_dependencyManager.createComponent()
.setInterface(ArtifactHelper.class.getName(), serviceProps)
.setImplementation(myHelper);
m_dependencyManager.add(myHelperService);
// Empty tag map to be reused througout test
final Map<String, String> tags = new HashMap<>();
// First, create a bundle and two artifacts, but do not provide a processor for the artifacts.
ArtifactObject b1 = createBasicBundleObject("bundle1");
Map<String, String> attr = new HashMap<>();
attr.put(ArtifactObject.KEY_URL, "http://myobject");
attr.put(ArtifactObject.KEY_PROCESSOR_PID, "my.processor.pid");
attr.put(ArtifactHelper.KEY_MIMETYPE, "mymime");
ArtifactObject a1 = m_artifactRepository.create(attr, tags);
attr = new HashMap<>();
attr.put(ArtifactObject.KEY_URL, "http://myotherobject");
attr.put(ArtifactObject.KEY_PROCESSOR_PID, "my.processor.pid");
attr.put(ArtifactObject.KEY_RESOURCE_ID, "mymime");
attr.put(ArtifactHelper.KEY_MIMETYPE, "mymime");
ArtifactObject a2 = m_artifactRepository.create(attr, tags);
FeatureObject g = createBasicFeatureObject("feature");
DistributionObject l = createBasicDistributionObject("distribution");
attr = new HashMap<>();
attr.put(TargetObject.KEY_ID, "myTarget");
StatefulTargetObject sgo = m_statefulTargetRepository.preregister(attr, tags);
m_artifact2featureRepository.create(b1, g);
m_artifact2featureRepository.create(a1, g);
m_artifact2featureRepository.create(a2, g);
m_feature2distributionRepository.create(g, l);
m_distribution2targetRepository.create(l, sgo.getTargetObject());
sgo.approve();
runAndWaitForEvent(new Callable<Void>() {
public Void call() throws Exception {
m_repositoryAdmin.commit();
return null;
}
}, false, TOPIC_STATUS_CHANGED);
assertEquals("Store state for target should still be new, because the resource processor is missing.", StoreState.New, sgo.getStoreState());
// Now, add a processor for the artifact.
attr = new HashMap<>();
attr.put(ArtifactObject.KEY_URL, "http://myprocessor");
attr.put(BundleHelper.KEY_RESOURCE_PROCESSOR_PID, "my.processor.pid");
attr.put(BundleHelper.KEY_SYMBOLICNAME, "my.processor.bundle");
attr.put(BundleHelper.KEY_VERSION, "1.0.0");
attr.put(ArtifactHelper.KEY_MIMETYPE, BundleHelper.MIMETYPE);
ArtifactObject b2 = m_artifactRepository.create(attr, tags);
sgo.approve();
runAndWaitForEvent(new Callable<Void>() {
public Void call() throws Exception {
m_repositoryAdmin.commit();
return null;
}
}, false, DeploymentVersionObject.TOPIC_ADDED, TOPIC_STATUS_CHANGED);
DeploymentVersionObject dep = m_deploymentVersionRepository.getMostRecentDeploymentVersion(sgo.getID());
DeploymentArtifact[] toDeploy = dep.getDeploymentArtifacts();
assertEquals("We expect to find four artifacts to deploy;", 4, toDeploy.length);
DeploymentArtifact bundle1 = toDeploy[0];
assertEquals(b1.getURL(), bundle1.getUrl());
DeploymentArtifact bundle2 = toDeploy[1];
assertEquals(b2.getURL(), bundle2.getUrl());
assertEquals("true", bundle2.getDirective(DeploymentArtifact.DIRECTIVE_ISCUSTOMIZER));
DeploymentArtifact artifact1 = toDeploy[2];
assertEquals(a1.getURL(), artifact1.getUrl());
assertEquals("my.processor.pid", artifact1.getDirective(DeploymentArtifact.DIRECTIVE_KEY_PROCESSORID));
DeploymentArtifact artifact2 = toDeploy[3];
assertEquals(a2.getURL(), artifact2.getUrl());
assertEquals("my.processor.pid", artifact2.getDirective(DeploymentArtifact.DIRECTIVE_KEY_PROCESSORID));
assertEquals(a2.getResourceId(), artifact2.getDirective(DeploymentArtifact.DIRECTIVE_KEY_RESOURCE_ID));
// Now, add a new version of the processor (ACE-373)
assertFalse("There should be no changes.", sgo.needsApprove());
attr = new HashMap<>();
attr.put(ArtifactObject.KEY_URL, "http://myprocessor/v2");
attr.put(BundleHelper.KEY_RESOURCE_PROCESSOR_PID, "my.processor.pid");
attr.put(BundleHelper.KEY_SYMBOLICNAME, "my.processor.bundle");
attr.put(BundleHelper.KEY_VERSION, "2.0.0");
attr.put(ArtifactHelper.KEY_MIMETYPE, BundleHelper.MIMETYPE);
ArtifactObject b3 = m_artifactRepository.create(attr, tags);
assertTrue("By adding a resource processor, we should have triggered a change that needs to be approved.", sgo.needsApprove());
sgo.approve();
runAndWaitForEvent(new Callable<Void>() {
public Void call() throws Exception {
m_repositoryAdmin.commit();
return null;
}
}, false, DeploymentVersionObject.TOPIC_ADDED, TOPIC_STATUS_CHANGED);
dep = m_deploymentVersionRepository.getMostRecentDeploymentVersion(sgo.getID());
toDeploy = dep.getDeploymentArtifacts();
assertEquals("We expect to find four artifacts to deploy;", 4, toDeploy.length);
boolean foundBundle = false;
boolean foundProcessor = false;
boolean foundArtifact1 = false;
boolean foundArtifact2 = false;
for (DeploymentArtifact a : toDeploy) {
String url = a.getUrl();
if (url.equals(b1.getURL())) {
foundBundle = true;
}
else if (url.equals(b3.getURL())) {
assertEquals("true", a.getDirective(DeploymentArtifact.DIRECTIVE_ISCUSTOMIZER));
foundProcessor = true;
}
else if (url.equals(a1.getURL())) {
assertEquals("my.processor.pid", a.getDirective(DeploymentArtifact.DIRECTIVE_KEY_PROCESSORID));
foundArtifact1 = true;
}
else if (url.equals(a2.getURL())) {
assertEquals("my.processor.pid", a.getDirective(DeploymentArtifact.DIRECTIVE_KEY_PROCESSORID));
assertEquals(a2.getResourceId(), a.getDirective(DeploymentArtifact.DIRECTIVE_KEY_RESOURCE_ID));
foundArtifact2 = true;
}
}
assertTrue("Could not find bundle in deployment", foundBundle);
assertTrue("Could not find processor in deployment", foundProcessor);
assertTrue("Could not find artifact 1 in deployment", foundArtifact1);
assertTrue("Could not find artifact 2 in deployment", foundArtifact2);
// Now, let's add a new resource processor that is *older* than the one we already have.
// Nothing should change.
assertFalse("There should be no changes.", sgo.needsApprove());
attr = new HashMap<>();
attr.put(ArtifactObject.KEY_URL, "http://myprocessor/v1.5");
attr.put(BundleHelper.KEY_RESOURCE_PROCESSOR_PID, "my.processor.pid");
attr.put(BundleHelper.KEY_SYMBOLICNAME, "my.processor.bundle");
attr.put(BundleHelper.KEY_VERSION, "1.5.0");
attr.put(ArtifactHelper.KEY_MIMETYPE, BundleHelper.MIMETYPE);
m_artifactRepository.create(attr, tags);
assertFalse("By adding an older resource processor, we should not have triggered a change.", sgo.needsApprove());
cleanUp();
m_dependencyManager.remove(myHelperService);
}
private void setupRepository() throws IOException, InterruptedException, InvalidSyntaxException {
User user = new MockUser();
addRepository("storeInstance", "apache", "store", true);
addRepository("targetInstance", "apache", "target", true);
addRepository("deploymentInstance", "apache", "deployment", true);
RepositoryAdminLoginContext loginContext = m_repositoryAdmin.createLoginContext(user);
loginContext
.add(loginContext.createShopRepositoryContext()
.setLocation(m_endpoint).setCustomer("apache").setName("store").setWriteable())
.add(loginContext.createTargetRepositoryContext()
.setLocation(m_endpoint).setCustomer("apache").setName("target").setWriteable())
.add(loginContext.createDeploymentRepositoryContext()
.setLocation(m_endpoint).setCustomer("apache").setName("deployment").setWriteable());
m_repositoryAdmin.login(loginContext);
m_repositoryAdmin.checkout();
}
/**
* Tests the full template mechanism, from importing templatable artifacts, to creating deployment
* versions with it. It uses the configuration (autoconf) helper, which uses a VelocityBased preprocessor.
*/
public void testTemplateProcessing() throws Exception {
setupRepository();
addObr("/obr", "store");
// create some template things
String xmlHeader =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><metatype:MetaData xmlns:metatype=\"http://www.osgi.org/xmlns/metatype/v1.0.0\">";
String xmlFooter = "</metatype:MetaData>";
String noTemplate = "<Attribute content=\"http://someURL\"/>";
String noTemplateProcessed = "<Attribute content=\"http://someURL\"/>";
final File noTemplateFile = createFileWithContents("template", ".xml", xmlHeader + noTemplate + xmlFooter);
String simpleTemplate = "<Attribute content=\"http://$context.name\"/>";
String simpleTemplateProcessed = "<Attribute content=\"http://mydistribution\"/>";
File simpleTemplateFile = createFileWithContents("template", ".xml", xmlHeader + simpleTemplate + xmlFooter);
// create some tree from artifacts to a target
FeatureObject go = runAndWaitForEvent(new Callable<FeatureObject>() {
public FeatureObject call() throws Exception {
ArtifactObject b1 = createBasicBundleObject("myBundle");
createBasicBundleObject("myProcessor", "1.0.0", "org.osgi.deployment.rp.autoconf");
FeatureObject go = createBasicFeatureObject("myfeature");
DistributionObject lo = createBasicDistributionObject("mydistribution");
TargetObject gwo = createBasicTargetObject("templatetarget2");
m_artifact2featureRepository.create(b1, go);
// note that we do not associate b2: this is a resource processor, so it will be packed
// implicitly. It should not be available to a preprocessor either.
m_feature2distributionRepository.create(go, lo);
m_distribution2targetRepository.create(lo, gwo);
return go;
}
}, false, TOPIC_ADDED);
ArtifactObject a1 = m_artifactRepository.importArtifact(noTemplateFile.toURI().toURL(), true);
Artifact2FeatureAssociation a2g = m_artifact2featureRepository.create(a1, go);
final StatefulTargetObject sgo = findStatefulTarget("templatetarget2");
// create a deploymentversion
assertTrue("With the new assignments, the SGO should need approval.", sgo.needsApprove());
sgo.approve();
runAndWaitForEvent(new Callable<Void>() {
public Void call() throws Exception {
m_repositoryAdmin.commit();
return null;
}
}, false, DeploymentVersionObject.TOPIC_ADDED, TOPIC_STATUS_CHANGED);
// find the deployment version
DeploymentVersionObject dvo = m_deploymentVersionRepository.getMostRecentDeploymentVersion("templatetarget2");
String inFile = tryGetStringFromURL(findXmlUrlInDeploymentObject(dvo), 10, 100);
assertEquals(xmlHeader + noTemplateProcessed + xmlFooter, inFile);
// try the simple template
m_artifact2featureRepository.remove(a2g);
a1 = m_artifactRepository.importArtifact(simpleTemplateFile.toURI().toURL(), true);
a2g = m_artifact2featureRepository.create(a1, go);
sgo.approve();
runAndWaitForEvent(new Callable<Void>() {
public Void call() throws Exception {
m_repositoryAdmin.commit();
return null;
}
}, false, DeploymentVersionObject.TOPIC_ADDED, TOPIC_STATUS_CHANGED);
// find the deployment version
dvo = m_deploymentVersionRepository.getMostRecentDeploymentVersion("templatetarget2");
// sleep for a while, to allow the OBR to process the file.
Thread.sleep(250);
inFile = tryGetStringFromURL(findXmlUrlInDeploymentObject(dvo), 10, 100);
assertEquals(xmlHeader + simpleTemplateProcessed + xmlFooter, inFile);
}
/**
* Tests the template processing mechanism: given a custom processor, do the correct calls go out?
*/
public void testTemplateProcessingInfrastructure() throws Exception {
setupRepository();
// create a preprocessor
MockArtifactPreprocessor preprocessor = new MockArtifactPreprocessor();
// create a helper
MockArtifactHelper helper = new MockArtifactHelper("mymime", preprocessor);
// register preprocessor and helper
Properties serviceProps = new Properties();
serviceProps.put(ArtifactHelper.KEY_MIMETYPE, "mymime");
Component helperService = m_dependencyManager.createComponent()
.setInterface(ArtifactHelper.class.getName(), serviceProps)
.setImplementation(helper);
m_dependencyManager.add(helperService);
String targetId = "templatetarget";
createBasicBundleObject("myProcessor", "1.0.0", "myProcessor.pid");
final ArtifactObject b1 = createBasicBundleObject("myBundle");
final ArtifactObject a1 = createBasicArtifactObject("myArtifact", "mymime", "myProcessor.pid");
final FeatureObject go = createBasicFeatureObject("myfeature");
final DistributionObject lo = createBasicDistributionObject("mydistribution");
final TargetObject gwo = createBasicTargetObject(targetId);
// create some tree from artifacts to a target
runAndWaitForEvent(new Callable<Object>() {
public Object call() throws Exception {
m_artifact2featureRepository.create(b1, go);
// note that we do not associate b2: this is a resource processor, so it will be packed
// implicitly. It should not be available to a preprocessor either.
m_artifact2featureRepository.create(a1, go);
m_feature2distributionRepository.create(go, lo);
m_distribution2targetRepository.create(lo, gwo);
return null;
}
}, false, TOPIC_STATUS_CHANGED);
StatefulTargetObject sgo = findStatefulTarget(targetId);
assertNotNull("Failed to find our target in the repository?!", sgo);
// wait until needsApprove is true; depending on timing, this could have happened before or after the TOPIC_ADDED.
int attempts = 0;
while (!sgo.needsApprove() && (attempts++ < 10)) {
Thread.sleep(100);
}
assertTrue("With the new assignments, the SGO should need approval.", sgo.needsApprove());
// create a deploymentversion
sgo.approve();
runAndWaitForEvent(new Callable<Void>() {
public Void call() throws Exception {
m_repositoryAdmin.commit();
return null;
}
}, false, DeploymentVersionObject.TOPIC_ADDED, TOPIC_STATUS_CHANGED);
// the preprocessor now has gotten its properties; inspect these
PropertyResolver target = preprocessor.getProps();
assertTrue("The property resolver should be able to resolve 'id'.", target.get("id").startsWith(targetId));
assertTrue("The property resolver should be able to resolve 'name'.", target.get("name").startsWith("mydistribution"));
assertNull("The property resolver should not be able to resolve 'someunknownproperty'.", target.get("someunknownproperty"));
cleanUp(); // we need to do this before the helper goes away
m_dependencyManager.remove(helperService);
}
private String tryGetStringFromURL(URL url, int tries, int interval) throws Exception {
while (true) {
try {
List<String> result = getResponse(url);
StringBuilder sb = new StringBuilder();
for (String line : result) {
if (sb.length() > 0) {
sb.append('\n');
}
sb.append(line);
}
return sb.toString();
}
catch (IOException ioe) {
if (--tries == 0) {
throw ioe;
} else {
Thread.sleep(interval);
}
}
}
}
/**
* The following code is borrowed from RepositoryTest.java, and is used to instantiate and
* use repository servlets.
*/
private StatefulTargetObject findStatefulTarget(String targetID) throws Exception {
int count = 10;
while (count-- > 0) {
for (StatefulTargetObject sgo : m_statefulTargetRepository.get()) {
if (sgo.getID().equals(targetID)) {
return sgo;
}
}
Thread.sleep(100);
}
return null;
}
/**
* Creates a temporary file with the given name and extension, and stores the given
* contents in it.
*/
private File createFileWithContents(String name, String extension, String contents) throws IOException {
File file = File.createTempFile(name, extension);
file.deleteOnExit();
Writer w = new OutputStreamWriter(new FileOutputStream(file));
w.write(contents);
w.close();
return file;
}
/**
* Helper method for testTemplateProcessing; finds the URL of the first deploymentartifact
* with 'xml' in its url.
*/
private URL findXmlUrlInDeploymentObject(DeploymentVersionObject dvo) throws MalformedURLException {
DeploymentArtifact[] artifacts = dvo.getDeploymentArtifacts();
for (DeploymentArtifact da : artifacts) {
if (da.getUrl().contains("xml")) {
return new URL(da.getUrl());
}
}
return null;
}
}