Add documentation about plugin builders compared to factories

LOG4J2-1411.


Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/912e3a8d
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/912e3a8d
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/912e3a8d

Branch: refs/heads/LOG4J-1181
Commit: 912e3a8d0ef4bf6db2802ac77bb43ab79c43f07b
Parents: 00b7de4
Author: Matt Sicker <[email protected]>
Authored: Sat Jun 4 19:18:52 2016 -0500
Committer: Matt Sicker <[email protected]>
Committed: Sat Jun 4 19:18:52 2016 -0500

----------------------------------------------------------------------
 .../log4j/test/appender/ListAppender.java       |  68 +++++++++--
 src/changes/changes.xml                         |   3 +
 src/site/site.xml                               |   1 +
 src/site/xdoc/manual/extending.xml              | 112 +++++++++++++++++++
 4 files changed, 174 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/912e3a8d/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
index cc897ea..30aa41b 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
@@ -28,11 +28,10 @@ import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.appender.AbstractAppender;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
 import 
org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.impl.MutableLogEvent;
 import org.apache.logging.log4j.core.layout.SerializedLayout;
 
@@ -175,16 +174,65 @@ public class ListAppender extends AbstractAppender {
         return Collections.unmodifiableList(data);
     }
 
-    @PluginFactory
-    public static ListAppender createAppender(
-            @PluginAttribute("name") @Required(message = "No name provided for 
ListAppender") final String name,
-            @PluginAttribute("entryPerNewLine") final boolean newLine,
-            @PluginAttribute("raw") final boolean raw,
-            @PluginElement("Layout") final Layout<? extends Serializable> 
layout,
-            @PluginElement("Filter") final Filter filter) {
+    public static ListAppender createAppender(final String name, final boolean 
newLine, final boolean raw,
+                                              final Layout<? extends 
Serializable> layout, final Filter filter) {
         return new ListAppender(name, filter, layout, newLine, raw);
     }
 
+    @PluginBuilderFactory
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    public static class Builder implements 
org.apache.logging.log4j.core.util.Builder<ListAppender> {
+
+        @PluginBuilderAttribute
+        @Required
+        private String name;
+
+        @PluginBuilderAttribute
+        private boolean entryPerNewLine;
+
+        @PluginBuilderAttribute
+        private boolean raw;
+
+        @PluginElement("Layout")
+        private Layout<? extends Serializable> layout;
+
+        @PluginElement("Filter")
+        private Filter filter;
+
+        public Builder setName(final String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder setEntryPerNewLine(final boolean entryPerNewLine) {
+            this.entryPerNewLine = entryPerNewLine;
+            return this;
+        }
+
+        public Builder setRaw(final boolean raw) {
+            this.raw = raw;
+            return this;
+        }
+
+        public Builder setLayout(final Layout<? extends Serializable> layout) {
+            this.layout = layout;
+            return this;
+        }
+
+        public Builder setFilter(final Filter filter) {
+            this.filter = filter;
+            return this;
+        }
+
+        @Override
+        public ListAppender build() {
+            return new ListAppender(name, filter, layout, entryPerNewLine, 
raw);
+        }
+    }
+
     /**
      * Gets the named ListAppender if it has been registered.
      *

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/912e3a8d/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 76dd0e1..8fe4113 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -58,6 +58,9 @@
       <action issue="LOG4J2-1399" dev="ggregory" type="update">
         Update Apache Commons CSV from 1.3 to 1.4.
       </action>
+      <action issue="LOG4J2-1411" dev="mattsicker" type="add">
+        Add documentation about plugin builders compared to factories.
+      </action>
     </release>
     <release version="2.6" date="2016-05-25" description="GA Release 2.6">
       <action issue="LOG4J2-1270" dev="rpopma" type="add">

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/912e3a8d/src/site/site.xml
----------------------------------------------------------------------
diff --git a/src/site/site.xml b/src/site/site.xml
index 85fc190..2aa49a0 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -201,6 +201,7 @@
         <item name="Appenders" href="/manual/extending.html#Appenders"/>
         <item name="Layouts" href="/manual/extending.html#Layouts"/>
         <item name="PatternConverters" 
href="/manual/extending.html#PatternConverters"/>
+        <item name="Plugin Builders" 
href="/manual/extending.html#Plugin_Builders"/>
         <item name="Custom Plugins" 
href="/manual/extending.html#Custom_Plugins"/>
       </item>
 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/912e3a8d/src/site/xdoc/manual/extending.xml
----------------------------------------------------------------------
diff --git a/src/site/xdoc/manual/extending.xml 
b/src/site/xdoc/manual/extending.xml
index 3088d82..5b3348a 100644
--- a/src/site/xdoc/manual/extending.xml
+++ b/src/site/xdoc/manual/extending.xml
@@ -392,6 +392,118 @@ public final class QueryConverter extends 
LogEventPatternConverter {
     }
 }</pre>
           </subsection>
+          <subsection name="Plugin Builders">
+            <p>
+              Some plugins take a lot of optional configuration options. When 
a plugin takes many options, it is more
+              maintainable to use a builder class rather than a factory method 
(see <i>Item 2: Consider a builder when
+              faced with many constructor parameters</i> in <i>Effective 
Java</i> by Joshua Bloch). There are some other
+              advantages to using an annotated builder class over an annotated 
factory method:
+            </p>
+            <ul>
+              <li>Attribute names don't need to be specified if they match the 
field name.</li>
+              <li>Default values can be specified in code rather than through 
an annotation (also allowing a
+              runtime-calculated default value which isn't allowed in 
annotations).</li>
+              <li>Adding new optional parameters doesn't require existing 
programmatic configuration to be refactored.</li>
+              <li>Easier to write unit tests using builders rather than 
factory methods with optional parameters.</li>
+              <li>Default values are specified via code rather than relying on 
reflection and injection, so they work
+              programmatically as well as in a configuration file.</li>
+            </ul>
+            <p>
+              Here is an example of a plugin factory from 
<code>ListAppender</code>:
+            </p>
+            <pre class="prettyprint linenums"><![CDATA[
+@PluginFactory
+public static ListAppender createAppender(
+        @PluginAttribute("name") @Required(message = "No name provided for 
ListAppender") final String name,
+        @PluginAttribute("entryPerNewLine") final boolean newLine,
+        @PluginAttribute("raw") final boolean raw,
+        @PluginElement("Layout") final Layout<? extends Serializable> layout,
+        @PluginElement("Filter") final Filter filter) {
+    return new ListAppender(name, filter, layout, newLine, raw);
+}
+            ]]></pre>
+            <p>
+              Here is that same factory using a builder pattern instead:
+            </p>
+            <pre class="prettyprint linenums"><![CDATA[
+@PluginBuilderFactory
+public static Builder newBuilder() {
+    return new Builder();
+}
+
+public static class Builder implements 
org.apache.logging.log4j.core.util.Builder<ListAppender> {
+
+    @PluginBuilderAttribute
+    @Required(message = "No name provided for ListAppender")
+    private String name;
+
+    @PluginBuilderAttribute
+    private boolean entryPerNewLine;
+
+    @PluginBuilderAttribute
+    private boolean raw;
+
+    @PluginElement("Layout")
+    private Layout<? extends Serializable> layout;
+
+    @PluginElement("Filter")
+    private Filter filter;
+
+    public Builder setName(final String name) {
+        this.name = name;
+        return this;
+    }
+
+    public Builder setEntryPerNewLine(final boolean entryPerNewLine) {
+        this.entryPerNewLine = entryPerNewLine;
+        return this;
+    }
+
+    public Builder setRaw(final boolean raw) {
+        this.raw = raw;
+        return this;
+    }
+
+    public Builder setLayout(final Layout<? extends Serializable> layout) {
+        this.layout = layout;
+        return this;
+    }
+
+    public Builder setFilter(final Filter filter) {
+        this.filter = filter;
+        return this;
+    }
+
+    @Override
+    public ListAppender build() {
+        return new ListAppender(name, filter, layout, entryPerNewLine, raw);
+    }
+}
+            ]]></pre>
+            <p>
+              The only difference in annotations is using 
<code>@PluginBuilderAttribute</code> instead of
+              <code>@PluginAttribute</code> so that default values and 
reflection can be used instead of specifying
+              them in the annotation. Either annotation can be used in a 
builder, but the former is better suited
+              for field injection while the latter is better suited for 
parameter injection. Otherwise, the same
+              annotations (<code>@PluginConfiguration</code>, 
<code>@PluginElement</code>, <code>@PluginNode</code>,
+              and <code>@PluginValue</code>) are all supported on fields. Note 
that a factory method is still required
+              to supply a builder, and this factory method should be annotated 
with <code>@PluginBuilderFactory</code>.
+              <!-- TODO: this will change with LOG4J2-1188 -->
+            </p>
+            <p>
+              When plugins are being constructed after a configuration has 
been parsed, a plugin builder will be used
+              if available, otherwise a plugin factory method will be used as 
a fallback. If a plugin contains neither
+              factory, then it cannot be used from a configuration file (it 
can still be used programmatically of
+              course).
+            </p>
+            <p>
+              Here is an example of using a plugin factory versus a plugin 
builder programmatically:
+            </p>
+            <pre class="prettyprint linenums"><![CDATA[
+ListAppender list1 = ListAppender.createAppender("List1", true, false, null, 
null);
+ListAppender list2 = 
ListAppender.newBuilder().setName("List1").setEntryPerNewLine(true).build();
+            ]]></pre>
+          </subsection>
           <subsection name="Custom Plugins">
             <p>See the <a href="plugins.html">Plugins</a> section of the 
manual.</p>
 <!-- TODO: some documentation here! -->

Reply via email to