From f55271374465aa272c3ef4a049d3c9c6e86d0863 Mon Sep 17 00:00:00 2001 From: Frankie Wu Date: Sun, 7 Apr 2013 17:48:53 +0800 Subject: [PATCH] initial commit for ABTest - API (draft) - SPI (draft) --- .../java/com/dianping/cat/CatCoreModule.java | 3 + .../java/com/dianping/cat/abtest/ABTest.java | 20 +++++ .../com/dianping/cat/abtest/ABTestId.java | 5 ++ .../dianping/cat/abtest/ABTestManager.java | 43 ++++++++++ .../cat/abtest/internal/DefaultABTest.java | 63 +++++++++++++++ .../cat/abtest/spi/ABTestContext.java | 15 ++++ .../cat/abtest/spi/ABTestContextManager.java | 13 +++ .../dianping/cat/abtest/spi/ABTestEntity.java | 53 +++++++++++++ .../cat/abtest/spi/ABTestEntityManager.java | 7 ++ .../cat/abtest/spi/ABTestGroupStrategy.java | 5 ++ .../spi/internal/DefaultABTestContext.java | 42 ++++++++++ .../internal/DefaultABTestContextManager.java | 79 +++++++++++++++++++ .../internal/DefaultABTestEntityManager.java | 33 ++++++++ .../build/ABTestComponentConfigurator.java | 26 ++++++ .../cat/build/ComponentsConfigurator.java | 8 +- .../com/dianping/cat/servlet/CatFilter.java | 6 +- .../resources/META-INF/plexus/components.xml | 13 +++ .../cat/abtest/sample/SampleTest.java | 44 +++++++++++ 18 files changed, 473 insertions(+), 5 deletions(-) create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/ABTest.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/ABTestId.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/ABTestManager.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/internal/DefaultABTest.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestContext.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestContextManager.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestEntity.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestEntityManager.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestGroupStrategy.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestContext.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestContextManager.java create mode 100644 cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestEntityManager.java create mode 100644 cat-core/src/main/java/com/dianping/cat/build/ABTestComponentConfigurator.java create mode 100644 cat-core/src/test/java/com/dianping/cat/abtest/sample/SampleTest.java diff --git a/cat-core/src/main/java/com/dianping/cat/CatCoreModule.java b/cat-core/src/main/java/com/dianping/cat/CatCoreModule.java index ab49897d4..e76227d33 100644 --- a/cat-core/src/main/java/com/dianping/cat/CatCoreModule.java +++ b/cat-core/src/main/java/com/dianping/cat/CatCoreModule.java @@ -13,6 +13,7 @@ import org.unidal.initialization.DefaultModuleContext; import org.unidal.initialization.Module; import org.unidal.initialization.ModuleContext; +import com.dianping.cat.abtest.ABTestManager; import com.dianping.cat.configuration.ClientConfigManager; import com.dianping.cat.configuration.ClientConfigReloader; import com.dianping.cat.configuration.client.entity.ClientConfig; @@ -53,6 +54,8 @@ public class CatCoreModule extends AbstractModule { Threads.forGroup("Cat").start(new ClientConfigReloader(clientConfigFile.getAbsolutePath(), config)); } } + + ABTestManager.initialize(); } @Override diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/ABTest.java b/cat-core/src/main/java/com/dianping/cat/abtest/ABTest.java new file mode 100644 index 000000000..868fc5745 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/ABTest.java @@ -0,0 +1,20 @@ +package com.dianping.cat.abtest; + +public interface ABTest { + public ABTestId getTestId(); + + public boolean isDefaultGroup(); + + public boolean isGroupA(); + + public boolean isGroupB(); + + public boolean isGroupC(); + + public boolean isGroupD(); + + public boolean isGroupE(); + + public boolean isGroup(String name); + +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/ABTestId.java b/cat-core/src/main/java/com/dianping/cat/abtest/ABTestId.java new file mode 100644 index 000000000..726d0b58d --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/ABTestId.java @@ -0,0 +1,5 @@ +package com.dianping.cat.abtest; + +public interface ABTestId { + public int getValue(); +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/ABTestManager.java b/cat-core/src/main/java/com/dianping/cat/abtest/ABTestManager.java new file mode 100644 index 000000000..916c57256 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/ABTestManager.java @@ -0,0 +1,43 @@ +package com.dianping.cat.abtest; + +import javax.servlet.http.HttpServletRequest; + +import org.unidal.lookup.ContainerLoader; + +import com.dianping.cat.abtest.internal.DefaultABTest; +import com.dianping.cat.abtest.spi.ABTestContextManager; + +public final class ABTestManager { + private static ABTestContextManager s_contextManager; + + public static ABTest getTest(ABTestId id) { + initialize(); + + return new DefaultABTest(id, s_contextManager); + } + + public static void initialize() { + if (s_contextManager == null) { + synchronized (ABTestManager.class) { + if (s_contextManager == null) { + try { + // it could be time-consuming due to load entities from the repository, i.e. database. + s_contextManager = ContainerLoader.getDefaultContainer().lookup(ABTestContextManager.class); + } catch (Exception e) { + throw new RuntimeException("Error when initializing ABTestContextManager!", e); + } + } + } + } + } + + public static void onRequestBegin(HttpServletRequest req) { + initialize(); + + s_contextManager.onRequestBegin(req); + } + + public static void onRequestEnd() { + s_contextManager.onRequestEnd(); + } +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/internal/DefaultABTest.java b/cat-core/src/main/java/com/dianping/cat/abtest/internal/DefaultABTest.java new file mode 100644 index 000000000..b7991ae7f --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/internal/DefaultABTest.java @@ -0,0 +1,63 @@ +package com.dianping.cat.abtest.internal; + +import com.dianping.cat.abtest.ABTest; +import com.dianping.cat.abtest.ABTestId; +import com.dianping.cat.abtest.spi.ABTestContext; +import com.dianping.cat.abtest.spi.ABTestContextManager; + +public class DefaultABTest implements ABTest { + private ABTestContextManager m_contextManager; + + private ABTestId m_id; + + public DefaultABTest(ABTestId id, ABTestContextManager contextManager) { + m_contextManager = contextManager; + m_id = id; + } + + @Override + public ABTestId getTestId() { + return m_id; + } + + private String getGroupName() { + ABTestContext ctx = m_contextManager.getContext(m_id); + + return ctx.getGroupName(); + } + + @Override + public boolean isDefaultGroup() { + return ABTestContext.DEFAULT_GROUP.equals(getGroupName()); + } + + @Override + public boolean isGroupA() { + return "A".equals(getGroupName()); + } + + @Override + public boolean isGroupB() { + return "B".equals(getGroupName()); + } + + @Override + public boolean isGroupC() { + return "C".equals(getGroupName()); + } + + @Override + public boolean isGroupD() { + return "D".equals(getGroupName()); + } + + @Override + public boolean isGroupE() { + return "E".equals(getGroupName()); + } + + @Override + public boolean isGroup(String name) { + return name.equals(getGroupName()); + } +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestContext.java b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestContext.java new file mode 100644 index 000000000..ebc79ca39 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestContext.java @@ -0,0 +1,15 @@ +package com.dianping.cat.abtest.spi; + +import javax.servlet.http.HttpServletRequest; + +public interface ABTestContext { + public final String DEFAULT_GROUP = "default"; + + public ABTestEntity getEntity(); + + public String getGroupName(); + + public void setGroupName(String groupName); + + public HttpServletRequest getHttpServletRequest(); +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestContextManager.java b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestContextManager.java new file mode 100644 index 000000000..0b87a94c7 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestContextManager.java @@ -0,0 +1,13 @@ +package com.dianping.cat.abtest.spi; + +import javax.servlet.http.HttpServletRequest; + +import com.dianping.cat.abtest.ABTestId; + +public interface ABTestContextManager { + public ABTestContext getContext(ABTestId testId); + + public void onRequestBegin(HttpServletRequest req); + + public void onRequestEnd(); +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestEntity.java b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestEntity.java new file mode 100644 index 000000000..31730b020 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestEntity.java @@ -0,0 +1,53 @@ +package com.dianping.cat.abtest.spi; + +public class ABTestEntity { + private int m_id; + + private String m_name; + + private String m_groupStrategy; + + private String m_groupStrategyConfiguration; + + private boolean m_active; + + public String getGroupStrategy() { + return m_groupStrategy; + } + + public String getGroupStrategyConfiguration() { + return m_groupStrategyConfiguration; + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public boolean isActive() { + return m_active; + } + + public void setActive(boolean active) { + m_active = active; + } + + public void setGroupStrategy(String groupStrategy) { + m_groupStrategy = groupStrategy; + } + + public void setGroupStrategyConfiguration(String groupStrategyConfiguration) { + m_groupStrategyConfiguration = groupStrategyConfiguration; + } + + public void setId(int id) { + m_id = id; + } + + public void setName(String name) { + m_name = name; + } +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestEntityManager.java b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestEntityManager.java new file mode 100644 index 000000000..c7be7c1b6 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestEntityManager.java @@ -0,0 +1,7 @@ +package com.dianping.cat.abtest.spi; + +import com.dianping.cat.abtest.ABTestId; + +public interface ABTestEntityManager { + public ABTestEntity getEntity(ABTestId id); +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestGroupStrategy.java b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestGroupStrategy.java new file mode 100644 index 000000000..97329add9 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/spi/ABTestGroupStrategy.java @@ -0,0 +1,5 @@ +package com.dianping.cat.abtest.spi; + +public interface ABTestGroupStrategy { + public void apply(ABTestContext ctx); +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestContext.java b/cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestContext.java new file mode 100644 index 000000000..5322bc186 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestContext.java @@ -0,0 +1,42 @@ +package com.dianping.cat.abtest.spi.internal; + +import javax.servlet.http.HttpServletRequest; + +import com.dianping.cat.abtest.spi.ABTestContext; +import com.dianping.cat.abtest.spi.ABTestEntity; + +public class DefaultABTestContext implements ABTestContext { + private String m_groupName = DEFAULT_GROUP; + + private HttpServletRequest m_req; + + private ABTestEntity m_entity; + + public DefaultABTestContext(ABTestEntity entity) { + m_entity = entity; + } + + @Override + public String getGroupName() { + return m_groupName; + } + + @Override + public void setGroupName(String groupName) { + m_groupName = groupName; + } + + public void setup(HttpServletRequest req) { + m_req = req; + } + + @Override + public HttpServletRequest getHttpServletRequest() { + return m_req; + } + + @Override + public ABTestEntity getEntity() { + return m_entity; + } +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestContextManager.java b/cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestContextManager.java new file mode 100644 index 000000000..afbd93ac8 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestContextManager.java @@ -0,0 +1,79 @@ +package com.dianping.cat.abtest.spi.internal; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.unidal.lookup.annotation.Inject; + +import com.dianping.cat.abtest.ABTestId; +import com.dianping.cat.abtest.spi.ABTestContext; +import com.dianping.cat.abtest.spi.ABTestContextManager; +import com.dianping.cat.abtest.spi.ABTestEntity; +import com.dianping.cat.abtest.spi.ABTestEntityManager; + +public class DefaultABTestContextManager implements ABTestContextManager { + @Inject + private ABTestEntityManager m_entityManager; + + private InheritableThreadLocal m_threadLocal = new InheritableThreadLocal() { + @Override + protected Entry initialValue() { + return new Entry(); + } + }; + + @Override + public ABTestContext getContext(ABTestId testId) { + Entry entry = m_threadLocal.get(); + Map map = entry.getContextMap(); + int id = testId.getValue(); + DefaultABTestContext ctx = map.get(id); + + if (ctx == null) { + ABTestEntity entity = m_entityManager.getEntity(testId); + + ctx = new DefaultABTestContext(entity); + ctx.setup(entry.getHttpServletRequest()); + map.put(id, ctx); + } + + return ctx; + } + + @Override + public void onRequestEnd() { + m_threadLocal.remove(); + } + + @Override + public void onRequestBegin(HttpServletRequest req) { + Entry entry = m_threadLocal.get(); + + entry.setHttpServletRequest(req); + + Map map = entry.getContextMap(); + for (DefaultABTestContext ctx : map.values()) { + ctx.setup(req); + } + } + + static class Entry { + private Map m_map = new HashMap(4); + + private HttpServletRequest m_req; + + public Map getContextMap() { + return m_map; + } + + public HttpServletRequest getHttpServletRequest() { + return m_req; + } + + public void setHttpServletRequest(HttpServletRequest req) { + m_req = req; + } + } +} diff --git a/cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestEntityManager.java b/cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestEntityManager.java new file mode 100644 index 000000000..9b6b7be51 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/abtest/spi/internal/DefaultABTestEntityManager.java @@ -0,0 +1,33 @@ +package com.dianping.cat.abtest.spi.internal; + +import java.util.HashMap; +import java.util.Map; + +import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable; +import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException; + +import com.dianping.cat.abtest.ABTestId; +import com.dianping.cat.abtest.spi.ABTestEntity; +import com.dianping.cat.abtest.spi.ABTestEntityManager; + +public class DefaultABTestEntityManager implements ABTestEntityManager, Initializable { + private Map m_entities = new HashMap(); + + @Override + public ABTestEntity getEntity(ABTestId id) { + ABTestEntity entity = m_entities.get(id.getValue()); + + if (entity == null) { + entity = new ABTestEntity(); + entity.setActive(false); + m_entities.put(id.getValue(), entity); + } + + return entity; + } + + @Override + public void initialize() throws InitializationException { + // TODO + } +} diff --git a/cat-core/src/main/java/com/dianping/cat/build/ABTestComponentConfigurator.java b/cat-core/src/main/java/com/dianping/cat/build/ABTestComponentConfigurator.java new file mode 100644 index 000000000..fa1c3e5a3 --- /dev/null +++ b/cat-core/src/main/java/com/dianping/cat/build/ABTestComponentConfigurator.java @@ -0,0 +1,26 @@ +package com.dianping.cat.build; + +import java.util.ArrayList; +import java.util.List; + +import org.unidal.lookup.configuration.AbstractResourceConfigurator; +import org.unidal.lookup.configuration.Component; + +import com.dianping.cat.abtest.spi.ABTestContextManager; +import com.dianping.cat.abtest.spi.ABTestEntityManager; +import com.dianping.cat.abtest.spi.internal.DefaultABTestContextManager; +import com.dianping.cat.abtest.spi.internal.DefaultABTestEntityManager; + +class ABTestComponentConfigurator extends AbstractResourceConfigurator { + @Override + public List defineComponents() { + List all = new ArrayList(); + + all.add(C(ABTestContextManager.class, DefaultABTestContextManager.class) // + .req(ABTestEntityManager.class)); + + all.add(C(ABTestEntityManager.class, DefaultABTestEntityManager.class)); + + return all; + } +} diff --git a/cat-core/src/main/java/com/dianping/cat/build/ComponentsConfigurator.java b/cat-core/src/main/java/com/dianping/cat/build/ComponentsConfigurator.java index 7cf014370..44c39320e 100644 --- a/cat-core/src/main/java/com/dianping/cat/build/ComponentsConfigurator.java +++ b/cat-core/src/main/java/com/dianping/cat/build/ComponentsConfigurator.java @@ -3,6 +3,10 @@ package com.dianping.cat.build; import java.util.ArrayList; import java.util.List; +import org.unidal.initialization.Module; +import org.unidal.lookup.configuration.AbstractResourceConfigurator; +import org.unidal.lookup.configuration.Component; + import com.dianping.cat.CatCoreModule; import com.dianping.cat.configuration.ClientConfigManager; import com.dianping.cat.configuration.ServerConfigManager; @@ -42,9 +46,6 @@ import com.dianping.cat.storage.dump.LocalMessageBucket; import com.dianping.cat.storage.dump.LocalMessageBucketManager; import com.dianping.cat.storage.dump.MessageBucket; import com.dianping.cat.storage.dump.MessageBucketManager; -import org.unidal.initialization.Module; -import org.unidal.lookup.configuration.AbstractResourceConfigurator; -import org.unidal.lookup.configuration.Component; public class ComponentsConfigurator extends AbstractResourceConfigurator { @Override @@ -111,6 +112,7 @@ public class ComponentsConfigurator extends AbstractResourceConfigurator { all.addAll(new CodecComponentConfigurator().defineComponents()); all.addAll(new StorageComponentConfigurator().defineComponents()); + all.addAll(new ABTestComponentConfigurator().defineComponents()); return all; } diff --git a/cat-core/src/main/java/com/dianping/cat/servlet/CatFilter.java b/cat-core/src/main/java/com/dianping/cat/servlet/CatFilter.java index 7dfce75f9..ea48033cd 100644 --- a/cat-core/src/main/java/com/dianping/cat/servlet/CatFilter.java +++ b/cat-core/src/main/java/com/dianping/cat/servlet/CatFilter.java @@ -13,6 +13,7 @@ import javax.servlet.http.HttpServletRequest; import com.dianping.cat.Cat; import com.dianping.cat.CatConstants; +import com.dianping.cat.abtest.ABTestManager; import com.dianping.cat.message.Event; import com.dianping.cat.message.Message; import com.dianping.cat.message.MessageProducer; @@ -24,13 +25,13 @@ public class CatFilter implements Filter { } @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, - ServletException { + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; String sessionToken = getSessionIdFromCookie(req); // setup for thread local data Cat.setup(sessionToken); + ABTestManager.onRequestBegin(req); MessageProducer cat = Cat.getProducer(); Transaction t = cat.newTransaction(CatConstants.TYPE_URL, getOriginalUrl(request)); @@ -66,6 +67,7 @@ public class CatFilter implements Filter { } finally { t.complete(); Cat.reset(); + ABTestManager.onRequestEnd(); } } diff --git a/cat-core/src/main/resources/META-INF/plexus/components.xml b/cat-core/src/main/resources/META-INF/plexus/components.xml index 6afcf6ba5..4042c53cd 100644 --- a/cat-core/src/main/resources/META-INF/plexus/components.xml +++ b/cat-core/src/main/resources/META-INF/plexus/components.xml @@ -302,5 +302,18 @@ + + com.dianping.cat.abtest.spi.ABTestContextManager + com.dianping.cat.abtest.spi.internal.DefaultABTestContextManager + + + com.dianping.cat.abtest.spi.ABTestEntityManager + + + + + com.dianping.cat.abtest.spi.ABTestEntityManager + com.dianping.cat.abtest.spi.internal.DefaultABTestEntityManager + diff --git a/cat-core/src/test/java/com/dianping/cat/abtest/sample/SampleTest.java b/cat-core/src/test/java/com/dianping/cat/abtest/sample/SampleTest.java new file mode 100644 index 000000000..4075db400 --- /dev/null +++ b/cat-core/src/test/java/com/dianping/cat/abtest/sample/SampleTest.java @@ -0,0 +1,44 @@ +package com.dianping.cat.abtest.sample; + +import org.junit.Test; + +import com.dianping.cat.abtest.ABTest; +import com.dianping.cat.abtest.ABTestId; +import com.dianping.cat.abtest.ABTestManager; + +public class SampleTest { + public static ABTest abtest1 = ABTestManager.getTest(MyABTestId.CASE1); + public static ABTest abtest2 = ABTestManager.getTest(MyABTestId.CASE2); + + @Test + public void usage() { + // some initialization for case 1 + + if (abtest1.isGroupA()) { + // Cat.logMetric(...); + } else if (abtest1.isGroupB()) { + // Cat.logMetric(...); + } else { + // Cat.logMetric(...); + } + + // some cleanup for case 1 + } + + public static enum MyABTestId implements ABTestId { + CASE1(1001), + + CASE2(1002); + + private int m_id; + + private MyABTestId(int id) { + m_id = id; + } + + @Override + public int getValue() { + return m_id; + } + } +} -- GitLab