예제 #1
0
/** Container for the language and body of a script. */
@Plugin(name = "Script", category = Node.CATEGORY, printObject = true)
public class Script extends AbstractScript {

  private static final Logger logger = StatusLogger.getLogger();

  public Script(String name, String language, String scriptText) {
    super(name, language, scriptText);
  }

  @PluginFactory
  public static Script createScript(
      // @formatter:off
      @PluginAttribute("name") final String name,
      @PluginAttribute("language") String language,
      @PluginValue("scriptText") final String scriptText) {
    // @formatter:on
    if (language == null) {
      logger.info("No script language supplied, defaulting to {}", DEFAULT_LANGUAGE);
      language = DEFAULT_LANGUAGE;
    }
    if (scriptText == null) {
      logger.error("No scriptText attribute provided for ScriptFile {}", name);
      return null;
    }
    return new Script(name, language, scriptText);
  }

  @Override
  public String toString() {
    return getName() != null ? getName() : super.toString();
  }
}
 @AfterClass
 public static void cleanupClass() {
   System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY);
   final LoggerContext ctx = LoggerContext.getContext();
   ctx.reconfigure();
   StatusLogger.getLogger().reset();
 }
예제 #3
0
/** Represents a key/value pair in the configuration. */
@Plugin(name = "property", category = Node.CATEGORY, printObject = true)
public final class Property {

  private static final Logger LOGGER = StatusLogger.getLogger();

  private final String name;
  private final String value;
  private final boolean valueNeedsLookup;

  private Property(final String name, final String value) {
    this.name = name;
    this.value = value;
    this.valueNeedsLookup = value != null && value.contains("${");
  }

  /**
   * Returns the property name.
   *
   * @return the property name.
   */
  public String getName() {
    return name;
  }

  /**
   * Returns the property value.
   *
   * @return the value of the property.
   */
  public String getValue() {
    return Objects.toString(
        value, Strings.EMPTY); // LOG4J2-1313 null would be same as Property not existing
  }

  /**
   * Returns {@code true} if the value contains a substitutable property that requires a lookup to be resolved.
   * @return {@code true} if the value contains {@code "${"}, {@code false} otherwise
   */
  public boolean isValueNeedsLookup() {
    return valueNeedsLookup;
  }

  /**
   * Creates a Property.
   *
   * @param name The key.
   * @param value The value.
   * @return A Property.
   */
  @PluginFactory
  public static Property createProperty(
      @PluginAttribute("name") final String name, @PluginValue("value") final String value) {
    if (name == null) {
      LOGGER.error("Property name cannot be null");
    }
    return new Property(name, value);
  }

  @Override
  public String toString() {
    return name + '=' + getValue();
  }
}
/**
 * PathCondition that accepts paths after the accumulated file size threshold is exceeded during the
 * file tree walk.
 */
@Plugin(name = "IfAccumulatedFileSize", category = "Core", printObject = true)
public final class IfAccumulatedFileSize implements PathCondition {
  private static final Logger LOGGER = StatusLogger.getLogger();
  private final long thresholdBytes;
  private long accumulatedSize;
  private final PathCondition[] nestedConditions;

  private IfAccumulatedFileSize(final long thresholdSize, final PathCondition[] nestedConditions) {
    if (thresholdSize <= 0) {
      throw new IllegalArgumentException(
          "Count must be a positive integer but was " + thresholdSize);
    }
    this.thresholdBytes = thresholdSize;
    this.nestedConditions =
        nestedConditions == null
            ? new PathCondition[0]
            : Arrays.copyOf(nestedConditions, nestedConditions.length);
  }

  public long getThresholdBytes() {
    return thresholdBytes;
  }

  public List<PathCondition> getNestedConditions() {
    return Collections.unmodifiableList(Arrays.asList(nestedConditions));
  }

  /*
   * (non-Javadoc)
   *
   * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#accept(java.nio.file.Path,
   * java.nio.file.Path, java.nio.file.attribute.BasicFileAttributes)
   */
  @Override
  public boolean accept(
      final Path basePath, final Path relativePath, final BasicFileAttributes attrs) {
    accumulatedSize += attrs.size();
    final boolean result = accumulatedSize > thresholdBytes;
    final String match = result ? ">" : "<=";
    final String accept = result ? "ACCEPTED" : "REJECTED";
    LOGGER.trace(
        "IfAccumulatedFileSize {}: {} accumulated size '{}' {} thresholdBytes '{}'",
        accept,
        relativePath,
        accumulatedSize,
        match,
        thresholdBytes);
    if (result) {
      return IfAll.accept(nestedConditions, basePath, relativePath, attrs);
    }
    return result;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#beforeFileTreeWalk()
   */
  @Override
  public void beforeFileTreeWalk() {
    accumulatedSize = 0;
    IfAll.beforeFileTreeWalk(nestedConditions);
  }

  /**
   * Create an IfAccumulatedFileSize condition.
   *
   * @param threshold The threshold accumulated file size from which files will be deleted.
   * @return An IfAccumulatedFileSize condition.
   */
  @PluginFactory
  public static IfAccumulatedFileSize createFileSizeCondition( //
      // @formatter:off
      @PluginAttribute("exceeds") final String size,
      @PluginElement("PathConditions") final PathCondition... nestedConditions) {
    // @formatter:on

    if (size == null) {
      LOGGER.error("IfAccumulatedFileSize missing mandatory size threshold.");
    }
    final long threshold = size == null ? Long.MAX_VALUE : FileSize.parse(size, Long.MAX_VALUE);
    return new IfAccumulatedFileSize(threshold, nestedConditions);
  }

  @Override
  public String toString() {
    final String nested =
        nestedConditions.length == 0 ? "" : " AND " + Arrays.toString(nestedConditions);
    return "IfAccumulatedFileSize(exceeds=" + thresholdBytes + nested + ")";
  }
}
public abstract class AbstractRolloverStrategy implements RolloverStrategy {
  protected static final Logger LOGGER = StatusLogger.getLogger();

