package org.sikuli.slides.api.interpreters;

import java.awt.Color;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.sikuli.api.Relative;
import org.sikuli.api.ScreenRegion;
import org.sikuli.api.Target;
import org.sikuli.slides.api.Context;
import org.sikuli.slides.api.actions.Action;
import org.sikuli.slides.api.actions.AssertExistAction;
import org.sikuli.slides.api.actions.AssertNotExistAction;
import org.sikuli.slides.api.actions.BookmarkAction;
import org.sikuli.slides.api.actions.BrowserAction;
import org.sikuli.slides.api.actions.CompoundAction;
import org.sikuli.slides.api.actions.DoubleClickAction;
import org.sikuli.slides.api.actions.DragAction;
import org.sikuli.slides.api.actions.DropAction;
import org.sikuli.slides.api.actions.EmptyAction;
import org.sikuli.slides.api.actions.LabelAction;
import org.sikuli.slides.api.actions.LeftClickAction;
import org.sikuli.slides.api.actions.OptionalAction;
import org.sikuli.slides.api.actions.ParallelAction;
import org.sikuli.slides.api.actions.PauseAction;
import org.sikuli.slides.api.actions.RetryAction;
import org.sikuli.slides.api.actions.RightClickAction;
import org.sikuli.slides.api.actions.SleepAction;
import org.sikuli.slides.api.actions.TargetAction;
import org.sikuli.slides.api.actions.TypeAction;
import org.sikuli.slides.api.actions.WaitAction;
import org.sikuli.slides.api.models.ImageElement;
import org.sikuli.slides.api.models.Slide;
import org.sikuli.slides.api.models.SlideElement;
import org.sikuli.slides.api.sikuli.ContextImageTarget;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;

public class DefaultInterpreter implements Interpreter {

	static Logger logger = LoggerFactory.getLogger(DefaultInterpreter.class);

	static class RegexActionInterpreter implements Interpreter {

		RegexActionInterpreter(String regex){
			this.regex = regex;
		}

		String regex;
		String[] arguments;
		SlideElement element;

		boolean parse(Slide slide){
			element = slide.select().textMatches(regex).first();
			if (element == null)
				return false;

			String text = element.getText();
			Pattern pattern = Pattern.compile(regex);
			Matcher matcher = pattern.matcher(text);
			if (!matcher.find())
				return false;

			arguments = new String[matcher.groupCount()];
			for (int i = 0 ; i < matcher.groupCount(); i++){
				// note: groups are 1-indexed
				arguments[i] = matcher.group(i+1);
			}

			return true;
		}

		@Override
		final public Action interpret(Slide slide) {
			if (!parse(slide))
				return null;

			Action action = interpret(slide, element, arguments);
			if (action == null)
				return null;

			slide.remove(element);
			return action;
		}

		/**
		 *
		 *
		 * @param slide the source slide
		 * @param element the element whose text content matches the regular expression
		 * @param arguments array of string arguments (not including the keyword)
		 * @return
		 */
		protected Action interpret(Slide slide, SlideElement element, String[] arguments){
			return null;
		}
	}

	static class SleepActionInterpreter extends RegexActionInterpreter {

		SleepActionInterpreter(){
			super("(?i)sleep\\s+(\\d+)");
		}

		@Override
		protected Action interpret(Slide slide, SlideElement element, String[] arguments){
			if (arguments.length != 1)
				return null;

			String durationString = arguments[0];
			Long duration = parseDuration(durationString);
			if (duration == null)
				return null;
			return new SleepAction(duration);
		}
	};

	static class BrowseActionInterpreter extends RegexActionInterpreter {

		BrowseActionInterpreter(){
			super("(?i)(browse|open)\\s+(.+)");
		}

		@Override
		protected Action interpret(Slide slide, SlideElement element, String[] arguments){
			if (arguments.length != 2)
				return null;

			String urlString = arguments[1];
			URL url = null;
			try {
				url = new URL(urlString);
			} catch (MalformedURLException e) {
				return null;
			}

			BrowserAction action = new BrowserAction();
			action.setUrl(url);
			return action;
		}
	}

	static class TypeActionInterpreter extends RegexActionInterpreter {

		TypeActionInterpreter(){
			super("(?i)type\\s+(.+)");
		}

		@Override
		protected Action interpret(Slide slide, SlideElement element, String[] arguments){
			if (arguments.length != 1)
				return null;

			TypeAction action = new TypeAction();
			action.setText(arguments[0]);
			return action;
		}
	}

	static class WaitActionInterpreter extends RegexActionInterpreter {

		WaitActionInterpreter(){
			super("(?i)wait\\s*(\\d*)");
		}

		@Override
		protected Action interpret(Slide slide, SlideElement element, String[] arguments){
			Long duration;

			String durationString = arguments[0];
			if (durationString.length() == 0){
				duration = Long.MAX_VALUE;
			}else{
				duration = parseDuration(durationString);
			}

			if (duration == null)
				return null;

			Target target = (new ContextImageTargetInterpreter()).interpret(slide);
			if (target == null)
				return null;
			WaitAction action = new WaitAction(target);
			action.setDuration(duration);
			return action;
		}
	}

