Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.javalang.Reflections;
import org.apache.brooklyn.util.net.Urls;
import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
import org.apache.brooklyn.util.text.Strings;
import org.apache.commons.beanutils.BeanUtils;
Expand Down Expand Up @@ -310,6 +311,27 @@ public static Object literal(Object expression) {
return expression;
}

/**
* Returns the arg with characters escaped so it is a valid part of a URL, or a
* {@link BrooklynDslDeferredSupplier} that returns this if the arguments are not yet fully
* resolved.
*
* See {@link Urls#encode(String)} for further details (it currently uses the encoding rules for
* "x-www-form-urlencoded")
*
* Do not call with a whole URL unless you want everything escaped (e.g. "http://myhost" will be
* encoded as "http%3A%2F%2Fmyhost").
*/
@DslAccessible
public static Object urlEncode(Object arg) {
if (resolved(arg)) {
// if all args are resolved, apply the transform now
return (arg == null) ? null : Urls.encode(arg.toString());
} else {
return new DslUrlEncode(arg);
}
}

/**
* Returns a formatted string or a {@link BrooklynDslDeferredSupplier} if the arguments
* are not yet fully resolved.
Expand All @@ -332,6 +354,50 @@ public static Object regexReplacement(final Object source, final Object pattern,
return new DslRegexReplacement(source, pattern, replacement);
}
}

/**
* Deferred execution of escaping a URL.
*
* @see DependentConfiguration#urlEncode(Object)
*/
protected static class DslUrlEncode extends BrooklynDslDeferredSupplier<String> {

private static final long serialVersionUID = -4849297712650560863L;

private final Object arg;

public DslUrlEncode(Object arg) {
this.arg = arg;
}

@Override
public final Maybe<String> getImmediately() {
return DependentConfiguration.urlEncodeImmediately(arg);
}

@Override
public Task<String> newTask() {
return DependentConfiguration.urlEncode(arg);
}

@Override
public int hashCode() {
return Objects.hashCode(arg);
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
DslUrlEncode that = DslUrlEncode.class.cast(obj);
return Objects.equal(this.arg, that.arg);
}

@Override
public String toString() {
return "$brooklyn:urlEncode("+arg+")";
}
}

/**
* Deferred execution of String formatting.
Expand All @@ -342,8 +408,8 @@ protected static class DslFormatString extends BrooklynDslDeferredSupplier<Strin

private static final long serialVersionUID = -4849297712650560863L;

private String pattern;
private Object[] args;
private final String pattern;
private final Object[] args;

public DslFormatString(String pattern, Object ...args) {
this.pattern = pattern;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,33 @@

import java.io.Reader;
import java.io.StringReader;
import java.util.Map;
import java.util.Set;

import org.apache.brooklyn.api.catalog.CatalogItem;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatform;
import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer;
import org.apache.brooklyn.camp.spi.Assembly;
import org.apache.brooklyn.camp.spi.AssemblyTemplate;
import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.StartableApplication;
import org.apache.brooklyn.core.entity.trait.Startable;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.core.mgmt.rebind.RebindOptions;
import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixture;
import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.stream.Streams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterMethod;
Expand Down Expand Up @@ -129,34 +137,31 @@ protected Reader loadYaml(String yamlFileName, String ...extraLines) throws Exce
return new StringReader(builder.toString());
}

/**
* @deprecated since 0.11.0, use {@link #createAndStartApplication(String)} instead,
* in the same way as {@link AbstractYamlTest}.
*/
@Deprecated
protected Entity createAndStartApplication(Reader input) throws Exception {
return createAndStartApplication(Streams.readFully(input));
}

protected Entity createAndStartApplication(String... multiLineYaml) throws Exception {
return createAndStartApplication(joinLines(multiLineYaml));
}

protected Entity createAndStartApplication(String input) throws Exception {
return createAndStartApplication(new StringReader(input));
return createAndStartApplication(input, MutableMap.<String,String>of());
}

