Changes for page Properties Module

Last modified by Vincent Massol on 2020/05/13 18:11

From version < 33.2 >
edited by Vincent Massol
on 2019/01/29 11:49
To version < 34.1 >
edited by Adel Atallah
on 2019/02/09 16:45
< >
Change comment: There is no comment for this version

Summary

Details

Page properties
Author
... ... @@ -1,1 +1,1 @@
1 -xwiki:XWiki.VincentMassol
1 +xwiki:XWiki.atallahade
ExtensionCode.ExtensionClass[0]
Description
... ... @@ -58,15 +58,14 @@
58 58   };
59 59  
60 60   /**
61 - * The name of the document to include.
61 + * The entity reference to include.
62 62   */
63 - private String document;
63 + private String reference;
64 64  
65 65   /**
66 - * Defines whether the included page is executed in its separated execution context or whether it's executed in the
67 - * context of the current page.
66 + * Type of the entity to include.
68 68   */
69 - private Context context;
68 + private EntityType type = EntityType.DOCUMENT;
70 70  
71 71   private String hiddenProperty;
72 72  
... ... @@ -73,24 +73,54 @@
73 73   public int publicField;
74 74  
75 75   /**
76 - * @param document the name of the document to include.
75 + * @param reference the reference of the resource to include
77 77   */
78 - @PropertyMandatory
79 - @PropertyDescription("the name of the document to include")
80 - public void setDocument(String document)
77 + @PropertyDescription("the reference of the resource to display")
78 + @PropertyGroup("stringReference")
79 + @PropertyFeature("reference")
80 + public void setReference(String reference)
81 81   {
82 - this.document = document;
82 + this.reference = reference;
83 83   }
84 84  
85 85   /**
86 - * @return the name of the document to include.
86 + * @return the reference of the resource to include
87 87   */
88 - public String getDocument()
88 + public String getReference()
89 89   {
90 - return this.document;
90 + return this.reference;
91 91   }
92 92  
93 93   /**
94 + * @return the type of the reference
95 + */
96 + @PropertyDescription("the type of the reference")
97 + @PropertyGroup("stringReference")
98 + public EntityType getType()
99 + {
100 + return this.type;
101 + }
102 +
103 + /**
104 + * @param type the type of the reference
105 + */
106 + public void setType(EntityType type)
107 + {
108 + this.type = type;
109 + }
110 +
111 + /**
112 + * @param page the reference of the page to include
113 + */
114 + @PropertyDescription("The reference of the page to include")
115 + @PropertyFeature("reference")
116 + public void setPage(String page)
117 + {
118 + this.reference = page;
119 + this.type = EntityType.PAGE;
120 + }
121 +
122 + /**
94 94   * @param context defines whether the included page is executed in its separated execution context or whether it's
95 95   * executed in the context of the current page.
96 96   */
... ... @@ -111,10 +111,10 @@
111 111   }
112 112  
113 113   @PropertyHidden
114 - public void setHiddenProperty(String hiddenProperty)
115 - {
116 - this.hiddenProperty = hiddenProperty;
117 - }
143 + public void setHiddenProperty(String hiddenProperty)
144 + {
145 + this.hiddenProperty = hiddenProperty;
146 + }
118 118  
119 119   public String getHiddenProperty()
120 120   {
... ... @@ -126,7 +126,7 @@
126 126  {{code language="java"}}
127 127  Map<String, String> values = new HashMap<String, String>();
128 128  
129 -values.put("document", "Space.Page");
158 +values.put("reference", "Space.Page");
130 130  values.put("context", "new");
131 131  values.put("publicField", "42");
132 132  values.put("hiddenProperty", "hiddenPropertyvalue");
... ... @@ -136,7 +136,7 @@
136 136  BeanManager beanManager = componentManager.getInstance(BeanManager.class);
137 137  beanManager.populate(bean, values);
138 138  
139 -assertEquals("Space.Page", bean.getDocument());
168 +assertEquals("Space.Page", bean.getReference());
140 140  assertEquals(Context.NEW, bean.getContext());
141 141  assertEquals(42, bean.publicField);
142 142  assertNull(bean.getHiddenProperty());
cogProvides APIs to handle Java Beans properties and conversion between Java type and strings
TypeJAR
Developed by

XWiki Development Team

Rating
Rate!
0 Votes
LicenseGNU Lesser General Public License 2.1
Bundled With

XWiki Enterprise, XWiki Enterprise Manager

Description

We previously used BeanUtils, for macros for example, but several needs were not covered by BeanUtils, so we started our own populate methods and the needed api around it. Basically the differences with BeanUtils are:

  • We needed to support any case for the properties names for macros
  • We needed to be able to inject custom fields in a bean for Wiki macros
  • We needed to support some annotation tweak in the bean description to make it easier to provide useful information, to the WYSIWYG for example, or to modify the behavior of the populate process, like making a property mandatory or hidden
  • BeanUtils does not support public fields (the Groovy way)
  • It's possible to implement a Converter component which can access anything an XWiki component can access so that you would be able to convert types like String into Document, etc.
  • Users are using only api/interface and any implementation component can be done for it.

Note that it's not a complete rewrite of BeanUtils. Xwiki-properties still use useful tools of javax.beans and ConvertUtils. The only things that have been completely rewritten are the populate method and the BeanInfo, that has been extended as a BeanDescriptor (to contain public fields, text description, etc.).

BeanManager

Populate a Java Bean

To populate a Map into a java bean use #populate(Object, Map).

By default a bean is parsed as follows:

  • java.beansIntrospector#getBeanInfo(Class) is used to get standard BeanInfo properties which are based on getters and setters names. See BeanInfo javadoc.
  • Public fields produce also properties
  • Any case is supported for properties names when populating
  • Each property is converted on the fly from the provided value to the property JAVA type using ConverterManager.

Then it's possible to tweak this behavior with annotations:

  • org.xwiki.properties.annotation.PropertyDescription: a text describing the property
  • org.xwiki.properties.annotation.PropertyHidden: indicates that the property should not be taken into account by BeanManager
  • org.xwiki.properties.annotation.PropertyMandatory: indicates that an error should be generated when no value is provided in the Map for this property when populating.
  • org.xwiki.properties.annotation.PropertyName: a text with the display name of the field
  • since 6.3 org.xwiki.properties.annotation.PropertyId: overwrite the property identifier
  • since 10.10 org.xwiki.properties.annotation.PropertyAdvanced: indicates that the property is to be used by more advanced users
  • since 10.11 org.xwiki.properties.annotation.PropertyGroup: used to group properties together
  • since 10.11 org.xwiki.properties.annotation.PropertyFeature: binds a property to a feature (two properties can be used for the same feature)
  • since 11.0 org.xwiki.properties.annotation.PropertyDisplayType: overrides the type of the property for display only (e.g. WYSIWYG)

Finally default implementation of BeanManager support JSR 303 (Bean Validation) if an implementation can be found.

Example

/**
 * Parameters for the {@link org.xwiki.rendering.internal.macro.include.IncludeMacro} Macro.
 */

public class IncludeMacroParameters
{
   public enum Context
   {
       /**
         * Macro executed in its own context.
         */

        NEW,

       /**
         * Macro executed in the context of the current page.
         */

        CURRENT;
   };

   /**
     * The name of the document to include.
     */

   private String document;

   /**
     * Defines whether the included page is executed in its separated execution context or whether it's executed in the
     * context of the current page.
     */

   private Context context;

   private String hiddenProperty;

   public int publicField;

   /**
     * @param document the name of the document to include.
     */

   @PropertyMandatory
   @PropertyDescription("the name of the document to include")
   public void setDocument(String document)
   {
       this.document = document;
   }

   /**
     * @return the name of the document to include.
     */

   public String getDocument()
   {
       return this.document;
   }

   /**
     * @param context defines whether the included page is executed in its separated execution context or whether it's
     *            executed in the context of the current page.
     */

   @PropertyDescription("defines whether the included page is executed in its separated execution context"
       + " or whether it's executed in the context of the current page")
   public void setContext(Context context)
   {
       this.context = context;
   }

   /**
     * @return defines whether the included page is executed in its separated execution context or whether it's executed
     *         in the context of the current page.
     */

   public Context getContext()
   {
       return this.context;
   }

   @PropertyHidden
   public void setHiddenProperty(String hiddenProperty)
   {
     this.hiddenProperty = hiddenProperty;
   }

   public String getHiddenProperty()
   {
     return this.hiddenProperty;
   }
}
/**
 * Parameters for the {@link org.xwiki.rendering.internal.macro.include.IncludeMacro} Macro.
 */

public class IncludeMacroParameters
{
   public enum Context
   {
       /**
         * Macro executed in its own context.
         */

        NEW,

       /**
         * Macro executed in the context of the current page.
         */

        CURRENT;
   };

   /**
     * The entity reference to include.
     */

   private String reference;

   /**
     * Type of the entity to include.
     */

   private EntityType type = EntityType.DOCUMENT;

   private String hiddenProperty;

   public int publicField;

   /**
     * @param reference the reference of the resource to include
     */

   @PropertyDescription("the reference of the resource to display")
   @PropertyGroup("stringReference")
   @PropertyFeature("reference")
   public void setReference(String reference)
   {
       this.reference = reference;
   }

   /**
     * @return the reference of the resource to include
     */

   public String getReference()
   {
       return this.reference;
   }

   /**
     * @return the type of the reference
     */

   @PropertyDescription("the type of the reference")
   @PropertyGroup("stringReference")
   public EntityType getType()
   {
       return this.type;
   }

   /**
     * @param type the type of the reference
     */

   public void setType(EntityType type)
   {
       this.type = type;
   }

   /**
     * @param page the reference of the page to include
     */

   @PropertyDescription("The reference of the page to include")
   @PropertyFeature("reference")
   public void setPage(String page)
   {
       this.reference = page;
       this.type = EntityType.PAGE;
   }

   /**
     * @param context defines whether the included page is executed in its separated execution context or whether it's
     *            executed in the context of the current page.
     */

   @PropertyDescription("defines whether the included page is executed in its separated execution context"
       + " or whether it's executed in the context of the current page")
   public void setContext(Context context)
   {
       this.context = context;
   }

   /**
     * @return defines whether the included page is executed in its separated execution context or whether it's executed
     *         in the context of the current page.
     */

   public Context getContext()
   {
       return this.context;
   }

   @PropertyHidden
     public void setHiddenProperty(String hiddenProperty)
     {
       this.hiddenProperty = hiddenProperty;
     }

   public String getHiddenProperty()
   {
     return this.hiddenProperty;
   }
}
Map<String, String> values = new HashMap<String, String>();

values.put("document", "Space.Page");
values.put("context", "new");
values.put("publicField", "42");
values.put("hiddenProperty", "hiddenPropertyvalue");

IncludeMacroParameters bean = new IncludeMacroParameters();

BeanManager beanManager = componentManager.getInstance(BeanManager.class);
beanManager.populate(bean, values);

assertEquals("Space.Page", bean.getDocument());
assertEquals(Context.NEW, bean.getContext());
assertEquals(42, bean.publicField);
assertNull(bean.getHiddenProperty());
Map<String, String> values = new HashMap<String, String>();

values.put("reference", "Space.Page");
values.put("context", "new");
values.put("publicField", "42");
values.put("hiddenProperty", "hiddenPropertyvalue");

IncludeMacroParameters bean = new IncludeMacroParameters();

BeanManager beanManager = componentManager.getInstance(BeanManager.class);
beanManager.populate(bean, values);

assertEquals("Space.Page", bean.getReference());
assertEquals(Context.NEW, bean.getContext());
assertEquals(42, bean.publicField);
assertNull(bean.getHiddenProperty());

Get bean descriptor

It's possible to ask BeanManager descriptor generated from a Java Bean for easy listing of properties, values, etc. For example this is used to fill ParameterDescriptors of most of the Java-based macros.

ConverterManager

The default implementation of ConverterManager embed converters for the following types:

  • Any Apache ConvertUtils conversion
  • Any java.lang.Enum child type
  • java.util.Collection
  • java.util.List
  • java.util.ArrayList
  • java.util.Set
  • java.util.LinkedHashSet
  • java.util.HashSet
  • java.awt.Color
  • java.util.Locale
  • org.xwiki.rendering.syntax.Syntax
  • org.xwiki.component.namespace.Namespace

Convert a value in a target Java type

To convert a value you can use the default implementation of org.xwiki.properties.ConverterManager component interface and its convert method.

ConverterManager converterManager = componentManager.getInstance(ConverterManager.class);
Integer intValue = converterManager.convert(Integer.class, "42");

Add a new Converter

There are two ways to add support for new types.

Converter component (Recommended)

You can add a new converter by implementing org.xwiki.properties.converter.Converter component interface. It's recommended to extend org.xwiki.properties.converter.AbstractConverter, which provides some helper to create a new Converter.

The minimum for an AbstractConverter child is:

  • Implements #convertToType(java.lang.Class, java.lang.Object)
  • Indicate the supported type:
    • [On 5.2 and more] Indicate the type as AbstractConverter generic type
    • [Before 5.2] Set the type name as component role hint
/**
 * Bean Utils converter that converts a value into an {@link Color} object.
 */

@Component
@Singleton
public class ColorConverter extends AbstractConverter<Color>
{
   /**
     * The String input supported by this {@link org.apache.commons.beanutils.Converter}.
     */

   private static final String USAGE = "Color value should be in the form of '#xxxxxx' or 'r,g,b'";

   @Override
   protected Color convertToType(Type type, Object value)
   {
        Color color = null;
       if (value != null) {
            color = parse(value.toString());
       }

       return color;
   }

   @Override
   protected String convertToString(Color value)
   {
        Color colorValue = (Color) value;

       return MessageFormat.format("{0},{1},{2}", colorValue.getRed(), colorValue.getGreen(), colorValue.getBlue());
   }

   /**
     * Parsers a String in the form "x, y, z" into an SWT RGB class.
     *
     * @param value the color as String
     * @return RGB
     */

   protected Color parseRGB(String value)
   {
        StringTokenizer items = new StringTokenizer(value, ",");

       try {
           int red = 0;
           if (items.hasMoreTokens()) {
                red = Integer.parseInt(items.nextToken().trim());
           }

           int green = 0;
           if (items.hasMoreTokens()) {
                green = Integer.parseInt(items.nextToken().trim());
           }

           int blue = 0;
           if (items.hasMoreTokens()) {
                blue = Integer.parseInt(items.nextToken().trim());
           }

           return new Color(red, green, blue);
       } catch (NumberFormatException ex) {
           throw new ConversionException(value + "is not a valid RGB colo", ex);
       }
   }

   /**
     * Parsers a String in the form "#xxxxxx" into an SWT RGB class.
     *
     * @param value the color as String
     * @return RGB
     */

   protected Color parseHtml(String value)
   {
       if (value.length() != 7) {
           throw new ConversionException(USAGE);
       }

       int colorValue = 0;
       try {
            colorValue = Integer.parseInt(value.substring(1), 16);
           return new Color(colorValue);
       } catch (NumberFormatException ex) {
           throw new ConversionException(value + "is not a valid Html color", ex);
       }
   }

   /**
     * Convert a String in {@link Color}.
     *
     * @param value the String to parse
     * @return the {@link Color}
     */

   public Color parse(String value)
   {
       if (value.length() <= 1) {
           throw new ConversionException(USAGE);
       }

       if (value.charAt(0) == '#') {
           return parseHtml(value);
       } else if (value.indexOf(',') != -1) {
           return parseRGB(value);
       } else {
           throw new ConversionException(USAGE);
       }
   }
}

Apache ConvertUtils Converter


When convert manager can't find a specific converter for a type it uses Apache ConvertUtils. See http://commons.apache.org/beanutils/ for more details.

Get Connected