	static class LeftClickActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {
			SlideElement keywordElement = slide.select().ignoreCase().textStartsWith("click").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);
			Action action = new LeftClickAction();
			return action;
		}
	}

	static class RightClickActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {
			SlideElement keywordElement = slide.select().ignoreCase().textStartsWith("rightclick").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);
			Action action = new RightClickAction();
			return action;
		}
	}

	static class DoubleClickActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {
			SlideElement keywordElement = slide.select().ignoreCase().textStartsWith("doubleclick").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);
			Action action = new DoubleClickAction();
			return action;
		}
	}

	static class DragActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {
			SlideElement keywordElement = slide.select().ignoreCase().textStartsWith("drag").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);
			Action action = new DragAction();
			return action;
		}
	}

	static class DropActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {
			SlideElement keywordElement = slide.select().ignoreCase().textStartsWith("drop").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);
			Action action = new DropAction();
			return action;
		}
	}

	static class ExistActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {
			SlideElement keywordElement = slide.select().ignoreCase().textStartsWith("exist").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);

			Target target = (new ContextImageTargetInterpreter()).interpret(slide);
			if (target == null)
				return null;
			return new AssertExistAction(target);
		}
	}

	static class NotExistActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {

			SlideElement keywordElement = slide.select().ignoreCase().textStartsWith("not exist").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);

			Target target = (new ContextImageTargetInterpreter()).interpret(slide);
			if (target == null)
				return null;
			return new AssertNotExistAction(target);
		}
	}

	static class TargetActionInterpreter implements Interpreter {

		private List<Interpreter> actionInterpreters;

		TargetActionInterpreter(){
			actionInterpreters = Lists.newArrayList();
			actionInterpreters.add(new LeftClickActionInterpreter());
			actionInterpreters.add(new DoubleClickActionInterpreter());
			actionInterpreters.add(new RightClickActionInterpreter());
			actionInterpreters.add(new DragActionInterpreter());
			actionInterpreters.add(new DropActionInterpreter());
			actionInterpreters.add(new TypeActionInterpreter());
		}

		@Override
		public Action interpret(Slide slide) {

			Action action = null;
			Iterator<Interpreter> iter = actionInterpreters.iterator();
			while (action == null && iter.hasNext()){
				action = iter.next().interpret(slide);
			}

			if (action == null)
				return null;

			Target target = (new ContextImageTargetInterpreter()).interpret(slide);
			if (target == null)
				return null;
			return new TargetAction(target, action);
		}
	}

	public static class ContextImageTargetInterpreter implements TargetInterpreter {

		public Target interpret(Slide slide, SlideElement targetElement) {

			ImageElement imageElement = (ImageElement) slide.select().intersects(targetElement).isImage().first();
			if (imageElement == null)
				return null;

			Target target = createTarget(imageElement, targetElement);
			if (target == null)
				return null;
			return target;
		}

		@Override
		public Target interpret(Slide slide) {

			SlideElement targetElement = slide.select().isTarget().first();
			if (targetElement == null)
				return null;

			return interpret(slide, targetElement);
		}

		Target createTarget(ImageElement imageElement, SlideElement targetElement){
			if (imageElement == null || targetElement == null)
				return null;

			int w = imageElement.getCx();
			int h = imageElement.getCy();
			if (w <= 0 || h <= 0)
				return null;

			double xmax = 1.0 * (targetElement.getOffx() + targetElement.getCx() - imageElement.getOffx()) / w;
			double ymax = 1.0 * (targetElement.getOffy() + targetElement.getCy() - imageElement.getOffy()) / h;
			double xmin = 1.0 * (targetElement.getOffx() - imageElement.getOffx()) / w;
			double ymin = 1.0 * (targetElement.getOffy() - imageElement.getOffy()) / h;

			xmax = Math.min(1.0, xmax);
			ymax = Math.min(1.0, ymax);
			xmin = Math.max(0, xmin);
			ymin = Math.max(0, ymin);

			return new ContextImageTarget(imageElement.getSource(), xmin, ymin, xmax, ymax);
		}
	}

	static class ScreenLocationInterpreter implements SpatialRelationshipInterpreter{

		@Override
		public SpatialRelationship interpret(Slide slide, SlideElement element) {
			final double xmin = 1.0 * element.getOffx() / 9144000;
			final double ymin = 1.0 * element.getOffy() / 6858000;
			final double xmax = 1.0 * (element.getOffx()  + element.getCx()) / 9144000;
			final double ymax = 1.0 * (element.getOffy()  + element.getCy()) / 6858000;
			return new SpatialRelationship(){
				@Override
				public ScreenRegion apply(Context input) {
					ScreenRegion screenRegion = input.getScreenRegion();
					return Relative.to(screenRegion).region(xmin, ymin, xmax, ymax).getScreenRegion();
				}
			};
		}
	}

	static class TargetLocationInterpreter implements SpatialRelationshipInterpreter{

		@Override
		public SpatialRelationship interpret(Slide slide, SlideElement element) {

			SlideElement targetElement = slide.select().isTarget().near(element, 200000).first();
			if (targetElement == null)
				return null;

			final Target target = (new ContextImageTargetInterpreter()).interpret(slide, targetElement);
			if (target == null)
				return null;

			final int x = UnitConverter.emuToPixels(element.getOffx() - targetElement.getOffx());
			final int y = UnitConverter.emuToPixels(element.getOffy() - targetElement.getOffy());
			final int width = UnitConverter.emuToPixels(element.getCx());
			final int height = UnitConverter.emuToPixels(element.getCy());

			return new SpatialRelationship(){
				@Override
				public ScreenRegion apply(Context context) {
					ScreenRegion region = context.getScreenRegion().find(target);
					if (region != null)
						return Relative.to(region).region(x,y,width,height).getScreenRegion();
					else
						return null;
				}
			};
		}
	}

	static class LabelInterpreter implements Interpreter{
		@Override
		public Action interpret(Slide slide) {
			SlideElement textElement = slide.select().isNotKeyword().hasText().first();
			if (textElement == null)
				return null;

			LabelAction action = new LabelAction();
			action.setSlideElement(textElement);
			action.setText(textElement.getText());
			double fontSize = UnitConverter.WholePointsToPoints(textElement.getTextSize());
			action.setFontSize((int)fontSize);

			try {
				Color bgColor = Color.decode("#" + textElement.getBackgroundColor());
				action.setBackgroundColor(bgColor);
			}catch(NumberFormatException e){

			}

			List<SpatialRelationshipInterpreter> locations =
					Lists.newArrayList(new TargetLocationInterpreter(), new ScreenLocationInterpreter());

			SpatialRelationship spatial = null;
			Iterator<SpatialRelationshipInterpreter> iter = locations.iterator();
			while (iter.hasNext() && spatial == null){
				spatial = iter.next().interpret(slide, textElement);
			}

			if (spatial != null)
				action.setSpatialRelationship(spatial);
			slide.remove(textElement);
			return action;
		}
	}

	static class SkipActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {
			SlideElement keywordElement = slide.select().textMatches("(?i)skip").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);
			return new EmptyAction();
		}
	}

	static class OptionalActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {
			SlideElement keywordElement = slide.select().textMatches("(?i)optional").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);
			return new OptionalAction();
		}
	}

	static class PauseActionInterpreter implements Interpreter {
		@Override
		public Action interpret(Slide slide) {
			SlideElement keywordElement = slide.select().textMatches("(?i)pause").first();
			if (keywordElement == null)
				return null;
			slide.remove(keywordElement);
			return new PauseAction();
		}
	}

	static class BookmarkActionInterpreter extends RegexActionInterpreter {

		BookmarkActionInterpreter(){
			super("(?i)bookmark\\s+([\\S]+)");
		}

		@Override
		protected Action interpret(Slide slide, SlideElement element, String[] arguments){
			if (arguments.length != 1)
				return null;

			BookmarkAction action = new BookmarkAction();
			action.setName(arguments[0]);
			return action;
		}
	}

	//	Action interpretAsLabel(Slide slide){
	//		List<SlideElement> textElements = slide.select().isNotKeyword().hasText().all();
	//		if (textElements.size() == 0)
	//			return null;
	//
	//		if (textElements.size() == 1){
	//			return interpretAsLabel(slide, textElements.get(0));
	//		}else{
	//			ParallelAction pa = new ParallelAction();
	//			for (SlideElement textElement : textElements){
	//				Action labelAction = interpretAsLabel(slide, textElement);
	//				pa.addChild(labelAction);
	//			}
	//			return pa;
	//		}
	//	}

	//	Target interpretAsImageTarget(Slide slide){
	//		SlideElement targetElement = slide.select().isTarget().first();
	//		if (targetElement == null)
	//			return null;
	//		return interpretAsImageTarget(slide, targetElement);
	//	}

	//	Target interpretAsImageTarget(Slide slide, SlideElement targetElement){
	//		ImageElement imageElement = (ImageElement) slide.select().intersects(targetElement).isImage().first();
	//		if (imageElement == null)
	//			return null;
	//
	//		Target target = createTarget(imageElement, targetElement);
	//		if (target == null)
	//			return null;
	//		return target;
	//	}

	//	Action interpretAsTargetAction(Slide slide, Action doAction){
	//		Target target = interpretAsImageTarget(slide);
	//		if (target == null)
	//			return doAction;
	//		return new RetryAction(new TargetAction(target, doAction),5000, 500);
	//	}

	//	Action interpretAsType(Slide slide){
	//		SlideElement keywordElement = slide.select().isKeyword(KeywordDictionary.TYPE).first();
	//		if (keywordElement == null)
	//			return null;
	//
	//		SlideElement textElement = slide.select().hasText().first();
	//		if (textElement == null)
	//			return null;
	//
	//		TypeAction typeAction = new TypeAction();
	//		typeAction.setText(textElement.getText());
	//
	//		slide.remove(keywordElement);
	//		slide.remove(textElement);
	//
	//		return interpretAsTargetAction(slide, typeAction);
	//	}
	//
	//	Action interpretAsBrowser(Slide slide){
	//		SlideElement keywordElement = slide.select().isKeyword(KeywordDictionary.BROWSER).first();
	//		if (keywordElement == null)
	//			return null;
	//
	//		SlideElement textElement = slide.select().hasText().first();
	//		if (textElement == null)
	//			return null;
	//
	//		URL url = null;
	//		try {
	//			url = new URL(textElement.getText());
	//		} catch (MalformedURLException e) {
	//			return null;
	//		}
	//
	//		slide.remove(keywordElement);
	//		slide.remove(textElement);
	//
	//		BrowserAction a = new BrowserAction();
	//		a.setUrl(url);
	//		return a;
	//	}
	//

	Long interpretAsDuration(SlideElement textElement){

		String arg = textElement.getText();

		// extract the time unit
		TimeUnit timeUnit = UnitConverter.extractTimeUnitFromString(arg);
		// if the time unit was not specified, default to seconds
		if(timeUnit==null){
			timeUnit=TimeUnit.SECONDS;
		}
		// extract the wait time string value, replace all non digits with blank
		String waitTimeString = arg.replaceAll("[^0-9.]", "");
		if(waitTimeString != null){
			double duration = Double.parseDouble(waitTimeString);
			Double durationInMilliSeconds = 0.0;
			if (timeUnit == TimeUnit.SECONDS){
				durationInMilliSeconds = duration * 1000;
			}else if (timeUnit == TimeUnit.MINUTES){
				durationInMilliSeconds = duration * 1000 * 60;
			}else if (timeUnit == TimeUnit.HOURS){
				durationInMilliSeconds = duration * 1000 * 60 * 60;
			}
			return durationInMilliSeconds.longValue();
		}else{

			logger.error("Error: Please write a valid time string."
					+" Valid examples include: 10 milliseconds, 10 seconds, 10 minutes.");

			return null;
		}
	}

	static Long parseDuration(String inputString){

		// extract the time unit
		TimeUnit timeUnit = UnitConverter.extractTimeUnitFromString(inputString);
		// if the time unit was not specified, default to seconds
		if(timeUnit==null){
			timeUnit=TimeUnit.SECONDS;
		}
		// extract the wait time string value, replace all non digits with blank
		String waitTimeString = inputString.replaceAll("[^0-9.]", "");
		if(waitTimeString != null){
			double duration = Double.parseDouble(waitTimeString);
			Double durationInMilliSeconds = 0.0;
			if (timeUnit == TimeUnit.SECONDS){
				durationInMilliSeconds = duration * 1000;
			}else if (timeUnit == TimeUnit.MINUTES){
				durationInMilliSeconds = duration * 1000 * 60;
			}else if (timeUnit == TimeUnit.HOURS){
				durationInMilliSeconds = duration * 1000 * 60 * 60;
			}
			return durationInMilliSeconds.longValue();
		}else{

			logger.error("Error: Please write a valid time string."
					+" Valid examples include: 10 milliseconds, 10 seconds, 10 minutes.");

			return null;
		}
	}

	//	Action interpretAsSleep(Slide slide){
	//		SlideElement keywordElement = slide.select().ignoreCase().textStartsWith("sleep").first();
	//		if (keywordElement == null)
	//			return null;
	//
	//		String[] toks = keywordElement.getText().split(" ");
	//		if (toks.length != 2)
	//			return null;
	//
	//		String durationString = toks[1];
	//		Long duration = parseDuration(durationString);
	//		if (duration == null)
	//			return null;
	//
	//		slide.remove(keywordElement);
	//
	//		SleepAction a = new SleepAction(duration);
	//		return a;
	//	}

	//	Action interpretAsWait(Slide slide){
	//		SlideElement keywordElement = slide.select().isKeyword(KeywordDictionary.WAIT).first();
	//		if (keywordElement == null)
	//			return null;
	//
	//		Target target = interpretAsImageTarget(slide);
	//		if (target == null)
	//			return null;
	//
	//		SlideElement textElement = slide.select().hasText().first();
	//		if (textElement == null)
	//			return null;
	//
	//		Long duration = interpretAsDuration(textElement);
	//		if (duration == null)
	//			return null;
	//
	//		slide.remove(keywordElement);
	//		slide.remove(textElement);
	//
	//		WaitAction a = new WaitAction(target);
	//		a.setDuration(duration);
	//		return a;
	//	}

	private Action interpretAsBookmark(Slide slide) {
		SlideElement keywordElement = slide.select().isKeyword(KeywordDictionary.BOOKMARK).first();
		if (keywordElement == null)
			return null;

		String text = keywordElement.getText();
		if (text.isEmpty()){
			logger.error("No name is specified for the bookmark keyword");
			return null;
		}

		slide.remove(keywordElement);
		BookmarkAction action = new BookmarkAction();
		action.setName(text);
		return action;
	}

	@Override
	public Action interpret(Slide inputSlide){

		ParallelAction parallelAction = new ParallelAction();

		// make a copy of the slide to work on
		Slide slide = new Slide(inputSlide);

		List<Interpreter> interpreters = Lists.newArrayList(
				new TargetActionInterpreter(),
				new ExistActionInterpreter(),
				new NotExistActionInterpreter(),
				new BrowseActionInterpreter(),
				new SleepActionInterpreter(),
				new WaitActionInterpreter()
				);

		List<Interpreter> controlActionInterpreters = Lists.newArrayList(
				new SkipActionInterpreter(),
				new OptionalActionInterpreter(),
				new PauseActionInterpreter(),
				new BookmarkActionInterpreter()
				);

		Action keywordAction = null;
		Iterator<Interpreter> iter = interpreters.iterator();
		while (keywordAction == null && iter.hasNext()){
			keywordAction = iter.next().interpret(slide);
		}

		if (keywordAction instanceof TargetAction){
			keywordAction = new RetryAction(keywordAction, 10000, 500);
		}

		if (keywordAction != null){
			parallelAction.addChild(keywordAction);
		}

		Action controlAction = null;
		iter = controlActionInterpreters.iterator();
		while (controlAction == null && iter.hasNext()){
			controlAction = iter.next().interpret(slide);
		}

		Interpreter labelInterpreter = new LabelInterpreter();
		Action labelAction;
		while ((labelAction = labelInterpreter.interpret(slide)) != null){

			// if there is no foreground action, create a sleep action as the foreground action,
			// this way, the label actions can stop on when the sleep action terminates
			if (!parallelAction.hasForegroundAction()){
				// TODO: makes the sleep duration customizable
				parallelAction.addChild(new SleepAction(5000));
			}

			labelAction = new RetryAction(labelAction, Long.MAX_VALUE, 500);
			parallelAction.addChildAsBackground(labelAction);
		}

		Action action = parallelAction;

		//		Action controlAction = null;
		//		if ((controlAction = interpretAsSkip(slide)) != null){
		//
		//		}else if ((controlAction = interpretAsOptional(slide)) != null){
		//
		//		}else if ((controlAction = interpretAsPause(slide)) != null){
		//
		//		}else if ((controlAction = interpretAsBookmark(slide)) != null){
		//
		//		}

		if (controlAction != null){
			((CompoundAction) controlAction).addChild(action);
			action = controlAction;
		}

		logger.debug("result:" + action);
		return action;
	}

}


