I want to build a Java agent with Byte Buddy, which intercepts JDBC connection creation and closing event. I want to support data sources such as HikariCP, DBCP, etc, also plain JDBC with RDBMS drivers.
My code:
Instrumention setup
new AgentBuilder.Default()
.type(startMatcher.and(isSubTypeOf(java.sql.Connection.class).and(not(isAbstract())))
.transform(constructorTransformer).transform(methodsTransformer).with(listener).installOn(inst);
Intercepting code
package org.wxt.xtools.agents.jdbcmon;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import org.wxt.xtools.agents.utils.StringUtils;
import org.wxt.xtools.agents.utils.TopN;
import ch.qos.logback.classic.Logger;
import io.prometheus.client.Histogram;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.This;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
/**
*
* #author ggfan
*
*/
public class JDBCAPIInterceptor {
// ***ATTENTION*** fields below must be public, as we will access them from the
// intercepting methods
public static Map<Integer, ConnectionInfo> conns = new HashMap<Integer, ConnectionInfo>();
public static Map<String, String> stacks = new HashMap<String, String>();
public static Map<String, Integer> counstsByStack = new HashMap<String, Integer>();
public static TopN<StatementExecutionInfo> slowStatements = new TopN<StatementExecutionInfo>(10, new Comparator<StatementExecutionInfo>() {
#Override
public int compare(StatementExecutionInfo o1, StatementExecutionInfo o2) {
long d1 = o1.getExecutionEndTime() - o1.getExecutionStartTime();
long d2 = o2.getExecutionEndTime() - o2.getExecutionStartTime();
return Long.compare(d1, d2);
}
});
public static Logger log = JDBCMonitor.log;
public static JDBCStatsReport getInfo() {
JDBCStatsReport report = new JDBCStatsReport();
List<ConnectionInfo> data = new ArrayList<ConnectionInfo>();
for (ConnectionInfo ci : conns.values()) {
ConnectionInfo copy = new ConnectionInfo();
copy.setHash(ci.getHash());
copy.setStackHash(ci.getStackHash() + "(" + counstsByStack.get(ci.getStackHash()) + ")");
copy.setCreationTime(ci.getCreationTime());
copy.setLastActiveTime(ci.getLastActiveTime());
copy.setLastMethod(ci.getLastMethod());
copy.setCurrentStatement(ci.getCurrentStatement());
data.add(copy);
}
report.setConnectionInfo(data);
report.setSlowStatementTopN(slowStatements);
return report;
}
#RuntimeType
public static Object interceptor(#Origin Class<?> clazz, #Origin Method method, #SuperCall Callable<?> callable,
#net.bytebuddy.implementation.bind.annotation.This Object inst, #AllArguments Object[] args)
throws Exception {
// this is equal to original method, will not cause inner calls to other
// matching methods get intercepted.
// Object o = callable.call();
log.debug("intercepting {}#{}", clazz.getName(), method.getName());
int hashCode = System.identityHashCode(inst);
if (java.sql.Connection.class.isAssignableFrom(clazz)) {
ConnectionInfo ci = conns.get(hashCode);
// TODO fix bug here!!!
if (ci == null) {
log.debug("connection#{} is not in records anymore, maybe called #{} after close, ignored", hashCode,
method.getName());
}
return interceptConnectionMethods(method, hashCode, ci, callable, args);
} else if (java.sql.Statement.class.isAssignableFrom(clazz)) {
return interceptStatementMethods(method, hashCode, callable, args);
} else if (java.sql.PreparedStatement.class.isAssignableFrom(clazz)) {
return interceptStatementMethods(method, hashCode, callable, args);
} else if (java.sql.CallableStatement.class.isAssignableFrom(clazz)) {
return interceptStatementMethods(method, hashCode, callable, args);
}
return null;
}
private static Object interceptConnectionMethods(Method method, int hashCode, ConnectionInfo ci,
Callable<?> callable, Object[] args) throws Exception {
Object o = callable.call();
log.debug("connection#{} used by {}", hashCode, method.getName());
ci.setLastActiveTime(System.currentTimeMillis());
ci.setLastMethod(method.getName());
int resultHash = System.identityHashCode(o);
if (method.getName().equals("close")) {
log.info("connection#{} released", hashCode);
String stackHash = ci.getStackHash();
Integer scount = counstsByStack.get(stackHash);
if (scount != null && scount > 0) {
int newscount = scount - 1;
log.info("set connection count to {} by stack hash {}", newscount, stackHash);
if (newscount == 0) {
counstsByStack.remove(stackHash);
stacks.remove(stackHash);
} else {
counstsByStack.put(stackHash, newscount);
}
} else {
log.error("connection count by stack hash {} is not supposed to be null or less than zero", stackHash);
}
conns.remove(hashCode);
} else if (method.getName().equals("createStatement")) {
StatementExecutionInfo stmt = new StatementExecutionInfo();
stmt.setType(StatementType.NORMAL);
stmt.setHash(resultHash);
conns.get(hashCode).setCurrentStatement(stmt);
log.info("statement#{} created, type {}, attached to connection#{}", resultHash, StatementType.NORMAL, hashCode);
} else if (method.getName().equals("prepareStatement")) {
StatementExecutionInfo stmt = new StatementExecutionInfo();
stmt.setType(StatementType.PREPARED);
stmt.setSqlText((String) args[0]);
stmt.setHash(resultHash);
conns.get(hashCode).setCurrentStatement(stmt);
log.info("statement#{} created, type {}, attached to connection#{}", resultHash, StatementType.PREPARED, hashCode);
} else if (method.getName().equals("prepareCall")) {
StatementExecutionInfo stmt = new StatementExecutionInfo();
stmt.setType(StatementType.CALLABLE);
stmt.setSqlText((String) args[0]);
stmt.setHash(resultHash);
conns.get(hashCode).setCurrentStatement(stmt);
log.info("statement#{} created, type {}, attached to connection#{}", resultHash, StatementType.CALLABLE, hashCode);
}
return o;
}
private static Object interceptStatementMethods(Method method, int hashCode, Callable<?> callable, Object[] args)
throws Exception {
log.info("intercepting statement method {}", method.getName());
Object o = null;
ConnectionInfo containingCI = conns.values().stream().filter(c -> c.getCurrentStatement().getHash() == hashCode)
.findFirst().orElse(null);
if (containingCI == null) {
log.warn("unexpected situation happened: statement can't found containing connection!");
}
if (method.getName().equals("close")) {
o = callable.call();
// TODO statement close method not intercepted ??
if (containingCI != null) {
containingCI.setCurrentStatement(null);
log.info("statement#{} closed, detached from connection#{}", hashCode, containingCI.getHash());
}
}
// all statement execution method trigger
else if (method.getName().startsWith("execute")) {
String stack = getCurrentThreadStackTrace();
String stackHash = StringUtils.md5(stack);
stacks.put(stackHash, stack);
log.info("statement#{} {} started by {}", hashCode, method.getName(), stackHash);
long est = System.currentTimeMillis();
if (containingCI != null) {
containingCI.getCurrentStatement().setExecutionStartTime(est);
containingCI.getCurrentStatement().setStackHash(stackHash);
containingCI.getCurrentStatement().setExecutionCallStack(stack);
if (args.length > 0) {
containingCI.getCurrentStatement().setSqlText((String) args[0]);
}
}
Histogram.Timer timer = JDBCMetrics.SQL_EXECUTION_TIME.startTimer();
try {
o = callable.call();
} finally {
timer.observeDuration();
}
long eet = System.currentTimeMillis();
log.info("statement#{} {} finished, duration is {}", hashCode, method.getName(), (eet - est));
if (containingCI != null) {
containingCI.getCurrentStatement().setExecutionEndTime(eet);
slowStatements.add(containingCI.getCurrentStatement());
}
}
return o;
}
public static String getCurrentThreadStackTrace() throws IOException {
StringWriter sw = new StringWriter();
Throwable t = new Throwable("");
t.printStackTrace(new PrintWriter(sw));
String stackTrace = sw.toString();
sw.close();
return stackTrace;
}
#Advice.OnMethodExit
public static void intercept(#net.bytebuddy.asm.Advice.Origin Constructor<?> m, #This Object inst)
throws Exception {
log.debug("----------------- constructor intercept -------------------------");
if (java.sql.Connection.class.isAssignableFrom(m.getDeclaringClass())) {
// some CP library override hashCode method
int hashCode = System.identityHashCode(inst);
ConnectionInfo ci = new ConnectionInfo();
ci.setHash(hashCode);
ci.setCreationTime(System.currentTimeMillis());
String stackTrace = getCurrentThreadStackTrace();
String shash = StringUtils.md5(stackTrace);
stacks.put(shash, stackTrace);
// ci.setStack(stackTrace);
ci.setStackHash(shash);
log.info("connection#{} acquired by stack#{}", hashCode, shash);
log.debug(stackTrace);
conns.put(hashCode, ci);
Integer scount = counstsByStack.get(ci.getStackHash());
if (scount == null) {
counstsByStack.put(ci.getStackHash(), 1);
} else {
counstsByStack.put(ci.getStackHash(), scount + 1);
}
}
}
}
However, this will not work for some cases. Take HikariCP for example:
HikariCP's implementation has an abstract class ProxyConnection, which has the close method implemented, then a class HikariProxyConnection extends ProxyConnection, which overides some methods, except close. If I setup instrumentation on HikariProxyConnection, the close method will not be intercepted. If I change my code to:
new AgentBuilder.Default()
.type(startMatcher.and(isSubTypeOf(java.sql.Connection.class).and(isAbstract()))
.transform(constructorTransformer).transform(methodsTransformer).with(listener).installOn(inst);
it will work for HikariCP, but not for other connection pool implementations.
For my use case, is there a unified way? No matter what connection pool is used, and even with plain JDBC.
Your matcher:
isSubTypeOf(java.sql.Connection.class).and(not(isAbstract())
explicitly excludes abstract classes. You would need to instrument all methods to achieve what you wanted if you are using Advice.
Also, it seems like you are blending concepts:
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.This;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
The latter annotations belong to MethodDelegation, not to Advice. Refer to the javadoc to see how advice should be applied.
Related
I'd like to find the methods which changed between two arbitrary Java files.
What I've Tried
I've tried using diff (GNU diffutils 3.3) to find changes to lines in the file and diff --show-c-function connect those lines to the changed method. Unfortunately, in Java this lists the class, not the function.
I also tried git diff which seems to properly be able to find the changed function (at least as displayed on GitHub), but it doesn't always list the full signature and my files are not in the same Git repository (https://github.com/apache/commons-math/commit/34adc606601cb578486d4a019b4655c5aff607b5).
Desired Results
Input:
~/repos/d4jBugs/Math_54_buggy/src/main/java/org/apache/commons/math/dfp/Dfp.java
~/repos/d4jBugs/Math_54_fixed/src/main/java/org/apache/commons/math/dfp/Dfp.java
State of Files:
The changed methods between those two files are public double toDouble() and protected Dfp(final DfpField field, double x)
Output: (fully qualified names)
org.apache.commons.math.dfp.Dfp.toDouble()
org.apache.commons.math.dfp.Dfp(final DfpField field, double x)
Summary
Can I find the modified methods with the GNU diffutils tool or git diff and if yes, how would I do that? (Note: I'm not bound to those tools and am happy to install something else if needed.)
I used JavaParser 3.4.4, but it probably works but has not been tested with other versions.
It can be imported in Gradle with:
compile group: 'com.github.javaparser', name: 'javaparser-core', version: '3.4.4'
You can use my class like:
HashSet<String> changedMethods = MethodDiff.methodDiffInClass(
oldFileNameWithPath,
newFileNameWithPath
);
MethodDiff Source:
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.printer.PrettyPrinterConfiguration;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* Created by Loren Klingman on 10/19/17.
* Finds Changes Between Methods of Two Java Source Files
*/
public class MethodDiff {
private static PrettyPrinterConfiguration ppc = null;
class ClassPair {
final ClassOrInterfaceDeclaration clazz;
final String name;
ClassPair(ClassOrInterfaceDeclaration c, String n) {
clazz = c;
name = n;
}
}
public static PrettyPrinterConfiguration getPPC() {
if (ppc != null) {
return ppc;
}
PrettyPrinterConfiguration localPpc = new PrettyPrinterConfiguration();
localPpc.setColumnAlignFirstMethodChain(false);
localPpc.setColumnAlignParameters(false);
localPpc.setEndOfLineCharacter("");
localPpc.setIndent("");
localPpc.setPrintComments(false);
localPpc.setPrintJavadoc(false);
ppc = localPpc;
return ppc;
}
public static <N extends Node> List<N> getChildNodesNotInClass(Node n, Class<N> clazz) {
List<N> nodes = new ArrayList<>();
for (Node child : n.getChildNodes()) {
if (child instanceof ClassOrInterfaceDeclaration) {
// Don't go into a nested class
continue;
}
if (clazz.isInstance(child)) {
nodes.add(clazz.cast(child));
}
nodes.addAll(getChildNodesNotInClass(child, clazz));
}
return nodes;
}
private List<ClassPair> getClasses(Node n, String parents, boolean inMethod) {
List<ClassPair> pairList = new ArrayList<>();
for (Node child : n.getChildNodes()) {
if (child instanceof ClassOrInterfaceDeclaration) {
ClassOrInterfaceDeclaration c = (ClassOrInterfaceDeclaration)child;
String cName = parents+c.getNameAsString();
if (inMethod) {
System.out.println(
"WARNING: Class "+cName+" is located inside a method. We cannot predict its name at"
+ " compile time so it will not be diffed."
);
} else {
pairList.add(new ClassPair(c, cName));
pairList.addAll(getClasses(c, cName + "$", inMethod));
}
} else if (child instanceof MethodDeclaration || child instanceof ConstructorDeclaration) {
pairList.addAll(getClasses(child, parents, true));
} else {
pairList.addAll(getClasses(child, parents, inMethod));
}
}
return pairList;
}
private List<ClassPair> getClasses(String file) {
try {
CompilationUnit cu = JavaParser.parse(new File(file));
return getClasses(cu, "", false);
} catch (FileNotFoundException f) {
throw new RuntimeException("EXCEPTION: Could not find file: "+file);
}
}
public static String getSignature(String className, CallableDeclaration m) {
return className+"."+m.getSignature().asString();
}
public static HashSet<String> methodDiffInClass(String file1, String file2) {
HashSet<String> changedMethods = new HashSet<>();
HashMap<String, String> methods = new HashMap<>();
MethodDiff md = new MethodDiff();
// Load all the method and constructor values into a Hashmap from File1
List<ClassPair> cList = md.getClasses(file1);
for (ClassPair c : cList) {
List<ConstructorDeclaration> conList = getChildNodesNotInClass(c.clazz, ConstructorDeclaration.class);
List<MethodDeclaration> mList = getChildNodesNotInClass(c.clazz, MethodDeclaration.class);
for (MethodDeclaration m : mList) {
String methodSignature = getSignature(c.name, m);
if (m.getBody().isPresent()) {
methods.put(methodSignature, m.getBody().get().toString(getPPC()));
} else {
System.out.println("Warning: No Body for "+file1+" "+methodSignature);
}
}
for (ConstructorDeclaration con : conList) {
String methodSignature = getSignature(c.name, con);
methods.put(methodSignature, con.getBody().toString(getPPC()));
}
}
// Compare everything in file2 to what is in file1 and log any differences
cList = md.getClasses(file2);
for (ClassPair c : cList) {
List<ConstructorDeclaration> conList = getChildNodesNotInClass(c.clazz, ConstructorDeclaration.class);
List<MethodDeclaration> mList = getChildNodesNotInClass(c.clazz, MethodDeclaration.class);
for (MethodDeclaration m : mList) {
String methodSignature = getSignature(c.name, m);
if (m.getBody().isPresent()) {
String body1 = methods.remove(methodSignature);
String body2 = m.getBody().get().toString(getPPC());
if (body1 == null || !body1.equals(body2)) {
// Javassist doesn't add spaces for methods with 2+ parameters...
changedMethods.add(methodSignature.replace(" ", ""));
}
} else {
System.out.println("Warning: No Body for "+file2+" "+methodSignature);
}
}
for (ConstructorDeclaration con : conList) {
String methodSignature = getSignature(c.name, con);
String body1 = methods.remove(methodSignature);
String body2 = con.getBody().toString(getPPC());
if (body1 == null || !body1.equals(body2)) {
// Javassist doesn't add spaces for methods with 2+ parameters...
changedMethods.add(methodSignature.replace(" ", ""));
}
}
// Anything left in methods was only in the first set and so is "changed"
for (String method : methods.keySet()) {
// Javassist doesn't add spaces for methods with 2+ parameters...
changedMethods.add(method.replace(" ", ""));
}
}
return changedMethods;
}
private static void removeComments(Node node) {
for (Comment child : node.getAllContainedComments()) {
child.remove();
}
}
}
I am trying to extend the Dynatrace App Mon plugin to be Jenkins pipeline compatible. I have taken the source code from Github dynatrace-dashboard-2.0.5 (source code from master branch of this repository and dynatrace github repository had some issue and wasn't working, so I ended up downloading the source code of release 2.0.5)
I wrote a class DynatraceAppMonBuildEnvStep which extends AbstractStepImpl and mimics the functionality of TABuildWrapper step. I am running into issue when Utils.updateBuildVariables(build, parameters); method is called in DynatraceAppMonBuildEnvStep class, which will eventually set the environment variables. I don't get any error/exception but it is not setting the environment variables which I need to inject in the mvn build command.
When I run bat 'set' in my pipeline script, it does not show below environment variable. I am able to see these env variables if I invoke the plugin via non pipeline.
dtMarker=testservice
dtPassword=password1
dtProfile=testprofile
dtServerUrl=https://<someurl>:8021
dtTestrunID=<test id>
dtUsername=<username>
dtVersionBuild=36
dtVersionMajor=testservice
The code flow is below: DynatraceAppMonBuildEnvStep -> Utils -> DynatraceVariablesAction -> DynatraceVariablesEnvironmentContributor -> buildEnvironmentFor (method).
DynatraceAppMonBuildEnvStep.java
package com.dynatrace.jenkins.steps;
import hudson.Extension;
import hudson.model.ParameterValue;
import hudson.model.PasswordParameterValue;
import hudson.model.Run;
import hudson.model.StringParameterValue;
import hudson.model.TaskListener;
import hudson.util.FormValidation;
import jenkins.model.GlobalConfiguration;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import com.dynatrace.jenkins.dashboard.Messages;
import com.dynatrace.jenkins.dashboard.TABuildSetupStatusAction;
import com.dynatrace.jenkins.dashboard.TAGlobalConfiguration;
import com.dynatrace.jenkins.dashboard.utils.BuildVarKeys;
import com.dynatrace.jenkins.dashboard.utils.Utils;
import com.dynatrace.sdk.server.exceptions.ServerResponseException;
import com.dynatrace.sdk.server.sessions.Sessions;
import com.dynatrace.sdk.server.sessions.models.StartRecordingRequest;
import com.dynatrace.sdk.server.testautomation.TestAutomation;
import com.dynatrace.sdk.server.testautomation.models.FetchTestRunsRequest;
import com.sun.jersey.api.client.ClientHandlerException;
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import java.io.PrintStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.net.ssl.SSLHandshakeException;
public final class DynatraceAppMonBuildEnvStep extends AbstractStepImpl {
/**
* The 1st arg is system profile name, the 2nd is build number
*/
private static final String RECORD_SESSION_NAME = "%s_Jenkins_build_%s";
public final String systemProfile;
// Test run attributes - no versionBuild attribute because it's taken from
// the build object
public final String versionMajor;
public final String versionMinor;
public final String versionRevision;
public final String versionMilestone;
public final String marker;
public final Boolean recordSession;
#DataBoundConstructor
public DynatraceAppMonBuildEnvStep(final String systemProfile, final String versionMajor, final String versionMinor,
final String versionRevision, final String versionMilestone, final String marker,
final Boolean recordSession) {
this.systemProfile = systemProfile;
this.versionMajor = versionMajor;
this.versionMinor = versionMinor;
this.versionRevision = versionRevision;
this.versionMilestone = versionMilestone;
this.marker = marker;
this.recordSession = recordSession;
}
private void setupBuildVariables(Run<?, ?> build, String serverUrl) {
final TAGlobalConfiguration globalConfig = GlobalConfiguration.all().get(TAGlobalConfiguration.class);
List<ParameterValue> parameters = new ArrayList<>(10);
parameters.add(new StringParameterValue(BuildVarKeys.BUILD_VAR_KEY_SYSTEM_PROFILE, systemProfile));
if (StringUtils.isNotEmpty(versionMajor)) {
parameters.add(new StringParameterValue(BuildVarKeys.BUILD_VAR_KEY_VERSION_MAJOR, versionMajor));
}
if (StringUtils.isNotEmpty(versionMinor)) {
parameters.add(new StringParameterValue(BuildVarKeys.BUILD_VAR_KEY_VERSION_MINOR, versionMinor));
}
if (StringUtils.isNotEmpty(versionRevision)) {
parameters.add(new StringParameterValue(BuildVarKeys.BUILD_VAR_KEY_VERSION_REVISION, versionRevision));
}
parameters.add(new StringParameterValue(BuildVarKeys.BUILD_VAR_KEY_VERSION_BUILD,
Integer.toString(build.getNumber())));
if (StringUtils.isNotEmpty(versionMilestone)) {
parameters.add(new StringParameterValue(BuildVarKeys.BUILD_VAR_KEY_VERSION_MILESTONE, versionMilestone));
}
if (StringUtils.isNotEmpty(marker)) {
parameters.add(new StringParameterValue(BuildVarKeys.BUILD_VAR_KEY_MARKER, marker));
}
if (StringUtils.isNotEmpty(serverUrl)) {
parameters.add(new StringParameterValue(BuildVarKeys.BUILD_VAR_KEY_GLOBAL_SERVER_URL, serverUrl));
}
if (StringUtils.isNotEmpty(globalConfig.username)) {
parameters.add(new StringParameterValue(BuildVarKeys.BUILD_VAR_KEY_GLOBAL_USERNAME, globalConfig.username));
}
if (StringUtils.isNotEmpty(globalConfig.password)) {
parameters
.add(new PasswordParameterValue(BuildVarKeys.BUILD_VAR_KEY_GLOBAL_PASSWORD, globalConfig.password));
}
System.out.println("first call to utlis.updateBuildVariables from step 1::::");
Utils.updateBuildVariables(build, parameters);
}
#Extension
public static final class DescriptorImpl extends AbstractStepDescriptorImpl {
public DescriptorImpl() {
super(Execution.class);
}
#Override
public String getFunctionName() {
return "dynatraceAppMonBuildEnvStep";
}
private static final boolean DEFAULT_RECORD_SESSION = false;
public static boolean getDefaultRecordSession() {
return DEFAULT_RECORD_SESSION;
}
#Nonnull
#Override
public String getDisplayName() {
return "Use Dynatrace AppMon to monitor tests";
}
public FormValidation doCheckSystemProfile(#QueryParameter final String systemProfile) {
if (StringUtils.isNotBlank(systemProfile)) {
return FormValidation.ok();
} else {
return FormValidation.error(Messages.RECORDER_VALIDATION_BLANK_SYSTEM_PROFILE());
}
}
public FormValidation doTestDynatraceConnection(#QueryParameter final String systemProfile) {
try {
final TestAutomation connection = new TestAutomation(Utils.createClient());
FetchTestRunsRequest request = new FetchTestRunsRequest(systemProfile);
// We set many constraints to ENSURE no or few testruns are
// returned as this is testing the connection only
request.setVersionBuildFilter("1024");
request.setVersionMajorFilter("1024");
request.setMaxBuilds(1);
try {
connection.fetchTestRuns(request);
} catch (ServerResponseException e) {
switch (e.getStatusCode()) {
case HTTP_UNAUTHORIZED:
return FormValidation.warning(Messages.RECORDER_VALIDATION_CONNECTION_UNAUTHORIZED());
case HTTP_FORBIDDEN:
return FormValidation.warning(Messages.RECORDER_VALIDATION_CONNECTION_FORBIDDEN());
case HTTP_NOT_FOUND:
return FormValidation.warning(Messages.RECORDER_VALIDATION_CONNECTION_NOT_FOUND());
default:
return FormValidation
.warning(Messages.RECORDER_VALIDATION_CONNECTION_OTHER_CODE(e.getStatusCode()));
}
}
return FormValidation.ok(Messages.RECORDER_VALIDATION_CONNECTION_OK());
} catch (Exception e) {
e.printStackTrace();
if (e.getCause() instanceof ClientHandlerException
&& e.getCause().getCause() instanceof SSLHandshakeException) {
return FormValidation.warning(Messages.RECORDER_VALIDATION_CONNECTION_CERT_EXCEPTION(e.toString()));
}
return FormValidation.warning(Messages.RECORDER_VALIDATION_CONNECTION_UNKNOWN(e.toString()));
}
}
}
public static final class Execution extends AbstractSynchronousNonBlockingStepExecution<Boolean> {
#Inject
private transient DynatraceAppMonBuildEnvStep step;
// public final Boolean recordSession = step.recordSession;
// public final Boolean recordSession = false;
#StepContextParameter
private transient TaskListener listener;
#StepContextParameter
private transient Run<?, ?> build;
#Override
protected Boolean run() throws Exception {
Boolean result = true;
final TAGlobalConfiguration globalConfig = GlobalConfiguration.all().get(TAGlobalConfiguration.class);
final Sessions sessions = new Sessions(Utils.createClient());
final PrintStream logger = listener.getLogger();
// logger.println("host is:"+globalConfig.host);
try {
String serverUrl = new URI(globalConfig.protocol, null, globalConfig.host, globalConfig.port, null,
null, null).toString();
if (step.recordSession) {
logger.println("Starting session recording via Dynatrace Server REST interface...");
StartRecordingRequest request = new StartRecordingRequest(step.systemProfile);
request.setPresentableName(
String.format(RECORD_SESSION_NAME, step.systemProfile, build.getNumber()));
final String sessionNameOut = sessions.startRecording(request);
logger.println("Dynatrace session " + sessionNameOut + " has been started");
}
step.setupBuildVariables(build, serverUrl);
} catch (Exception e) {
e.printStackTrace();
build.addAction(new TABuildSetupStatusAction(true));
logger.println(
"ERROR: Dynatrace AppMon Plugin - build set up failed (see the stacktrace to get more information):\n"
+ e.toString());
}
// final PrintStream logger = listener.getLogger();
/*
* logger.println("Dynatrace AppMon Plugin - build tear down...");
* try { if (recordSession) { final String storedSessionName =
* storeSession(logger); Utils.updateBuildVariable(build,
* BuildVarKeys.BUILD_VAR_KEY_STORED_SESSION_NAME,
* storedSessionName); } } catch (Exception e) {
* e.printStackTrace(); logger.
* println("ERROR: Dynatrace AppMon Plugin - build tear down failed (see the stacktrace to get more information):\n"
* + e.toString()); }
*/
return result;
}
private static final long serialVersionUID = 1L;
/**
* #return stored session name
*/
/*
* private String storeSession(final PrintStream logger) throws
* ServerResponseException, ServerConnectionException { logger.
* println("Storing session via Dynatrace Server REST interface...");
* String sessionName = sessions.stopRecording(step.systemProfile);
* logger.println("Dynatrace session " + sessionName +
* " has been stored"); return sessionName; }
*/
}
}
**Utils.java**
package com.dynatrace.jenkins.dashboard.utils;
import com.dynatrace.jenkins.dashboard.TABuildSetupStatusAction;
import com.dynatrace.jenkins.dashboard.TAGlobalConfiguration;
import com.dynatrace.jenkins.dashboard.model_2_0_0.*;
import com.dynatrace.sdk.server.BasicServerConfiguration;
import com.dynatrace.sdk.server.DynatraceClient;
import com.dynatrace.sdk.server.testautomation.models.TestRuns;
import hudson.model.AbstractBuild;
import hudson.model.ParameterValue;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.StringParameterValue;
import jenkins.model.GlobalConfiguration;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.util.*;
/**
* Created by krzysztof.necel on 2016-01-25.
*/
public final class Utils {
public static final String TEST_MEASURE_UNIT_DEFAULT = "num";
public static final String DYNATRACE_ICON_24_X_24_FILEPATH = "/plugin/dynatrace-dashboard/images/dynatrace_icon_24x24.png";
public static final String DYNATRACE_ICON_48_X_48_FILEPATH = "/plugin/dynatrace-dashboard/images/dynatrace_icon_48x48.png";
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##");
private static final String FORMAT_DOUBLE_NULL_VALUE = "N/A";
private Utils() {
}
public static DynatraceClient createClient() {
final TAGlobalConfiguration globalConfig = GlobalConfiguration.all().get(TAGlobalConfiguration.class);
BasicServerConfiguration config = new BasicServerConfiguration(globalConfig.username,
globalConfig.password,
globalConfig.protocol.startsWith("https"),
globalConfig.host,
globalConfig.port,
globalConfig.validateCerts,
//connection timeout, 0 stands for infinite
0);
return new DynatraceClient(config);
}
public static TAReportDetails convertTestRuns(TestRuns sdkTestRuns) {
ArrayList<TestRun> testRuns = new ArrayList<>();
if (sdkTestRuns != null) {
for (com.dynatrace.sdk.server.testautomation.models.TestRun tr : sdkTestRuns.getTestRuns()) {
testRuns.add(convertTestRun(tr));
}
}
return new TAReportDetails(testRuns);
}
public static TestRun convertTestRun(com.dynatrace.sdk.server.testautomation.models.TestRun sdkTestRun) {
List<TestResult> testResults = new ArrayList<>();
for (com.dynatrace.sdk.server.testautomation.models.TestResult sdkResult : sdkTestRun.getTestResults()) {
testResults.add(convertTestResult(sdkResult));
}
Map<TestStatus, Integer> testRunSummary = new EnumMap<>(TestStatus.class);
testRunSummary.put(TestStatus.FAILED, sdkTestRun.getFailedCount());
testRunSummary.put(TestStatus.DEGRADED, sdkTestRun.getDegradedCount());
testRunSummary.put(TestStatus.VOLATILE, sdkTestRun.getVolatileCount());
testRunSummary.put(TestStatus.IMPROVED, sdkTestRun.getImprovedCount());
testRunSummary.put(TestStatus.PASSED, sdkTestRun.getPassedCount());
return new TestRun(testResults, testRunSummary, sdkTestRun.getId(), convertTestCategory(sdkTestRun.getCategory()));
}
public static TestResult convertTestResult(com.dynatrace.sdk.server.testautomation.models.TestResult sdkTestResult) {
Set<TestMeasure> measures = new HashSet<>();
for (com.dynatrace.sdk.server.testautomation.models.TestMeasure sdkMeasure : sdkTestResult.getMeasures()) {
measures.add(convertTestMeasure(sdkMeasure));
}
return new TestResult(new Date(sdkTestResult.getExecutionTime()), sdkTestResult.getName(), sdkTestResult.getPackageName(), sdkTestResult.getPlatform(), convertTestStatus(sdkTestResult.getStatus()), measures);
}
public static TestMeasure convertTestMeasure(com.dynatrace.sdk.server.testautomation.models.TestMeasure sdkTestMeasure) {
String unit = sdkTestMeasure.getUnit() != null ? sdkTestMeasure.getUnit() : TEST_MEASURE_UNIT_DEFAULT;
return new TestMeasure(sdkTestMeasure.getName(),
sdkTestMeasure.getMetricGroup(),
sdkTestMeasure.getExpectedMin(),
sdkTestMeasure.getExpectedMax(),
sdkTestMeasure.getValue(),
unit,
sdkTestMeasure.getViolationPercentage());
}
public static TestCategory convertTestCategory(com.dynatrace.sdk.server.testautomation.models.TestCategory sdkTestCategory) {
switch (sdkTestCategory) {
case UNIT:
return TestCategory.UNIT;
case UI_DRIVEN:
return TestCategory.UI_DRIVEN;
case WEB_API:
return TestCategory.WEB_API;
case PERFORMANCE:
return TestCategory.PERFORMANCE;
}
throw new IllegalArgumentException("Could not convert TestCategory");
}
public static TestStatus convertTestStatus(com.dynatrace.sdk.server.testautomation.models.TestStatus sdkTestStatus) {
return TestStatus.valueOf(sdkTestStatus.name());
}
public static Map<TestStatus, Integer> createReportAggregatedSummary(TAReportDetails reportDetails) {
// just sum all the reports for test runs
final Map<TestStatus, Integer> summary = new EnumMap<>(TestStatus.class);
for (TestRun testRun : reportDetails.getTestRuns()) {
Map<TestStatus, Integer> testRunSummary = testRun.getSummary();
for (Map.Entry<TestStatus, Integer> entry : testRunSummary.entrySet()) {
Integer value = summary.get(entry.getKey());
summary.put(entry.getKey(), value == null ? entry.getValue() : entry.getValue() + value);
}
}
return summary;
}
public static String formatDouble(Double d) {
return d == null ? FORMAT_DOUBLE_NULL_VALUE : DECIMAL_FORMAT.format(d);
}
public static String formatDoublePercentage(Double d) {
return d == null ? FORMAT_DOUBLE_NULL_VALUE : DECIMAL_FORMAT.format(d * 100);
}
public static boolean isValidBuild(AbstractBuild build, PrintStream logger, String message) {
if (build.getResult() == Result.ABORTED) {
logger.println("Build has been aborted - " + message);
return false;
}
TABuildSetupStatusAction setupStatusAction = build.getAction(TABuildSetupStatusAction.class);
if (setupStatusAction != null && setupStatusAction.isSetupFailed()) {
logger.println("Failed to set up environment for Dynatrace AppMon Plugin - " + message);
return false;
}
return true;
}
public static boolean isValidBuild(Run build, PrintStream logger, String message) {
if (build.getResult() == Result.ABORTED) {
logger.println("Build has been aborted - " + message);
return false;
}
TABuildSetupStatusAction setupStatusAction = build.getAction(TABuildSetupStatusAction.class);
if (setupStatusAction != null && setupStatusAction.isSetupFailed()) {
logger.println("Failed to set up environment for Dynatrace AppMon Plugin - " + message);
return false;
}
return true;
}
public static void updateBuildVariables(AbstractBuild<?, ?> build, List<ParameterValue> parameters) {
DynatraceVariablesAction existingAction = build.getAction(DynatraceVariablesAction.class);
if (existingAction == null) {
build.addAction(new DynatraceVariablesAction(parameters));
} else {
build.replaceAction(existingAction.createUpdated(parameters));
}
}
public static void updateBuildVariables(Run<?, ?> build, List<ParameterValue> parameters) {
DynatraceVariablesAction existingAction = build.getAction(DynatraceVariablesAction.class);
if (existingAction == null) {
build.addAction(new DynatraceVariablesAction(parameters));
} else {
build.replaceAction(existingAction.createUpdated(parameters));
}
}
public static void updateBuildVariable(AbstractBuild<?, ?> build, String key, String value) {
updateBuildVariables(build, Collections.<ParameterValue>singletonList(new StringParameterValue(key, value)));
}
public static void updateBuildVariable(Run<?, ?> build, String key, String value) {
updateBuildVariables(build, Collections.<ParameterValue>singletonList(new StringParameterValue(key, value)));
}
}
**DynatraceVariablesAction.java**
package com.dynatrace.jenkins.dashboard.utils;
import hudson.EnvVars;
import hudson.Extension;
import hudson.model.*;
import javax.annotation.Nonnull;
import java.util.*;
public class DynatraceVariablesAction extends ParametersAction {
private List<ParameterValue> parameters = new ArrayList<>();
public DynatraceVariablesAction(Collection<? extends ParameterValue> parameters) {
this.parameters.addAll(parameters);
}
#Override
public List<ParameterValue> getParameters() {
return Collections.unmodifiableList(parameters);
}
#Override
public ParameterValue getParameter(String name) {
for (ParameterValue p : parameters) {
if (p == null) continue;
if (p.getName().equals(name))
return p;
}
return null;
}
#Nonnull
#Override
public DynatraceVariablesAction createUpdated(Collection<? extends ParameterValue> overrides) {
List<ParameterValue> newParams = new ArrayList<>(overrides);
outer:
for (ParameterValue value : this.parameters) {
for (ParameterValue newParam : newParams) {
if (newParam.getName().equals(value.getName())) {
continue outer;
}
}
newParams.add(value);
}
return new DynatraceVariablesAction(newParams);
}
#Extension
public static final class DynatraceBuildVariablesContributor extends BuildVariableContributor {
#Override
public void buildVariablesFor(AbstractBuild r, Map<String, String> variables) {
DynatraceVariablesAction a = r.getAction(DynatraceVariablesAction.class);
if (a == null) {
return;
}
for (ParameterValue spv : a.getParameters()) {
variables.put(spv.getName(), String.valueOf(spv.getValue()));
}
}
}
#Extension
public static final class DynatraceVariablesEnvironmentContributor extends EnvironmentContributor {
#Override
public void buildEnvironmentFor(Run r, EnvVars vars, TaskListener listener) {
DynatraceVariablesAction a = r.getAction(DynatraceVariablesAction.class);
if (a == null) {
return;
}
for (ParameterValue spv : a.getParameters()) {
vars.put(spv.getName(), String.valueOf(spv.getValue()));
}
}
}
}
I also tried to set the environment variables by extending InvisibleAction class instead of ParametersAction, as performance-signature-dynatrace-plugin is doing but that didn't seem to work as well. Dynatrace performance signature plugin is setting the environment variables in a bit different way. See these two links:
[https://github.com/jenkinsci/performance-signature-dynatrace-plugin/blob/master/dynatrace/src/main/java/de/tsystems/mms/apm/performancesignature/dynatrace/PerfSigEnvInvisAction.java][1]
[https://github.com/jenkinsci/performance-signature-dynatrace-plugin/blob/master/dynatrace/src/main/java/de/tsystems/mms/apm/performancesignature/dynatrace/PerfSigEnvContributor.java][2]
[1]: https://github.com/jenkinsci/performance-signature-dynatrace-plugin/blob/master/dynatrace/src/main/java/de/tsystems/mms/apm/performancesignature/dynatrace/PerfSigEnvInvisAction.java
[2]: https://github.com/jenkinsci/performance-signature-dynatrace-plugin/blob/master/dynatrace/src/main/java/de/tsystems/mms/apm/performancesignature/dynatrace/PerfSigEnvContributor.java
Any help would be highly appreciated. I hope I was able to describe the problem. Please feel free to comment if my issue/question is not clear.
Thanks.
I followed the example of the page of primefaces (http://www.primefaces.org/showcase/ui/datatableLazy.jsf), and it worked but because the example is loading the list on the constructor it is no exactly lazy loading. I understand that is a dummy example, so on my bean I added this lines
private LazyDataModel<Ficha> lazyListFichas;
And my init funcion I changed some parts to this
#PostConstruct
public void init() {
System.out.println("Inicializando fichas");
tienePadres=false;
fichaDM.setFicha(new Ficha());
//obtenerFichas();
//lazyListFichas = new FichaLazyList(fichaDM.getFichas);
lazyListFichas = new FichaLazyList();
}
My FichaLazyList is like this
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.ejb.EJB;
import javax.faces.bean.ViewScoped;
import org.primefaces.model.LazyDataModel;
import org.primefaces.model.SortOrder;
import ec.edu.puce.biologia.dao.FichaDao;
import ec.edu.puce.biologia.util.LazySorter;
#ViewScoped
public class FichaLazyList extends LazyDataModel <Ficha> {
private static final long serialVersionUID = 1L;
private List<Ficha> fichas=new ArrayList<Ficha>();
#EJB
private FichaDao fichaDao;
public FichaLazyList() {
}
public FichaLazyList(List<Ficha> fichas) {
this.fichas = fichas;
}
#Override
public Ficha getRowData(String rowKey) {
for(Ficha ficha : fichas) {
if(ficha.getIdFicha().equals(rowKey))
return ficha;
}
return null;
}
#Override
public Object getRowKey(Ficha ficha) {
return ficha.getIdFicha();
}
#Override
public List<Ficha> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String,String> filters) {
List<Ficha> data = new ArrayList<Ficha>();
//filter
fichas = fichaDao.encontrarPagina(first, pageSize);
if(fichas==null){
System.out.println("Lista nula");
}
for(Ficha ficha : fichas) {
boolean match = true;
for(Iterator<String> it = filters.keySet().iterator(); it.hasNext();) {
try {
String filterProperty = it.next();
String filterValue = filters.get(filterProperty);
String fieldValue = String.valueOf(ficha.getClass().getField(filterProperty).get(ficha));
if(filterValue == null || fieldValue.startsWith(filterValue)) {
match = true;
}
else {
match = false;
break;
}
} catch(Exception e) {
match = false;
}
}
if(match) {
data.add(ficha);
}
}
long total = fichaDao.contar();
System.out.println("xxx"+total);
//sort
if(sortField != null) {
Collections.sort(data, new LazySorter(sortField, sortOrder));
}
//rowCount
// int dataSize = data.size();
this.setRowCount((int) total);
//paginate
if(total > pageSize) {
try {
return data.subList(first, first + pageSize);
}
catch(IndexOutOfBoundsException e) {
return data.subList(first, first + ((int) total % pageSize));
}
}
else {
return data;
}
}
public List<Ficha> getFichas() {
return fichas;
}
public void setFichas(List<Ficha> fichas) {
this.fichas = fichas;
}
}
I get this error,
ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/catalogoBiologia].[Faces Servlet]] (http-localhost-127.0.0.1-8080-1) Servlet.service() for servlet Faces Servlet threw exception: java.lang.NullPointerException
at ec.edu.puce.biologia.model.FichaLazyList.load(FichaLazyList.java:55) [classes:]
Line 55 is this
fichas = fichaDao.encontrarPagina(first, pageSize);
Should it be viewscope or session scope, What am I doing wrong when I try to inject the ejb
So thanks Balusc, I was only calling it in my function with new. I changed value="#{taxonomiaController.lazyListFichas}" from my datatable to value="#{fichaLazyList}", In my head I didnt see FichaLazyList as a bean, it was another class, anyways that solved the problem and, I added #ManagedBean and #RequestScoped to FichaLazyList I guess it could be also #viewScoped. It seemed to me that should be RequestedScoped, if not I ll fix this.
Also I added this to the init function, it is another approach using an anoymous class:
lazyListFichas = new LazyDataModel() {
#Override
public List load(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters){
List<Ficha> lazyFicha = new ArrayList<Ficha>();
lazyFicha = servicioFicha.obtenerTodos(first, pageSize);
// populateLazyRandomCars(lazyFicha, pageSize);
return lazyFicha;
}
};
/**
* In a real application, this number should be resolved by a projection
* query
*/
lazyListFichas.setRowCount((int) servicioFicha.contarRegistrosTotal());
I think the problem is not in your managed bean,the problem is in your model EJB or Hibernate
because the value of fichas is null so fichaDao.encontrarPagina(first, pageSize) returns null check the code of fichaDao and your hibernate settings to connect to database.
Here's my code:
public String generateEISReports_PDF(){
surveyReportService.setSurveyReportDA(surveyReportDA);
surveyReportList = surveyReportService.getSurveyReport(surveyType, surveyDate, projectCode, employeeCode);
if(surveyReportList != null){
System.out.println(surveyReportList + "testing");
System.out.println(surveyReportList.size() + "size ito");
for (SurveyReport surveyReport : surveyReportList) {
System.out.println(surveyReport.getRiskRank().toString() + "asdf");
surveyReports.add(surveyReport);
}
}
this.compileTheJasperReports();
return SUCCESS;
}
I am getting the whole row values with this code as an object. I want to iterate the field values in every list of objects. How can I do that?
By using reflection you can achieve that. Following is the code. This might help you.
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class SurveyReport {
public int localServeryCont; // if you make it private then changes have to be done in 'SurveyIterator' inner class.
public String surveyReporterName;
public static void main(String[] args) {
List<SurveyReport> surveyReportList = new ArrayList<SurveyReport>();
SurveyReport sr = new SurveyReport(); //first object creation
sr.localServeryCont = 10;
sr.surveyReporterName = "AAA";
surveyReportList.add(sr);
sr = new SurveyReport(); //second object creation
sr.localServeryCont = 100;
sr.surveyReporterName = "BBB";
surveyReportList.add(sr); //two objects are in the list-object.
for (SurveyReport surveyReport : surveyReportList) {
Iterator<String> itr = surveyReport.iterator(); //You can work around with 'java.lang.Iterable' to use 'foreach' loop
while (itr.hasNext()) { //this is what you might be expecting
System.out.println("SurveyReport's object's values : " + itr.next());
}
}
}
public Iterator<String> iterator() { //here is method to get iterator object.
return new SurveyIterator();
}
private class SurveyIterator implements Iterator<String> { //creating 'SurveyIterator' INNER class
private int totalAvailableField = SurveyReport.class.getDeclaredFields().length;
int cursor = 0;
Field[] surveyReportFields = SurveyReport.class.getFields();
#Override
public boolean hasNext() {
return cursor != totalAvailableField;
}
#Override
public String next() {
String next = null;
try {
next = (surveyReportFields[cursor].get(SurveyReport.this)).toString();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
cursor++;
return next;
}
#Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}
I have a method of a class in a jar whose body I want to exchange with my own. In this case I just want to have the method print out "GOT IT" to the console and return true;
I am using the system loader to load the classes of the jar. I am using reflection to make the system classloader be able to load classes by bytecode. This part seems to be working correctly.
I am following the method replacement example found here: asm.ow2.org/current/asm-transformations.pdf.
My code is as follows:
public class Main
{
public static void main(String[] args)
{
URL[] url = new URL[1];
try
{
url[0] = new URL("file:////C://Users//emist//workspace//tmloader//bin//runtime//tmgames.jar");
verifyValidPath(url[0]);
}
catch (Exception ex)
{
System.out.println("URL error");
}
Loader l = new Loader();
l.loadobjection(url);
}
public static void verifyValidPath(URL url) throws FileNotFoundException
{
File filePath = new File(url.getFile());
if (!filePath.exists())
{
throw new FileNotFoundException(filePath.getPath());
}
}
}
class Loader
{
private static final Class[] parameters = new Class[] {URL.class};
public static void addURL(URL u) throws IOException
{
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try
{
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] {u});
}
catch (Throwable t)
{
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}
}
private Class loadClass(byte[] b, String name)
{
//override classDefine (as it is protected) and define the class.
Class clazz = null;
try
{
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class cls = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method =
cls.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });
// protected method invocaton
method.setAccessible(true);
try
{
Object[] args = new Object[] {name, b, new Integer(0), new Integer(b.length)};
clazz = (Class) method.invoke(loader, args);
}
finally
{
method.setAccessible(false);
}
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
return clazz;
}
public void loadobjection(URL[] myJar)
{
try
{
Loader.addURL(myJar[0]);
//tmcore.game is the class that holds the main method in the jar
/*
Class<?> classToLoad = Class.forName("tmcore.game", true, this.getClass().getClassLoader());
if(classToLoad == null)
{
System.out.println("No tmcore.game");
return;
}
*/
MethodReplacer mr = null;
ClassReader cr = new ClassReader("tmcore.objwin");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MethodVisitor mv = null;
try
{
mr = new MethodReplacer(cw, "Test", "(Ljava/lang/String;ZLjava/lang/String;)Z");
}
catch (Exception e)
{
System.out.println("Method Replacer Exception");
}
cr.accept(mr, ClassReader.EXPAND_FRAMES);
PrintWriter pw = new PrintWriter(System.out);
loadClass(cw.toByteArray(), "tmcore.objwin");
Class<?> classToLoad = Class.forName("tmcore.game", true, this.getClass().getClassLoader());
if(classToLoad == null)
{
System.out.println("No tmcore.game");
return;
}
//game doesn't have a default constructor, so we need to get the reference to public game(String[] args)
Constructor ctor = classToLoad.getDeclaredConstructor(String[].class);
if(ctor == null)
{
System.out.println("can't find constructor");
return;
}
//Instantiate the class by calling the constructor
String[] args = {"tmgames.jar"};
Object instance = ctor.newInstance(new Object[]{args});
if(instance == null)
{
System.out.println("Can't instantiate constructor");
}
//get reference to main(String[] args)
Method method = classToLoad.getDeclaredMethod("main", String[].class);
//call the main method
method.invoke(instance);
}
catch (Exception ex)
{
System.out.println(ex.getMessage());
ex.printStackTrace();
}
}
}
public class MethodReplacer extends ClassVisitor implements Opcodes
{
private String mname;
private String mdesc;
private String cname;
public MethodReplacer(ClassVisitor cv, String mname, String mdesc)
{
super(Opcodes.ASM4, cv);
this.mname = mname;
this.mdesc = mdesc;
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces)
{
this.cname = name;
cv.visit(version, access, name, signature, superName, interfaces);
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions)
{
String newName = name;
if(name.equals(mname) && desc.equals(mdesc))
{
newName = "orig$" + name;
generateNewBody(access, desc, signature, exceptions, name, newName);
System.out.println("Replacing");
}
return super.visitMethod(access, newName, desc, signature, exceptions);
}
private void generateNewBody(int access, String desc, String signature, String[] exceptions,
String name, String newName)
{
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(access, cname, newName, desc);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("GOTit!");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}
The problem seems to be at mv.visitMethodInsn(access, cname, newName, desc); in generateMethodBody inside MethodReplacer.
I get an "Illegal Type in constant pool" error.
I'm not sure what I'm missing...but after reading and testing for about 3 days I'm still not getting anywhere.
[Edit]
In case you were wondering, tmcore is a single player "Objection" game for lawyers. I'm doing this for the fun of it. The program successfully launches the game and everything is fine, removing the modifications from MethodReplacer makes the game behave as designed. So the issue seems to be isolated to bad bytecode/modifications by me inside the method replacer.
[EDIT2]
CheckClassAdapter.verify(cr, true, pw); returns the exact same bytecode that the function is supposed to have before editing. It is as if the changes are not being done.
[EDIT3]
copy of classtoload commented out as per comments
If you are using Eclipse, you should install Bytecode Outline - it is indispensible.
I built a small test for what you want to achieve (this should match the signature of your test method, you will have to change package and classname):
package checkASM;
public class MethodCall {
public boolean Test(String a, boolean b, String c) {
System.out.println("GOTit");
return false;
}
}
requires the following bytecode to build the method:
{
mv = cw.visitMethod(ACC_PUBLIC, "Test",
"(Ljava/lang/String;ZLjava/lang/String;)Z", null, null);
mv.visitCode();
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitFieldInsn(GETSTATIC, "java/lang/System",
"out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("GOTit");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V");
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLocalVariable("this", "LcheckASM/MethodCall;", null, l1, l3, 0);
mv.visitLocalVariable("a", "Ljava/lang/String;", null, l1, l3, 1);
mv.visitLocalVariable("b", "Z", null, l1, l3, 2);
mv.visitLocalVariable("c", "Ljava/lang/String;", null, l1, l3, 3);
mv.visitMaxs(4, 4);
mv.visitEnd();
}
Calls to visitLineNumber can be omitted. So apparently, you are missing all labels, forgot to load the method parameters, did not ignore the return value, set the wrong values for visitMaxs (this is not necessarily needed, it depends on your ClassWriter flags if I recall correctly) and did not visit local variables (or parameters in this case).
Additionally, your classloading seems to be a little confused / messed up.
I don't have the jar (so I can't say if these work), but maybe you could replace Main and Loader:
Main:
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
public class Main {
public static void main(String[] args) {
try {
Loader.instrumentTmcore(args);
} catch (Exception e) {
System.err.println("Ooops");
e.printStackTrace();
}
}
}
Loader:
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
public class Loader {
public static ClassReader fetchReader(String binaryName) throws Exception {
return new ClassReader(
Loader.class.getClassLoader().getSystemResourceAsStream(
binaryName.replace('.', '/') + ".class"
)
)
;
}
public static synchronized Class<?> loadClass(byte[] bytecode)
throws Exception {
ClassLoader scl = ClassLoader.getSystemClassLoader();
Class<?>[] types = new Class<?>[] {
String.class, byte[].class, int.class, int.class
};
Object[] args = new Object[] {
null, bytecode, 0, bytecode.length
};
Method m = ClassLoader.class.getMethod("defineClass", types);
m.setAccessible(true);
return (Class<?>) m.invoke(scl, args);
}
public static void instrumentTmcore(String[] args) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MethodReplacer mr = new MethodReplacer(cw, "Test",
"(Ljava/lang/String;ZLjava/lang/String;)Z");
fetchReader("tmcore.objwin").accept(mr, ClassReader.EXPAND_FRAMES);
loadClass(cw.toByteArray());
Class.forName("tmcore.game")
.getMethod("main", new Class<?>[] {args.getClass()})
.invoke(null, new Object[] { args });
}
}
ASKER'S ANSWER MOVED FROM QUESTION
The java bytecode was never the problem. It is the way I was loading the jar which made it impossible to instrument the code.
Thanks to Ame for helping me tackle it.
The following code works:
MAIN
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.io.FileInputStream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
public class Main implements Opcodes
{
public static void main(String[] args) throws Exception
{
byte[] obj = readClass("tmcore/obj.class");
ClassReader objReader = new ClassReader(obj);
ClassWriter objWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
MethodReplacer demoReplacer = new MethodReplacer(objWriter, "run", "()V");
demoReplacer.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "tmcore/obj", null, "java/applet/Applet", new String[] { "java/lang/Runnable" });
objReader.accept(demoReplacer, ClassReader.EXPAND_FRAMES);
objReader = new ClassReader(objWriter.toByteArray());
Class objC = Loader.loadClass(objWriter.toByteArray(), "tmcore.obj");
if(objC == null)
{
System.out.println("obj cannot be loaded");
}
Class game = ClassLoader.getSystemClassLoader().loadClass("tmcore.game");
if(game == null)
{
System.out.println("Can't load game");
return;
}
Constructor ctor = game.getDeclaredConstructor(String[].class);
if(ctor == null)
{
System.out.println("can't find constructor");
return;
}
//Instantiate the class by calling the constructor
String[] arg = {"tmgames.jar"};
Object instance = ctor.newInstance(new Object[]{args});
if(instance == null)
{
System.out.println("Can't instantiate constructor");
}
//get reference to main(String[] args)
Method method = game.getDeclaredMethod("main", String[].class);
//call the main method
method.invoke(instance);
}
public static void verifyValidPath(String path) throws FileNotFoundException
{
File filePath = new File(path);
if (!filePath.exists())
{
throw new FileNotFoundException(filePath.getPath());
}
}
public static byte[] readClass(String classpath) throws Exception
{
verifyValidPath(classpath);
File f = new File(classpath);
FileInputStream file = new FileInputStream(f);
if(file == null)
throw new FileNotFoundException();
byte[] classbyte = new byte[(int)f.length()];
int offset = 0, numRead = 0;
while (offset < classbyte.length
&& (numRead=file.read(classbyte, offset, classbyte.length-offset)) >= 0)
{
offset += numRead;
}
if (offset < classbyte.length)
{
file.close();
throw new IOException("Could not completely read file ");
}
file.close();
return classbyte;
}
}
LOADER:
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
class Loader
{
private static final Class[] parameters = new Class[] {URL.class};
public static void addURL(URL u) throws IOException
{
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try
{
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] {u});
}
catch (Throwable t)
{
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}
}
public static Class loadClass(byte[] b, String name)
{
//override classDefine (as it is protected) and define the class.
Class clazz = null;
try
{
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class cls = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method =
cls.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });
// protected method invocaton
method.setAccessible(true);
try
{
Object[] args = new Object[] {name, b, new Integer(0), new Integer(b.length)};
clazz = (Class) method.invoke(loader, args);
}
finally
{
method.setAccessible(false);
}
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
return clazz;
}
}
MethodReplacer remains the same.