  public AbstractRolloverStrategy() {}
}
예제 #6
0
/** The MongoDB implementation of {@link NoSqlProvider}. */
@Plugin(name = "MongoDb", category = "Core", printObject = true)
public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> {

  private static final WriteConcern DEFAULT_WRITE_CONCERN = WriteConcern.ACKNOWLEDGED;
  private static final Logger LOGGER = StatusLogger.getLogger();
  private static final int DEFAULT_PORT = 27017;

  private final String collectionName;
  private final DB database;
  private final String description;
  private final WriteConcern writeConcern;

  private MongoDbProvider(
      final DB database,
      final WriteConcern writeConcern,
      final String collectionName,
      final String description) {
    this.database = database;
    this.writeConcern = writeConcern;
    this.collectionName = collectionName;
    this.description = "mongoDb{ " + description + " }";
  }

  @Override
  public MongoDbConnection getConnection() {
    return new MongoDbConnection(this.database, this.writeConcern, this.collectionName);
  }

  @Override
  public String toString() {
    return this.description;
  }

  /**
   * Factory method for creating a MongoDB provider within the plugin manager.
   *
   * @param collectionName The name of the MongoDB collection to which log events should be written.
   * @param writeConcernConstant The {@link WriteConcern} constant to control writing details,
   *     defaults to {@link WriteConcern#ACKNOWLEDGED}.
   * @param writeConcernConstantClassName The name of a class containing the aforementioned static
   *     WriteConcern constant. Defaults to {@link WriteConcern}.
   * @param databaseName The name of the MongoDB database containing the collection to which log
   *     events should be written. Mutually exclusive with {@code
   *     factoryClassName&factoryMethodName!=null}.
   * @param server The host name of the MongoDB server, defaults to localhost and mutually exclusive
   *     with {@code factoryClassName&factoryMethodName!=null}.
   * @param port The port the MongoDB server is listening on, defaults to the default MongoDB port
   *     and mutually exclusive with {@code factoryClassName&factoryMethodName!=null}.
   * @param userName The username to authenticate against the MongoDB server with.
   * @param password The password to authenticate against the MongoDB server with.
   * @param factoryClassName A fully qualified class name containing a static factory method capable
   *     of returning a {@link DB} or a {@link MongoClient}.
   * @param factoryMethodName The name of the public static factory method belonging to the
   *     aforementioned factory class.
   * @return a new MongoDB provider.
   */
  @PluginFactory
  public static MongoDbProvider createNoSqlProvider(
      @PluginAttribute("collectionName") final String collectionName,
      @PluginAttribute("writeConcernConstant") final String writeConcernConstant,
      @PluginAttribute("writeConcernConstantClass") final String writeConcernConstantClassName,
      @PluginAttribute("databaseName") final String databaseName,
      @PluginAttribute(value = "server", defaultString = "localhost") @ValidHost
          final String server,
      @PluginAttribute(value = "port", defaultString = "" + DEFAULT_PORT) @ValidPort
          final String port,
      @PluginAttribute("userName") final String userName,
      @PluginAttribute(value = "password", sensitive = true) final String password,
      @PluginAttribute("factoryClassName") final String factoryClassName,
      @PluginAttribute("factoryMethodName") final String factoryMethodName) {
    DB database;
    String description;
    if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
      try {
        final Class<?> factoryClass = LoaderUtil.loadClass(factoryClassName);
        final Method method = factoryClass.getMethod(factoryMethodName);
        final Object object = method.invoke(null);

        if (object instanceof DB) {
          database = (DB) object;
        } else if (object instanceof MongoClient) {
          if (Strings.isNotEmpty(databaseName)) {
            database = ((MongoClient) object).getDB(databaseName);
          } else {
            LOGGER.error(
                "The factory method [{}.{}()] returned a MongoClient so the database name is "
                    + "required.",
                factoryClassName,
                factoryMethodName);
            return null;
          }
        } else if (object == null) {
          LOGGER.error(
              "The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName);
          return null;
        } else {
          LOGGER.error(
              "The factory method [{}.{}()] returned an unsupported type [{}].",
              factoryClassName,
              factoryMethodName,
              object.getClass().getName());
          return null;
        }

        description = "database=" + database.getName();
        final List<ServerAddress> addresses = database.getMongo().getAllAddress();
        if (addresses.size() == 1) {
          description +=
              ", server=" + addresses.get(0).getHost() + ", port=" + addresses.get(0).getPort();
        } else {
          description += ", servers=[";
          for (final ServerAddress address : addresses) {
            description += " { " + address.getHost() + ", " + address.getPort() + " } ";
          }
          description += "]";
        }
      } catch (final ClassNotFoundException e) {
        LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e);
        return null;
      } catch (final NoSuchMethodException e) {
        LOGGER.error(
            "The factory class [{}] does not have a no-arg method named [{}].",
            factoryClassName,
            factoryMethodName,
            e);
        return null;
      } catch (final Exception e) {
        LOGGER.error(
            "The factory method [{}.{}()] could not be invoked.",
            factoryClassName,
            factoryMethodName,
            e);
        return null;
      }
    } else if (Strings.isNotEmpty(databaseName)) {
      final List<MongoCredential> credentials = new ArrayList<>();
      description = "database=" + databaseName;
      if (Strings.isNotEmpty(userName) && Strings.isNotEmpty(password)) {
        description +=
            ", username="******", passwordHash="
                + NameUtil.md5(password + MongoDbProvider.class.getName());
        credentials.add(
            MongoCredential.createCredential(userName, databaseName, password.toCharArray()));
      }
      try {
        final int portInt = TypeConverters.convert(port, int.class, DEFAULT_PORT);
        description += ", server=" + server + ", port=" + portInt;
        database =
            new MongoClient(new ServerAddress(server, portInt), credentials).getDB(databaseName);
      } catch (final Exception e) {
        LOGGER.error(
            "Failed to obtain a database instance from the MongoClient at server [{}] and "
                + "port [{}].",
            server,
            port);
        return null;
      }
    } else {
      LOGGER.error("No factory method was provided so the database name is required.");
      return null;
    }

    try {
      database.getCollectionNames(); // Check if the database actually requires authentication
    } catch (final Exception e) {
      LOGGER.error(
          "The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider.",
          e);
      return null;
    }

    final WriteConcern writeConcern =
        toWriteConcern(writeConcernConstant, writeConcernConstantClassName);

    return new MongoDbProvider(database, writeConcern, collectionName, description);
  }

  private static WriteConcern toWriteConcern(
      final String writeConcernConstant, final String writeConcernConstantClassName) {
    WriteConcern writeConcern;
    if (Strings.isNotEmpty(writeConcernConstant)) {
      if (Strings.isNotEmpty(writeConcernConstantClassName)) {
        try {
          final Class<?> writeConcernConstantClass =
              LoaderUtil.loadClass(writeConcernConstantClassName);
          final Field field = writeConcernConstantClass.getField(writeConcernConstant);
          writeConcern = (WriteConcern) field.get(null);
        } catch (final Exception e) {
          LOGGER.error(
              "Write concern constant [{}.{}] not found, using default.",
              writeConcernConstantClassName,
              writeConcernConstant);
          writeConcern = DEFAULT_WRITE_CONCERN;
        }
      } else {
        writeConcern = WriteConcern.valueOf(writeConcernConstant);
        if (writeConcern == null) {
          LOGGER.warn(
              "Write concern constant [{}] not found, using default.", writeConcernConstant);
          writeConcern = DEFAULT_WRITE_CONCERN;
        }
      }
    } else {
      writeConcern = DEFAULT_WRITE_CONCERN;
    }
    return writeConcern;
  }
}
예제 #7
0
/**
 * LogEventListener server that receives LogEvents over a JMS {@link javax.jms.Destination}.
 *
 * @since 2.1
 */
public class JmsServer extends LogEventListener implements MessageListener, LifeCycle {

  private static final Logger LOGGER = StatusLogger.getLogger();
  private final AtomicReference<State> state = new AtomicReference<State>(State.INITIALIZED);
  private final JmsManager jmsManager;
  private MessageConsumer messageConsumer;

