package com.redmadrobot.chronos;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

/**
 * Basic interface for providing GUI elements an access to Chronos.
 *
 * @author maximefimov
 */
public final class ChronosConnector {

    private final static String KEY_CHRONOS_LISTENER_ID = "chronos_listener_id";

    private ChronosListener mChronosListener;

    private Object mGUIClient;

    /**
     * GUI client should call this method in its own onCreate() method.
     *
     * @param client             a client that holds the helper
     * @param savedInstanceState parameter from the corresponding client method
     */
    public final void onCreate(@NonNull final Object client,
            @Nullable final Bundle savedInstanceState) {
        mGUIClient = client;
        if (savedInstanceState != null) {
            mChronosListener = ChronosListenerManager.getInstance()
                    .getListener(savedInstanceState.getInt(KEY_CHRONOS_LISTENER_ID));
        } else {
            mChronosListener = ChronosListenerManager.getInstance()
                    .createListener();
        }
    }

    /**
     * GUI client should call this method in its own onResume() method.
     */
    public final void onResume() {
        mChronosListener.onResume(mGUIClient);
    }

    /**
     * GUI client should call this method in its own onSaveInstanceState() method.
     *
     * @param outState parameter from the corresponding client method
     */
    public final void onSaveInstanceState(@Nullable final Bundle outState) {
        if (outState != null) {
            outState.putInt(KEY_CHRONOS_LISTENER_ID, mChronosListener.getId());
        }
    }

    /**
     * GUI client should call this method in its own onPause() method.
     */
    public final void onPause() {
        mChronosListener.onPause();
    }

    /**
     * Runs an operation in a background thread. Only one operation with the given tag may run in a
     * single moment of time. The result will be delivered to {@link Chronos#OWN_CALLBACK_METHOD_NAME}
     * method. If {@code broadcast} is {@code true} all other Chronos clients will receive the
     * result in {@link Chronos#BROADCAST_CALLBACK_METHOD_NAME} method. Both method must have only
     * one parameter of type {@link ChronosOperation#getResultClass()} to receive a result.
     *
     * @param operation an operation to be run in background
     * @param tag       value which prohibits running new operations with the same tag while there
     *                  is a running one
     * @param broadcast {@code true} if any other Chronos clients should be able to receive a
     *                  result; {@code false} otherwise
     * @return a unique launch id, may be the same with the previous run with the same tag, if the
     * operation is still running
     * @see #runOperation(ChronosOperation, boolean)
     * @see #cancelOperation(int, boolean)
     * @see #cancelOperation(String, boolean)
     */
    public final int runOperation(@NonNull final ChronosOperation operation, @NonNull final String tag,
            final boolean broadcast) {
        return mChronosListener.invoke(operation, tag, broadcast);
    }

    /**
     * Runs an operation in a background thread. The result will be delivered to {@link
     * Chronos#OWN_CALLBACK_METHOD_NAME} method. If {@code broadcast} is {@code true} all other
     * Chronos clients will receive the result in {@link Chronos#BROADCAST_CALLBACK_METHOD_NAME}
     * method. Both method must have only one parameter of type {@link ChronosOperation#getResultClass()}
     * to receive a result.
     *
     * @param operation an operation to be run in background
     * @param broadcast {@code true} if any other Chronos clients should be able to receive a
     *                  result; {@code false} otherwise
     * @return a unique launch id
     * @see #runOperation(ChronosOperation, String, boolean)
     * @see #cancelOperation(int, boolean)
     */
    public final int runOperation(@NonNull final ChronosOperation operation, final boolean broadcast) {
        return mChronosListener.invoke(operation, broadcast);
    }

    /**
     * Cancels a running operation. It is not guaranteed that execution would be interrupted
     * immediately, however, no result would be delivered to the GUI client, or any other Chronos
     * clients, if it was a broadcast run.
     *
     * @param id           id of launch, that needs to be cancelled
     * @param mayInterrupt {@code true} if threads executing operations task should be interrupted;
     *                     otherwise, in-progress tasks are allowed to complete
     * @return {@code false} if the task could not be cancelled, typically because it has already
     * completed normally; {@code true} otherwise
     * @see #runOperation(ChronosOperation, String, boolean)
     * @see #runOperation(ChronosOperation, boolean)
     * @see #cancelOperation(String, boolean)
     */
    public final boolean cancelOperation(final int id, final boolean mayInterrupt) {
        return mChronosListener.cancel(id, mayInterrupt);
    }

    /**
     * Cancels a running operation. It is not guaranteed that execution would be interrupted
     * immediately, however, no result would be delivered to the GUI client, or any other Chronos
     * clients, if it was a broadcast run.
     *
     * @param tag          a tag with the operation was launched
     * @param mayInterrupt {@code true} if threads executing operations task should be interrupted;
     *                     otherwise, in-progress tasks are allowed to complete
     * @return {@code false} if the task could not be cancelled, typically because it has already
     * completed normally or there is no running operation with a given tag; {@code true} otherwise
     * @see #runOperation(ChronosOperation, String, boolean)
     * @see #runOperation(ChronosOperation, boolean)
     * @see #cancelOperation(int, boolean)
     */
    public final boolean cancelOperation(@NonNull final String tag, final boolean mayInterrupt) {
        return mChronosListener.cancel(tag, mayInterrupt);
    }

    /**
     * Checks if an operation with given launch id is running.
     *
     * @param id an id of the operation launch
     * @return {@code true} if the operation is running, {@code false} otherwise
     */
    public final boolean isOperationRunning(final int id) {
        return mChronosListener.isRunning(id);
    }

    /**
     * Checks if an operation with given launch tag is running.
     *
     * @param tag a pre-cache key of the operation launch
     * @return {@code true} if the operation is running, {@code false} if it is not running, or
     * there was no operation launch with the tag at all
     */
    public final boolean isOperationRunning(@NonNull final String tag) {
        return mChronosListener.isRunning(tag);
    }
}


package com.redmadrobot.chronos;

import org.jetbrains.annotations.Contract;

import android.support.annotation.NonNull;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import de.greenrobot.event.EventBus;

/**
 * An entity which runs operations.
 *
 * @author maximefimov
 */
final class ChronosService {

    @NonNull
    private final static ChronosService INSTANCE = new ChronosService();

    @NonNull
    private final AtomicInteger mLastOperationId = new AtomicInteger(0);

    @NonNull
    private final EventBus mEventBus = EventBus.getDefault();

    @NonNull
    private final ExecutorService mExecutorService = Executors.newCachedThreadPool();

    private ChronosService() {
    }

    @NonNull
    @Contract(pure = true)
    static ChronosService getInstance() {
        return INSTANCE;
    }

    /**
     * Runs operation, handling all the exceptions that may ne thrown while running.
     *
     * @param operation       an operation to be executed
     * @param operationResult an empty result object to be filled with business-logic content
     * @param <Output>        class of the result, returned by the Operations' {@code run} method
     */
    private static <Output> void silentRun(@NonNull final ChronosOperation<Output> operation,
            @NonNull final ChronosOperationResult<Output> operationResult) {
        try {
            final Output output = operation.run();
            operationResult.setOutput(output);
        } catch (Exception e) {
            operationResult.setException(e);
        }
    }

    /**
     * Creates a template object for storing operations' run result.
     *
     * @param operation       an operation to create a result for
     * @param broadcastResult {@code true} if the result should be broadcasted, {@code false}
     *                        otherwise
     * @param <Output>        class of the result, returned by the Operations' {@code run} method
     * @return an empty OperationResult without business-logic content
     */
    @NonNull
    @Contract(pure = true)
    private <Output> ChronosOperationResult<Output> createEmptyResult(
            @NonNull final ChronosOperation<Output> operation, final boolean broadcastResult) {
        final ChronosOperationResult<Output> operationResult;
        final Class<? extends ChronosOperationResult<Output>> resultClass = operation
                .getResultClass();
        try {
            operationResult = resultClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Can't create a new instance of " + resultClass.getName());
        } catch (IllegalAccessException e) {
            throw new RuntimeException(resultClass.getName() + " constructor is not accessible");
        }
        operationResult.setId(mLastOperationId.incrementAndGet());
        operationResult.setOperation(operation);
        operationResult.setBroadcast(broadcastResult);

        return operationResult;
    }

    /**
     * Runs operation in background.
     *
     * @param operation       an operation to be executed
     * @param <Output>        class of the result, returned by the Operations' {@code run} method
     * @param broadcastResult {@code true} if the result should be broadcasted, {@code false}
     *                        otherwise
     * @return the unique id of the launch
     */
    final <Output> int runAsync(@NonNull final ChronosOperation<Output> operation,
            final boolean broadcastResult) {
        final ChronosOperationResult<Output> result = createEmptyResult(operation, broadcastResult);
        final int id = result.getId();

        synchronized (ChronosService.this) {
            RunningOperationStorage.getInstance().operationStarted(id, operation,
                    mExecutorService.submit(new Runnable() {
                        @Override
                        public void run() {
                            silentRun(operation, result);
                            mEventBus.post(result);
                            synchronized (ChronosService.this) {
                                RunningOperationStorage.getInstance().operationFinished(id);
                            }
                        }
                    }));
        }
        return id;
    }

    /**
     * Runs operation in the same thread.
     *
     * @param operation       an operation to be executed
     * @param <Output>        class of the result, returned by the Operations' {@code run} method
     * @param broadcastResult {@code true} if the result should be broadcasted, {@code false}
     *                        otherwise
     * @return operations' result
     */
    @NonNull
    final <Output> ChronosOperationResult<Output> runSync(
            @NonNull final ChronosOperation<Output> operation, final boolean broadcastResult) {
        final ChronosOperationResult<Output> result = createEmptyResult(operation, broadcastResult);

        silentRun(operation, result);
        mEventBus.post(result);

        return result;
    }
}


package com.redmadrobot.chronos_sample.samples;

import com.redmadrobot.chronos.gui.activity.ChronosActivity;
import com.redmadrobot.chronos_sample.R;
import com.redmadrobot.chronos_sample.operations.SimpleOperation;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

