Ralph,

Can you provide Javdoc on ScriptRef that explains the difference
with ScriptFile?

Gary
---------- Forwarded message ----------
From: <[email protected]>
Date: Tue, Oct 6, 2015 at 11:12 PM
Subject: logging-log4j2 git commit: LOG4J2-1136 - Add Script references
To: [email protected]


Repository: logging-log4j2
Updated Branches:
  refs/heads/LOG4J2-1136 6054027be -> 8aae118a0


LOG4J2-1136 - Add Script references


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

Branch: refs/heads/LOG4J2-1136
Commit: 8aae118a0bd2588fcfe1019bfcefd8753de03893
Parents: 6054027
Author: Ralph Goers <[email protected]>
Authored: Tue Oct 6 23:12:32 2015 -0700
Committer: Ralph Goers <[email protected]>
Committed: Tue Oct 6 23:12:32 2015 -0700

----------------------------------------------------------------------
 .../core/config/AbstractConfiguration.java      |  7 ++-
 .../log4j/core/config/ScriptsPlugin.java        | 43 ++++++++++++++++
 .../logging/log4j/core/filter/ScriptFilter.java | 26 +++++++---
 .../core/layout/ScriptPatternSelector.java      | 31 +++++++++---
 .../logging/log4j/core/script/ScriptFile.java   | 10 ++--
 .../log4j/core/script/ScriptManager.java        |  5 ++
 .../logging/log4j/core/script/ScriptRef.java    | 53 ++++++++++++++++++++
 .../log4j/core/filter/ScriptRefFilterTest.java  | 37 ++++++++++++++
 .../test/resources/log4j-scriptRef-filters.xml  | 47 +++++++++++++++++
 9 files changed, 242 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8aae118a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
index 5481e55..e7d9fdd 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
@@ -56,6 +56,7 @@ import org.apache.logging.log4j.core.lookup.MapLookup;
 import org.apache.logging.log4j.core.lookup.StrLookup;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.core.net.Advertiser;
+import org.apache.logging.log4j.core.script.AbstractScript;
 import org.apache.logging.log4j.core.script.ScriptManager;
 import org.apache.logging.log4j.core.selector.ContextSelector;
 import org.apache.logging.log4j.core.util.Constants;
@@ -426,7 +427,11 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
             if (child.getObject() == null) {
                 continue;
             }
