/* Copyright 2014 Sven van der Meer <vdmeer.sven@mykolab.com>
 *
 * 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 de.vandermeer.execs;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroupFile;

import de.vandermeer.execs.options.AO_AppHomeDirectory;
import de.vandermeer.execs.options.AO_ClassmapFile;
import de.vandermeer.execs.options.AO_PropertyFile;
import de.vandermeer.execs.options.AO_StgFile;
import de.vandermeer.execs.options.AO_Target;
import de.vandermeer.execs.options.ApplicationOption;
import de.vandermeer.execs.options.ExecS_CliParser;

/**
 * Application to generate run scripts for other applications, supporting windows, CygWin and bash.
 *
 * @author     Sven van der Meer <vdmeer.sven@mykolab.com>
 * @version    v0.4.0 build 170413 (13-Apr-17) for Java 1.8
 * @since      v0.0.6
 */
public class Gen_RunScripts implements ExecS_Application {

	/** Application name. */
	public final static String APP_NAME = "gen-run-scripts";

	/** Application display name. */
	public final static String APP_DISPLAY_NAME = "Generate Application Run Scripts";

	/** Application version, should be same as the version in the class JavaDoc. */
	public final static String APP_VERSION = "v0.4.0 build 170413 (13-Apr-17) for Java 1.8";

	/** A property key for the script name of the generic run script, without file extension. */
	public final static String PROP_RUN_SCRIPT_NAME = "run.script.name";

	/** A property key for the class name of the ExexS executor to be used, must be a fully qualified class name. */
	public final static String PROP_RUN_CLASS = "run.script.class";

	/** A property key for the JAVA class path. */
	public final static String PROP_JAVA_CP = "java.classpath";

	/** The string all java properties for running the JVM must start with. */
	public final static String PROP_JAVAPROP_START = "java.property.";

	/** A property key to set auto-script generation for registered executable applications. */
	public final static String PROP_EXECS_CLASSMAP = "execs.classmap";

	/** A property key to set class map for executable applications. */
	public final static String PROP_EXECS_AUTOGEN_REGISTERED = "execs.autogenerate.registered";

	/** Local CLI options for CLI parsing. */
	protected ExecS_CliParser cli;

	/** The application option for the property file. */
	protected AO_PropertyFile optionPropFile;

	/** The application option for the target. */
	protected AO_Target optionTarget;

	/** The application option for the STG file. */
	protected AO_StgFile optionStgFile;

	/** The application option for the class map file. */
	protected AO_ClassmapFile optionClassMapFile;

	/** The application option for the application home directory. */
	protected AO_AppHomeDirectory optionAppHome;

	/** Properties for configuration options. */
	protected Properties configuration;

	/** Properties with a class map. */
	protected Properties classMap;

	/** String template group loaded from STG file. */
	protected STGroupFile stg;

	/** The target set for generation. */
	protected String target;

	/** The application home directory, ideally something like "abc/xyz" as root for generating scripts. */
	protected String applicationDir;

	/** The name of the output directory, null means cannot output or not tested yet. */
	protected String outputDir;

	/** Local class map, must be set by calling ExecS instance. */
	Map<String, Class<? extends ExecS_Application>> execClassMap;

	/**
	 * Returns a new generator with parameterized CLI object.
	 */
	public Gen_RunScripts() {
		this.cli = new ExecS_CliParser();

		this.optionPropFile = new AO_PropertyFile(false, "de/vandermeer/execs/configuration.properties", "File name of a property file with specific configuration options to generate run scripts.");
		this.cli.addOption(this.optionPropFile);

		this.optionTarget = new AO_Target(true, "The target defines if the script is generated for bat (Windows batch file), sh (UNIX bash script) or cyg (Cygwin bash script).");
		this.cli.addOption(this.optionTarget);

		this.optionStgFile = new AO_StgFile(false, "de/vandermeer/execs/executable-script.stg", "The STG (String Template Group) file must define a large set of templates for the generation of run scripts. Details are in the JavaDoc of the application implementation.");
		this.cli.addOption(this.optionStgFile);

		this.optionClassMapFile = new AO_ClassmapFile(false, PROP_EXECS_CLASSMAP, "The class map file contains mappings from a class name to a script name. This mapping is used to generate run scripts for applications that are not registered with an executor, or if automated generation (for registered applications) is not required or wanted.");
		this.cli.addOption(this.optionClassMapFile);

		this.optionAppHome = new AO_AppHomeDirectory(true, "The application home directory will be used as the absolute path in which the script is started in. All other paths are calculated from this absolute path.");
		this.cli.addOption(this.optionAppHome);
	}

	/**
	 * Hook for a calling ExecS instance to set its class map for the script generator
	 * @param execClassMap calling executor class map to create run scripts from
	 */
	public void setClassMap(Map<String, Class<? extends ExecS_Application>> execClassMap){
		this.execClassMap = execClassMap;
	}

	@Override
	public int executeApplication(String[] args) {
		// parse command line, exit with help screen if error
		int ret = ExecS_Application.super.executeApplication(args);
		if(ret!=0){
			return ret;
		}

		// init generator, exit on error (init methods will have printed error to standard error)
		ret = this.initConfiguration();
		if(ret!=0){
			return ret;
		}
		ret = this.initApplicationDir();
		if(ret!=0){
			return ret;
		}
		ret = this.initTargetAndStg();
		if(ret!=0){
			return ret;
		}
		ret = this.initClassmap();
		if(ret==-1){
			return ret;
		}
		ret = this.initOutputDir();
		if(ret!=0){
			return ret;
		}

		HashMap<String, Boolean> targetMap = new HashMap<>();
		targetMap.put(target, true);
		String fnExtension = this.stg.getInstanceOf("fnExtension").add("target", targetMap).render();
		String targetFileSep = this.stg.getInstanceOf("pathSeparator").add("target", targetMap).render();

		//build main run script
		String outFN = this.outputDir + File.separator + this.configuration.get(PROP_RUN_SCRIPT_NAME) + fnExtension;
		System.out.println(" --> generating main run script - " + outFN);
		ST targetRunST = this.stg.getInstanceOf("generateRun");
		targetRunST.add("target", targetMap);
		targetRunST.add("class", this.configuration.get(PROP_RUN_CLASS));
		targetRunST.add("applicationHome", this.applicationDir);
		for(Object key : this.configuration.keySet()){
			if(StringUtils.startsWith(key.toString(), PROP_JAVAPROP_START)){
				String[] kv = StringUtils.split(this.configuration.getProperty(key.toString()), ":");
				HashMap<String, String> javaProperties = new HashMap<>();
				javaProperties.put("key", kv[0]);
				ST execHomeVar = this.stg.getInstanceOf("execHomeVar");
				javaProperties.put("val", StringUtils.replace(kv[1], "{APPLICATION_HOME}", execHomeVar.add("target", targetMap).render()));
				targetRunST.add("javaProperties", javaProperties);
			}
			if(PROP_JAVA_CP.equals(key.toString())){
				String[] cp = StringUtils.split(this.configuration.getProperty(key.toString()), " , ");
				ST classpath = this.stg.getInstanceOf("classpath");
				classpath.add("target", targetMap);
				for(String s : cp){
					classpath.add("classpath", StringUtils.replace(s, "/", targetFileSep));
				}
				targetRunST.add("classPath", classpath.render());
			}
		}
		this.writeFile(outFN, targetRunST);

		//build all scripts for classmap, if set
		if(this.classMap!=null){
			for(Object key : this.classMap.keySet()){
				outFN = this.outputDir + File.separator + this.classMap.get(key) + fnExtension;
				System.out.println(" --> generating script from class map - " + outFN);

				ST targetExecST = this.generateScript(key.toString(), targetMap);
				this.writeFile(outFN, targetExecST);
			}
		}

		//build scripts for all registered applications using the specified run class
		if(this.configuration.get(PROP_EXECS_AUTOGEN_REGISTERED)!=null && BooleanUtils.toBoolean(this.configuration.get(PROP_EXECS_AUTOGEN_REGISTERED).toString())){
			if(this.execClassMap!=null){
				for(String s : execClassMap.keySet()){
					outFN = this.outputDir + File.separator + s + fnExtension;
					System.out.println(" --> generating script from auto-gen-reg - " + outFN);
					ST targetExecST = this.generateScript(execClassMap.get(s).getName(), targetMap);
					this.writeFile(outFN, targetExecST);
				}
			}
		}

		//build a generic header that can be used outside this class for generating other scripts
		ST headerST = this.stg.getInstanceOf("header");
		headerST.add("target", targetMap);
		headerST.add("applicationHome", this.applicationDir);
		this.writeFile(this.outputDir + File.separator + "_header", headerST);

		return 0;
	}

	/**
	 * Generates a script.
	 * @param clazz the class for the script
	 * @param targetMap map for targets
	 * @return the final template for the script
	 */
	protected final ST generateScript(String clazz, HashMap<String, Boolean> targetMap){
		ST ret = this.stg.getInstanceOf("generateExec");
		ret.add("target", targetMap);
		ret.add("applicationHome", this.applicationDir);
		ret.add("runName", this.configuration.get(PROP_RUN_SCRIPT_NAME));
		ret.add("class", clazz);
		return ret;
	}

