001 /*
002 * $Id: ExceptionFactory.java,v 1.11 2010/07/17 12:04:48 oboehm Exp $
003 *
004 * Copyright (c) 2009 by Oliver Boehm
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *
018 * (c)reated 05.03.2009 by oliver (ob@aosd.de)
019 */
020 package patterntesting.exception;
021
022 import javax.management.JMException;
023
024 import org.apache.commons.logging.*;
025 import org.aspectj.lang.JoinPoint;
026 import org.aspectj.lang.reflect.CodeSignature;
027
028 import patterntesting.runtime.jmx.MBeanHelper;
029 import patterntesting.runtime.util.*;
030
031 /**
032 * This class can create for testing purpose any desired checked exception.
033 * Implemented with the help of Thomas.Darimont@web.de (via mailing list of
034 * aspectj-users@eclipse.org).
035 * <br/>
036 * It is realized as Singleton to be able to extract an MBean interface for the
037 * use via JMX.
038 * <br/>
039 * The exceptions are controlled by AbstractTestExceptionFactory.
040 * You will see the ExceptionFactory not before the first
041 * method marked as @TestException has finished (because it is realized as
042 * after advice). If you want to see it before
043 * call <tt>ExceptionFactory.getInstance()</tt> (with the creation of
044 * the instance it is also registered as MBean).
045 *
046 * @author <a href="boehm@javatux.de">oliver</a>
047 * @since 05.03.2009
048 * @version $Revision: 1.11 $
049 */
050 public final class ExceptionFactory implements ExceptionFactoryMBean {
051
052 private static final Log log = LogFactory.getLog(ExceptionFactory.class);
053 private static final ExceptionFactory instance;
054 /** number of provoked exceptions. */
055 private long numberOfProvoked = 0L;
056 /** maximal number of provoked exceptions. */
057 private long maxNumberOfProvoked = 0L;
058 /** the last provoked exception which was thrown. */
059 private Throwable lastProvoked;
060 /** only classes and subclasses of 'limitedTo' can be provoked. */
061 private Class<? extends Throwable> limitedTo = Throwable.class;
062 /** to exception to be fired */
063 private Class<? extends Throwable> fire = null;
064 /** the scope can be limited to a single class. */
065 private Class<?> scope;
066
067 static {
068 instance = new ExceptionFactory();
069 try {
070 if (Assertions.enabled) {
071 instance.registerMeAsMBean();
072 if (log.isDebugEnabled()) {
073 log.debug("ExceptionFactory registered as MBean");
074 }
075 } else {
076 if (log.isDebugEnabled()) {
077 log.debug("ExceptionFactory is disabled (no assertions)");
078 }
079 }
080 } catch (JMException e) {
081 log.info("can't register " + instance + " as MBean", e);
082 }
083 }
084
085 private ExceptionFactory() {
086 if (log.isTraceEnabled()) {
087 log.trace(this + " created");
088 }
089 }
090
091 /**
092 * We implement registerAsMBean ourself and not via MBeanRegistry
093 * interface because we don't need the other methods declared in this
094 * interface. And because the Ajdoc generation via maven failed
095 * (it does not recognize patterntesting-rt as AspectJ lib).
096 *
097 * @throws JMException if it can't be registered to JMX
098 */
099 private void registerMeAsMBean() throws JMException {
100 MBeanHelper.registerMBean(this);
101 }
102
103 /**
104 * Normally the ExceptionFactory register itself at JMX. But only if the
105 * class is loaded. To force the classloader to load the ExceptionFactory
106 * you can use the getInstance() method or this method here.
107 *
108 * @throws JMException if it can't be registerd at JMX
109 * @since 1.0
110 */
111 public static void registerAsMBean() throws JMException {
112 ExceptionFactory factory = ExceptionFactory.getInstance();
113 synchronized (factory) {
114 if (!MBeanHelper.isRegistered(factory)) {
115 factory.registerMeAsMBean();
116 }
117 }
118 }
119
120 /**
121 * It is realized as singleton because of the MBean interface.
122 *
123 * @return the only instance
124 */
125 public static ExceptionFactory getInstance() {
126 return instance;
127 }
128
129 /**
130 * Gets the maximal number of provoked exceptions
131 * (default is Long.MAX_VALUE).
132 *
133 * @return the number of provoked exceptions
134 * @see ExceptionFactoryMBean#getMaxNumberOfProvoked()
135 */
136 public synchronized long getMaxNumberOfProvoked() {
137 return this.maxNumberOfProvoked;
138 }
139
140 /**
141 * Gets the number of provoked exceptions.
142 *
143 * @return the number of provoked exceptions
144 * @see ExceptionFactoryMBean#getNumberOfProvoked()
145 */
146 public synchronized long getNumberOfProvoked() {
147 return this.numberOfProvoked;
148 }
149
150 /**
151 * Limit the number of provoked maximal total number of provoked exceptions
152 * to n (default is Long.MAX_VALUE).
153 *
154 * @param n the maximal number of provoked exceptions
155 * @see ExceptionFactoryMBean#setMaxNumberOfProvoked(long)
156 */
157 public synchronized void setMaxNumberOfProvoked(final long n) {
158 this.maxNumberOfProvoked = n;
159 }
160
161 /**
162 * Checks if is active.
163 *
164 * @return true if active
165 * @see ExceptionFactoryMBean#isActive()
166 */
167 public synchronized boolean isActive() {
168 return this.maxNumberOfProvoked > this.numberOfProvoked;
169 }
170
171 /**
172 * You can only provoke Exceptions if the active flag is set.
173 *
174 * @param active true or false
175 * @see ExceptionFactoryMBean#setActive(boolean)
176 */
177 public synchronized void setActive(final boolean active) {
178 this.maxNumberOfProvoked = active ? Long.MAX_VALUE
179 : this.numberOfProvoked;
180 }
181
182 /**
183 * Activate.
184 *
185 * @see ExceptionFactoryMBean#activate()
186 */
187 public synchronized void activate() {
188 this.setActive(true);
189 }
190
191 /**
192 * Activate once.
193 *
194 * @see ExceptionFactoryMBean#activateOnce()
195 */
196 public synchronized void activateOnce() {
197 this.maxNumberOfProvoked = this.numberOfProvoked + 1;
198 }
199
200 /**
201 * Deactivate.
202 *
203 * @see ExceptionFactoryMBean#deactivate()
204 */
205 public synchronized void deactivate() {
206 this.setActive(false);
207 }
208
209 /**
210 * To see if the (Sun) Java-VM was called with the option "-ea"
211 * ("enable asserts") this getter shows it.
212 *
213 * @return true if asserts are enabled
214 * @see ExceptionFactoryMBean#isAssertsEnabled()
215 */
216 public boolean isAssertsEnabled() {
217 return Assertions.enabled;
218 }
219
220 /**
221 * Gets the last provoked exception.
222 *
223 * @return the last exception which was thrown by one of the provoke methods
224 * @see ExceptionFactoryMBean#getLastProvoked()
225 */
226 public synchronized Throwable getLastProvoked() {
227 return lastProvoked;
228 }
229
230 /**
231 * Only exceptions of this returned type (or subclasses of it) are thrown.
232 * The return value is a String because of the use as MBean (works better
233 * in the JConsole).
234 *
235 * @return the limitedTo (e.g. "java.lang.Throwable")
236 * @deprecated replaced by {@link #getFire()} (will be removed with 1.2)
237 */
238 public synchronized String getLimitedTo() {
239 return limitedTo.getName();
240 }
241
242 /**
243 * You want to limit the provoked exceptions to IOException (or subclass)?
244 * Then give it as parameter to this setter method.
245 * </br>
246 * If you want to reset it and allow all Exceptions call
247 * <tt>setLimitedTo(Trowable.class)</tt>.
248 *
249 * @param limitedTo
250 * the limitedTo to set (e.g. IOException.class)
251 * @deprecated replaced by {@link #setFire(String)} (will be removed with 1.2)
252 */
253 public synchronized void setLimitedTo(
254 final Class<? extends Throwable> limitedTo) {
255 this.limitedTo = limitedTo;
256 }
257
258 /**
259 * You want to limit the provoked exceptions to IOException (or subclass)?
260 * Then give it as parameter to this setter method.
261 * </br>
262 * If you want to reset it and allow all Exceptions call
263 * <tt>setLimitedTo("java.lang.Throwable")</tt>.
264 * <br>
265 * For better use with the JConsole the class could given as String.
266 * But don't forget to give the complete classname (with package) as
267 * String.
268 *
269 * @param limitedTo e.g. "java.io.IOException"
270 * @throws ClassNotFoundException the class not found exception
271 * @deprecated replaced by {@link #setFire(String)} (will be removed with 1.2)
272 */
273 @SuppressWarnings("unchecked")
274 public synchronized void setLimitedTo(final String limitedTo)
275 throws ClassNotFoundException {
276 this.limitedTo = (Class<? extends Throwable>) Class.forName(limitedTo);
277 }
278
279 /**
280 * "Not limited" means, limitedTo is a super class of the given type.
281 * @param type the class type
282 * @return true or false
283 */
284 // TODO remove in v1.2
285 private synchronized boolean isNotLimited(final Class<?> type) {
286 if (type.equals(limitedTo)) {
287 return true;
288 }
289 if (type.equals(Throwable.class)) {
290 return false;
291 }
292 return isNotLimited(type.getSuperclass());
293 }
294
295 /**
296 * "can be fired" means, that the given type is a subclass of "fire".
297 * @param type the exception type
298 * @return true or false
299 */
300 private boolean canBeFired(final Class<? extends Throwable> type) {
301 // return this.fire.isAssignableFrom(type);
302 if (this.fire == null) {
303 return true;
304 }
305 return type.isAssignableFrom(this.fire);
306 }
307
308 /**
309 * Be careful - you can provoke any Exception with the method without the
310 * need to declare it with a throws statement. For example
311 * <pre>provoke(IOException.class)</pre>
312 * would throw an IOException.
313 *
314 * @param type e.g. IOException.class
315 * @see ExceptionThrower#provoke(Class)
316 */
317 public synchronized void provoke(final Class<? extends Throwable> type) {
318 if (Assertions.enabled && this.isActive() && this.isNotLimited(type)
319 && this.canBeFired(type)) {
320 fire(type);
321 } else {
322 if (log.isTraceEnabled()) {
323 log.trace("active flag not set or " + this.getFire()
324 + " cannot be fired here");
325 }
326 }
327 }
328
329 private void fire(final Class<? extends Throwable> type) {
330 this.numberOfProvoked++;
331 if (this.fire == null) {
332 ExceptionThrower.provoke(type);
333 } else {
334 ExceptionThrower.provoke(this.fire);
335 }
336 }
337
338 /**
339 * This methods throws one of the exception which is possible for the given
340 * joinpoint. But only if the joinpoint matches one of the registered
341 * objects, classes or threads.
342 *
343 * @param jp the joinpoint for which an exception should be provoked
344 */
345 @SuppressWarnings("unchecked")
346 public synchronized void provokeFor(JoinPoint jp) {
347 if ((this.scope == null) || this.matchScope(jp.getThis())) {
348 CodeSignature sig = (CodeSignature) jp.getSignature();
349 this.provokeOneOf(sig.getExceptionTypes());
350 } else {
351 if (log.isDebugEnabled()) {
352 log.debug("no excecption provoked for "
353 + JoinPointHelper.getAsShortString(jp)
354 + " because scope = " + this.getScope());
355 }
356 }
357 }
358
359 private boolean matchScope(Object target) {
360 return matchScope(target.getClass());
361 }
362
363 /**
364 * Checks if a scope is limited to the class itself. Subclasses and
365 * interfaces are also supported as parameter.
366 *
367 * @param target the target class
368 * @return true if target and scope are the same class
369 */
370 private boolean matchScope(Class<?> target) {
371 return this.scope.isAssignableFrom(target);
372 }
373
374 /**
375 * This method throws the first given Throwable type. If this fails the
376 * next element array is tried to be created as Throwable.
377 * <br/>
378 * You can only provoke an Exception if the active flag is set.
379 * <br/>
380 *
381 * @param types a class array with exception types
382 */
383 protected synchronized void provokeOneOf(final Class<? extends Throwable>[] types) {
384 if (this.isActive()) {
385 for (int i = 0; i < types.length; i++) {
386 if (isNotLimited(types[i]) && this.canBeFired(types[i])) {
387 fire(types[i]);
388 break;
389 }
390 }
391 } else {
392 if (log.isTraceEnabled()) {
393 log.trace("active flag not set or not a subclass of "
394 + this.limitedTo + " -> no "
395 + Converter.toString(types) + " thrown");
396 }
397 }
398 }
399
400 /**
401 * To limit the exception to be thrown for a given class you can use
402 * this method here.
403 * <br/>
404 * This setter is not part of the ExceptionFactoryMBean interface because
405 * JMX allows only one setter for an attribute. Otherwise you'll get an
406 * <i>javax.management.NotCompliantMBeanException:
407 * Attribute Scope has more than one setter</i>.
408 *
409 * @param target the target
410 * @see patterntesting.exception.ExceptionFactoryMBean#setScope(java.lang.String)
411 */
412 public synchronized void setScope(final Class<?> target) {
413 this.scope = target;
414 }
415
416 /**
417 * To limit the exception to be thrown for a given class you can use
418 * this method here.
419 * <br/>
420 * A string is excepted instead of a Class object because of the jconsole.
421 * With the jconsole only basic types or strings are available as input
422 * fields.
423 *
424 * @since 1.1
425 * @param classname for which an exception should be thrown
426 * @see patterntesting.exception.ExceptionFactoryMBean#setScope(java.lang.String)
427 */
428 public synchronized void setScope(final String classname) {
429 try {
430 Class<?> clazz = Class.forName(classname);
431 this.setScope(clazz);
432 } catch (ClassNotFoundException e) {
433 throw new IllegalArgumentException(classname + " not found", e);
434 }
435 }
436
437 /**
438 * To set the scope back to "all classes" use this method here.
439 *
440 * @since 1.1
441 */
442 public synchronized void resetScope() {
443 this.scope = null;
444
445 }
446
447 /**
448 * Returns the scope for which the exceptions will be thrown.
449 * The default value is "all classes" if the scope is not set.
450 *
451 * @since 1.1
452 * @return class for which an exception will be thrown.
453 * @see patterntesting.exception.ExceptionFactoryMBean#getScope()
454 */
455 public synchronized String getScope() {
456 if (this.scope == null) {
457 return ALL_CLASSES;
458 }
459 return this.scope.getName();
460 }
461
462 /**
463 * Only exceptions of this returned type will be fired.
464 * The return value is a String because of the use as MBean (works better
465 * in the JConsole).
466 *
467 * @return the exeption to be fired (e.g. "java.lang.Throwable")
468 * @see patterntesting.exception.ExceptionFactoryMBean#getFire()
469 * @since 1.1
470 */
471 public synchronized String getFire() {
472 if (this.fire == null) {
473 return "all exceptions";
474 }
475 return this.fire.getName();
476 }
477
478 /**
479 * You want to provoked an SocketException whenever it is possible?
480 * Then give it as parameter to this setter method.
481 * <br>
482 * For better use with the JConsole the class could given as String.
483 * But don't forget to give the complete classname (with package) as
484 * String.
485 *
486 * @param classname e.g. "java.net.SocketException"
487 * @throws ClassNotFoundException if parameter is not a class name
488 * @see patterntesting.exception.ExceptionFactoryMBean#setFire(java.lang.String)
489 * @since 1.1
490 */
491 @SuppressWarnings("unchecked")
492 public synchronized void setFire(String classname) throws ClassNotFoundException {
493 this.fire = (Class<? extends Throwable>) Class.forName(classname);
494 }
495
496 /**
497 * You want to provoked an SocketException whenever it is possible?
498 * Then give it as parameter to this setter method.
499 *
500 * @param fire e.g. SocketException.class
501 * @since 1.1
502 */
503 public synchronized void setFire(Class<? extends Throwable> fire) {
504 this.fire = fire;
505 }
506
507 /**
508 * Allows again that all exceptions would be fired.
509 * @see patterntesting.exception.ExceptionFactoryMBean#resetFire()
510 * @since 1.1
511 */
512 public synchronized void resetFire() {
513 this.fire = null;
514 }
515
516 /**
517 * Resets all preferences and deactivates ExceptionFactory.
518 * @see patterntesting.exception.ExceptionFactoryMBean#reset()
519 * @since 1.1
520 */
521 public synchronized void reset() {
522 this.resetScope();
523 this.resetFire();
524 this.deactivate();
525 this.limitedTo = Throwable.class;
526 }
527
528 }