-            if (child.getName().equalsIgnoreCase("Appenders")) {
+            if (child.getName().equalsIgnoreCase("Scripts")) {
+                for (AbstractScript script :
child.getObject(AbstractScript[].class)) {
+                   scriptManager.addScript(script);
+                }
+            } else if (child.getName().equalsIgnoreCase("Appenders")) {
                 appenders = child.getObject();
             } else if (child.isInstanceOf(Filter.class)) {
                 addFilter(child.getObject(Filter.class));

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8aae118a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java
new file mode 100644
index 0000000..bd01dbe
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.config;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.script.AbstractScript;
+
+/**
+ * A  container of Scripts.
+ */
+@Plugin(name = "scripts", category = "Core")
+public final class ScriptsPlugin {
+
+    private ScriptsPlugin() {
+    }
+
+    /**
+     * Return the array of scripts
+     * @param scripts An array of Scripts.
+     * @return The array of AbstractScripts.
+     */
+    @PluginFactory
+    public static AbstractScript[] createScripts(@PluginElement("Scripts")
final AbstractScript[] scripts) {
+
+        return scripts;
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8aae118a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
index 9e8acca..549437a 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
@@ -33,9 +33,11 @@ import
org.apache.logging.log4j.core.config.plugins.PluginFactory;
 import org.apache.logging.log4j.core.script.AbstractScript;
 import org.apache.logging.log4j.core.script.Script;
 import org.apache.logging.log4j.core.script.ScriptFile;
+import org.apache.logging.log4j.core.script.ScriptRef;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ObjectMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.status.StatusLogger;

 /**
  * Returns the onMatch result if the script returns True and returns the
onMisMatch value otherwise.
@@ -44,6 +46,7 @@ import org.apache.logging.log4j.message.SimpleMessage;
 public final class ScriptFilter extends AbstractFilter {

     private static final long serialVersionUID = 1L;
+    private static org.apache.logging.log4j.Logger logger =
StatusLogger.getLogger();

     private final AbstractScript script;
     private final Configuration configuration;
@@ -53,7 +56,9 @@ public final class ScriptFilter extends AbstractFilter {
         super(onMatch, onMismatch);
         this.script = script;
         this.configuration = configuration;
-        configuration.getScriptManager().addScript(script);
+        if (!(script instanceof ScriptRef)) {
+            configuration.getScriptManager().addScript(script);
+        }
     }

     @Override
@@ -134,19 +139,28 @@ public final class ScriptFilter extends
AbstractFilter {
     public static ScriptFilter createFilter(
             @PluginElement("Script") final Script script,
             @PluginElement("ScriptFile") final ScriptFile scriptFile,
+            @PluginElement("ScriptRef") final ScriptRef scriptRef,
             @PluginAttribute("onMatch") final Result match,
             @PluginAttribute("onMismatch") final Result mismatch,
             @PluginConfiguration final Configuration configuration) {

-        if (script == null && scriptFile == null) {
-            LOGGER.error("A Script or ScriptFile element must be provided
for this ScriptFilter");
+        if (script == null && scriptFile == null && scriptRef == null) {
+            LOGGER.error("A Script, ScriptFile or ScriptRef element must
be provided for this ScriptFilter");
             return null;
         }
-        if (script != null && scriptFile != null) {
-            LOGGER.error("One of a Script or ScriptFile element must be
provided for this ScriptFilter, but not both");
+        if ((script != null && (scriptFile != null || scriptRef != null))
|| (scriptFile != null && scriptRef != null) ) {
+            LOGGER.error("Only one Script, ScriptFile or ScriptRef element
can be provided for this ScriptFilter");
             return null;
         }
-        return new ScriptFilter(script != null ? script : scriptFile,
configuration, match, mismatch);
+        if (scriptRef != null) {
+            if
(configuration.getScriptManager().getScript(scriptRef.getName()) == null) {
+                logger.error("No script with name {} has been declared.",
script.getName());
+                return null;
+            }
+        }
+
+        return new ScriptFilter(script != null ? script : scriptFile !=
null ? scriptFile : scriptRef, configuration,
+                match, mismatch);
     }

 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8aae118a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java
index e9e77b2..6edb9f3 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java
@@ -28,7 +28,10 @@ import
org.apache.logging.log4j.core.config.plugins.PluginElement;
 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
 import org.apache.logging.log4j.core.pattern.PatternFormatter;
 import org.apache.logging.log4j.core.pattern.PatternParser;
+import org.apache.logging.log4j.core.script.AbstractScript;
 import org.apache.logging.log4j.core.script.Script;
+import org.apache.logging.log4j.core.script.ScriptFile;
+import org.apache.logging.log4j.core.script.ScriptRef;
 import org.apache.logging.log4j.status.StatusLogger;

 import javax.script.SimpleBindings;
@@ -51,16 +54,18 @@ public class ScriptPatternSelector implements
PatternSelector {
     private final String defaultPattern;

     private static Logger LOGGER = StatusLogger.getLogger();
-    private final Script script;
+    private final AbstractScript script;
     private final Configuration configuration;


-    public ScriptPatternSelector(final Script script, final PatternMatch[]
properties, final String defaultPattern,
+    public ScriptPatternSelector(final AbstractScript script, final
PatternMatch[] properties, final String defaultPattern,
                                  final boolean alwaysWriteExceptions,
final boolean noConsoleNoAnsi,
                                  final Configuration config) {
         this.script = script;
         this.configuration = config;
-        config.getScriptManager().addScript(script);
+        if (!(script instanceof ScriptRef)) {
+            config.getScriptManager().addScript(script);
+        }
         final PatternParser parser =
PatternLayout.createPatternParser(config);
         for (PatternMatch property : properties) {
             try {
@@ -98,23 +103,35 @@ public class ScriptPatternSelector implements
PatternSelector {

     @PluginFactory
     public static ScriptPatternSelector
createSelector(@PluginElement("Script") Script script,
+
 @PluginElement("ScriptFile") ScriptFile scriptFile,
+
 @PluginElement("ScriptRef") ScriptRef scriptRef,

@PluginElement("PatternMatch") final PatternMatch[] properties,

@PluginAttribute("defaultPattern") String defaultPattern,

@PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true)
final boolean alwaysWriteExceptions,

@PluginAttribute(value = "noConsoleNoAnsi", defaultBoolean = false) final
boolean noConsoleNoAnsi,

@PluginConfiguration final Configuration config) {
-        if (script == null) {
-            LOGGER.error("No script provided");
+        if (script == null && scriptFile == null && scriptRef == null) {
+            LOGGER.error("A Script, ScriptFile or ScriptRef element must
be provided for this ScriptFilter");
+            return null;
+        }
+        if ((script != null && (scriptFile != null || scriptRef != null))
|| (scriptFile != null && scriptRef != null) ) {
+            LOGGER.error("Only one Script, ScriptFile or ScriptRef element
can be provided for this ScriptFilter");
             return null;
         }
+        if (scriptRef != null) {
+            if (config.getScriptManager().getScript(script.getName()) ==
null) {
+                LOGGER.error("No script with name {} has been declared.",
script.getName());
+                return null;
+            }
+        }
         if (defaultPattern == null) {
             defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
         }
         if (properties == null || properties.length == 0) {
             LOGGER.warn("No marker patterns were provided");
         }
-        return new ScriptPatternSelector(script, properties,
defaultPattern, alwaysWriteExceptions,
-                noConsoleNoAnsi, config);
+        return new ScriptPatternSelector(script != null ? script :
scriptFile != null ? scriptFile : scriptRef,
+                properties, defaultPattern, alwaysWriteExceptions,
noConsoleNoAnsi, config);
     }

     @Override

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8aae118a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
index f1bf300..d4c2615 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
@@ -48,8 +48,8 @@ public class ScriptFile extends AbstractScript {
     private final boolean isWatched;


-    public ScriptFile(Path filePath, String language, boolean isWatched,
String scriptText) {
-        super(filePath.toString(), language, scriptText);
+    public ScriptFile(String name, Path filePath, String language, boolean
isWatched, String scriptText) {
+        super(name, language, scriptText);
         this.filePath = filePath;
         this.isWatched = isWatched;
     }
@@ -65,6 +65,7 @@ public class ScriptFile extends AbstractScript {
     @PluginFactory
     public static ScriptFile createScript(
             // @formatter:off
+            @PluginAttribute("name") String name,
             @PluginAttribute("language") String language,
             @PluginAttribute("path") final String filePathOrUri,
             @PluginAttribute("isWatched") final Boolean isWatched,
@@ -74,6 +75,9 @@ public class ScriptFile extends AbstractScript {
             logger.error("No script path provided for ScriptFile");
             return null;
         }
+        if (name == null) {
+            name = filePathOrUri;
+        }
         final URI uri = NetUtils.toURI(filePathOrUri);
         final File file = FileUtils.fileFromUri(uri);
         if (language == null && file != null) {
@@ -105,6 +109,6 @@ public class ScriptFile extends AbstractScript {
             logger.error("Unable to convert {} to a Path", uri.toString());
             return null;
         }
-        return new ScriptFile(path, language, isWatched == null ?
Boolean.FALSE : isWatched, scriptText);
+        return new ScriptFile(name, path, language, isWatched == null ?
Boolean.FALSE : isWatched, scriptText);
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8aae118a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
index b7b701a..9a0e998 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
@@ -111,6 +111,11 @@ public class ScriptManager implements FileWatcher {
         }
     }

+    public AbstractScript getScript(final String name) {
+        ScriptRunner runner = scripts.get(name);
+        return runner != null ? runner.getScript() : null;
+    }
+
     @Override
     public void fileModified(final File file) {
         ScriptRunner runner = scripts.get(file.toString());

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8aae118a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptRef.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptRef.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptRef.java
new file mode 100644
index 0000000..94075fe
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptRef.java
@@ -0,0 +1,53 @@
+package org.apache.logging.log4j.core.script;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.Node;
+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.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.config.plugins.PluginValue;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Container for the language and body of a script.
+ */
+@Plugin(name = "ScriptRef", category = Node.CATEGORY, printObject = true)
+public class ScriptRef extends AbstractScript {
+
+    private static final Logger logger = StatusLogger.getLogger();
+    private final ScriptManager scriptManager;
+
+    public ScriptRef(String name, ScriptManager scriptManager) {
+        super(name, null, null);
+        this.scriptManager = scriptManager;
+    }
+
+    @Override
+    public String getLanguage() {
+        AbstractScript script = this.scriptManager.getScript(getName());
+        return script != null ? script.getLanguage() : null;
+    }
+
+
+    @Override
+    public String getScriptText() {
+        AbstractScript script = this.scriptManager.getScript(getName());
+        return script != null ? script.getScriptText() : null;
+    }
+
+    @PluginFactory
+    public static ScriptRef createReference(
+            // @formatter:off
+            @PluginAttribute("ref") final String name,
+            @PluginConfiguration Configuration configuration) {
+            // @formatter:on
+        if (name == null) {
+            logger.error("No script name provided");
+            return null;
+        }
+        return new ScriptRef(name, configuration.getScriptManager());
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8aae118a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptRefFilterTest.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptRefFilterTest.java
b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptRefFilterTest.java
new file mode 100644
index 0000000..f95536d
--- /dev/null
+++
b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptRefFilterTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.filter;
+
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.ClassRule;
+
+/**
+ *
+ */
+public class ScriptRefFilterTest extends AbstractScriptFilterTest {
+
+    private static final String CONFIG = "log4j-scriptRef-filters.xml";
+
+    @ClassRule
+    public static LoggerContextRule context = new
LoggerContextRule(CONFIG);
+
+    @Override
+    public LoggerContextRule getContext() {
+        return context;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8aae118a/log4j-core/src/test/resources/log4j-scriptRef-filters.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-scriptRef-filters.xml
b/log4j-core/src/test/resources/log4j-scriptRef-filters.xml
new file mode 100644
index 0000000..1cef279
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-scriptRef-filters.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements. See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License. You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<Configuration status="ERROR">
+<Scripts>
+  <ScriptFile name="filter.js" language="JavaScript"
path="src/test/resources/scripts/filter.js" charset="UTF-8" />
+  <ScriptFile name="filter.groovy"
path="src/test/resources/scripts/filter.groovy" charset="UTF-8" />
+</Scripts>
+<Appenders>
+  <List name="List">
+    <PatternLayout pattern="[%-5level] %c{1.} %msg%n"/>
+  </List>
+</Appenders>
+<Loggers>
+  <Logger name="TestJavaScriptFilter" level="trace" additivity="false">
+    <AppenderRef ref="List">
+      <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
+        <ScriptRef ref="filter.js" />
+      </ScriptFilter>
+    </AppenderRef>
+  </Logger>
+  <Logger name="TestGroovyFilter" level="trace" additivity="false">
+    <AppenderRef ref="List">
+      <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
+        <ScriptRef ref="filter.groovy" />
+      </ScriptFilter>
+    </AppenderRef>
+  </Logger>
+  <Root level="trace">
+    <AppenderRef ref="List" />
+  </Root>
+</Loggers>
+</Configuration>
\ No newline at end of file




-- 
E-Mail: [email protected] | [email protected]
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Reply via email to