package org.sikuli.slides.driver;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.List;

import org.sikuli.slides.api.io.PPTXSlidesReader;
import org.sikuli.slides.api.io.SlidesReader;
import org.sikuli.slides.api.models.Slide;
import org.sikuli.slides.driver.annotations.WidgetSlide;

public class WidgetSlideFactory {

	/**
	 * Create an object where each field represents an {@link Widget} that can be found and interacted with
	 * automatically
	 *
	 * @param classToProxy
	 * @return
	 */
	public static <T> T create(Class<T> classToProxy) {

		if (!classToProxy.isAnnotationPresent(WidgetSlide.class))
			// path to pptx file is not specified
			throw new RuntimeException("@Source annotation is missing in " + classToProxy + ". Need this to know where" +
					"to read slides");

		WidgetSlide source = classToProxy.getAnnotation(WidgetSlide.class);
		source.value();

//		if (source.url().isEmpty() > 0){
		SlidesReader reader = new PPTXSlidesReader();

		List<Slide> slides;
//		File file = new File(source.value());
		try {

			if (!source.value().isEmpty()){
				slides = reader.read(new File(source.value()));
			}else if (!source.url().isEmpty()){
				slides = reader.read(new URL(source.url()));
			}else{
				throw new RuntimeException("@Source is not specified correctly");
			}

		} catch (IOException e) {
			// error in reading the slides from the given source
			throw new RuntimeException(e);
		}

		DefaultSlideDriver driver;
		if (slides.size() == 0){
			// there is no slide found
			driver = new DefaultSlideDriver();
		}else{

			driver = new DefaultSlideDriver(slides.get(0));
		}

		T page = instantiatePage(driver, classToProxy);
		initElements(driver, page);
		return page;
	}

	static void initElements(SlideDriver driverRef, Object page) {
		initElements(new DefaultWidgetLocatorFactory(driverRef), page);
	}
	//
	static void initElements(WidgetLocatorFactory factoryRef, Object page) {
		initElements(new DefaultFieldDecorator(factoryRef), page);
	}

	static void initElements(FieldDecorator decorator, Object page) {
		Class<?> proxyIn = page.getClass();
		while (proxyIn != Object.class) {
			proxyFields(decorator, page, proxyIn);
			proxyIn = proxyIn.getSuperclass();
		}
	}