/**
 * An sample of it is possible to cancell a running operation.
 *
 * @author maximefimov
 */
public final class DataLoadCancel extends ChronosActivity {

    private final static String KEY_DATA = "data";

    private final static String TAG_DATA_LOADING = "data_loading";

    private TextView mTextOutput;

    private String mData = null;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_data_load_cancel);

        mTextOutput = (TextView) findViewById(R.id.text_output);

        if (savedInstanceState != null) {
            mData = savedInstanceState.getString(KEY_DATA);
        }

        final View cancelView = findViewById(R.id.button_cancel);
        cancelView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                // in any moment of time you can try to cancel a running operation
                // though the run may not be cancelled, e.g. the run has been finished already, or never been issued at all
                final boolean cancelResult = cancelOperation(TAG_DATA_LOADING);
                if (cancelResult) {
                    mTextOutput.setText("Operation launch is cancelled");
                } else {
                    showToast("Can't cancel operation launch");
                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mData == null) {
            runOperation(new SimpleOperation(""), TAG_DATA_LOADING);
        } else {
            showData();
        }
    }

    @Override
    protected void onSaveInstanceState(@NonNull final Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(KEY_DATA, mData);
    }

    private void showData() {
        mTextOutput.setText("Data is '" + mData + "'");
    }

    // if Operations run is cancelled, there would be no call to this method, thus you should handle
    // cancelling in the place it happened
    public void onOperationFinished(final SimpleOperation.Result result) {
        if (result.isSuccessful()) {
            mData = result.getOutput();
            showData();
        } else {
            mTextOutput.setText(result.getErrorMessage());
        }
    }

    private void showToast(@NonNull final String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }
}


package com.nitorcreations.willow.eventhandler;

import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.logging.ConsoleHandler;
import java.util.logging.Filter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.commons.beanutils.BeanUtils;
import org.eclipse.sisu.space.SpaceModule;
import org.eclipse.sisu.space.URLClassSpace;
import org.eclipse.sisu.wire.WireModule;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.nitorcreations.willow.utils.MergeableProperties;
import com.nitorcreations.willow.utils.SimpleFormatter;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Main class for event handler.
 *
 * @author mtommila
 */
@Named
@Singleton
@SuppressFBWarnings(value={"DM_EXIT"}, justification="cli tool needs to convey correct exit code")
public class Main {

  protected Logger logger = Logger.getLogger(getClass().getName());

  @Inject
  protected Injector injector;

  protected MergeableProperties properties;

  @Inject
  protected ExecutorService executorService;

  @Inject
  protected EventPoller eventPoller;

  /**
   * Command-line entry point.
   *
   * @param args The command line arguments. These should be URLs that point to the configuration file(s).
   */
  public static void main(String... args) {
    main(new Main(), args);
  }

  /**
   * Subclass specific entry point.
   *
   * @param main The actual main class implementation to use.
   * @param args The command line arguments.
   */
  protected static void main(Main main, String... args) {
    main.getInjector().getInstance(main.getMainClass()).doMain(args);
  }

  /**
   * Get an Injector to be used in instantiating the main class.
   *
   * @return An Injector.
   */
  protected Injector getInjector() {
    return Guice.createInjector(getModules());
  }

  /**
   * Get the Guice modules to be used in injecting dependencies.
   *
   * @return The modules.
   */
  protected Module[] getModules() {
    Module[] modules = { new WireModule(new EventHandlerModule(),
        new SpaceModule(new URLClassSpace(Main.class.getClassLoader()))) };
    return modules;
  }

  /**
   * Get the main class to be instantiated and run.
   *
   * @return The main class to be used.
   */
  protected Class<? extends Main> getMainClass() {
    return getClass();
  }

  /**
   * Actual main implementation.
   *
   * @param args The command line arguments. These should be URLs that point to the configuration file(s).
   */
  protected void doMain(String... args) {
    properties = new MergeableProperties();
    properties.putAll(System.getProperties());
    for (String arg : args) {
      properties.merge(arg);
    }
    // Read event handling configuration
    Map<String, List<EventHandler>> eventHandlers = new LinkedHashMap<>();
    MergeableProperties allProperties = properties.getPrefixed("willow-event-handler.handlers");
    int i = 0;
    while (true) {
      MergeableProperties handlerProperties = allProperties.getPrefixed("[" + i++ + "]");
      if (handlerProperties.isEmpty()) {
        break;
      }
      String handlerClassName = handlerProperties.getProperty("handler");
      String eventClassName = handlerProperties.getProperty("event");
      try {
        EventHandler eventHandler = (EventHandler) injector.getInstance(Class.forName(handlerClassName));
        MergeableProperties beanProperties = handlerProperties.getPrefixed("properties");
        for (String propertyName : beanProperties.stringPropertyNames()) {
          String propertyValue = beanProperties.getProperty(propertyName);
          BeanUtils.setProperty(eventHandler, propertyName, propertyValue);
        }
        List<EventHandler> eventHandlersForType = eventHandlers.get(eventClassName);
        if (eventHandlersForType == null) {
          eventHandlersForType = new ArrayList<>();
          eventHandlers.put(eventClassName, eventHandlersForType);
        }
        eventHandlersForType.add(eventHandler);
      } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
        logger.log(Level.SEVERE, "Error instantiating handler", e);
        System.exit(1);
      }
    }

    if (eventHandlers .isEmpty()) {
      logger.info("No event handlers configured. Exiting.");
      System.exit(0);
    }

    Runtime.getRuntime().addShutdownHook(new Thread() {
      @Override
      public void run() {
        Main.this.stop();
      }
    });

    // Start polling events
    String uri = properties.getProperty("willow-event-handler.eventsUri");
    try {
      logger.info("Initializing event poller");
      eventPoller.initialize(eventHandlers, new URI(uri));
    } catch (URISyntaxException e) {
      logger.log(Level.SEVERE, "Invalid URI: " + uri, e);
      System.exit(1);
    }
  }

  protected void stop() {
    System.out.println("Willow event handler stopping...");
    executorService.shutdown();
    System.out.println("Willow event handler stopped.");
  }

  protected void setupLogging() {
    Logger rootLogger = Logger.getLogger("");
    for (Handler nextHandler : rootLogger.getHandlers()) {
      rootLogger.removeHandler(nextHandler);
    }
    Handler console = new ConsoleHandler();
    console.setLevel(Level.INFO);
    console.setFormatter(new SimpleFormatter());
    rootLogger.addHandler(console);
    rootLogger.setLevel(Level.INFO);
    console.setFilter(new Filter() {
      @Override
      public boolean isLoggable(LogRecord record) {
        return record.getLoggerName() == null || !record.getLoggerName().startsWith("org.eclipse.jetty.util.log");
      }
    });
  }
}


package com.nitorcreations.willow.utils;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * An OutputStream that flushes out to a Logger.

 *
 * Note that no data is written out to the Category until the stream is
 *   flushed or closed.

 *
 * Example:

 * // make sure everything sent to System.err is logged
 * System.setErr(new PrintStream(new LoggingOutputStream(Logger.getLogger(""), Level.WARNING), true));
 *
 * // make sure everything sent to System.out is also logged
 * System.setOut(new PrintStream(new LoggingOutputStream(Logger.getLogger(""), Level.INFO), true));
 *

 *
 * @author Jim Moore
 * @see Logger
 */
public class LoggingOutputStream extends OutputStream {
  protected static final String LINE_SEPERATOR = System.getProperty("line.separator");
  /**
   * Used to maintain the contract of {@link #close()}.
   */
  protected boolean hasBeenClosed = false;
  /**
   * The internal buffer where data is stored.
   */
  protected byte[] buf;
  /**
   * The number of valid bytes in the buffer. This value is always
   *   in the range <tt>0</tt> through <tt>buf.length</tt>; elements
   *   <tt>buf[0]</tt> through <tt>buf[count-1]</tt> contain valid
   *   byte data.
   */
  protected int count;
  /**
   * Remembers the size of the buffer for speed.
   */
  private int bufLength;
  /**
   * The default number of bytes in the buffer.
   */
  public static final int DEFAULT_BUFFER_LENGTH = 512;
  /**
   * The logger to write to.
   */
  protected final Logger logger;
  /**
   * The log level to use when writing to the Category.
   */
  protected final Level level;
  /**
   * The charset to use for strings written to this stream
   */
  protected final Charset charset;

  public LoggingOutputStream() {
    this(Logger.getAnonymousLogger(), Level.INFO);
  }

  /**
   * Creates the LoggingOutputStream to flush to the given Category with the default charset
   *
   * @param logger     the Logger to write to
   * @param level      the Level to use when writing to the logger
   * @exception IllegalArgumentException if logger == null or level == null
   */
  public LoggingOutputStream(Logger logger, Level level) throws IllegalArgumentException {
    this(logger, level, Charset.defaultCharset());
  }

  /**
   * Creates the LoggingOutputStream to flush to the given Category.
   *
   * @param logger     the Logger to write to
   * @param level      the Level to use when writing to the logger
   * @param charset    the Charset to use when interpreting written bytes
   * @exception IllegalArgumentException if logger == null or level == null
   */
  public LoggingOutputStream(Logger logger, Level level, Charset charset) throws IllegalArgumentException {
    if (logger == null) {
      throw new IllegalArgumentException("logger == null");
    }
    if (level == null) {
      throw new IllegalArgumentException("level == null");
    }
    this.level = level;
    this.logger = logger;
    this.charset = charset;
    bufLength = DEFAULT_BUFFER_LENGTH;
    buf = new byte[DEFAULT_BUFFER_LENGTH];
    count = 0;
  }

  /**
   * Closes this output stream and releases any system resources
   *   associated with this stream. The general contract of close
   *   is that it closes the output stream. A closed stream cannot perform
   *   output operations and cannot be reopened.
   */
  @Override
  public void close() {
    flush();
    hasBeenClosed = true;
  }

