001/* 002 * (C) Copyright 2011-2018 Nuxeo (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.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import javax.naming.NamingException; 027import javax.transaction.Status; 028import javax.transaction.Synchronization; 029import javax.transaction.Transaction; 030import javax.transaction.TransactionManager; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.apache.geronimo.transaction.manager.TransactionImpl; 035import org.apache.geronimo.transaction.manager.TransactionManagerImpl; 036import org.apache.geronimo.transaction.manager.TransactionManagerMonitor; 037import org.apache.geronimo.transaction.manager.XidImpl; 038import org.apache.logging.log4j.ThreadContext; 039import org.javasimon.SimonManager; 040import org.javasimon.Stopwatch; 041import org.nuxeo.ecm.core.management.jtajca.TransactionMonitor; 042import org.nuxeo.ecm.core.management.jtajca.TransactionStatistics; 043import org.nuxeo.ecm.core.management.jtajca.internal.DefaultMonitorComponent.ServerInstance; 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 toggle(); 067 } 068 069 @Override 070 public void uninstall() { 071 if (tm == null) { 072 return; 073 } 074 unbindManagementInterface(); 075 if (enabled) { 076 toggle(); 077 } 078 } 079 080 protected ServerInstance self; 081 082 protected void bindManagementInterface() { 083 self = DefaultMonitorComponent.bind(TransactionMonitor.class, this); 084 } 085 086 protected void unbindManagementInterface() { 087 DefaultMonitorComponent.unbind(self); 088 self = null; 089 } 090 091 protected TransactionManagerImpl lookup() { 092 TransactionManager tm = NuxeoContainer.getTransactionManager(); 093 if (tm == null) { // try setup trough NuxeoTransactionManagerFactory 094 try { 095 tm = TransactionHelper.lookupTransactionManager(); 096 } catch (NamingException cause) { 097 throw new RuntimeException("Cannot lookup tx manager", cause); 098 } 099 } 100 if (!(tm instanceof TransactionManagerImpl)) { 101 return null; 102 } 103 return (TransactionManagerImpl) tm; 104 } 105 106 protected TransactionStatistics lastCommittedStatistics; 107 108 protected TransactionStatistics lastRollbackedStatistics; 109 110 protected final Map<Object, DefaultTransactionStatistics> activeStatistics = new HashMap<>(); 111 112 public static String id(Object key) { 113 if (key instanceof XidImpl) { 114 byte[] globalId = ((XidImpl) key).getGlobalTransactionId(); 115 StringBuilder sb = new StringBuilder(); 116 for (byte aGlobalId : globalId) { 117 sb.append(Integer.toHexString(aGlobalId)); 118 } 119 return sb.toString().replaceAll("0*$", ""); 120 } 121 return key.toString(); 122 } 123 124 public static String id(Transaction tx) { 125 return Integer.toHexString(tx.hashCode()); 126 } 127 128 @Override 129 public void threadAssociated(Transaction tx) { 130 long now = System.currentTimeMillis(); 131 Object key = tm.getTransactionKey(); 132 ThreadContext.put("tx", id(key)); 133 Stopwatch sw = SimonManager.getStopwatch("tx"); 134 final Thread thread = Thread.currentThread(); 135 DefaultTransactionStatistics info = new DefaultTransactionStatistics(key); 136 info.split = sw.start(); 137 info.threadName = thread.getName(); 138 info.status = TransactionStatistics.Status.fromTx(tx); 139 140 info.startTimestamp = now; 141 info.startCapturedContext = new Throwable("** start invoke context **"); 142 synchronized (this) { 143 activeStatistics.put(key, info); 144 } 145 if (TransactionStatistics.Status.ACTIVE == info.status) { 146 tm.registerInterposedSynchronization(this); // register end status 147 } 148 if (log.isTraceEnabled()) { 149 log.trace(info.toString()); 150 } 151 } 152 153 @Override 154 public void threadUnassociated(Transaction tx) { 155 try { 156 Object key = ((TransactionImpl) tx).getTransactionKey(); 157 DefaultTransactionStatistics stats; 158 synchronized (DefaultTransactionMonitor.class) { 159 stats = activeStatistics.remove(key); 160 } 161 if (stats == null) { 162 log.debug(key + " not found in active statistics map"); 163 return; 164 } 165 stats.split.stop(); 166 stats.split = null; 167 if (log.isTraceEnabled()) { 168 log.trace(stats); 169 } 170 if (TransactionStatistics.Status.COMMITTED.equals(stats.status)) { 171 lastCommittedStatistics = stats; 172 } else if (TransactionStatistics.Status.ROLLEDBACK.equals(stats.status)) { 173 lastRollbackedStatistics = stats; 174 } 175 } finally { 176 ThreadContext.remove("tx"); 177 } 178 } 179 180 @Override 181 public List<TransactionStatistics> getActiveStatistics() { 182 List<TransactionStatistics> l = new ArrayList<>(activeStatistics.values()); 183 l.sort((o1, o2) -> o1.getStartDate().compareTo(o2.getEndDate())); 184 return l; 185 } 186 187 @Override 188 public long getActiveCount() { 189 return tm.getActiveCount(); 190 } 191 192 @Override 193 public long getTotalCommits() { 194 return tm.getTotalCommits(); 195 } 196 197 @Override 198 public long getTotalRollbacks() { 199 return tm.getTotalRollbacks(); 200 } 201 202 @Override 203 public TransactionStatistics getLastCommittedStatistics() { 204 return lastCommittedStatistics; 205 } 206 207 @Override 208 public TransactionStatistics getLastRollbackedStatistics() { 209 return lastRollbackedStatistics; 210 } 211 212 protected DefaultTransactionStatistics thisStatistics() { 213 Object key = tm.getTransactionKey(); 214 DefaultTransactionStatistics stats; 215 synchronized (this) { 216 stats = activeStatistics.get(key); 217 } 218 if (stats == null) { 219 log.debug(key + " not found in active statistics map"); 220 } 221 return stats; 222 } 223 224 @Override 225 public void beforeCompletion() { 226 DefaultTransactionStatistics stats = thisStatistics(); 227 if (stats == null) { 228 return; 229 } 230 stats.endCapturedContext = new Throwable("** end invoke context **"); 231 } 232 233 @Override 234 public void afterCompletion(int code) { 235 DefaultTransactionStatistics stats = thisStatistics(); 236 if (stats == null) { 237 return; 238 } 239 stats.endTimestamp = System.currentTimeMillis(); 240 stats.status = TransactionStatistics.Status.fromCode(code); 241 switch (code) { 242 case Status.STATUS_COMMITTED: 243 lastCommittedStatistics = stats; 244 break; 245 case Status.STATUS_ROLLEDBACK: 246 lastRollbackedStatistics = stats; 247 stats.endCapturedContext = new Throwable("** rollback context **"); 248 break; 249 } 250 } 251 252 @Override 253 public boolean toggle() { 254 if (enabled) { 255 tm.removeTransactionAssociationListener(this); 256 activeStatistics.clear(); 257 enabled = false; 258 } else { 259 tm.addTransactionAssociationListener(this); 260 enabled = true; 261 } 262 return enabled; 263 } 264 265 @Override 266 public boolean getEnabled() { 267 return enabled; 268 } 269 270}