Re: [xwiki-devs] [xwiki-notifications] r28878 - in contrib/sandbox: . xwiki-wiki-components xwiki-wiki-components/src xwiki-wiki-components/src/main xwiki-wiki-components/src/main/java xwiki-wiki-components/src/main/java/com xwiki-wiki-components/src/main/java/co
16 May
2010
16 May
'10
1:25 p.m.
----- "Thomas Mortagne" <[email protected]> wrote: > On Sat, May 15, 2010 at 17:08, jvelociter > <[email protected]> wrote: > > > > Author: jvelociter > > Date: 2010-05-15 17:08:25 +0200 (Sat, 15 May 2010) > > New Revision: 28878 > > > > Added: > > contrib/sandbox/xwiki-wiki-components/ > > contrib/sandbox/xwiki-wiki-components/pom.xml > > contrib/sandbox/xwiki-wiki-components/src/ > > contrib/sandbox/xwiki-wiki-components/src/main/ > > contrib/sandbox/xwiki-wiki-components/src/main/java/ > > contrib/sandbox/xwiki-wiki-components/src/main/java/com/ > > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/ > > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/ > > > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/internal/ > > > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/internal/DefaultWikiComponentBuilder.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/internal/WikiComponentInitializer.java > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/ > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/ > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/ > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/ > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/InvalidComponentDefinitionException.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/MethodOutputHandler.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponent.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentBuilder.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentException.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentInvocationHandler.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentManager.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/ > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultMethodOutputHandler.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultWikiComponent.java > > > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultWikiComponentManager.java > > contrib/sandbox/xwiki-wiki-components/src/main/resources/ > > contrib/sandbox/xwiki-wiki-components/src/main/resources/META-INF/ > > > contrib/sandbox/xwiki-wiki-components/src/main/resources/META-INF/components.txt > > contrib/sandbox/xwiki-wiki-components/src/test/ > > contrib/sandbox/xwiki-wiki-components/src/test/java/ > > contrib/sandbox/xwiki-wiki-components/src/test/java/org/ > > contrib/sandbox/xwiki-wiki-components/src/test/java/org/xwiki/ > > > contrib/sandbox/xwiki-wiki-components/src/test/java/org/xwiki/component/ > > > contrib/sandbox/xwiki-wiki-components/src/test/java/org/xwiki/component/wiki/ > > > contrib/sandbox/xwiki-wiki-components/src/test/java/org/xwiki/component/wiki/WikiComponentManagerTest.java > > Log: > > XWIKI-5195 Make possible to implement any component from the wiki, > using objects > > > > Proposal implementation, for review. Right now supports definition > of component implementation as wiki objects, with support for multiple > interfaces implementations and methods return value via explicit call > or plain block rendering if no explicit call and return type is > String. > > > > Missing : > > - More tests > > - Support for requirements bindings > > - Support for inputs handling (method arguments bindings) > > > > Open questions I can think of : > > - Should we refuse to register a component if it does not declare > implementation of all its role methods ? (to avoid potential NPEs, for > example) > > Why NPE ? The proxy is supposed to generate a proper exception when > something is calling unimplemented method. Right. It's actually doing so already (throwing NoSuchMethodException). I think the question still holds, though. We could decide to enforce compliance to the interface in order to allow a component to be registered (would reduce collateral damage for components relying on that role - like the OM interacting with an EventListener not declaring onEvent) > > > - Better name for XWiki.ComponentInterfaceClass ? (It's the class > representing an extra interface the component implementation > implements)= > > > > Note: module right now relies on XWIKI-5194 (bridge event for XWiki > initialized) being applied to xwiki-core/xwiki-bridge > > > > > > [...] > > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/internal/DefaultWikiComponentBuilder.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/internal/DefaultWikiComponentBuilder.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/internal/DefaultWikiComponentBuilder.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,167 @@ > > [...] > > > +/** > > + * Default implementation of a wiki component builder, that is > using the legacy XWiki core module. > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +@Component > > +public class DefaultWikiComponentBuilder implements > WikiComponentBuilder > > +{ > > + > > + /** > > + * The name of the document that holds the XClass definition of > an implementation of an interface by a component. > > + */ > > + private static final String XWIKI_COMPONENT_INTERFACE_CLASS = > "XWiki.ComponentInterfaceClass"; > > + > > + /** > > + * The name of the document that holds the XClass definition of > a method of a component. > > + */ > > + private static final String XWIKI_COMPONENT_METHOD_CLASS = > "XWiki.ComponentMethodClass"; > > + > > + /** > > + * The property name of the name of a component method. > > + */ > > + private static final String COMPONENT_METHOD_NAME_FIELD = > "name"; > > + > > + /** > > + * The property name of the name of an implemented interface. > (Checkstyle fix). > > + */ > > + private static final String COMPONENT_INTERFACE_NAME_FIELD = > COMPONENT_METHOD_NAME_FIELD; > > + > > + /** > > + * Execution, needed to access the XWiki context map. > > + */ > > + @Requirement > > + private Execution execution; > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public WikiComponent build(DocumentReference reference) throws > InvalidComponentDefinitionException, > > + WikiComponentException > > + { > > + try { > > + XWikiDocument componentDocument = > getXWikiContext().getWiki().getDocument(reference, getXWikiContext()); > > + BaseObject componentObject = > componentDocument.getObject("XWiki.ComponentClass"); > > + > > + if (componentObject == null) { > > + throw new InvalidComponentDefinitionException("No > component object could be found"); > > + } > > + > > + String role = componentObject.getStringValue("role"); > > + > > + if (StringUtils.isBlank(role)) { > > + throw new InvalidComponentDefinitionException("No > role were precised in the component"); > > + } > > + > > + Class< ? > roleAsClass; > > + try { > > + roleAsClass = Class.forName(role); > > + } catch (ClassNotFoundException e) { > > + throw new InvalidComponentDefinitionException("The > role class could not be found", e); > > + } > > + > > + String roleHint = > StringUtils.defaultIfEmpty(componentObject.getStringValue("roleHint"), > "default"); > > + > > + DefaultWikiComponent component = new > DefaultWikiComponent(reference, roleAsClass, roleHint); > > + > component.setHandledMethods(this.getHandledMethods(componentDocument)); > > + > component.setImplementedInterfaces(this.getDeclaredInterfaces(componentDocument)); > > + > > + return component; > > + > > + } catch (XWikiException e) { > > + throw new WikiComponentException("Failed to build wiki > component for document " + reference.toString()); > > + } > > + } > > + > > + /** > > + * @param componentDocument the document holding the component > description > > + * @return the map of component handled methods/method body > > + */ > > + private Map<String, String> getHandledMethods(XWikiDocument > componentDocument) > > + { > > + Map<String, String> handledMethods = new HashMap<String, > String>(); > > + if > (componentDocument.getObjectNumbers(XWIKI_COMPONENT_METHOD_CLASS) > 0) > { > > + for (BaseObject iface : > componentDocument.getObjects(XWIKI_COMPONENT_METHOD_CLASS)) { > > + if > (!StringUtils.isBlank(iface.getStringValue(COMPONENT_METHOD_NAME_FIELD))) > { > > + > handledMethods.put(iface.getStringValue(COMPONENT_METHOD_NAME_FIELD), > iface.getStringValue("code")); > > + } > > + } > > + } > > + return handledMethods; > > + > > + } > > + > > + /** > > + * @param componentDocument the document holding the component > description > > + * @return the array of interfaces declared (and actually > existing) by the document > > + */ > > + private Class< ? >[] getDeclaredInterfaces(XWikiDocument > componentDocument) > > + { > > + List<Class< ? >> interfaces = new ArrayList<Class< ? >>(); > > + if > (componentDocument.getObjectNumbers(XWIKI_COMPONENT_INTERFACE_CLASS) > > 0) { > > + for (BaseObject iface : > componentDocument.getObjects(XWIKI_COMPONENT_INTERFACE_CLASS)) { > > + if > (!StringUtils.isBlank(iface.getStringValue(COMPONENT_INTERFACE_NAME_FIELD))) > { > > + try { > > + Class< ? > implemented = > Class.forName(iface.getStringValue(COMPONENT_INTERFACE_NAME_FIELD)); > > + interfaces.add(implemented); > > + } catch (ClassNotFoundException e) { > > + // Silent > > + } > > + } > > + } > > + } > > + return interfaces.toArray(new Class< ? >[] {}); > > + } > > + > > + /** > > + * @return a XWikiContext, retrieved from our execution > > + */ > > + private XWikiContext getXWikiContext() > > + { > > + return (XWikiContext) > execution.getContext().getProperty("xwikicontext"); > > + } > > + > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/internal/WikiComponentInitializer.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/internal/WikiComponentInitializer.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/com/xpn/xwiki/internal/WikiComponentInitializer.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,339 @@ > > [...] > > > +/** > > + * Initializes the Wiki Component feature. First ensure all needed > XClasses are up-to-date, then registers existing > > + * components. > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +@Component("wikiComponentInitializer") > > +public class WikiComponentInitializer extends AbstractLogEnabled > implements EventListener > > +{ > > + /** > > + * The XClass defining a component implementation. > > + */ > > + private static final String WIKI_COMPONENT_CLASS = > "XWiki.ComponentClass"; > > + > > + /** > > + * The XClass defining a component requirement. > > + */ > > + private static final String WIKI_COMPONENT_REQUIREMENT_CLASS = > "XWiki.ComponentRequirementClass"; > > + > > + /** > > + * The XClass defining a component method. > > + */ > > + private static final String WIKI_COMPONENT_METHOD_CLASS = > "XWiki.ComponentMethodClass"; > > + > > + /** > > + * The XClass defining a component interface implementation. > > + */ > > + private static final String WIKI_COMPONENT_INTERFACE_CLASS = > "XWiki.ComponentInterfaceClass"; > > + > > + /** > > + * The name property of the {@link > WIKI_COMPONENT_INTERFACE_CLASS} XClass. > > + */ > > + private static final String INTERFACE_NAME_FIELD = "name"; > > + > > + /** > > + * The name property of the {@link WIKI_COMPONENT_METHOD_CLASS} > XClass. (Fix checkstyle). > > + */ > > + private static final String METHOD_NAME_FIELD = > INTERFACE_NAME_FIELD; > > + > > + /** > > + * The role property of both {@link WIKI_COMPONENT_CLASS} and > {@link WIKI_COMPONENT_REQUIREMENT_CLASS}. > > + */ > > + private static final String COMPONENT_ROLE_HINT_FIELD = > "roleHint"; > > + > > + /** > > + * The role hint property of both {@link WIKI_COMPONENT_CLASS} > and {@link WIKI_COMPONENT_REQUIREMENT_CLASS}. > > + */ > > + private static final String COMPONENT_ROLE_FIELD = "role"; > > + > > + /** > > + * Our execution. Needed to access the XWiki context. > > + */ > > + @Requirement > > + private Execution execution; > > + > > + /** > > + * The wiki component manager that knows how to register > component definition against the underlying CM. > > + */ > > + @Requirement > > + private WikiComponentManager wikiComponentManager; > > + > > + /** > > + * Builder that creates component description from document > references. > > + */ > > + @Requirement > > + private WikiComponentBuilder wikiComponentBuilder; > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public List<Event> getEvents() > > + { > > + return Arrays.<Event> asList(new > XWikiInitializedBridgeEvent()); > > + } > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public String getName() > > + { > > + return "wikiComponentInitializer"; > > + } > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public void onEvent(Event arg0, Object arg1, Object arg2) > > + { > > + // First step, verify that all XClasses exists and are > up-to-date (act if not). > > + this.installOrUpdateComponentXClasses(); > > + // Second step, lookup and register existing components. > > + this.registerExistingWikiComponents(); > > + } > > + > > + /** > > + * Registers existing components. Query them against the store, > and if they are built properly (valid definition) > > + * register them against the CM. > > + */ > > + private void registerExistingWikiComponents() > > + { > > + String query = > > + ", BaseObject as obj, StringProperty as role where > obj.className='XWiki.ComponentClass'" > > + + " and obj.name=doc.fullName and role.id.id=obj.id > and role.id.name='role' and role.value <>''"; > > + try { > > + for (DocumentReference ref : > getXWikiContext().getWiki().getStore().searchDocumentReferences(query, > > + getXWikiContext())) { > > + try { > > + WikiComponent component = > this.wikiComponentBuilder.build(ref); > > + > > + > this.wikiComponentManager.registerWikiComponent(component); > > + } catch (InvalidComponentDefinitionException e) { > > + // Fail quietly and only log at the debug > level. > > + getLogger().debug("Invalid wiki component > definition for reference " + ref.toString(), e); > > + } catch (WikiComponentException e) { > > + // Fail quietly and only log at the debug > level. > > + getLogger().debug("Failed to register wiki > component for reference " + ref.toString(), e); > > + } > > + } > > + } catch (XWikiException e) { > > + getLogger().error("Failed to register existing wiki > components", e); > > + } > > + > > + } > > + > > + /** > > + * Verify that all XClasses exists and are up-to-date (act if > not). > > + */ > > + private void installOrUpdateComponentXClasses() > > + { > > + try { > > + this.installOrUpdateComponentXClass(); > > + this.installOrUpdateComponentRequirementXClass(); > > + this.installOrUpdateComponentMethodXClass(); > > + this.installOrUpdateComponentInterfaceXClass(); > > + } catch (XWikiException e) { > > + getLogger().error("Failed to install or update wiki > component XClasses", e); > > + } > > + } > > + > > + /** > > + * Verify that the {@link #WIKI_COMPONENT_INTERFACE_CLASS} > exists and is up-to-date (act if not). > > + * > > + * @throws XWikiException on failure > > + */ > > + private void installOrUpdateComponentInterfaceXClass() throws > XWikiException > > + { > > + XWikiContext xcontext = getXWikiContext(); > > + XWikiDocument doc = > xcontext.getWiki().getDocument(WIKI_COMPONENT_INTERFACE_CLASS, > xcontext); > > + > > + BaseClass bclass = doc.getXClass(); > > + bclass.setName(WIKI_COMPONENT_INTERFACE_CLASS); > > + > > + boolean needsUpdate = false; > > + > > + needsUpdate |= this.initializeXClassDocumentMetadata(doc, > "Wiki Component Implements Interface XWiki Class"); > > + needsUpdate |= bclass.addTextField(INTERFACE_NAME_FIELD, > "Interface Qualified Name", 30); > > + > > + if (needsUpdate) { > > + this.update(doc); > > + } > > + } > > + > > + /** > > + * Verify that the {@link #WIKI_COMPONENT_CLASS} exists and is > up-to-date (act if not). > > + * > > + * @throws XWikiException on failure > > + */ > > + private void installOrUpdateComponentXClass() throws > XWikiException > > + { > > + XWikiContext xcontext = getXWikiContext(); > > + XWikiDocument doc = > xcontext.getWiki().getDocument(WIKI_COMPONENT_CLASS, xcontext); > > + > > + BaseClass bclass = doc.getXClass(); > > + bclass.setName(WIKI_COMPONENT_CLASS); > > + > > + boolean needsUpdate = false; > > + > > + needsUpdate |= this.initializeXClassDocumentMetadata(doc, > "Wiki Component XWiki Class"); > > + needsUpdate |= bclass.addTextField(COMPONENT_ROLE_FIELD, > "Component role", 30); > > + needsUpdate |= > bclass.addTextField(COMPONENT_ROLE_HINT_FIELD, "Component role hint", > 30); > > + > > + if (needsUpdate) { > > + this.update(doc); > > + } > > + } > > + > > + /** > > + * Verify that the {@link #WIKI_COMPONENT_REQUIREMENT_CLASS} > exists and is up-to-date (act if not). > > + * > > + * @throws XWikiException on failure > > + */ > > + private void installOrUpdateComponentRequirementXClass() throws > XWikiException > > + { > > + XWikiContext xcontext = getXWikiContext(); > > + XWikiDocument doc = > xcontext.getWiki().getDocument(WIKI_COMPONENT_REQUIREMENT_CLASS, > xcontext); > > + > > + BaseClass bclass = doc.getXClass(); > > + bclass.setName(WIKI_COMPONENT_REQUIREMENT_CLASS); > > + > > + boolean needsUpdate = false; > > + > > + needsUpdate |= this.initializeXClassDocumentMetadata(doc, > "Wiki Component Requirement XWiki Class"); > > + needsUpdate |= bclass.addTextField(COMPONENT_ROLE_FIELD, > "Requirement role", 30); > > + needsUpdate |= > bclass.addTextField(COMPONENT_ROLE_HINT_FIELD, "Requirement role > hint", 30); > > + needsUpdate |= bclass.addTextField("bindingName", "Binding > name", 30); > > + needsUpdate |= bclass.addStaticListField("type", > "Requirement type", "single=Single|list=List|map=Map"); > > + > > + if (needsUpdate) { > > + this.update(doc); > > + } > > + } > > + > > + /** > > + * Verify that the {@link #WIKI_COMPONENT_METHOD_CLASS} exists > and is up-to-date (act if not). > > + * > > + * @throws XWikiException on failure > > + */ > > + private void installOrUpdateComponentMethodXClass() throws > XWikiException > > + { > > + XWikiContext xcontext = getXWikiContext(); > > + XWikiDocument doc = > xcontext.getWiki().getDocument(WIKI_COMPONENT_METHOD_CLASS, xcontext); > > + > > + BaseClass bclass = doc.getXClass(); > > + bclass.setName(WIKI_COMPONENT_METHOD_CLASS); > > + > > + boolean needsUpdate = false; > > + > > + needsUpdate |= this.initializeXClassDocumentMetadata(doc, > "Wiki Component Method XWiki Class"); > > + needsUpdate |= bclass.addTextField(METHOD_NAME_FIELD, > "Method name", 30); > > + needsUpdate |= bclass.addTextAreaField("code", "Method body > code", 40, 20); > > + > > + if (needsUpdate) { > > + this.update(doc); > > + } > > + } > > + > > + /** > > + * Utility method for updating a wiki macro class definition > document. > > + * > > + * @param doc xwiki document containing the wiki macro class. > > + * @throws XWikiException if an error occurs while saving the > document. > > + */ > > + private void update(XWikiDocument doc) throws XWikiException > > + { > > + XWikiContext xcontext = getXWikiContext(); > > + xcontext.getWiki().saveDocument(doc, xcontext); > > + } > > + > > + /** > > + * Helper method to prepare a document that will hold an XClass > definition, setting its initial metadata, if needed > > + * (author, title, parent, content, etc.). > > + * > > + * @param doc the document to prepare > > + * @param title the title to set > > + * @return true if the doc has been modified and needs saving, > false otherwise > > + */ > > + private boolean initializeXClassDocumentMetadata(XWikiDocument > doc, String title) > > + { > > + boolean needsUpdate = false; > > + > > + if (StringUtils.isBlank(doc.getCreator())) { > > + needsUpdate = true; > > + doc.setCreator(XWikiRightService.SUPERADMIN_USER); > > + } > > + if (StringUtils.isBlank(doc.getAuthor())) { > > + needsUpdate = true; > > + doc.setAuthor(doc.getCreator()); > > + } > > + if (StringUtils.isBlank(doc.getParent())) { > > + needsUpdate = true; > > + doc.setParent("XWiki.XWikiClasses"); > > + } > > + if (StringUtils.isBlank(doc.getTitle())) { > > + needsUpdate = true; > > + doc.setTitle(title); > > + } > > + if (StringUtils.isBlank(doc.getContent()) || > !XWikiDocument.XWIKI20_SYNTAXID.equals(doc.getSyntaxId())) { > > + needsUpdate = true; > > + doc.setContent("{{include document=\"XWiki.ClassSheet\" > /}}"); > > + doc.setSyntaxId(XWikiDocument.XWIKI20_SYNTAXID); > > + } > > + return needsUpdate; > > + } > > + > > + /** > > + * @return the XWikiContext extracted from the execution. > > + */ > > + private XWikiContext getXWikiContext() > > + { > > + return (XWikiContext) > this.execution.getContext().getProperty("xwikicontext"); > > + } > > + > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/InvalidComponentDefinitionException.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/InvalidComponentDefinitionException.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/InvalidComponentDefinitionException.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,59 @@ > > [...] > > > +/** > > + * Exception thrown by component builders when a document holds an > invalid compoentn definition. > > + * (For example if no role has been specified). > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +public class InvalidComponentDefinitionException extends Exception > > +{ > > + /** > > + * Constructor of this exception. > > + */ > > + public InvalidComponentDefinitionException() > > + { > > + super(); > > + } > > + > > + /** > > + * Constructor of this exception. > > + * > > + * @param message a message associated with the exception, that > explains why the definition is invalid > > + */ > > + public InvalidComponentDefinitionException(String message) > > + { > > + super(message); > > + } > > + > > + /** > > + * Constructor of this exception. > > + * > > + * @param message a message associated with the exception, that > explains why the definition is invalid > > + * @param t the root cause > > + */ > > + public InvalidComponentDefinitionException(String message, > Throwable t) > > + { > > + super(message); > > + } > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/MethodOutputHandler.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/MethodOutputHandler.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/MethodOutputHandler.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,44 @@ > > [...] > > > +/** > > + * Utility for wiki methods to return a value. An implementation of > this interface is binded in the context of a > > + * wiki method execution, so that such method scripts can return a > value using {@link #returnValue(Object)}. > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +public interface MethodOutputHandler > > +{ > > + > > + /** > > + * Stores a value in the method invocation context for further > return. > > + * Note that if this method is called multiple times during the > invocation, the last one wins. > > + * > > + * @param value the value to return > > + */ > > + void returnValue(Object value); > > + > > + /** > > + * @return the current stored return value (null if not set > yet). > > + */ > > + Object getReturnValue(); > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponent.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponent.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponent.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,58 @@ > > [...] > > > +/** > > + * Represents the definition of a wiki component implementation. > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +public interface WikiComponent > > +{ > > + /** > > + * @return the reference to the document holding this wiki > component definition. > > + */ > > + DocumentReference getDocumentReference(); > > + > > + /** > > + * @return the role implemented by this component > implementation. > > + */ > > + Class< ? > getRole(); > > + > > + /** > > + * @return the hint of the role implemented by this component > implementation. > > + */ > > + String getRoleHint(); > > + > > + /** > > + * @return the extra list of interfaces this component > implementation implements. > > + */ > > + Class< ? >[] getImplementedInterfaces(); > > + > > + /** > > + * @return the map of method name/wiki code this component > implementation handles. > > + */ > > + Map<String, String> getHandledMethods(); > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentBuilder.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentBuilder.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentBuilder.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,46 @@ > > [...] > > > +/** > > + * Constructs a {@link WikiComponent} out of the data contained in > the document pointed by a {@link DocumentReference}. > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +@ComponentRole > > +public interface WikiComponentBuilder > > +{ > > + > > + /** > > + * Builds a wiki component representation extracting the data > stored as objects of document. > > + * > > + * @param reference the reference to the document that holds > component definition objects > > + * @return the constructed component definition > > + * @throws InvalidComponentDefinitionException when the data in > the document is not a valid component definition > > + * @throws WikiComponentException the builder failed to create > the component out of the document (for example due to > > + * a failure by tge underlying store, etc.) > > + */ > > + WikiComponent build(DocumentReference reference) throws > InvalidComponentDefinitionException, WikiComponentException; > > + > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentException.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentException.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentException.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,51 @@ > > [...] > > > +/** > > + * A generic exception thrown by this module, usually a wrapper > around lower level exceptions. > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +public class WikiComponentException extends Exception > > +{ > > + /** > > + * Constructor of this exception. > > + * > > + * @param message the message associated with the exception > > + */ > > + public WikiComponentException(String message) > > + { > > + super(message); > > + } > > + > > + /** > > + * Constructor of this exception. > > + * > > + * @param message the message associated with the exception > > + * @param t the root cause > > + */ > > + public WikiComponentException(String message, Throwable t) > > + { > > + super(message, t); > > + } > > + > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentInvocationHandler.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentInvocationHandler.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentInvocationHandler.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,245 @@ > > [...] > > > +/** > > + * Method invocation handler for wiki component proxy instances. > Has a reference on a map of name/body wiki code of > > + * supported methods. > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +public class WikiComponentInvocationHandler implements > InvocationHandler > > +{ > > + /** > > + * The key under which the output is kept in the method > invocation context. > > + */ > > + private static final String METHOD_CONTEXT_OUTPUT_KEY = > "output"; > > + > > + /** > > + * The key under which the context document is kept in the > XWiki context. > > + */ > > + private static final String XWIKI_CONTEXT_DOC_KEY = "doc"; > > + > > + /** > > + * Pre-loaded hasCode Method. > > + * > > + * @see {@link Object#hashCode()} > > + */ > > + private static Method hashCodeMethod; > > + > > + /** > > + * Pre-loaded equals method. > > + * > > + * @see {@link Object#equals(Object)} > > + */ > > + private static Method equalsMethod; > > + > > + /** > > + * Pre-loaded toString method. > > + * > > + * @see {@link Object#toString()} > > + */ > > + private static Method toStringMethod; > > + > > + static { > > + try { > > + hashCodeMethod = Object.class.getMethod("hashCode", > null); > > + equalsMethod = Object.class.getMethod("equals", new > Class[] {Object.class}); > > + toStringMethod = Object.class.getMethod("toString", > null); > > + } catch (NoSuchMethodException e) { > > + throw new NoSuchMethodError(e.getMessage()); > > + } > > + } > > + > > + /** > > + * Map hosting handled methods. Keys are method names and > values are wiki code to "execute". > > + */ > > + private Map<String, String> handledMethods; > > + > > + /** > > + * Our component manager. > > + */ > > + private ComponentManager componentManager; > > + > > + /** > > + * The reference to the document. > > + */ > > + private DocumentReference componentReference; > > + > > + /** > > + * Constructor of this invocation handler. > > + * > > + * @param componentReference reference to the document holding > the component definition > > + * @param methods the map of methods handled by the component > instance > > + * @param componentManager the component manager > > + */ > > + public WikiComponentInvocationHandler(DocumentReference > componentReference, Map<String, String> methods, > > + ComponentManager componentManager) > > + { > > + this.componentReference = componentReference; > > + this.handledMethods = methods; > > + this.componentManager = componentManager; > > + } > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public Object invoke(Object proxy, Method method, Object[] > args) throws Throwable > > + { > > + if (!this.handledMethods.containsKey(method.getName())) { > > + if (method.getDeclaringClass() == Object.class) { > > + return this.proxyObjectMethod(proxy, method, args); > > + } else { > > + throw new NoSuchMethodException(); > > + } > > + } else { > > + return this.executeWikiContent(method); > > + } > > + > > + } > > + > > + /** > > + * "Executes" the wiki content associated to the passed method. > > + * > > + * @param method the method to execute > > + * @return the result of the execution > > + * @throws Exception when an error occurs during execution > > + */ > > + @SuppressWarnings("unchecked") > > + private Object executeWikiContent(Method method) throws > Exception > > + { > > + XDOM xdom; > > + Map xwikiContext = null; > > + Object contextDoc = null; > > + > > + Parser parser = componentManager.lookup(Parser.class, > Syntax.XWIKI_2_0.toIdString()); > > + xdom = parser.parse(new > StringReader(this.handledMethods.get(method.getName()))); > > + > > + Execution execution = > componentManager.lookup(Execution.class); > > + Transformation macroTransformation = > componentManager.lookup(Transformation.class, "macro"); > > + DocumentAccessBridge docBridge = > componentManager.lookup(DocumentAccessBridge.class); > > + > > + Map<String, Object> methodContext = new HashMap<String, > Object>(); > > + methodContext.put(METHOD_CONTEXT_OUTPUT_KEY, new > DefaultMethodOutputHandler()); > > + > > + // Place macro context inside xwiki context > ($context.macro). > > + xwikiContext = (Map) > execution.getContext().getProperty("xwikicontext"); > > + xwikiContext.put("method", methodContext); > > + // Save current context document. > > + contextDoc = xwikiContext.get(XWIKI_CONTEXT_DOC_KEY); > > + // Make sure has prog rights > > + xwikiContext.put(XWIKI_CONTEXT_DOC_KEY, > docBridge.getDocument(this.componentReference)); > > + > > + // Perform internal macro transformations. > > + macroTransformation.transform(xdom, Syntax.XWIKI_2_0); > > + > > + if (methodContext.get(METHOD_CONTEXT_OUTPUT_KEY) != null > > + && ((MethodOutputHandler) > methodContext.get(METHOD_CONTEXT_OUTPUT_KEY)).getReturnValue() != > null) { > > + return > method.getReturnType().cast(((MethodOutputHandler) > > + > methodContext.get(METHOD_CONTEXT_OUTPUT_KEY)).getReturnValue()); > > + } else if (method.getReturnType().equals(String.class)) { > > + // If return type is String and no specific return > value has been provided during the macro > > + // expansion, then we return the content redered as > > + WikiPrinter printer = new DefaultWikiPrinter(); > > + BlockRenderer renderer = > componentManager.lookup(BlockRenderer.class, > Syntax.PLAIN_1_0.toIdString()); > > + renderer.render(xdom, printer); > > + return printer.toString(); > > + } else { > > + // surrender > > + return null; > > + } > > + } > > Don't do the same mistake than wik macros (see > http://jira.xwiki.org/jira/browse/XWIKI-5030), you should store the > XDOM instead of the source to avoid reparsing it each time you execute > a virtual method. OK. Will take care of that. Jerome. > > > + > > + /** > > + * Proxies a method of the {@link Object} class. > > + * > > + * @param proxy the proxy instance > > + * @param method the method to proxy > > + * @param args possible arguments to the method invocation > > + * @return the result of the proxied call > > + */ > > + private Object proxyObjectMethod(Object proxy, Method method, > Object[] args) > > + { > > + if (method.equals(hashCodeMethod)) { > > + return proxyHashCode(proxy); > > + } else if (method.equals(equalsMethod)) { > > + return proxyEquals(proxy, args[0]); > > + } else if (method.equals(toStringMethod)) { > > + return proxyToString(proxy); > > + } else { > > + throw new InternalError("unexpected Object method > dispatched: " + method); > > + } > > + } > > + > > + /** > > + * Default behavior for {@link Object#hashCode()} when not > overridden in the wiki component definition. > > + * > > + * @param proxy the proxy object > > + * @return a hash code for the proxy object, as if using > standard {Object{@link #hashCode()}. > > + */ > > + protected Integer proxyHashCode(Object proxy) > > + { > > + return new Integer(System.identityHashCode(proxy)); > > + } > > + > > + /** > > + * Default behavior for {@link Object#equals(Object)} when not > overridden in the wiki component definition. > > + * > > + * @param proxy the proxy object > > + * @param other the other object of the comparison > > + * @return the result of the equality comparison between the > passed proxy and other object > > + */ > > + protected Boolean proxyEquals(Object proxy, Object other) > > + { > > + return (proxy == other ? Boolean.TRUE : Boolean.FALSE); > > + } > > + > > + /** > > + * Default behavior for {@link Object#toString()} when not > overridden in the wiki component definition. > > + * > > + * @param proxy the proxy object > > + * @return the String representation of the passed proxy object > > + */ > > + protected String proxyToString(Object proxy) > > + { > > + return proxy.getClass().getName() + '@' + > Integer.toHexString(proxy.hashCode()); > > + } > > + > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentManager.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentManager.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/WikiComponentManager.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,46 @@ > > [...] > > > +package org.xwiki.component.wiki; > > + > > +import org.xwiki.component.annotation.ComponentRole; > > + > > +/** > > + * A WikiComponentManager is responsible for registering and > unregistering components that are defined as wiki pages. > > + * Each {@link WikiComponent} managed by such manager is associated > to a {@link org.xwiki.model.DocumentReference}. The > > + * referred document contains XObjects that define the role, hint > and behavior (method bodies) of the component. This > > + * document may also define requirements (other components to be > binded in the method bodies execution context) and > > + * possible extra interfaces (for example to implement {@link > org.xwiki.component.phase.Initializable}). > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +@ComponentRole > > +public interface WikiComponentManager > > +{ > > + > > + /** > > + * Registers the passed component against the underlying > component repository. > > + * > > + * @param component the component to register > > + * @throws WikiComponentException when failed to register the > component against the CM > > + */ > > + void registerWikiComponent(WikiComponent component) throws > WikiComponentException; > > + > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultMethodOutputHandler.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultMethodOutputHandler.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultMethodOutputHandler.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,54 @@ > > [...] > > > +package org.xwiki.component.wiki.internal; > > + > > +import org.xwiki.component.wiki.MethodOutputHandler; > > + > > +/** > > + * Default method output handler. > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +public class DefaultMethodOutputHandler implements > MethodOutputHandler > > +{ > > + > > + /** > > + * The stored return value. > > + */ > > + private Object returnValue; > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public void returnValue(Object value) > > + { > > + this.returnValue = value; > > + } > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public Object getReturnValue() > > + { > > + return this.returnValue; > > + } > > + > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultWikiComponent.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultWikiComponent.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultWikiComponent.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,139 @@ > > [...] > > > +package org.xwiki.component.wiki.internal; > > + > > +import java.util.HashMap; > > +import java.util.Map; > > + > > +import org.xwiki.component.wiki.WikiComponent; > > +import org.xwiki.model.reference.DocumentReference; > > + > > +/** > > + * Default implementation of a wiki component definition. > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +public class DefaultWikiComponent implements WikiComponent > > +{ > > + /** > > + * @see {@link #getDocumentReference()} > > + */ > > + private DocumentReference documentReference; > > + > > + /** > > + * @see {@link #getHandledMethods()} > > + */ > > + private Map<String, String> handledMethods = new > HashMap<String, String>(); > > + > > + /** > > + * @see {@link #getRole()} > > + */ > > + private Class< ? > role; > > + > > + /** > > + * @see {@link #getRoleHint()} > > + */ > > + private String roleHint; > > + > > + /** > > + * @see {@link #getImplementedInterfaces()} > > + */ > > + private Class< ? >[] implementedInterfaces = new Class< ? > >[]{}; > > + > > + /** > > + * Constructor of this component. > > + * > > + * @param reference the document holding the component > definition > > + * @param role the role implemented > > + * @param roleHint the role hint for this role implementation > > + */ > > + public DefaultWikiComponent(DocumentReference reference, Class< > ? > role, String roleHint) > > + { > > + this.documentReference = reference; > > + this.role = role; > > + this.roleHint = roleHint; > > + } > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public DocumentReference getDocumentReference() > > + { > > + return this.documentReference; > > + } > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public Map<String, String> getHandledMethods() > > + { > > + return this.handledMethods; > > + } > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public Class< ? > getRole() > > + { > > + return this.role; > > + } > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public String getRoleHint() > > + { > > + return this.roleHint; > > + } > > + > > + /** > > + * {@inheritDoc} > > + */ > > + public Class< ? >[] getImplementedInterfaces() > > + { > > + return this.implementedInterfaces; > > + } > > + > > + /** > > + * Sets the handled method. > > + * > > + * @see {@link #getHandledMethods()} > > + * > > + * @param methods the methods this component will handle > > + */ > > + public void setHandledMethods(Map<String, String> methods) > > + { > > + this.handledMethods = methods; > > + } > > + > > + /** > > + * Sets the implemented interfaces. > > + * > > + * @see {@link #getImplementedInterfaces()} > > + * > > + * @param interfaces the interfaces this component will > implement. > > + */ > > + public void setImplementedInterfaces(Class< ? >[] interfaces) > > + { > > + this.implementedInterfaces = interfaces; > > + } > > + > > +} > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultWikiComponentManager.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultWikiComponentManager.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/main/java/org/xwiki/component/wiki/internal/DefaultWikiComponentManager.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,130 @@ > > [...] > > > + > > +/** > > + * Default implementation of {@link WikiComponentManager}. Creates > proxy objects which method invocation handler keeps a > > + * reference on a set of declared method and associated wiki > content to "execute". > > + * > > + * @since 2.4-M2 > > + * @version $Id$ > > + */ > > +@Component > > +public class DefaultWikiComponentManager extends AbstractLogEnabled > implements WikiComponentManager > > +{ > > + /** > > + * Component manager against which wiki component will be > registered. > > + */ > > + @Requirement > > + private ComponentManager mainComponentManager; > > + > > + /** > > + * {@inheritDoc} > > + */ > > + @SuppressWarnings("unchecked") > > + public void registerWikiComponent(WikiComponent component) > throws WikiComponentException > > + { > > + try { > > + // Get the component role interface > > + Class< ? > role = component.getRole(); > > + > > + // Create the method invocation handler of the proxy > > + InvocationHandler handler = > > + new > WikiComponentInvocationHandler(component.getDocumentReference(), > component.getHandledMethods(), > > + mainComponentManager); > > + > > + // Prepare array of all interfaces the component > implementation declares, that is the interface declared as > > + // component role > > + // plus possible extra other interfaces > > + Class< ? >[] allImplementedInterfaces = new Class< ? > >[component.getImplementedInterfaces().length + 1]; > > + System.arraycopy(component.getImplementedInterfaces(), > 0, allImplementedInterfaces, 0, component > > + .getImplementedInterfaces().length); > > + > allImplementedInterfaces[component.getImplementedInterfaces().length] > = role; > > + > > + // Create the component instance and its descritor > > + Object instance = > Proxy.newProxyInstance(role.getClassLoader(), > allImplementedInterfaces, handler); > > + ComponentDescriptor componentDescriptor = > this.createComponentDescriptor(role, component.getRoleHint()); > > + > > + // Since we are responsible to create the component > instance, > > + // we also are responsible of its initialization (if > needed) > > + if (this.isInitializable(allImplementedInterfaces)) { > > + try { > > + ((Initializable) instance).initialize(); > > + } catch (InitializationException e) { > > + getLogger().error("Failed to initialize wiki > component", e); > > + } > > + } > > + > > + // Finally, register the component against the CM > > + > this.mainComponentManager.registerComponent(componentDescriptor, > role.cast(instance)); > > + } catch (ComponentRepositoryException e) { > > + throw new WikiComponentException("Failed to register > wiki component against component repository", e); > > + } > > + } > > + > > + /** > > + * Helper method to create a component descriptor from role and > hint. > > + * > > + * @param role the component role of the descriptor to create > > + * @param roleHint the hint of the implementation for the > descriptor to create > > + * @return the constructed {@link ComponentDescriptor} > > + */ > > + @SuppressWarnings("unchecked") > > + private ComponentDescriptor createComponentDescriptor(Class > role, String roleHint) > > + { > > + DefaultComponentDescriptor cd = new > DefaultComponentDescriptor(); > > + cd.setRole(role); > > + cd.setRoleHint(roleHint); > > + return cd; > > + } > > + > > + /** > > + * Helper method that checks if at least one of an array of > interfaces is the {@link Initializable} class. > > + * > > + * @param interfaces the array of interfaces to test > > + * @return true if at least one of the passed interfaces is the > is the {@link Initializable} class. > > + */ > > + private boolean isInitializable(Class< ? >[] interfaces) > > + { > > + for (Class< ? > iface : interfaces) { > > + if (Initializable.class.equals(iface)) { > > + return true; > > + } > > + } > > + return false; > > + } > > +} > > > > [...] > > > > > Added: > contrib/sandbox/xwiki-wiki-components/src/test/java/org/xwiki/component/wiki/WikiComponentManagerTest.java > > =================================================================== > > --- > contrib/sandbox/xwiki-wiki-components/src/test/java/org/xwiki/component/wiki/WikiComponentManagerTest.java > (rev 0) > > +++ > contrib/sandbox/xwiki-wiki-components/src/test/java/org/xwiki/component/wiki/WikiComponentManagerTest.java > 2010-05-15 15:08:25 UTC (rev 28878) > > @@ -0,0 +1,34 @@ > > [...] > > > + > > +public class WikiComponentManagerTest extends > AbstractComponentTestCase > > +{ > > + private WikiComponentManager manager; > > + > > + @Before > > + public void setUp() throws Exception > > + { > > + manager = > getComponentManager().lookup(WikiComponentManager.class); > > + } > > + > > + @Test > > + public void testRegisterWikiComponent() throws Exception > > + { > > + DocumentReference pseudoReference = new > DocumentReference("somewiki","XWiki","MyComponent"); > > + > > + DefaultWikiComponent wc = new > DefaultWikiComponent(pseudoReference, WikiComponentManager.class, > "test"); > > + > > + this.manager.registerWikiComponent(wc); > > + > > + WikiComponentManager registered = > this.getComponentManager().lookup(WikiComponentManager.class, "test"); > > + > > + Assert.assertNotNull(registered); > > + } > > +} > > > > -- > Thomas Mortagne > _______________________________________________ > devs mailing list > [email protected] > http://lists.xwiki.org/mailman/listinfo/devs
5947
Age (days ago)
5947
Last active (days ago)
0 comments
1 participants
participants (1)
-
Jerome Velociter