  /**
   * Writes the specified byte to this output stream. The general
   * contract for write is that one byte is written
   * to the output stream. The byte to be written is the eight
   * low-order bits of the argument b. The 24
   * high-order bits of b are ignored.
   *
   * @param b          the byte to write
   *
   * @exception IOException
   *                   if an I/O error occurs. In particular,
   *                   an IOException may be thrown if the
   *                   output stream has been closed.
   */
  @Override
  public void write(final int b) throws IOException {
    if (hasBeenClosed) {
      throw new IOException("The stream has been closed.");
    }
    // don't log nulls
    if (b == 0) {
      return;
    }
    // would this be writing past the buffer?
    if (count == bufLength) {
      // grow the buffer
      final int newBufLength = bufLength + DEFAULT_BUFFER_LENGTH;
      final byte[] newBuf = new byte[newBufLength];
      System.arraycopy(buf, 0, newBuf, 0, bufLength);
      buf = newBuf;
      bufLength = newBufLength;
    }
    buf[count] = (byte) b;
    count++;
    if (count >= LINE_SEPERATOR.length()) {
      checkLine();
    }
  }

  /**
   * Checks if the end of the buffer contains a newline and if so, flushes the stream
   */
  private void checkLine() {
    String endStr = new String(buf, count - LINE_SEPERATOR.length(), LINE_SEPERATOR.length(), charset);
    if (LINE_SEPERATOR.equals(endStr)) {
      flush();
    }
  }

  /**
   * Flushes this output stream and forces any buffered output bytes
   *   to be written out. The general contract of flush is
   *   that calling it is an indication that, if any bytes previously
   *   written have been buffered by the implementation of the output
   *   stream, such bytes should immediately be written to their
   *   intended destination.
   */
  @Override
  @SuppressWarnings("PMD.UselessParentheses")
  public void flush() {
    if (count == 0) {
      return;
    }
    // don't print out blank lines; flushing from PrintStream puts out these
    if ((count == LINE_SEPERATOR.length()) && ((char) buf[0]) == LINE_SEPERATOR.charAt(0) && ((count == 1) || // <- Unix & Mac, -> Windows
        ((count == 2) && ((char) buf[1]) == LINE_SEPERATOR.charAt(1)))) {
      reset();
      return;
    }
    final byte[] theBytes = new byte[count];
    System.arraycopy(buf, 0, theBytes, 0, count);
    String message = new String(theBytes, charset);
    if (message.endsWith(LINE_SEPERATOR)) {
      message = message.substring(0, message.length() - LINE_SEPERATOR.length());
    }
    logger.log(level, message);
    reset();
  }

  private void reset() {
    // not resetting the buffer -- assuming that if it grew that it
    //   will likely grow similarly again
    count = 0;
  }
}


package com.btr.proxy.selector.pac;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/*****************************************************************************
 * Script source that will load the content of a PAC file from an webserver. The
 * script content is cached once it was downloaded.
 *
 * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
 ****************************************************************************/

public class UrlPacScriptSource implements PacScriptSource {
  private final Logger log = Logger.getLogger(UrlPacScriptSource.class.getCanonicalName());

  private final String scriptUrl;
  private String scriptContent;
  private long expireAtMillis;

  /*************************************************************************
   * Constructor
   *
   * @param url
   *          the URL to download the script from.
   ************************************************************************/

  public UrlPacScriptSource(String url) {
    super();
    this.expireAtMillis = 0;
    this.scriptUrl = url;
  }

  /*************************************************************************
   * getScriptContent
   *
   * @see com.btr.proxy.selector.pac.PacScriptSource#getScriptContent()
   ************************************************************************/

  @Override
  public synchronized String getScriptContent() throws IOException {
    if (this.scriptContent == null || this.expireAtMillis > 0
        && this.expireAtMillis < System.currentTimeMillis()) {
      ProxySelector old = ProxySelector.getDefault();
      try {
        // Reset it again with next download we should get a new expire info
        this.expireAtMillis = 0;
        ProxySelector.setDefault(new ProxySelector() {
          @Override
          public List<Proxy> select(URI uri) {
            return Arrays.asList(Proxy.NO_PROXY);
          }
          @Override
          public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
          }
        });
        this.scriptContent = readPacFileContent(this.scriptUrl);
      } finally {
        ProxySelector.setDefault(old);
      }
    }
    return this.scriptContent;
  }

  /*************************************************************************
   * Reads a PAC script from a local file.
   *
   * @param scriptUrl
   * @return the content of the script file.
   * @throws IOException
   * @throws URISyntaxException
   ************************************************************************/
  private String readPacFileContent(String scriptUrl) throws IOException {
    try (InputStream in = new URL(scriptUrl).openConnection(Proxy.NO_PROXY).getInputStream();
        BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
      StringBuilder result = new StringBuilder();
      try {
        String line;
        while ((line = r.readLine()) != null) {
          result.append(line).append("\n");
        }
      } finally {
        r.close();
      }
      return result.toString();
    }
  }

  /***************************************************************************
   * @see java.lang.Object#toString()
   **************************************************************************/
  @Override
  public String toString() {
    return this.scriptUrl;
  }

  /*************************************************************************
   * isScriptValid
   *
   * @see com.btr.proxy.selector.pac.PacScriptSource#isScriptValid()
   ************************************************************************/

  @Override
  public boolean isScriptValid() {
    try {
      String script = getScriptContent();
      if (script.trim().length() == 0) {
        log.log(Level.FINE, "PAC script is empty. Skipping script!");
        return false;
      }
      if (script.indexOf("FindProxyForURL") == -1) {
        log.log(Level.FINE,
            "PAC script entry point FindProxyForURL not found. Skipping script!");
        return false;
      }
      return true;
    } catch (IOException e) {
      log.log(Level.FINE, "File reading error", e);
      return false;
    }
  }

}


package com.nitorcreations.willow.eventhandler;

import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.nitorcreations.willow.messages.event.EventMessage;
import com.nitorcreations.willow.messages.metrics.MetricConfig;
import com.nitorcreations.willow.sshagentauth.SSHUtil;

/**
 * Event poller for receiving events from metrics server via websocket and dispatching them to event handlers.
 *
 * @author mtommila
 */
public class EventPoller {
  private Logger logger = Logger.getLogger(getClass().getName());

  private Map<String, List<EventHandler>> eventHandlers;

  @Inject
  private Gson gson;

  @Inject
  private ExecutorService executorService;

  /**
   * Initialize the event poller.
   *
   * @param eventHandlers The handlers for different event types.
   * @param uri The web socket connection URI.
   */
  public void initialize(Map<String, List<EventHandler>> eventHandlers, URI uri) {
    this.eventHandlers = eventHandlers;
    EventListener listener = new EventListener(uri);
    executorService.submit(listener);
    logger.fine("Started event listener");
  }

  /**
   * Websocket message listener. Dispatches events to the event handlers.
   * Autowired to Jetty websocket implementation via annotations.
   *
   * @author mtommila
   */
  @WebSocket
  public class EventListener implements Runnable {

    private Logger logger = Logger.getLogger(getClass().getName());

    private URI uri;
    private WebSocketClient client;
    private CountDownLatch connectLatch;
    private Set<String> messageIds = new HashSet<>();

    private EventListener(URI uri) {
      this.uri = uri;
    }

    private synchronized WebSocketClient getClient() {
      if (this.client == null) {
        this.client = new WebSocketClient();
      }
      return this.client;
    }

    private void connect() throws Exception {
      logger.info("Connecting event listener");
      WebSocketClient connectClient = getClient();
      connectClient.start();
      connectClient.setAsyncWriteTimeout(5000);
      connectClient.setConnectTimeout(2000);
      connectClient.setStopTimeout(5000);
      ClientUpgradeRequest request = new ClientUpgradeRequest();
      request.setHeader("Authorization", SSHUtil.getPublicKeyAuthorization(System.getProperty("user.name", "willow")));
      Future<Session> future = connectClient.connect(this, uri, request);
      logger.info("Connecting to : " + uri);
      try {
        future.get();
        logger.info("Connected to : " + uri);
      } catch (Exception e) {
        logger.log(Level.SEVERE, "Failed to connect event poller with uri " + uri, e);
        if (connectClient.isRunning()) {
          connectClient.stop();
          connectClient.destroy();
        }
        throw e;
      }
    }

    @OnWebSocketMessage
    public void messageReceived(Session session, Reader message) {
      if (message == null) {
        return;
      }
      EventMessage[] eventMessages = null;
      List<EventMessage> uniqueEventMessages = new ArrayList<>();
      try {
        JsonObject json = gson.fromJson(message, JsonObject.class);
        if (json == null || json.get("data") == null) {
          return;
        }
        eventMessages = gson.fromJson(json.get("data"), EventMessage[].class);
        if (eventMessages == null) {
          return;
        }
      } catch (Exception e) {
        logger.log(Level.INFO, "Failure in unmarshalling event data", e);
        return;
      }
      // Loop through all events that happened during the retrieved time window
      Set<String> newMessageIds = new HashSet<>(eventMessages.length);
      synchronized (messageIds) {
        for (EventMessage eventMessage : eventMessages) {
          if (eventMessage != null && eventMessage.eventType != null && eventMessage.getId() != null) {
            String id = eventMessage.getId();
            newMessageIds.add(id);
            // Ignore duplicate messages
            if (!messageIds.contains(id)) {
              uniqueEventMessages.add(eventMessage);
            }
          }
        }
        // Clear the previous message IDs and set the now received message IDs, note the synchronization
        messageIds.clear();
        messageIds.addAll(newMessageIds);
      }
      // Loop through configured handlers to handle the non-duplicate message
      for (EventMessage eventMessage : uniqueEventMessages) {
        List<EventHandler> messageEventHandlers = eventHandlers.get(eventMessage.eventType);
        if (messageEventHandlers != null) {
          for (EventHandler eventHandler : messageEventHandlers) {
            try {
              eventHandler.handle(eventMessage);
            } catch (Exception e) {
              logger.log(Level.INFO, "Failure in handling event data", e);
            }
          }
        }
      }
    }

    @OnWebSocketClose
    public void handleClose(Session closedSession, int statusCode, String reason) {
      logger.log(Level.INFO, "Event listener websocket closed with status " + statusCode + ". Reason: " + reason);
      closeClient();
    }