  public JmsServer(
      final String connectionFactoryBindingName,
      final String destinationBindingName,
      final String username,
      final String password) {
    final String managerName = JmsServer.class.getName() + '@' + JmsServer.class.hashCode();
    final JndiManager jndiManager = JndiManager.getDefaultManager(managerName);
    jmsManager =
        JmsManager.getJmsManager(
            managerName,
            jndiManager,
            connectionFactoryBindingName,
            destinationBindingName,
            username,
            password);
  }

  @Override
  public void onMessage(final Message message) {
    try {
      if (message instanceof ObjectMessage) {
        final Object body = ((ObjectMessage) message).getObject();
        if (body instanceof LogEvent) {
          log((LogEvent) body);
        } else {
          LOGGER.warn(
              "Expected ObjectMessage to contain LogEvent. Got type {} instead.", body.getClass());
        }
      } else {
        LOGGER.warn(
            "Received message of type {} and JMSType {} which cannot be handled.",
            message.getClass(),
            message.getJMSType());
      }
    } catch (final JMSException e) {
      LOGGER.catching(e);
    }
  }

  @Override
  public void start() {
    if (state.compareAndSet(State.INITIALIZED, State.STARTING)) {
      try {
        messageConsumer = jmsManager.createMessageConsumer();
        messageConsumer.setMessageListener(this);
      } catch (final JMSException e) {
        throw new LoggingException(e);
      }
    }
  }

  @Override
  public void stop() {
    try {
      messageConsumer.close();
    } catch (final JMSException ignored) {
    }
    jmsManager.release();
  }

  @Override
  public boolean isStarted() {
    return state.get() == State.STARTED;
  }

  @Override
  public boolean isStopped() {
    return state.get() == State.STOPPED;
  }
}
/**
 * Creates {@link AsyncEventRouter} instances based on user-specified system properties. The {@code
 * AsyncEventRouter} created by this factory is used in AsyncLogger, AsyncLoggerConfig and
 * AsyncAppender to control if events are logged in the current thread, the background thread, or
 * discarded.
 *
 * <p>Property {@code "log4j2.AsyncEventRouter"} controls the routing behaviour. If this property is
 * not specified or has value {@code "Default"}, this factory creates {@link
 * DefaultAsyncEventRouter} objects.
 *
 * <p>If this property has value {@code "Discard"}, this factory creates {@link
 * DiscardingAsyncEventRouter} objects. By default, this router discards events of level {@code
 * INFO}, {@code DEBUG} and {@code TRACE} if the queue is full. This can be adjusted with property
 * {@code "log4j2.DiscardThreshold"} (name of the level at which to start discarding).
 *
 * <p>For any other value, this factory interprets the value as the fully qualified name of a class
 * implementing the {@link AsyncEventRouter} interface. The class must have a default constructor.
 *
 * @since 2.6
 */
public class AsyncEventRouterFactory {
  static final String PROPERTY_NAME_ASYNC_EVENT_ROUTER = "log4j2.AsyncEventRouter";
  static final String PROPERTY_VALUE_DEFAULT_ASYNC_EVENT_ROUTER = "Default";
  static final String PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER = "Discard";
  static final String PROPERTY_NAME_DISCARDING_THRESHOLD_LEVEL = "log4j2.DiscardThreshold";

  private static final Logger LOGGER = StatusLogger.getLogger();

  /**
   * Creates and returns {@link AsyncEventRouter} instances based on user-specified system
   * properties.
   *
   * <p>Property {@code "log4j2.AsyncEventRouter"} controls the routing behaviour. If this property
   * is not specified or has value {@code "Default"}, this method returns {@link
   * DefaultAsyncEventRouter} objects.
   *
   * <p>If this property has value {@code "Discard"}, this method returns {@link
   * DiscardingAsyncEventRouter} objects.
   *
   * <p>For any other value, this method interprets the value as the fully qualified name of a class
   * implementing the {@link AsyncEventRouter} interface. The class must have a default constructor.
   *
   * @return a new AsyncEventRouter
   */
  public static AsyncEventRouter create() {
    final String router =
        PropertiesUtil.getProperties().getStringProperty(PROPERTY_NAME_ASYNC_EVENT_ROUTER);
    if (router == null
        || PROPERTY_VALUE_DEFAULT_ASYNC_EVENT_ROUTER.equals(router)
        || DefaultAsyncEventRouter.class.getSimpleName().equals(router)
        || DefaultAsyncEventRouter.class.getName().equals(router)) {
      return new DefaultAsyncEventRouter();
    }
    if (PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER.equals(router)
        || DiscardingAsyncEventRouter.class.getSimpleName().equals(router)
        || DiscardingAsyncEventRouter.class.getName().equals(router)) {
      return createDiscardingAsyncEventRouter();
    }
    return createCustomRouter(router);
  }

  private static AsyncEventRouter createCustomRouter(final String router) {
    try {
      final Class<? extends AsyncEventRouter> cls =
          LoaderUtil.loadClass(router).asSubclass(AsyncEventRouter.class);
      LOGGER.debug("Creating custom AsyncEventRouter '{}'", router);
      return cls.newInstance();
    } catch (final Exception ex) {
      LOGGER.debug(
          "Using DefaultAsyncEventRouter. Could not create custom AsyncEventRouter '{}': {}",
          router,
          ex.toString());
      return new DefaultAsyncEventRouter();
    }
  }

  private static AsyncEventRouter createDiscardingAsyncEventRouter() {
    final PropertiesUtil util = PropertiesUtil.getProperties();
    final String level =
        util.getStringProperty(PROPERTY_NAME_DISCARDING_THRESHOLD_LEVEL, Level.INFO.name());
    final Level thresholdLevel = Level.toLevel(level, Level.INFO);
    LOGGER.debug("Creating custom DiscardingAsyncEventRouter(discardThreshold:{})", thresholdLevel);
    return new DiscardingAsyncEventRouter(thresholdLevel);
  }
}
예제 #9
0
/**
 * Base class for PluginVisitor implementations. Provides convenience methods as well as all method
 * implementations other than the {@code visit} method.
 *
 * @param <A> the Plugin annotation type.
 */
public abstract class AbstractPluginVisitor<A extends Annotation> implements PluginVisitor<A> {

  protected static final Logger LOGGER = StatusLogger.getLogger();

  protected final Class<A> clazz;
  protected A annotation;
  protected String[] aliases;
  protected Class<?> conversionType;
  protected StrSubstitutor substitutor;
  protected Member member;

  /**
   * This constructor must be overridden by implementation classes as a no-arg constructor.
   *
   * @param clazz the annotation class this PluginVisitor is for.
   */
  protected AbstractPluginVisitor(final Class<A> clazz) {
    this.clazz = clazz;
  }

  @SuppressWarnings("unchecked")
  @Override
  public PluginVisitor<A> setAnnotation(final Annotation annotation) {
    final Annotation a = Assert.requireNonNull(annotation, "No annotation was provided");
    if (this.clazz.isInstance(a)) {
      this.annotation = (A) a;
    }
    return this;
  }

  @Override
  public PluginVisitor<A> setAliases(final String... aliases) {
    this.aliases = aliases;
    return this;
  }

