001/* 002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * matic 016 */ 017package org.nuxeo.ecm.core.management.jtajca.internal; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.Comparator; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import javax.management.ObjectInstance; 027import javax.naming.NamingException; 028import javax.transaction.Status; 029import javax.transaction.Synchronization; 030import javax.transaction.Transaction; 031import javax.transaction.TransactionManager; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.apache.geronimo.transaction.manager.TransactionImpl; 036import org.apache.geronimo.transaction.manager.TransactionManagerImpl; 037import org.apache.geronimo.transaction.manager.TransactionManagerMonitor; 038import org.apache.geronimo.transaction.manager.XidImpl; 039import org.apache.log4j.MDC; 040import org.javasimon.SimonManager; 041import org.javasimon.Stopwatch; 042import org.nuxeo.ecm.core.management.jtajca.TransactionMonitor; 043import org.nuxeo.ecm.core.management.jtajca.TransactionStatistics; 044import org.nuxeo.runtime.jtajca.NuxeoContainer; 045import org.nuxeo.runtime.transaction.TransactionHelper; 046 047/** 048 * @author matic 049 */ 050public class DefaultTransactionMonitor implements TransactionManagerMonitor, TransactionMonitor, Synchronization { 051 052 protected static final Log log = LogFactory.getLog(DefaultTransactionMonitor.class); 053 054 protected TransactionManagerImpl tm; 055 056 protected boolean enabled; 057 058 @Override 059 public void install() { 060 tm = lookup(); 061 if (tm == null) { 062 log.warn("Cannot monitor transactions, not a geronimo tx manager"); 063 return; 064 } 065 bindManagementInterface(); 066 } 067 068 @Override 069 public void uninstall() { 070 if (tm == null) { 071 return; 072 } 073 unbindManagementInterface(); 074 if (enabled) { 075 toggle(); 076 } 077 } 078 079 protected ObjectInstance self; 080 081 protected void bindManagementInterface() { 082 self = DefaultMonitorComponent.bind(TransactionMonitor.class, this); 083 } 084 085 protected void unbindManagementInterface() { 086 DefaultMonitorComponent.unbind(self); 087 self = null; 088 } 089 090 protected TransactionManagerImpl lookup() { 091 TransactionManager tm = NuxeoContainer.getTransactionManager(); 092 if (tm == null) { // try setup trough NuxeoTransactionManagerFactory 093 try { 094 tm = TransactionHelper.lookupTransactionManager(); 095 } catch (NamingException cause) { 096 throw new RuntimeException("Cannot lookup tx manager", cause); 097 } 098 } 099 if (!(tm instanceof TransactionManagerImpl)) { 100 return null; 101 } 102 return (TransactionManagerImpl) tm; 103 } 104 105 protected TransactionStatistics lastCommittedStatistics; 106 107 protected TransactionStatistics lastRollbackedStatistics; 108 109 protected final Map<Object, DefaultTransactionStatistics> activeStatistics = new HashMap<Object, DefaultTransactionStatistics>(); 110 111 public static String id(Object key) { 112 if (key instanceof XidImpl) { 113 byte[] globalId = ((XidImpl) key).getGlobalTransactionId(); 114 StringBuffer buffer = new StringBuffer(); 115 for (int i = 0; i < globalId.length; i++) { 116 buffer.append(Integer.toHexString(globalId[i])); 117 } 118 return buffer.toString().replaceAll("0*$", ""); 119 } 120 return key.toString(); 121 } 122 123 public static String id(Transaction tx) { 124 return Integer.toHexString(tx.hashCode()); 125 } 126 127 @Override 128 public void threadAssociated(Transaction tx) { 129 long now = System.currentTimeMillis(); 130 Object key = tm.getTransactionKey(); 131 MDC.put("tx", id(key)); 132 Stopwatch sw = SimonManager.getStopwatch("tx"); 133 final Thread thread = Thread.currentThread(); 134 DefaultTransactionStatistics info = new DefaultTransactionStatistics(key); 135 info.split = sw.start(); 136 info.threadName = thread.getName(); 137 info.status = TransactionStatistics.Status.fromTx(tx); 138 139 info.startTimestamp = now; 140 info.startCapturedContext = new Throwable("** start invoke context **"); 141 synchronized (this) { 142 activeStatistics.put(key, info); 143 } 144 tm.registerInterposedSynchronization(this); // register end status 145 if (log.isTraceEnabled()) { 146 log.trace(info.toString()); 147 } 148 } 149 150 @Override 151 public void threadUnassociated(Transaction tx) { 152 try { 153 Object key = ((TransactionImpl) tx).getTransactionKey(); 154 DefaultTransactionStatistics stats; 155 synchronized (DefaultTransactionMonitor.class) { 156 stats = activeStatistics.remove(key); 157 } 158 if (stats == null) { 159 log.debug(key + " not found in active statistics map"); 160 return; 161 } 162 stats.split.stop(); 163 stats.split = null; 164 if (log.isTraceEnabled()) { 165 log.trace(stats); 166 } 167 if (TransactionStatistics.Status.COMMITTED.equals(stats.status)) { 168 lastCommittedStatistics = stats; 169 } else if (TransactionStatistics.Status.ROLLEDBACK.equals(stats.status)) { 170 lastRollbackedStatistics = stats; 171 } 172 } finally { 173 MDC.remove("tx"); 174 } 175 } 176 177 @Override 178 public List<TransactionStatistics> getActiveStatistics() { 179 List<TransactionStatistics> l = new ArrayList<TransactionStatistics>(activeStatistics.values()); 180 Collections.sort(l, new Comparator<TransactionStatistics>() { 181 @Override 182 public int compare(TransactionStatistics o1, TransactionStatistics o2) { 183 return o1.getStartDate().compareTo(o2.getEndDate()); 184 } 185 }); 186 return l; 187 } 188 189 @Override 190 public long getActiveCount() { 191 return tm.getActiveCount(); 192 } 193 194 @Override 195 public long getTotalCommits() { 196 return tm.getTotalCommits(); 197 } 198 199 @Override 200 public long getTotalRollbacks() { 201 return tm.getTotalRollbacks(); 202 } 203 204 @Override 205 public TransactionStatistics getLastCommittedStatistics() { 206 return lastCommittedStatistics; 207 } 208 209 @Override 210 public TransactionStatistics getLastRollbackedStatistics() { 211 return lastRollbackedStatistics; 212 } 213 214 protected DefaultTransactionStatistics thisStatistics() { 215 Object key = tm.getTransactionKey(); 216 DefaultTransactionStatistics stats; 217 synchronized (this) { 218 stats = activeStatistics.get(key); 219 } 220 if (stats == null) { 221 log.debug(key + " not found in active statistics map"); 222 } 223 return stats; 224 } 225 226 @Override 227 public void beforeCompletion() { 228 DefaultTransactionStatistics stats = thisStatistics(); 229 if (stats == null) { 230 return; 231 } 232 stats.endCapturedContext = new Throwable("** end invoke context **"); 233 } 234 235 @Override 236 public void afterCompletion(int code) { 237 DefaultTransactionStatistics stats = thisStatistics(); 238 if (stats == null) { 239 return; 240 } 241 stats.endTimestamp = System.currentTimeMillis(); 242 stats.status = TransactionStatistics.Status.fromCode(code); 243 switch (code) { 244 case Status.STATUS_COMMITTED: 245 lastCommittedStatistics = stats; 246 break; 247 case Status.STATUS_ROLLEDBACK: 248 lastRollbackedStatistics = stats; 249 stats.endCapturedContext = new Throwable("** rollback context **"); 250 break; 251 } 252 } 253 254 @Override 255 public boolean toggle() { 256 if (enabled) { 257 tm.removeTransactionAssociationListener(this); 258 activeStatistics.clear(); 259 enabled = false; 260 } else { 261 tm.addTransactionAssociationListener(this); 262 enabled = true; 263 } 264 return enabled; 265 } 266 267 @Override 268 public boolean getEnabled() { 269 return enabled; 270 } 271 272}