	private static void proxyFields(FieldDecorator decorator, Object page, Class<?> proxyIn) {
		Field[] fields = proxyIn.getDeclaredFields();
		for (Field field : fields) {
			Object value = decorator.decorate(page.getClass().getClassLoader(), field);
			if (value != null) {
				try {
					field.setAccessible(true);
					field.set(page, value);
				} catch (IllegalAccessException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	private static <T> T instantiatePage(SlideDriver driver, Class<T> pageClassToProxy) {
		try {
			try {
				// try to see if there's a constructor that takes a Driver object as an argument
				Constructor<T> constructor = pageClassToProxy.getConstructor(SlideDriver.class);
				return constructor.newInstance(driver);
			} catch (NoSuchMethodException e) {
				// if not, use the default constructor
				return pageClassToProxy.newInstance();
			}
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}

}


package org.sikuli.slides.api;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Map;

import org.sikuli.api.DesktopScreenRegion;
import org.sikuli.api.ScreenRegion;
import org.sikuli.slides.api.listeners.ExecutionVisualizer;
import org.sikuli.slides.api.models.Slide;
import org.stringtemplate.v4.ST;

import com.google.common.base.Objects;
import com.google.common.collect.Maps;

/**
 * Describes the context where an action is executed
 *

 * Examples of uses:
 *

 * Specify which {@link ScreenRegion} on to execute an action, for instance, to operate only
 * within the left half of the screen or to operate on the entire secondary screen.
 *

 * Specify image recognition parameters such as minimum score of similarity and the amount
 * of time to wait and retry
 *
 * @author Sikuli Lab
 *
 */
public class Context {
	private static final long DEFAULT_WAIT_TIME = 5000;
	private static final float DEFAULT_MIN_SCORE = 0.7f;

	private ScreenRegion screenRegion;
	private Slide slide;
	private ExecutionListener executionListener = new ExecutionVisualizer();

	// the default slide selector is to accept all
	private ExecutionFilter filter = ExecutionFilter.Factory.createAllFilter();
	private Map<String, Object> parameters = Maps.newHashMap();
	private float minScore = DEFAULT_MIN_SCORE;
	// how long to wait for a target in ms
	private long waitTime = DEFAULT_WAIT_TIME;

	/**
	 * Construct a new context with a particular screen region.
	 *
	 * @param screenRegion
	 */
	public Context(ScreenRegion screenRegion) {
		this.screenRegion = checkNotNull(screenRegion);
	}

	/**
	 * Construct a new context, which is a copy of the
	 * given context but with a different slide.
	 *
	 * @param context	the context to copy data from
	 * @param slide	the new slide
	 */
	public Context(Context context, Slide slide){
		checkNotNull(context);
		copyFrom(context);
		this.slide = checkNotNull(slide);
	}

	/**
	 * Construct a new context, which is a copy of the
	 * given context but with a different screen region.
	 *
	 * @param context	the context to copy data from
	 * @param screenRegion	the new screen region
	 */
	public Context(Context context, ScreenRegion screenRegion){
		copyFrom(context);
		this.screenRegion = screenRegion;
	}

	/**
	 * Construct a default context. The screen region is
	 * set to the entire primary screen.
	 */
	public Context(){
		screenRegion = new DesktopScreenRegion();
	}

	/**
	 * Construct a context by copying all data from another context
	 * @param context another context to copy data from
	 */
	public Context(Context context) {
		checkNotNull(context);
		copyFrom(context);
	}

	private void copyFrom(Context copy){
		setMinScore(copy.getMinScore());
		setScreenRegion(copy.getScreenRegion());
		setExecutionFilter(copy.getExecutionFilter());
		setExecutionListener(copy.getExecutionListener());
		setWaitTime(copy.getWaitTime());
		setSlide(copy.getSlide());
		parameters = Maps.newHashMap(copy.getParameters());
	}

	/**
	 * Get the screen region associated with this context
	 * @return	the screen region
	 */
	public ScreenRegion getScreenRegion() {
		return screenRegion;
	}

	/**
	 * Set the screen region of this context
	 * @param screenRegion the screen region
	 */
	public void setScreenRegion(ScreenRegion screenRegion) {
		this.screenRegion = checkNotNull(screenRegion);
	}

	/**
	 * Add a parameter
	 * @param key	key of the parameter
	 * @param value	value of the parameter
	 */
	public void addParameter(String key, Object value){
		parameters.put(key, value);
	}

	/**
	 * Get the parameters map
	 * @return	the parameters map
	 */
	public Map<String, Object> getParameters() {
		return Maps.newHashMap(parameters);
	}

	/**
	 * Render the given template string as a string based on the parameter values
	 * associated with this context
	 *
	 * @param templateText	the template string
	 * @return	the rendered string
	 */
	public String render(String templateText) {
		checkNotNull(templateText);
		ST st = new ST(templateText);
		for (Map.Entry<String, Object> e : parameters.entrySet()){
			st.add(e.getKey(), e.getValue());
		}
		return st.render();
	}

	/**
	 * Get the execution filter associated with this context.
	 * @return	the execution filter
	 */
	public ExecutionFilter getExecutionFilter() {
		return filter;
	}

	/**
	 * Set the execution filter for this context. The filter can be used to accept or reject an execution event.
	 * For instance, accept only those with a slide number > 3
	 * or accept only those whose Action is not a TYPE action
	 *
	 * @param filter	the filter
	 */
	public void setExecutionFilter(ExecutionFilter filter) {
		this.filter = filter;
	}

	public String toString(){
		return Objects.toStringHelper(this)
				.add("minScore", minScore)
				.add("parameter", parameters)
				.add("screenRegion", screenRegion).toString();
	}

	/**
	 * Get the minimum score
	 * @return	minimum score
	 */
	public float getMinScore() {
		return minScore;
	}

	/**
	 * Set the minimum score of similarity for something to be considered as a match
	 * @param minScore	the minimum score
	 */
	public void setMinScore(float minScore) {
		this.minScore = minScore;
	}

	/**
	 * Get the wait time
	 * @return	wait time
	 */
	public long getWaitTime() {
		return waitTime;
	}

	/**
	 * Set the wait time
	 * @param waitTime	time in milliseconds
	 */
	public void setWaitTime(long waitTime) {
		this.waitTime = waitTime;
	}

	/**
	 * Get the execution listener associated with this context
	 * @return	an {@link ExecutionListener} object
	 */
	public ExecutionListener getExecutionListener() {
		return executionListener;
	}

	/**
	 * Set the execution listener associated with this context
	 * @param executionListener	the {@link ExecutionListener} object
	 */
	public void setExecutionListener(ExecutionListener executionListener) {
		this.executionListener = executionListener;
	}

	/**
	 * Get the slide associated with this context
	 * @return	a {@link Slide} object
	 */
	public Slide getSlide() {
		return slide;
	}

	/**
	 * Set the slide associated with this context
	 * @param slide	a {@link Slide} object
	 */
	public void setSlide(Slide slide) {
		this.slide = slide;
	}

}


package org.sikuli.slides.api.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Objects;
import com.google.common.io.Files;

class PPTXBundle {

	static Logger logger = LoggerFactory.getLogger(PPTXBundle.class);

	// the root directory of the decompressed bundle on the file system
	private File rootDir;
	// the name of the pptx file
	private String name = "";
	// number of slides
	private int slideCount = 0;

	public String toString(){
		return Objects.toStringHelper(this)
				.add("count", slideCount)
				.add("rootDir", rootDir).toString();
	}

	public static PPTXBundle createFrom(File pptxFile) throws IOException{
		if (pptxFile == null || !pptxFile.exists()){
			logger.error("File does not exist: {}", pptxFile);
			throw new IOException();
		}

		File workingDir = Files.createTempDir();
		if (workingDir == null){
			logger.error("Failed to create sikuli-slides working directory in " + System.getProperty("java.io.tmpdir"));
			throw new IOException();
		}

		PPTXBundle bundle = new PPTXBundle(pptxFile, workingDir);
		if (!bundle.isValid()){
			logger.error("File is not a valid pptx: {}", pptxFile);
			throw new IOException();
		}
		return bundle;
	}

	// takes pptxFile as the input input and tmpDir to unzip the file to
	PPTXBundle(File pptxFile, File tmpDir){
		name = pptxFile.getName();
		rootDir = new File(tmpDir, FilenameUtils.removeExtension(name));

		doUnZipFile(pptxFile, rootDir);

		if (isValid()){
			slideCount = getSlidesDirectory().list().length - 1;
		}
	}

	boolean isValid(){
		return rootDir.exists() && getSlidesDirectory().exists() && getRelationshipsDirectory().exists();
	}

	int getSlideCount(){
		return slideCount;
	}

	File getRelationshipsDirectory(){
		return new File(rootDir, "ppt" + File.separator + "slides" + File.separator + "_rels");
	}

	File getSlidesDirectory(){
		return new File(rootDir, "ppt" + File.separator + "slides");
	}

	// Get the file of the slide rel for a slide number
	File getSlideXMLRel(int slideNumber){
		String filename = String.format("slide%d.xml.rels", slideNumber);
		return new File(getRelationshipsDirectory(), filename);
	}

	File getSlideXML(int slideNumber){
		String filename = String.format("slide%d.xml", slideNumber);
		return new File(getSlidesDirectory(), filename);
	}

	static void doUnZipFile(File file, File dest){
		try {
	         ZipFile zipFile = new ZipFile(file);
	         if (zipFile.isEncrypted()) {
//	            zipFile.setPassword(password);
	         }
	         zipFile.extractAll(dest.getAbsolutePath());
	    } catch (ZipException e) {
	    	logger.error(e.getMessage());
	    }
	}

}


package org.sikuli.slides.api.concurrent;

import static com.google.common.base.Preconditions.checkNotNull;

import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;

import org.jnativehook.mouse.NativeMouseEvent;
import org.sikuli.api.ScreenRegion;
import org.sikuli.api.robot.desktop.DesktopScreen;

class ScreenRegionLatch extends NativeInputLatch {

	private ScreenRegion screenRegion;
	private int screenOffsetX;
	private int screenOffsetY;

	ScreenRegionLatch(ScreenRegion screenRegion){
		this.screenRegion = checkNotNull(screenRegion);

		// calculate the x,y offsets of the target screen, which can be
		// the secondary screen. So we can map the x,y given by NativeHook
		// to the x,y of the ScreenRegion object
		int id =  ((DesktopScreen) screenRegion.getScreen()).getScreenId();
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsDevice [] devices = ge.getScreenDevices();
		Rectangle bounds = devices[id].getDefaultConfiguration().getBounds();
		this.screenOffsetX = bounds.x;
		this.screenOffsetY = bounds.y;
	}

	/**
	 * Check the mouse action is within the screen region range.
	 * @param e native mouse event
	 * @return true if the mouse event was within the screen region range.
	 * Otherwise, it returns false.
	 */
	protected boolean inRange(NativeMouseEvent e){
		Rectangle r = screenRegion.getBounds();
		r.x += screenOffsetX;
		r.y += screenOffsetY;

		int x = e.getX();
		int y = e.getY();
		return r.contains(x,y);
	}
}


package org.sikuli.slides.api.models;

import static com.google.common.base.Preconditions.checkNotNull;

import java.awt.Rectangle;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.sikuli.slides.api.interpreters.Keyword;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;

// Convenient utility class for selecting slide elements
public class Selector {
	static Logger logger = LoggerFactory.getLogger(Selector.class);
	private Collection<SlideElement> elements;
	private boolean ignoreCase = false;

	Selector(Collection<SlideElement> elements){
		this.elements = elements;
	}

	public List<SlideElement> all(){
		return Lists.newArrayList(elements);
	}

	public SlideElement first(){
		if (elements.size()>=1){
			return elements.iterator().next();
		}else{
			return null;
		}
	}

	public boolean exist() {
		return elements.size() > 0;
	}

	public static Selector select(Slide slide){
		return new Selector(slide.getElements());
	}

	public static Selector select(Collection<SlideElement> elements){
		return new Selector(elements);
	}

	public Selector below(final SlideElement element){
		checkNotNull(element);
		final Rectangle r = element.getBounds();
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				return e != element && e.getBounds().y >= r.y;
			}
		});
		return this;
	}

	public Selector toRightOf(final SlideElement element){
		checkNotNull(element);
		final Rectangle r = element.getBounds();
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				return e != element && e.getBounds().x >= r.x;
			}
		});
		return this;
	}

	public Selector intersects(final SlideElement element){
		checkNotNull(element);
		final Rectangle r = element.getBounds();
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				return e != element && r.intersects(e.getBounds());
			}
		});
		return this;
	}

	public Selector near(final SlideElement element, int radius){
		checkNotNull(element);
		final Rectangle r = element.getBounds();
		r.x -= radius;
		r.y -= radius;
		r.width += (2*radius);
		r.height += (2*radius);
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				return e != element && r.intersects(e.getBounds());
			}
		});
		return this;
	}

	// vertical center line is within each other's y range
	// namely, if they have the same x range, they would intersect
	public Selector overlapVerticallyWith(final SlideElement element, final float minOverlapRatio){
		checkNotNull(element);
		final Rectangle r1 = element.getBounds();
		r1.x = 0; r1.width = 1;
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				if (e == element){
					return false;
				}
				if (r1.height == 0){
					return false;
				}
				Rectangle r2 =e.getBounds();
				r2.x = 0; r2.width = 1;
				Rectangle intersection = r1.intersection(r2);
				float yOverlapRatio = 1f * intersection.height / r1.height;
				return yOverlapRatio > minOverlapRatio;
			}
		});
		return this;
	}

	public Selector orderByY(){
		List<SlideElement> list = Lists.newArrayList(elements);
		Collections.sort(list, new Comparator<SlideElement>(){
			@Override
			public int compare(SlideElement a, SlideElement b) {
				return a.getOffy() - b.getOffy();
			}
		});
		elements = list;
		return this;
	}

	public Selector orderByX(){
		List<SlideElement> list = Lists.newArrayList(elements);
		Collections.sort(list, new Comparator<SlideElement>(){
			@Override
			public int compare(SlideElement a, SlideElement b) {
				return a.getOffx() - b.getOffx();
			}
		});
		elements = list;
		return this;
	}

	public Selector print(PrintStream out){
		for (SlideElement element : elements){
			out.println(element);
		}
		return this;
	}

	public Selector print(){
		for (SlideElement element : elements){
			System.out.println(element);
		}
		return this;
	}

	public Selector hasText(){
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				return hasText(e);
			}
		});
		return this;
	}

	public Selector hasNoText(){
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				return !hasText(e);
			}
		});
		return this;
	}

	public Selector textContains(final String str){
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				if (!ignoreCase){
					return e.getText() != null && e.getText().contains(str);
				}else{
					return e.getText() != null && e.getText().toLowerCase().contains(str.toLowerCase());
				}
			}
		});
		return this;
	}

	public Selector textStartsWith(final String str){
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				if (!ignoreCase){
					return e.getText() != null && e.getText().startsWith(str);
				}else{
					return e.getText() != null && e.getText().toLowerCase().startsWith(str.toLowerCase());
				}
			}
		});
		return this;
	}

	public Selector textMatches(final String regex){
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				return e.getText() != null && e.getText().matches(regex);
			}
		});
		return this;
	}

	public Selector ignoreCase(){
		ignoreCase = true;
		return this;
	}

	public Selector nameStartsWith(final String prefix){
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				return e.getName().startsWith(prefix);
			}
		});
		return this;
	}

	static public boolean hasText(SlideElement e){
		return e.getText() != null && ! e.getText().isEmpty();
	}

	public Selector isTarget(){
		final List<SlideElement> images = Selector.select(elements).isImage().all();
		// find elements that intersect at least one image
		elements = Collections2.filter(elements, new Predicate<SlideElement>(){
			@Override
			public boolean apply(SlideElement e) {
				return !hasText(e) && Selector.select(images).intersects(e).exist();
			}
		});
		return this;
	}

	public Selector isKeyword() {
		List<SlideElement> images = Lists.newArrayList();
		for (SlideElement e : elements){
			if (e instanceof KeywordElement){
				images.add((KeywordElement) e);
			}
		}
		elements = images;
		return this;
	}

	public Selector isKeyword(Keyword keyword) {
		List<SlideElement> newList = Lists.newArrayList();
		for (SlideElement e : elements){
			if (e instanceof KeywordElement && ((KeywordElement) e).getKeyword() == keyword){
				newList.add((KeywordElement) e);
			}
		}
		elements = newList;
		return this;
	}

	public Selector isImage() {
		List<SlideElement> images = Lists.newArrayList();
		for (SlideElement e : elements){
			if (e instanceof ImageElement){
				images.add((ImageElement) e);
			}
		}
		elements = images;
		return this;
	}

	public Selector isNotKeyword() {
		List<SlideElement> newList = Lists.newArrayList();
		for (SlideElement e : elements){
			if (!(e instanceof KeywordElement)){
				newList.add(e);
			}
		}
		elements = newList;
		return this;
	}

}


package org.springframework.integration.aws.sns.config;

import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler;

/**
 * The namespace handler for the Sns namespace
 *
 * @author Sayantam Dey
 * @since 1.0
 *
 */
public class SnsNamespaceHandler extends AbstractIntegrationNamespaceHandler {

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.beans.factory.xml.NamespaceHandler#init()
	 */
	@Override
	public void init() {
		this.registerBeanDefinitionParser("inbound-channel-adapter",
				new SnsInboundChannelAdapterParser());
		this.registerBeanDefinitionParser("outbound-channel-adapter",
				new SnsOutboundChannelAdapterParser());
		this.registerBeanDefinitionParser("outbound-gateway",
				new SnsOutboundGatewayParser());
		this.registerBeanDefinitionParser("publish-subscribe-channel",
				new SnsPublishSubscribeChannelParser());
	}
}


package org.springframework.integration.aws.sqs.config;

import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler;

/**
 * The namespace handler for the Sqs namespace
 *
 * @author Sayantam Dey
 * @since 1.0
 *
 */
public class SqsNamespaceHandler extends AbstractIntegrationNamespaceHandler {

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.beans.factory.xml.NamespaceHandler#init()
	 */
	@Override
	public void init() {
		this.registerBeanDefinitionParser("inbound-channel-adapter",
				new SqsInboundChannelAdapterParser());
		this.registerBeanDefinitionParser("outbound-channel-adapter",
				new SqsOutboundChannelAdapterParser());
		this.registerBeanDefinitionParser("outbound-gateway",
				new SqsOutboundGatewayParser());
		this.registerBeanDefinitionParser("channel", new SqsChannelParser());
	}
}