protected Entity createAndStartApplication(Reader input) throws Exception {
AssemblyTemplate at = platform.pdp().registerDeploymentPlan(input);
Assembly assembly;
try {
assembly = at.getInstantiator().newInstance().instantiate(at, platform);
} catch (Exception e) {
getLogger().warn("Unable to instantiate " + at + " (rethrowing): " + e);
throw e;
}
getLogger().info("Test - created " + assembly);
final Entity app = mgmt().getEntityManager().getEntity(assembly.getId());
getLogger().info("App - " + app);

// wait for app to have started
Set<Task<?>> tasks = mgmt().getExecutionManager().getTasksWithAllTags(ImmutableList.of(
BrooklynTaskTags.EFFECTOR_TAG,
BrooklynTaskTags.tagForContextEntity(app),
BrooklynTaskTags.tagForEffectorCall(app, "start", ConfigBag.newInstance(ImmutableMap.of("locations", ImmutableMap.of())))));
Iterables.getOnlyElement(tasks).get();

protected Entity createAndStartApplication(String input, Map<String,?> startParameters) throws Exception {
EntitySpec<?> spec =
mgmt().getTypeRegistry().createSpecFromPlan(CampTypePlanTransformer.FORMAT, input, RegisteredTypeLoadingContexts.spec(Application.class), EntitySpec.class);
final Entity app = mgmt().getEntityManager().createEntity(spec);
getLogger().info("Test created app, and will now start " + app);

// start the app (happens automatically if we use camp to instantiate, but not if we use crate spec approach)
app.invoke(Startable.START, startParameters).get();
return app;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,34 @@ public void testDslFormatStringWithDeferredSupplier() throws Exception {
Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_NAME), "hello world");
}

@Test
public void testDslUrlEncode() throws Exception {
String unescapedVal = "name@domain?!/&:%";
String escapedVal = "name%40domain%3F%21%2F%26%3A%25";

String unescapedUrlFormat = "http://%s:password@mydomain.com";
String escapedUrl = "http://" + escapedVal + ":password@mydomain.com";

Entity testEntity = setupAndCheckTestEntityInBasicYamlWith(
" brooklyn.config:",
" test.confObject: \"" + unescapedVal + "\"",
" test.confName:",
" $brooklyn:urlEncode:",
" - $brooklyn:config(\"test.confObject\")",
" test.confUrl:",
" $brooklyn:formatString:",
" - " + unescapedUrlFormat,
" - $brooklyn:urlEncode:",
" - $brooklyn:config(\"test.confObject\")");

Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_NAME), escapedVal);
Assert.assertEquals(getConfigInTask(testEntity, ConfigKeys.newStringConfigKey("test.confUrl")), escapedUrl);

Entity e2 = rebind(testEntity);
Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_NAME), escapedVal);
Assert.assertEquals(getConfigInTask(e2, ConfigKeys.newStringConfigKey("test.confUrl")), escapedUrl);
}