	/**
	 * Loads and tests the configuration from configuration properties file.
	 * The default configuration file name is "de/vandermeer/execs/configuration.properties".
	 * The file name can be overwritten using the "--property-file" CLI option.
	 * The method will also test for some configuration keys to exist and fail if they are not defined.
	 * @return 0 on success with configuration loaded, -1 on error with errors printed on standard error
	 */
	protected final int initConfiguration(){
		String propFile = this.optionPropFile.getValue();

		this.configuration = this.loadProperties(propFile);
		if(this.configuration==null){
			System.err.println(this.getAppName() + ": could not load configuration properties from file <" + propFile + ">, exiting");
			return -1;
		}

		if(this.configuration.get(PROP_RUN_SCRIPT_NAME)==null){
			System.err.println(this.getAppName() + ": configuration does not contain key <" + PROP_RUN_SCRIPT_NAME + ">, exiting");
			return -1;
		}
		if(this.configuration.get(PROP_RUN_CLASS)==null){
			System.err.println(this.getAppName() + ": configuration does not contain key <" + PROP_RUN_CLASS + ">, exiting");
			return -1;
		}
		if(this.configuration.get(PROP_JAVA_CP)==null){
			System.err.println(this.getAppName() + ": configuration does not contain key <" + PROP_JAVA_CP + ">, exiting");
			return -1;
		}

		System.out.println(this.getAppName() + ": using configuration: ");
		System.out.println("  - run script name: " + this.configuration.get(PROP_RUN_SCRIPT_NAME));
		System.out.println("  - run class      : " + this.configuration.get(PROP_RUN_CLASS));
		System.out.println("  - java cp        : " + this.configuration.get(PROP_JAVA_CP));
		System.out.println("  - auto-gen reg   : " + this.configuration.get(PROP_EXECS_AUTOGEN_REGISTERED));

		for(Object key : this.configuration.keySet()){
			if(StringUtils.startsWith(key.toString(), PROP_JAVAPROP_START)){
				System.out.println("  - java property  : " + key + " = " + this.configuration.getProperty(key.toString()));
			}
		}
		System.out.println();

		return 0;
	}

	/**
	 * Sets target for generation and initializes an STG object from an stg template file.
	 * The default template file name is "de/vandermeer/execs/executable-script.stg".
	 * This default can be overwritten using the property "stg.file" in the configuration properties file.
	 * The default and the property file name can be overwritten using the "--stg-file" CLI option.
	 * The set target (CLI option "--target") must be supported by the template file, otherwise this method will fail.
	 * @return 0 on success with configuration loaded, -1 on error with errors printed on standard error
	 */
	protected final int initTargetAndStg(){
		this.target = this.optionTarget.getValue();
		if(this.target==null){
			System.err.println(this.getAppName() + ": no target set");
			return -1;
		}

		String fileName = this.optionStgFile.getValue();
		try{
			this.stg = new STGroupFile(fileName);
		}
		catch(Exception e){
			System.err.println(this.getAppName() + ": cannot load stg file <" + fileName + ">, general exception\n--> " + e);
			return -1;
		}
		String[] availableTargets = null;
		try{
			availableTargets = StringUtils.split(this.stg.getInstanceOf("supportedTargets").render(), " , ");
		}
		catch(Exception e){
			System.err.println(this.getAppName() + ": stg file <" + fileName + "> does not contain <supportedTargets> function");
			return -1;
		}
		if(availableTargets.length==0){
			System.err.println(this.getAppName() + ": stg file <" + fileName + "> does not have a list of targets in <supportedTargets> function");
			return -1;
		}
		if(!ArrayUtils.contains(availableTargets, this.target)){
			System.err.println(this.getAppName() + ": target " + this.target + " not supported in stg file <" + fileName + ">");
			return -1;
		}

		System.out.println(this.getAppName() + ": generating scripts for target: " + this.target);
		System.out.println();
		return 0;
	}

	/**
	 * Initializes the application directory for the generator.
	 * There is no default set and no configuration property can be used.
	 * The application directory has to be set using the CLI option "--application-directory".
	 * Otherwise this method will fail.
	 * @return 0 on success with configuration loaded, -1 on error with errors printed on standard error
	 */
	protected final int initApplicationDir(){
		this.applicationDir = this.optionAppHome.getValue();
		if(this.applicationDir==null){
			System.err.println(this.getAppName() + ": no application directory set");
			return -1;
		}

		return 0;
	}

	/**
	 * Loads a classmap from a property file.
	 * A classmap maps class names to script names.
	 * A class name must be an implementation of the executable application interface {@link ExecS_Application}.
	 * No default map is used.
	 * This default can be overwritten using the property "execs.classmap" in the configuration properties file.
	 * The default and the property file name can be overwritten using the "--classmap-file" CLI option.
	 * @return 0 on success with configuration loaded, -1 on error with errors printed on standard error
	 */
	protected final int initClassmap(){
		this.optionClassMapFile.setPropertyValue(this.configuration);
		String fileName = this.optionClassMapFile.getValue();

		if(fileName==null){
			System.err.println(this.getAppName() + ": no classmap file name given");
			return -2;
		}

		this.classMap = this.loadProperties(fileName);
		if(this.classMap==null){
			System.err.println(this.getAppName() + ": could not load classmap, exiting");
			return -1;
		}

		System.out.println(this.getAppName() + ": generating scripts for:");
		for(Object key : this.classMap.keySet()){
			System.out.println("  - " + key + " --> " + this.classMap.getProperty(key.toString()));
		}
		System.out.println();

		return 0;
	}

	/**
	 * Tests and if necessary creates an output directory.
	 * The root path is the current directory as given by the system property "user.dir".
	 * The created output directory has the name of the specified target for the generator.
	 * The method fails if the output directory cannot be created or if it exists and is write protected.
	 * @return 0 on success with configuration loaded, -1 on error with errors printed on standard error
	 */
	protected final int initOutputDir(){
		String parent = System.getProperty("user.dir");
		String target = parent + File.separator + this.target;
		File targetDir = new File(target);
		File parentDir = targetDir.getParentFile();

		if(targetDir.exists()){
			//target dir exists, let's see if it is what we want it to be
			if(!targetDir.isDirectory()){
				System.err.println(this.getAppName() + ": target dir <" + target + "> exists but is not a directory, exiting");
				return -1;
			}
			if(!targetDir.canWrite()){
				System.err.println(this.getAppName() + ": target dir <" + target + "> exists but but cannot write into it, exiting");
				return -1;
			}
		}
		else{
			//target dir does not exist, let's see if we can create it the way we need
			if(!parentDir.isDirectory()){
				System.err.println(this.getAppName() + ": target dir parent <" + parent + "> exists but is not a directory, exiting");
				return -1;
			}
			if(!parentDir.canWrite()){
				System.err.println(this.getAppName() + ": target dir parent <" + parent + "> exists but but cannot write into it, exiting");
				return -1;
			}

			if(!targetDir.mkdir()){
				System.err.println(this.getAppName() + ": could not create target dir <" + target + ">, exiting");
				return -1;
			}
		}

		this.outputDir = target;
		return 0;
	}

	/**
	 * Loads properties from a file.
	 * @param filename property file loaded from class path or file system
	 * @return loaded properties, null if nothing could be loaded plus error messages to standard error if exceptions are caught
	 */
	protected final Properties loadProperties(String filename){
		Properties ret = new Properties();

		URL url = null;
		File f = new File(filename.toString());
		if(f.exists()){
			try{
				url = f.toURI().toURL();
			}
			catch(Exception ignore){}
		}
		else{
			ClassLoader loader = Thread.currentThread().getContextClassLoader();
			url = loader.getResource(filename);
			if(url==null){
				loader = Gen_RunScripts.class.getClassLoader();
				url = loader.getResource(filename);
			}
		}

		try{
			ret.load(url.openStream());
		}
		catch (IOException e){
			System.err.println(this.getAppName() + ": cannot load property file <" + filename + ">, IO exception\n--><" + e + ">");
		}
		catch (Exception e){
			System.err.println(this.getAppName() + ": cannot load property file <" + filename + ">, general exception\n--><" + e + ">");
		}
		return ret;
	}

	/**
	 * Writes an ST object to a file.
	 * @param fn file name
	 * @param st ST template
	 * @return 0 on success with configuration loaded, -1 on error with errors printed on standard error
	 */
	protected final int writeFile(String fn, ST st){
		try {
			FileWriter fs = new FileWriter(fn);
			BufferedWriter bw = new BufferedWriter(fs);
			bw.write(st.render());
			bw.close();
		}
		catch (IOException e) {
			e.printStackTrace();
			return -1;
		}
		return 0;
	}

	@Override
	public ExecS_CliParser getCli() {
		return this.cli;
	}

	@Override
	public String getAppDescription() {
		return "Generates run scripts for executable applications.";
	}

	@Override
	public String getAppName() {
		return APP_NAME;
	}

	@Override
	public String getAppDisplayName(){
		return APP_DISPLAY_NAME;
	}

	@Override
	public ApplicationOption<?>[] getAppOptions() {
		return new ApplicationOption[]{this.optionAppHome, this.optionClassMapFile, this.optionPropFile, this.optionStgFile, this.optionTarget};
	}

	@Override
	public String getAppVersion() {
		return APP_VERSION;
	}

	@Override
	public void appHelpScreen(){
		ExecS_Application.super.appHelpScreen();

		System.out.println("Property file keys:");
		System.out.println(" - " + PROP_RUN_CLASS + " - the class name for executing applications");
		System.out.println(" - " + PROP_RUN_SCRIPT_NAME + " - the script name for running the main application executor");
		System.out.println(" - " + PROP_JAVA_CP + " - JAVA classpath, comma separates list, {APPLICATION_HOME} will be added to all entries");
		System.out.println(" - " + PROP_JAVAPROP_START + " - start of a particular JAVA runtime property");
		System.out.println("      + any key with the start should have the form of key:value and will be translated to -Dkey:value");
		System.out.println("      + for example: 'java.property.encoding = file.encoding:UTF-8' will be translated to -Dfile.encoding:UTF-8");
		System.out.println("      + {APPLICATION_HOME} will be added to all entries");
		System.out.println(" - " + PROP_EXECS_CLASSMAP + " - class map file with mappings from class to executable name");
		System.out.println(" - " + PROP_EXECS_AUTOGEN_REGISTERED + " - flag to auto generate all registered applications");

		System.out.println();
	}

}


