Implementation of a MapFilter.

https://github.com/apache/logging-log4cxx/pull/24
https://github.com/apache/logging-log4cxx/pull/25
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 94fd8d3..2e64860 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -107,6 +107,7 @@
 			<action issue="LOGCXX-262" type="fix">socketappendertestcase and xmlsocketappendertestcase not run</action>
 			<action issue="LOGCXX-249" type="fix">Console appender crashes if layout is not set</action>
 
+			<action issue="24" system="GHPR" type="add">Implementation of map-based filter.</action>
 			<action issue="21" system="GHPR" type="add">Added support for building log4cxx as a statically linked library on Windows.</action>
 			<action issue="14" system="GHPR" type="add">Replaced ant build with cmake.</action>
 			<action issue="13" system="GHPR" type="add">JSONLayout</action>
diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt
index 321edf0..8f7d609 100644
--- a/src/main/cpp/CMakeLists.txt
+++ b/src/main/cpp/CMakeLists.txt
@@ -87,6 +87,7 @@
   logmanager.cpp
   logstream.cpp
   manualtriggeringpolicy.cpp
+  mapfilter.cpp
   mdc.cpp
   messagebuffer.cpp
   messagepatternconverter.cpp
diff --git a/src/main/cpp/Makefile.am b/src/main/cpp/Makefile.am
index 236a9a6..3643623 100644
--- a/src/main/cpp/Makefile.am
+++ b/src/main/cpp/Makefile.am
@@ -93,6 +93,7 @@
         logmanager.cpp \
         logstream.cpp \
         manualtriggeringpolicy.cpp \
+        mapfilter.cpp \
         messagebuffer.cpp \
         messagepatternconverter.cpp \
         methodlocationpatternconverter.cpp \
diff --git a/src/main/cpp/mapfilter.cpp b/src/main/cpp/mapfilter.cpp
new file mode 100644
index 0000000..09e38ca
--- /dev/null
+++ b/src/main/cpp/mapfilter.cpp
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+#include <log4cxx/logstring.h>
+#include <log4cxx/filter/mapfilter.h>
+#include <log4cxx/spi/loggingevent.h>
+#include <log4cxx/helpers/stringhelper.h>
+#include <log4cxx/helpers/optionconverter.h>
+
+using namespace log4cxx;
+using namespace log4cxx::filter;
+using namespace log4cxx::spi;
+using namespace log4cxx::helpers;
+
+IMPLEMENT_LOG4CXX_OBJECT(MapFilter)
+
+MapFilter::MapFilter() : acceptOnMatch(true), mustMatchAll(false)
+{
+
+}
+
+void MapFilter::setOption(	const LogString& option,
+							const LogString& value)
+{
+	if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("ACCEPTONMATCH"), LOG4CXX_STR("acceptonmatch")))
+	{
+		acceptOnMatch = OptionConverter::toBoolean(value, acceptOnMatch);
+	}
+	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("OPERATOR"), LOG4CXX_STR("operator")))
+	{
+		mustMatchAll = StringHelper::equalsIgnoreCase(value, LOG4CXX_STR("AND"), LOG4CXX_STR("and")) ? true : false;
+	}
+	else if (!option.empty() && !value.empty())
+	{
+		keyVals[option] = value;
+	}
+}
+
+Filter::FilterDecision MapFilter::decide(
+	const log4cxx::spi::LoggingEventPtr& event) const
+{
+	if (keyVals.empty())
+	{
+		return Filter::NEUTRAL;
+	}
+
+	bool matched = true;
+
+	for (KeyVals::const_iterator it = keyVals.begin(); it != keyVals.end(); ++it)
+	{
+		LogString curval;
+		event->getMDC(it->first, curval);
+
+		if (curval.empty() || curval != it->second)
+		{
+			matched = false;
+		}
+		else
+		{
+			matched = true;
+		}
+
+		if (mustMatchAll != matched)
+		{
+			break;
+		}
+	}
+
+	if (acceptOnMatch)
+	{
+		return matched ? Filter::ACCEPT : Filter::DENY;
+	}
+	else
+	{
+		return matched ? Filter::DENY : Filter::ACCEPT;
+	}
+}
diff --git a/src/main/include/log4cxx/filter/mapfilter.h b/src/main/include/log4cxx/filter/mapfilter.h
index d6db108..768fbd6 100644
--- a/src/main/include/log4cxx/filter/mapfilter.h
+++ b/src/main/include/log4cxx/filter/mapfilter.h
@@ -19,26 +19,98 @@
 
 #include <log4cxx/spi/filter.h>
 
