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 ab49897d4c5489ceede7f7619b1f5c5cd0884c60..e76227d33535c19143651d441c2a3ef3eef62bf9 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 0000000000000000000000000000000000000000..868fc57454854c7d56ef087a67cec3b348c7ddd3 --- /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 0000000000000000000000000000000000000000..726d0b58d5337631b1c04f62db5a23ff745a6c04 --- /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 0000000000000000000000000000000000000000..916c5725625da27eac328c4c114f7a87a37074a9 --- /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 0000000000000000000000000000000000000000..b7991ae7f70378f824cc69f8eb1456be49160583 --- /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 0000000000000000000000000000000000000000..ebc79ca39d16025b8075fc1cf1f6813f54640f99 --- /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 0000000000000000000000000000000000000000..0b87a94c74d53f08e09aa4a45d531bd1db16f61a --- /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 0000000000000000000000000000000000000000..31730b02076a239886fee0c229318c28326b66b3 --- /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 0000000000000000000000000000000000000000..c7be7c1b601775da90ba175eca9e3445175a1dad --- /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 0000000000000000000000000000000000000000..97329add900f1ef7d7eeea6d8d2fd6e264579378 --- /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 0000000000000000000000000000000000000000..5322bc186eab66c6e2593cb0d8e091fc206a94b6 --- /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 0000000000000000000000000000000000000000..afbd93ac85a73edb9245d971e2cd8aab25966f54 --- /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 0000000000000000000000000000000000000000..9b6b7be5130ba64231bea1eb5c6729262ad6e15b --- /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 0000000000000000000000000000000000000000..fa1c3e5a3eae220dec922a40d1f9647639a75f1e --- /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 7cf01437042f9cd9a515e502fe25a1eba007b48a..44c39320e347c9742210e9d0ef380085d0b27a7c 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 7dfce75f938c71463a60788d0b4c66ee1a707fb4..ea48033cd576582c576d5a0de74e45e777af20fc 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 6afcf6ba5c9f48d2627ae9a611f7b38a3928ab75..4042c53cde20238f59a6aadf9d2b13ed7c700d9b 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 0000000000000000000000000000000000000000..4075db4008bae98747cd4822734cfdd156b85817 --- /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; + } + } +}