    @OnWebSocketError
    public void handleError(Session errorSession, Throwable throwable) {
      logger.log(Level.INFO, "Event listener websocket error", throwable);
      closeClient();
    }

    @OnWebSocketConnect
    public void startPolling(Session session) {
      logger.info("Sending poll request for events");
      MetricConfig metricConfig = new MetricConfig();
      long now = System.currentTimeMillis();
      metricConfig.setId("eventhandler");
      metricConfig.setMetricKey("/event");
      metricConfig.setStart(now - 20000);   // For the first chunk, send events from 20s ago
      metricConfig.setStop(now);            // ... up to the current moment.
      metricConfig.setStep(10000);          // Then keep sending data every 10 seconds.
      metricConfig.setMinSteps(2);          // Every time send data for the last 2 * 10 = 20 seconds.
      String json = gson.toJson(metricConfig);
      logger.info("Sending polling request " + json);
      try {
        session.getRemote().sendString(json);
      } catch (IOException e) {
        logger.log(Level.SEVERE, "Failed to send event polling request", e);
        closeClient();
      }
    }

    @Override
    public void run() {
      while (!Thread.currentThread().isInterrupted()) {
        if (client == null || !client.isRunning() && !client.isStarting() || client.isFailed()) {
          try {
            connect();
            connectLatch = new CountDownLatch(1);
          } catch (Exception e) {
            logger.log(Level.SEVERE, "Error setting up websocket connection - retrying", e);
            connectLatch = null;
          }
        }
        try {
          if (connectLatch != null) {
            connectLatch.await();
          } else {
            Thread.sleep(1000);
          }
        } catch (InterruptedException e) {
        }
      }
    }

    private void closeClient() {
      try {
        if (client.isRunning()) {
          client.stop();
          client.destroy();
        }
      } catch (Exception e) {
        logger.log(Level.INFO, "Error stopping websocket client", e);
      } finally {
        connectLatch.countDown();
      }
    }
  }
}


package com.nitorcreations.willow.deployer.statistics;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;

import com.nitorcreations.willow.deployer.DeployerControl;

/**
 * Abstract base class for statistics senders that use JMX to connect to the child process.
 *
 * @author Mikko Tommila
 */
public abstract class AbstractJMXStatisticsSender extends AbstractChildStatisticsSender {
  private static class MBeanConnectionInfo {
    public JMXConnector connector = null;
    public MBeanServerConnection server = null;
    long childPid = -2;
  }
  protected Map<String, MBeanConnectionInfo> connections = new HashMap<>();

  protected AbstractJMXStatisticsSender() {
  }

  @Override
  public void stop() {
    for (String childName : connections.keySet()) {
      closeMBeanServerConnection(childName);
    }
    super.stop();
  }

  /**
   * Return the MBean server connection to the child process.
   *
   * @return MBean server connection.
   */
  protected MBeanServerConnection getMBeanServerConnection(String childName) {
    MBeanConnectionInfo connInfo = connections.get(childName);
    if (connInfo == null) {
      connInfo = new MBeanConnectionInfo();
      connections.put(childName, connInfo);
    }
    long childPid = main.getFirstJavaChildPid(childName);
    if (childPid > 0 && childPid != connInfo.childPid && connInfo.connector != null) {
      try {
        connInfo.connector.close();
      } catch (IOException e) {
        logger.log(Level.FINE, "Failed to close JMXConnector", e);
      }
      connInfo.connector = null;
      connInfo.server = null;
    }
    if (childPid > 0) {
      try {
        if (connInfo.connector == null) {
          connInfo.connector = DeployerControl.getJMXConnector(childPid);
        }
        if (connInfo.connector != null && connInfo.server == null) {
          connInfo.server = connInfo.connector.getMBeanServerConnection();
          connInfo.childPid = childPid;
        }
      } catch (IOException e) {
        logger.log(Level.FINE, "Failed to get JMX connection", e);
        try {
          connInfo.connector.close();
        } catch (Exception e2) {
          logger.log(Level.FINE, "Failed to close JMXConnector", e2);
        }
        connInfo.connector = null;
        connInfo.server = null;
      }
    }
    return connInfo.server;
  }

  /**
   * Close the MBean server connection.
   */
  protected void closeMBeanServerConnection(String childName) {
    MBeanConnectionInfo connInfo = connections.get(childName);
    if (connInfo != null && connInfo.connector != null) {
      try {
        connInfo.connector.close();
      } catch (IOException e) {
        logger.log(Level.FINE, "Failed to close JMXConnector", e);
      }
      connInfo.connector = null;
    }
  }
}


/*
 * Copyright 2002-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.nitorcreations.willow.utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.parser.ParserException;

/**
 * Base class for Yaml factories.
 *
 * @author Dave Syer
 * @since 4.1
 */
public class YamlProcessor {

  private final Logger logger = Logger.getLogger(getClass().getCanonicalName());

  private ResolutionMethod resolutionMethod = ResolutionMethod.OVERRIDE;

  private InputStream[] resources = new InputStream[0];

  private List<DocumentMatcher> documentMatchers = Collections.emptyList();

  private boolean matchDefault = true;

  /**
   * A map of document matchers allowing callers to selectively use only
   * some of the documents in a YAML URI. In YAML documents are
   * separated by --- lines, and each document is converted
   * to properties before the match is made. E.g.
   *
   *

   * environment: dev
   * url: http://dev.bar.com
   * name: Developer Setup
   * ---
   * environment: prod
   * url:http://foo.bar.com
   * name: My Cool App
   *

   *
   * when mapped with
   * documentMatchers = YamlProcessor.mapMatcher({"environment": "prod"})
   * would end up as
   *
   *

   * environment=prod
   * url=http://foo.bar.com
   * name=My Cool App
   * url=http://dev.bar.com
   *

   * @param matchers a map of keys to value patterns (regular expressions)
   */
  public void setDocumentMatchers(DocumentMatcher... matchers) {
    this.documentMatchers = Arrays.asList(matchers);
  }

  /**
   * Flag indicating that a document for which all the
   * {@link #setDocumentMatchers(DocumentMatcher...) document matchers} abstain will
   * nevertheless match.
   * @param matchDefault the flag to set (default true)
   */
  public void setMatchDefault(boolean matchDefault) {
    this.matchDefault = matchDefault;
  }

  /**
   * Method to use for resolving URIs. Each URI will be converted to a Map, so
   * this property is used to decide which map entries to keep in the final output from
   * this factory.
   * @param resolutionMethod the resolution method to set (defaults to
   * {@link ResolutionMethod#OVERRIDE}).
   */
  public void setResolutionMethod(ResolutionMethod resolutionMethod) {
    this.resolutionMethod = resolutionMethod;
  }

  /**
   * Set locations of YAML {@link String URIs} to be loaded.
   * @see ResolutionMethod
   */
  public void setResources(InputStream... resources) {
    this.resources = resources;
  }

