public class InstrumentationSupplierAgent { public static volatile Instrumentation instrumentation; public static void agentmain(String args, Instrumentation inst) throws Exception { System.out.println("==**agent started**=="); InstrumentationSupplierAgent.instrumentation = inst; System.out.println("==**agent execution complete**=="); } }
@Component public class AgentInstaller { private static final Logger log = LoggerFactory.getLogger(AgentInstaller.class); private final JiraHome jiraHome; private final JiraProperties jiraProperties; @Autowired public AgentInstaller( @ComponentImport JiraHome jiraHome, @ComponentImport JiraProperties jiraProperties ) { this.jiraHome = jiraHome; this.jiraProperties = jiraProperties; } private static File getInstrumentationDirectory(JiraHome jiraHome) throws IOException { final File dataDirectory = jiraHome.getDataDirectory(); final File instrFolder = new File(dataDirectory, "instrumentation"); if (!instrFolder.exists()) { Files.createDirectory(instrFolder.toPath()); } return instrFolder; } private static File loadFileFromCurrentJar(File destination, String fileName) throws IOException { try (InputStream resourceAsStream = AgentInstaller.class.getResourceAsStream("/lib/" + fileName)) { final File existingFile = new File(destination, fileName); if (!existingFile.exists() || !isCheckSumEqual(new FileInputStream(existingFile), resourceAsStream)) { Files.deleteIfExists(existingFile.toPath()); existingFile.createNewFile(); try (OutputStream os = new FileOutputStream(existingFile)) { IOUtils.copy(resourceAsStream, os); } } return existingFile; } } private static boolean isCheckSumEqual(InputStream existingFileStream, InputStream newFileStream) { try (InputStream oldIs = existingFileStream; InputStream newIs = newFileStream) { return Arrays.equals(getMDFiveDigest(oldIs), getMDFiveDigest(newIs)); } catch (NoSuchAlgorithmException | IOException e) { log.error("Error to compare checksum for streams {},{}", existingFileStream, newFileStream); return false; } } private static byte[] getMDFiveDigest(InputStream is) throws IOException, NoSuchAlgorithmException { final MessageDigest md = MessageDigest.getInstance("MD5"); md.digest(IOUtils.toByteArray(is)); return md.digest(); } public void install() throws PluginException { try { log.trace("Trying to install tools and agent"); if (!isProperAgentLoaded()) { log.info("Instrumentation agent is not installed yet or has wrong version"); final String pid = getPid(); log.debug("Current VM PID={}", pid); final URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); log.debug("System classLoader={}", systemClassLoader); final Class<?> virtualMachine = getVirtualMachineClass( systemClassLoader, "com.sun.tools.attach.VirtualMachine", true ); log.debug("VM class={}", virtualMachine); Method attach = virtualMachine.getMethod("attach", String.class); Method loadAgent = virtualMachine.getMethod("loadAgent", String.class); Method detach = virtualMachine.getMethod("detach"); Object vm = null; try { log.trace("Attaching to VM with PID={}", pid); vm = attach.invoke(null, pid); final File agentFile = getAgentFile(); log.debug("Agent file: {}", agentFile); loadAgent.invoke(vm, agentFile.getAbsolutePath()); } finally { tryToDetach(vm, detach); } } else { log.info("Instrumentation agent is already installed"); } } catch (Exception e) { throw new IllegalPluginStateException("Failed to load: agent and tools are not installed properly", e); } } private boolean isProperAgentLoaded() { try { ClassLoader.getSystemClassLoader().loadClass(InstrumentationProvider.INSTRUMENTATION_CLASS_NAME); return true; } catch (Exception e) { return false; } } private void tryToDetach(Object vm, Method detach) { try { if (vm != null) { log.trace("Detaching from VM: {}", vm); detach.invoke(vm); } else { log.warn("Failed to detach, vm is null"); } } catch (Exception e) { log.warn("Failed to detach", e); } } private String getPid() { String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName(); return nameOfRunningVM.split("@", 2)[0]; } private Class<?> getVirtualMachineClass(URLClassLoader systemClassLoader, String className, boolean tryLoadTools) throws Exception { log.trace("Trying to get VM class, loadingTools={}", tryLoadTools); try { return systemClassLoader.loadClass(className); } catch (ClassNotFoundException e) { if (tryLoadTools) { final OS os = getRunningOs(); os.tryToLoadTools(systemClassLoader, jiraHome); return getVirtualMachineClass(systemClassLoader, className, false); } else { throw new ReflectiveOperationException("Failed to load VM class", e); } } } private OS getRunningOs() { final String osName = jiraProperties.getSanitisedProperties().get("os.name"); log.debug("OS name: {}", osName); if (Pattern.compile(".*[Ll]inux.*").matcher(osName).matches()) { return OS.LINUX; } else if (Pattern.compile(".*[Ww]indows.*").matcher(osName).matches()) { return OS.WINDOWS; } else { throw new IllegalStateException("Unknown OS running"); } } private File getAgentFile() throws IOException { final File agent = loadFileFromCurrentJar(getInstrumentationDirectory(jiraHome), "instrumentation-agent.jar"); agent.deleteOnExit(); return agent; } private enum OS { WINDOWS { @Override protected String getToolsFilename() { return "tools-windows.jar"; } @Override protected String getAttachLibFilename() { return "attach.dll"; } }, LINUX { @Override protected String getToolsFilename() { return "tools-linux.jar"; } @Override protected String getAttachLibFilename() { return "libattach.so"; } }; public void tryToLoadTools(URLClassLoader systemClassLoader, JiraHome jiraHome) throws Exception { log.trace("Trying to load tools"); final File instrumentationDirectory = getInstrumentationDirectory(jiraHome); appendLibPath(instrumentationDirectory.getAbsolutePath()); loadFileFromCurrentJar(instrumentationDirectory, getAttachLibFilename()); resetCache(); final File tools = loadFileFromCurrentJar(instrumentationDirectory, getToolsFilename()); final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); method.setAccessible(true); method.invoke(systemClassLoader, tools.toURI().toURL()); } private void resetCache() throws NoSuchFieldException, IllegalAccessException { Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); fieldSysPath.setAccessible(true); fieldSysPath.set(null, null); } private void appendLibPath(String instrumentationDirectory) { if (System.getProperty("java.library.path") != null) { System.setProperty("java.library.path", System.getProperty("java.library.path") + System.getProperty("path.separator") + instrumentationDirectory); } else { System.setProperty("java.library.path", instrumentationDirectory); } } protected abstract String getToolsFilename(); protected abstract String getAttachLibFilename(); } }
private boolean isProperAgentLoaded() { try { ClassLoader.getSystemClassLoader().loadClass(InstrumentationProvider.INSTRUMENTATION_CLASS_NAME); return true; } catch (Exception e) { return false; } }
private Class<?> getVirtualMachineClass(URLClassLoader systemClassLoader, String className, boolean tryLoadTools) throws Exception { log.trace("Trying to get VM class, loadingTools={}", tryLoadTools); try { return systemClassLoader.loadClass(className); } catch (ClassNotFoundException e) { if (tryLoadTools) { final OS os = getRunningOs(); os.tryToLoadTools(systemClassLoader, jiraHome); return getVirtualMachineClass(systemClassLoader, className, false); } else { throw new ReflectiveOperationException("Failed to load VM class", e); } } }
private OS getRunningOs() { final String osName = jiraProperties.getSanitisedProperties().get("os.name"); log.debug("OS name: {}", osName); if (Pattern.compile(".*[Ll]inux.*").matcher(osName).matches()) { return OS.LINUX; } else if (Pattern.compile(".*[Ww]indows.*").matcher(osName).matches()) { return OS.WINDOWS; } else { throw new IllegalStateException("Unknown OS running"); } }
# [solaris] sun.tools.attach.SolarisAttachProvider
# [windows] sun.tools.attach.WindowsAttachProvider
# [linux] sun.tools.attach.LinuxAttachProvider
# [macosx] sun.tools.attach.BsdAttachProvider
# [aix] sun.tools.attach.AixAttachProvider
public void tryToLoadTools(URLClassLoader systemClassLoader, JiraHome jiraHome) throws Exception { log.trace("Trying to load tools"); final File instrumentationDirectory = getInstrumentationDirectory(jiraHome);//{JIRA_HOME}/data/instrumentation loadFileFromCurrentJar(instrumentationDirectory, getAttachLibFilename());// final File tools = loadFileFromCurrentJar(instrumentationDirectory, getToolsFilename());// tools.jar ... }
private static File loadFileFromCurrentJar(File destination, String fileName) throws IOException { try (InputStream resourceAsStream = AgentInstaller.class.getResourceAsStream("/lib/" + fileName)) { final File existingFile = new File(destination, fileName); if (!existingFile.exists() || !isCheckSumEqual(new FileInputStream(existingFile), resourceAsStream)) { Files.deleteIfExists(existingFile.toPath());// - existingFile.createNewFile(); try (OutputStream os = new FileOutputStream(existingFile)) { IOUtils.copy(resourceAsStream, os); } } return existingFile; } }
static { System.loadLibrary("attach"); }
private void appendLibPath(String instrumentationDirectory) { if (System.getProperty("java.library.path") != null) { System.setProperty("java.library.path", System.getProperty("java.library.path") + System.getProperty("path.separator") + instrumentationDirectory); } else { System.setProperty("java.library.path", instrumentationDirectory); } }
private void resetCache() throws NoSuchFieldException, IllegalAccessException { Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); fieldSysPath.setAccessible(true); fieldSysPath.set(null, null); }
final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); method.setAccessible(true); method.invoke(systemClassLoader, tools.toURI().toURL());
... 19 more
Caused by: com.sun.tools.attach.AttachNotSupportedException: no providers installed
at com.sun.tools.attach.VirtualMachine.attach (VirtualMachine.java:203)
Method attach = virtualMachine.getMethod("attach", String.class); Method loadAgent = virtualMachine.getMethod("loadAgent", String.class); Method detach = virtualMachine.getMethod("detach"); Object vm = null; try { final String pid = getPid(); log.debug("Current VM PID={}", pid); log.trace("Attaching to VM with PID={}", pid); vm = attach.invoke(null, pid); final File agentFile = getAgentFile(); log.debug("Agent file: {}", agentFile); loadAgent.invoke(vm, agentFile.getAbsolutePath()); } finally { tryToDetach(vm, detach); }
private String getPid() { String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName(); return nameOfRunningVM.split("@", 2)[0]; }
@Override public void afterPropertiesSet() throws Exception { this.agentInstaller.install(); this.serviceTracker.open(); }
private ServiceTracker<InstrumentationConsumer, Void> initTracker(final BundleContext bundleContext, final InstrumentationProvider instrumentationProvider) { return new ServiceTracker<>(bundleContext, InstrumentationConsumer.class, new ServiceTrackerCustomizer<InstrumentationConsumer, Void>() { @Override public Void addingService(ServiceReference<InstrumentationConsumer> serviceReference) { try { log.trace("addingService called"); final InstrumentationConsumer consumer = bundleContext.getService(serviceReference); log.debug("Consumer: {}", consumer); if (consumer != null) { applyInstrumentation(consumer, instrumentationProvider); } } catch (Throwable t) { log.error("Error on 'addingService'", t); } return null; } @Override public void modifiedService(ServiceReference<InstrumentationConsumer> serviceReference, Void aVoid) { } @Override public void removedService(ServiceReference<InstrumentationConsumer> serviceReference, Void aVoid) { } }); }
private void applyInstrumentation(InstrumentationConsumer consumer, InstrumentationProvider instrumentationProvider) { final Instrumentation instrumentation; try { instrumentation = instrumentationProvider.getInstrumentation(); consumer.applyInstrumentation(instrumentation); } catch (InstrumentationAgentException e) { log.error("Error on getting insrumentation", e); } }
@Component public class InstrumentationProviderImpl implements InstrumentationProvider { private static final Logger log = LoggerFactory.getLogger(InstrumentationProviderImpl.class); @Override public Instrumentation getInstrumentation() throws InstrumentationAgentException { try { final Class<?> agentClass = ClassLoader.getSystemClassLoader().loadClass(INSTRUMENTATION_CLASS_NAME);// , javaagents log.debug("Agent class loaded from system classloader", agentClass); final Field instrumentation = agentClass.getDeclaredField(INSTRUMENTATION_FIELD_NAME);// reflection log.debug("Instrumentation field: {}", instrumentation); final Object instrumentationValue = instrumentation.get(null); if (instrumentationValue == null) { throw new NullPointerException("instrumentation data is null. Seems agent is not installed"); } return (Instrumentation) instrumentationValue; } catch (Throwable e) { String msg = "Error getting instrumentation"; log.error(msg, e); throw new InstrumentationAgentException("Error getting instrumentation", e); } } }
IssueService.CreateValidationResult validateCreate(@Nullable ApplicationUser var1, IssueInputParameters var2);
IssueService.UpdateValidationResult validateUpdate(@Nullable ApplicationUser var1, Long var2, IssueInputParameters var3);
private void tryToFixClassloader(ClassLoader originalClassLoader, BundleWiringImpl.BundleClassLoader bundleClassLoader) { try { final ClassLoader originalParent = originalClassLoader.getParent(); if (originalParent != null) { if (!(originalParent instanceof BundleProxyClassLoader)) { final BundleProxyClassLoader proxyClassLoader = new BundleProxyClassLoader<>(originalParent, bundleClassLoader); FieldUtils.writeDeclaredField(originalClassLoader, "parent", proxyClassLoader, true); } } } catch (IllegalAccessException e) { log.warn("Error on try to fix originalClassLoader {}", originalClassLoader, e); } }
... .transform((builder, typeDescription, classloader) -> { builder.method(named("validateCreate").and(ElementMatchers.isPublic())).intercept(MethodDelegation.to(Interceptor.class)); if (!ClassUtils.isVisible(InstrumentationConsumer.class, classloader)) { tryToFixClassloader(classloader, (BundleWiringImpl.BundleClassLoader) Interceptor.class.getClassLoader()); } }) .installOn(instrumentation);
class BundleProxyClassLoader<T extends BundleWiringImpl.BundleClassLoader> extends ClassLoader { private static final Logger log = LoggerFactory.getLogger(BundleProxyClassLoader.class); private final Set<T> proxies; private final Method loadClass; private final Method shouldDelegate; public BundleProxyClassLoader(ClassLoader parent, T proxy) { super(parent); this.loadClass = getLoadClassMethod(); this.shouldDelegate = getShouldDelegateMethod(); this.proxies = new HashSet<>(); proxies.add(proxy); } private Method getLoadClassMethod() throws IllegalStateException { try { Method loadClass = ClassLoader.class.getDeclaredMethod("loadClass", String.class, boolean.class); loadClass.setAccessible(true); return loadClass; } catch (NoSuchMethodException e) { throw new IllegalStateException("Failed to get loadClass method", e); } } private Method getShouldDelegateMethod() throws IllegalStateException { try { Method shouldDelegate = BundleWiringImpl.class.getDeclaredMethod("shouldBootDelegate", String.class); shouldDelegate.setAccessible(true); return shouldDelegate; } catch (NoSuchMethodException e) { throw new IllegalStateException("Failed to get shouldDelegate method", e); } } @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { log.trace("Trying to find already loaded class {}", name); Class<?> c = findLoadedClass(name); if (c == null) { log.trace("This is new class. Trying to load {} with OSGi", name); c = tryToLoadWithProxies(name, resolve); if (c == null) { log.trace("Failed to load with OSGi. Trying to load {} with parent CL", name); c = super.loadClass(name, resolve); } } if (c == null) { throw new ClassNotFoundException(name); } return c; } } private Class<?> tryToLoadWithProxies(String name, boolean resolve) { for (T proxy : proxies) { try { final String pkgName = Util.getClassPackage(name); //avoid cycle if(!isShouldDelegatePackageLoad(proxy, pkgName)) { log.trace("The load of class {} should not be delegated to OSGI parent, so let's try to load with bundles", name); return (Class<?>) this.loadClass.invoke(proxy, name, resolve); } } catch (ReflectiveOperationException e) { log.trace("Class {} is not found with {}", name, proxy); } } return null; } private boolean isShouldDelegatePackageLoad(T proxy, String pkgName) throws IllegalAccessException, InvocationTargetException { return (boolean)this.shouldDelegate.invoke( FieldUtils.readDeclaredField(proxy, "m_wiring", true), pkgName ); } }
public class DefaultIssueServiceValidateCreateAdvice { @Advice.OnMethodExit(onThrowable = IllegalArgumentException.class) public static void intercept( @Advice.Return(readOnly = false) CreateValidationResult originalResult,// - (readOnly = false) @Advice.Thrown Throwable throwable,// - @Advice.Argument(0) ApplicationUser user, @Advice.Argument(1) IssueInputParameters issueInputParameters ) { try { if (throwable == null) { //current plugin key final Plugin plugin = ComponentAccessor.getPluginAccessor().getEnabledPlugin("org.jrx.jira.instrumentation.issue-validation"); //related aggregator class final Class<?> issueValidatorClass = plugin != null ? plugin.getClassLoader().loadClass("org.jrx.jira.instrumentation.validation.spi.issueservice.IssueServiceValidateCreateValidatorAggregator") : null; final Object issueValidator = issueValidatorClass != null ? ComponentAccessor.getOSGiComponentInstanceOfType(issueValidatorClass) : null;// API JIRA if (issueValidator != null) { final Method validate = issueValidator.getClass().getMethod("validate", CreateValidationResult.class, ApplicationUser.class, IssueInputParameters.class); if (validate != null) { final CreateValidationResult validationResult = (CreateValidationResult) validate .invoke(issueValidator, originalResult, user, issueInputParameters); if (validationResult != null) { originalResult = validationResult; } } else { System.err.println("==**Warn: method validate is not found on aggregator " + "**=="); } } } //Nothing should break service } catch (Throwable e) { System.err.println("==**Warn: Exception on additional logic of validateCreate " + e + "**=="); } } }
@Component @ExportAsService public class DefaultIssueServiceTransformer implements InstrumentationConsumer { private static final Logger log = LoggerFactory.getLogger(DefaultIssueServiceTransformer.class); private static final AgentBuilder.Listener listener = new LogTransformListener(log); private final String DEFAULT_ISSUE_SERVICE_CLASS_NAME = "com.atlassian.jira.bc.issue.DefaultIssueService"; @Override public void applyInstrumentation(Instrumentation instrumentation) { new AgentBuilder.Default().disableClassFormatChanges() .with(new AgentBuilder.Listener.Filtering( new StringMatcher(DEFAULT_ISSUE_SERVICE_CLASS_NAME, EQUALS_FULLY), listener )) .with(AgentBuilder.TypeStrategy.Default.REDEFINE) .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) .type(named(DEFAULT_ISSUE_SERVICE_CLASS_NAME)) .transform((builder, typeDescription, classloader) -> builder //transformation is idempotent!!! You can call it many times with same effect //no way to add advice on advice if it applies to original class //https://github.com/raphw/byte-buddy/issues/206 .visit(Advice.to(DefaultIssueServiceValidateCreateAdvice.class).on(named("validateCreate").and(ElementMatchers.isPublic()))) .visit(Advice.to(DefaultIssueServiceValidateUpdateAdvice.class).on(named("validateUpdate").and(ElementMatchers.isPublic())))) .installOn(instrumentation); } }
public interface IssueServiceValidateCreateValidator { @Nonnull CreateValidationResult validate( final @Nonnull CreateValidationResult originalResult, final ApplicationUser user, final IssueInputParameters issueInputParameters ); }
@Component @ExportAsService(IssueServiceValidateCreateValidatorAggregator.class) public class IssueServiceValidateCreateValidatorAggregator implements IssueServiceValidateCreateValidator { private static final Logger log = LoggerFactory.getLogger(IssueServiceValidateCreateValidatorAggregator.class); private final BundleContext bundleContext; @Autowired public IssueServiceValidateCreateValidatorAggregator(BundleContext bundleContext) { this.bundleContext = bundleContext; } @Nonnull @Override public IssueService.CreateValidationResult validate(@Nonnull final IssueService.CreateValidationResult originalResult, final ApplicationUser user, final IssueInputParameters issueInputParameters) { try { log.trace("Executing validate of IssueServiceValidateCreateValidatorAggregator"); final Collection<ServiceReference<IssueServiceValidateCreateValidator>> serviceReferences = bundleContext.getServiceReferences(IssueServiceValidateCreateValidator.class, null); log.debug("Found services: {}", serviceReferences); return applyValidations(originalResult, serviceReferences, user, issueInputParameters); } catch (InvalidSyntaxException e) { log.warn("Exception on getting IssueServiceValidateCreateValidator", e); return originalResult; } } private IssueService.CreateValidationResult applyValidations(@Nonnull IssueService.CreateValidationResult originalResult, Collection<ServiceReference<IssueServiceValidateCreateValidator>> serviceReferences, ApplicationUser user, IssueInputParameters issueInputParameters) { IssueService.CreateValidationResult result = originalResult; for (ServiceReference<IssueServiceValidateCreateValidator> serviceReference : serviceReferences) { final IssueServiceValidateCreateValidator service = bundleContext.getService(serviceReference); if (service != null) { result = service.validate(result, user, issueInputParameters); } else { log.debug("Failed to get service from {}", serviceReference); } } return result; } }
@Component @ExportAsService public class TestIssueServiceCreateValidator implements IssueServiceValidateCreateValidator { @Nonnull @Override public IssueService.CreateValidationResult validate(@Nonnull IssueService.CreateValidationResult originalResult, ApplicationUser user, IssueInputParameters issueInputParameters) { originalResult.getErrorCollection().addError(IssueFieldConstants.ASSIGNEE, "This validation works", ErrorCollection.Reason.VALIDATION_FAILED); return originalResult; } }
Source: https://habr.com/ru/post/316124/
All Articles