  @Override
  public PluginVisitor<A> setConversionType(final Class<?> conversionType) {
    this.conversionType =
        Assert.requireNonNull(conversionType, "No conversion type class was provided");
    return this;
  }

  @Override
  public PluginVisitor<A> setStrSubstitutor(final StrSubstitutor substitutor) {
    this.substitutor = Assert.requireNonNull(substitutor, "No StrSubstitutor was provided");
    return this;
  }

  @Override
  public PluginVisitor<A> setMember(final Member member) {
    this.member = member;
    return this;
  }

  /**
   * Removes an Entry from a given Map using a key name and aliases for that key. Keys are
   * case-insensitive.
   *
   * @param attributes the Map to remove an Entry from.
   * @param name the key name to look up.
   * @param aliases optional aliases of the key name to look up.
   * @return the value corresponding to the given key or {@code null} if nonexistent.
   */
  protected static String removeAttributeValue(
      final Map<String, String> attributes, final String name, final String... aliases) {
    for (final Map.Entry<String, String> entry : attributes.entrySet()) {
      final String key = entry.getKey();
      final String value = entry.getValue();
      if (key.equalsIgnoreCase(name)) {
        attributes.remove(key);
        return value;
      }
      if (aliases != null) {
        for (final String alias : aliases) {
          if (key.equalsIgnoreCase(alias)) {
            attributes.remove(key);
            return value;
          }
        }
      }
    }
    return null;
  }

  /**
   * Converts the given value into the configured type falling back to the provided default value.
   *
   * @param value the value to convert.
   * @param defaultValue the fallback value to use in case of no value or an error.
   * @return the converted value whether that be based on the given value or the default value.
   */
  protected Object convert(final String value, final Object defaultValue) {
    if (defaultValue instanceof String) {
      return TypeConverters.convert(
          value, this.conversionType, Strings.trimToNull((String) defaultValue));
    }
    return TypeConverters.convert(value, this.conversionType, defaultValue);
  }
}
예제 #10
0
/**
 * AsyncLogger is a logger designed for high throughput and low latency logging. It does not perform
 * any I/O in the calling (application) thread, but instead hands off the work to another thread as
 * soon as possible. The actual logging is performed in the background thread. It uses the LMAX
 * Disruptor library for inter-thread communication. (<a
 * href="http://lmax-exchange.github.com/disruptor/"
 * >http://lmax-exchange.github.com/disruptor/</a>)
 *
 * <p>To use AsyncLogger, specify the System property {@code
 * -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector} before you
 * obtain a Logger, and all Loggers returned by LogManager.getLogger will be AsyncLoggers.
 *
 * <p>Note that for performance reasons, this logger does not include source location by default.
 * You need to specify {@code includeLocation="true"} in the configuration or any %class, %location
 * or %line conversion patterns in your log4j.xml configuration will produce either a "?" character
 * or no output at all.
 *
 * <p>For best performance, use AsyncLogger with the RandomAccessFileAppender or
 * RollingRandomAccessFileAppender, with immediateFlush=false. These appenders have built-in support
 * for the batching mechanism used by the Disruptor library, and they will flush to disk at the end
 * of each batch. This means that even with immediateFlush=false, there will never be any items left
 * in the buffer; all log events will all be written to disk in a very efficient manner.
 */
public class AsyncLogger extends Logger {
  private static final long serialVersionUID = 1L;
  private static final int SLEEP_MILLIS_BETWEEN_DRAIN_ATTEMPTS = 50;
  private static final int MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN = 200;
  private static final int RINGBUFFER_MIN_SIZE = 128;
  private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024;
  private static final StatusLogger LOGGER = StatusLogger.getLogger();
  private static final ThreadNameStrategy THREAD_NAME_STRATEGY = ThreadNameStrategy.create();

  /** Strategy for deciding whether thread name should be cached or not. */
  static enum ThreadNameStrategy { // LOG4J2-467
    CACHED {
      @Override
      public String getThreadName(final Info info) {
        return info.cachedThreadName;
      }
    },
    UNCACHED {
      @Override
      public String getThreadName(final Info info) {
        return Thread.currentThread().getName();
      }
    };

    abstract String getThreadName(Info info);

    static ThreadNameStrategy create() {
      final String name =
          PropertiesUtil.getProperties()
              .getStringProperty("AsyncLogger.ThreadNameStrategy", CACHED.name());
      try {
        return ThreadNameStrategy.valueOf(name);
      } catch (final Exception ex) {
        LOGGER.debug(
            "Using AsyncLogger.ThreadNameStrategy.CACHED: '{}' not valid: {}", name, ex.toString());
        return CACHED;
      }
    }
  }

  private static volatile Disruptor<RingBufferLogEvent> disruptor;
  private static final Clock CLOCK = ClockFactory.getClock();
  private static volatile NanoClock nanoClock = new DummyNanoClock();

  private static final ExecutorService EXECUTOR =
      Executors.newSingleThreadExecutor(new DaemonThreadFactory("AsyncLogger-"));

  static {
    initInfoForExecutorThread();
    LOGGER.debug("AsyncLogger.ThreadNameStrategy={}", THREAD_NAME_STRATEGY);
    final int ringBufferSize = calculateRingBufferSize();

    final WaitStrategy waitStrategy = createWaitStrategy();
    disruptor =
        new Disruptor<>(
            RingBufferLogEvent.FACTORY, ringBufferSize, EXECUTOR, ProducerType.MULTI, waitStrategy);
    disruptor.handleExceptionsWith(getExceptionHandler());
    disruptor.handleEventsWith(new RingBufferLogEventHandler());

    LOGGER.debug(
        "Starting AsyncLogger disruptor with ringbuffer size {}...",
        disruptor.getRingBuffer().getBufferSize());
    disruptor.start();
  }

  /**
   * Constructs an {@code AsyncLogger} with the specified context, name and message factory.
   *
   * @param context context of this logger
   * @param name name of this logger
   * @param messageFactory message factory of this logger
   */
  public AsyncLogger(
      final LoggerContext context, final String name, final MessageFactory messageFactory) {
    super(context, name, messageFactory);
  }

  private static int calculateRingBufferSize() {
    int ringBufferSize = RINGBUFFER_DEFAULT_SIZE;
    final String userPreferredRBSize =
        PropertiesUtil.getProperties()
            .getStringProperty("AsyncLogger.RingBufferSize", String.valueOf(ringBufferSize));
    try {
      int size = Integer.parseInt(userPreferredRBSize);
      if (size < RINGBUFFER_MIN_SIZE) {
        size = RINGBUFFER_MIN_SIZE;
        LOGGER.warn(
            "Invalid RingBufferSize {}, using minimum size {}.",
            userPreferredRBSize,
            RINGBUFFER_MIN_SIZE);
      }
      ringBufferSize = size;
    } catch (final Exception ex) {
      LOGGER.warn(
          "Invalid RingBufferSize {}, using default size {}.", userPreferredRBSize, ringBufferSize);
    }
    return Integers.ceilingNextPowerOfTwo(ringBufferSize);
  }

