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
67 changes: 67 additions & 0 deletions appmap-java-maven-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
AppLand AppMap Maven Plugin for Java
--------------------------------


- [Building](#building)
- [Agent Configuration](#agent-configuration)
- [Maven Plugin Configuration](#maven-plugin-config)
- [Plugin Goals](#plugin-goals)
- [Plugin configuration options](#plugin-configuration)
- [Example](#example)


# Building
Artifacts will be written to `target/` use `appmap-java-plugin-[VERSION].jar`. as your maven plugin.
```bash
$ mvn clean install
```

# Agent Configuration
When you run your program, the agent reads configuration settings from `appmap.yml` by default.

Please read configuration options from [AppMap Java Agent README.md](../README.md)

# Maven Plugin Configuration

## Plugin goals
prepare-agent : adds appmap.jar to JVM execution as javaagent

## Plugin configuration options
outputDirectory (default: ./target/appmap/)
configFile (default: ./appmap.yml)
debug (enabled|disabled, default: disabled)
eventValueSize (integer, default 1024)
skip(Boolean, default false)

## Example plugin config in a standard POM.xml file
```xml
<!-- AppMap Java agent, default parameters -->
<plugin>
<groupId>com.appland</groupId>
<artifactId>appmap-maven-plugin</artifactId>
<version>${appmap-java.version}</version>
<configuration>
<outputDirectory></outputDirectory>
<configFile>appmap.yml</configFile>
<debug>enabled</debug>
<eventValueSize>1024</eventValueSize>
<skip>false</skip>
</configuration>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
</executions>
</plugin>
```


# Running
To run the java agent with correct plugin configuration you only need to build your project as usual without skipping
the test goal.

```bash
$ mvn clean install
```
70 changes: 70 additions & 0 deletions appmap-java-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.appland</groupId>
<artifactId>appmap-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
<version>0.5.0-SNAPSHOT</version>
<name>Appland Java Recorder Maven Plugin</name>
<description>This maven plugin helps you automatically generate an Appland AppMap using the Java Recorder.
</description>
<url>https://github.com/applandinc/appmap-java/tree/master/java-maven-plugin</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.appland</groupId>
<artifactId>appmap-agent</artifactId>
<version>0.5.0</version>
</dependency>

<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.5.4</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<!-- see http://jira.codehaus.org/browse/MNG-5346 -->
<skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
</configuration>
<executions>
<execution>
<id>mojo-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.appland.appmap;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.util.*;

import static java.lang.String.format;

public abstract class AppMapAgentMojo extends AbstractMojo {

static final String APPMAP_AGENT_ARTIFACT_NAME = "com.appland:appmap-agent";
static final String SUREFIRE_ARG_LINE = "argLine";

@Parameter(property = "skip")
protected boolean skip = false;

@Parameter(property = "project.outputDirectory")
protected File outputDirectory = new File("tmp");

@Parameter(property = "project.configFile")
protected File configFile = new File("appmap.yml");

@Parameter(property = "project.debug")
protected String debug = "disabled";

@Parameter(property = "project.eventValueSize")
protected Integer eventValueSize = 1024;

@Parameter(property = "plugin.artifactMap")
protected Map<String, Artifact> pluginArtifactMap;

@Parameter(property = "project")
private MavenProject project;

public abstract void execute() throws MojoExecutionException;

protected void skipMojo() {
}

protected void loadAppMapJavaAgent() {
final String newValue = buildArguments();
setProjectArgLineProperty(newValue);
getLog().info(SUREFIRE_ARG_LINE
+ " set to " + StringEscapeUtils.unescapeJava(newValue));
}

/**
* This method builds the needed parameter to run the Agent, if previous configuration is found is also attached in
* the SUREFIRE_ARG_LINE, if previous version of the AppMap agent is found is removed and replaced with the version
* of this maven plugin
*
* @return formatted and escaped arguments to run on command line
*/
private String buildArguments() {
List<String> args = new ArrayList<String>();
final String oldConfig = getCurrentArgLinePropertyValue();
if (oldConfig != null) {
final List<String> oldArgs = Arrays.asList(oldConfig.split(" "));
removeOldAppMapAgentFromCommandLine(oldArgs);
args.addAll(oldArgs);
}
addMvnAppMapCommandLineArgsFirst(args);
StringBuilder builder = new StringBuilder();
for ( String arg : args) {
builder.append(arg).append(" ");
}
return builder.toString();
}

/**
* Generate required quotes JVM argument based on current configuration and
* prepends it to the given argument command line. If a agent with the same
* JAR file is already specified this parameter is removed from the existing
* command line, does the same for xbootclasspath command.
*/
private void removeOldAppMapAgentFromCommandLine(List<String> oldArgs) {
final String plainAgent = format("-javaagent:%s", getAppMapAgentJar());
final String xbootClasspath = format("-Xbootclasspath/a:%s", getAppMapAgentJar());
for (final Iterator<String> i = oldArgs.iterator(); i.hasNext(); ) {
final String oldCommand = i.next();
if (oldCommand.startsWith(plainAgent) || oldCommand.startsWith(xbootClasspath)) {
i.remove();
}
}
}

private void addMvnAppMapCommandLineArgsFirst(List<String> args) {
args.add(StringEscapeUtils.escapeJava(
format("-Xbootclasspath/a:%s", getAppMapAgentJar(), this)
));
args.add(StringEscapeUtils.escapeJava(
format("-javaagent:%s=%s", getAppMapAgentJar(), this)
));

args.add(0, "-Dappmap.debug=" + StringEscapeUtils.escapeJava(debug));
args.add(0, "-Dappmap.output.directory=" + StringEscapeUtils.escapeJava(format("%s", outputDirectory)));
args.add(0, "-Dappmap.config.file=" + StringEscapeUtils.escapeJava(format("%s", configFile)));
args.add(0, "-Dappmap.event.valueSize=" + eventValueSize);
}


private Object setProjectArgLineProperty(String newValue) {
return project.getProperties().setProperty(SUREFIRE_ARG_LINE, newValue);
}

private String getCurrentArgLinePropertyValue() {
return project.getProperties().getProperty(SUREFIRE_ARG_LINE);
}

protected File getAppMapAgentJar() {
return pluginArtifactMap.get(APPMAP_AGENT_ARTIFACT_NAME).getFile();
}

public MavenProject getProject() {
return project;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.appland.appmap;


import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;

/**
* Goal that adds appmap.jar to JVM execution as javaagent,
* right before the test execution begins.
*/
@Mojo(name = "prepare-agent", defaultPhase = LifecyclePhase.TEST_COMPILE)
public class LoadJavaAppMapAgentMojo extends AppMapAgentMojo {

@Override
public void execute()
throws MojoExecutionException {
try {
if (skip) {
getLog().info("Skipping AppLand AppMap execution because property skip is set.");
skipMojo();
return;
} else {
getLog().info("Initializing AppLand AppMap Java Recorder." );
loadAppMapJavaAgent();
}
} catch (Exception e) {
getLog().error("Error initializing AppLand AppMap Java Recorder");
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.appland.appmap;

import static org.junit.Assert.assertEquals;

import com.appland.appmap.output.v1.Event;

import com.appland.appmap.record.IRecordingSession;
import com.appland.appmap.record.Recorder;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;

public class LoadJavaMapAgentMojoTest {

@Before
public void before() throws Exception {
final IRecordingSession.Metadata metadata =
new IRecordingSession.Metadata();

Recorder.getInstance().start(metadata);
}

@Test
public void testAllEventsWritten() {
final Recorder recorder = Recorder.getInstance();
final Long threadId = Thread.currentThread().getId();
final Event[] events = new Event[] {
new Event(),
new Event(),
new Event(),
};

for (int i = 0; i < events.length; i++) {
final Event event = events[i];
event
.setDefinedClass("SomeClass")
.setMethodId("SomeMethod")
.setStatic(false)
.setLineNumber(315)
.setThreadId(threadId);

recorder.add(event);
assertEquals(event, recorder.getLastEvent());
}

final String appmapJson = recorder.stop();
final String expectedJson = "\"thread_id\":" + threadId.toString();
final int numMatches = StringUtils.countMatches(appmapJson, expectedJson);
assertEquals(numMatches, events.length);
}
}