  /**
   * Provide an opportunity for subclasses to process the Yaml parsed from the supplied
   * URIs. Each URI is parsed in turn and the documents inside checked against
   * the {@link #setDocumentMatchers(DocumentMatcher...) matchers}. If a document
   * matches it is passed into the callback, along with its representation as
   * Properties. Depending on the {@link #setResolutionMethod(ResolutionMethod)} not all
   * of the documents will be parsed.
   * @param callback a callback to delegate to once matching documents are found
   * @see #createYaml()
   */
  protected void process(MatchCallback callback) {
    Yaml yaml = createYaml();
    for (InputStream next : this.resources) {
      boolean found = process(callback, yaml, next);
      if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
        return;
      }
    }
  }

  /**
   * Create the {@link Yaml} instance to use.
   */
  protected Yaml createYaml() {
    return new Yaml(new StrictMapAppenderConstructor());
  }

  private boolean process(MatchCallback callback, Yaml yaml, InputStream resource) {
    int count = 0;
    try {
      if (this.logger.isLoggable(Level.FINE)) {
        this.logger.fine("Loading from YAML: " + resource);
      }
      try (InputStream stream = resource) {
        for (Object object : yaml.loadAll(stream)) {
          if (object != null && process(asMap(object), callback)) {
            count++;
            if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND) {
              break;
            }
          }
        }
        if (this.logger.isLoggable(Level.FINE)) {
          this.logger.fine("Loaded " + count + " document" + (count > 1 ? "s" : "") +
              " from YAML URI: " + resource);
        }
      }
    } catch (IOException ex) {
      handleProcessError(resource, ex);
    }
    return count > 0;
  }

  private void handleProcessError(InputStream resource, IOException ex) {
    if (this.resolutionMethod != ResolutionMethod.FIRST_FOUND &&
        this.resolutionMethod != ResolutionMethod.OVERRIDE_AND_IGNORE) {
      throw new IllegalStateException(ex);
    }
    this.logger.warning("Could not load map from " + resource + ": " + ex.getMessage());
  }

  @SuppressWarnings("unchecked")
  private Map<String, Object> asMap(Object object) {
    // YAML can have numbers as keys
    Map<String, Object> result = new LinkedHashMap<String, Object>();
    if (!(object instanceof Map)) {
      // A document can be a text literal
      result.put("document", object);
      return result;
    }

    Map<Object, Object> map = (Map<Object, Object>) object;
    for (Entry<Object, Object> entry : map.entrySet()) {
      Object value = entry.getValue();
      if (value instanceof Map) {
        value = asMap(value);
      }
      Object key = entry.getKey();
      if (key instanceof CharSequence) {
        result.put(key.toString(), value);
      }
      else {
        // It has to be a map key in this case
        result.put("[" + key.toString() + "]", value);
      }
    }
    return result;
  }

  private boolean process(Map<String, Object> map, MatchCallback callback) {
    Properties properties = new Properties();
    properties.putAll(getFlattenedMap(map));

    if (this.documentMatchers.isEmpty()) {
      if (this.logger.isLoggable(Level.FINE)) {
        this.logger.fine("Merging document (no matchers set)" + map);
      }
      callback.process(properties, map);
      return true;
    }

    MatchStatus result = MatchStatus.ABSTAIN;
    for (DocumentMatcher matcher : this.documentMatchers) {
      MatchStatus match = matcher.matches(properties);
      result = MatchStatus.getMostSpecific(match, result);
      if (match == MatchStatus.FOUND) {
        if (this.logger.isLoggable(Level.FINE)) {
          this.logger.fine("Matched document with document matcher: " + properties);
        }
        callback.process(properties, map);
        return true;
      }
    }

    if (result == MatchStatus.ABSTAIN && this.matchDefault) {
      if (this.logger.isLoggable(Level.FINE)) {
        this.logger.fine("Matched document with default matcher: " + map);
      }
      callback.process(properties, map);
      return true;
    }

    this.logger.fine("Unmatched document");
    return false;
  }

  /**
   * Return a flattened version of the given map, recursively following any nested Map
   * or Collection values. Entries from the resulting map retain the same order as the
   * source. When called with the Map from a {@link MatchCallback} the result will
   * contain the same values as the {@link MatchCallback} Properties.
   * @param source the source map
   * @return a flattened map
   * @since 4.1.3
   */
  protected final Map<String, Object> getFlattenedMap(Map<String, Object> source) {
    Map<String, Object> result = new LinkedHashMap<String, Object>();
    buildFlattenedMap(result, source, null);
    return result;
  }

  private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, String path) {
    for (Entry<String, Object> entry : source.entrySet()) {
      String key = entry.getKey();
      if (path != null && !path.trim().isEmpty()) {
        if (key.startsWith("[")) {
          key = path + key;
        }
        else {
          key = path + "." + key;
        }
      }
      Object value = entry.getValue();
      if (value instanceof String) {
        result.put(key, value);
      }
      else if (value instanceof Map) {
        // Need a compound key
        @SuppressWarnings("unchecked")
        Map<String, Object> map = (Map<String, Object>) value;
        buildFlattenedMap(result, map, key);
      }
      else if (value instanceof Collection) {
        // Need a compound key
        @SuppressWarnings("unchecked")
        Collection<Object> collection = (Collection<Object>) value;
        int count = 0;
        for (Object object : collection) {
          buildFlattenedMap(result,
              Collections.singletonMap("[" + (count++) + "]", object), key);
        }
      }
      else {
        result.put(key, value == null ? "" : value);
      }
    }
  }

  /**
   * Callback interface used to process properties in a resulting map.
   */
  public interface MatchCallback {

    /**
     * Process the properties.
     * @param properties the properties to process
     * @param map a mutable result map
     */
    void process(Properties properties, Map<String, Object> map);
  }

  /**
   * Strategy interface used to test if properties match.
   */
  public interface DocumentMatcher {

    /**
     * Test if the given properties match.
     * @param properties the properties to test
     * @return the status of the match.
     */
    MatchStatus matches(Properties properties);
  }

  /**
   * Status returned from {@link DocumentMatcher#matches(java.util.Properties)}
   */
  public enum MatchStatus {

    /**
     * A match was found.
     */
    FOUND,

    /**
     * No match was found.
     */
    NOT_FOUND,

    /**
     * The matcher should not be considered.
     */
    ABSTAIN;

    /**
     * Compare two {@link MatchStatus} items, returning the most specific status.
     */
    public static MatchStatus getMostSpecific(MatchStatus a, MatchStatus b) {
      return (a.ordinal() < b.ordinal() ? a : b);
    }
  }

  /**
   * Method to use for resolving URIs.
   */
  public enum ResolutionMethod {

    /**
     * Replace values from earlier in the list.
     */
    OVERRIDE,

    /**
     * Replace values from earlier in the list, ignoring any failures.
     */
    OVERRIDE_AND_IGNORE,

    /**
     * Take the first URI in the list that exists and use just that.
     */
    FIRST_FOUND
  }

  /**
   * A specialized {@link Constructor} that checks for duplicate keys.
   */
  protected static class StrictMapAppenderConstructor extends Constructor {

    public  StrictMapAppenderConstructor() {
      super();
    }

    @Override
    protected Map<Object, Object> constructMapping(MappingNode node) {
      try {
        return super.constructMapping(node);
      } catch (IllegalStateException e) {
        throw new ParserException("while parsing MappingNode",
            node.getStartMark(), e.getMessage(), node.getEndMark());
      }
    }
    private static class DefaultMap extends AbstractMap<Object, Object> {
      private final Map<Object, Object> delegate;
      public DefaultMap(Map<Object, Object> delegate) {
        this.delegate = delegate;
      }
      @Override
      public Object put(Object key, Object value) {
        if (delegate.containsKey(key)) {
          throw new IllegalStateException("duplicate key: " + key);
        }
        return delegate.put(key, value);
      }
      @Override
      public Set<Entry<Object, Object>> entrySet() {
        return delegate.entrySet();
      }
    }
    @Override
    protected Map<Object, Object> createDefaultMap() {
      final Map<Object, Object> delegate = super.createDefaultMap();
      return new DefaultMap(delegate);
    }
  }
  private static class PutAllMatchCallback implements MatchCallback {
    private final Properties properties;
    public PutAllMatchCallback(Properties properties) {
      this.properties = properties;
    }
    @Override
    public void process(Properties properties, Map<String, Object> map) {
      this.properties.putAll(properties);
    }
  }
  protected Properties createProperties() {
    final Properties result = new Properties();
    process(new PutAllMatchCallback(result));
    return result;
  }

}


package com.nitorcreations.willow.autoscaler.metrics;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.nitorcreations.willow.autoscaler.config.AutoScalingGroupConfig;
import com.nitorcreations.willow.autoscaler.config.AutoScalingPolicy;
import com.nitorcreations.willow.autoscaler.deployment.AutoScalingGroupDeploymentStatus;
import com.nitorcreations.willow.messages.metrics.TimePoint;

public class AutoScalingGroupStatus {

  private final AutoScalingGroupConfig group;
  private AutoScalingGroupDeploymentStatus deploymentStatus;
  private final Map<String, List<TimePoint<Double>>> metricsData;

  public AutoScalingGroupStatus(AutoScalingGroupConfig group) {
    this.group = group;
    this.deploymentStatus = null;
    this.metricsData = new ConcurrentHashMap<>();
  }

  public synchronized void setDeploymentStatus(AutoScalingGroupDeploymentStatus deploymentStatus) {
    this.deploymentStatus = deploymentStatus;
  }

  public synchronized void addMetricValue(String metricName, TimePoint<Double> value) {
    List<TimePoint<Double>> data = metricsData.get(metricName);
    if (data == null) {
      data = new ArrayList<>();
      metricsData.put(metricName, data);
    }
    data.add(value);
    if (data.size() > 200) {
      data.remove(0);
    }
  }

  public synchronized List<TimePoint<Double>> getMetricValues(String metricName) {
    List<TimePoint<Double>> data = metricsData.get(metricName);
    if (data == null) {
      return Collections.emptyList();
    }
    return new ArrayList<>(data);
  }

  public AutoScalingGroupConfig getGroup() {
    return group;
  }

  public synchronized AutoScalingGroupDeploymentStatus getDeploymentStatus() {
    return deploymentStatus;
  }

  /**
   * Returns a list of auto scaling policies that have triggered, e.g. value is past threshold.
   *
   * Considers a policy triggered if there a few data points consistently past the threshold.
   *
   * @return
   */
  public List<AutoScalingPolicy> getTriggeredPolicies() {
    List<AutoScalingPolicy> triggeredPolicies = new ArrayList<>();
    for (AutoScalingPolicy policy : group.getScalingPolicies()) {
      List<TimePoint<Double>> metricValues = getMetricValues(policy.getMetricName());
      int triggerCount = 0;
      int triggerThreshold = 3;
      int inspectCount = 0;
      if (metricValues != null && metricValues.size() > 2) {
        List<TimePoint<Double>> reverse = new ArrayList<>(metricValues);
        Collections.reverse(reverse);
        for (TimePoint<Double> p : reverse) {
          ComparisonOperation op = ComparisonOperation.fromSymbol(policy.getMetricComparison());
          if (op.compare(p.getValue(), Double.valueOf(policy.getMetricThreshold()))) {
            triggerCount++;
          }
          inspectCount++;
          if (inspectCount >= 3) {
            break;
          }
        }
        if (triggerCount >= triggerThreshold) {
          triggeredPolicies.add(policy);
        }
      }
    }
    return triggeredPolicies;
  }

  public TimePoint<Double> getLastValueFor(String metric) {
    List<TimePoint<Double>> data = getMetricValues(metric);
    if (!data.isEmpty()) {
      return data.get(data.size() - 1);
    } else {
      return null;
    }
  }
}


package com.btr.proxy.selector.pac;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

/***************************************************************************
 * Implementation of PAC JavaScript functions.
 *
 * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
 ***************************************************************************
 */
public class PacScriptMethods implements ScriptMethods {
  private final Logger log = Logger.getLogger(PacScriptMethods.class
      .getCanonicalName());
  public static final String OVERRIDE_LOCAL_IP = "com.btr.proxy.pac.overrideLocalIP";

  private final static String GMT = "GMT";

  private final static List<String> DAYS = Collections.unmodifiableList(Arrays
      .asList("SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"));

  private final static List<String> MONTH = Collections.unmodifiableList(Arrays
      .asList("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP",
          "OCT", "NOV", "DEC"));

  private Calendar currentTime;

  /*************************************************************************
   * Constructor
   ************************************************************************/

  public PacScriptMethods() {
    super();
  }

  /*************************************************************************
   * isPlainHostName
   *
   * @see com.btr.proxy.selector.pac.ScriptMethods#isPlainHostName(java.lang.String)
   ************************************************************************/
  @Override
  public boolean isPlainHostName(String host) {
    return host.indexOf(".") < 0;
  }

  /*************************************************************************
   * Tests if an URL is in a given domain.
   *
   * @param host
   *          is the host name from the URL.
   * @param domain
   *          is the domain name to test the host name against.
   * @return true if the domain of host name matches.
   ************************************************************************/
  @Override
  public boolean dnsDomainIs(String host, String domain) {
    return host.endsWith(domain);
  }

