001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  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
019package org.apache.oozie.util.db;
020
021import com.google.common.base.Preconditions;
022import org.apache.oozie.util.XLog;
023
024import java.lang.reflect.InvocationTargetException;
025import java.util.concurrent.ThreadLocalRandom;
026import java.util.concurrent.atomic.AtomicLong;
027
028public class RuntimeExceptionInjector<E extends RuntimeException> {
029    private static final XLog LOG = XLog.getLog(RuntimeExceptionInjector.class);
030    private static final AtomicLong failureCounter = new AtomicLong(0);
031
032    private final Class<E> runtimeExceptionClass;
033    private final int failurePercent;
034
035    public RuntimeExceptionInjector(final Class<E> runtimeExceptionClass, final int failurePercent) {
036        Preconditions.checkArgument(failurePercent <= 100 && failurePercent >= 0,
037                "illegal value for failure %: " + failurePercent);
038
039        this.runtimeExceptionClass = runtimeExceptionClass;
040        this.failurePercent = failurePercent;
041    }
042
043    public void inject(final String errorMessage) {
044        LOG.trace("Trying to inject random failure. [errorMessage={0}]", errorMessage);
045
046        final ThreadLocalRandom random = ThreadLocalRandom.current();
047        final int randomVal = random.nextInt(0, 100); // range:  [0..99]
048
049        if (randomVal < failurePercent) {
050            final long count = failureCounter.incrementAndGet();
051            LOG.warn("Injecting random failure. [runtimeExceptionClass.name={0};count={1};errorMessage={2}]",
052                    runtimeExceptionClass.getName(), count, errorMessage);
053            E injected;
054
055            try {
056                injected = runtimeExceptionClass.getConstructor(String.class).newInstance(
057                        "injected random failure #" + count + " ." + errorMessage);
058            } catch (final InstantiationException | IllegalAccessException | InvocationTargetException
059                    | NoSuchMethodException outer) {
060                try {
061                    LOG.warn("Instantiating without error message. [runtimeExceptionClass.name={0};outer.message={1}]",
062                            runtimeExceptionClass.getName(), outer.getMessage());
063                    injected = runtimeExceptionClass.newInstance();
064                } catch (final InstantiationException | IllegalAccessException inner) {
065                    LOG.error("Could not instantiate. [runtimeExceptionClass.name={0};inner.message={1}]",
066                            runtimeExceptionClass.getName(), inner.getMessage());
067                    throw new RuntimeException(inner);
068                }
069
070            }
071
072            throw injected;
073        }
074
075        LOG.trace("Did not inject random failure.");
076    }
077}