package org.intellimate.izou.sdk.frameworks.music.resources;

import org.intellimate.izou.events.EventModel;
import org.intellimate.izou.identification.Identification;
import org.intellimate.izou.sdk.frameworks.music.player.Progress;
import org.intellimate.izou.sdk.resource.Resource;

import java.util.HashMap;
import java.util.Optional;

/**
 * this resource is simply used hold the progress for events,can also be obtained via the resourceManager
 * @author LeanderK
 * @version 1.0
 */
public class ProgressResource extends Resource<HashMap<String, Long>> {
    public static final String ID = "izou.music.resource.progress";

    /**
     * creates a new Resource.
     * This method is thread-safe.
     *
     * @param provider   the Provider of the Resource
     */
    public ProgressResource(Identification provider) {
        super(ID, provider);
    }

    /**
     * creates a new Resource.
     * This method is thread-safe.
     *
     * @param provider   the Provider of the Resource
     * @param progress   the resource
     */
    public ProgressResource(Identification provider, Progress progress) {
        super(ID, provider, progress.export());
    }

    /**
     * creates a new Resource.
     * This method is thread-safe.
     *
     * @param provider   the Provider of the Resource
     * @param consumer   the ID of the Consumer
     */
    public ProgressResource(Identification provider, Identification consumer) {
        super(ID, provider, consumer);
    }

    /**
     * creates a new Resource.
     * This method is thread-safe.
     *
     * @param provider   the Provider of the Resource
     * @param progress   the resource
     * @param consumer   the ID of the Consumer
     */
    public ProgressResource(Identification provider, Progress progress, Identification consumer) {
        super(ID, provider, progress.export(), consumer);
    }

    /**
     * gets the first Progress if found in the EventModel
     * @param eventModel the EventModel
     * @return return the optional Progress
     */
    public static Optional<Progress> getProgress(EventModel eventModel) {
        if (eventModel.getListResourceContainer().containsResourcesFromSource(ID)) {
            return eventModel
                    .getListResourceContainer()
                    .provideResource(ID)
                    .stream()
                    .findAny()
                    .flatMap(Progress::importResource);

        } else {
            return Optional.empty();
        }
    }
}


/*
 * Copyright 2010 Workplace Systems PLC (http://www.workplacesystems.com/).
 *
 * 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.workplacesystems.utilsj.collections.decorators;

import java.util.Collection;
import java.util.Iterator;
import com.workplacesystems.utilsj.Callback;
import com.workplacesystems.utilsj.collections.SyncUtils;

/**
 * SynchronizedCollection decorates another Collection
 * to synchronize its behaviour for a multi-threaded environment.
 *

 * Iterators must be manually synchronized:
 *

 * synchronized (coll) {
 *   Iterator it = coll.iterator();
 *   // do stuff with iterator
 * }
 *
 * @since Commons Collections 3.0
 * @version $Revision: 1.5 $ $Date: 2003/09/05 03:35:07 $
 *
 * @author Stephen Colebourne
 */
public class SynchronizedCollection<E> implements Collection<E>, SynchronizedDecorator {

    /** The collection to decorate */
    protected final Collection<E> collection;
    /** The object to lock on, needed for List/SortedSet views */
    protected final Object lock;

    /**
     * Factory method to create a synchronized collection.
     *
     * @param coll  the collection to decorate, must not be null
     * @throws IllegalArgumentException if collection is null
     */
    public static <E> Collection<E> decorate(Collection<E> coll) {
        return new SynchronizedCollection<E>(coll);
    }

    /**
     * Constructor that wraps (not copies).
     *
     * @param collection  the collection to decorate, must not be null
     * @throws IllegalArgumentException if the collection is null
     */
    protected SynchronizedCollection(Collection<E> collection) {
        if (collection == null) {
            throw new IllegalArgumentException("Collection must not be null");
        }
        this.collection = collection;
        this.lock = SyncUtils.createMutex(collection);
    }

    /**
     * Constructor that wraps (not copies).
     *
     * @param collection  the collection to decorate, must not be null
     * @param lock  the lock object to use, must not be null
     * @throws IllegalArgumentException if the collection is null
     */
    protected SynchronizedCollection(Collection<E> collection, Object lock) {
        if (collection == null) {
            throw new IllegalArgumentException("Collection must not be null");
        }
        this.collection = collection;
        this.lock = SyncUtils.createMutex(lock);
    }

    public Object getLockObject() {
        return lock;
    }

