MIGRATION GUIDE FOR JSP DEVELOPERS ================================== Required changes: - Stripes Tags. Add Stripes packages and imports to JSPs - WikiContext Creation. All top-level JSPs that create non-page-related WikiContexts must use the tag instead of createContext(). - JSP Content Pages. When JSP content templates (*Content.jsp) don't use the default name, JSP authors will be required to supply it. Recommended changes: - Setting WikiContext request attribute directly is discouraged. JSPs should not set the WikiContext or WikiSession as a request attribute, and is discouraged. - Parsing request parameters is discouraged. Setting request attributes should use WikiActionBean accessors instead of parsing request parameters directly. - no longer needed. JSPWiki's web.xml now sets the default bundle name (templates.default) for JSP tags. Enhancements: - JSP Page Variables. The current WikiContext and other objects are now saved automatically as request attributes, and are available to JSPs as JSTL variables 'wikiContext,' 'wikiActionBean,' 'wikiEngine,' and 'wikiSession.' REQUIRED CHANGES Stripes Tags ------------ Add Stripes packages and imports to JSPs. What JSP authors must do: Include these lines for all top-level JSPs: <%@ page import="com.ecyrd.jspwiki.action.*" %> <%@ taglib uri="/WEB-INF/stripes.tld" prefix="stripes" %> WikiContext Creation -------------------- All top-level JSPs that create non-page-related WikiContexts must use the tag instead of createContext(). What JSP authors must do: For login, group, workflow and other contexts that do not operate on WikiPages, replace lines like these: WikiContext wikiContext = wiki.createContext( request, WikiContext.LOGIN ); ...with these: To retrieve the WikiActionBean later, scriptlet code should call this static method: WikiActionBean wikiContext = WikiActionBeanFactory.findActionBean( request ); Background: WikiEngine.createContext() still works for those cases where creating a WikiContext (page-style ActionBean) is needed. In JSPWiki 3.0, WikiContext becomes an interface for WikiActionBeans that handle page viewing, editing, preview, diffs, etc. Thus, calls to WikiEngine.createContext() still work fine because the method is expected to return a WikiContext. But for other ActionBeans (e.g., LoginActionBean), WikiEngine.createContext() will not work because the return type is a WikiContext rather than the more generic WikiActionBean type that is required. Instead, scriptlet code should use tag. Note that every time or createContext() is called, the request's "current" is re-set, which effectively over-writes previous values. Note: JSPs that create these (pre-3.0) WikiContexts will need to be changed: ADMIN, CREATE_GROUP, DELETE_GROUP, EDIT_GROUP, ERROR, FIND, INSTALL, LOGIN, LOGOUT, MESSAGE, NONE, OTHER, PREFS, RSS, VIEW_GROUP, WORKFLOW. Admin.jsp NewGroup.jsp DeleteGroup.jsp EditGroup.jsp Search.jsp templates/default/AJAXSearch.jsp Install.jsp LoginForm.jsp LostPassword.jsp admin/SecurityConfig.jsp UserPreferences.jsp Group.jsp Workflow.jsp Scriptlet code -------------- Move JSP scriptlet code to ActionBean event handlers What JSP authors must do: (instructions go here) JSP Content Pages ----------------- When JSP content templates (*Content.jsp) don't use the default name, JSP authors will be required to supply it. What JSP authors must do: Add a line to scriptlet code similar to the following to the top-level JSP: wikiContext.setVariable( "contentTemplate", "PageContent.jsp" ); Background: Because the hard-coded Command classes are gone in JSPWiki, JSPWiki now calculates a default content template name. The content JSP is automatically calculated as the simple bean name, minus "ActionBean", plus "Content.jsp." Thus, the default for ViewActionBean.jsp would be ViewContent.jsp. This works in most cases, but not, for example, with Wiki.jsp (the content template is PageContent.jsp, not WikiContent.jsp). To override the default behavior, add a line similar to this to top-level JSPs, ideally right before the wiki:Include tag: wikiContext.setVariable( "contentTemplate", "PageContent.jsp" ); The 'contentTemplate' variable value will be read, downstream, by ContentTag when including the contents. Forms and form fields --------------------- Migration to Stripes tags: stripes:form. must change accept-charset to acceptcharset RECOMMENDED CHANGES Setting WikiContext request attribute directly is discouraged ------------------------------------------------------------- JSPs should not set the WikiContext or WikiSession as a request attribute, and is discouraged. What JSP authors must do: Lines like these: request.setAttribute( WikiTagBase.ATTR_CONTEXT, wikiContext ); should be removed. This seems to be a factor ONLY in two JSPs: Message.jsp and Workflow.jsp. Background: WikiInterceptor and WikiEngine.createContext() now assume sole responsibility for making the WikiContext available as a request attribute. Parsing request parameters is discouraged ----------------------------------------- Setting request attributes should use WikiActionBean accessors instead of parsing request parameters directly. What JSP authors must do: Stripes automatic parameter-binding system makes parsing request parameters unneccessary. Stripes will also automatically perform type conversion between the supplied parameter (always String or String[]) to the desired target type, based on the WikiActionBean property type. Thus, lines like these: request.setAttribute( "message", request.getParameter("message")); should be changed to: request.setAttribute( "message", messageActionBean.getMessage()); no longer needed -------------------------------- JSPWiki's web.xml now sets the default bundle name (templates.default) for JSP tags. What JSP authors must do: Remove these lines from JSPs: URL redirects ------------- General behaviors need to change in the JSP layer so that instead of looking up a URL and then calling redirect directly, we need instead to obtain RedirectResolutions. ENHANCEMENTS JSP Page Variables ------------------ The current WikiContext and other objects are now saved automatically as request attributes, and are available to JSPs as JSTL variables 'wikiContext,' 'wikiActionBean,' 'wikiEngine,' and 'wikiSession.' What JSP authors must do: Nothing. Assuming the WikiActionBean was created and resolved correctly, these four variables (wikiContext, wikiActionBEan, wikiEngine and wikiSession) are all available "for free" by JSPs. Details: in previous versions of JSPWiki, top-level JSPs were required to stash the WikiContext into request scope using either scriptlet code similar to this: request.setAttribute( WikiTagBase.ATTR_CONTEXT, this ); JSPs could also call WikiContext.hasAccess() and the save would be done automatically. In 3.0, however, responsibility for stashing the WikiContext now belongs to the Stripes WikiInterceptor and/or WikiEngine.createContext(). All that is require is that any of these three conditions be true: 1) the JSP calls WikiEngine.createContext(), 2) the JSP invokes the tag, or 3) the JSP is invoked indirectly by a Stripes ActionBean handle method. In all three cases, the current WikiActionBean is saved as the variable 'wikiActionBean'. If the WikiActionBean is *also* a WikiContext, the it is also saved as the variable named WikiTagBase.ATTR_CONTEXT ('wikiContext'). This is essentially unchanged from before. Also, if the WikiActionBean is *not* a WikiContext, to preserve backwards compatibility a fake WikiContext (a ViewActionBean that points to the front page) is saved instead. Thus, the JSTL variable 'wikiContext' will always be present. The addition of JSTL variables makes JSP scripting much simpler. For example, scriptlet code in JSPs that needs to obtain a reference to the WikiEngine can simply use ${wikiEngine}. This means... <% WikiContext c = WikiContext.findContext(pageContext); WikiPage wikipage = c.getPage(); %> ...can simply be replaced by ${wikiContext.page}. API Changes =========== WIKIACTIONBEANS --------------- Background ---------- WikiContext is the all-singing, all-dancing "request context" class in JSPWiki. It encapsulates the idea of "doing a wiki activity": viewing a page, editing a group, commenting, etc. In Stripes parlance, these are what you'd call "ActionBeans." WikiContext was originally conceived to encompass everything and everything you might want to do to a wiki page. In JSPWiki 2.4 and higher, WikiContext got overloaded with lots of activities that have nothing to do with pages, like creating groups and registering. It has been clear for a while that WikiContext needed to split itself into multiple classes and interfaces. The most logical thing to do is make WikiContext the general class or interface actions that deal exclusively with pages. Therefore: WikiContext becomes an abstract subclass of AbstractActionBean. The new class hierarchy for WikiContext and related types, for 3.0, looks like this: WikiActionBean extends ActionBean [Stripes] AbstractActionBean implements WikiActionBean | +--WikiContext extends AbstractActionBean | | | +--ViewActionBean extends WikiContext | +--EditActionBean extends WikiContext ... ... ... +--GroupActionBean extends AbstractActionBean | +--UserPreferencesActionBean extends AbstractActionBean ... WikiActionBeans are the core action class in JSPWiki 3.0. All WikiActionBeans are expected to adhere to the following contract: - must have a zero-argument constructor - must set the ActionBeanContext immediately after instantiation (setContext) - must set the associated ActionBeanContext's WikiEngine immediately after instantiation (setWikiEngine) - must return non-null HttpServletRequest and HttpServletResponse objects, either via WikiActionBean methods or via WikiActionBeanContext.getRequest/getResponse. Note that because WikiContext becomes abstract, it is not possible to create them directly any more. See the next section for details on how to create them. Creating WikiActionBeans ------------------------ WikiActionBeans can be created in five ways: 1) Injection by UseActionBeanTag due to tag in JSP. This is the preferred way because it can be done with only a single changed line at the top of a top-level JSP: Technically, the WikiActionBean is looked up by Stripes' configured ActionResolver (by default, the AnnotatedClassActionResolver); the bean itself is instantiated using its zero-argument constructor. Stripes guarantees that the following things happen after instantiation, as part of its ActionBeanResolution lifecycle stage: - The WikiActionBean's setActionBeanContext() method is called (by AnnotatedClassActionResolver); this causes a reference to a new WikiActionBeanContext to be set in the WikiActionBean - The WikiActionBeanContext's setServletContext() method is called (by UseActionBeanTag); this sets an internal reference to the WikiEngine - The WikiActionBeanContext's setRequest/setResponse methods are called (by DefaultActionBeanContextFactory). This will also retrieve and set a reference to the user's WikiSession - After the ActionBean is resolved by Stripes, the JSPWiki WikiInterceptor fires and stashes the WikiActionBean into the HTTP request's ACTIONBEAN attribute. It also stashes the WikiContext (which will either be the WikiActionBean or a fake WikiContext), WikiEngine and WikiSession. What this means, in particular, is that JSP EL syntax can be used to access the "wikiActionBean," "wikiContext," "wikiEngine," and "wikiSession" attributes. - If a Stripes event handler method is being called, the JSPWiki WikiInterceptor fires again just after the event handler is identified, and checks for proper access by the user. Access is determined by checking if the user possesses the required permission (null means "allowed"). If not, a RedirectResolution is returned that directs the user to the login page and appends all request parameters. (Note: we should make it check for authentication; if the user is already logged in, it should redirect to a "forbidden" page. Thus, when is used in a JSP, the WikiActionBean in question is guaranteed to have a non-null, valid WikiActionBeanContext associated with it. This WikiActionBeanContext, in turn, is guaranteed to return non-null results for its getResponse/getRequest/getServletContext methods, as well as its getWikiEngine and getWikiSession methods. And the WikiSession, WikiEngine, WikiContext and WikiActionBean are all available as JSP EL variables. 2) Stripes automatic binding of ActionBean via POST/GET to /dispatcher, /action/* or *.action URLs. This method is used, in particular, by generated Stripes form elements. It is rare that a user would actually specify one of these URLs directly. As with the previous method, the WikiActionBean is looked up by Stripes' configured ActionResolver. The same guarantees apply: after resolution and instantiation, the WikiActionBean will have a non-null, valid WikiActionBeanContext, and that the WikiActionBeanContext will return non-null results for getResponse/getRequest/getServletContext/getWikiEngine/getWikiSession. The four variables named above (wikiActionBean, wikiContext, wikiEngine, wikiSession) are injected as request attributes. These two techniques are the preferred ways to create WikiActionBeans. There are three other, less preferred, ways: 3) WikiEngine.createContext( HttpServletRequest, String). Although the and URl-binding methods are now the preferred way to create WikiActionBeans (and their WikiContext subclasses), for backwards compatibility reasons the WikiEngine.createContext() has been retrofitted to create WikiContexts in the proper way. Note that for backwards compatibility reasons, createContext() ONLY creates objects of type WikiContext rather than the superclass type WikiActionBean. (It would have been too much work to change the return type.) WikiEngine.createContext honors the same contract as the previous techniques. If null is supplied for the request parameter, a mock objects is synthesized. Thus, when the createContext() method is used, the resulting WikiContext is guaranteed to have a non-null, valid WikiActionBeanContext associated with it. This WikiActionBeanContext, in turn, is guaranteed to return non-null results for its getServletContext/getWikiEngine/getRequest/getResponse methods. Note that the createContext() technique differs in one important respect from the or Stripes URL binding techniques. Although it will correctly identity the page requested by the user (by inspecting request parameters), it will not do anything special if the page is a "special page." If special page resolution and redirection is required, use the <stripes:useActionBean> JSP tag instead. 4) WikiActionBeanFactory.newActionBean(HttpServletRequest,HttpServletResponse,Class). This method performs a similar role to createContext(), in the sense that it will instantiate an arbitrary WikiActionBean class and, in the case of WikiContext subclasses, bind a WikiPage to it. As with the previous techniques, it will instantiate a new WikiActionBean and associate it with a new, synthetic, WikiActionBeanContext. If nulls are supplied for the request or response parameters, mock objects are synthesized so that the contract is honored. 5) WikiActionBeanFactory.newViewActionBean( HttpServletRequest,HttpServletResponse,WikiPage). This methods instantiates a ViewActionBean and associates a WikiActionBeanContext with the bean. The WikiActionBeanContext's setWikiEngine and setServletContext methods are called after instantiation. If request or response objects are supplied, it is associated with the WikiActionContext. If not, mock objects are synthesized so that the contract is honored. In addition to these five techniques, callers can create new WikiActionBeans via the zero-arg constructor [WikiContext context = new ViewActionBean()]; however, callers must also honor the contract for returning non-null WikiEngine, WikiActionBeanContex and request/response references. It's much easier, instead, to call the factory method WikiActionBeanFactory.newActionBean( WikiPage page), which does it for you. WikiActionBean Security ----------------------- In 2.4, security permissions were added to WikiContexts. Each WikiContext could specify what Permission a user needed to access that context by causing the method requiredPermission() to return a value. This worked well, although it required each WikiContext type ("view","edit" etc.) to figure out a way to express the Permission through code. This, in part, was what let to the disaster that is the Command class. In 3.0, Permissions are much more flexible because they are annotation-driven. Moreover, they are specified at the method level, NOT at the class (WikiContext) level. Because WikiContext (and its subclasses) implement ActionBean, this is another way of saying, "Permissions annotate methods that do things." For example, consider ViewActionBean, which is a WikiContext subclass that displays a wiki page. Its default event is the "view()" method, which simply forwards control to itself and causes the page to display. Here's the method signature: @DefaultHandler @HandlesEvent("view") @EventPermission(permissionClass=PagePermission.class, target="${page.qualifiedName}", actions=PagePermission.VIEW_ACTION) public Resolution view() { ... } Note the @EventPermission annotation. It defines the Permission class and its target and actions. The "permissionClass" attribute tells use that the Permission class this method needs is "PagePermission". Note also the JSTL-style syntax in the target and actions attributes -- these allow JSTL-access to bean properties for the instantiated ViewActionBean. In this case, "page" is the bean attribute that returns the value of this ViewActionBean's getPage() method. The nested syntax "page.qualifiedName" is equivalent to getPage().getQualifiedName(). Neat, huh? Notes from Janne Visit: ---------------------- ActionContext: interface to most of the forwarding methods below. WikiActionBeanContext implements this. WikiActionBeanContext would contain (private/protected) get/sets for JCR context. Not in ActionContext or WikiContext interfaces. Mappings from JCR paths to permissions and URLs: JCR node: /wikis/{space}/{page}/{sub}/{subsub} Permission target: {space}:{page}/{sub}/{subsub} URL: /wiki/{space}:{page}/{sub}/{subsub} -- where space == "default" if omitted Agreed: wipe out ISO text support and use UTF-8 completely. Deprecations: WikiSession messages should/will go away with Stripes. Don't need Stripes templates yet. JMX: use annotations to expose particular Manager methods as MBean. WIKICONTEXT ----------- In JSPWiki versions prior to 3.0, WikiContext objects were created by calling a public constructor, followed by a call to setRequestContext(String). In 3.0, however, WikiContext becomes an interface [abtract class?] so contructors don't work. Instead, callers wishing to create WikiContexts should instead use the factory method WikiActionBeanFactory.newActionBean(request,response,beanClass). As an alternative, callers may also call WikiEngine.createContext(request,String). FORWARDING METHODS -- MOVE TO SUPERINTERFACE ACTIONCONTEXT clone() -- needs refactoring getBundle() to super getCurrentUser(): to super getEngine(): to super getHttpParameter(String): to super getHttpRequest(): to super getLocale(m_context) -- forwards to WikiActionBeanContext.getLocale(); uses Stripes-supplied Locale rather than the one set by Preferences cookies. This is also shared with Stripes class ActionBeanContext, which is rather convenient, no? getRequestContext(): to super getTemplate(): to super getVariable(String): to super getWikiSession(): to super setVariable(String,Object): to super setTemplate(): to super WIKI-CONTEXT-SPECIFIC getName(): refactored slightly getPage() getRealPage() getURL(String, String) -- essentially the same, although it assumes StripesURLConstructor is used (?) getViewURL() D hasAccess(HttpServletResponse) -- used in MANY JSPs. Refactored to look up the permission info for the method that contains @DefaultHander annotation. D hasAccess(HttpServletResponse,boolean) -- referenced only by other hasAccess(). setRequestContext() -- now delegates to set the action bean event (if request context matches, otherwise throw error; NONE always matches) setPage() setRealPage() findContext() -- no change hasAdminPermissions() BACKWARDS INCOMPATIBILITIES WikiContext(WikiEngine,WikiPage) WikiContext(WikiEngine,HttpServletRequest,Command) WikiContext(WikiEngine,HttpServletRequest,WikiPage) getRedirectURL(). Used in exactly 2 places... Wiki.jsp + WikiEngine (forward to WikiContext) getTarget() getURLPattern() getCommand() requiredPermission() -- only used in tests and WikiContext itself. Ok to kill. Replaced by WikiInterceptor in combination with event-level annotations targetedCommand() getContentTemplate(): only used by ContentTag and Command/CommandResolver tests getJSP(): only used by ContentTag and Command/CommandResolver tests setDefaultTemplate() [Protected] findCommand() [Protected] updateCommand() [Protected] WIKIENGINE ---------- DEPRECATIONS getURLContructor() -- should use URLBuilder instead, with request awareness getViewURL() -- refactored; should use URLBuilder instead, with request awareness createContext() -- changes its internal behavior. It delegates to m_engine.getWikiActionBeanFactory().newActionBean(Class becomes , becomes (b) becomes ; type attribute goes away. Value attribute contents should be moved into the body of the tag. So: becomes this: becomes ; type attribute goes away becomes ; type attribute goes away (c) becomes ; type attribute goes away. "Name" should be the name of the event handler for the ActionBean that is executed. The value attribute should be moved into the body of the tag. So: becomes this: (c) If a form action URL has an & appended to it, these should be added back as tags. E.g.,
Items processed by JspMigrator + JSPWikiJspTransformer: (a) Remove onsubmit="return Wiki.submitOnce(this);" because Stripes takes care of double-submits (b) accept-charset becomes "acceptcharset" and should use UTF-8. acceptcharset="${wikiEngine.contentEncoding}" (c) INFO Hidden values are PROBABLY candidates to become bean properties that are automatically bound. E.g., in LoginForm.jsp, disappears because setRedirect() is already part of LoginActionBean. (d) INFO Consider replacing: with this:
However, MessagesTag has been retrofitted to print Stripes errors for the current ActionBean in addition to the standard kind. So even though are preferred, works for now (but should be considered deprecated). tag re-factored so the set of Valididation for the current ActionBean are always appended to the messages list. This allows to be used transparently in place of . LocalePicker will need to default to the user's Preferences setting... we will need to create a separate class for this that extends DefaultLocalePicker. Validation guidelines: 1. Default the @Validate messages to the same resource bundle used by the templates. This would be default*.properties. Practically speaking, this means that the current contents of StripesResources.properties (which have the default messages for @Validate validations) would be appended to default*.properties. Stripes messages and keys are VERY stable. 2. For @ValidateMethod custom validation methods, always add SimpleErrors to the ValidationProperties object. The message string passed to the SimpleError constructor should be the final text that is obtained by looking it up in CoreResources. 3. For event handler methods (i.e., they have a @HandlesEvent annotation) that generate errors, do the same as #2: look up the final string in CoreResources and pass it to the SimpleError constructor. Eclipse Tools Notes ------------------- TestEngine: Because of its use of Stripes mock objects, TestEngine needs to be able to find the various implementations provided in JSPWiki. Therefore, it is extremely sensitive to changes in the build path. In particular, the mock servlet filter used by TestEngine hard-wires in the relative location build for finding ActionBeans. This is the directory (relative to the project root) that the Ant build scripts use for placing generated Java class files. The Eclipse project configuration must configure itself the same way. To run unit tests in Eclipse, the build directory absolutely must place generated class files in this directory, rather than the Eclipse default of classes. If unit tests do not run in Eclipse for some reason, this is the likeliest culprit. Use JVM args -Xmx1024m Eclipse... Software Update New Remote Site: Name: Findbugs URL: http://findbugs.cs.umd.edu/eclipse Name: CheckStyle Eclipse Plug-in URL: http://eclipse-cs.sourceforge.net/update Bugs Bugs Bugs ============== i18n ---- Switching to Stripes for URL building (via URLBuilder) causes UTF-8 strings to be created by the getURL methods: So: ÄitiSyöÖljyä becomes: ÄitiSyöÖljyä JSPWikiMarkupParserTest.testAttachmentLink Expected This should be an attachment link(info) Actual This should be an attachment link(info) Solution: hacked WikiConext so that getUrl() special-cases the AttachActionBean and NoneActionBean output UndefinedPagesPluginTest.testSimpleUndefined Expected Foobar 2
Actual Foobar 2
Page 1000
Page 1001
Page 1002
Page 1003
Testing issues -------------- Both tests needed to be changed because Stripes encodes in UTF-8. JSPWikiMarkupParserTest.testCCLinkWithScandics() Old value of 'page': %C4itiSy%F6%D6ljy%E4 New value: %C3%84itiSy%C3%B6%C3%96ljy%C3%A4 JSPWikiMarkupParserTest.testScandicPagename1() Old value of 'page': %C5%E4Test New value: %C3%85%C3%A4Test GroupsTest: Discovered Groups plugin has this line in it: String rewriteUrl = context.getContext().getResponse().encodeURL( url ); ...but the test ran without being in a MockServletContext. Thus, its parent WikiActionBeanContext had no associated response! Plugin test should *always* test inside a mock object. Also, in TestEngine I had to override all of the getHTML methods so that the manufactured WikiContext had properly injected requests and responses. GroupsPlugin right now does not generate Groups.jsp links, but Group.action links. Maybe it should generate .jsp links? JSP Tier changes ---------------- All of the non-top-level JSPs that shouldn't be directly instantiated are moved to /WEB-INF/jsp/layout. 2.6 commonheader.jsp 'JsonUrl' : '<%= WikiContext.findContext(pageContext).getURL( WikiContext.NONE, "JSON-RPC" ) %>' 3.0 'JsonUrl' : 'JSON-RPC' JSP Locale Support Because Stripes automatically sets the locale, there is no need to use methods like this: LocaleSupport.getLocalizedMessage(pageContext, "attach.tab") Instead, you can simply use: ...except that you can't, at least not in attributes! Damn. Stripes layout system --------------------- This is replaced: by Wiki:Tab needs to be able to evaluate attribute contents... now it doesn't... Things that don't work ---------------------- RSSGenerator, probably... FeedDiscoveryTag probably needs a total re-compare. Groups looks like it generates URLs in a funny way... FeedDiscoveryTeg generates URLs in a funny way... Not sure about ResolutionException and RedirectException... committed them anyway. Selenium tests are temporarily borked because we use the old JSPs.