
ãã®ç¶æ³ã§ã®æåŸã®åžæã¯ã java.lang.instrumentããã±ãŒãžã®äœ¿çšã§ãã æ¢ã«å®è¡äžã®VMã§ã³ãŒãã䜿çšããŠJavaã§äœãã©ã®ããã«è¡ãããšãã§ãããã«èå³ããã人ã¯ãcatã«ããããã
Habrã«ã¯ããã§ã«ãã€ãã³ãŒãã®åŠçã«é¢ããèšäºããããŸãã
- JVMãµãŒãã¹ã®JavaãšãŒãžã§ã³ã
- AOPã®çè«ãšå®è·µã Yandexã§ã®æ¹æ³
- ã¢ã¹ãã¯ãæåããã°ã©ãã³ã°ã åºæ¬
ãã ãããããã®ãã¯ãããžãŒã®äœ¿çšã¯ãååãšããŠããã®ã³ã°ãŸãã¯ãã®ä»ã®åçŽãªæ©èœã«éå®ãããŸãã ããããèšè£ ã䜿çšããŠã¢ããªã±ãŒã·ã§ã³ã®æ©èœãæ¡åŒµããããã«è©æ¬ºãããããšãããã©ãã§ããããïŒ
ãã®èšäºã§ã¯ãã¢ããªã±ãŒã·ã§ã³ã«æ°ããæ©èœãè¿œå ããããã«ãJavaãšãŒãžã§ã³ãã¢ããªã±ãŒã·ã§ã³ïŒ OSGiãšByte Buddyã©ã€ãã©ãªã®äž¡æ¹ïŒãèšæž¬ããæ¹æ³ã瀺ããŸãã ãã®èšäºã¯äž»ã«JIRAã§äœæ¥ãã人ã ã«ãšã£ãŠèå³æ·±ããã®ã§ããã䜿çšãããã¢ãããŒãã¯éåžžã«æ®éçã§ãããä»ã®ãã©ãããã©ãŒã ã«é©çšã§ããŸãã
ææŠãã
ãããã£ãŠãJIRA APIã2ã3æ¥èª¿æ»ããåŸãã¿ã¹ã¯ã®äœæ/å€æŽæã«ãã£ãŒã«ãå€ã®çžäºæ€èšŒãå®è£ ããã®ãæ®éã§ããããšãç解ããŠããŸãïŒ1ã€ã®ãã£ãŒã«ãã®æå¹ãªå€ãå¥ã®ãã£ãŒã«ãã®å€ã«äŸåããå ŽåïŒã ã¢ãããŒãã¯æ©èœããŠããŸãããé£ãããŠäŸ¿å©ã§ã¯ãªãã®ã§ã空ãæéã«èšç»BãåŸãããã«ç 究ãç¶ããããšã«ããŸããã
æè¿ã®ãžã§ãŒã«ãŒã¯ ã ãã€ãããã£ã©ã€ãã©ãªã«é¢ããã©ãã¡ãšã«ãŠã£ã³ã¿ãŒãã«ã¿ãŒã® è¬æŒã ç¹éããŸãããããã¯ããã䟿å©ãªé«ã¬ãã«ã·ã§ã«ã§åŒ·åãªäœã¬ãã«ãã€ãã³ãŒãç·šéAPIãã©ããããŸãã ãã®ã©ã€ãã©ãªã¯çŸåšéåžžã«äººæ°ããããç¹ã«æè¿ã§ã¯MockitoãšHibernateã§äœ¿çšãããŠããŸãã ãšããããRafaelã¯æ¢ã«ããŒããããã¯ã©ã¹ã®Byte Buddyã§å€æŽããå¯èœæ§ã«ã€ããŠè©±ããŸããã
ãããã¯æèã ïŒããšæããä»äºãå§ããŸãã
èšèš
ãŸããRafaelã®ã¬ããŒãããããã§ã«ããŒããããŠããã¯ã©ã¹ã®å€æŽã¯ãjavaãšãŒãžã§ã³ãã®èµ·åæã«äœ¿çšã§ããjava.lang.instrument.Instrumentationã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšããããšã§ã®ã¿å¯èœã§ããããšãæãåºããŠãã ããã VMã®èµ·åæã«ã³ãã³ãã©ã€ã³ã䜿çšããããJDKã«ä»å±ãããã©ãããã©ãŒã äŸåã®Attach APIã䜿çšããŠã€ã³ã¹ããŒã«ã§ããŸãã
ããã«ã¯éèŠãªè©³çŽ°ããããŸã-ãšãŒãžã§ã³ãã¯åé€ã§ããŸãã-ãã®ã¯ã©ã¹ã¯VMãçµäºãããŸã§ããŒãããããŸãŸã§ãã
attach APIã®ãµããŒãã«é¢ããŠJIRAã«é¢ããŠã¯ãããã§ã¯JDKäžã§å®è¡ãããããšãä¿èšŒã§ããŸãããããã«ãJIRAãå®è¡ãããOSãä¿èšŒããããšã¯ã§ããŸããã
次ã«ãJIRAæ©èœãæ¡åŒµããããã®ã¡ã€ã³ãŠãããã¯ã¢ããªã³ -ã¹ããã€ãã®ãã³ãã«ã§ããããšãæãåºããŸãã ã€ãŸãããã¹ãŠã®ããžãã¯ã¯ããããäœã§ãããã¢ããªã³ãšããŠèšèšããå¿ èŠããããŸãã ããã¯ãã·ã¹ãã ã«å€æŽãå ããå Žåãã¹ãçã§åæå¯èœã§ãªããã°ãªããªããšããèŠä»¶ãæå³ããŸãã
ãããã®å¶éãèãããšãã°ããŒãã«ã«2ã€ã®ã¿ã¹ã¯ããããŸãã
- ãšãŒãžã§ã³ãã®ã€ã³ã¹ããŒã«ïŒã¢ããªã³ã®ã€ã³ã¹ããŒã«äžã«å®è¡ããäºéã€ã³ã¹ããŒã«ã«å¯Ÿããä¿è·ãæäŸããLinuxããã³WindowsãJDKããã³JREã§ã®ãšãŒãžã§ã³ãã®ã€ã³ã¹ããŒã«ããµããŒãããå¿
èŠããããŸãã
ãªããªã ãšãŒãžã§ã³ããåé€ããããšã¯ã§ããŸããããã®æŽæ°ã«ã¯ã¢ããªã±ãŒã·ã§ã³ã®åèµ·åãå¿ èŠã§ã-ããã¯OSGiã®æŠå¿µã«é©åããŸããã ãããã£ãŠããšãŒãžã§ã³ãã®è²¬ä»»ãæå°éã«æããŠããã®æŽæ°ã®å¿ èŠæ§ãã§ããéãçºçããªãããã«ããå¿ èŠããããŸãã
- ã€ã³ã¹ãã«ã¡ã³ããŒã·ã§ã³ã®å®è£ ïŒã¢ããªã³ã®ã€ã³ã¹ããŒã«äžã«çºçããå¿ èŠããããã¯ã©ã¹ã®å€æã®ã¹ãçæ§ã確èªããæ€èšŒããžãã¯ã®æ¡åŒµæ§ã確èªããå¿ èŠããããŸãã
ã³ã³ããŒãã³ãéã§è²¬ä»»ãåæ£ããããšã次ã®ã¹ããŒã ãåŸãããŸããã

