On Sat, May 15, 2010 at 17:08, jvelociter
<contrib-notifications(a)xwiki.org> 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.
- 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.
+
+ /**
+ * 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