    //-----------------------------------------------------------------------
    public boolean add(final E object) {
        Boolean ret = SyncUtils.synchronizeWrite(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                _return(collection.add(object) ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    public boolean addAll(final Collection<? extends E> coll) {
        Boolean ret = SyncUtils.synchronizeWrite(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                _return(collection.addAll(coll) ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    public void clear() {
        SyncUtils.synchronizeWrite(lock, new Callback<Object>() {
            @Override
            protected void doAction() {
                collection.clear();
            }
        });
    }

    public boolean contains(final Object object) {
        Boolean ret = SyncUtils.synchronizeRead(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                _return(collection.contains(object) ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    public boolean containsAll(final Collection<?> coll) {
        Boolean ret = SyncUtils.synchronizeRead(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                _return(collection.containsAll(coll) ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    public boolean isEmpty() {
        Boolean ret = SyncUtils.synchronizeRead(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                _return(collection.isEmpty() ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    /**
     * Iterators must be manually synchronized.
     *

     * synchronized (coll) {
     *   Iterator it = coll.iterator();
     *   // do stuff with iterator
     * }
     *
     * @return an iterator that must be manually synchronized on the collection
     */
    public Iterator<E> iterator() {
        return collection.iterator();
    }

    public Object[] toArray() {
        return SyncUtils.synchronizeRead(lock, new Callback<Object[]>() {
            @Override
            protected void doAction() {
                _return(collection.toArray());
            }
        });
    }

    public <T> T[] toArray(final T[] object) {
        return SyncUtils.synchronizeRead(lock, new Callback<T[]>() {
            @Override
            protected void doAction() {
                _return(collection.toArray(object));
            }
        });
    }

    public boolean remove(final Object object) {
        Boolean ret = SyncUtils.synchronizeWrite(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                _return(collection.remove(object) ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    public boolean removeAll(final Collection<?> coll) {
        Boolean ret = SyncUtils.synchronizeWrite(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                _return(collection.removeAll(coll) ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    public boolean retainAll(final Collection<?> coll) {
        Boolean ret = SyncUtils.synchronizeWrite(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                _return(collection.retainAll(coll) ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    public int size() {
        Integer ret = SyncUtils.synchronizeRead(lock, new Callback<Integer>() {
            @Override
            protected void doAction() {
                _return(new Integer(collection.size()));
            }
        });
        return ret.intValue();
    }

    @Override
    public boolean equals(final Object object) {
        Boolean ret = SyncUtils.synchronizeRead(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                if (object == SynchronizedCollection.this) {
                    _return(Boolean.TRUE);
                }
                _return(collection.equals(object) ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    @Override
    public int hashCode() {
        Integer ret = SyncUtils.synchronizeRead(lock, new Callback<Integer>() {
            @Override
            protected void doAction() {
                _return(new Integer(collection.hashCode()));
            }
        });
        return ret.intValue();
    }

    @Override
    public String toString() {
        return SyncUtils.synchronizeRead(lock, new Callback<String>() {
            @Override
            protected void doAction() {
                _return(collection.toString());
            }
        });
    }
}


package org.firepick;

import java.net.*;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * FireREST service locator discovers FireREST services on local network
 */
public class ServiceResolver {
  static Logger logger = LoggerFactory.getLogger(ServiceResolver.class);
  private InetAddress address;
  private URL url;
  private int attempts;
  private JSONResult config;
  private int msTimeout = 500;

  private ServiceResolver(ServiceResolver resolver, int msTimeout) {
    this.url = resolver.url;
    this.address = resolver.address;
    this.msTimeout = msTimeout;
  }

  public ServiceResolver(URL url) {
    if (url == null) {
      throw new NullPointerException("url cannot be null");
    }
    this.url = url;
  }

  public ServiceResolver(InetAddress address) {
    if (address == null) {
      throw new NullPointerException("address cannot be null");
    }
    this.address = address;
  }

  public ServiceResolver withTimeout(int msTimeout) {
    return new ServiceResolver(this, msTimeout);
  }

  /**
   * Discover FireREST services located in a range of IPv4 InetAddresses.
   * E.g., The range of 256 addresses that starts with 10.0.1.128 ends with 10.0.2.127.
   *
   * @param start first InetAddress to scan (null for localhost)
   * @param count number of subsequent InetAddresses in the range
   * @param msTimeout maximum time to wait for host response
   * @return ServiceResolvers that resolve to a FireREST service.
   */
  public static Collection<ServiceResolver> discover(InetAddress start, int count, int msTimeout) {
    Collection<ServiceResolver> result = new ArrayList<ServiceResolver>();
    Collection<InetAddress> hosts = IPv4Scanner.scanRange(start, count, msTimeout);
    for (InetAddress host: hosts) {
      ServiceResolver resolver = new ServiceResolver(host);
      logger.info("resolving {} {}", host.getHostAddress(), host.getCanonicalHostName());
      JSONResult config = resolver.getConfig();
      if (config != null) {
         result.add(resolver);
      }
    }

    return result;
  }

  public URL getURL() {
    return url;
  }

  /**
   * (Re-)discover the service configuration
   *
   * @return true if config.json was successfully obtained
   */
  public void resolve() {
    attempts++;
    config = null;
    Exception caughtException = null;
    String host = address == null ? "null" : address.getCanonicalHostName();
    if (url == null) {
      URL attemptUrl = null;
      int [] ports = {8080, 80};
      for (int i=0; i < ports.length; i++) {
	try {
	  attemptUrl = new URL("http", host, ports[i], "/firerest/config.json");
	  break;
	} catch (Exception e) {
	  attemptUrl = null;
	  caughtException = e;
	}
      }
      if (attemptUrl == null) {
	throw new FireRESTException("Could not resolve service at " + host, caughtException);
      }
      url = attemptUrl;
    }

    if (config == null) {
      logger.info("Resolving {}", url);
      config = new FireREST().withTimeout(msTimeout).getJSON(url);
    }
  }

  /**
   * Return number of attempts to resolve() service.
   */
  public int getAttempts() {
    return attempts;
  }

  public InetAddress getAddress() {
    return address;
  }

  /**
   * Return the cached service configuration, resolving the service if required.
   *
   * @return service configuration or null if service could not be resolved.
   */
  public JSONResult getConfig() {
    if (attempts == 0) {
      try {
	resolve();
      } catch(Exception e) {
        // discard exception
      }
    }
    if (config == null) {
      logger.info("{} => no response", url);
      return null;
    }

    logger.info("{} => {}", url, config.get("FireREST").getString());
    return config;
  }

}


package org.sakaiproject.nakamura.lite.storage.jdbc;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.sakaiproject.nakamura.api.lite.CacheHolder;
import org.sakaiproject.nakamura.api.lite.RemoveProperty;
import org.sakaiproject.nakamura.api.lite.StorageClientException;
import org.sakaiproject.nakamura.api.lite.StorageClientUtils;
import org.sakaiproject.nakamura.api.lite.StorageConstants;
import org.sakaiproject.nakamura.api.lite.content.Content;
import org.sakaiproject.nakamura.lite.storage.spi.CachableDisposableIterator;
import org.sakaiproject.nakamura.lite.storage.spi.DirectCacheAccess;
import org.sakaiproject.nakamura.lite.storage.spi.DisposableIterator;
import org.sakaiproject.nakamura.lite.storage.spi.monitor.StatsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class WideColumnIndexer extends AbstractIndexer implements CachingIndexer {

    private static final String Q_INDEX_UPDATE = "index_update";
    private static final String Q_INDEX_DELETE = "index_delete";
    private static final String Q_INDEX_INSERT = "index_insert";
    private static final String Q_INDEX_REMOVE = "index_remove";
    private static final String SQL_INSERT_WIDESTRING_ROW = "insert-widestring-row";
    private static final String SQL_UPDATE_WIDESTRING_ROW = "update-widestring-row";
    private static final String SQL_DELETE_WIDESTRING_ROW = "delete-widestring-row";
    private static final String SQL_EXISTS_WIDESTRING_ROW = "exists-widestring-row";
    private static final int SQL_QUERY_TEMPLATE_PART = 0;
    private static final int SQL_WHERE_PART = 1;
    private static final int SQL_WHERE_ARRAY_PART = 2;
    private static final int SQL_WHERE_ARRAY_WHERE_PART = 3;
    private static final int SQL_SORT_CLAUSE_PART = 4;
    private static final int SQL_SORT_LIST_PART = 5;

    private static final Logger LOGGER = LoggerFactory.getLogger(WideColumnIndexer.class);
    /**
     * Values to exclude from the query cache key.
     */
    private static final Set<String> EXCLUDE_CACHE_KEYS = ImmutableSet.of(StorageConstants.CACHEABLE);

    /**
     * Set of keys in query properties that cant be cached, normally because
     * invalidation is too hard.
     */
    private static final Set<String> DONT_CACHE_KEYS = ImmutableSet.of(StorageConstants.ITEMS, StorageConstants.PAGE,
            StorageConstants.SORT);
    private JDBCStorageClient client;
    private Map<String, String> indexColumnsNames;
    private Map<String, String> indexColumnsTypes;
    private int gets;

    public WideColumnIndexer(JDBCStorageClient jdbcStorageClient, Map<String, String> indexColumnsNames,
            Set<String> indexColumnTypes, Map<String, Object> sqlConfig, StatsService statsService) {
        super(indexColumnsNames.keySet());
        this.client = jdbcStorageClient;
        this.indexColumnsNames = indexColumnsNames;
        Builder<String, String> b = ImmutableMap.builder();
        for (String k : Sets.union(indexColumnTypes, JDBCStorageClient.AUTO_INDEX_COLUMNS_TYPES)) {
            String[] type = StringUtils.split(k, "=", 2);
            b.put(type[0], type[1]);
        }
        this.indexColumnsTypes = b.build();
    }

    public void index(Map<String, PreparedStatement> statementCache, String keySpace, String columnFamily, String key, String rid,
            Map<String, Object> values) throws StorageClientException, SQLException {
        ResultSet rs = null;

        try {
            Set<String> removeArrayColumns = Sets.newHashSet();
            Set<String> removeColumns = Sets.newHashSet();
            Map<String, Object[]> updateArrayColumns = Maps.newHashMap();
            Map<String, Object> updateColumns = Maps.newHashMap();
            for (Entry<String, Object> e : values.entrySet()) {
                String k = e.getKey();
                Object o = e.getValue();
                Object[] valueMembers = (o instanceof Object[]) ? (Object[]) o : new Object[] { o };
                if (shouldIndex(keySpace, columnFamily, k)) {
                    if (isColumnArray(keySpace, columnFamily, k)) {
                        if (o instanceof RemoveProperty || o == null || valueMembers.length == 0) {
                            removeArrayColumns.add(k);
                        } else {
                            removeArrayColumns.add(k);
                            updateArrayColumns.put(k, valueMembers);
                        }
                    } else {
                        if (o instanceof RemoveProperty || o == null || valueMembers.length == 0) {
                            removeColumns.add(k);
                        } else {
                            updateColumns.put(k, valueMembers[0]);
                        }

                    }
                }
            }

            if (!StorageClientUtils.isRoot(key) && getColumnName(keySpace, columnFamily, Content.PARENT_HASH_FIELD) != null) {
                String parent = StorageClientUtils.getParentObjectPath(key);
                String hash = client.rowHash(keySpace, columnFamily, parent);
                LOGGER.debug("Hash of {}:{}:{} is {} ", new Object[] { keySpace, columnFamily, parent, hash });
                updateColumns.put(Content.PARENT_HASH_FIELD, hash);
                invalidate(keySpace, columnFamily, ImmutableMap.of(Content.PARENT_HASH_FIELD, (Object) hash,
                        StorageConstants.CUSTOM_STATEMENT_SET, "listchildren", StorageConstants.CACHEABLE, true));
            }

            LOGGER.debug("Removing Array {} ", removeArrayColumns);
            LOGGER.debug("Updating Array {} ", updateArrayColumns);
            LOGGER.debug("Removing  {} ", removeColumns);
            LOGGER.debug("Updating  {} ", updateColumns);

            // arrays are stored in css, so we can re-use css sql.
            PreparedStatement removeStringColumn = client.getStatement(keySpace, columnFamily,
                    JDBCStorageClient.SQL_REMOVE_STRING_COLUMN, rid, statementCache);
            int nbatch = 0;
            for (String column : removeArrayColumns) {
                removeStringColumn.clearWarnings();
                removeStringColumn.clearParameters();
                removeStringColumn.setString(1, rid);
                removeStringColumn.setString(2, column);
                removeStringColumn.addBatch();
                LOGGER.debug("Removing {} {} ", rid, column);
                nbatch++;
            }
            if (nbatch > 0) {
                long t = System.currentTimeMillis();
                removeStringColumn.executeBatch();
                client.checkSlow(columnFamily, Q_INDEX_REMOVE, t,
                        client.getSql(keySpace, columnFamily, JDBCStorageClient.SQL_REMOVE_STRING_COLUMN));
                nbatch = 0;
            }

            // add the column values in
            PreparedStatement insertStringColumn = client.getStatement(keySpace, columnFamily,
                    JDBCStorageClient.SQL_INSERT_STRING_COLUMN, rid, statementCache);
            for (Entry<String, Object[]> e : updateArrayColumns.entrySet()) {
                for (Object o : e.getValue()) {
                    insertStringColumn.clearWarnings();
                    insertStringColumn.clearParameters();
                    insertStringColumn.setString(1, o.toString());
                    insertStringColumn.setString(2, rid);
                    insertStringColumn.setString(3, e.getKey());
                    insertStringColumn.addBatch();
                    LOGGER.debug("Inserting {} {} {} ", new Object[] { o.toString(), rid, e.getKey() });
                    nbatch++;
                }
            }
            if (nbatch > 0) {
                long t = System.currentTimeMillis();
                insertStringColumn.executeBatch();
                client.checkSlow(columnFamily, Q_INDEX_INSERT, t,
                        client.getSql(keySpace, columnFamily, JDBCStorageClient.SQL_INSERT_STRING_COLUMN));
                nbatch = 0;
            }
            if (removeColumns.size() == 0 && updateColumns.size() == 0) {
                return; // nothing to add or remove, do nothing.
            }

            if (removeColumns.size() > 0 && updateColumns.size() == 0) {
                // exists, columns to remove, none to update, therefore
                // delete row this assumes that the starting point is a
                // complete map
                PreparedStatement deleteWideStringColumn = client.getStatement(keySpace, columnFamily, SQL_DELETE_WIDESTRING_ROW,
                        rid, statementCache);
                deleteWideStringColumn.clearParameters();
                deleteWideStringColumn.setString(1, rid);
                long t = System.currentTimeMillis();
                deleteWideStringColumn.execute();
                client.checkSlow(columnFamily, Q_INDEX_DELETE, t, client.getSql(keySpace, columnFamily, SQL_DELETE_WIDESTRING_ROW));
                LOGGER.debug("Executed {} with {} ", deleteWideStringColumn, rid);
            } else if (updateColumns.size() > 0 || removeColumns.size() > 0) {
                //
                // build an update query, record does not exists, but there
                // is stuff to add
                String[] sqlParts = StringUtils.split(client.getSql(keySpace, columnFamily, SQL_UPDATE_WIDESTRING_ROW), ";");
                StringBuilder setOperations = new StringBuilder();
                for (Entry<String, Object> e : updateColumns.entrySet()) {
                    join(setOperations, " ,").append(
                            MessageFormat.format(sqlParts[1], getColumnName(keySpace, columnFamily, e.getKey())));
                }
                for (String toRemove : removeColumns) {
                    join(setOperations, " ,").append(
                            MessageFormat.format(sqlParts[1], getColumnName(keySpace, columnFamily, toRemove)));
                }
                String finalSql = MessageFormat.format(sqlParts[0], setOperations);
                LOGGER.debug("Performing {} ", finalSql);
                PreparedStatement updateColumnPst = client.getStatement(finalSql, statementCache);
                updateColumnPst.clearWarnings();
                updateColumnPst.clearParameters();
                int i = 1;
                for (Entry<String, Object> e : updateColumns.entrySet()) {
                    updateColumnPst.setString(i, e.getValue().toString());
                    LOGGER.debug("   Param {} {} ", i, e.getValue().toString());
                    i++;
                }
                for (String toRemove : removeColumns) {
                    updateColumnPst.setNull(i, toSqlType(columnFamily, toRemove));
                    LOGGER.debug("   Param {} NULL ", i);
                    i++;
                }
                LOGGER.debug("   Param {} {} ", i, rid);
                updateColumnPst.setString(i, rid);
                long t = System.currentTimeMillis();
                int n = updateColumnPst.executeUpdate();
                client.checkSlow(columnFamily, Q_INDEX_UPDATE, t, finalSql);
                if (n == 0) {
                    // part 0 is the final ,part 1 is the template for column
                    // names,
                    // part 2 is the template for parameters.
                    // insert into x ( columnsnames ) values ()
                    StringBuilder columnNames = new StringBuilder();
                    StringBuilder paramHolders = new StringBuilder();
                    for (Entry<String, Object> e : updateColumns.entrySet()) {
                        columnNames.append(" ,").append(getColumnName(keySpace, columnFamily, e.getKey()));
                        paramHolders.append(" ,").append("?");
                    }
                    finalSql = MessageFormat.format(client.getSql(keySpace, columnFamily, SQL_INSERT_WIDESTRING_ROW),
                            columnNames.toString(), paramHolders.toString());
                    LOGGER.debug("Insert SQL {} ", finalSql);
                    PreparedStatement insertColumnPst = client.getStatement(finalSql, statementCache);
                    insertColumnPst.clearWarnings();
                    insertColumnPst.clearParameters();
                    insertColumnPst.setString(1, rid);
                    LOGGER.debug("   Param 1 {} ", rid);
                    i = 2;
                    for (Entry<String, Object> e : updateColumns.entrySet()) {
                        LOGGER.debug("   Param {} {} ", i, e.getValue().toString());
                        insertColumnPst.setString(i, e.getValue().toString());
                        i++;
                    }
                    t = System.currentTimeMillis();
                    insertColumnPst.executeUpdate();
                    client.checkSlow(columnFamily, Q_INDEX_UPDATE, t, finalSql);
                }
            }
        } finally {
            if (rs != null) {
                rs.close();
            }
        }

    }

    private String getColumnName(String keySpace, String columnFamily, String key) {
        return indexColumnsNames.get(columnFamily + ":" + key);
    }

    private int toSqlType(String columnFamily, String k) {
        String type = indexColumnsTypes.get(columnFamily + ":" + k);
        if (type == null) {
            return Types.VARCHAR;
        } else if (type.startsWith("String")) {
            return Types.VARCHAR;
        } else if (type.startsWith("int")) {
            return Types.INTEGER;
        } else if (type.startsWith("Date")) {
            return Types.DATE;
        }
        return Types.VARCHAR;
    }

    private boolean isColumnArray(String keySpace, String columnFamily, String k) {
        String type = indexColumnsTypes.get(columnFamily + ":" + k);
        if (type != null && type.endsWith("[]")) {
            return true;
        }
        return false;
    }

    public DisposableIterator<Map<String, Object>> find(final String keySpace, final String columnFamily,
            Map<String, Object> properties, final DirectCacheAccess cachingManager) throws StorageClientException {

        final boolean rawResults = properties != null && properties.containsKey(StorageConstants.RAWRESULTS);

        DisposableIterator<Map<String, Object>> cachedIterator = cachedFind(keySpace, columnFamily, properties, rawResults,
                cachingManager);
        if (cachedIterator != null) {
            return cachedIterator;
        }

        String[] keys = null;
        if (properties != null && properties.containsKey(StorageConstants.CUSTOM_STATEMENT_SET)) {
            String customStatement = (String) properties.get(StorageConstants.CUSTOM_STATEMENT_SET);
            keys = new String[] { "wide-" + customStatement + "." + keySpace + "." + columnFamily,
                    "wide-" + customStatement + "." + columnFamily, "wide-" + customStatement,
                    "wide-block-find." + keySpace + "." + columnFamily, "wide-block-find." + columnFamily, "wide-block-find" };
        } else {
            keys = new String[] { "wide-block-find." + keySpace + "." + columnFamily, "wide-block-find." + columnFamily,
                    "wide-block-find" };
        }

        String sql = client.getSql(keys);
        if (sql == null) {
            throw new StorageClientException("Failed to locate SQL statement for any of  " + Arrays.toString(keys));
        }

        // collect information on paging
        long page = 0;
        long items = 25;
        String sortProp = null;
        if (properties != null) {
            if (properties.containsKey(StorageConstants.PAGE)) {
                page = Long.valueOf(String.valueOf(properties.get(StorageConstants.PAGE)));
            }
            if (properties.containsKey(StorageConstants.ITEMS)) {
                items = Long.valueOf(String.valueOf(properties.get(StorageConstants.ITEMS)));
            }
            sortProp = (String) properties.get(StorageConstants.SORT);
        }
        long offset = page * items;

        // collect information on sorting
        List<String> sortingList = Lists.newArrayList();
        if (sortProp != null) {
            String[] sorts = StringUtils.split(sortProp);
            if (sorts.length == 1) {
                if (shouldIndex(keySpace, columnFamily, sorts[0]) && !isColumnArray(keySpace, columnFamily, sorts[0])) {
                    sortingList.add(getColumnName(keySpace, columnFamily, sorts[0]));
                    sortingList.add("asc");
                }
            } else if (sorts.length > 1) {
                for (int i = 0; i < sorts.length; i += 2) {
                    if (shouldIndex(keySpace, columnFamily, sorts[0]) && !isColumnArray(keySpace, columnFamily, sorts[i])) {
                        sortingList.add(getColumnName(keySpace, columnFamily, sorts[0]));
                        sortingList.add(sorts[i + 1]);
                    }
                }
            }
        }
        String[] sorts = sortingList.toArray(new String[sortingList.size()]);
        String[] statementParts = StringUtils.split(sql, ';');
        /*
         * Part 0 basic SQL template; {0} is the where clause {1} is the sort
         * clause {2} is the from {3} is the to record eg select rid from css
         * where {0} {1} LIMIT {2} ROWS {3} Part 1 where clause for non array
         * matches; {0} is the columnName eg {0} = ? Part 2 where clause for
         * array matches (not possible to sort on array matches) {0} is the
         * table alias, {1} is the where clause eg rid in ( select {0}.rid from
         * css {0} where {1} ) Part 3 the where clause for array matches {0} is
         * the table alias eg {0}.cid = ? and {0}.v = ? Part 3 sort clause {0}
         * is the list to sort by eg sort by {0} Part 4 sort elements, {0} is
         * the column, {1} is the order eg {0} {1} Dont include , AND or OR, the
         * code will add those as appropriate.
         */

        StringBuilder whereClause = new StringBuilder();
        List<Object> parameters = Lists.newArrayList();
        int set = 0;
        for (Entry<String, Object> e : properties.entrySet()) {
            Object v = e.getValue();
            String k = e.getKey();
            if (shouldFind(keySpace, columnFamily, k) || (v instanceof Map)) {
                if (v != null) {
                    // check for a value map and treat sub terms as for OR
                    // terms.
                    // Only go 1 level deep; don't recurse. That's just silly.
                    if (v instanceof Map) {
                        // start the OR grouping
                        @SuppressWarnings("unchecked")
                        Set<Entry<String, Object>> subterms = ((Map<String, Object>) v).entrySet();
                        StringBuilder subQuery = new StringBuilder();
                        for (Iterator<Entry<String, Object>> subtermsIter = subterms.iterator(); subtermsIter.hasNext();) {
                            Entry<String, Object> subterm = subtermsIter.next();
                            String subk = subterm.getKey();
                            Object subv = subterm.getValue();
                            // check that each subterm should be indexed
                            if (shouldFind(keySpace, columnFamily, subk)) {
                                set = processEntry(statementParts, keySpace, columnFamily, subQuery, parameters, subk, subv, sorts,
                                        set, " OR ");
                            }
                        }
                        if (subQuery.length() > 0) {
                            join(whereClause, " AND ").append("( ").append(subQuery.toString()).append(" ) ");
                        }
                    } else {
                        // process a first level non-map value as an AND term

                        if (v instanceof Iterable<?>) {
                            for (Object vo : (Iterable<?>) v) {
                                set = processEntry(statementParts, keySpace, columnFamily, whereClause, parameters, k, vo, sorts,
                                        set, " AND ");
                            }
                        } else {
                            set = processEntry(statementParts, keySpace, columnFamily, whereClause, parameters, k, v, sorts, set,
                                    " AND ");
                        }
                    }
                } else if (!k.startsWith("_")) {
                    LOGGER.debug("Search on {}:{} filter dropped due to null value.", columnFamily, k);
                }
            } else {
                if (!k.startsWith("_")) {
                    LOGGER.warn("Search on {}:{} is not supported, filter dropped ", columnFamily, k);
                }
            }
        }
        // there was no where clause generated
        // to avoid returneing everything, we wont return anything.
        if (whereClause.length() == 0) {
            return cacheResults(keySpace, columnFamily, properties, new PreemptiveCachedMapIterator(client, keySpace, columnFamily,
                    ImmutableMap.of("rows", (Object) ImmutableList.of()), rawResults, cachingManager));
        }

        StringBuilder sortClause = new StringBuilder();
        if (statementParts.length > SQL_SORT_CLAUSE_PART) {
            StringBuilder sortList = new StringBuilder();
            for (int i = 0; i < sorts.length; i += 2) {
                if (shouldFind(keySpace, columnFamily, sorts[0])) {
                    join(sortList, ", ").append(MessageFormat.format(statementParts[SQL_SORT_LIST_PART], sorts[i], sorts[i + 1]));
                }
            }
            if (sortList.length() > 0) {
                sortClause.append(MessageFormat.format(statementParts[SQL_SORT_CLAUSE_PART], sortList.toString()));
            }
        }

        final String sqlStatement = MessageFormat.format(statementParts[SQL_QUERY_TEMPLATE_PART], whereClause.toString(),
                sortClause.toString(), items, offset);

        PreparedStatement tpst = null;
        ResultSet trs = null;
        try {

            LOGGER.debug("Preparing {} ", sqlStatement);
            tpst = client.getConnection().prepareStatement(sqlStatement);
            client.inc("iterator");
            tpst.clearParameters();
            int i = 1;
            for (Object params : parameters) {
                tpst.setObject(i, params);
                LOGGER.debug("Setting {} ", params);
                i++;
            }

            long qtime = System.currentTimeMillis();
            trs = tpst.executeQuery();
            client.checkSlow(columnFamily, "index_find", System.currentTimeMillis() - qtime, sqlStatement);
            client.inc("iterator r");
            LOGGER.debug("Executed ");

            // pass control to the iterator.
            PreparedStatement pst = tpst;
            ResultSet rs = trs;
            tpst = null;
            trs = null;
            return client.registerDisposable(cacheResults(keySpace, columnFamily, properties, new PreemptiveCachedMapIterator(
                    client, keySpace, columnFamily, rs, pst, rawResults, cachingManager)));
        } catch (SQLException e) {
            LOGGER.error(e.getMessage(), e);
            client.resetConnection(null);
            throw new StorageClientException(e.getMessage() + " SQL Statement was " + sqlStatement, e);
        } finally {
            // trs and tpst will only be non null if control has not been passed
            // to the iterator.
            try {
                if (trs != null) {
                    trs.close();
                    client.dec("iterator r");
                }
            } catch (SQLException e) {
                LOGGER.warn(e.getMessage(), e);
            }
            try {
                if (tpst != null) {
                    tpst.close();
                    client.dec("iterator");
                }
            } catch (SQLException e) {
                LOGGER.warn(e.getMessage(), e);
            }
        }
    }

    private StringBuilder join(StringBuilder sb, String joinWord) {
        if (sb.length() > 0) {
            sb.append(joinWord);
        }
        return sb;
    }

    /**
     * @param statementParts
     * @param where
     * @param params
     * @param k
     * @param v
     * @param t
     * @param conjunctionOr
     */
    private int processEntry(String[] statementParts, String keySpace, String columnFamily, StringBuilder subQuery,
            List<Object> params, String key, Object value, String[] sorts, int tableIndex, String logicalJoin) {
        if (isColumnArray(keySpace, columnFamily, key)) {
            String tableName = "a" + tableIndex;
            tableIndex++;
            if (value instanceof Iterable<?>) {
                StringBuilder arraySubQuery = new StringBuilder();
                // SQL_WHERE_ARRAY_WHERE_PART is ( {0}cid = ? AND {0}v = ? )
                for (Iterator<?> valueIterator = ((Iterable<?>) value).iterator(); valueIterator.hasNext();) {
                    Object o = valueIterator.next();
                    params.add(key);
                    params.add(o);
                    join(arraySubQuery, " OR ").append(MessageFormat.format(statementParts[SQL_WHERE_ARRAY_WHERE_PART], tableName));
                }
                // SQL_WHERE_ARRAY_PART is rid in (select rid from css {0} where
                // {1} )
                if (arraySubQuery.length() > 0) {
                    join(subQuery, logicalJoin).append(
                            MessageFormat.format(statementParts[SQL_WHERE_ARRAY_PART], tableName, arraySubQuery));
                }
            } else {
                params.add(key);
                params.add(value);
                String whereClause = MessageFormat.format(statementParts[SQL_WHERE_ARRAY_WHERE_PART], tableName);
                join(subQuery, logicalJoin).append(
                        MessageFormat.format(statementParts[SQL_WHERE_ARRAY_PART], tableName, whereClause));
            }
        } else {
            String column = getColumnName(keySpace, columnFamily, key);
            if (value instanceof Iterable<?>) {
                StringBuilder arraySubQuery = new StringBuilder();
                // SQL_WHERE_PART is {0} = ?
                for (Iterator<?> valueIterator = ((Iterable<?>) value).iterator(); valueIterator.hasNext();) {
                    Object o = valueIterator.next();
                    params.add(o);
                    join(arraySubQuery, " OR ").append(MessageFormat.format(statementParts[SQL_WHERE_PART], column));
                }
                if (arraySubQuery.length() > 0) {
                    join(subQuery, logicalJoin).append(" ( ").append(arraySubQuery.toString()).append(" ) ");
                }
            } else {
                params.add(value);
                LOGGER.debug("Adding {} {} ", statementParts[SQL_WHERE_PART], column);
                join(subQuery, logicalJoin).append(MessageFormat.format(statementParts[SQL_WHERE_PART], column));
            }

        }
        return tableIndex;

    }

    // Query Caching
    // -----------------------------------------------------------------------------------

    @Override
    public void invalidate(String keySpace, String columnFamily, Map<String, Object> queryProperties) {
        Map<String, CacheHolder> queryCache = client.getQueryCache();
        if (queryCache == null) {
            return;
        }
        String cacheKey = getCacheKey(keySpace, columnFamily, queryProperties);
        if (cacheKey != null) {
            LOGGER.debug("Removing Cached Query {} ",cacheKey);
            queryCache.remove(cacheKey);
        }
    }

    private String getCacheKey(String keySpace, String columnFamily, Map<String, Object> queryProperties) {
        if (Boolean.parseBoolean(String.valueOf(queryProperties.get(StorageConstants.CACHEABLE)))) {
            List<String> keys = Lists.newArrayList(queryProperties.keySet());
            Collections.sort(keys);
            StringBuilder sb = new StringBuilder();
            sb.append(keySpace).append(";").append(columnFamily);
            boolean hasKey = false;
            for (String key : keys) {
                if (DONT_CACHE_KEYS.contains(key)) {
                    hasKey = false;
                    LOGGER.debug("Query cant be cached becuase it contains {} ", key);
                    break;
                }
                if (!EXCLUDE_CACHE_KEYS.contains(key)) {
                    sb.append(key).append(":").append(queryProperties.get(key)).append(";");
                    hasKey = true;
                }
            }
            if (hasKey) {
                // we might want to hash this to prevent the key getting
                // massive.
                LOGGER.debug("Cached Query Key is {} ", sb.toString());
                return sb.toString();
            }
        }
        return null;
    }

    private DisposableIterator<Map<String, Object>> cachedFind(String keySpace, String columnFamily,
            Map<String, Object> queryProperties, boolean rawResults, DirectCacheAccess cachingManager) {
        Map<String, CacheHolder> queryCache = client.getQueryCache();
        if (queryCache == null) {
            gets++;
            if (gets % 1000 == 0) {
                LOGGER.info("No Query Cache in use, please provide one.");
            }
            return null;
        }
        String cacheKey = getCacheKey(keySpace, columnFamily, queryProperties);
        if (cacheKey != null) {
            CacheHolder ch = queryCache.get(cacheKey);
            if (ch != null) {
                Map<String, Object> m = ch.get();
                if (m != null) {
                    LOGGER.debug("Got Cached Query at {} ",cacheKey);
                    return new PreemptiveCachedMapIterator(client, keySpace, columnFamily, m, rawResults, cachingManager);
                }
            }
        }
        LOGGER.debug("No Cached Query at {} ",cacheKey);
        return null;
    }

    private DisposableIterator<Map<String, Object>> cacheResults(String keySpace, String columnFamily,
            Map<String, Object> queryProperties, CachableDisposableIterator<Map<String, Object>> disposableIterator) {
        Map<String, CacheHolder> queryCache = client.getQueryCache();
        if (queryCache == null) {
            return disposableIterator;
        }
        String cacheKey = getCacheKey(keySpace, columnFamily, queryProperties);
        if (cacheKey != null) {
            Map<String, Object> m = disposableIterator.getResultsMap();
            if (m != null) {
                LOGGER.debug("Saved Cached Results at {} ",cacheKey);
                queryCache.put(cacheKey, new CacheHolder(m));
            }
        }
        return disposableIterator;
    }

}


/**
 *
 * Copyright 2003-2007 Jive Software.
 *
 * All rights reserved. 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 org.jamppa.client.plugin.xep0077;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.dom4j.Element;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.xmpp.packet.IQ;

/**
 * Represents registration packets. An empty GET query will cause the server to
 * return information about it's registration support. SET queries can be used
 * to create accounts or update existing account information. XMPP servers may
 * require a number of attributes to be set when creating a new account. The
 * standard account attributes are as follows:
 *

 *
name -- the user's name.
 *
first -- the user's first name.
 *
last -- the user's last name.
 *
email -- the user's email address.
 *
city -- the user's city.
 *
state -- the user's state.
 *
zip -- the user's ZIP code.
 *
phone -- the user's phone number.
 *
url -- the user's website.
 *
date -- the date the registration took place.
 *
misc -- other miscellaneous information to associate with the account.
 *
text -- textual information to associate with the account.
 *
remove -- empty flag to remove account.
 *

 *
 * @author Matt Tucker
 */
public class Registration extends IQ {

    private Element queryEl;

    public Registration() {
        super();
        queryEl = element.addElement("query", "jabber:iq:register");
    }

    public Registration(Element el) {
        super(el);
        queryEl = element.element("query");
    }

    /**
     * Returns the registration instructions, or <tt>null</tt> if no
     * instructions have been set. If present, instructions should be displayed
     * to the end-user that will complete the registration process.
     *
     * @return the registration instructions, or <tt>null</tt> if there are
     *         none.
     */
    public String getInstructions() {
        return queryEl.elementText("instructions");
    }

    /**
     * Sets the registration instructions.
     *
     * @param instructions
     *            the registration instructions.
     */
    public void setInstructions(String instructions) {
        PacketParserUtils.updateText(queryEl, "instructions", instructions);
    }

    /**
     * Returns the map of String key/value pairs of account attributes.
     *
     * @return the account attributes.
     */
    @SuppressWarnings("unchecked")
    public Map<String, String> getAttributes() {
        Map<String, String> attributes = null;
        List<Element> elements = queryEl.elements();
        for (Element element : elements) {
            if (!element.getName().equals("instructions")) {
                if (attributes == null) {
                    attributes = new HashMap<String, String>();
                }
                attributes.put(element.getName(), element.getText());
            }
        }
        return attributes;
    }

    /**
     * Sets the account attributes. The map must only contain String key/value
     * pairs.
     *
     * @param attributes
     *            the account attributes.
     */
    public void setAttributes(Map<String, String> attributes) {
        for (Entry<String, String> attribute : attributes.entrySet()) {
            PacketParserUtils.updateText(queryEl, attribute.getKey(),
                    attribute.getValue());
        }
    }
}


package org.intellimate.izou.sdk;

import org.apache.logging.log4j.spi.ExtendedLogger;
import org.intellimate.izou.identification.Identifiable;
import org.intellimate.izou.identification.Identification;
import org.intellimate.izou.identification.IllegalIDException;
import org.intellimate.izou.sdk.contentgenerator.EventListener;
import org.intellimate.izou.sdk.properties.PropertiesAssistant;
import org.intellimate.izou.sdk.specification.ContentGeneratorModel;
import org.intellimate.izou.sdk.specification.context.ContentGenerators;
import org.intellimate.izou.system.context.*;
import org.intellimate.izou.system.context.System;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;

/**
 * The Izou context is a means for all addOns to get general information they might need. Every addOn its own context
 * and can use it to reach certain Izou components. It controls what an addOn has access to and what it does not have
 * access to.
 * <br>
 * For instance, the addOn should have access to a logger (created in Izou for the addOn), but it should not have
 * access to classes like the AddOnManager, which loads all addOns at the start. Hence the logger is included in the
 * context, but the addOn manager is not. Anything that is not included in the context, and addOn does not have access to.
 * So in short, the context exists to give addOns access to higher Izou components while still denying access to other
 * components.
 */
public class Context implements org.intellimate.izou.system.Context {
    private final PropertiesAssistant propertiesAssistant;
    private final org.intellimate.izou.system.Context context;
    private final org.intellimate.izou.sdk.specification.context.ThreadPool threadPool;
    private final ContentGenerators contentGenerators;

    /**
     * Creates a context for the addOn.
     * @param context the context to use
     */
    public Context(org.intellimate.izou.system.Context context) {
        this.context = context;
        threadPool = new ThreadPoolImpl();
        contentGenerators = new ContentGeneratorsImpl();
        propertiesAssistant = new PropertiesAssistant(this, getAddOns().getAddOn().getID());
    }

    /**
     * Gets the API used for the Properties
     *
     * @return Properties
     */
    public PropertiesAssistant getPropertiesAssistant() {
        return propertiesAssistant;
    }

    /**
     * returns the API used for interaction with Events
     *
     * @return Events
     */
    @Override
    public Events getEvents() {
        return context.getEvents();
    }

    /**
     * returns the API used for interaction with Resource
     *
     * @return Resource
     */
    @Override
    public Resources getResources() {
        return context.getResources();
    }

    /**
     * returns the API used for interaction with Files
     *
     * @return Files
     */
    @Override
    public Files getFiles() {
        return context.getFiles();
    }

    /**
     * returns the API used to log
     * @return Logger
     */
    @Override
    public ExtendedLogger getLogger() {
        return context.getLogger();
    }

    /**
     * returns the API used to manage the ThreadPool
     *
     * @return ThreadPool
     */
    @Override
    public org.intellimate.izou.sdk.specification.context.ThreadPool getThreadPool() {
        return threadPool;
    }

    /**
     * returns the API to manage the Activators
     *
     * @return Activator
     */
    @Override
    public Activators getActivators() {
        return context.getActivators();
    }

    /**
     * returns the API used to manage the OutputPlugins and OutputExtensions
     *
     * @return Output
     */
    @Override
    public Output getOutput() {
        return context.getOutput();
    }

    @Override
    public System getSystem() {
        return context.getSystem();
    }

    /**
     * Returns the API used to manage the ContentGenerators.
     *
     * @return ContentGenerator
     */
    public ContentGenerators getContentGenerators() {
        return contentGenerators;
    }

    /**
     * Returns the API used to manage the addOns.
     *
     * @return The API used to manage the addOns.
     */
    @Override
    public AddOns getAddOns() {
        return context.getAddOns();
    }

    private class ContentGeneratorsImpl implements ContentGenerators {

        /**
         * Register an ContentGenerator
         *
         * @param contentGenerator the contentGenerator to register
         * @throws IllegalIDException not implemented yet
         */
        @Override
        public void registerContentGenerator(ContentGeneratorModel contentGenerator) throws IllegalIDException {
            List<? extends EventListener> triggeredEvents = contentGenerator.getTriggeredEvents();
            if (triggeredEvents != null) {
                triggeredEvents.stream()
                        .filter(Objects::nonNull)
                        .forEach(propertiesAssistant.getEventPropertiesAssistant()::registerEventListener);
            }
            getResources().registerResourceBuilder(contentGenerator);
        }

        /**
         * Unregisters an ContentGenerator
         *
         * @param contentGenerator the ContentGenerator to unregister
         */
        @Override
        public void unregisterContentGenerator(ContentGeneratorModel contentGenerator) {
            List<? extends EventListener> triggeredEvents = contentGenerator.getTriggeredEvents();
            for (EventListener eventListener : triggeredEvents) {
                propertiesAssistant.getEventPropertiesAssistant().unregisterEventID(eventListener.getDescriptorID());
            }
            getResources().unregisterResourceBuilder(contentGenerator);
        }
    }

    private class ThreadPoolImpl implements org.intellimate.izou.sdk.specification.context.ThreadPool {
        private ExecutorService executorService = null;

        /**
         * returns a ThreadPool associated with the AddOn
         *
         * @return an instance of ExecutorService or Null if there is a problem with the Identifier
         */
        @Override
        public ExecutorService getThreadPool() {
            if (executorService != null) {
                return executorService;
            } else {
                try {
                    executorService = getThreadPool(getAddOns().getAddOn());
                } catch (IllegalIDException e) {
                    getLogger().error("Unable to obtain ExecutorService", e);
                    return null;
                }
                return executorService;
            }
        }

        /**
         * returns a NEW ThreadPool where all the IzouPlugins are running
         *
         * @param identifiable the Identifiable to set each created Task as the Source
         * @return an instance of ExecutorService
         * @throws org.intellimate.izou.identification.IllegalIDException not implemented yet
         */
        @Override
        public ExecutorService getThreadPool(Identifiable identifiable) throws IllegalIDException {
            return context.getThreadPool().getThreadPool(identifiable);
        }

        /**
         * tries everything to log the exception
         *
         * @param throwable the Throwable
         * @param target    an instance of the thing which has thrown the Exception
         */
        @Override
        public void handleThrowable(Throwable throwable, Object target) {
            context.getThreadPool().handleThrowable(throwable, target);
        }

        /**
         * returns the ID of the Manager
         */
        @Override
        public Identification getManagerIdentification() {
            return context.getThreadPool().getManagerIdentification();
        }
    }
}


package org.intellimate.izou.sdk.frameworks.music.events;

import org.intellimate.izou.events.EventModel;
import org.intellimate.izou.identification.Identifiable;
import org.intellimate.izou.identification.Identification;
import org.intellimate.izou.sdk.events.CommonEvents;
import org.intellimate.izou.sdk.events.Event;
import org.intellimate.izou.sdk.frameworks.common.resources.SelectorResource;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;

/**
 * use this event to stop the music from playing
 * @author LeanderK
 * @version 1.0
 */
public class StopMusic extends Event {
    public static final String ID = "izou.music.events.stop";
    /**
     * Creates a new Event Object
     *
     * @param source      the source of the Event, most likely a this reference.
     * @throws IllegalArgumentException if one of the Arguments is null or empty
     */
    protected StopMusic(Identification source)
            throws IllegalArgumentException {
        super(CommonEvents.Type.RESPONSE_TYPE, source, new ArrayList<>(Arrays.asList(ID,
                CommonEvents.Descriptors.STOP_DESCRIPTOR, CommonEvents.Descriptors.NOT_INTERRUPT)));
    }

    /**
     * creates a new StopRequest
     * @param source the caller
     * @param target the target who should start playing
     * @return the optional StopMusicRequest
     */
    public static Optional<StopMusic> createStopMusic(Identification source, Identification target) {
        if (target == null || target.equals(source))
            return Optional.empty();
        try {
            StopMusic stopRequest = new StopMusic(source);
            stopRequest.addResource(new SelectorResource(source, target));
            return Optional.of(stopRequest);
        } catch (IllegalArgumentException e) {
            return Optional.empty();
        }
    }

    /**
     * verifies that the StopMusicRequest is correct and checks whether the you are meant to react to it
     * @param eventModel the EventModel to check against
     * @param player the identifiable
     * @return true if verified, false if not
     */
    public static boolean verify(EventModel eventModel, Identifiable player) {
        if (!eventModel.containsDescriptor(StopMusic.ID))
            return false;
        return SelectorResource.isTarget(eventModel, player)
                .orElse(true);
    }
}


package com.github.ddth.cql;

import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.AuthenticationException;
import com.datastax.driver.core.exceptions.NoHostAvailableException;
import com.datastax.driver.dse.DseCluster;
import com.datastax.driver.dse.DseSession;
import com.github.ddth.cql.internal.ClusterIdentifier;
import com.github.ddth.cql.internal.SessionIdentifier;

/**
 * Datastax's session manager for DSE.
 *
 *

 * Features:
 *

 *
Cache opened {@link DseCluster}s.

 *
Cache opened {@link DseSession}s (per {@link DseCluster}).

 *

 *

 *
 *

 * Usage:
 *

 *
Create & initialize a {@link DseSessionManager} instance:

 *
 * {@code SessionManager sessionManager = new SessionManager().init();}

 *
Obtain a {@link DseCluster}:

 *
 * {@code DseCluster cluster = sessionManager.getCluster("host1:port1,host2:port2", "username", "password", "authorizationId");}
 *

 *
Obtain a {@link DseCluster}:

 *
 * {@code DseSession session = sessionManager.getSession("host1:port1,host2:port2", "username", "password", "authorizationId", "keyspace");}
 *

 *
...do business work with the obtained {@link DseCluster} or {@link DseSession}
 * ...

 *
Before existing the application:

 *     {@code sessionManager.destroy();}

 *

 *

 *
 *

 * {@code authorizationId}: DSE allows a user to connect as another user or role, provide the name
 * of the user/role you want to connect as via this parameter.
 *

 *
 *

 * Best practices:
 *

 *
{@link DseCluster} is thread-safe; create a single instance (per target DSE cluster), and
 * share it throughout the application.

 *
{@link DseSession} is thread-safe; create a single instance (per target DSE cluster), and
 * share it throughout the application (prefix table name with keyspace in all queries, e.g.
 * {@code SELECT * FROM my_keyspace.my_table}).

 *
Create {@link DseSession} without associated keyspace:
 * {@code sessionManager.getSession("host1:port1,host2:port2", "username", "password", "authorizationId", null)}
 * (prefix table name with keyspace in all queries, e.g.
 * {@code SELECT * FROM my_keyspace.my_table}).

 *

 *

 *
 * @author Thanh Ba Nguyen <btnguyen2k@gmail.com>
 * @since 0.4.0
 */
public class DseSessionManager extends SessionManager {

    private String defaultAuthorizationId;

    /**
     * Default authorization-id used by {@link #getCluster()} and {@link #getSession()}.
     *
     * @return
     * @since 0.4.0
     */
    public String getDefaultAuthorizationId() {
        return defaultAuthorizationId;
    }

    /**
     * Default authorization-id used by {@link #getCluster()} and {@link #getSession()}.
     *
     * @param defaultAuthorizationId
     * @return
     * @since 0.4.0
     */
    public DseSessionManager getDefaultAuthorizationId(String defaultAuthorizationId) {
        this.defaultAuthorizationId = defaultAuthorizationId;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected DseCluster createCluster(ClusterIdentifier ci) {
        return DseUtils.newDseCluster(ci.hostsAndPorts, ci.username, ci.password,
                ci.authorizationId, getConfiguration());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected DseSession createSession(Cluster cluster, String keyspace) {
        return DseUtils.newDseSession((DseCluster) cluster, keyspace);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DseSessionManager init() {
        super.init();
        return this;
    }

    /**
     * Obtain a DSE cluster instance.
     *
     * @param key
     * @return
     */
    @Override
    synchronized protected DseCluster getCluster(ClusterIdentifier key) {
        return (DseCluster) super.getCluster(key);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DseCluster getCluster(String hostsAndPorts, String username, String password) {
        return getCluster(hostsAndPorts, username, password, null);
    }

    /**
     * Obtain a DSE cluster instance.
     *
     * @param hostsAndPorts
     *            format: "host1:port1,host2,host3:port3". If no port is
     *            specified, the {@link CqlUtils#DEFAULT_CASSANDRA_PORT} is used.
     * @param username
     * @param password
     * @param authorizationId
     *            DSE allows a user to connect as another user or role, provide the name of the
     *            user/role you want to connect as via this parameter
     * @return
     */
    public DseCluster getCluster(String hostsAndPorts, String username, String password,
            String authorizationId) {
        return getCluster(
                ClusterIdentifier.getInstance(hostsAndPorts, username, password, authorizationId));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DseCluster getCluster() {
        return getCluster(getDefaultHostsAndPorts(), getDefaultPassword(), getDefaultPassword(),
                defaultAuthorizationId);
    }

    /*----------------------------------------------------------------------*/

    /**
     * {@inheritDoc}
     */
    @Override
    synchronized protected DseSession getSession(SessionIdentifier si, boolean forceNew) {
        return (DseSession) super.getSession(si, forceNew);

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DseSession getSession(String hostsAndPorts, String username, String password,
            String keyspace) {
        return getSession(hostsAndPorts, username, password, null, keyspace, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DseSession getSession(String hostsAndPorts, String username, String password,
            String keyspace, boolean forceNew) {
        return getSession(hostsAndPorts, username, password, null, keyspace, forceNew);
    }

    /**
     * Obtain a DSE session instance.
     *
     *

     * The existing session instance will be returned if such existed.
     *

     *
     * @param hostsAndPorts
     * @param username
     * @param password
     * @param authorizationId
     *            DSE allows a user to connect as another user or role, provide the name of the
     *            user/role you want to connect as via this parameter
     * @param keyspace
     * @return
     * @throws NoHostAvailableException
     * @throws AuthenticationException
     */
    public DseSession getSession(String hostsAndPorts, String username, String password,
            String authorizationId, String keyspace) {
        return getSession(hostsAndPorts, username, password, authorizationId, keyspace, false);
    }

    /**
     * Obtain a DSE session instance.
     *
     * @param hostsAndPorts
     * @param username
     * @param password
     * @param authorizationId
     *            DSE allows a user to connect as another user or role, provide the name of the
     *            user/role you want to connect as via this parameter
     * @param keyspace
     * @param forceNew
     *            force create new session instance (the existing one, if any,
     *            will be closed by calling {@link Session#closeAsync()})
     * @return
     * @throws NoHostAvailableException
     * @throws AuthenticationException
     */
    public DseSession getSession(String hostsAndPorts, String username, String password,
            String authorizationId, String keyspace, boolean forceNew) {
        return getSession(SessionIdentifier.getInstance(hostsAndPorts, username, password,
                authorizationId, keyspace), forceNew);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DseSession getSession() {
        return getSession(false);
    }

    /**
     * {@inheritDoc}
     */
    public DseSession getSession(boolean forceNew) {
        return getSession(getDefaultHostsAndPorts(), getDefaultUsername(), getDefaultPassword(),
                defaultAuthorizationId, getDefaultKeyspace(), forceNew);
    }

    /*----------------------------------------------------------------------*/
}