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
devs(a)xwiki.org
http://lists.xwiki.org/mailman/listinfo/devs