å®è£
ãšãŒãžã§ã³ã
ãŸãããšãŒãžã§ã³ããäœæããŸãã
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**=="); } }
ã³ãŒãã¯ç°¡åã§ã説æã¯å¿ èŠãªããšæããŸãã åæãããããã«-æãäžè¬çãªããžãã¯ã¯ãé »ç¹ã«æŽæ°ããå¿ èŠãããããšã¯ã»ãšãã©ãããŸããã
ãããã€ããŒ
次ã«ããã®ãšãŒãžã§ã³ããã¿ãŒã²ããVMã«æ¥ç¶ããã¢ããªã³ãäœæããŸãã ãšãŒãžã§ã³ãã®ã€ã³ã¹ããŒã«ããžãã¯ããå§ããŸãããã ã¹ãã€ã©ãŒã®äžã®å®å šãªã€ã³ã¹ããŒã©ãŒã³ãŒãïŒ
AgentInstaller.java
@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; } }
䜿çšå¯èœãªå Žåããã以äžã€ã³ã¹ããŒã«ããå¿ èŠã¯ãããŸããã ããããæåã®ã€ã³ã¹ããŒã«ãããããšãŒãžã§ã³ãããŸã èªã¿èŸŒãŸããŠããªããšããŸããã-attach APIã䜿çšããŠèªåã§ãããè¡ããŸãã åã®ã±ãŒã¹ãšåæ§ã«ããŸãJDKã§äœæ¥ããŠãããã©ããã確èªããŸãã ç®çã®APIã¯ãè¿œå ã®æäœãè¡ããã©ããã«é¢ä¿ãªãå©çšã§ããŸãã ããã§ãªãå Žåã¯ãAPIããé ä¿¡ãããŠã¿ãŠãã ããã
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); } } }
次ã«ãæ¥ç¶APIã®ã€ã³ã¹ããŒã«æé ãæ€èšããŸãã JREãJDKã«ãå€æãããã¿ã¹ã¯ã¯ãã³ã³ããOSã®å®çŸ©ããå§ãŸããŸãã JIRAã§ã¯ãOSå®çŸ©ã³ãŒãã¯æ¢ã«å®è£ ãããŠããŸãã
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"); } }
ããã§ãã©ã®OSã§å®è¡ãããŠããããããã£ãããã¢ã¿ããAPIãã¢ã¿ããããæ¹æ³ãæ€èšããŠãã ããã æåã«ã ã¢ã¿ããAPIãå®éã«æ§æãããã®ãèŠãŠãã ããã ç§ãèšã£ãããã«ãããã¯ãã©ãããã©ãŒã ã«äŸåããŠããŸãã
泚ïŒtools.jarã¯ãã©ãããã©ãŒã ã«äŸåããªããã®ãšããŠãªã¹ããããŠããŸãããããã¯å®å šã«çå®ã§ã¯ãããŸããã META-INF / services /ã§ã¯ãæ§æãã¡ã€ã«com.sun.tools.attach.spi.AttachProviderãé衚瀺ã«ãªããç°å¢ã§å©çšå¯èœãªãããã€ããŒããªã¹ããããŸãã
ïŒ[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
ãããã¯ããã©ãããã©ãŒã ã«éåžžã«äŸåããŠããŸãã
åœé¢ãå¿ èŠãªãã¡ã€ã«ãã¢ã»ã³ããªã«æ¥ç¶ããããã«ã察å¿ããJDKãã£ã¹ããªãã¥ãŒã·ã§ã³ããã©ã€ãã©ãªãã¡ã€ã«ãštools.jarã®ã³ããŒãæœåºããŠããªããžããªã«é 眮ããããšã«ããŸããã
éèŠãªã®ã¯ãã¢ã¿ããAPIãã¡ã€ã«ãèªã¿èŸŒãã åŸã¯åé€ãŸãã¯å€æŽã§ããªããããã¢ããªã³ãåé€ããã³æŽæ°ã§ããããã«ããå Žåã¯ãjarããã©ã€ãã©ãªãçŽæ¥èªã¿èŸŒãå¿ èŠã¯ãªãããšã§ããããããjarãã¡ã€ã«ããJIRAããã¢ã¯ã»ã¹å¯èœãªéãã§èœã¡çããå Žæã«ã³ããŒããŸãã
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; } }
éåžžã®ãã¡ã€ã«æäœã«å ããŠããã®ã³ãŒãã¯ãã§ãã¯ãµã èšç®ãå®è¡ããŸãã ã³ãŒãã®èšè¿°æã«ãã©ã³ã¿ã€ã ã§æŽæ°äžå¯èœãªã³ã³ããŒãã³ããæ€èšŒãããã®æ¹æ³ãæåã«æãæµ®ãã³ãŸããã ååãšããŠãã¢ãŒãã£ãã¡ã¯ããããŒãžã§ã³ç®¡çããå ŽåãããŒãžã§ã³ãã§ãã¯ãå®è¡ããããšãã§ããŸãã ãã¡ã€ã«ããã§ã«ããŒããããŠãããããã§ãã¯ãµã ãã¢ãŒã«ã€ãããã®ã¢ãŒãã£ãã¡ã¯ããšäžèŽããªãå Žåããããã眮ãæããããšããŸãã
ã ããããã¡ã€ã«ããããŸããããŠã³ããŒãããæ¹æ³ãç解ããŸãããã æãé£ããããšããå§ããŸããã-ãã€ãã£ãã©ã€ãã©ãªãããŒãããŸãã attach APIã®è žã調ã¹ããšãã¿ã¹ã¯ãå®è¡ãããšã次ã®ã³ãŒãã䜿çšããŠã©ã€ãã©ãªãã¢ã³ããŒããããããšãçŽæ¥ããããŸãã
static { System.loadLibrary("attach"); }
ããã¯ãã©ã€ãã©ãªã®å Žæããjava.library.pathãã«è¿œå ããå¿ èŠãããããšã瀺åããŠããŸã
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); } }
ãã®åŸãç®çã®ãã€ãã£ãã©ã€ãã©ãªãã¡ã€ã«ãæ£ãããã£ã¬ã¯ããªã«è¿œå ããæåã®æŸèæããœãªã¥ãŒã·ã§ã³ã«è¿œå ããŸãã ãJava.library.pathãã¯ãClassLoaderã¯ã©ã¹ã®ãã©ã€ããŒãéçæååsys_paths []ã«ãã£ãã·ã¥ãããŸãã ããŠããã©ã€ããŒãã«ã¯äœãå¿ èŠã§ãã-ãã£ãã·ã¥ããªã»ããããŸããã...
private void resetCache() throws NoSuchFieldException, IllegalAccessException { Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); fieldSysPath.setAccessible(true); fieldSysPath.set(null, null); }
ããã§ã¯ããã€ãã£ãéšåãããŠã³ããŒãããŸãã-Javaã®APIã®éšåã«æž¡ããŸãã JDKã®tools.jarã¯ãã·ã¹ãã ããŒããŒã«ãã£ãŠããŒããããŸãã åãããšãããå¿ èŠããããŸãã
å°ãè¯ããªã£ãã®ã§ãã·ã¹ãã ããŒããŒãjava.net.URLClassLoaderãå®è£ ããŠããããšãããããŸãã
èŠããã«ããã®ããŒããŒã¯ã¯ã©ã¹ã®å ŽæãURLã®ãªã¹ããšããŠä¿åããŸãã ããŠã³ããŒãããå¿ èŠãããã®ã¯ããã®ãªã¹ãã«tools- [OS] .jarã®URLãè¿œå ããããšã ãã§ãã URLClassLoader APIãç 究ããåŸãåã³æ²ãããªããŸããã å¿ èŠãªããšãæ£ç¢ºã«è¡ãaddURLã¡ãœãããä¿è·ãããŠããããšãããããŸãã ãããš...现é·ããããã¿ã€ããžã®ããäžã€ã®ããã¯ã¢ããïŒ
final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); method.setAccessible(true); method.invoke(systemClassLoader, tools.toURI().toURL());
ããŠãæåŸã«ããã¹ãŠãä»®æ³ãã·ã³ã¯ã©ã¹ãããŒãããæºåãã§ããŸããã
çŸåšã®OSGiã¯ã©ã¹ããŒããŒã§ã¯ãªããã·ã¹ãã ã«åžžã«ããŠã³ããŒãããå¿ èŠããããŸãã æ¥ç¶äžã«ããã®ã¯ã©ã¹ããŒããŒã¯ãã€ãã£ãã©ã€ãã©ãªãããŒãããŸããããã¯äžåºŠããå®è¡ã§ããŸããã OSGiã¯ã©ã¹ããŒããŒã¯ããã³ãã«ãã€ã³ã¹ããŒã«ãããšãã«äœæãããŸãïŒæ°ãããã³ãã«ãäœæãããã³ã«ïŒã ãã®ããããã®çš®ã®ãã®ãååŸãããªã¹ã¯ããããŸãã
âŠ19ãã®ä»
åå ïŒcom.sun.tools.attach.AttachNotSupportedExceptionïŒãããã€ããŒãã€ã³ã¹ããŒã«ãããŠããŸãã
com.sun.tools.attach.VirtualMachine.attachïŒVirtualMachine.java:203ïŒã§
説æã¯æããã§ã¯ãããŸããããæ¬åœã®çç±ã¯ãæ¢ã«ããŒããããŠããã©ã€ãã©ãªãããŒãããããšããŠããããšã§ããããã«ã€ããŠã¯ãattachã¡ãœããã販売ããŠå®éã®äŸå€ã確èªããããšã«ãã£ãŠã®ã¿ç¥ãããšãã§ããŸãã
ã¯ã©ã¹ãããŒãããããå¿ èŠãªã¡ãœãããããŒãããŠãæçµçã«ãšãŒãžã§ã³ããã¢ã¿ããã§ããŸãã
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); }
ããã§ã®å¯äžã®åŸ®åŠãªç¹ã¯ãä»®æ³ãã·ã³ã®PIDã§ãã
private String getPid() { String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName(); return nameOfRunningVM.split("@", 2)[0]; }
ãã®ã¡ãœããã¯éæšæºã§ãããéåžžã«æ©èœããŸããJava9 Process APIå šè¬ã§ã¯ãåé¡ãªããããè¡ãããšãã§ããŸãã
ã¢ããªã³
次ã«ããã®ããžãã¯ãã¢ããªã³ã«åã蟌ã¿ãŸãã ã¢ããªã³ã®ã€ã³ã¹ããŒã«äžã«ã³ãŒããåŒã³åºãæ©èœã«èå³ããããŸã-ããã¯ãæšæºã®ã¹ããªã³ã°InitializingBeanã䜿çšããŠè¡ãããŸãã
@Override public void afterPropertiesSet() throws Exception { this.agentInstaller.install(); this.serviceTracker.open(); }
ãŸãããšãŒãžã§ã³ãã€ã³ã¹ããŒã«ããžãã¯ïŒäžèšã§èª¬æïŒãåŒã³åºãã次ã«ServiceTrackerãéããŸããããã¯ãOSGiã§ãã¯ã€ãããŒããã¿ãŒã³ãå®è£ ããããã®äž»èŠãªã¡ã«ããºã ã®1ã€ã§ãã ã€ãŸãããã®ããšã«ãããã³ã³ããå ã®ç¹å®ã®ã¿ã€ãã®ãµãŒãã¹ãè¿œå /å€æŽãããšãã«ããžãã¯ãå®è¡ã§ããŸãã
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) { } }); }
ããã§ãInstrumentationConsumerã¯ã©ã¹ãå®è£ ãããµãŒãã¹ãã³ã³ããã«ç»é²ããããã³ã«ã次ã®ããšãè¡ããŸãã
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); } }
java.lang.instrument.Instrumentationãªããžã§ã¯ãã¯æ¬¡ã®ããã«åä¿¡ãããŸãã
@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); } } }
æ€èšŒãšã³ãžã³ã®äœæã«ç§»ããŸãããã
æ€èšŒãšã³ãžã³
å€æŽãè¡ãããšãæãå¹æçãªãã€ã³ããèŠã€ããŸã-DefaultIssueServiceã¯ã©ã¹ïŒå®éããã¹ãŠã®äœæ/å€æŽåŒã³åºãããã®ãã€ã³ããééããããã§ã¯ãããŸããããããã¯å¥ã®ãããã¯ã§ãïŒãšãã®ã¡ãœããïŒ
validateCreateïŒ
IssueService.CreateValidationResult validateCreate(@Nullable ApplicationUser var1, IssueInputParameters var2);
ããã³validateUpdateïŒ
IssueService.UpdateValidationResult validateUpdate(@Nullable ApplicationUser var1, Long var2, IssueInputParameters var3);
ãããŠãã©ã®ãããªããžãã¯ãæ¬ èœããŠããã®ã ãããšæããŸãã
ã¡ã€ã³ããžãã¯ãåŒã³åºããåŸãå¿ èŠã«å¿ããŠçµæãå€æŽã§ãããã³ãŒãã«ããåæãã©ã¡ãŒã¿ãŒã®ã«ã¹ã¿ã æ€èšŒãåŒã³åºãããå¿ èŠããããŸãã
ByteBuddyã¯ãã¢ã€ãã¢ãå®è£ ããããã®2ã€ã®ãªãã·ã§ã³ãæäŸããŸããå²ã蟌ã¿ã®æ¯æŽãšã¢ããã€ã¹ã¡ã«ããºã ã®æ¯æŽã§ãã ã¢ãããŒãã®éãã¯ãRaphaelã®ãã¬ãŒã³ããŒã·ã§ã³ã®ã¹ã©ã€ãã«ã¯ã£ãããšçŸããŠããŸãã
Interceptor APIã¯ååã«ææžåãããŠãããã©ã®ãããªãã¯ã¯ã©ã¹ããã®1ã€ãšããŠæ©èœã§ããŸã ã詳现ã¯ãã¡ããã芧ãã ãã ã InterceptoråŒã³åºãã¯ãå ã®ã¡ãœããã®å ã®ãã€ãã³ãŒãINSTEADã«åã蟌ãŸããŠããŸãã
ãã®æ¹æ³ã䜿çšããããšãããšã2ã€ã®é倧ãªæ¬ ç¹ãèŠã€ãããŸããã
- äžè¬ã«ãå
ã®ã¡ãœãããããã«ã¯ã¡ãœããåŒã³åºãã®ãªããžã§ã¯ããååŸããæ©äŒããããŸã ã ãã ãã ããŒãæžã¿ã¯ã©ã¹ã®ã·ã°ããã£ã®å€æŽã«é¢ããå¶éã®ããããã§ã«ããŒãæžã¿ã®ã¯ã©ã¹ãã€ã³ã¹ãã«ã¡ã³ããããšãå
ã®ã¡ãœããã倱ãããŸãïŒåãã¯ã©ã¹ã®ãã©ã€ããŒãã¡ãœãããšããŠä¿åã§ããªãããïŒã ãããã£ãŠãå
ã®ããžãã¯ãåå©çšããå Žåã¯ãèªåã§åäœæããå¿
èŠããããŸãã
- ãªããªã å®éã«å¥ã®ã¯ã©ã¹ã®ã¡ãœãããåŒã³åºãå Žåãã¯ã©ã¹ãã§ãŒã³å ã®ã¯ã©ã¹éã®å¯èŠæ§ã確ä¿ããå¿ èŠããããŸãã ã¯ã©ã¹ãOSGiã³ã³ãããŒå ã§ã€ã³ã¹ãã«ã¡ã³ããããŠããå Žåãå¯èŠæ§ã«åé¡ã¯ãããŸããã ãã ãããã®å ŽåãJIRA APIã®ã»ãšãã©ã®ã¯ã©ã¹ã¯ãOSGiã®å€éšã«ããWebappClassLoaderã«ãã£ãŠããŒããããŸããã€ãŸããInterceptorã®ã¡ãœãããåŒã³åºãããšãããšãåœç¶ã®ClassNotFoundExceptionãçºçããŸãã
ãããžã§ã¯ãã®äœæ¥ã®éçšã§ããã®åé¡ã解決ãããªãã·ã§ã³ãçãŸããŸãããã ã¢ããªã±ãŒã·ã§ã³å šäœã®ã¯ã©ã¹ãããŒãããããžãã¯ã«å¹²æžããããã培åºçãªãã¹ããããã«äœ¿çšããŠã¹ãã€ã©ãŒã®äžã«çœ®ãããšã¯ãå§ãããŸããã
ããŒãããŒããŒã®åé¡ã解決ãã
äž»ãªã¢ã€ãã¢ã¯ãWebappClassLoaderã®èŠªãã§ãŒã³ãåæããããã«ClassLoaderãããã·ãæ¿å
¥ããããšã§ããããã¯ãWebappClassLoaderã®å®éã®èŠªã«ããŠã³ããŒããå§ä»»ããåã«ãBundleClassLoaderã䜿çšããŠã¯ã©ã¹ãããŒãããããšããŸãã
ãã®ããã«ïŒ
ã¢ãããŒãã®å®è£ ã¯æ¬¡ã®ããã«ãªããŸãã
èšæž¬ã¢ããªã±ãŒã·ã§ã³ãããã¯ã§äœ¿çšããå¿ èŠããããŸãã
ãã®å ŽåãWebappClassLoaderãä»ããŠOSGiã¯ã©ã¹ãããŒãã§ããŸãã 泚æãã¹ãå¯äžã®ããšã¯ãOSGiã䜿çšããŠã¯ã©ã¹ãããŒãããããšããªãããšã§ãããã®ããŒãã¯ãOSGiã®å€éšã«å§ä»»ãããŸãã ããã¯æããã«ã«ãŒããšäŸå€ã«ã€ãªãããŸãã
BundleProxyClassLoaderã³ãŒãïŒ
誰ãããã®ã¢ã€ãã¢ãéçºãããå Žåã«åããŠä¿åããŸããã
ãã®ããã«ïŒ