+#if defined(_MSC_VER)
+	#pragma warning ( push )
+	#pragma warning ( disable: 4251 )
+#endif
+
 namespace log4cxx
 {
 namespace filter
 {
 
-
+/**
+ * A Filter that operates on a Map and can be used like in the following example:
+ * <pre>
+ * &lt;filter class="MapFilter"&gt;
+ *     &lt;param name="user.ip"       value="127.0.0.1" /&gt;
+ *     &lt;param name="user.name"     value="test2"     /&gt;
+ *     &lt;param name="Operator"      value="AND"       /&gt;
+ *     &lt;param name="AcceptOnMatch" value="false"     /&gt;
+ * &lt;/filter&gt;
+ * </pre>
+ */
 class LOG4CXX_EXPORT MapFilter: public log4cxx::spi::Filter
 {
+	typedef std::map < LogString, LogString > KeyVals;
+
+	private:
+		bool	acceptOnMatch;
+		bool	mustMatchAll; // true = AND; false = OR
+		KeyVals	keyVals;
+
 	public:
 		DECLARE_LOG4CXX_OBJECT(MapFilter)
 		BEGIN_LOG4CXX_CAST_MAP()
-		LOG4CXX_CAST_ENTRY(log4cxx::spi::Filter)
+		LOG4CXX_CAST_ENTRY(MapFilter)
+		LOG4CXX_CAST_ENTRY_CHAIN(log4cxx::spi::Filter)
 		END_LOG4CXX_CAST_MAP()
 
 		MapFilter();
 
+		/**
+		Set options
+		*/
+		virtual void setOption(const LogString& option,
+			const LogString& value);
 
+		inline void setKeyValue(const LogString& strKey, const LogString& strValue)
+		{
+			this->keyVals[strKey] = strValue;
+		}
+
+		inline const LogString& getValue(const LogString& strKey) const
+		{
+			static	const LogString					empty;
+					const KeyVals::const_iterator	it(this->keyVals.find(strKey));
+
+			return (it != keyVals.end() ? it->second : empty);
+		}
+
+		inline void setAcceptOnMatch(bool acceptOnMatch1)
+		{
+			this->acceptOnMatch = acceptOnMatch1;
+		}
+
+		inline bool getAcceptOnMatch() const
+		{
+			return acceptOnMatch;
+		}
+
+		inline bool getMustMatchAll() const
+		{
+			return mustMatchAll;
+		}
+
+		inline void setMustMatchAll(bool mustMatchAll1)
+		{
+			this->mustMatchAll = mustMatchAll1;
+		}
+
+		/**
+		Returns {@link log4cxx::spi::Filter#NEUTRAL NEUTRAL}
+		is there is no string match.
+		*/
 		FilterDecision decide(const spi::LoggingEventPtr& event) const;
+}; // class MapFilter
 
-};
-}
-}
+LOG4CXX_PTR_DEF(MapFilter);
+
+} // namespace filter
+} // namespace log4cxx
+
+#if defined(_MSC_VER)
+	#pragma warning (pop)
 #endif
+
+#endif // _LOG4CXX_FILTER_MAPFILTER_H
diff --git a/src/test/cpp/Makefile.am b/src/test/cpp/Makefile.am
index 8b6c95d..1f42361 100644
--- a/src/test/cpp/Makefile.am
+++ b/src/test/cpp/Makefile.am
@@ -67,6 +67,7 @@
     filter/levelmatchfiltertest.cpp \
     filter/levelrangefiltertest.cpp \
     filter/loggermatchfiltertest.cpp \
