NetBeans Plugin Development: JRebel Experience Report

52
NetBeans Plugin Development: JRebel Experience Report JavaOne SF 2012
  • date post

    21-Oct-2014
  • Category

    Technology

  • view

    1.174
  • download

    1

description

 

Transcript of NetBeans Plugin Development: JRebel Experience Report

Page 1: NetBeans Plugin Development: JRebel Experience Report

NetBeans Plugin Development:JRebel Experience Report

JavaOne SF 2012

Page 2: NetBeans Plugin Development: JRebel Experience Report
Page 3: NetBeans Plugin Development: JRebel Experience Report

whoami

Anton ArhipovJRebel Product Lead

@[email protected]

Page 4: NetBeans Plugin Development: JRebel Experience Report

Agenda• JRebel intro– Plugin requirements

• NetBeans plugin development• Integrating with NetBeans server adaptors• Integrating with NetBeans debugger• Packaging & publishing

Page 5: NetBeans Plugin Development: JRebel Experience Report

ENTER JREBEL

Page 6: NetBeans Plugin Development: JRebel Experience Report

The Turnaround Cycle

Make changes

Build, deploy,

wait

Observe results

AVG 2.5 min

Page 7: NetBeans Plugin Development: JRebel Experience Report

MyObject

MyObject.class

ClassLoader

Code101000101100010010 New code

111000100101010010

Make changesin IDE

JRebel

Fram

ewor

k

Configuration(XML, annotations,..)

Page 8: NetBeans Plugin Development: JRebel Experience Report

in action

Page 9: NetBeans Plugin Development: JRebel Experience Report

Why IDE plugin?

UsabilityAutomationDebugger

Page 10: NetBeans Plugin Development: JRebel Experience Report

JRebel Plugin for NetBeans

VM args

BreakpointsStepping

Settings, UX

Process Launcher

Debugger

Workbench

Net

Bean

sJRebel Plugin

Page 11: NetBeans Plugin Development: JRebel Experience Report

ENTER NETBEANS PLUGINS

Page 12: NetBeans Plugin Development: JRebel Experience Report

Books

Page 13: NetBeans Plugin Development: JRebel Experience Report

UI Elements

• Buttons/Actions• Toolbars/Menus• Settings/Options• Popups• etc

Page 14: NetBeans Plugin Development: JRebel Experience Report

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"/>

Page 15: NetBeans Plugin Development: JRebel Experience Report

Options

Page 16: NetBeans Plugin Development: JRebel Experience Report

<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)

Page 17: NetBeans Plugin Development: JRebel Experience Report

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 {

Page 18: NetBeans Plugin Development: JRebel Experience Report

CUSTOM JVM ARGUMENTS

Page 19: NetBeans Plugin Development: JRebel Experience Report

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

Page 20: NetBeans Plugin Development: JRebel Experience Report

Server settings: Tomcat

Can put your arguments here

Page 21: NetBeans Plugin Development: JRebel Experience Report

Server settings: Glassfish

No VM arguments?

Page 22: NetBeans Plugin Development: JRebel Experience Report

Project Deployment

Aha!!

Page 23: NetBeans Plugin Development: JRebel Experience Report

Toggle

Simplest, from the user’s point of view: should not

care about project type of runtime differences

Page 24: NetBeans Plugin Development: JRebel Experience Report

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);

}}

Page 25: NetBeans Plugin Development: JRebel Experience Report

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

Page 26: NetBeans Plugin Development: JRebel Experience Report

PATCHING THE PLATFORM

Page 27: NetBeans Plugin Development: JRebel Experience Report

org.openide.modules.ModuleInstall

public class Installer extends ModuleInstall { @Override public void restored() { // patch platform classes here

}}

Page 28: NetBeans Plugin Development: JRebel Experience Report

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);

Page 29: NetBeans Plugin Development: JRebel Experience Report

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

Page 30: NetBeans Plugin Development: JRebel Experience Report

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

Page 31: NetBeans Plugin Development: JRebel Experience Report

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);

Page 32: NetBeans Plugin Development: JRebel Experience Report

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

Page 33: NetBeans Plugin Development: JRebel Experience Report

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

Page 34: NetBeans Plugin Development: JRebel Experience Report

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($$);"); } }});

Page 35: NetBeans Plugin Development: JRebel Experience Report

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

Page 36: NetBeans Plugin Development: JRebel Experience Report

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

Page 37: NetBeans Plugin Development: JRebel Experience Report

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);";

Page 38: NetBeans Plugin Development: JRebel Experience Report

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

Page 39: NetBeans Plugin Development: JRebel Experience Report

Pros & Cons

+ Can get stuff done even if the platform doesn’t provide a proper interface

- Brittle for maintenance

Page 40: NetBeans Plugin Development: JRebel Experience Report

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)

Page 41: NetBeans Plugin Development: JRebel Experience Report

DEBUGGER INTEGRATION

Dude, where’s my car breakpoint?

Page 42: NetBeans Plugin Development: JRebel Experience Report

Breakpoints

Line 11: initial breakpoint

Page 43: NetBeans Plugin Development: JRebel Experience Report

Breakpoints

Line 14: same breakpoint, but new version of the class

Need to “transfer” the breakpoint from original class to versioned class

Page 44: NetBeans Plugin Development: JRebel Experience Report

Patching

• o.n.m.d.jpda.breakpoints.LineBreakpointImpl– setRequests– getLocations

Page 45: NetBeans Plugin Development: JRebel Experience Report

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){}

}

Page 46: NetBeans Plugin Development: JRebel Experience Report

RELEASING THE PLUGIN

Page 47: NetBeans Plugin Development: JRebel Experience Report

Packaging

Page 48: NetBeans Plugin Development: JRebel Experience Report

Packaging

Plugin has to be signed

Plugin itself

Module descriptor

JRebel distribution

Dependencies

Page 49: NetBeans Plugin Development: JRebel Experience Report

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

Page 50: NetBeans Plugin Development: JRebel Experience Report

Publishing

• http://plugins.netbeans.org/• Verification is performed manually (by

volunteers)• Main criteria: should not break the platform• Takes some time

Page 51: NetBeans Plugin Development: JRebel Experience Report

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

Page 52: NetBeans Plugin Development: JRebel Experience Report

Credits

• Big thanks to NetBeans team for awesome co-operation & providing the new API for debugger and server adaptors!