Allow dependencies to be specified when compiling, using maven coords

This commit is contained in:
Andy Clement
2017-07-04 16:44:20 -07:00
committed by markfisher
parent 5ba41cd3ce
commit 2fdfda9416
10 changed files with 1273 additions and 4 deletions

View File

@@ -46,6 +46,31 @@
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.3.9</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>3.3.9</version>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-file</artifactId>
<version>1.0.2.v20150114</version>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-http</artifactId>
<version>1.0.2.v20150114</version>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-connector-basic</artifactId>
<version>1.0.2.v20150114</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.function.compiler.java;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -41,6 +42,8 @@ public class CompilationResult {
List<Class<?>> compiledClasses = new ArrayList<>();
private Map<String, byte[]> classBytes = new HashMap<>();
private List<File> resolvedAdditionalDependencies = new ArrayList<>();
public CompilationResult(boolean successfulCompilation) {
this.successfulCompilation = successfulCompilation;
@@ -49,6 +52,14 @@ public class CompilationResult {
public void addClassBytes(String name, byte[] bytes) {
this.classBytes.put(name, bytes);
}
public void setResolvedAdditionalDependencies(List<File> resolvedAdditionalDependencies) {
this.resolvedAdditionalDependencies = resolvedAdditionalDependencies;
}
public List<File> getResolvedAdditionalDependencies() {
return this.resolvedAdditionalDependencies;
}
public byte[] getClassBytes(String classname) {
return this.classBytes.get(classname);
@@ -66,8 +77,12 @@ public class CompilationResult {
return Collections.unmodifiableList(compilationMessages);
}
public void recordCompilationMessage(CompilationMessage compilationMessage) {
this.compilationMessages.add(compilationMessage);
public void recordCompilationMessage(CompilationMessage message) {
this.compilationMessages.add(message);
}
public void recordCompilationMessages(List<CompilationMessage> messages) {
this.compilationMessages.addAll(messages);
}
public void setCompiledClasses(List<Class<?>> compiledClasses) {

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* Licensed 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.springframework.cloud.function.compiler.java;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.ProxySelector;
import org.eclipse.aether.repository.RemoteRepository;
/**
* Composite {@link ProxySelector}.
*
* @author Dave Syer
*/
public class CompositeProxySelector implements ProxySelector {
private List<ProxySelector> selectors = new ArrayList<ProxySelector>();
public CompositeProxySelector(List<ProxySelector> selectors) {
this.selectors = selectors;
}
@Override
public Proxy getProxy(RemoteRepository repository) {
for (ProxySelector selector : this.selectors) {
Proxy proxy = selector.getProxy(repository);
if (proxy != null) {
return proxy;
}
}
return null;
}
}

View File

@@ -0,0 +1,477 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* Licensed 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.springframework.cloud.function.compiler.java;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import javax.inject.Singleton;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.MavenArtifactRepository;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.DefaultModelReader;
import org.apache.maven.model.io.ModelReader;
import org.apache.maven.model.locator.DefaultModelLocator;
import org.apache.maven.model.locator.ModelLocator;
import org.apache.maven.model.validation.DefaultModelValidator;
import org.apache.maven.model.validation.ModelValidator;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.DependencyResolutionResult;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingRequest.RepositoryMerging;
import org.apache.maven.project.ProjectBuildingResult;
import org.apache.maven.repository.internal.DefaultArtifactDescriptorReader;
import org.apache.maven.repository.internal.DefaultVersionRangeResolver;
import org.apache.maven.repository.internal.DefaultVersionResolver;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.repository.internal.SnapshotMetadataGeneratorFactory;
import org.apache.maven.repository.internal.VersionsMetadataGeneratorFactory;
import org.apache.maven.settings.Profile;
import org.apache.maven.settings.Repository;
import org.codehaus.plexus.ContainerConfiguration;
import org.codehaus.plexus.DefaultContainerConfiguration;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.MutablePlexusContainer;
import org.codehaus.plexus.PlexusConstants;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.classworlds.ClassWorld;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.impl.ArtifactDescriptorReader;
import org.eclipse.aether.impl.MetadataGeneratorFactory;
import org.eclipse.aether.impl.VersionRangeResolver;
import org.eclipse.aether.impl.VersionResolver;
import org.eclipse.aether.impl.guice.AetherModule;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
import org.eclipse.aether.repository.ProxySelector;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.eclipse.aether.util.repository.JreProxySelector;
import org.eclipse.sisu.inject.DefaultBeanLocator;
import org.eclipse.sisu.plexus.ClassRealmManager;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
public class DependencyResolver {
private static DependencyResolver instance = new DependencyResolver();
private static Properties globals;
private LocalRepositoryManagerFactory localRepositoryManagerFactory;
private PlexusContainer container;
private final Object lock = new Object();
private ProjectBuilder projectBuilder;
private RepositorySystem repositorySystem;
private MavenSettings settings;
public static DependencyResolver instance() {
return instance;
}
public static void close() {
instance = new DependencyResolver();
}
private DependencyResolver() {
}
private void initialize() {
if (this.container == null) {
synchronized (lock) {
if (this.container == null) {
ClassWorld classWorld = new ClassWorld("plexus.core",
Thread.currentThread().getContextClassLoader());
ContainerConfiguration config = new DefaultContainerConfiguration()
.setClassWorld(classWorld)
.setRealm(classWorld.getClassRealm("plexus.core"))
.setClassPathScanning(PlexusConstants.SCANNING_INDEX)
.setAutoWiring(true).setName("maven");
PlexusContainer container;
try {
container = new DefaultPlexusContainer(config, new AetherModule(),
new DependencyResolutionModule());
localRepositoryManagerFactory = container
.lookup(LocalRepositoryManagerFactory.class);
container.addComponent(
new ClassRealmManager((MutablePlexusContainer) container,
new DefaultBeanLocator()),
ClassRealmManager.class.getName());
projectBuilder = container.lookup(ProjectBuilder.class);
repositorySystem = container.lookup(RepositorySystem.class);
}
catch (Exception e) {
throw new IllegalStateException("Cannot create container", e);
}
this.container = container;
this.settings = new MavenSettingsReader().readSettings();
}
}
}
}
public List<Dependency> dependencies(Resource resource) {
return dependencies(resource, new Properties());
}
public List<Dependency> dependencies(final Resource resource,
final Properties properties) {
initialize();
try {
ProjectBuildingRequest request = getProjectBuildingRequest(properties);
request.setResolveDependencies(true);
synchronized (DependencyResolver.class) {
ProjectBuildingResult result = projectBuilder
.build(new PropertiesModelSource(properties, resource), request);
DependencyResolver.globals = null;
DependencyResolutionResult dependencies = result
.getDependencyResolutionResult();
if (!dependencies.getUnresolvedDependencies().isEmpty()) {
StringBuilder builder = new StringBuilder();
for (Dependency dependency : dependencies
.getUnresolvedDependencies()) {
List<Exception> errors = dependencies
.getResolutionErrors(dependency);
for (Exception exception : errors) {
if (builder.length() > 0) {
builder.append("\n");
}
builder.append(exception.getMessage());
}
}
throw new RuntimeException(builder.toString());
}
return runtime(dependencies.getDependencies());
}
}
catch (ProjectBuildingException | NoLocalRepositoryManagerException e) {
throw new IllegalStateException("Cannot build model", e);
}
}
public File resolve(Dependency dependency) {
initialize();
return collectNonTransitive(Arrays.asList(dependency)).iterator().next()
.getArtifact().getFile();
}
private List<Dependency> runtime(List<Dependency> dependencies) {
List<Dependency> list = new ArrayList<>();
for (Dependency dependency : dependencies) {
if (!"test".equals(dependency.getScope())
&& !"provided".equals(dependency.getScope())) {
list.add(dependency);
}
}
return list;
}
private ProjectBuildingRequest getProjectBuildingRequest(Properties properties)
throws NoLocalRepositoryManagerException {
DefaultProjectBuildingRequest projectBuildingRequest = new DefaultProjectBuildingRequest();
DefaultRepositorySystemSession session = createSession(properties);
projectBuildingRequest.setRepositoryMerging(RepositoryMerging.REQUEST_DOMINANT);
projectBuildingRequest.setRemoteRepositories(mavenRepositories(properties));
projectBuildingRequest.getRemoteRepositories()
.addAll(mavenRepositories(settings));
projectBuildingRequest.setRepositorySession(session);
projectBuildingRequest.setProcessPlugins(false);
projectBuildingRequest.setBuildStartTime(new Date());
projectBuildingRequest.setUserProperties(properties);
projectBuildingRequest.setSystemProperties(System.getProperties());
return projectBuildingRequest;
}
private Collection<? extends ArtifactRepository> mavenRepositories(
MavenSettings settings) {
List<ArtifactRepository> list = new ArrayList<>();
for (Profile profile : settings.getActiveProfiles()) {
for (Repository repository : profile.getRepositories()) {
addRepositoryIfMissing(list, repository.getId(), repository.getUrl(),
repository.getReleases() != null
? repository.getReleases().isEnabled() : true,
repository.getSnapshots() != null
? repository.getSnapshots().isEnabled() : false);
}
}
return list;
}
private List<ArtifactRepository> mavenRepositories(Properties properties) {
List<ArtifactRepository> list = new ArrayList<>();
addRepositoryIfMissing(list, "spring-snapshots", "https://repo.spring.io/libs-snapshot", true, true);
addRepositoryIfMissing(list, "central", "https://repo1.maven.org/maven2", true, false);
return list;
}
private List<RemoteRepository> aetherRepositories(Properties properties) {
List<RemoteRepository> list = new ArrayList<>();
for (ArtifactRepository input : mavenRepositories(properties)) {
list.add(remote(input));
}
return list;
}
private RemoteRepository remote(ArtifactRepository input) {
return new RemoteRepository.Builder(input.getId(), input.getLayout().getId(),
input.getUrl()).setSnapshotPolicy(policy(input.getSnapshots()))
.setReleasePolicy(policy(input.getReleases())).build();
}
private RepositoryPolicy policy(ArtifactRepositoryPolicy input) {
RepositoryPolicy policy = new RepositoryPolicy(input.isEnabled(),
RepositoryPolicy.UPDATE_POLICY_DAILY,
RepositoryPolicy.CHECKSUM_POLICY_WARN);
return policy;
}
private void addRepositoryIfMissing(List<ArtifactRepository> list, String id,
String url, boolean releases, boolean snapshots) {
for (ArtifactRepository repo : list) {
if (url.equals(repo.getUrl())) {
return;
}
if (id.equals(repo.getId())) {
return;
}
}
list.add(repo(id, url, releases, snapshots));
}
private ArtifactRepository repo(String id, String url, boolean releases,
boolean snapshots) {
MavenArtifactRepository repository = new MavenArtifactRepository();
repository.setLayout(new DefaultRepositoryLayout());
repository.setId(id);
repository.setUrl(url);
ArtifactRepositoryPolicy enabled = new ArtifactRepositoryPolicy();
enabled.setEnabled(true);
ArtifactRepositoryPolicy disabled = new ArtifactRepositoryPolicy();
disabled.setEnabled(false);
repository.setReleaseUpdatePolicy(releases ? enabled : disabled);
repository.setSnapshotUpdatePolicy(snapshots ? enabled : disabled);
return repository;
}
private DefaultRepositorySystemSession createSession(Properties properties)
throws NoLocalRepositoryManagerException {
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
LocalRepository repository = localRepository(properties);
session.setLocalRepositoryManager(
localRepositoryManagerFactory.newInstance(session, repository));
applySettings(session);
ProxySelector existing = session.getProxySelector();
if (existing == null || !(existing instanceof CompositeProxySelector)) {
JreProxySelector fallback = new JreProxySelector();
ProxySelector selector = existing == null ? fallback
: new CompositeProxySelector(Arrays.asList(existing, fallback));
session.setProxySelector(selector);
}
return session;
}
private void applySettings(DefaultRepositorySystemSession session) {
MavenSettingsReader.applySettings(settings, session);
}
private LocalRepository localRepository(Properties properties) {
return new LocalRepository(getM2RepoDirectory());
}
public Model readModel(Resource resource) {
return readModel(resource, new Properties());
}
public Model readModel(final Resource resource, final Properties properties) {
initialize();
try {
ProjectBuildingRequest request = getProjectBuildingRequest(properties);
request.setResolveDependencies(false);
ProjectBuildingResult result = projectBuilder
.build(new PropertiesModelSource(properties, resource), request);
return result.getProject().getModel();
}
catch (Exception e) {
throw new IllegalStateException("Failed to build model from effective pom",
e);
}
}
private File getM2RepoDirectory() {
return new File(getDefaultM2HomeDirectory(), "repository");
}
private File getDefaultM2HomeDirectory() {
String mavenRoot = System.getProperty("maven.home");
if (StringUtils.hasLength(mavenRoot)) {
return new File(mavenRoot);
}
return new File(System.getProperty("user.home"), ".m2");
}
private List<ArtifactResult> collectNonTransitive(List<Dependency> dependencies) {
try {
List<ArtifactRequest> artifactRequests = getArtifactRequests(dependencies);
List<ArtifactResult> result = this.repositorySystem
.resolveArtifacts(createSession(new Properties()), artifactRequests);
return result;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private List<ArtifactRequest> getArtifactRequests(List<Dependency> dependencies) {
List<ArtifactRequest> list = new ArrayList<>();
for (Dependency dependency : dependencies) {
ArtifactRequest request = new ArtifactRequest(dependency.getArtifact(), null,
null);
request.setRepositories(aetherRepositories(new Properties()));
list.add(request);
}
return list;
}
static Properties getGlobals() {
return globals;
}
@SuppressWarnings("deprecation")
private static final class PropertiesModelSource
implements org.apache.maven.model.building.ModelSource {
private final Properties properties;
private final Resource resource;
private PropertiesModelSource(Properties properties, Resource resource) {
this.properties = properties;
this.resource = resource;
}
@Override
public InputStream getInputStream() throws IOException {
DependencyResolver.globals = properties;
return new BufferedInputStream(resource.getInputStream()) {
@Override
public void close() throws IOException {
DependencyResolver.globals = null;
super.close();
}
};
}
@Override
public String getLocation() {
return resource.getDescription();
}
}
}
class DependencyResolutionModule extends AbstractModule {
@Override
protected void configure() {
bind(ModelLocator.class).to(DefaultModelLocator.class).in(Singleton.class);
bind(ModelReader.class).to(DefaultModelReader.class).in(Singleton.class);
bind(ModelValidator.class).to(DefaultModelValidator.class).in(Singleton.class);
bind(RepositoryConnectorFactory.class).to(BasicRepositoryConnectorFactory.class)
.in(Singleton.class);
bind(ArtifactDescriptorReader.class) //
.to(DefaultArtifactDescriptorReader.class).in(Singleton.class);
bind(VersionResolver.class) //
.to(DefaultVersionResolver.class).in(Singleton.class);
bind(VersionRangeResolver.class) //
.to(DefaultVersionRangeResolver.class).in(Singleton.class);
bind(MetadataGeneratorFactory.class).annotatedWith(Names.named("snapshot")) //
.to(SnapshotMetadataGeneratorFactory.class).in(Singleton.class);
bind(MetadataGeneratorFactory.class).annotatedWith(Names.named("versions")) //
.to(VersionsMetadataGeneratorFactory.class).in(Singleton.class);
bind(TransporterFactory.class).annotatedWith(Names.named("http"))
.to(HttpTransporterFactory.class).in(Singleton.class);
bind(TransporterFactory.class).annotatedWith(Names.named("file"))
.to(FileTransporterFactory.class).in(Singleton.class);
}
@Provides
@Singleton
Set<MetadataGeneratorFactory> provideMetadataGeneratorFactories(
@Named("snapshot") MetadataGeneratorFactory snapshot,
@Named("versions") MetadataGeneratorFactory versions) {
Set<MetadataGeneratorFactory> factories = new HashSet<>();
factories.add(snapshot);
factories.add(versions);
return Collections.unmodifiableSet(factories);
}
@Provides
@Singleton
Set<RepositoryConnectorFactory> provideRepositoryConnectorFactories(
RepositoryConnectorFactory factory) {
return Collections.singleton(factory);
}
@Provides
@Singleton
Set<TransporterFactory> provideTransporterFactories(
@Named("file") TransporterFactory file,
@Named("http") TransporterFactory http) {
// Order is decided elsewhere (by priority)
Set<TransporterFactory> factories = new HashSet<TransporterFactory>();
factories.add(file);
factories.add(http);
return Collections.unmodifiableSet(factories);
}
}

View File

@@ -0,0 +1,325 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed 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.springframework.cloud.function.compiler.java;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.maven.model.ActivationFile;
import org.apache.maven.model.ActivationOS;
import org.apache.maven.model.ActivationProperty;
import org.apache.maven.model.building.ModelProblemCollector;
import org.apache.maven.model.building.ModelProblemCollectorRequest;
import org.apache.maven.model.path.DefaultPathTranslator;
import org.apache.maven.model.profile.DefaultProfileSelector;
import org.apache.maven.model.profile.ProfileActivationContext;
import org.apache.maven.model.profile.activation.FileProfileActivator;
import org.apache.maven.model.profile.activation.JdkVersionProfileActivator;
import org.apache.maven.model.profile.activation.OperatingSystemProfileActivator;
import org.apache.maven.model.profile.activation.PropertyProfileActivator;
import org.apache.maven.settings.Activation;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Profile;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.crypto.SettingsDecryptionResult;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.AuthenticationSelector;
import org.eclipse.aether.repository.MirrorSelector;
import org.eclipse.aether.repository.ProxySelector;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import org.eclipse.aether.util.repository.ConservativeAuthenticationSelector;
import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
import org.eclipse.aether.util.repository.DefaultProxySelector;
/**
* An encapsulation of settings read from a user's Maven settings.xml.
*
* @author Andy Wilkinson
* @see MavenSettingsReader
*/
public class MavenSettings {
private final boolean offline;
private final MirrorSelector mirrorSelector;
private final AuthenticationSelector authenticationSelector;
private final ProxySelector proxySelector;
private final String localRepository;
private final List<Profile> activeProfiles;
/**
* Create a new {@link MavenSettings} instance.
* @param settings the source settings
* @param decryptedSettings the decrypted settings
*/
public MavenSettings(Settings settings, SettingsDecryptionResult decryptedSettings) {
this.offline = settings.isOffline();
this.mirrorSelector = createMirrorSelector(settings);
this.authenticationSelector = createAuthenticationSelector(decryptedSettings);
this.proxySelector = createProxySelector(decryptedSettings);
this.localRepository = settings.getLocalRepository();
this.activeProfiles = determineActiveProfiles(settings);
}
private MirrorSelector createMirrorSelector(Settings settings) {
DefaultMirrorSelector selector = new DefaultMirrorSelector();
for (Mirror mirror : settings.getMirrors()) {
selector.add(mirror.getId(), mirror.getUrl(), mirror.getLayout(), false,
mirror.getMirrorOf(), mirror.getMirrorOfLayouts());
}
return selector;
}
private AuthenticationSelector createAuthenticationSelector(
SettingsDecryptionResult decryptedSettings) {
DefaultAuthenticationSelector selector = new DefaultAuthenticationSelector();
for (Server server : decryptedSettings.getServers()) {
AuthenticationBuilder auth = new AuthenticationBuilder();
auth.addUsername(server.getUsername()).addPassword(server.getPassword());
auth.addPrivateKey(server.getPrivateKey(), server.getPassphrase());
selector.add(server.getId(), auth.build());
}
return new ConservativeAuthenticationSelector(selector);
}
private ProxySelector createProxySelector(
SettingsDecryptionResult decryptedSettings) {
DefaultProxySelector selector = new DefaultProxySelector();
for (Proxy proxy : decryptedSettings.getProxies()) {
Authentication authentication = new AuthenticationBuilder()
.addUsername(proxy.getUsername()).addPassword(proxy.getPassword())
.build();
selector.add(
new org.eclipse.aether.repository.Proxy(proxy.getProtocol(),
proxy.getHost(), proxy.getPort(), authentication),
proxy.getNonProxyHosts());
}
return selector;
}
private List<Profile> determineActiveProfiles(Settings settings) {
SpringBootCliModelProblemCollector problemCollector = new SpringBootCliModelProblemCollector();
List<org.apache.maven.model.Profile> activeModelProfiles = createProfileSelector()
.getActiveProfiles(createModelProfiles(settings.getProfiles()),
new SpringBootCliProfileActivationContext(
settings.getActiveProfiles()),
problemCollector);
if (!problemCollector.getProblems().isEmpty()) {
throw new IllegalStateException(createFailureMessage(problemCollector));
}
List<Profile> activeProfiles = new ArrayList<Profile>();
Map<String, Profile> profiles = settings.getProfilesAsMap();
for (org.apache.maven.model.Profile modelProfile : activeModelProfiles) {
activeProfiles.add(profiles.get(modelProfile.getId()));
}
return activeProfiles;
}
private String createFailureMessage(
SpringBootCliModelProblemCollector problemCollector) {
StringWriter message = new StringWriter();
PrintWriter printer = new PrintWriter(message);
printer.println("Failed to determine active profiles:");
for (ModelProblemCollectorRequest problem : problemCollector.getProblems()) {
printer.println(" " + problem.getMessage() + (problem.getLocation() != null
? " at " + problem.getLocation() : ""));
if (problem.getException() != null) {
printer.println(indentStackTrace(problem.getException(), " "));
}
}
return message.toString();
}
private String indentStackTrace(Exception ex, String indent) {
return indentLines(printStackTrace(ex), indent);
}
private String printStackTrace(Exception ex) {
StringWriter stackTrace = new StringWriter();
PrintWriter printer = new PrintWriter(stackTrace);
ex.printStackTrace(printer);
return stackTrace.toString();
}
private String indentLines(String input, String indent) {
StringWriter indented = new StringWriter();
PrintWriter writer = new PrintWriter(indented);
String line;
BufferedReader reader = new BufferedReader(new StringReader(input));
try {
while ((line = reader.readLine()) != null) {
writer.println(indent + line);
}
}
catch (IOException ex) {
return input;
}
return indented.toString();
}
private DefaultProfileSelector createProfileSelector() {
DefaultProfileSelector selector = new DefaultProfileSelector();
selector.addProfileActivator(new FileProfileActivator()
.setPathTranslator(new DefaultPathTranslator()));
selector.addProfileActivator(new JdkVersionProfileActivator());
selector.addProfileActivator(new PropertyProfileActivator());
selector.addProfileActivator(new OperatingSystemProfileActivator());
return selector;
}
private List<org.apache.maven.model.Profile> createModelProfiles(
List<Profile> profiles) {
List<org.apache.maven.model.Profile> modelProfiles = new ArrayList<org.apache.maven.model.Profile>();
for (Profile profile : profiles) {
org.apache.maven.model.Profile modelProfile = new org.apache.maven.model.Profile();
modelProfile.setId(profile.getId());
if (profile.getActivation() != null) {
modelProfile
.setActivation(createModelActivation(profile.getActivation()));
}
modelProfiles.add(modelProfile);
}
return modelProfiles;
}
private org.apache.maven.model.Activation createModelActivation(
Activation activation) {
org.apache.maven.model.Activation modelActivation = new org.apache.maven.model.Activation();
modelActivation.setActiveByDefault(activation.isActiveByDefault());
if (activation.getFile() != null) {
ActivationFile activationFile = new ActivationFile();
activationFile.setExists(activation.getFile().getExists());
activationFile.setMissing(activation.getFile().getMissing());
modelActivation.setFile(activationFile);
}
modelActivation.setJdk(activation.getJdk());
if (activation.getOs() != null) {
ActivationOS os = new ActivationOS();
os.setArch(activation.getOs().getArch());
os.setFamily(activation.getOs().getFamily());
os.setName(activation.getOs().getName());
os.setVersion(activation.getOs().getVersion());
modelActivation.setOs(os);
}
if (activation.getProperty() != null) {
ActivationProperty property = new ActivationProperty();
property.setName(activation.getProperty().getName());
property.setValue(activation.getProperty().getValue());
modelActivation.setProperty(property);
}
return modelActivation;
}
public boolean getOffline() {
return this.offline;
}
public MirrorSelector getMirrorSelector() {
return this.mirrorSelector;
}
public AuthenticationSelector getAuthenticationSelector() {
return this.authenticationSelector;
}
public ProxySelector getProxySelector() {
return this.proxySelector;
}
public String getLocalRepository() {
return this.localRepository;
}
public List<Profile> getActiveProfiles() {
return this.activeProfiles;
}
private static final class SpringBootCliProfileActivationContext
implements ProfileActivationContext {
private final List<String> activeProfiles;
SpringBootCliProfileActivationContext(List<String> activeProfiles) {
this.activeProfiles = activeProfiles;
}
@Override
public List<String> getActiveProfileIds() {
return this.activeProfiles;
}
@Override
public List<String> getInactiveProfileIds() {
return Collections.emptyList();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Map<String, String> getSystemProperties() {
return (Map) System.getProperties();
}
@Override
public Map<String, String> getUserProperties() {
return Collections.emptyMap();
}
@Override
public File getProjectDirectory() {
return new File(".");
}
@Override
public Map<String, String> getProjectProperties() {
return Collections.emptyMap();
}
}
private static final class SpringBootCliModelProblemCollector
implements ModelProblemCollector {
private final List<ModelProblemCollectorRequest> problems = new ArrayList<ModelProblemCollectorRequest>();
@Override
public void add(ModelProblemCollectorRequest req) {
this.problems.add(req);
}
List<ModelProblemCollectorRequest> getProblems() {
return this.problems;
}
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed 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.springframework.cloud.function.compiler.java;
import java.io.File;
import java.lang.reflect.Field;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.building.DefaultSettingsBuilderFactory;
import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
import org.apache.maven.settings.building.SettingsBuildingException;
import org.apache.maven.settings.building.SettingsBuildingRequest;
import org.apache.maven.settings.crypto.DefaultSettingsDecrypter;
import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecrypter;
import org.apache.maven.settings.crypto.SettingsDecryptionResult;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
import org.sonatype.plexus.components.cipher.PlexusCipherException;
import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
/**
* {@code MavenSettingsReader} reads settings from a user's Maven settings.xml file,
* decrypting them if necessary using settings-security.xml.
*
* @author Andy Wilkinson
*/
public class MavenSettingsReader {
private static final Logger log = LoggerFactory.getLogger(MavenSettingsReader.class);
private final String homeDir;
public MavenSettingsReader() {
this(System.getProperty("user.home"));
}
public MavenSettingsReader(String homeDir) {
this.homeDir = homeDir;
}
public MavenSettings readSettings() {
Settings settings = loadSettings();
SettingsDecryptionResult decrypted = decryptSettings(settings);
if (!decrypted.getProblems().isEmpty()) {
log.error(
"Maven settings decryption failed. Some Maven repositories may be inaccessible");
// Continue - the encrypted credentials may not be used
}
return new MavenSettings(settings, decrypted);
}
public static void applySettings(MavenSettings settings,
DefaultRepositorySystemSession session) {
if (settings.getLocalRepository() != null) {
try {
session.setLocalRepositoryManager(
new SimpleLocalRepositoryManagerFactory().newInstance(session,
new LocalRepository(settings.getLocalRepository())));
}
catch (NoLocalRepositoryManagerException e) {
throw new IllegalStateException(
"Cannot set local repository to " + settings.getLocalRepository(),
e);
}
}
session.setOffline(settings.getOffline());
session.setMirrorSelector(settings.getMirrorSelector());
session.setAuthenticationSelector(settings.getAuthenticationSelector());
session.setProxySelector(settings.getProxySelector());
}
private Settings loadSettings() {
File settingsFile = new File(this.homeDir, ".m2/settings.xml");
if (settingsFile.exists()) {
log.info("Reading settings from: " + settingsFile);
}
else {
log.info("No settings found at: " + settingsFile);
}
SettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
request.setUserSettingsFile(settingsFile);
request.setSystemProperties(System.getProperties());
try {
return new DefaultSettingsBuilderFactory().newInstance().build(request)
.getEffectiveSettings();
}
catch (SettingsBuildingException ex) {
throw new IllegalStateException(
"Failed to build settings from " + settingsFile, ex);
}
}
private SettingsDecryptionResult decryptSettings(Settings settings) {
DefaultSettingsDecryptionRequest request = new DefaultSettingsDecryptionRequest(
settings);
return createSettingsDecrypter().decrypt(request);
}
private SettingsDecrypter createSettingsDecrypter() {
SettingsDecrypter settingsDecrypter = new DefaultSettingsDecrypter();
setField(DefaultSettingsDecrypter.class, "securityDispatcher", settingsDecrypter,
new SpringBootSecDispatcher());
return settingsDecrypter;
}
private void setField(Class<?> sourceClass, String fieldName, Object target,
Object value) {
try {
Field field = sourceClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(target, value);
}
catch (Exception ex) {
throw new IllegalStateException(
"Failed to set field '" + fieldName + "' on '" + target + "'", ex);
}
}
private class SpringBootSecDispatcher extends DefaultSecDispatcher {
private static final String SECURITY_XML = ".m2/settings-security.xml";
SpringBootSecDispatcher() {
File file = new File(MavenSettingsReader.this.homeDir, SECURITY_XML);
this._configurationFile = file.getAbsolutePath();
try {
this._cipher = new DefaultPlexusCipher();
}
catch (PlexusCipherException e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@@ -22,7 +22,9 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.tools.FileObject;
@@ -31,6 +33,8 @@ import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardLocation;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.Dependency;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,6 +54,8 @@ public class MemoryBasedJavaFileManager implements JavaFileManager {
private List<CloseableFilterableJavaFileObjectIterable> toClose = new ArrayList<>();
private Map<String,File> resolvedAdditionalDependencies = new LinkedHashMap<>();
public MemoryBasedJavaFileManager() {
outputCollector = new CompilationOutputCollector();
}
@@ -84,6 +90,11 @@ public class MemoryBasedJavaFileManager implements JavaFileManager {
else if (location == StandardLocation.CLASS_PATH
&& (kinds == null || kinds.contains(Kind.CLASS))) {
String javaClassPath = getClassPath();
if (!resolvedAdditionalDependencies.isEmpty()) {
for (File resolvedAdditionalDependency: resolvedAdditionalDependencies.values()) {
javaClassPath += File.pathSeparatorChar + resolvedAdditionalDependency.toURI().toString().substring("file:".length());
}
}
logger.debug("Creating iterable for class path: {}", javaClassPath);
resultIterable = new IterableClasspath(javaClassPath, packageName, recurse);
toClose.add(resultIterable);
@@ -203,4 +214,35 @@ public class MemoryBasedJavaFileManager implements JavaFileManager {
return outputCollector.getCompiledClasses();
}
public List<CompilationMessage> addAndResolveDependencies(String[] dependencies) {
List<CompilationMessage> resolutionMessages = new ArrayList<>();
for (String dependency: dependencies) {
if (dependency.startsWith("maven:")) {
// Resolving an explicit external archive
String coordinates = dependency.replaceFirst("maven:\\/*", "");
DependencyResolver engine = DependencyResolver.instance();
try {
File resolved = engine.resolve(new Dependency(new DefaultArtifact(coordinates), "runtime"));
// Example:
// dependency = maven://org.springframework:spring-expression:4.3.9.RELEASE
// resolved.toURI() = file:/Users/aclement/.m2/repository/org/springframework/spring-expression/4.3.9.RELEASE/spring-expression-4.3.9.RELEASE.jar
resolvedAdditionalDependencies.put(dependency, resolved);
} catch (RuntimeException re) {
CompilationMessage compilationMessage =
new CompilationMessage(CompilationMessage.Kind.ERROR,re.getMessage(),null,0,0);
resolutionMessages.add(compilationMessage);
}
}
else {
resolutionMessages.add(new CompilationMessage(CompilationMessage.Kind.ERROR,
"Unrecognized dependency: "+dependency+" (expected something of the form: maven://groupId:artifactId:version)",null,0,0));
}
}
return resolutionMessages;
}
public Map<String, File> getResolvedAdditionalDependencies() {
return resolvedAdditionalDependencies;
}
}

View File

@@ -38,7 +38,7 @@ import org.slf4j.LoggerFactory;
* @author Andy Clement
*/
public class RuntimeJavaCompiler {
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
private static Logger logger = LoggerFactory.getLogger(RuntimeJavaCompiler.class);
@@ -49,13 +49,15 @@ public class RuntimeJavaCompiler {
* classes.
* @param className the name of the class (dotted form, e.g. com.foo.bar.Goo)
* @param classSourceCode the full source code for the class
* @param dependencies optional maven coordinates for dependencies "maven://groupId:artifactId:version"
* @return a CompilationResult that encapsulates what happened during compilation (classes/messages produced)
*/
public CompilationResult compile(String className, String classSourceCode) {
public CompilationResult compile(String className, String classSourceCode, String... dependencies) {
logger.info("Compiling source for class {} using compiler {}",className,compiler.getClass().getName());
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();
MemoryBasedJavaFileManager fileManager = new MemoryBasedJavaFileManager();
List<CompilationMessage> resolutionMessages = fileManager.addAndResolveDependencies(dependencies);
// JavaFileObject sourceFile = new StringBasedJavaSourceFileObject(className, classSourceCode);
JavaFileObject sourceFile = InMemoryJavaFileObject.getSourceJavaFileObject(className, classSourceCode);
// new InMemoryJavaFileObject(StandardLocation.SOURCE_PATH, className, javax.tools.JavaFileObject.Kind.SOURCE, null);
@@ -69,6 +71,8 @@ public class RuntimeJavaCompiler {
boolean success = task.call();
CompilationResult compilationResult = new CompilationResult(success);
compilationResult.recordCompilationMessages(resolutionMessages);
compilationResult.setResolvedAdditionalDependencies(new ArrayList<>(fileManager.getResolvedAdditionalDependencies().values()));
// If successful there may be no errors but there might be info/warnings
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) {

View File

@@ -16,8 +16,10 @@
package org.springframework.cloud.function.compiler.java;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
/**
* Very simple classloader that can be used to load the compiled types.
@@ -32,6 +34,22 @@ public class SimpleClassLoader extends URLClassLoader {
super(NO_URLS, classLoader);
}
public SimpleClassLoader(List<File> resolvedAdditionalDependencies, ClassLoader classLoader) {
super(toUrls(resolvedAdditionalDependencies), classLoader);
}
private static URL[] toUrls(List<File> resolvedAdditionalDependencies) {
URL[] urls = new URL[resolvedAdditionalDependencies.size()];
for (int i=0,max=resolvedAdditionalDependencies.size();i<max;i++) {
try {
urls[i] = resolvedAdditionalDependencies.get(i).toURI().toURL();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
return urls;
}
public Class<?> defineClass(String name, byte[] bytes) {
return super.defineClass(name, bytes, 0, bytes.length);
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed 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.springframework.cloud.function.compiler.java;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.List;
import java.util.function.Supplier;
import org.junit.Test;
/**
* @author Andy Clement
*/
@SuppressWarnings("unchecked")
public class RuntimeJavaCompilerTests {
@Test
public void basicCompilation() {
RuntimeJavaCompiler rjc = new RuntimeJavaCompiler();
CompilationResult cr = rjc.compile("A", "public class A {}");
List<CompilationMessage> compilationMessages = cr.getCompilationMessages();
assertTrue(compilationMessages.isEmpty());
}
@Test
public void missingType() throws Exception {
RuntimeJavaCompiler rjc = new RuntimeJavaCompiler();
CompilationResult cr = rjc.compile("A",
"public class A implements java.util.function.Supplier { "+
" public String get() {\n"+
" ExpressionParser parser = new SpelExpressionParser();\n" +
" Expression exp = parser.parseExpression(\"'Hello World'\");\n" +
" String message = (String) exp.getValue();"+
" return message;\n"+
" }\n"+
"}");
List<CompilationMessage> compilationMessages = cr.getCompilationMessages();
assertEquals(3,compilationMessages.size());
assertTrue(compilationMessages.get(0).getMessage().contains("cannot find symbol"));
assertTrue(compilationMessages.get(0).getMessage().contains("class ExpressionParser"));
assertTrue(compilationMessages.get(1).getMessage().contains("cannot find symbol"));
assertTrue(compilationMessages.get(1).getMessage().contains("class SpelExpressionParser"));
assertTrue(compilationMessages.get(2).getMessage().contains("cannot find symbol"));
assertTrue(compilationMessages.get(2).getMessage().contains("class Expression"));
}
@Test
public void okWithImportedDependencies() throws Exception {
RuntimeJavaCompiler rjc = new RuntimeJavaCompiler();
CompilationResult cr = rjc.compile("A",
"import org.springframework.expression.*;\n"+
"import org.springframework.expression.spel.standard.*;\n"+
"public class A implements java.util.function.Supplier {\n"+
" public String get() {\n"+
" ExpressionParser parser = new SpelExpressionParser();\n" +
" Expression exp = parser.parseExpression(\"'Hello World'\");\n" +
" String message = (String) exp.getValue();\n"+
" return message;\n"+
" }\n"+
"}","maven://org.springframework:spring-expression:4.3.9.RELEASE");
List<CompilationMessage> compilationMessages = cr.getCompilationMessages();
assertTrue(compilationMessages.isEmpty());
try (SimpleClassLoader cl = new SimpleClassLoader(this.getClass().getClassLoader())) {
Class<?> clazz = cl.defineClass("A",cr.getClassBytes("A"));
Supplier<String> supplier = (Supplier<String>) clazz.newInstance();
assertEquals("Hello World",supplier.get());
}
}
@Test
public void okWithImportedDependencies2() throws Exception {
RuntimeJavaCompiler rjc = new RuntimeJavaCompiler();
String source =
"import org.joda.time.*;\n"+
"public class A implements java.util.function.Supplier {\n"+
" public String get() {\n"+
" DateTime dt = new DateTime();\n" +
" int month = dt.getMonthOfYear();\n"+
" return String.valueOf(month>0);\n"+
" }\n"+
"}";
CompilationResult cr = rjc.compile("A", source, "maven://joda-time:joda-time:2.9.9");
List<CompilationMessage> compilationMessages = cr.getCompilationMessages();
assertTrue(compilationMessages.isEmpty());
List<File> resolvedAdditionalDependencies = cr.getResolvedAdditionalDependencies();
try (SimpleClassLoader cl = new SimpleClassLoader(resolvedAdditionalDependencies, this.getClass().getClassLoader())) {
Class<?> clazz = cl.defineClass("A",cr.getClassBytes("A"));
Supplier<String> supplier = (Supplier<String>) clazz.newInstance();
assertEquals("true",supplier.get());
}
cr = rjc.compile("A", source,
"maven://org.springframework:spring-expression:4.3.9.RELEASE",
"maven://joda-time:joda-time:2.9.9");
compilationMessages = cr.getCompilationMessages();
assertTrue(compilationMessages.isEmpty());
resolvedAdditionalDependencies = cr.getResolvedAdditionalDependencies();
try (SimpleClassLoader cl = new SimpleClassLoader(resolvedAdditionalDependencies, this.getClass().getClassLoader())) {
Class<?> clazz = cl.defineClass("A",cr.getClassBytes("A"));
Supplier<String> supplier = (Supplier<String>) clazz.newInstance();
assertEquals("true",supplier.get());
}
}
@Test
public void dependencyResolution() throws Exception {
// Failure:
RuntimeJavaCompiler rjc = new RuntimeJavaCompiler();
CompilationResult cr = rjc.compile("A",
"public class A {}",
"maven://org.springframework:spring-expression2:4.3.9.RELEASE"); // extra '2' in there
List<CompilationMessage> compilationMessages = cr.getCompilationMessages();
assertEquals(1,compilationMessages.size());
// ERROR:org.eclipse.aether.resolution.ArtifactResolutionException: Could not find artifact org.springframework:spring-expression2:jar:4.3.9.RELEASE in spring-snapshots (https://repo.spring.io/libs-snapshot)
assertTrue(compilationMessages.get(0).getMessage().contains("Could not find artifact org.springframework:spring-expression2:jar:4.3.9.RELEASE"));
// Failure:
rjc = new RuntimeJavaCompiler();
cr = rjc.compile("A",
"public class A {}",
"trouble://org.springframework:spring-expression:4.3.9.RELEASE"); // rogue prefix (should be "maven:")
compilationMessages = cr.getCompilationMessages();
assertEquals(1,compilationMessages.size());
assertTrue(compilationMessages.get(0).toString(),compilationMessages.get(0).getMessage().contains("Unrecognized dependency: "));
// Success
rjc = new RuntimeJavaCompiler();
cr = rjc.compile("A",
"public class A {}",
"maven://joda-time:joda-time:2.9.9");
compilationMessages = cr.getCompilationMessages();
assertEquals(0,compilationMessages.size());
List<File> resolvedAdditionalDependencies = cr.getResolvedAdditionalDependencies();
assertEquals(1, resolvedAdditionalDependencies.size());
assertTrue("Expected this to end with 'joda-time-2.9.9.jar': "+resolvedAdditionalDependencies.get(0).toString(),
resolvedAdditionalDependencies.get(0).toString().endsWith("joda-time-2.9.9.jar"));
}
}