package org.springframework.integration.aws.sqs.config;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.aws.sqs.SqsExecutorProxy;
import org.springframework.integration.aws.sqs.core.SqsExecutor;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.Assert;
import org.w3c.dom.Element;

/**
 * Contains various utility methods for parsing Sqs Adapter specific namesspace
 * elements as well as for the generation of the the respective
 * {@link BeanDefinition}s.
 *
 * @author Sayantam Dey
 * @since 1.0
 *
 */
public final class SqsParserUtils {

	/** Prevent instantiation. */
	private SqsParserUtils() {
		throw new AssertionError();
	}

	/**
	 * Create a new {@link BeanDefinitionBuilder} for the class
	 * {@link SqsExecutor}. Initialize the wrapped {@link SqsExecutor} with
	 * common properties.
	 *
	 * @param element
	 *            Must not be null
	 * @param parserContext
	 *            Must not be null
	 * @return The BeanDefinitionBuilder for the SqsExecutor
	 */
	public static BeanDefinitionBuilder getSqsExecutorBuilder(
			final Element element, final ParserContext parserContext) {

		Assert.notNull(element, "The provided element must not be null.");
		Assert.notNull(parserContext,
				"The provided parserContext must not be null.");

		final BeanDefinitionBuilder sqsExecutorBuilder = BeanDefinitionBuilder
				.genericBeanDefinition(SqsExecutor.class);

		IntegrationNamespaceUtils.setValueIfAttributeDefined(
				sqsExecutorBuilder, element, "queue-name");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				sqsExecutorBuilder, element, "queue");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				sqsExecutorBuilder, element, "aws-credentials-provider");

		IntegrationNamespaceUtils.setValueIfAttributeDefined(
				sqsExecutorBuilder, element, "receive-message-wait-timeout");

		IntegrationNamespaceUtils.setValueIfAttributeDefined(
				sqsExecutorBuilder, element, "region-id");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				sqsExecutorBuilder, element, "prefetch-count");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				sqsExecutorBuilder, element, "message-delay");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				sqsExecutorBuilder, element, "maximum-message-size");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				sqsExecutorBuilder, element, "message-retention-period");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				sqsExecutorBuilder, element, "visibility-timeout");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				sqsExecutorBuilder, element, "aws-client-configuration");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				sqsExecutorBuilder, element, "message-marshaller");

		IntegrationNamespaceUtils.setValueIfAttributeDefined(
				sqsExecutorBuilder, element, "queue-url");

		return sqsExecutorBuilder;

	}

	public static String getExecutorBeanName(String channelId) {
		return String.format("%s.sqsExecutor", channelId);
	}

	public static void registerExecutorProxy(Element element,
			String sqsExecutorBeanName, ParserContext parserContext) {

		if (element.hasAttribute("sqs-executor-proxy")) {
			String sqsProxyBeanName = element
					.getAttribute("sqs-executor-proxy");
			BeanDefinitionBuilder sqsExecutorProxyBuilder = BeanDefinitionBuilder
					.genericBeanDefinition(SqsExecutorProxy.class);
			sqsExecutorProxyBuilder
					.addConstructorArgReference(sqsExecutorBeanName);
			parserContext.registerBeanComponent(new BeanComponentDefinition(
					sqsExecutorProxyBuilder.getBeanDefinition(),
					sqsProxyBeanName));
		}
	}

}


package org.springframework.integration.aws.sns.config;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.aws.sns.SnsExecutorProxy;
import org.springframework.integration.aws.sns.core.HttpEndpoint;
import org.springframework.integration.aws.sns.core.SnsExecutor;
import org.springframework.integration.aws.sqs.config.SqsParserUtils;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.Assert;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.amazonaws.services.sns.model.Subscription;

/**
 * Contains various utility methods for parsing Sns Adapter specific namespace
 * elements as well as for the generation of the the respective
 * {@link BeanDefinition}s.
 *
 * @author Sayantam Dey
 * @since 1.0
 *
 */
public final class SnsParserUtils {

	/** Prevent instantiation. */
	private SnsParserUtils() {
		throw new AssertionError();
	}

	/**
	 * Create a new {@link BeanDefinitionBuilder} for the class
	 * {@link SnsExecutor}. Initialize the wrapped {@link SnsExecutor} with
	 * common properties.
	 *
	 * @param element
	 *            Must not be null
	 * @param parserContext
	 *            Must not be null
	 * @return The BeanDefinitionBuilder for the SnsExecutor
	 */
	public static BeanDefinitionBuilder getSnsExecutorBuilder(
			final Element element, final ParserContext parserContext) {

		Assert.notNull(element, "The provided element must not be null.");
		Assert.notNull(parserContext,
				"The provided parserContext must not be null.");

		final BeanDefinitionBuilder snsExecutorBuilder = BeanDefinitionBuilder
				.genericBeanDefinition(SnsExecutor.class);

		IntegrationNamespaceUtils.setValueIfAttributeDefined(
				snsExecutorBuilder, element, "topic-name");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				snsExecutorBuilder, element, "sns-test-proxy");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				snsExecutorBuilder, element, "aws-credentials-provider");

		IntegrationNamespaceUtils.setValueIfAttributeDefined(
				snsExecutorBuilder, element, "region-id");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				snsExecutorBuilder, element, "aws-client-configuration");

		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(
				snsExecutorBuilder, element, "message-marshaller");

		IntegrationNamespaceUtils.setValueIfAttributeDefined(
				snsExecutorBuilder, element, "topic-arn");

		return snsExecutorBuilder;

	}

	public static void registerSubscriptions(final Element element,
			final ParserContext parserContext,
			final BeanDefinitionBuilder snsExecutorBuilder,
			final String channelAdapterId) {

		List<Subscription> subscriptionList = new ArrayList<Subscription>();
		Map<String, BeanReference> sqsExecutorMap = new ManagedMap<String, BeanReference>();

		// HTTP endpoint
		Element endpointElement = DomUtils.getChildElementByTagName(element,
				"endpoint");
		if (endpointElement != null) {
			String baseURI = endpointElement.getAttribute("base-uri");
			String requestPath = null;
			if (endpointElement.hasAttribute("request-path")) {
				requestPath = endpointElement.getAttribute("request-path");
			} else {
				requestPath = String.format("/%s.do", channelAdapterId);
			}
			subscriptionList.add(new Subscription().withEndpoint(
					baseURI.concat(requestPath)).withProtocol(
					baseURI.startsWith("https") ? "https" : "http"));

			// register a HttpEndpoint at this path
			BeanDefinitionBuilder httpEndpointBuilder = BeanDefinitionBuilder
					.genericBeanDefinition(HttpEndpoint.class);
			String beanName = String
					.format("%s-httpEndpoint", channelAdapterId);
			parserContext.registerBeanComponent(new BeanComponentDefinition(
					httpEndpointBuilder.getBeanDefinition(), beanName,
					new String[] { requestPath }));
			snsExecutorBuilder.addPropertyReference("httpEndpoint", beanName);
		}

		// subscriptions element
		Element subscriptionsElement = DomUtils.getChildElementByTagName(
				element, "subscriptions");
		if (subscriptionsElement != null) {
			NodeList childNodes = subscriptionsElement.getChildNodes();
			for (int i = 0; i < childNodes.getLength(); i++) {
				Node child = childNodes.item(i);
				if (child.getNodeType() == Node.ELEMENT_NODE) {
					Element childElement = (Element) child;
					String localName = child.getLocalName();
					if ("http".equals(localName)) {
						String uri = childElement.getAttribute("base-uri");
						if (childElement.hasAttribute("request-path")) {
							uri = uri.concat(childElement
									.getAttribute("request-path"));
						}
						subscriptionList.add(new Subscription().withEndpoint(
								uri).withProtocol(
								uri.startsWith("https") ? "https" : "http"));

					} else if ("sqs".equals(localName)) {
						String queueArn = null;
						if (childElement.hasAttribute("queue-arn")) {
							queueArn = childElement.getAttribute("queue-arn");
						}
						String queueId = null;
						if (childElement.hasAttribute("queue-id")) {
							queueId = childElement.getAttribute("queue-id");
						}
						Assert.state(queueArn != null || queueId != null,
								"One of 'queue-arn' or 'queue-id' needs to be defined");
						if (queueId != null) {
							String sqsBeanName = SqsParserUtils
									.getExecutorBeanName(queueId);
							snsExecutorBuilder.addDependsOn(sqsBeanName);
							sqsExecutorMap.put(queueId,
									new RuntimeBeanReference(sqsBeanName));
							subscriptionList.add(new Subscription()
									.withEndpoint(queueId).withProtocol("sqs"));

						} else {
							subscriptionList
									.add(new Subscription().withEndpoint(
											queueArn).withProtocol("sqs"));
						}
					}
				}
			}
			if (!sqsExecutorMap.isEmpty()) {
				snsExecutorBuilder.addPropertyValue("sqsExecutorMap",
						sqsExecutorMap);
			}
		}

		snsExecutorBuilder.addPropertyValue("subscriptionList",
				subscriptionList);

	}

	public static void registerExecutorProxy(Element element,
			String snsExecutorBeanName, ParserContext parserContext) {

		if (element.hasAttribute("sns-executor-proxy")) {
			String snsProxyBeanName = element
					.getAttribute("sns-executor-proxy");
			BeanDefinitionBuilder snsExecutorProxyBuilder = BeanDefinitionBuilder
					.genericBeanDefinition(SnsExecutorProxy.class);
			snsExecutorProxyBuilder
					.addConstructorArgReference(snsExecutorBeanName);
			parserContext.registerBeanComponent(new BeanComponentDefinition(
					snsExecutorProxyBuilder.getBeanDefinition(),
					snsProxyBeanName));
		}
	}

}


package org.springframework.integration.aws.sqs.core;

import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.aws.AwsUtil;
import org.springframework.integration.aws.JsonMessageMarshaller;
import org.springframework.integration.aws.MessageMarshaller;
import org.springframework.integration.aws.MessageMarshallerException;
import org.springframework.integration.aws.Permission;
import org.springframework.integration.aws.sqs.SqsHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.policy.Policy;
import com.amazonaws.auth.policy.Principal;
import com.amazonaws.auth.policy.Resource;
import com.amazonaws.auth.policy.Statement;
import com.amazonaws.auth.policy.Statement.Effect;
import com.amazonaws.auth.policy.actions.SQSActions;
import com.amazonaws.auth.policy.conditions.ArnCondition;
import com.amazonaws.auth.policy.conditions.ArnCondition.ArnComparisonType;
import com.amazonaws.auth.policy.conditions.ConditionFactory;
import com.amazonaws.auth.policy.internal.JsonPolicyWriter;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClient;
import com.amazonaws.services.sqs.model.AddPermissionRequest;
import com.amazonaws.services.sqs.model.CreateQueueRequest;
import com.amazonaws.services.sqs.model.CreateQueueResult;
import com.amazonaws.services.sqs.model.DeleteMessageRequest;
import com.amazonaws.services.sqs.model.GetQueueAttributesRequest;
import com.amazonaws.services.sqs.model.GetQueueAttributesResult;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import com.amazonaws.services.sqs.model.ReceiveMessageResult;
import com.amazonaws.services.sqs.model.SendMessageRequest;
import com.amazonaws.services.sqs.model.SendMessageResult;
import com.amazonaws.services.sqs.model.SetQueueAttributesRequest;
import com.amazonaws.util.Md5Utils;