  /**
   * Initialize an {@code Info} object that is threadlocal to the consumer/appender thread. This
   * Info object uniquely has attribute {@code isAppenderThread} set to {@code true}. All other Info
   * objects will have this attribute set to {@code false}. This allows us to detect Logger.log()
   * calls initiated from the appender thread, which may cause deadlock when the RingBuffer is full.
   * (LOG4J2-471)
   */
  private static void initInfoForExecutorThread() {
    EXECUTOR.submit(
        new Runnable() {
          @Override
          public void run() {
            final boolean isAppenderThread = true;
            final Info info =
                new Info(
                    new RingBufferLogEventTranslator(), //
                    Thread.currentThread().getName(),
                    isAppenderThread);
            Info.THREADLOCAL.set(info);
          }
        });
  }

  private static WaitStrategy createWaitStrategy() {
    final String strategy =
        PropertiesUtil.getProperties().getStringProperty("AsyncLogger.WaitStrategy");
    LOGGER.debug("property AsyncLogger.WaitStrategy={}", strategy);
    if ("Sleep".equals(strategy)) {
      return new SleepingWaitStrategy();
    } else if ("Yield".equals(strategy)) {
      return new YieldingWaitStrategy();
    } else if ("Block".equals(strategy)) {
      return new BlockingWaitStrategy();
    }
    LOGGER.debug("disruptor event handler uses BlockingWaitStrategy");
    return new BlockingWaitStrategy();
  }

  private static ExceptionHandler<RingBufferLogEvent> getExceptionHandler() {
    final String cls =
        PropertiesUtil.getProperties().getStringProperty("AsyncLogger.ExceptionHandler");
    if (cls == null) {
      LOGGER.debug("No AsyncLogger.ExceptionHandler specified");
      return null;
    }
    try {
      @SuppressWarnings("unchecked")
      final ExceptionHandler<RingBufferLogEvent> result =
          Loader.newCheckedInstanceOf(cls, ExceptionHandler.class);
      LOGGER.debug("AsyncLogger.ExceptionHandler={}", result);
      return result;
    } catch (final Exception ignored) {
      LOGGER.debug("AsyncLogger.ExceptionHandler not set: error creating " + cls + ": ", ignored);
      return null;
    }
  }

  /** Tuple with the event translator and thread name for a thread. */
  static class Info {
    private static final ThreadLocal<Info> THREADLOCAL =
        new ThreadLocal<Info>() {
          @Override
          protected Info initialValue() {
            // by default, set isAppenderThread to false
            return new Info(
                new RingBufferLogEventTranslator(), Thread.currentThread().getName(), false);
          }
        };
    private final RingBufferLogEventTranslator translator;
    private final String cachedThreadName;
    private final boolean isAppenderThread;

    public Info(
        final RingBufferLogEventTranslator translator,
        final String threadName,
        final boolean appenderThread) {
      this.translator = translator;
      this.cachedThreadName = threadName;
      this.isAppenderThread = appenderThread;
    }

    // LOG4J2-467
    private String threadName() {
      return THREAD_NAME_STRATEGY.getThreadName(this);
    }
  }

  @Override
  public void logMessage(
      final String fqcn,
      final Level level,
      final Marker marker,
      final Message message,
      final Throwable thrown) {

    final Disruptor<RingBufferLogEvent> temp = disruptor;
    if (temp == null) { // LOG4J2-639
      LOGGER.fatal("Ignoring log event after log4j was shut down");
    } else {
      logMessage0(temp, fqcn, level, marker, message, thrown);
    }
  }

  private void logMessage0(
      final Disruptor<RingBufferLogEvent> theDisruptor,
      final String fqcn,
      final Level level,
      final Marker marker,
      final Message message,
      final Throwable thrown) {
    final Info info = Info.THREADLOCAL.get();
    logMessageInAppropriateThread(info, theDisruptor, fqcn, level, marker, message, thrown);
  }

  private void logMessageInAppropriateThread(
      final Info info,
      final Disruptor<RingBufferLogEvent> theDisruptor,
      final String fqcn,
      final Level level,
      final Marker marker,
      final Message message,
      final Throwable thrown) {
    if (!logMessageInCurrentThread(info, theDisruptor, fqcn, level, marker, message, thrown)) {
      logMessageInBackgroundThread(info, fqcn, level, marker, message, thrown);
    }
  }

  /**
   * LOG4J2-471: prevent deadlock when RingBuffer is full and object being logged calls Logger.log()
   * from its toString() method
   *
   * @param info threadlocal information - used to determine if the current thread is the background
   *     appender thread
   * @param theDisruptor used to check if the buffer is full
   * @param fqcn fully qualified caller name
   * @param level log level
   * @param marker optional marker
   * @param message log message
   * @param thrown optional exception
   * @return {@code true} if the event has been logged in the current thread, {@code false} if it
   *     should be passed to the background thread
   */
  private boolean logMessageInCurrentThread(
      Info info,
      final Disruptor<RingBufferLogEvent> theDisruptor,
      final String fqcn,
      final Level level,
      final Marker marker,
      final Message message,
      final Throwable thrown) {
    if (info.isAppenderThread && theDisruptor.getRingBuffer().remainingCapacity() == 0) {
      // bypass RingBuffer and invoke Appender directly
      final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();
      strategy.log(this, getName(), fqcn, marker, level, message, thrown);
      return true;
    }
    return false;
  }

  /**
   * Enqueues the specified message to be logged in the background thread.
   *
   * @param info holds some cached information
   * @param fqcn fully qualified caller name
   * @param level log level
   * @param marker optional marker
   * @param message log message
   * @param thrown optional exception
   */
  private void logMessageInBackgroundThread(
      Info info,
      final String fqcn,
      final Level level,
      final Marker marker,
      final Message message,
      final Throwable thrown) {

    message.getFormattedMessage(); // LOG4J2-763: ask message to freeze parameters

    initLogMessageInfo(info, fqcn, level, marker, message, thrown);
    enqueueLogMessageInfo(info);
  }

  private void initLogMessageInfo(
      Info info,
      final String fqcn,
      final Level level,
      final Marker marker,
      final Message message,
      final Throwable thrown) {
    info.translator.setValues(
        this,
        getName(),
        marker,
        fqcn,
        level,
        message, //
        // don't construct ThrowableProxy until required
        thrown, //

        // config properties are taken care of in the EventHandler
        // thread in the #actualAsyncLog method

        // needs shallow copy to be fast (LOG4J2-154)
        ThreadContext.getImmutableContext(), //

        // needs shallow copy to be fast (LOG4J2-154)
        ThreadContext.getImmutableStack(), //

        // Thread.currentThread().getName(), //
        // info.cachedThreadName, //
        info.threadName(), //

        // location: very expensive operation. LOG4J2-153:
        // Only include if "includeLocation=true" is specified,
        // exclude if not specified or if "false" was specified.
        calcLocationIfRequested(fqcn),

        // System.currentTimeMillis());
        // CoarseCachedClock: 20% faster than system clock, 16ms gaps
        // CachedClock: 10% faster than system clock, smaller gaps
        // LOG4J2-744 avoid calling clock altogether if message has the timestamp
        eventTimeMillis(message), //
        nanoClock.nanoTime() //
        );
  }