ã¢ãããŒãã®å®è£ ã¯æ¬¡ã®ããã«ãªããŸãã
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);
ãã®å ŽåãWebappClassLoaderãä»ããŠOSGiã¯ã©ã¹ãããŒãã§ããŸãã 泚æãã¹ãå¯äžã®ããšã¯ãOSGiã䜿çšããŠã¯ã©ã¹ãããŒãããããšããªãããšã§ãããã®ããŒãã¯ãOSGiã®å€éšã«å§ä»»ãããŸãã ããã¯æããã«ã«ãŒããšäŸå€ã«ã€ãªãããŸãã
BundleProxyClassLoaderã³ãŒãïŒ
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 ); } }
誰ãããã®ã¢ã€ãã¢ãéçºãããå Žåã«åããŠä¿åããŸããã
ã€ã³ã¹ãã«ã¡ã³ããŒã·ã§ã³ãå®è£ ããããã®2çªç®ã®ãªãã·ã§ã³ã¯ãã¢ããã€ã¹ã䜿çšããããšã§ãã ãã®æ¹æ³ã¯ææžåããããã®ããããã£ãšæªãã§ããå®éãäŸã¯Githubã®ãã±ãããšStackOverflowãžã®å¿çã§ããèŠã€ãããŸããã
ãããããã¹ãŠãããã»ã©æªãããã§ã¯ãããŸãã
ããã§ç§ãã¡ã¯ã©ãã¡ãšã«ã«æ¬æãè¡šããå¿
èŠããããŸã-ç§ãèŠããã¹ãŠã®è³ªåãšãã±ããã«ã¯è©³çŽ°ãªèª¬æãšäŸãæäŸãããŠããã®ã§ããããç解ããããšã¯é£ãããããŸãã-ãããã®äœåãå®ãçµã³ãããã«å€ãã®ãããžã§ã¯ãã§ãã€ãããã£ãèŠãããšãé¡ã£ãŠããŸãã
ããã©ã«ãã§ã¯ãã¢ããã€ã¹ã¡ãœãããã¯ã©ã¹ã³ãŒãã«åã蟌ãŸããŠãããšããç¹ã§ãæåã®ãã®ãšç°ãªããŸãã ç§ãã¡ã«ãšã£ãŠãããã¯æ¬¡ã®ããšãæå³ããŸãã
- ClassLoadersã䜿çšããªããã³ã¹
- å ã®ããžãã¯ã®ä¿å-å ã®ã³ãŒãã®åãŸãã¯åŸã«ç¹å®ã®ã¢ã¯ã·ã§ã³ã®ã¿ãå®è¡ã§ããŸã
å®ç§ã«èãããŸããå ã®åŒæ°ãå ã®ã³ãŒãã®çµæïŒäŸå€ãå«ãïŒãããã«å ã®ã³ãŒãã®åã«æ©èœããã¢ããã€ã¹ã®çµæãååŸã§ããã·ãã¯ãªAPIãæäŸãããŠããŸãããã ããåžžã«ãbutãããããåã蟌ã¿ã«ã¯ã³ãŒãã«ããã€ãã®å¶éããããåã蟌ã¿ãå¯èœã§ãã
- ãã¹ãŠã®åã蟌ã¿ã³ãŒãã¯1ã€ã®ã¡ãœããã§å®è¡ããå¿ èŠããããŸã
- ã¡ãœããã«ã¯ãåã蟌ã¿å ã®ã¯ã©ã¹ã«ã¢ã¯ã»ã¹ã§ããªãã¯ã©ã¹ã¡ãœãããžã®åŒã³åºããå«ããããšã¯ã§ããŸããããããŠå¿åïŒãããªãã©ã ãïŒïŒ
- äŸå€ãããã¢ããã¯ãµããŒããããŠããŸãã-äŸå€ã¯ã¡ãœããæ¬äœã§æ瀺çã«ã¹ããŒããå¿ èŠããããŸã
Byte Buddyã®ããã¥ã¡ã³ãã«ã¯ãããã®å¶éã®èª¬æã¯ãããŸããã§ããã
ãã¢ããã€ã¹ã®ã¹ã¿ã€ã«ã§ããžãã¯ãèšè¿°ããŠã¿ãŸããããèŠããŠããããã«ãå¿ èŠãªèšæž¬ãæå°éã«æããå¿ èŠããããŸããããã¯ãç¹å®ã®æ€èšŒãã§ãã¯ãç¡èŠããããšãæå³ããŸã-æ°ãããã§ãã¯ã衚瀺ããããšãã«ãvalidateCreate / validateUpdateãåŒã³åºããããšãã«å®è¡ããããã§ãã¯ã®ãªã¹ãã«èªåçã«è¿œå ãããDefaultIssueServiceã¯ã©ã¹ã®ã³ãŒããå€æŽããå¿ èŠããªãããšã確èªããŸã
OSGiã§ã¯ãããã¯ç°¡åã§ãããDefaultIssueServiceã¯ãã¬ãŒã ã¯ãŒã¯ã®ç¯å²å€ã§ãããããã§OSGiãã¯ããã¯ã䜿çšããããšã¯ã§ããŸããã
çªç¶ãJIRA APIã圹ç«ã¡ãŸããåã¢ããªã³ã¯ããã®ãã©ã°ã€ã³ãæ€çŽ¢ã§ããç¹å®ã®ããŒãæã€Pluginã¯ã©ã¹ïŒå€ãã®ç¹å¥ãªæ©èœãåãããã³ãã«ã®ã©ãããŒïŒã®ãªããžã§ã¯ããšããŠJIRAã§è¡šãããŸãã
ããŒã¯ã¢ããªã³æ§æã§èšå®ããããã©ã°ã€ã³APIã¯DefaultIssueServiceãšåãã¯ã©ã¹ããŒããŒã§ããŒããããŸã-ãããã£ãŠãã¢ããã€ã¹ã§ãã©ã°ã€ã³ãåŒã³åºãããã®ãã©ã°ã€ã³ã«ä»å±ããã¯ã©ã¹ãããŒãããããšã劚ãããã®ã¯äœããããŸããããšãã°ããã§ãã¯ã¢ã°ãªã²ãŒã¿ãŒãªã©ã§ãã
ãã®åŸãåã³æšæºã®com.atlassian.jira.component.ComponentAccessorïŒgetOSGiComponentInstanceOfTypeãä»ããŠãã®ã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ãååŸã§ããŸãã
ãããŠéæ³ã¯ãããŸããïŒ
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 + "**=="); } } }
DefaultIssueServiceValidateUpdateAdviceã¯ãã¯ã©ã¹åãšã¡ãœããåã«äŒŒãŠããŸããç®çã®ã¡ãœããã«ã¢ããã€ã¹ãé©çšããInstrumentationConsumerãäœæããŸãã
@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); } }
ããã§ãçŽ æµãªããŒãã¹ã«ã€ããŠã話ãããŸããã¢ããã€ã¹ã®é©çšã¯içã§ãïŒã¢ããªã³VMãåã€ã³ã¹ããŒã«ãããšèªåçã«å€æãè¡ããããããå€æã2åé©çšããªãããã«æ³šæããå¿ èŠã¯ãããŸããã
ãŸããäºã¯å°ããã§ã-ã¢ã°ãªã²ãŒã¿ãŒãæžããŸããæåã«ãæ€èšŒAPIãå®çŸ©ããŸãã
public interface IssueServiceValidateCreateValidator { @Nonnull CreateValidationResult validate( final @Nonnull CreateValidationResult originalResult, final ApplicationUser user, final IssueInputParameters issueInputParameters ); }
次ã«ãåŒã³åºãæã«æšæºã®OSGiããŒã«ã䜿çšããŠã䜿çšå¯èœãªãã¹ãŠã®æ€èšŒãååŸããŠå®è¡ããŸãã
@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; } }
æ°ããã¿ã¹ã¯ãäœæããŠã¿ãŠãã ããïŒ
ããã§ãéçºãããã¢ããªã³ããã¢ããªã³ãåé€ããŠåã€ã³ã¹ããŒã«ã§ããŸã-JIRAã®åäœãæ£ããå€æŽãããŸãã
ãããã«
ãããã£ãŠãã¢ããªã±ãŒã·ã§ã³APIïŒãã®å Žåã¯JIRAïŒãåçã«æ¡åŒµããããŒã«ãå ¥æããŸããããã¡ããããã®ã¢ãããŒããå®çšŒåã§äœ¿çšããåã«ã培åºçãªãã¹ããå¿ èŠã§ãããç§ã®æèŠã§ã¯ããœãªã¥ãŒã·ã§ã³ã¯å®å šã«ã¯åŒ·åãããŠããããé©åãªç 究ã§ããã®ã¢ãããŒãã¯ã絶æçãªåé¡ãã解決ããããã«äœ¿çšã§ããŸã-é·åœã®ãµãŒãããŒãã£ã®æ¬ é¥ãä¿®æ£ããAPIãæ¡åŒµãããªã©ã
ãããžã§ã¯ãèªäœã®å®å šãªã³ãŒãã¯Githubã§èŠãããšãã§ããŸã-å¥åº·ã®ããã«ããã䜿çšããŠãã ããïŒ
PS èšäºãè€éã«ããªãããã«ããããžã§ã¯ãã¢ã»ã³ããªã®è©³çŽ°ãšJIRAã®ã¢ããªã³ã®éçºã®æ©èœã«ã€ããŠã¯èª¬æããŸããã§ãããããã«ã€ããŠã¯ããã¡ããã芧ãã ããã