/**
 * Bundles common core logic for the Sqs components.
 *
 * @author Sayantam Dey
 * @since 1.0
 *
 */
public class SqsExecutor implements InitializingBean, DisposableBean {

	private static final String SNS_MESSAGE_KEY = "Message";
	private static final int DEFAULT_RECV_MESG_WAIT = 20; // seconds
	private static final String QUEUE_ARN_KEY = "QueueArn";
	private static final int DEFAULT_MESSAGE_PREFETCH_COUNT = 10;

	private final Log log = LogFactory.getLog(SqsExecutor.class);

	private String queueName;
	private BlockingQueue<String> queue;
	private AWSCredentialsProvider awsCredentialsProvider;
	private AmazonSQS sqsClient;
	private String queueUrl;
	private String queueArn;
	private String regionId;
	private int receiveMessageWaitTimeout;
	private int prefetchCount;
	private final BlockingQueue<com.amazonaws.services.sqs.model.Message> prefetchQueue;
	private Integer messageDelay;
	private Integer maximumMessageSize;
	private Integer messageRetentionPeriod;
	private Integer visibilityTimeout;
	private ClientConfiguration awsClientConfiguration;
	private MessageMarshaller messageMarshaller;

	private volatile int destroyWaitTime;
	private Set<Permission> permissions;

	/**
	 * Constructor.
	 */
	public SqsExecutor() {
		this.receiveMessageWaitTimeout = DEFAULT_RECV_MESG_WAIT;
		this.destroyWaitTime = 0;
		this.prefetchCount = DEFAULT_MESSAGE_PREFETCH_COUNT;
		this.prefetchQueue = new LinkedBlockingQueue<com.amazonaws.services.sqs.model.Message>(
				prefetchCount);
	}

	/**
	 * Verifies and sets the parameters. E.g. initializes the to be used
	 */
	@Override
	public void afterPropertiesSet() {

		Assert.isTrue(this.queueName != null || this.queueUrl != null,
				"Either queueName or queueUrl must not be empty.");

		Assert.isTrue(queue != null || awsCredentialsProvider != null,
				"Either queue or awsCredentialsProvider needs to be provided");

		if (messageMarshaller == null) {
			messageMarshaller = new JsonMessageMarshaller();
		}

		if (queue == null) {
			if (sqsClient == null) {
				if (awsClientConfiguration == null) {
					sqsClient = new AmazonSQSClient(awsCredentialsProvider);
				} else {
					sqsClient = new AmazonSQSClient(awsCredentialsProvider,
							awsClientConfiguration);
				}
			}
			if (regionId != null) {
				sqsClient.setEndpoint(String.format("sqs.%s.amazonaws.com",
						regionId));
			}
			if (queueName != null) {
				createQueueIfNotExists();
			}

			addPermissions();
		}
	}

	private void createQueueIfNotExists() {
		for (String qUrl : sqsClient.listQueues().getQueueUrls()) {
			if (qUrl.contains(queueName)) {
				queueUrl = qUrl;
				break;
			}
		}
		if (queueUrl == null) {
			CreateQueueRequest request = new CreateQueueRequest(queueName);
			Map<String, String> queueAttributes = new HashMap<String, String>();
			queueAttributes.put("ReceiveMessageWaitTimeSeconds", Integer
					.valueOf(receiveMessageWaitTimeout).toString());
			if (messageDelay != null) {
				queueAttributes.put("DelaySeconds", messageDelay.toString());
			}
			if (maximumMessageSize != null) {
				queueAttributes.put("MaximumMessageSize",
						maximumMessageSize.toString());
			}
			if (messageRetentionPeriod != null) {
				queueAttributes.put("MessageRetentionPeriod",
						messageRetentionPeriod.toString());
			}
			if (visibilityTimeout != null) {
				queueAttributes.put("VisibilityTimeout",
						visibilityTimeout.toString());
			}
			request.setAttributes(queueAttributes);
			CreateQueueResult result = sqsClient.createQueue(request);
			queueUrl = result.getQueueUrl();
			log.debug("New queue available at: " + queueUrl);
		} else {
			log.debug("Queue already exists: " + queueUrl);
		}

		resolveQueueArn();
	}

	private void resolveQueueArn() {
		GetQueueAttributesRequest request = new GetQueueAttributesRequest(
				queueUrl);
		GetQueueAttributesResult result = sqsClient.getQueueAttributes(request
				.withAttributeNames(Collections.singletonList(QUEUE_ARN_KEY)));
		queueArn = result.getAttributes().get(QUEUE_ARN_KEY);
	}

	private void addPermissions() {
		if (permissions != null && permissions.isEmpty() == false) {
			GetQueueAttributesResult result = sqsClient
					.getQueueAttributes(new GetQueueAttributesRequest(queueUrl,
							Arrays.asList("Policy")));

			AwsUtil.addPermissions(result.getAttributes(), permissions,
					new AwsUtil.AddPermissionHandler() {

						@Override
						public void execute(Permission p) {
							sqsClient.addPermission(new AddPermissionRequest()
									.withQueueUrl(queueUrl)
									.withLabel(p.getLabel())
									.withAWSAccountIds(p.getAwsAccountIds())
									.withActions(p.getActions()));
						}
					});
		}
	}

	/**
	 * Executes the outbound Sqs Operation.
	 *
	 */
	public Object executeOutboundOperation(final Message<?> message) {

		try {
			String serializedMessage = messageMarshaller.serialize(message);
			if (queue == null) {
				SendMessageRequest request = new SendMessageRequest(queueUrl,
						serializedMessage);
				SendMessageResult result = sqsClient.sendMessage(request);
				log.debug("Message sent, Id:" + result.getMessageId());
			} else {
				queue.add(serializedMessage);
			}
		} catch (MessageMarshallerException e) {
			log.error(e.getMessage(), e);
			throw new MessagingException(e.getMessage(), e.getCause());
		}

		return message.getPayload();
	}

	/**
	 * Execute the Sqs operation. Delegates to {@link SqsExecutor#poll(Message)}
	 * .
	 */
	public Message<?> poll() {
		return poll(0);
	}

	/**
	 * Execute a retrieving (polling) Sqs operation.
	 *
	 * @param timeout
	 *            time to wait for a message to return.
	 *
	 * @return The payload object, which may be null.
	 */
	public Message<?> poll(long timeout) {

		Message<?> message = null;
		String payloadJSON = null;
		com.amazonaws.services.sqs.model.Message qMessage = null;
		int timeoutSeconds = (timeout > 0 ? ((int) (timeout / 1000))
				: receiveMessageWaitTimeout);
		destroyWaitTime = timeoutSeconds;
		try {
			if (queue == null) {
				if (prefetchQueue.isEmpty()) {
					ReceiveMessageRequest request = new ReceiveMessageRequest(
							queueUrl).withWaitTimeSeconds(timeoutSeconds)
							.withMaxNumberOfMessages(prefetchCount)
							.withAttributeNames("All");

					ReceiveMessageResult result = sqsClient
							.receiveMessage(request);
					for (com.amazonaws.services.sqs.model.Message sqsMessage : result
							.getMessages()) {
						prefetchQueue.offer(sqsMessage);
					}
					qMessage = prefetchQueue.poll();
				} else {
					qMessage = prefetchQueue.remove();
				}
				if (qMessage != null) {
					payloadJSON = qMessage.getBody();
					// MD5 verification
					try {
						byte[] computedHash = Md5Utils
								.computeMD5Hash(payloadJSON.getBytes("UTF-8"));
						String hexDigest = new String(
								Hex.encodeHex(computedHash));
						if (!hexDigest.equals(qMessage.getMD5OfBody())) {
							payloadJSON = null; // ignore this message
							log.warn("Dropped message due to MD5 checksum failure");
						}
					} catch (Exception e) {
						log.warn(
								"Failed to verify MD5 checksum: "
										+ e.getMessage(), e);
					}
				}
			} else {
				try {
					payloadJSON = queue.poll(timeoutSeconds, TimeUnit.SECONDS);
				} catch (InterruptedException e) {
					log.warn(e.getMessage(), e);
				}
			}
			if (payloadJSON != null) {
				JSONObject qMessageJSON = new JSONObject(payloadJSON);
				if (qMessageJSON.has(SNS_MESSAGE_KEY)) { // posted from SNS
					payloadJSON = qMessageJSON.getString(SNS_MESSAGE_KEY);
					// XXX: other SNS attributes?
				}
				Message<?> packet = null;
				try {
					packet = messageMarshaller.deserialize(payloadJSON);
				} catch (MessageMarshallerException marshallingException) {
					throw new MessagingException(
							marshallingException.getMessage(),
							marshallingException.getCause());
				}
				MessageBuilder<?> builder = MessageBuilder.fromMessage(packet);
				if (qMessage != null) {
					builder.setHeader(SqsHeaders.MSG_RECEIPT_HANDLE,
							qMessage.getReceiptHandle());
					builder.setHeader(SqsHeaders.AWS_MESSAGE_ID,
							qMessage.getMessageId());
					for (Map.Entry<String, String> e : qMessage.getAttributes()
							.entrySet()) {
						if (e.getKey().equals("ApproximateReceiveCount")) {
							builder.setHeader(SqsHeaders.RECEIVE_COUNT,
									Integer.valueOf(e.getValue()));
						} else if (e.getKey().equals("SentTimestamp")) {
							builder.setHeader(SqsHeaders.SENT_AT,
									new Date(Long.valueOf(e.getValue())));
						} else if (e.getKey().equals(
								"ApproximateFirstReceiveTimestamp")) {
							builder.setHeader(SqsHeaders.FIRST_RECEIVED_AT,
									new Date(Long.valueOf(e.getValue())));
						} else if (e.getKey().equals("SenderId")) {
							builder.setHeader(SqsHeaders.SENDER_AWS_ID,
									e.getValue());
						} else {
							builder.setHeader(e.getKey(), e.getValue());
						}
					}
				} else {
					builder.setHeader(SqsHeaders.MSG_RECEIPT_HANDLE, "");
					// to satisfy test conditions
				}

				message = builder.build();
			}

		} catch (JSONException e) {
			log.warn(e.getMessage(), e);

		} finally {
			destroyWaitTime = 0;
		}

		return message;
	}