  private long eventTimeMillis(final Message message) {
    return message instanceof TimestampMessage
        ? ((TimestampMessage) message).getTimestamp()
        : CLOCK.currentTimeMillis();
  }

  /**
   * Returns the caller location if requested, {@code null} otherwise.
   *
   * @param fqcn fully qualified caller name.
   * @return the caller location if requested, {@code null} otherwise.
   */
  private StackTraceElement calcLocationIfRequested(String fqcn) {
    final boolean includeLocation = privateConfig.loggerConfig.isIncludeLocation();
    return includeLocation ? location(fqcn) : null;
  }

  private void enqueueLogMessageInfo(Info info) {
    // LOG4J2-639: catch NPE if disruptor field was set to null after our check above
    try {
      // Note: do NOT use the temp variable above!
      // That could result in adding a log event to the disruptor after it was shut down,
      // which could cause the publishEvent method to hang and never return.
      disruptor.publishEvent(info.translator);
    } catch (final NullPointerException npe) {
      LOGGER.fatal("Ignoring log event after log4j was shut down.");
    }
  }

  private static StackTraceElement location(final String fqcnOfLogger) {
    return Log4jLogEvent.calcLocation(fqcnOfLogger);
  }

  /**
   * This method is called by the EventHandler that processes the RingBufferLogEvent in a separate
   * thread.
   *
   * @param event the event to log
   */
  public void actualAsyncLog(final RingBufferLogEvent event) {
    final Map<Property, Boolean> properties = privateConfig.loggerConfig.getProperties();
    event.mergePropertiesIntoContextMap(properties, privateConfig.config.getStrSubstitutor());
    final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();
    strategy.log(this, event);
  }

  public static void stop() {
    final Disruptor<RingBufferLogEvent> temp = disruptor;

    // Must guarantee that publishing to the RingBuffer has stopped
    // before we call disruptor.shutdown()
    disruptor = null; // client code fails with NPE if log after stop = OK
    if (temp == null) {
      return; // stop() has already been called
    }

    // Calling Disruptor.shutdown() will wait until all enqueued events are fully processed,
    // but this waiting happens in a busy-spin. To avoid (postpone) wasting CPU,
    // we sleep in short chunks, up to 10 seconds, waiting for the ringbuffer to drain.
    for (int i = 0; hasBacklog(temp) && i < MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN; i++) {
      try {
        Thread.sleep(SLEEP_MILLIS_BETWEEN_DRAIN_ATTEMPTS); // give up the CPU for a while
      } catch (final InterruptedException e) { // ignored
      }
    }
    temp.shutdown(); // busy-spins until all events currently in the disruptor have been processed
    EXECUTOR.shutdown(); // finally, kill the processor thread
    Info.THREADLOCAL.remove(); // LOG4J2-323
  }

  /** Returns {@code true} if the specified disruptor still has unprocessed events. */
  private static boolean hasBacklog(final Disruptor<?> theDisruptor) {
    final RingBuffer<?> ringBuffer = theDisruptor.getRingBuffer();
    return !ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize());
  }

  /**
   * Creates and returns a new {@code RingBufferAdmin} that instruments the ringbuffer of the {@code
   * AsyncLogger}.
   *
   * @param contextName name of the global {@code AsyncLoggerContext}
   * @return a new {@code RingBufferAdmin} that instruments the ringbuffer
   */
  public static RingBufferAdmin createRingBufferAdmin(final String contextName) {
    return RingBufferAdmin.forAsyncLogger(disruptor.getRingBuffer(), contextName);
  }

  /**
   * Returns the {@code NanoClock} to use for creating the nanoTime timestamp of log events.
   *
   * @return the {@code NanoClock} to use for creating the nanoTime timestamp of log events
   */
  public static NanoClock getNanoClock() {
    return nanoClock;
  }

  /**
   * Sets the {@code NanoClock} to use for creating the nanoTime timestamp of log events.
   *
   * <p>FOR INTERNAL USE. This method may be called with a different {@code NanoClock}
   * implementation when the configuration changes.
   *
   * @param nanoClock the {@code NanoClock} to use for creating the nanoTime timestamp of log events
   */
  public static void setNanoClock(NanoClock nanoClock) {
    AsyncLogger.nanoClock = Objects.requireNonNull(nanoClock, "NanoClock must be non-null");
  }
}
public class ElasticsearchHttpClient {

  private static final Logger logger = StatusLogger.getLogger();