  /*************************************************************************
   * Is true if the host name matches exactly the specified host name, or if
   * there is no domain name part in the host name, but the unqualified host
   * name matches.
   *
   * @param host
   *          the host name from the URL.
   * @param domain
   *          fully qualified host name with domain to match against.
   * @return true if matches else false.
   ************************************************************************/
  @Override
  public boolean localHostOrDomainIs(String host, String domain) {
    return domain.startsWith(host);
  }

  /*************************************************************************
   * Tries to resolve the host name. Returns true if succeeds.
   *
   * @param host
   *          is the host name from the URL.
   * @return true if resolvable else false.
   ************************************************************************/
  @Override
  public boolean isResolvable(String host) {
    try {
      InetAddress.getByName(host).getHostAddress();
      return true;
    } catch (UnknownHostException ex) {
      log.log(Level.FINE, "Hostname not resolveable {0}.", host);
    }
    return false;
  }

  /*************************************************************************
   * Returns true if the IP address of the host matches the specified IP address
   * pattern. Pattern and mask specification is done the same way as for SOCKS
   * configuration.
   *
   * Example: isInNet(host, "198.95.0.0", "255.255.0.0") is true if the IP
   * address of the host matches 198.95.*.*.
   *
   * @param host
   *          a DNS host name, or IP address. If a host name is passed, it will
   *          be resolved into an IP address by this function.
   * @param pattern
   *          an IP address pattern in the dot-separated format.
   * @param mask
   *          mask for the IP address pattern informing which parts of the IP
   *          address should be matched against. 0 means ignore, 255 means
   *          match.
   * @return true if it matches else false.
   ************************************************************************/
  @Override
  public boolean isInNet(String host, String pattern, String mask) {
    host = dnsResolve(host);
    if (host == null || host.length() == 0) {
      return false;
    }
    long lhost = parseIpAddressToLong(host);
    long lpattern = parseIpAddressToLong(pattern);
    long lmask = parseIpAddressToLong(mask);
    return (lhost & lmask) == lpattern;
  }

  /*************************************************************************
   * Convert a string representation of a IP to a long.
   *
   * @param address
   *          to convert.
   * @return the address as long.
   ************************************************************************/
  private long parseIpAddressToLong(String address) {
    long result = 0;
    String[] parts = address.split("\\.");
    long shift = 24;
    for (String part : parts) {
      long lpart = Long.parseLong(part);

      result |= lpart << shift;
      shift -= 8;
    }
    return result;
  }

  /*************************************************************************
   * Resolves the given DNS host name into an IP address, and returns it in the
   * dot separated format as a string.
   *
   * @param host
   *          the host to resolve.
   * @return the resolved IP, empty string if not resolvable.
   ************************************************************************/
  @Override
  public String dnsResolve(String host) {
    try {
      return InetAddress.getByName(host).getHostAddress();
    } catch (UnknownHostException e) {
      log.log(Level.FINE, "DNS name not resolvable {0}.", host);
    }
    return "";
  }

  /*************************************************************************
   * Returns the IP address of the host that the process is running on, as a
   * string in the dot-separated integer format.
   *
   * @return an IP as string.
   ************************************************************************/
  @Override
  public String myIpAddress() {
    return getLocalAddressOfType(Inet4Address.class);
  }

  /*************************************************************************
   * Get the current IP address of the computer. This will return the first
   * address of the first network interface that is a "real" IP address of the
   * given type.
   *
   * @param cl
   *          the type of address we are searching for.
   * @return the address as string or "" if not found.
   ************************************************************************/
  private String getLocalAddressOfType(Class<? extends InetAddress> cl) {
    try {
      String overrideIP = System.getProperty(OVERRIDE_LOCAL_IP);
      if (overrideIP != null && overrideIP.trim().length() > 0) {
        return overrideIP.trim();
      }
      Enumeration<NetworkInterface> interfaces = NetworkInterface
          .getNetworkInterfaces();
      while (interfaces.hasMoreElements()) {
        NetworkInterface current = interfaces.nextElement();
        if (!current.isUp() || current.isLoopback() || current.isVirtual()) {
          continue;
        }
        Enumeration<InetAddress> addresses = current.getInetAddresses();
        while (addresses.hasMoreElements()) {
          InetAddress adr = addresses.nextElement();
          if (cl.isInstance(adr)) {
            log.log(Level.FINER, "Local address resolved to {0}", adr);
            return adr.getHostAddress();
          }
        }
      }
      return "";
    } catch (IOException e) {
      log.log(Level.FINE, "Local address not resolvable.");
      return "";
    }
  }

  /*************************************************************************
   * Returns the number of DNS domain levels (number of dots) in the host name.
   *
   * @param host
   *          is the host name from the URL.
   * @return number of DNS domain levels.
   ************************************************************************/
  @Override
  public int dnsDomainLevels(String host) {
    int count = 0;
    int startPos = 0;
    while ((startPos = host.indexOf(".", startPos + 1)) > -1) {
      count++;
    }
    return count;
  }

  /*************************************************************************
   * Returns true if the string matches the specified shell expression.
   * Actually, currently the patterns are shell expressions, not regular
   * expressions.
   *
   * @param str
   *          is any string to compare (e.g. the URL, or the host name).
   * @param shexp
   *          is a shell expression to compare against.
   * @return true if the string matches, else false.
   ************************************************************************/
  @Override
  public boolean shExpMatch(String str, String shexp) {
    StringTokenizer tokenizer = new StringTokenizer(shexp, "*");
    int startPos = 0;
    while (tokenizer.hasMoreTokens()) {
      String token = tokenizer.nextToken();
      int temp = str.indexOf(token, startPos);

      // Must start with first token
      if (startPos == 0 && !shexp.startsWith("*") && temp != 0) {
        return false;
      }
      // Last one ends with last token
      if (!tokenizer.hasMoreTokens() && !shexp.endsWith("*")
          && !str.endsWith(token)) {
        return false;
      }

      if (temp == -1) {
        return false;
      } else {
        startPos = temp + token.length();
      }
    }

    return true;
  }

  /*************************************************************************
   * Only the first parameter is mandatory. Either the second, the third, or
   * both may be left out. If only one parameter is present, the function yields
   * a true value on the weekday that the parameter represents. If the string
   * "GMT" is specified as a second parameter, times are taken to be in GMT,
   * otherwise in local time zone. If both wd1 and wd2 are defined, the
   * condition is true if the current weekday is in between those two weekdays.
   * Bounds are inclusive. If the "GMT" parameter is specified, times are taken
   * to be in GMT, otherwise the local time zone is used.
   *
   * @param wd1
   *          weekday 1 is one of SUN MON TUE WED THU FRI SAT
   * @param wd2
   *          weekday 2 is one of SUN MON TUE WED THU FRI SAT
   * @param gmt
   *          "GMT" for gmt time format else "undefined"
   * @return true if current day matches the criteria.
   ************************************************************************/
  @Override
  public boolean weekdayRange(String wd1, String wd2, String gmt) {
    boolean useGmt = GMT.equalsIgnoreCase(wd2) || GMT.equalsIgnoreCase(gmt);
    Calendar cal = getCurrentTime(useGmt);

    int currentDay = cal.get(Calendar.DAY_OF_WEEK) - 1;
    int from = DAYS.indexOf(wd1 == null ? null : wd1
        .toUpperCase(Locale.ENGLISH));
    int to = DAYS.indexOf(wd2 == null ? null : wd2.toUpperCase(Locale.ENGLISH));
    if (to == -1) {
      to = from;
    }

    if (to < from) {
      return currentDay >= from || currentDay <= to;
    } else {
      return currentDay >= from && currentDay <= to;
    }
  }

  /*************************************************************************
   * Sets a calendar with the current time. If this is set all date and time
   * based methods will use this calendar to determine the current time instead
   * of the real time. This is only be used by unit tests and is not part of the
   * public API.
   *
   * @param cal
   *          a Calendar to set.
   ************************************************************************/
  public void setCurrentTime(Calendar cal) {
    this.currentTime = cal;
  }

  /*************************************************************************
   * Gets a calendar set to the current time. This is used by the date and time
   * based methods.
   *
   * @param useGmt
   *          flag to indicate if the calendar is to be created in GMT time or
   *          local time.
   * @return a Calendar set to the current time.
   ************************************************************************/
  private Calendar getCurrentTime(boolean useGmt) {
    if (this.currentTime != null) { // Only used for unit tests
      return (Calendar) this.currentTime.clone();
    }
    return Calendar.getInstance(useGmt ? TimeZone.getTimeZone(GMT) : TimeZone
        .getDefault());
  }

  /*************************************************************************
   * Only the first parameter is mandatory. All other parameters can be left out
   * therefore the meaning of the parameters changes. The method definition
   * shows the version with the most possible parameters filled. The real
   * meaning of the parameters is guessed from it's value. If "from" and "to"
   * are specified then the bounds are inclusive. If the "GMT" parameter is
   * specified, times are taken to be in GMT, otherwise the local time zone is
   * used.
   *
   * @param day1
   *          is the day of month between 1 and 31 (as an integer).
   * @param month1
   *          one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
   * @param year1
   *          is the full year number, for example 1995 (but not 95). Integer.
   * @param day2
   *          is the day of month between 1 and 31 (as an integer).
   * @param month2
   *          one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
   * @param year2
   *          is the full year number, for example 1995 (but not 95). Integer.
   * @param gmt
   *          "GMT" for gmt time format else "undefined"
   * @return true if the current date matches the given range.
   ************************************************************************/
  @Override
  public boolean dateRange(Object day1, Object month1, Object year1,
      Object day2, Object month2, Object year2, Object gmt) {

    // Guess the parameter meanings.
    Map<String, Integer> params = new HashMap<String, Integer>();
    parseDateParam(params, day1);
    parseDateParam(params, month1);
    parseDateParam(params, year1);
    parseDateParam(params, day2);
    parseDateParam(params, month2);
    parseDateParam(params, year2);
    parseDateParam(params, gmt);

    // Get current date
    boolean useGmt = params.get("gmt") != null;
    Calendar cal = getCurrentTime(useGmt);
    Date current = cal.getTime();

    // Build the "from" date
    if (params.get("day1") != null) {
      cal.set(Calendar.DAY_OF_MONTH, params.get("day1"));
    }
    if (params.get("month1") != null) {
      cal.set(Calendar.MONTH, params.get("month1"));
    }
    if (params.get("year1") != null) {
      cal.set(Calendar.YEAR, params.get("year1"));
    }
    Date from = cal.getTime();

    // Build the "to" date
    Date to;
    if (params.get("day2") != null) {
      cal.set(Calendar.DAY_OF_MONTH, params.get("day2"));
    }
    if (params.get("month2") != null) {
      cal.set(Calendar.MONTH, params.get("month2"));
    }
    if (params.get("year2") != null) {
      cal.set(Calendar.YEAR, params.get("year2"));
    }
    to = cal.getTime();

    // Need to increment to the next month?
    if (to.before(from)) {
      cal.add(Calendar.MONTH, +1);
      to = cal.getTime();
    }
    // Need to increment to the next year?
    if (to.before(from)) {
      cal.add(Calendar.YEAR, +1);
      cal.add(Calendar.MONTH, -1);
      to = cal.getTime();
    }

    return current.compareTo(from) >= 0 && current.compareTo(to) <= 0;
  }