	public String acknowlegdeReceipt(Message<?> message) {
		String receiptHandle = (String) message.getHeaders().get(
				SqsHeaders.MSG_RECEIPT_HANDLE);
		if (sqsClient != null && receiptHandle != null
				&& !receiptHandle.isEmpty()) {
			sqsClient.deleteMessage(new DeleteMessageRequest(queueUrl,
					receiptHandle));
		}

		return receiptHandle;
	}

	public String getQueueArn() {
		if (queueArn == null) {
			resolveQueueArn();
		}
		return queueArn;
	}

	public String getQueueUrl() {
		return queueUrl;
	}

	/**
	 * Example property to illustrate usage of properties in Spring Integration
	 * components. Replace with your own logic.
	 *
	 * @param queueName
	 *            Must not be null
	 */
	public void setQueueName(String queueName) {
		Assert.hasText(queueName, "queueName must be neither null nor empty");
		this.queueName = queueName;
	}

	/**
	 * Set the queue implementation. Useful for testing the queue without
	 * actually invoking AWS.
	 *
	 * @param queue
	 */
	public void setQueue(BlockingQueue<String> queue) {
		this.queue = queue;
	}

	/**
	 * Sets the AWS client configuration.
	 *
	 * @param awsClientConfiguration
	 */
	public void setAwsClientConfiguration(
			ClientConfiguration awsClientConfiguration) {
		this.awsClientConfiguration = awsClientConfiguration;
	}

	/**
	 * Sets the AWS credentials provider.
	 *
	 * @param awsCredentialsProvider
	 */
	public void setAwsCredentialsProvider(
			AWSCredentialsProvider awsCredentialsProvider) {
		this.awsCredentialsProvider = awsCredentialsProvider;
	}

	public int getReceiveMessageWaitTimeout() {
		return receiveMessageWaitTimeout;
	}

	/**
	 * Sets the timeout (in seconds) for a receive message operation, defaults
	 * to {@value #DEFAULT_RECV_MESG_WAIT} seconds.
	 *
	 * @param receiveMessageWaitTimeout
	 */
	public void setReceiveMessageWaitTimeout(int receiveMessageWaitTimeout) {
		Assert.isTrue(receiveMessageWaitTimeout >= 0
				&& receiveMessageWaitTimeout <= 20,
				"'receiveMessageWaitTimeout' must be an integer from 0 to 20 (seconds).");
		this.receiveMessageWaitTimeout = receiveMessageWaitTimeout;
	}

	/**
	 * Sets the AWS region ID, defaults to us-east.
	 *
	 * @param regionId
	 */
	public void setRegionId(String regionId) {
		this.regionId = regionId;
	}

	/**
	 * Sets the number of messages to prefetch, defaults to
	 * {@value #DEFAULT_MESSAGE_PREFETCH_COUNT}.
	 *
	 * @param prefetchCount
	 */
	public void setPrefetchCount(int prefetchCount) {
		Assert.isTrue(prefetchCount >= 0 && prefetchCount <= 10,
				"'prefetchCount' must be an integer from 0 to 10.");
		this.prefetchCount = prefetchCount;
	}

	/**
	 * Sets the message delivery delay from SQS. By default there is no delay.
	 *
	 * @param messageDelay
	 */
	public void setMessageDelay(Integer messageDelay) {
		Assert.isTrue(messageDelay >= 0 && messageDelay <= 900,
				"'messageDelay' must be an integer from 0 to 900 (15 minutes).");
		this.messageDelay = messageDelay;
	}

	/**
	 * Sets the maximum message size.
	 *
	 * @param maximumMessageSize
	 */
	public void setMaximumMessageSize(Integer maximumMessageSize) {
		Assert.isTrue(
				maximumMessageSize >= 1024 && maximumMessageSize <= 65536,
				"'maximumMessageSize' must be an integer from 1024 bytes (1 KiB) up to 65536 bytes (64 KiB).");
		this.maximumMessageSize = maximumMessageSize;
	}

	/**
	 * Sets the message retention period at SQS. Messages older than this will
	 * be automatically be dropped by SQS.
	 *
	 * @param messageRetentionPeriod
	 */
	public void setMessageRetentionPeriod(Integer messageRetentionPeriod) {
		Assert.isTrue(
				messageRetentionPeriod >= 60
						&& messageRetentionPeriod <= 1209600,
				"'messageRetentionPeriod' must be an integer representing seconds, from 60 (1 minute) to 1209600 (14 days)");
		this.messageRetentionPeriod = messageRetentionPeriod;
	}

	/**
	 * Sets the visibility timeout in seconds. SQS must receive an
	 * acknowledgment before this timeout occurs or else the message is
	 * re-delivered.
	 *
	 * @param visibilityTimeout
	 */
	public void setVisibilityTimeout(Integer visibilityTimeout) {
		Assert.isTrue(
				visibilityTimeout >= 0 && visibilityTimeout <= 43200,
				"'visibilityTimeout' must be an integer representing seconds, from 0 to 43200 (12 hours)");
		this.visibilityTimeout = visibilityTimeout;
	}

	@Override
	public void destroy() throws Exception {
		if (sqsClient != null) {
			if (destroyWaitTime > 0) {
				Thread.sleep(destroyWaitTime * 1000);
			}
			sqsClient.shutdown();
		}

	}

	public void addSnsPublishPolicy(String topicName, String topicArn) {

		if (queueArn == null) {
			resolveQueueArn();
		}

		String publishPolicyKey = String.format("SNS-%s-SQS-%s", topicName,
				queueName);
		String policyId = null;
		GetQueueAttributesRequest getAttrRequest = new GetQueueAttributesRequest(
				queueUrl);
		getAttrRequest.setAttributeNames(Collections.singletonList("Policy"));
		GetQueueAttributesResult result = sqsClient
				.getQueueAttributes(getAttrRequest);
		Map<String, String> attributes = result.getAttributes();
		String policyStr = attributes.get("Policy");
		log.debug("Policy:" + policyStr);
		if (policyStr != null) {
			try {
				JSONObject policyJSON = new JSONObject(policyStr);
				policyId = policyJSON.getString("Id");
			} catch (JSONException e) {
				log.error(e.getMessage(), e);
			}
		}

		if (policyId == null || !policyId.equals(publishPolicyKey)) {
			Statement statement = new Statement(Effect.Allow);
			statement
					.withActions(SQSActions.SendMessage)
					.withPrincipals(Principal.AllUsers)
					.withResources(new Resource(queueArn))
					.withConditions(
							new ArnCondition(ArnComparisonType.ArnEquals,
									ConditionFactory.SOURCE_ARN_CONDITION_KEY,
									topicArn));
			Policy policy = new Policy();
			policy.setId(publishPolicyKey);
			policy.setStatements(Collections.singletonList(statement));

			SetQueueAttributesRequest request = new SetQueueAttributesRequest();
			request.setQueueUrl(queueUrl);
			String policyJSON = (new JsonPolicyWriter())
					.writePolicyToString(policy);
			log.debug(policyJSON);
			request.setAttributes(Collections
					.singletonMap("Policy", policyJSON));

			sqsClient.setQueueAttributes(request);
		}
	}

	public void setSqsClient(AmazonSQS sqsClient) {
		this.sqsClient = sqsClient;
	}

	public void setMessageMarshaller(MessageMarshaller messageMarshaller) {
		this.messageMarshaller = messageMarshaller;
	}

	public void setPermissions(Set<Permission> permissions) {
		this.permissions = permissions;
	}

	public void setQueueUrl(String queueUrl) {
		this.queueUrl = queueUrl;
	}

}


package org.springframework.integration.aws.sns.core;

import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.aws.AwsUtil;
import org.springframework.integration.aws.JsonMessageMarshaller;
import org.springframework.integration.aws.MessageMarshaller;
import org.springframework.integration.aws.MessageMarshallerException;
import org.springframework.integration.aws.Permission;
import org.springframework.integration.aws.sns.support.SnsTestProxy;
import org.springframework.integration.aws.sqs.core.SqsExecutor;
import org.springframework.util.Assert;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.sns.AmazonSNSClient;
import com.amazonaws.services.sns.model.AddPermissionRequest;
import com.amazonaws.services.sns.model.CreateTopicRequest;
import com.amazonaws.services.sns.model.CreateTopicResult;
import com.amazonaws.services.sns.model.GetTopicAttributesResult;
import com.amazonaws.services.sns.model.PublishRequest;
import com.amazonaws.services.sns.model.PublishResult;
import com.amazonaws.services.sns.model.SubscribeRequest;
import com.amazonaws.services.sns.model.SubscribeResult;
import com.amazonaws.services.sns.model.Subscription;
import com.amazonaws.services.sns.model.Topic;

/**
 * Bundles common core logic for the Sns components.
 *
 * @author Sayantam Dey
 * @since 1.0
 *
 */
public class SnsExecutor implements InitializingBean, DisposableBean {

	private final Log log = LogFactory.getLog(SnsExecutor.class);

	private String topicName;
	private SnsTestProxy snsTestProxy;
	private AWSCredentialsProvider awsCredentialsProvider;
	private AmazonSNSClient client;
	private String regionId;
	private String topicArn;
	private HttpEndpoint httpEndpoint;
	private List<Subscription> subscriptionList;
	private Map<String, SqsExecutor> sqsExecutorMap;
	private ClientConfiguration awsClientConfiguration;
	private MessageMarshaller messageMarshaller;
	private Set<Permission> permissions;

	/**
	 * Verifies and sets the parameters. E.g. initializes the to be used
	 */
	@Override
	public void afterPropertiesSet() {

		Assert.isTrue(this.topicName != null || this.topicArn != null,
				"Either topicName or topicArn must not be empty.");

		Assert.isTrue(snsTestProxy != null || awsCredentialsProvider != null,
				"Either snsTestProxy or awsCredentialsProvider needs to be provided");

		if (messageMarshaller == null) {
			messageMarshaller = new JsonMessageMarshaller();
		}

		if (snsTestProxy == null) {
			if (awsClientConfiguration == null) {
				client = new AmazonSNSClient(awsCredentialsProvider);
			} else {
				client = new AmazonSNSClient(awsCredentialsProvider,
						awsClientConfiguration);
			}
			if (regionId != null) {
				client.setEndpoint(String.format("sns.%s.amazonaws.com",
						regionId));
			}
			if (topicArn == null) {
				createTopicIfNotExists();
			}
			processSubscriptions();
			addPermissions();
		}
	}