  private static final char[] HEX_CHARS = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
  };

  private static final Set<Character> JS_ESCAPE_CHARS;

  static {
    Set<Character> mandatoryEscapeSet = new HashSet<Character>();
    mandatoryEscapeSet.add('"');
    mandatoryEscapeSet.add('\\');
    JS_ESCAPE_CHARS = Collections.unmodifiableSet(mandatoryEscapeSet);
  }

  private final Queue<String> requests = new ConcurrentLinkedQueue<String>();

  private final ReentrantLock lock = new ReentrantLock(true);

  private final String url;

  private final String index;

  private final String type;

  private final boolean create;

  private final int maxActionsPerBulkRequest;

  private final boolean logresponses;

  private final ScheduledExecutorService service;

  private volatile boolean closed = false;

  private HttpURLConnection connection;

  public ElasticsearchHttpClient(
      String url,
      String index,
      String type,
      boolean create,
      int maxActionsPerBulkRequest,
      long flushSecs,
      boolean logresponses) {
    this.url = url;
    this.index = index;
    this.type = type;
    this.create = create;
    this.maxActionsPerBulkRequest = maxActionsPerBulkRequest;
    this.logresponses = logresponses;
    this.closed = false;
    this.service = Executors.newScheduledThreadPool(1);
    service.schedule(
        new Callable<Object>() {
          @Override
          public Object call() throws Exception {
            try {
              flush();
            } catch (IOException e) {
              logger.error(e.getMessage(), e);
              throw new AppenderLoggingException(e);
            }
            return null;
          }
        },
        flushSecs,
        TimeUnit.SECONDS);
  }

  public ElasticsearchHttpClient index(Map<String, Object> source) {
    if (closed) {
      logger.error("logger is closed");
      throw new AppenderLoggingException("logger is closed");
    }
    try {
      requests.add(build(index, type, create, source));
    } catch (Exception e) {
      logger.error(e);
      closed = true;
    }
    return this;
  }

  public void flush() throws IOException {
    lock.lock();
    try {
      while (!requests.isEmpty()) {
        if (closed) {
          logger.error("logger is closed");
          return;
        }
        if (connection == null) {
          connection = (HttpURLConnection) new URL(url).openConnection();
        }
        try {
          connection.setDoOutput(true);
          connection.setRequestMethod("POST");
        } catch (Exception e) {
          logger.error(e);
          // retry
          connection = (HttpURLConnection) new URL(url).openConnection();
          connection.setDoOutput(true);
          connection.setRequestMethod("POST");
        }
        StringBuilder sb = new StringBuilder();
        int i = maxActionsPerBulkRequest;
        String request;
        while ((request = requests.poll()) != null && (i-- >= 0)) {
          sb.append(request);
        }
        OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
        writer.write(sb.toString());
        writer.close();
        if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
          // read response
          if (logresponses) {
            sb.setLength(0);
            BufferedReader in =
                new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            String s;
            while ((s = in.readLine()) != null) {
              sb.append(s);
            }
            in.close();
            logger.info(sb.toString());
          }
        } else {
          throw new AppenderLoggingException(
              "no OK response: "
                  + connection.getResponseCode()
                  + " "
                  + connection.getResponseMessage());
        }
      }
    } catch (Throwable t) {
      logger.error(t);
      closed = true;
      throw new AppenderLoggingException("Elasticsearch HTTP error", t);
    } finally {
      lock.unlock();
    }
  }

  public void close() throws IOException {
    if (!closed) {
      service.shutdownNow();
      flush();
      connection.getOutputStream().close();
      connection.disconnect();
    }
    closed = true;
  }

  private String build(String index, String type, boolean create, Map<String, Object> source) {
    index = index.indexOf('\'') < 0 ? index : getIndexNameDateFormat(index).format(new Date());
    StringBuilder sb = new StringBuilder();
    sb.append("{\"")
        .append(create ? "create" : "index")
        .append("\":{\"_index\":\"")
        .append(index)
        .append("\",\"_type\":\"")
        .append(type)
        .append("\"}}\n{");
    build(sb, source);
    sb.append("}\n");
    return sb.toString();
  }

  @SuppressWarnings("unchecked")
  private void build(StringBuilder sb, Object object) {
    if (object instanceof Map) {
      sb.append('{');
      build(sb, (Map<String, Object>) object);
      sb.append('}');
    } else if (object instanceof List) {
      sb.append("[");
      build(sb, (List<Object>) object);
      sb.append("]");
    } else if (object != null) {
      if (object instanceof Number) {
        sb.append(object);
      } else if (object instanceof Boolean) {
        sb.append(((Boolean) object) ? "true" : "false");
      } else if (object instanceof Date) {
        sb.append('"');
        sb.append(format((Date) object));
        sb.append('"');
      } else {
        sb.append('"');
        escape(sb, object.toString());
        sb.append('"');
      }
    } else {
      sb.append("null");
    }
  }

  private void build(StringBuilder sb, List<Object> list) {
    boolean started = false;
    for (Object object : list) {
      if (started) {
        sb.append(',');
      }
      build(sb, object);
      started = true;
    }
  }

  private void build(StringBuilder sb, Map<String, Object> map) {
    boolean started = false;
    for (Map.Entry<String, Object> me : map.entrySet()) {
      if (started) {
        sb.append(',');
      }
      // try to parse message as JSON
      if ("message".equals(me.getKey()) && me.getValue() != null) {
        JsonParser parser = new JsonParser(new StringReader(me.getValue().toString()));
        try {
          build(sb, (Map<String, Object>) parser.parse());
        } catch (Throwable e) {
          sb.append("\"").append(me.getKey()).append("\":");
          build(sb, me.getValue());
        }
      } else {
        sb.append("\"").append(me.getKey()).append("\":");
        build(sb, me.getValue());
      }
      started = true;
    }
  }

  private void escape(StringBuilder out, CharSequence plainText) {
    int pos = 0;
    int len = plainText.length();
    for (int charCount, i = 0; i < len; i += charCount) {
      int codePoint = Character.codePointAt(plainText, i);
      charCount = Character.charCount(codePoint);
      if (!isControlCharacter(codePoint) && !mustEscapeCharInJsString(codePoint)) {
        continue;
      }
      out.append(plainText, pos, i);
      pos = i + charCount;
      switch (codePoint) {
        case '\b':
          out.append("\\b");
          break;
        case '\t':
          out.append("\\t");
          break;
        case '\n':
          out.append("\\n");
          break;
        case '\f':
          out.append("\\f");
          break;
        case '\r':
          out.append("\\r");
          break;
        case '\\':
          out.append("\\\\");
          break;
        case '/':
          out.append("\\/");
          break;
        case '"':
          out.append("\\\"");
          break;
        default:
          appendHexJavaScriptRepresentation(out, codePoint);
          break;
      }
    }
    out.append(plainText, pos, len);
  }

  private boolean isControlCharacter(int codePoint) {
    return codePoint < 0x20
        || codePoint == 0x2028 // Line separator
        || codePoint == 0x2029 // Paragraph separator
        || (codePoint >= 0x7f && codePoint <= 0x9f);
  }

  private void appendHexJavaScriptRepresentation(StringBuilder sb, int codePoint) {
    sb.append("\\u")
        .append(HEX_CHARS[(codePoint >>> 12) & 0xf])
        .append(HEX_CHARS[(codePoint >>> 8) & 0xf])
        .append(HEX_CHARS[(codePoint >>> 4) & 0xf])
        .append(HEX_CHARS[codePoint & 0xf]);
  }

  private boolean mustEscapeCharInJsString(int codepoint) {
    if (!Character.isSupplementaryCodePoint(codepoint)) {
      char c = (char) codepoint;
      return JS_ESCAPE_CHARS.contains(c);
    }
    return false;
  }

  private static final String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.S'Z'";

  private static final TimeZone GMT = TimeZone.getTimeZone("GMT");

  private static final ThreadLocal<Map<String, SimpleDateFormat>> df =
      new ThreadLocal<Map<String, SimpleDateFormat>>() {
        public Map<String, SimpleDateFormat> initialValue() {
          return new HashMap<String, SimpleDateFormat>();
        }
      };

  private SimpleDateFormat getDateFormat(String format) {
    Map<String, SimpleDateFormat> formatters = df.get();
    SimpleDateFormat formatter = formatters.get(format);
    if (formatter == null) {
      formatter = new SimpleDateFormat();
      formatter.applyPattern(ISO_FORMAT);
      formatter.setTimeZone(GMT);
      formatters.put(format, formatter);
    }
    return formatter;
  }

  private SimpleDateFormat getIndexNameDateFormat(String index) {
    Map<String, SimpleDateFormat> formatters = df.get();
    SimpleDateFormat formatter = formatters.get(index);
    if (formatter == null) {
      formatter = new SimpleDateFormat();
      formatter.applyPattern(index);
      formatters.put(index, formatter);
    }
    return formatter;
  }

  private String format(Date date) {
    if (date == null) {
      return null;
    }
    return getDateFormat(ISO_FORMAT).format(date);
  }

  class JsonParser {

    private static final int DEFAULT_BUFFER_SIZE = 1024;

    private final Reader reader;

    private final char[] buf;

    private int index;

    private int fill;

    private int ch;

    private StringBuilder sb;

    private int start;

    public JsonParser(Reader reader) {
      this(reader, DEFAULT_BUFFER_SIZE);
    }

    public JsonParser(Reader reader, int buffersize) {
      this.reader = reader;
      buf = new char[buffersize];
      start = -1;
    }

    public Object parse() throws IOException {
      read();
      skipBlank();
      Object result = parseValue();
      skipBlank();
      if (ch != -1) {
        throw new IOException("unexpected character: " + ch);
      }
      return result;
    }

    private Object parseValue() throws IOException {
      switch (ch) {
        case 'n':
          return parseNull();
        case 't':
          return parseTrue();
        case 'f':
          return parseFalse();
        case '"':
          return parseString();
        case '[':
          return parseList();
        case '{':
          return parseMap();
        case '-':
        case '+':
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          return parseNumber();
      }
      throw new IOException("value");
    }

    private List<Object> parseList() throws IOException {
      read();
      List<Object> list = new ArrayList<Object>();
      skipBlank();
      if (parseChar(']')) {
        return list;
      }
      do {
        skipBlank();
        list.add(parseValue());
        skipBlank();
      } while (parseChar(','));
      if (!parseChar(']')) {
        expected("',' or ']'");
      }
      return list;
    }

    private Map<String, Object> parseMap() throws IOException {
      read();
      Map<String, Object> object = new LinkedHashMap<String, Object>();
      skipBlank();
      if (parseChar('}')) {
        return object;
      }
      do {
        skipBlank();
        if (ch != '"') {
          expected("name");
        }
        String name = parseString();
        skipBlank();
        if (!parseChar(':')) {
          expected("':'");
        }
        skipBlank();
        object.put(name, parseValue());
        skipBlank();
      } while (parseChar(','));
      if (!parseChar('}')) {
        expected("',' or '}'");
      }
      return object;
    }

    private Object parseNull() throws IOException {
      read();
      checkForChar('u');
      checkForChar('l');
      checkForChar('l');
      return null;
    }

    private Object parseTrue() throws IOException {
      read();
      checkForChar('r');
      checkForChar('u');
      checkForChar('e');
      return Boolean.TRUE;
    }

    private Object parseFalse() throws IOException {
      read();
      checkForChar('a');
      checkForChar('l');
      checkForChar('s');
      checkForChar('e');
      return Boolean.FALSE;
    }

    private void checkForChar(char ch) throws IOException {
      if (!parseChar(ch)) {
        expected("'" + ch + "'");
      }
    }

    private String parseString() throws IOException {
      read();
      startCapture();
      while (ch != '"') {
        if (ch == '\\') {
          pauseCapture();
          parseEscaped();
          startCapture();
        } else if (ch < 0x20) {
          expected("valid string character");
        } else {
          read();
        }
      }
      String s = endCapture();
      read();
      return s;
    }

    private void parseEscaped() throws IOException {
      read();
      switch (ch) {
        case '"':
        case '/':
        case '\\':
          sb.append((char) ch);
          break;
        case 'b':
          sb.append('\b');
          break;
        case 't':
          sb.append('\t');
          break;
        case 'f':
          sb.append('\f');
          break;
        case 'n':
          sb.append('\n');
          break;
        case 'r':
          sb.append('\r');
          break;
        case 'u':
          char[] hex = new char[4];
          for (int i = 0; i < 4; i++) {
            read();
            if (!isHexDigit()) {
              expected("hexadecimal digit");
            }
            hex[i] = (char) ch;
          }
          sb.append((char) Integer.parseInt(String.valueOf(hex), 16));
          break;
        default:
          expected("valid escape sequence");
      }
      read();
    }

    private Object parseNumber() throws IOException {
      startCapture();
      parseChar('-');
      int firstDigit = ch;
      if (!parseDigit()) {
        expected("digit");
      }
      if (firstDigit != '0') {
        while (parseDigit()) {}
      }
      parseFraction();
      parseExponent();
      return endCapture();
    }

    private boolean parseFraction() throws IOException {
      if (!parseChar('.')) {
        return false;
      }
      if (!parseDigit()) {
        expected("digit");
      }
      while (parseDigit()) {}
      return true;
    }

    private boolean parseExponent() throws IOException {
      if (!parseChar('e') && !parseChar('E')) {
        return false;
      }
      if (!parseChar('+')) {
        parseChar('-');
      }
      if (!parseDigit()) {
        expected("digit");
      }
      while (parseDigit()) {}
      return true;
    }

    private boolean parseChar(char ch) throws IOException {
      if (this.ch != ch) {
        return false;
      }
      read();
      return true;
    }

    private boolean parseDigit() throws IOException {
      if (!isDigit()) {
        return false;
      }
      read();
      return true;
    }

    private void skipBlank() throws IOException {
      while (isWhiteSpace()) {
        read();
      }
    }

    private void read() throws IOException {
      if (ch == -1) {
        throw new IOException("unexpected end of input");
      }
      if (index == fill) {
        if (start != -1) {
          sb.append(buf, start, fill - start);
          start = 0;
        }
        fill = reader.read(buf, 0, buf.length);
        index = 0;
        if (fill == -1) {
          ch = -1;
          return;
        }
      }
      ch = buf[index++];
    }

    private void startCapture() {
      if (sb == null) {
        sb = new StringBuilder();
      }
      start = index - 1;
    }

    private void pauseCapture() {
      int end = ch == -1 ? index : index - 1;
      sb.append(buf, start, end - start);
      start = -1;
    }

    private String endCapture() {
      int end = ch == -1 ? index : index - 1;
      String captured;
      if (sb.length() > 0) {
        sb.append(buf, start, end - start);
        captured = sb.toString();
        sb.setLength(0);
      } else {
        captured = new String(buf, start, end - start);
      }
      start = -1;
      return captured;
    }

    private boolean isWhiteSpace() {
      return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
    }

    private boolean isDigit() {
      return ch >= '0' && ch <= '9';
    }

    private boolean isHexDigit() {
      return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F';
    }

    private void expected(String expected) throws IOException {
      if (ch == -1) {
        throw new IOException("unexpected end of input");
      }
      throw new IOException("expected " + expected);
    }
  }
}
public class StoreConfiguration<T> {
  protected static final StatusLogger LOGGER = StatusLogger.getLogger();

  private String location;
  private String password;

  public StoreConfiguration(final String location, final String password) {
    this.location = location;
    this.password = password;
  }

  public String getLocation() {
    return this.location;
  }

  public void setLocation(final String location) {
    this.location = location;
  }

  public String getPassword() {
    return this.password;
  }

  public char[] getPasswordAsCharArray() {
    return this.password == null ? null : this.password.toCharArray();
  }

  public void setPassword(final String password) {
    this.password = password;
  }

  /** @throws StoreConfigurationException May be thrown by subclasses */
  protected T load() throws StoreConfigurationException {
    return null;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((this.location == null) ? 0 : this.location.hashCode());
    result = prime * result + ((this.password == null) ? 0 : this.password.hashCode());
    return result;
  }

  @Override
  public boolean equals(final Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (!(obj instanceof StoreConfiguration)) {
      return false;
    }
    final StoreConfiguration<?> other = (StoreConfiguration<?>) obj;
    if (this.location == null) {
      if (other.location != null) {
        return false;
      }
    } else if (!this.location.equals(other.location)) {
      return false;
    }
    if (this.password == null) {
      if (other.password != null) {
        return false;
      }
    } else if (!this.password.equals(other.password)) {
      return false;
    }
    return true;
  }
}