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