+    filter/mapfiltertest.cpp \
     filter/stringmatchfiltertest.cpp
 
 helpers = \
diff --git a/src/test/cpp/filter/CMakeLists.txt b/src/test/cpp/filter/CMakeLists.txt
index 83dc498..3b4bcb0 100644
--- a/src/test/cpp/filter/CMakeLists.txt
+++ b/src/test/cpp/filter/CMakeLists.txt
@@ -4,6 +4,7 @@
     levelmatchfiltertest.cpp
     levelrangefiltertest.cpp
     loggermatchfiltertest.cpp
+    mapfiltertest.cpp
     stringmatchfiltertest.cpp
 )
 set(ALL_LOG4CXX_TESTS ${ALL_LOG4CXX_TESTS} filtertests PARENT_SCOPE)
diff --git a/src/test/cpp/filter/mapfiltertest.cpp b/src/test/cpp/filter/mapfiltertest.cpp
new file mode 100644
index 0000000..bfb9e53
--- /dev/null
+++ b/src/test/cpp/filter/mapfiltertest.cpp
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+#include <log4cxx/filter/mapfilter.h>
+#include <log4cxx/logger.h>
+#include <log4cxx/spi/filter.h>
+#include <log4cxx/spi/loggingevent.h>
+#include "../logunit.h"
+
+using namespace log4cxx;
+using namespace log4cxx::filter;
+using namespace log4cxx::spi;
+using namespace log4cxx::helpers;
+
+/**
+ * Unit tests for MapFilter.
+ */
+LOGUNIT_CLASS(MapFilterTest)
+{
+	LOGUNIT_TEST_SUITE(MapFilterTest);
+	LOGUNIT_TEST(test1);
+	LOGUNIT_TEST(test2);
+	LOGUNIT_TEST(test3);
+	LOGUNIT_TEST(test4);
+	LOGUNIT_TEST_SUITE_END();
+
+public:
+
+	/**
+	 * Check that MapFilter.decide() returns Filter.NEUTRAL
+	 *   when there are no map entries specified.
+	 */
+	void test1()
+	{
+		LoggingEventPtr event(new LoggingEvent(
+				LOG4CXX_STR("MapFilterTest"),
+				Level::getInfo(),
+				LOG4CXX_STR("Hello, World"),
+				LOG4CXX_LOCATION));
+		FilterPtr filter(new MapFilter());
+		Pool p;
+		filter->activateOptions(p);
+		LOGUNIT_ASSERT_EQUAL(Filter::NEUTRAL, filter->decide(event));
+	}
+
+	/**
+	 * Check that MapFilter.decide() returns Filter.ACCEPT or Filter.DENY
+	 *   based on Accept on Match setting when key/value does not match
+	 */
+	void test2()
+	{
+		LoggingEventPtr event(new LoggingEvent(
+				LOG4CXX_STR("MapFilterTest"),
+				Level::getInfo(),
+				LOG4CXX_STR("Hello, World"),
+				LOG4CXX_LOCATION));
+		MDC::put(LOG4CXX_STR("my.ip"), LOG4CXX_STR("localhost"));
+		MapFilterPtr filter(new MapFilter());
+		filter->setKeyValue(LOG4CXX_STR("my.ip"), LOG4CXX_STR("127.0.0.1"));
+		Pool p;
+		filter->activateOptions(p);
+
+		filter->setAcceptOnMatch(true);
+		LOGUNIT_ASSERT_EQUAL(Filter::DENY, filter->decide(event));
+
+		filter->setAcceptOnMatch(false);
+		LOGUNIT_ASSERT_EQUAL(Filter::ACCEPT, filter->decide(event));
+	}
+
+	/**
+	 * Check that MapFilter.decide() returns Filter.ACCEPT or Filter.DENY
+	 *   based on Accept on Match setting when key/value matches
+	 */
+	void test3()
+	{
+		LoggingEventPtr event(new LoggingEvent(
+				LOG4CXX_STR("MapFilterTest"),
+				Level::getInfo(),
+				LOG4CXX_STR("Hello, World"),
+				LOG4CXX_LOCATION));
+		MDC::put(LOG4CXX_STR("my.ip"), LOG4CXX_STR("127.0.0.1"));
+		MapFilterPtr filter(new MapFilter());
+		filter->setKeyValue(LOG4CXX_STR("my.ip"), LOG4CXX_STR("127.0.0.1"));
+		Pool p;
+		filter->activateOptions(p);
+
+		filter->setAcceptOnMatch(true);
+		LOGUNIT_ASSERT_EQUAL(Filter::ACCEPT, filter->decide(event));
+
+		filter->setAcceptOnMatch(false);
+		LOGUNIT_ASSERT_EQUAL(Filter::DENY, filter->decide(event));
+	}
+
+	/**
+	 * Check that MapFilter.decide() ANDs or ORs multiple key/values
+	 *   based on operator setting
+	 */
+	void test4()
+	{
+		LoggingEventPtr event(new LoggingEvent(
+				LOG4CXX_STR("MapFilterTest"),
+				Level::getInfo(),
+				LOG4CXX_STR("Hello, World"),
+				LOG4CXX_LOCATION));
+		MDC::put(LOG4CXX_STR("my.ip"), LOG4CXX_STR("127.0.0.1"));
+		MDC::put(LOG4CXX_STR("my.name"), LOG4CXX_STR("Test"));
+		MapFilterPtr filter(new MapFilter());
+		filter->setKeyValue(LOG4CXX_STR("my.ip"), LOG4CXX_STR("127.0.0.1"));
+		filter->setKeyValue(LOG4CXX_STR("my.name"), LOG4CXX_STR("Unknown"));
+		filter->setAcceptOnMatch(true);
+		Pool p;
+		filter->activateOptions(p);
+
+		filter->setMustMatchAll(true);      // AND T/F
+		LOGUNIT_ASSERT_EQUAL(Filter::DENY, filter->decide(event));      // does not match second
+
+		filter->setMustMatchAll(false); // OR T/F
+		LOGUNIT_ASSERT_EQUAL(Filter::ACCEPT, filter->decide(event));    // matches first
+
+		filter->setKeyValue(LOG4CXX_STR("my.name"), LOG4CXX_STR("Test"));
+
+		filter->setMustMatchAll(true);      // AND T/T
+		LOGUNIT_ASSERT_EQUAL(Filter::ACCEPT, filter->decide(event));    // matches all
+
+		filter->setMustMatchAll(false); // OR T/T
+		LOGUNIT_ASSERT_EQUAL(Filter::ACCEPT, filter->decide(event));    // matches first
+
+		filter->setKeyValue(LOG4CXX_STR("my.ip"), LOG4CXX_STR("localhost"));
+
+		filter->setMustMatchAll(true);      // AND F/T
+		LOGUNIT_ASSERT_EQUAL(Filter::DENY, filter->decide(event));      // does not match first
+
+		filter->setMustMatchAll(false); // OR F/T
+		LOGUNIT_ASSERT_EQUAL(Filter::ACCEPT, filter->decide(event));    // matches second
+
+		filter->setKeyValue(LOG4CXX_STR("my.name"), LOG4CXX_STR("Unkonwn"));
+
+		filter->setMustMatchAll(true);      // AND F/F
+		LOGUNIT_ASSERT_EQUAL(Filter::DENY, filter->decide(event));      // does not match first
+
+		filter->setMustMatchAll(false); // OR F/F
+		LOGUNIT_ASSERT_EQUAL(Filter::DENY, filter->decide(event));      // matches none
+	}
+
+};
+
+
+LOGUNIT_TEST_SUITE_REGISTRATION(MapFilterTest);
+
+