	private void createTopicIfNotExists() {
		for (Topic topic : client.listTopics().getTopics()) {
			if (topic.getTopicArn().contains(topicName)) {
				topicArn = topic.getTopicArn();
				break;
			}
		}
		if (topicArn == null) {
			CreateTopicRequest request = new CreateTopicRequest(topicName);
			CreateTopicResult result = client.createTopic(request);
			topicArn = result.getTopicArn();
			log.debug("Topic created, arn: " + topicArn);
		} else {
			log.debug("Topic already created: " + topicArn);
		}
	}

	private void processSubscriptions() {
		if (subscriptionList != null && !subscriptionList.isEmpty()) {
			for (Subscription subscription : subscriptionList) {
				if (subscription.getProtocol().startsWith("http")) {
					processUrlSubscription(subscription);
				} else {
					// sqs subscription
					processSqsSubscription(subscription);
				}
			}
		}
	}

	private void processUrlSubscription(Subscription urlSubscription) {

		String snsUrlSubscriptionArn = null;
		for (Subscription subscription : client.listSubscriptions()
				.getSubscriptions()) {
			if (subscription.getTopicArn().equals(topicArn)
					&& subscription.getProtocol().equals(
							urlSubscription.getProtocol())
					&& subscription.getEndpoint().contains(
							urlSubscription.getEndpoint())) {
				if (!subscription.getSubscriptionArn().equals(
						"PendingConfirmation")) {
					snsUrlSubscriptionArn = subscription.getSubscriptionArn();
					break;
				}
			}
		}
		if (snsUrlSubscriptionArn == null) {

			SubscribeRequest request = new SubscribeRequest(topicArn,
					urlSubscription.getProtocol(),
					urlSubscription.getEndpoint());
			SubscribeResult result = client.subscribe(request);
			snsUrlSubscriptionArn = result.getSubscriptionArn();
			log.info("Subscribed URL to SNS with subscription ARN: "
					+ snsUrlSubscriptionArn);
		} else {
			log.info("Already subscribed with ARN: " + snsUrlSubscriptionArn);
		}
	}

	private void processSqsSubscription(Subscription sqsSubscription) {
		Assert.state(sqsExecutorMap != null,
				"'sqsExecutorMap' must not be null");

		SqsExecutor sqsExecutor = null;
		String endpointValue = sqsSubscription.getEndpoint();
		if (sqsExecutorMap.containsKey(endpointValue)) {
			sqsExecutor = sqsExecutorMap.get(endpointValue);
			sqsSubscription.setEndpoint(sqsExecutor.getQueueArn());
		} else {
			// endpointValue is the queue-arn
			sqsSubscription.setEndpoint(endpointValue);
		}

		String snsSqsSubscriptionArn = null;
		for (Subscription subscription : client.listSubscriptions()
				.getSubscriptions()) {
			if (subscription.getTopicArn().equals(topicArn)
					&& subscription.getProtocol().equals(
							sqsSubscription.getProtocol())
					&& subscription.getEndpoint().equals(
							sqsSubscription.getEndpoint())) {
				snsSqsSubscriptionArn = subscription.getSubscriptionArn();
				break;
			}
		}
		if (snsSqsSubscriptionArn == null) {
			SubscribeRequest request = new SubscribeRequest(topicArn,
					sqsSubscription.getProtocol(),
					sqsSubscription.getEndpoint());
			SubscribeResult result = client.subscribe(request);
			snsSqsSubscriptionArn = result.getSubscriptionArn();
			log.info("Subscribed SQS to SNS with subscription ARN: "
					+ snsSqsSubscriptionArn);
		} else {
			log.info("Already subscribed with ARN: " + snsSqsSubscriptionArn);
		}
		if (sqsExecutor != null) {
			sqsExecutor.addSnsPublishPolicy(topicName, topicArn);
		}
	}

	private void addPermissions() {
		if (permissions != null && permissions.isEmpty() == false) {
			GetTopicAttributesResult result = client
					.getTopicAttributes(topicArn);

			AwsUtil.addPermissions(result.getAttributes(), permissions,
					new AwsUtil.AddPermissionHandler() {

						@Override
						public void execute(Permission p) {
							client.addPermission(new AddPermissionRequest()
									.withTopicArn(topicArn)
									.withLabel(p.getLabel())
									.withAWSAccountIds(p.getAwsAccountIds())
									.withActionNames(p.getActions()));
						}
					});
		}
	}

	/**
	 * Executes the outbound Sns Operation.
	 *
	 */
	public Object executeOutboundOperation(final Message<?> message) {

		try {
			String serializedMessage = messageMarshaller.serialize(message);

			if (snsTestProxy == null) {
				PublishRequest request = new PublishRequest();
				PublishResult result = client.publish(request.withTopicArn(
						topicArn).withMessage(serializedMessage));
				log.debug("Published message to topic: "
						+ result.getMessageId());
			} else {
				snsTestProxy.dispatchMessage(serializedMessage);
			}

		} catch (MessageMarshallerException e) {
			log.error(e.getMessage(), e);
			throw new MessagingException(e.getMessage(), e.getCause());
		}

		return message.getPayload();
	}

	public void registerHandler(NotificationHandler notificationHandler) {

		Assert.notNull(httpEndpoint, "'httpEndpoint' must not be null");
		Assert.notNull(notificationHandler,
				"'notificationHandler' must not be null");

		notificationHandler.setMessageMarshaller(messageMarshaller);
		httpEndpoint.setNotificationHandler(notificationHandler);
		if (snsTestProxy != null) {
			snsTestProxy.setHttpEndpoint(httpEndpoint);
			httpEndpoint.setPassThru(true);
		}
	}

	public String getTopicArn() {
		return topicArn;
	}

	/**
	 * Sets the topic ARN. Must not be empty.
	 *
	 * @param topicArn
	 */
	public void setTopicArn(String topicArn) {
		this.topicArn = topicArn;
	}

	/**
	 * Sets topic name
	 *
	 * @param topicName
	 *            Must not be null
	 */
	public void setTopicName(String topicName) {
		this.topicName = topicName;
	}

	/**
	 * Sets the SnsTestProxy instance for testing without actual AWS.
	 *
	 * @param snsTestProxy
	 */
	public void setSnsTestProxy(SnsTestProxy snsTestProxy) {
		this.snsTestProxy = snsTestProxy;
	}

	/**
	 * Sets the AWS credentials provider.
	 *
	 * @param awsCredentialsProvider
	 */
	public void setAwsCredentialsProvider(
			AWSCredentialsProvider awsCredentialsProvider) {
		this.awsCredentialsProvider = awsCredentialsProvider;
	}

	/**
	 * Sets the AWS client configuration.
	 *
	 * @param awsClientConfiguration
	 */
	public void setAwsClientConfiguration(
			ClientConfiguration awsClientConfiguration) {
		this.awsClientConfiguration = awsClientConfiguration;
	}

	/**
	 * Sets the AWS region ID.
	 *
	 * @param regionId
	 */
	public void setRegionId(String regionId) {
		this.regionId = regionId;
	}

	public void setHttpEndpoint(HttpEndpoint httpEndpoint) {
		this.httpEndpoint = httpEndpoint;
	}

	public void setSubscriptionList(List<Subscription> subscriptionList) {
		this.subscriptionList = subscriptionList;
	}

	public void setSqsExecutorMap(Map<String, SqsExecutor> sqsExecutorMap) {
		this.sqsExecutorMap = sqsExecutorMap;
	}

	public void setMessageMarshaller(MessageMarshaller messageMarshaller) {
		this.messageMarshaller = messageMarshaller;
	}

	/**
	 * Sets the permissions to be applied to the SNS topic.
	 *
	 * @param permissions
	 */
	public void setPermissions(Set<Permission> permissions) {
		this.permissions = permissions;
	}

	@Override
	public void destroy() throws Exception {
		if (client != null) {
			client.shutdown();
		}
	}

}


package com.fizzed.jne;

/*
 * #%L
 * jne
 * %%
 * Copyright (C) 2015 Fizzed, Inc
 * %%
 * 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.
 * #L%
 */

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Iterator;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

/**
 * Utilities for working with jar files.
 *
 * @author joelauer
 */
public class JarUtil {

    static public File getJarFileForResource(URL resource) throws IOException {
        if (!resource.getProtocol().equalsIgnoreCase("jar")) {
            throw new IOException("Resource protocol was not jar");
        }

        // e.g. file:/home/joelauer/.m2/repository/com/mfizz/mfz-jne-cat/1.0.0-SNAPSHOT/mfz-jne-cat-1.0.0-SNAPSHOT.jar!/jne/linux/x64/cat
        String file = resource.getFile();
        int exclaimPos = file.lastIndexOf("!");
        if (exclaimPos < 0) {
            throw new IOException("Missing ! char (invalid jar resource)");
        }

        String tempJarFile = file.substring(0, exclaimPos);
        //System.out.println("tempJarFile: " + tempJarFile);

        try {
            File jarFile = new File(new URL(tempJarFile).toURI());
            //System.out.println("jarFile: " + jarFile);
            //System.out.println("exists? " + jarFile.exists());
            return jarFile;
        } catch (MalformedURLException e) {
            throw new IOException("Unable to create uri for jar file", e);
        } catch (URISyntaxException e) {
            throw new IOException("Unable to create uri for jar file", e);
        }
    }

    // e.g. /drivers/h2/h2-1.3.162.jar
    static public String getManifestVersionNumber(File file) throws IOException {
        JarFile jar = new JarFile(file);
        Manifest manifest = jar.getManifest();
        String versionNumber = null;
        java.util.jar.Attributes attributes = manifest.getMainAttributes();
        if (attributes!=null) {
            Iterator it = attributes.keySet().iterator();
            while (it.hasNext()) {
                Attributes.Name key = (Attributes.Name)it.next();
                String keyword = key.toString();
                if (keyword.equals("Implementation-Version") || keyword.equals("Bundle-Version")){
                    versionNumber = (String)attributes.get(key);
                    break;
                }
            }
        }
        jar.close();

        if (versionNumber == null || versionNumber.equals("")) {
            return null;
        }

        return versionNumber;
    }

    /**
    static public String getFileVersionNumber(File file) {
        String versionNumber = null;
        String fileNameNoExt = file.getName().substring(0, file.getName().lastIndexOf("."));
        if (fileName.contains(".")) {
            // version will be from last "-" to end
            int delimiter = fileName.lastIndexOf("-");

            String majorVersion = fileName.substring(0, fileName.indexOf("."));
            String minorVersion = fileName.substring(fileName.indexOf("."));

            if (majorVersion.indexOf("_")>delimiter) delimiter = majorVersion.indexOf("_");
            majorVersion = majorVersion.substring(delimiter+1, fileName.indexOf("."));
            versionNumber = majorVersion + minorVersion;
        }
        System.out.println("Version: " + versionNumber); //"Version: 1.3.162"
    }
    */

}