@Test
public void testDslEntityById() throws Exception {
Entity testEntity = setupAndCheckTestEntityInBasicYamlWith(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.io.BaseEncoding;
import com.google.common.net.UrlEscapers;

public class CatalogOsgiLibraryTest extends AbstractYamlTest {

Expand Down Expand Up @@ -220,14 +219,34 @@ public void testLibraryUrlUsingExternalizedConfig() throws Exception {
CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0");
assertCatalogLibraryUrl(item, classpathUrl);
}

// TODO See https://issues.apache.org/jira/browse/BROOKLYN-421
// Need to somehow escape the username and password (or include them explicitly as a
// "Authorization" header instead of embedding them in the URI).

@Test(groups="Broken")
public void testLibraryUrlUsingExternalizedConfigForCredentials() throws Exception {
runLibraryUrlUsingExternalizedConfigForCredentials(true);
}

@Test
public void testLibraryUrlUsingExternalizedConfigForCredentialsLenient() throws Exception {
runLibraryUrlUsingExternalizedConfigForCredentials(false);
}

/**
* See https://issues.apache.org/jira/browse/BROOKLYN-421.
*
* TODO java.net.URLEncoder.encode() gets it wrong for:
* <ul>
* <li>" " (i.e. space - char %20). It turns that into "+" in the url.
* <li>"*" (i.e char %2A) is not escaped - this is wrong according to https://en.wikipedia.org/wiki/Percent-encoding (RFC 3986).
* </ul>
*/
protected void runLibraryUrlUsingExternalizedConfigForCredentials(boolean includeUrlEncoderBrokenChars) throws Exception {
StringBuilder passwordBuilder = new StringBuilder();
for (int i = 1; i < 128; i++) {
if (!includeUrlEncoderBrokenChars && (i == 32 || i == 42)) continue;
passwordBuilder.append((char)i);
}
String username = "myuser@mydomain.com";
String password = "Myp4ss@?/:!";
String password = passwordBuilder.toString();

// Add an externalized config provider, which will return us a username + password
Map<String, String> externalConfig = ImmutableMap.of("username", username, "password", password);
Expand All @@ -242,8 +261,10 @@ public void testLibraryUrlUsingExternalizedConfigForCredentials() throws Excepti
" libraries:",
" - $brooklyn:formatString:",
" - http://%s:%s@" + jarUrl.getHost() + ":" + jarUrl.getPort() + jarUrl.getPath(),
" - $brooklyn:external(\"myprovider\", \"username\")",
" - $brooklyn:external(\"myprovider\", \"password\")",
" - $brooklyn:urlEncode:",
" - $brooklyn:external(\"myprovider\", \"username\")",
" - $brooklyn:urlEncode:",
" - $brooklyn:external(\"myprovider\", \"password\")",
" item:",
" services:",
" - type: org.apache.brooklyn.test.osgi.entities.SimpleApplication");
Expand All @@ -257,8 +278,13 @@ public void testLibraryUrlUsingExternalizedConfigForCredentials() throws Excepti
assertEquals(authHeader, expectedHeader, "headers=" + Arrays.toString(req.getAllHeaders()));

// Expect url to have been correctly escaped
String escapedUsername = UrlEscapers.urlFragmentEscaper().escape(username);
String escapedPassword = UrlEscapers.urlFragmentEscaper().escape(password);
String escapedUsername = "myuser%40mydomain.com";
String escapedPassword;
if (includeUrlEncoderBrokenChars) {
escapedPassword = "%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E%7F";
} else {
escapedPassword = "%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%21%22%23%24%25%26%27%28%29%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E%7F";
}
String expectedUrl = "http://" + escapedUsername + ":" + escapedPassword+ "@" + jarUrl.getHost() + ":" + jarUrl.getPort() + jarUrl.getPath();

CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,43 @@ public void testEntity() throws Exception {
assertEquals(actualValue.get(), entity);
}

@Test
public void testFormatString() throws Exception {
// literals (non-deferred) can be resolved immediately
assertEquals(BrooklynDslCommon.formatString("myval"), "myval");
assertEquals(BrooklynDslCommon.formatString("%s", "myval"), "myval");

BrooklynDslDeferredSupplier<?> arg = BrooklynDslCommon.attributeWhenReady(TestApplication.MY_ATTRIBUTE.getName());
BrooklynDslDeferredSupplier<?> dsl = (BrooklynDslDeferredSupplier<?>) BrooklynDslCommon.formatString("%s", arg);

Maybe<?> actualValue = execDslImmediately(dsl, String.class, app, true);
assertTrue(actualValue.isAbsent());

app.sensors().set(TestApplication.MY_ATTRIBUTE, "myval");
assertEquals(execDslEventually(dsl, String.class, app, Asserts.DEFAULT_LONG_TIMEOUT).get(), "myval");
assertEquals(execDslImmediately(dsl, String.class, app, true).get(), "myval");
}

@Test
public void testUrlEncode() throws Exception {
String origVal = "name@domain?!/&:%";
String encodedVal = "name%40domain%3F%21%2F%26%3A%25";

// literals (non-deferred) can be resolved immediately
assertEquals(BrooklynDslCommon.urlEncode("myval"), "myval");
assertEquals(BrooklynDslCommon.urlEncode(origVal), encodedVal);

BrooklynDslDeferredSupplier<?> arg = BrooklynDslCommon.attributeWhenReady(TestApplication.MY_ATTRIBUTE.getName());
BrooklynDslDeferredSupplier<?> dsl = (BrooklynDslDeferredSupplier<?>) BrooklynDslCommon.urlEncode(arg);

Maybe<?> actualValue = execDslImmediately(dsl, String.class, app, true);
assertTrue(actualValue.isAbsent());

app.sensors().set(TestApplication.MY_ATTRIBUTE, origVal);
assertEquals(execDslEventually(dsl, String.class, app, Asserts.DEFAULT_LONG_TIMEOUT).get(), encodedVal);
assertEquals(execDslImmediately(dsl, String.class, app, true).get(), encodedVal);
}

@Test
public void testEntityNotFound() throws Exception {
BrooklynDslDeferredSupplier<?> dsl = BrooklynDslCommon.entity("myIdDoesNotExist");
Expand Down
Loading