åé¡ã解決ããããã«ã¢ããªã±ãŒã·ã§ã³ã§äœ¿çšå¯èœãªAPIãååã§ã¯ãªããã³ãŒããããã«å€æŽããå¯èœæ§ããªãå Žåãã©ãããã°ããã§ããïŒ

ãã®ç¶æ³ã§ã®æåŸã®åžæã¯ã
java.lang.instrumentããã±ãŒãžã®äœ¿çšã§ãã æ¢ã«å®è¡äžã®VMã§ã³ãŒãã䜿çšããŠJavaã§äœãã©ã®ããã«è¡ãããšãã§ãããã«èå³ããã人ã¯ãcatã«ããããã
Habrã«ã¯ããã§ã«ãã€ãã³ãŒãã®åŠçã«é¢ããèšäºããããŸãã
ãã ãããããã®ãã¯ãããžãŒã®äœ¿çšã¯ãååãšããŠããã®ã³ã°ãŸãã¯ãã®ä»ã®åçŽãªæ©èœã«éå®ãããŸãã ããããèšè£
ã䜿çšããŠã¢ããªã±ãŒã·ã§ã³ã®æ©èœãæ¡åŒµããããã«è©æ¬ºãããããšãããã©ãã§ããããïŒ
ãã®èšäºã§ã¯ãã¢ããªã±ãŒã·ã§ã³ã«æ°ããæ©èœãè¿œå ããããã«ã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);
ãã¡ã€ã«ãã³ããŒããã«ã¯ã次ã®æ¹æ³ã䜿çšããŸãã
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());
éåžžã®ãã¡ã€ã«æäœã«å ããŠããã®ã³ãŒãã¯ãã§ãã¯ãµã èšç®ãå®è¡ããŸãã ã³ãŒãã®èšè¿°æã«ãã©ã³ã¿ã€ã ã§æŽæ°äžå¯èœãªã³ã³ããŒãã³ããæ€èšŒãããã®æ¹æ³ãæåã«æãæµ®ãã³ãŸããã ååãšããŠãã¢ãŒãã£ãã¡ã¯ããããŒãžã§ã³ç®¡çããå ŽåãããŒãžã§ã³ãã§ãã¯ãå®è¡ããããšãã§ããŸãã ãã¡ã€ã«ããã§ã«ããŒããããŠãããããã§ãã¯ãµã ãã¢ãŒã«ã€ãããã®ã¢ãŒãã£ãã¡ã¯ããšäžèŽããªãå Žåããããã眮ãæããããšããŸãã
ã ããããã¡ã€ã«ããããŸããããŠã³ããŒãããæ¹æ³ãç解ããŸãããã æãé£ããããšããå§ããŸããã-ãã€ãã£ãã©ã€ãã©ãªãããŒãããŸãã 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);
æ€èšŒãšã³ãžã³ã®äœæã«ç§»ããŸãããã
æ€èšŒãšã³ãžã³
å€æŽãè¡ãããšãæãå¹æçãªãã€ã³ããèŠã€ããŸã-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ã䜿çšããŠã¯ã©ã¹ãããŒãããããšããŸãã
ãã®ããã«ïŒ
ã¢ãããŒãã®å®è£
ã¯æ¬¡ã®ããã«ãªããŸãã
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);
誰ãããã®ã¢ã€ãã¢ãéçºãããå Žåã«åããŠä¿åããŸããã
ã€ã³ã¹ãã«ã¡ã³ããŒã·ã§ã³ãå®è£
ããããã®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,
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
ããã§ãçŽ æµãªããŒãã¹ã«ã€ããŠã話ãããŸããã¢ããã€ã¹ã®é©çšã¯içã§ãïŒã¢ããªã³VMãåã€ã³ã¹ããŒã«ãããšèªåçã«å€æãè¡ããããããå€æã2åé©çšããªãããã«æ³šæããå¿
èŠã¯ãããŸãããè¿œå æ©èœ, -
, . , JRE (, JAXB ..), â , ..
, .. .
ãŸããäºã¯å°ããã§ã-ã¢ã°ãªã²ãŒã¿ãŒãæžããŸããæåã«ãæ€èšŒ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ã®ã¢ããªã³ã®éçºã®æ©èœã«ã€ããŠã¯èª¬æããŸããã§ãããããã«ã€ããŠã¯ããã¡ããã芧ãã ããã