  /*************************************************************************
   * Try to guess the type of the given parameter and put it into the params
   * map.
   *
   * @param params
   *          a map to put the parsed parameters into.
   * @param value
   *          to parse and specify the type for.
   ************************************************************************/
  private void parseDateParam(Map<String, Integer> params, Object value) {
    if (value instanceof Number) {
      int n = ((Number) value).intValue();
      if (n <= 31) {
        // Its a day
        if (params.get("day1") == null) {
          params.put("day1", n);
        } else {
          params.put("day2", n);
        }
      } else {
        // Its a year
        if (params.get("year1") == null) {
          params.put("year1", n);
        } else {
          params.put("year2", n);
        }
      }
    }

    if (value instanceof String) {
      int n = MONTH.indexOf(((String) value).toUpperCase(Locale.ENGLISH));
      if (n > -1) {
        // Its a month
        if (params.get("month1") == null) {
          params.put("month1", n);
        } else {
          params.put("month2", n);
        }
      }
    }

    if (GMT.equalsIgnoreCase(String.valueOf(value))) {
      params.put("gmt", 1);
    }
  }

  /*************************************************************************
   * Some parameters can be left out therefore the meaning of the parameters
   * changes. The method definition shows the version with the most possible
   * parameters filled. The real meaning of the parameters is guessed from it's
   * value. If "from" and "to" are specified then the bounds are inclusive. If
   * the "GMT" parameter is specified, times are taken to be in GMT, otherwise
   * the local time zone is used.

   *
   *

   * timeRange(hour)
   * timeRange(hour1, hour2)
   * timeRange(hour1, min1, hour2, min2)
   * timeRange(hour1, min1, sec1, hour2, min2, sec2)
   * timeRange(hour1, min1, sec1, hour2, min2, sec2, gmt)
   *

   *
   * @param hour1
   *          is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
   * @param min1
   *          minutes from 0 to 59.
   * @param sec1
   *          seconds from 0 to 59.
   * @param hour2
   *          is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
   * @param min2
   *          minutes from 0 to 59.
   * @param sec2
   *          seconds from 0 to 59.
   * @param gmt
   *          "GMT" for gmt time format else "undefined"
   * @return true if the current time matches the given range.
   ************************************************************************/
  @Override
  public boolean timeRange(Object hour1, Object min1, Object sec1,
      Object hour2, Object min2, Object sec2, Object gmt) {
    boolean useGmt = GMT.equalsIgnoreCase(String.valueOf(min1))
        || GMT.equalsIgnoreCase(String.valueOf(sec1))
        || GMT.equalsIgnoreCase(String.valueOf(min2))
        || GMT.equalsIgnoreCase(String.valueOf(gmt));

    Calendar cal = getCurrentTime(useGmt);
    cal.set(Calendar.MILLISECOND, 0);
    Date current = cal.getTime();
    Date from;
    Date to;
    if (sec2 instanceof Number) {
      cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
      cal.set(Calendar.MINUTE, ((Number) min1).intValue());
      cal.set(Calendar.SECOND, ((Number) sec1).intValue());
      from = cal.getTime();

      cal.set(Calendar.HOUR_OF_DAY, ((Number) hour2).intValue());
      cal.set(Calendar.MINUTE, ((Number) min2).intValue());
      cal.set(Calendar.SECOND, ((Number) sec2).intValue());
      to = cal.getTime();
    } else if (hour2 instanceof Number) {
      cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
      cal.set(Calendar.MINUTE, ((Number) min1).intValue());
      cal.set(Calendar.SECOND, 0);
      from = cal.getTime();

      cal.set(Calendar.HOUR_OF_DAY, ((Number) sec1).intValue());
      cal.set(Calendar.MINUTE, ((Number) hour2).intValue());
      cal.set(Calendar.SECOND, 59);
      to = cal.getTime();
    } else if (min1 instanceof Number) {
      cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
      cal.set(Calendar.MINUTE, 0);
      cal.set(Calendar.SECOND, 0);
      from = cal.getTime();

      cal.set(Calendar.HOUR_OF_DAY, ((Number) min1).intValue());
      cal.set(Calendar.MINUTE, 59);
      cal.set(Calendar.SECOND, 59);
      to = cal.getTime();
    } else {
      cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
      cal.set(Calendar.MINUTE, 0);
      cal.set(Calendar.SECOND, 0);
      from = cal.getTime();

      cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
      cal.set(Calendar.MINUTE, 59);
      cal.set(Calendar.SECOND, 59);
      to = cal.getTime();
    }

    if (to.before(from)) {
      cal.setTime(to);
      cal.add(Calendar.DATE, +1);
      to = cal.getTime();
    }

    return current.compareTo(from) >= 0 && current.compareTo(to) <= 0;
  }

  // Microsoft PAC extensions for IPv6 support.

  /*************************************************************************
   * isResolvableEx
   *
   * @see com.btr.proxy.selector.pac.ScriptMethods#isResolvableEx(java.lang.String)
   ************************************************************************/
  @Override
  public boolean isResolvableEx(String host) {
    return isResolvable(host);
  }

  /*************************************************************************
   * isInNetEx
   *
   * @see com.btr.proxy.selector.pac.ScriptMethods#isInNetEx(java.lang.String,
   *      java.lang.String)
   ************************************************************************/
  @Override
  public boolean isInNetEx(String ipAddress, String ipPrefix) {
    // TODO rossi 27.06.2011 Auto-generated method stub
    return false;
  }

  /*************************************************************************
   * dnsResolveEx
   *
   * @see com.btr.proxy.selector.pac.ScriptMethods#dnsResolveEx(java.lang.String)
   ************************************************************************/
  @Override
  public String dnsResolveEx(String host) {
    StringBuilder result = new StringBuilder();
    try {
      InetAddress[] list = InetAddress.getAllByName(host);
      for (InetAddress inetAddress : list) {
        result.append(inetAddress.getHostAddress());
        result.append("; ");
      }
    } catch (UnknownHostException e) {
      log.log(Level.FINE, "DNS name not resolvable {0}.", host);
    }
    return result.toString();
  }

  /*************************************************************************
   * myIpAddressEx
   *
   * @see com.btr.proxy.selector.pac.ScriptMethods#myIpAddressEx()
   ************************************************************************/
  @Override
  public String myIpAddressEx() {
    return getLocalAddressOfType(Inet6Address.class);
  }

  /*************************************************************************
   * sortIpAddressList
   *
   * @see com.btr.proxy.selector.pac.ScriptMethods#sortIpAddressList(java.lang.String)
   ************************************************************************/
  @Override
  public String sortIpAddressList(String ipAddressList) {
    if (ipAddressList == null || ipAddressList.trim().length() == 0) {
      return "";
    }
    String[] ipAddressToken = ipAddressList.split(";");
    List<InetAddress> parsedAddresses = new ArrayList<InetAddress>();
    for (String ip : ipAddressToken) {
      try {
        parsedAddresses.add(InetAddress.getByName(ip));
      } catch (UnknownHostException e) {
        log.log(Level.FINE, "Ip address not resolveable {0}.", ip);
      }
    }
    Collections.sort(parsedAddresses, null);
    return ipAddressList;
  }

  /*************************************************************************
   * getClientVersion
   *
   * @see com.btr.proxy.selector.pac.ScriptMethods#getClientVersion()
   ************************************************************************/
  @Override
  public String getClientVersion() {
    return "1.0";
  }

}


package com.btr.proxy.selector.pac;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/****************************************************************************
 * Utility to check availablility of javax.script
 *
 * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
 ***************************************************************************/
abstract class ScriptAvailability {

  /*************************************************************************
   * Checks whether javax.script is available or not. Completely done per
   * Reflection to allow compilation under Java 1.5
   *
   * @return true if javax.script is available; false otherwise
   ************************************************************************/
  public static boolean isJavaxScriptingAvailable() {
    Object engine = null;
    try {
      Class<?> managerClass = Class.forName("javax.script.ScriptEngineManager");
      Method m = managerClass.getMethod("getEngineByMimeType", String.class);
      engine = m.invoke(managerClass.newInstance(), "text/javascript");
    } catch (ClassNotFoundException | NoSuchMethodException
        | IllegalAccessException | InvocationTargetException
        | InstantiationException e) {
      return false;
    }

    return engine != null;
  }

  /*************************************************************************
   * Constructor
   ************************************************************************/

  ScriptAvailability() {
    super();
  }
}


package com.btr.proxy.selector.pac;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang3.StringUtils;

/*****************************************************************************
 * ProxySelector that will use a PAC script to find an proxy for a given URI.
 *
 * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
 ****************************************************************************/
public class PacProxySelector extends ProxySelector {
  protected static final Logger log = Logger.getLogger(PacProxySelector.class
      .getCanonicalName());

  private final static int PROXY_TYPE_LEN = 6;
  private static final String PAC_PROXY = "PROXY";
  private static final String PAC_SOCKS = "SOCKS";
  private static final String PAC_DIRECT = "DIRECT";

