Index: api/pom.xml =================================================================== --- api/pom.xml (revision 45500) +++ api/pom.xml (working copy) @@ -156,6 +156,12 @@ + org.easymock + easymockclassextension + test + + + org.onehippo.cms7 hippo-repository-testutils ${hippo.repository.version} Index: api/src/main/java/org/hippoecm/frontend/model/TraceMonitor.java =================================================================== --- api/src/main/java/org/hippoecm/frontend/model/TraceMonitor.java (revision 45500) +++ api/src/main/java/org/hippoecm/frontend/model/TraceMonitor.java (working copy) @@ -15,35 +15,79 @@ */ package org.hippoecm.frontend.model; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.jcr.Item; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.jcr.Item; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +/** + * javax.jcr.Item usage tracing/monitoring utility. + *

+ * + * Note: This is turned off by default. Set '-DTraceMonitor.enabled=true' system property to turn it on. + * Also, the default trace item size is 2000. You can increase it by setting '-DTraceMonitor.max=3000' for instance. + * Setting it to zero means unlimited size, which could be dangerous at runtime. So use it only in debugging mode! + * + *

+ */ +@SuppressWarnings("unchecked") public class TraceMonitor { - static final Logger log = LoggerFactory.getLogger(TraceMonitor.class); + static Logger log = LoggerFactory.getLogger(TraceMonitor.class); - private static final Map initializedAndNotDetached = new HashMap(); + static final String ENABLED_PROP = TraceMonitor.class.getSimpleName() + ".enabled"; + static final String MAX_PROP = TraceMonitor.class.getSimpleName() + ".max"; + static final int DEFAULT_MAX_SIZE = 2000; + private static final boolean enabled; + private static final int maxSize; + private static final Map initializedAndNotDetached; + + static { + enabled = BooleanUtils.toBoolean(System.getProperty(ENABLED_PROP)); + + int max = NumberUtils.toInt(System.getProperty(MAX_PROP), -1); + if (max < 0) { + maxSize = DEFAULT_MAX_SIZE; + } else { + maxSize = max; + } + + if (enabled) { + if (maxSize > 0) { + log.info("{}: Max monitoring items set to {}", TraceMonitor.class.getSimpleName(), maxSize); + initializedAndNotDetached = Collections.synchronizedMap(new LRUMap(maxSize)); + } else { + log.warn("{}: No limit to monitoring items.", TraceMonitor.class.getSimpleName()); + initializedAndNotDetached = new ConcurrentHashMap(); + } + } else { + initializedAndNotDetached = null; + } + } protected static final void track(Item item) { - if (log.isDebugEnabled()) { + if (initializedAndNotDetached != null && log.isDebugEnabled()) { initializedAndNotDetached.put(item.toString(), getCallee()); } } protected static final void release(Item item) { - if (log.isDebugEnabled()) { + if (initializedAndNotDetached != null && log.isDebugEnabled()) { initializedAndNotDetached.remove(item.toString()); } } protected static final void trace(Item item) { - if (log.isDebugEnabled() && initializedAndNotDetached.containsKey(item.toString())) { + if (initializedAndNotDetached != null && log.isDebugEnabled() && initializedAndNotDetached.containsKey(item.toString())) { String stackTrace = initializedAndNotDetached.get(item.toString()); log.debug(stackTrace); } @@ -60,4 +104,11 @@ return os.toString(); } + protected static final int getSize() { + if (initializedAndNotDetached != null) { + return initializedAndNotDetached.size(); + } else { + return 0; + } + } } Index: api/src/test/java/org/hippoecm/frontend/model/TraceMonitorTest.java =================================================================== --- api/src/test/java/org/hippoecm/frontend/model/TraceMonitorTest.java (revision 0) +++ api/src/test/java/org/hippoecm/frontend/model/TraceMonitorTest.java (revision 0) @@ -0,0 +1,110 @@ +/* + * Copyright 2014-2014 Hippo B.V. (http://www.onehippo.com) + * + * Licensed 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.hippoecm.frontend.model; + +import static org.junit.Assert.assertEquals; + +import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.easymock.classextension.EasyMock; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onehippo.repository.mock.MockNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * TraceMonitor test + *

+ * If you run this in a command line, you can run these for instance: + *

+ *
    + *
  • $ mvn test -Dtest=TraceMonitorTest
  • + *
  • $ mvn test -Dtest=TraceMonitorTest -DTraceMonitor.enabled=true
  • + *
  • $ mvn test -Dtest=TraceMonitorTest -DTraceMonitor.enabled=true -DTraceMonitor.max=3000
  • + *
+ */ +public class TraceMonitorTest { + + private static Logger log = LoggerFactory.getLogger(TraceMonitorTest.class); + + private static Logger originalLogger; + private static Logger testLogger; + + @Before + public void before() throws Exception { + originalLogger = TraceMonitor.log; + testLogger = EasyMock.createNiceMock(Logger.class); + EasyMock.expect(testLogger.isDebugEnabled()).andReturn(true).anyTimes(); + EasyMock.replay(testLogger); + TraceMonitor.log = testLogger; + } + + @After + public void after() throws Exception { + TraceMonitor.log = originalLogger; + } + + @Test + public void testDefault() throws Exception { + boolean enabled = BooleanUtils.toBoolean(System.getProperty(TraceMonitor.ENABLED_PROP)); + int maxSize = NumberUtils.toInt(System.getProperty(TraceMonitor.MAX_PROP), -1); + + if (maxSize < 0) { + maxSize = TraceMonitor.DEFAULT_MAX_SIZE; + } + + if (!enabled) { + log.info("Testing the default setting without any system properties..."); + + for (int i = 1; i <= maxSize; i++) { + TraceMonitor.track(MockNode.root().addNode("node-" + i, "nt:unstructured")); + assertEquals(0, TraceMonitor.getSize()); + } + } else { + if (maxSize == 0) { + log.info("Testing the setting with `-DTraceMonitor.enabled=true -DTraceMonitor.max=0' ..."); + + for (int i = 1; i <= maxSize; i++) { + TraceMonitor.track(MockNode.root().addNode("node-" + i, "nt:unstructured")); + assertEquals(i, TraceMonitor.getSize()); + } + + int doubledMaxSize = 2 * maxSize; + + for (int i = maxSize; i <= doubledMaxSize; i++) { + TraceMonitor.track(MockNode.root().addNode("node-" + i, "nt:unstructured")); + assertEquals(i, TraceMonitor.getSize()); + } + } else { + log.info("Testing the setting with `-DTraceMonitor.enabled=true -DTraceMonitor.max=N' (N is 2000 by default) ..."); + + for (int i = 1; i <= maxSize; i++) { + TraceMonitor.track(MockNode.root().addNode("node-" + i, "nt:unstructured")); + assertEquals(i, TraceMonitor.getSize()); + } + + // one more cycle to track, but the size shouldn't increase over the maximum. + for (int i = 1; i <= maxSize; i++) { + TraceMonitor.track(MockNode.root().addNode("node-" + i, "nt:unstructured")); + assertEquals(maxSize, TraceMonitor.getSize()); + } + } + } + } + +} Index: api/src/test/resources/log4j.xml =================================================================== --- api/src/test/resources/log4j.xml (revision 0) +++ api/src/test/resources/log4j.xml (revision 0) @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +