NetBeans Plugin Development: JRebel Experience Report
-
date post
21-Oct-2014 -
Category
Technology
-
view
1.174 -
download
1
description
Transcript of NetBeans Plugin Development: JRebel Experience Report
NetBeans Plugin Development:JRebel Experience Report
JavaOne SF 2012
Agenda• JRebel intro– Plugin requirements
• NetBeans plugin development• Integrating with NetBeans server adaptors• Integrating with NetBeans debugger• Packaging & publishing
ENTER JREBEL
The Turnaround Cycle
Make changes
Build, deploy,
wait
Observe results
AVG 2.5 min
MyObject
MyObject.class
ClassLoader
Code101000101100010010 New code
111000100101010010
Make changesin IDE
JRebel
Fram
ewor
k
Configuration(XML, annotations,..)
in action
Why IDE plugin?
UsabilityAutomationDebugger
JRebel Plugin for NetBeans
VM args
BreakpointsStepping
Settings, UX
Process Launcher
Debugger
Workbench
Net
Bean
sJRebel Plugin
ENTER NETBEANS PLUGINS
Books
UI Elements
• Buttons/Actions• Toolbars/Menus• Settings/Options• Popups• etc
layers.xml <folder name="Actions"> <folder name="JRebel"> <file name="JRebelToggleAction.instance"> <attr name="delegate" newvalue="o.z.j.n.JRebelToggleAction"/> </file> </folder> </folder>
<folder name="Toolbars"> <folder name="Build"> <file name="JRebelToggleAction.shadow"> <attr name="originalFile" stringvalue="Actions/JRebel/JRebelToggleAction.instance"/> <attr name="position" intvalue="250"/>
Options
<folder name="OptionsDialog"> <file name="JRebelOptions.instance"> <attr name="instanceCreate" methodvalue="o.n.s.o.OptionsCategory.createCategory"/> <attr name="controller" newvalue="o.z.j.n.JRebelOptionsPanelController"/> </file></folder>
<folder name="Services"> <file name="org-zeroturnaround-jrebel-netbeans-options.settings" url="options.xml"/></folder>
Options (Old API)
Options (New API) @OptionsPanelController.SubRegistration( location = "Advanced", displayName = "#AdvancedOption_DisplayName_Super", keywords = "#AdvancedOption_Keywords_Super", keywordsCategory = "Advanced/Super")
@org.openide.util.NbBundle.Messages( {"AdvancedOption_DisplayName_Super=Super", "AdvancedOption_Keywords_Super=super"})
public final class SuperOptionsPanelController extends OptionsPanelController {
CUSTOM JVM ARGUMENTS
JVM arguments• JRebel is bootstrapped using JVM arguments:
• Various NetBeans project types pass JVM arguments slightly differently:
-javaagent:/path/to/jrebel.jar
-noverify -Xbootclasspath/p:jrebel-bootstrap.jar;jrebel.jar
-Drebel.log=true -Drebel.jersey_plugin=true
Web project VS Maven project
JBoss VS Tomcat VS Glassfish
Server settings: Tomcat
Can put your arguments here
Server settings: Glassfish
No VM arguments?
Project Deployment
Aha!!
Toggle
Simplest, from the user’s point of view: should not
care about project type of runtime differences
Togglepublic final class JRebelToggleAction extends BooleanStateAction { public void initialize() { super.initialize(); setBooleanState(JRebelSettings.isEnabled()); }
public void actionPerformed(ActionEvent ev) { super.actionPerformed(ev); JRebelSettings.setEnabled(getBooleanState()); // NbPreferences.forModule(JRebelSettings.class) // .putBoolean(“enabled”, true);
}}
Challenge
No extension points provided by the NetBeans platform
Load-Time Weaving of NetBeans platform classes
Might not be the brightest bulb idea, but seems to work fine
PATCHING THE PLATFORM
org.openide.modules.ModuleInstall
public class Installer extends ModuleInstall { @Override public void restored() { // patch platform classes here
}}
PatcherString classToPatch = …ClassLoader cl = Lookup.getDefault() .lookup(ClassLoader.class);Object o = ClassLoader#findLoadedClass(classToPatch )
ClassPool cp = new ClassPool();cp.appendClassPath(new LoaderClassPath(cl));CtClass ctc = cp.get(classToPatch);patch(cp, ctc);ctc.toClass(cl, null);
PatcherString classToPatch = …ClassLoader cl = Lookup.getDefault() .lookup(ClassLoader.class);Object o = ClassLoader#findLoadedClass(classToPatch )
ClassPool cp = new ClassPool();cp.appendClassPath(new LoaderClassPath(cl));CtClass ctc = cp.get(classToPatch);patch(cp, ctc);ctc.toClass(cl, null);
e.g.
“org.netbeans.modules.glassfish.common.StartTask”
“org.netbeans.modules.tomcat5.ide.StartTomcat$StartRunnable”
etc
PatcherString classToPatch = …ClassLoader cl = Lookup.getDefault() .lookup(ClassLoader.class);Object o = ClassLoader#findLoadedClass(classToPatch )
ClassPool cp = new ClassPool();cp.appendClassPath(new LoaderClassPath(cl));CtClass ctc = cp.get(classToPatch);patch(cp, ctc);ctc.toClass(cl, null);
a class loader capable for finding
resources from any module
PatcherString classToPatch = …ClassLoader cl = Lookup.getDefault() .lookup(ClassLoader.class);Object o = ClassLoader#findLoadedClass(classToPatch )
ClassPool cp = new ClassPool();cp.appendClassPath(new LoaderClassPath(cl));CtClass ctc = cp.get(classToPatch);patch(cp, ctc);ctc.toClass(cl, null);
Method m = ClassLoader.class
.getDeclaredMethod("findLoadedClass", String.class);
m.setAccessible(true);
Object o = m.invoke(cl, patchedClassName);
PatcherString classToPatch = …ClassLoader cl = Lookup.getDefault() .lookup(ClassLoader.class);Object o = ClassLoader#findLoadedClass(classToPatch )
ClassPool cp = new ClassPool();cp.appendClassPath(new LoaderClassPath(cl));CtClass ctc = cp.get(classToPatch);patch(cp, ctc);ctc.toClass(cl, null);
Enter Javassist
PatcherString classToPatch = …ClassLoader cl = Lookup.getDefault() .lookup(ClassLoader.class);Object o = ClassLoader#findLoadedClass(classToPatch )
ClassPool cp = new ClassPool();cp.appendClassPath(new LoaderClassPath(cl));CtClass ctc = cp.get(classToPatch);patch(cp, ctc);ctc.toClass(cl, null);
This is where patching
happens really
patch(…)final CtMethod method = ctc.getDeclaredMethod("run");
method.instrument(new ExprEditor() { public void edit(MethodCall m) { if ("getJavaOpts".equals(m.getMethodName())) { m.replace( "if (command == CommandType.START)" + OPTS + "$_ = opts + $proceed($$);"); } }});
patch(…)final CtMethod method = ctc.getDeclaredMethod("run");
method.instrument(new ExprEditor() { public void edit(MethodCall m) { if ("getJavaOpts".equals(m.getMethodName())) { m.replace( "if (command == CommandType.START)" + OPTS + "$_ = opts + $proceed($$);"); } }});
Patching
StartTomcat$StartRunnable#run()
for Tomcat launcher
patch(…)final CtMethod method = ctc.getDeclaredMethod("run");
method.instrument(new ExprEditor() { public void edit(MethodCall m) { if ("getJavaOpts".equals(m.getMethodName())) { m.replace( "if (command == CommandType.START)" + OPTS + "$_ = opts + $proceed($$);"); } }});
Should append custom options
to getJavaOpts result
patch(…)final CtMethod method = ctc.getDeclaredMethod("run");
method.instrument(new ExprEditor() { public void edit(MethodCall m) { if ("getJavaOpts".equals(m.getMethodName())) { m.replace( "if (command == CommandType.START)" + OPTS + "$_ = opts + $proceed($$);"); } }});
String OPTS = "ClassLoader cl = (ClassLoader) Lookup.getDefault().lookup(ClassLoader.class);" +
"Class locator = cl.loadClass(\"org.zeroturnaround.jrebel.netbeans.JRebelLocator\");" +
"Method getJRebelOptsStr = locator.getMethod(\"getJRebelOptsStr\", null);" +
"String opts = (String) getJRebelOptsStr.invoke(null, null);";
patch(…)final CtMethod method = ctc.getDeclaredMethod("run");
method.instrument(new ExprEditor() { public void edit(MethodCall m) { if ("getJavaOpts".equals(m.getMethodName())) { m.replace( "if (command == CommandType.START)" + OPTS + "$_ = opts + $proceed($$);"); } }});
String OPTS = "ClassLoader cl = (ClassLoader) Lookup.getDefault().lookup(ClassLoader.class);" +
"Class locator = cl.loadClass(\"org.zeroturnaround.jrebel.netbeans.JRebelLocator\");" +
"Method getJRebelOptsStr = locator.getMethod(\"getJRebelOptsStr\", null);" +
"String opts = (String) getJRebelOptsStr.invoke(null, null);";
-javaagent:/path/to/jrebel.jar etc
Pros & Cons
+ Can get stuff done even if the platform doesn’t provide a proper interface
- Brittle for maintenance
New API (7.2)
• SPI for JVM options passed to SE program or EE server (Bug 206196)
@StartupArgumentsProvider.Registration( position=10, displayName="#DESC_JRebel", startMode={StartMode.NORMAL, StartMode.DEBUG})
public class MyArgsProvider implements StartupArgumentsProvider {@Overridepublic List<String> getArguments(ServerInstance instance, StartMode mode)
DEBUGGER INTEGRATION
Dude, where’s my car breakpoint?
Breakpoints
Line 11: initial breakpoint
Breakpoints
Line 14: same breakpoint, but new version of the class
Need to “transfer” the breakpoint from original class to versioned class
Patching
• o.n.m.d.jpda.breakpoints.LineBreakpointImpl– setRequests– getLocations
New API (7.3)
• Allow to provide additional binary classes to submit breakpoints on (Bug 215680)
@BreakpointClassFilter.Registrationpublic class MyFilter extends BreakpointClassFilter {
@Override public ClassNames filterClassNames(ClassNames classNames, JPDABreakpoint breakpoint){}
}
RELEASING THE PLUGIN
Packaging
Packaging
Plugin has to be signed
Plugin itself
Module descriptor
JRebel distribution
Dependencies
Maven Build
• NetBeans modules maven plugin– nbm-maven-plugin
• Properties maven plugin– properties-maven-plugin
• Maven dependency plugin– maven-dependency-plugin
• Maven compiler plugin• Maven JAR plugin
Publishing
• http://plugins.netbeans.org/• Verification is performed manually (by
volunteers)• Main criteria: should not break the platform• Takes some time
Lessons Learned
• UX is hard• Follow the logic of the target IDE• Follow the new APIs • Annotations over layer.xml• If no required extension point provided, can
get around with patching via ModuleInstall• Publishing is not 100% predictable process
Credits
• Big thanks to NetBeans team for awesome co-operation & providing the new API for debugger and server adaptors!