  private PacScriptParser pacScriptParser;

  private static volatile boolean enabled = true;

  /*************************************************************************
   * Constructor
   *
   * @param pacSource
   *          the source for the PAC file.
   ************************************************************************/

  public PacProxySelector(PacScriptSource pacSource) {
    super();
    selectEngine(pacSource);
  }

  /*************************************************************************
   * Can be used to enable / disable the proxy selector. If disabled it will
   * return DIRECT for all urls.
   *
   * @param enable
   *          the new status to set.
   ************************************************************************/

  public static void setEnabled(boolean enable) {
    enabled = enable;
  }

  /*************************************************************************
   * Checks if the selector is currently enabled.
   *
   * @return true if enabled else false.
   ************************************************************************/

  public static boolean isEnabled() {
    return enabled;
  }

  /*************************************************************************
   * Selects one of the available PAC parser engines.
   *
   * @param pacSource
   *          to use as input.
   ************************************************************************/

  private void selectEngine(PacScriptSource pacSource) {
    try {
      log.log(Level.INFO, "Using javax.script JavaScript engine.");
      this.pacScriptParser = new JavaxPacScriptParser(pacSource);
    } catch (Exception e) {
      log.log(Level.INFO, "PAC parser error.", e);
    }
  }

  /*************************************************************************
   * connectFailed
   *
   * @see java.net.ProxySelector#connectFailed(java.net.URI,
   *      java.net.SocketAddress, java.io.IOException)
   ************************************************************************/
  @Override
  public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
    // Not used.
  }

  /*************************************************************************
   * select
   *
   * @see java.net.ProxySelector#select(java.net.URI)
   ************************************************************************/
  @Override
  public List<Proxy> select(URI uri) {
    if (uri == null) {
      throw new IllegalArgumentException("URI must not be null.");
    }

    // Fix for Java 1.6.16+ where we get a infinite loop because
    // URL.connect(Proxy.NO_PROXY) does not work as expected.
    if (!enabled) {
      return noProxyList();
    }

    return findProxy(uri);
  }

  /*************************************************************************
   * Evaluation of the given URL with the PAC-file.
   *
   * Two cases can be handled here: DIRECT Fetch the object directly from the
   * content HTTP server denoted by its URL PROXY name:port Fetch the object via
   * the proxy HTTP server at the given location (name and port)
   *
   * @param uri
   *          URI to be evaluated.
   * @return Proxy-object list as result of the evaluation.
   ************************************************************************/

  private List<Proxy> findProxy(URI uri) {
    try {
      List<Proxy> proxies = new ArrayList<Proxy>();
      String parseResult = this.pacScriptParser.evaluate(uri.toString(),
          uri.getHost());
      String[] proxyDefinitions = parseResult.split("[;]");
      for (String proxyDef : proxyDefinitions) {
        if (!StringUtils.isBlank(proxyDef)) {
          proxies.add(buildProxyFromPacResult(proxyDef));
        }
      }
      return proxies;
    } catch (ProxyEvaluationException e) {
      log.log(Level.INFO, "PAC resolving error.", e);
      return noProxyList();
    }
  }

  /*************************************************************************
   * The proxy evaluator will return a proxy string. This method will take this
   * string and build a matching Proxy for it.
   *
   * @param pacResult
   *          the result from the PAC parser.
   * @return a Proxy
   ************************************************************************/
  public static Proxy buildProxyFromPacResult(String pacResult) {
    if (StringUtils.isBlank(pacResult) || pacResult.trim().toUpperCase(Locale.ENGLISH).startsWith(PAC_DIRECT)) {
      return Proxy.NO_PROXY;
    }
    String proxyDef = pacResult.trim();
    Proxy.Type type = Proxy.Type.HTTP;
    if (proxyDef.toUpperCase(Locale.ENGLISH).startsWith(PAC_SOCKS)) {
      type = Proxy.Type.SOCKS;
    } else if (!proxyDef.toUpperCase(Locale.ENGLISH).startsWith(PAC_PROXY)) {
      return Proxy.NO_PROXY;
    }

    String host = proxyDef.substring(PROXY_TYPE_LEN);
    Integer port = DEFAULT_PROXY_PORT;

    // Split port from host
    int indexOfPort = host.indexOf(':');
    if (indexOfPort != -1) {
      port = Integer.parseInt(host.substring(indexOfPort + 1).trim());
      host = host.substring(0, indexOfPort).trim();
    }

    SocketAddress adr = InetSocketAddress.createUnresolved(host, port);
    return new Proxy(type, adr);
  }

  public static final int DEFAULT_PROXY_PORT = 80;

  private static List<Proxy> noProxyList;

  /*************************************************************************
   * Gets an unmodifiable proxy list that will have as it's only entry an DIRECT
   * proxy.
   *
   * @return a list with a DIRECT proxy in it.
   ************************************************************************/

  public static synchronized List<Proxy> noProxyList() {
    if (noProxyList == null) {
      ArrayList<Proxy> list = new ArrayList<Proxy>(1);
      list.add(Proxy.NO_PROXY);
      noProxyList = Collections.unmodifiableList(list);
    }
    return noProxyList;
  }

  /*************************************************************************
   * Build a PAC proxy selector for the given URL.
   *
   * @param url
   *          to fetch the PAC script from.
   * @return a PacProxySelector or null if it is not possible to build a working
   *         selector.
   ************************************************************************/

  public static PacProxySelector buildPacSelectorForUrl(String url) {
    PacProxySelector result = null;
    PacScriptSource pacSource = new UrlPacScriptSource(url);
    if (pacSource.isScriptValid()) {
      result = new PacProxySelector(pacSource);
    }
    return result;
  }

}


package com.btr.proxy.selector.pac;

import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

/*****************************************************************************
 * PAC parser using the Rhino JavaScript engine bundled with Java 1.6

 * If you need PAC support with Java 1.5 then you should have a look at
 * RhinoPacScriptParser.
 *
 * More information about PAC can be found there:

 * Proxy_auto-config

 * web-browser-auto-proxy-configuration

 *
 * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
 ****************************************************************************/
public class JavaxPacScriptParser implements PacScriptParser {
  static final String SCRIPT_METHODS_OBJECT = "__pacutil";
  static final Logger log = Logger.getLogger(JavaxPacScriptParser.class
      .getCanonicalName());
  private final PacScriptSource source;
  private final ScriptEngine engine;

  /*************************************************************************
   * Constructor
   *
   * @param source
   *          the source for the PAC script.
   * @throws ProxyEvaluationException
   *           on error.
   ************************************************************************/
  public JavaxPacScriptParser(PacScriptSource source)
      throws ProxyEvaluationException {
    this.source = source;
    this.engine = setupEngine();
  }

  /*************************************************************************
   * Initializes the JavaScript engine and adds aliases for the functions
   * defined in ScriptMethods.
   *
   * @throws ProxyEvaluationException
   *           on error.
   ************************************************************************/
  private ScriptEngine setupEngine() throws ProxyEvaluationException {
    ScriptEngineManager mng = new ScriptEngineManager();
    ScriptEngine newEngine = mng.getEngineByMimeType("text/javascript");
    assert newEngine != null : "Javascript engine not available";
    newEngine.put(SCRIPT_METHODS_OBJECT, new PacScriptMethods());

    Class<?> scriptMethodsClazz = ScriptMethods.class;
    Method[] scriptMethods = scriptMethodsClazz.getMethods();

    for (Method method : scriptMethods) {
      String name = method.getName();
      int args = method.getParameterTypes().length;
      StringBuilder toEval = new StringBuilder(name).append(" = function(");
      for (int i = 0; i < args; i++) {
        if (i > 0) {
          toEval.append(",");
        }
        toEval.append("arg").append(i);
      }
      toEval.append(") {return ");

      String functionCall = buildFunctionCallCode(name, args);

      // If return type is java.lang.String convert it to a JS string
      if (String.class.isAssignableFrom(method.getReturnType())) {
        functionCall = "String(" + functionCall + ")";
      }
      toEval.append(functionCall).append("; }");
      try {
        newEngine.eval(toEval.toString());
      } catch (ScriptException e) {
        log.log(Level.INFO, "JS evaluation error when creating alias for "
            + name + ".", e);
        throw new ProxyEvaluationException("Error setting up script engine", e);
      }
    }

    return newEngine;
  }

  /*************************************************************************
   * Builds a JavaScript code snippet to call a function that we bind.
   *
   * @param functionName
   *          of the bound function
   * @param args
   *          of the bound function
   * @return the JS code to invoke the method.
   ************************************************************************/

  private String buildFunctionCallCode(String functionName, int args) {
    StringBuilder functionCall = new StringBuilder();
    functionCall.append(SCRIPT_METHODS_OBJECT).append(".").append(functionName)
    .append("(");
    for (int i = 0; i < args; i++) {
      if (i > 0) {
        functionCall.append(",");
      }
      functionCall.append("arg").append(i);
    }
    functionCall.append(")");
    return functionCall.toString();
  }

  /***************************************************************************
   * Gets the source of the PAC script used by this parser.
   *
   * @return a PacScriptSource.
   **************************************************************************/
  @Override
  public PacScriptSource getScriptSource() {
    return this.source;
  }

  /*************************************************************************
   * Evaluates the given URL and host against the PAC script.
   *
   * @param url
   *          the URL to evaluate.
   * @param host
   *          the host name part of the URL.
   * @return the script result.
   * @throws ProxyEvaluationException
   *           on execution error.
   ************************************************************************/
  @Override
  public String evaluate(String url, String host)
      throws ProxyEvaluationException {
    try {
      StringBuilder script = new StringBuilder(this.source.getScriptContent());
      String evalMethod = " ;FindProxyForURL (\"" + url + "\",\"" + host
          + "\")";
      script.append(evalMethod);
      Object result = this.engine.eval(script.toString());
      return (String) result;
    } catch (Exception e) {
      log.log(Level.INFO, "JS evaluation error.", e);
      throw new ProxyEvaluationException("Error while executing PAC script: "
          + e.getMessage(), e);
    }

  }
}