public class JPEParserManager {

	private static JPEParserManager instance;
	private HashMap<String, JPEParser> registeredFormats = new HashMap<String, JPEParser>();

	/**
	 * A singleton
	 * 
	 * @return The {@link JPEParserManager} instance
	 */
	public static JPEParserManager getInstance() {
		if (instance == null) {
			instance = new JPEParserManager();
		}

		return instance;
	}

	/**
	 * Registers a {@link JPEParser}
	 * 
	 * @param parser
	 *            The {@link JPEParser}
	 */
	public void registerJPEParser(JPEParser parser) {
		this.registeredFormats.put(parser.getFormat(), parser);
	}

	/**
	 * Gets a {@link JPEParser} which {@link JPEParser#getFormat()} is equals to
	 * the format
	 * 
	 * @param format
	 *            The format to compare with {@link JPEParser#FORMAT_JSON} or
	 *            {@link JPEParser#FORMAT_XML}
	 * @return The {@link JPEParser}
	 * @throws NoParserRegisteredException
	 *             if no {@link JPEParser} with the format specified is
	 *             registerd into the {@link JPEParserManager}
	 */
	public JPEParser getJPEParser(String format)
			throws NoParserRegisteredException {
		JPEParser parser = this.registeredFormats.get(format);

		if (parser == null) {
			throw new NoParserRegisteredException(
					"No parser registered for format: " + format);
		}

		try {
			return parser.getClass().newInstance();
		} catch (InstantiationException e) {
			throw new NoParserRegisteredException(
					"No parser registered for format: " + format);
		} catch (IllegalAccessException e) {
			throw new NoParserRegisteredException(
					"No parser registered for format: " + format);
		}
	}

} public class CSVParser extends DefaultJPEParser {

	private final static Logger log = LoggerFactory.getLogger(CSVParser.class);

	@Override
	public ArrayList<JTSFeature> parse(String contentFile,
			DescribeService service, LocalFilter filter) {

		CsvReader csvReader = createReader(contentFile, service);
		boolean hasPassedFilter = false;
		try {
			csvReader.readHeaders();
			setCurrentFeatureType(service);
			fc = (ArrayList<JTSFeature>) contentHandler
					.startFeatureCollection();
			JTSFeature feature;
			Point point;
			String element;
			while (csvReader.readRecord()) {
				hasPassedFilter = false;
				feature = (JTSFeature) contentHandler.startFeature();
				point = (Point) contentHandler.startPoint();

				for (String destProp : this.currentFeatureType.getElements()
						.keySet()) {
					element = csvReader.get(this.currentFeatureType
							.getElements().get(destProp).getInput());
					if (filter == null) {
						hasPassedFilter = true;
					} else {
						hasPassedFilter = hasPassedFilter
								|| filter.apply(element);
					}

					addAttribute(service.encode(element), feature, destProp);
				}

				try {
					if (this.currentFeatureType.getCombinedLonLat() != null) {
						String[] latLon = csvReader.get(
								this.currentFeatureType.getCombinedLonLat())
								.split(this.currentFeatureType
										.getLonLatSeparator());

						int lonPos = 0;
						int latPos = 1;

						if (this.currentFeatureType.getReverseLonLat()) {
							lonPos = 1;
							latPos = 0;
						}

						setLatitude(
								Utils.formatNumber(latLon[latPos],
										service.getDecimalSeparator(),
										service.getNumberSeparator()), point);
						setLongitude(
								Utils.formatNumber(latLon[lonPos],
										service.getDecimalSeparator(),
										service.getNumberSeparator()), point);
					} else {
						setLatitude(
								Utils.formatNumber(csvReader
										.get(this.currentFeatureType.getLat()),
										service.getDecimalSeparator(), service
												.getNumberSeparator()), point);
						setLongitude(
								Utils.formatNumber(csvReader
										.get(this.currentFeatureType.getLon()),
										service.getDecimalSeparator(), service
												.getNumberSeparator()), point);
					}

					contentHandler.addGeometryToFeature(feature, contentHandler
							.endPoint(point, service.getSRS(),
									DescribeService.DEFAULT_SRS, null));

					if (hasPassedFilter) {
						fillCategories(feature, service);
						fillService(feature, service);
						contentHandler.addFeatureToCollection(fc, feature);
					}
				} catch (NumberFormatException e) {
					// Cuando los datos vienen mal
					log.warn("CSVParser", e);
				} catch (ArrayIndexOutOfBoundsException e) {
					// Cuando los datos vienen mal
					log.warn("CSVParser", e);
				}
			}

			return (ArrayList) fc;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	private CsvReader createReader(String contentFile, DescribeService service) {
		InputStream is = new ByteArrayInputStream(contentFile.getBytes());

		CsvReader csvReader = new CsvReader(is, service.getCsvSeparator()
				.toCharArray()[0], Charset.forName(service.getEncoding()));
		return csvReader;
	}

	@Override
	public String getFormat() {
		return JPEParserFormatEnum.CSV.format;
	}

} @SuppressWarnings("deprecation")
public class KMLParser extends DefaultJPEParser {

	@SuppressWarnings({ "deprecation" })
	@Override
	public ArrayList<JTSFeature> parse(String contentFile,
			DescribeService service, LocalFilter filter) {
		InputStream inputStream = new ByteArrayInputStream(
				contentFile.getBytes());

		fc = (ArrayList<JTSFeature>) contentHandler.startFeatureCollection();
		this.setCurrentFeatureType(service);

		JTSFeature feature;
		Point point;

		StreamingParser parser;

		try {
			KMLConfiguration configuration = new KMLConfiguration();
			parser = new StreamingParser(configuration, inputStream,
					KML.Placemark);

			@SuppressWarnings("unused")
			SimpleFeature f = null;
			boolean hasPassedFilter = false;
			String element;
			while ((f = (SimpleFeature) parser.parse()) != null) {
				hasPassedFilter = false;
				feature = (JTSFeature) contentHandler.startFeature();
				point = (Point) contentHandler.startPoint();

				for (String destProp : this.currentFeatureType.getElements()
						.keySet()) {
					element = this.currentFeatureType.getElements()
							.get(destProp).getInput();
					String attribute = (String) f.getAttribute(element);
					if (attribute == null) {
						Set<Entry<Object, Object>> data = f.getUserData()
								.entrySet();
						Iterator<Entry<Object, Object>> it = data.iterator();

						while (it.hasNext()) {
							try {
								Entry<Object, Object> extendedData = it.next();
								if (extendedData.getKey().equals(element)) {
									attribute = extendedData.getValue()
											.toString();
									break;
								} else {
									String[] parts = extendedData.getValue()
											.toString().split(",");
									for (String part : parts) {
										String[] aa = part.trim().split("=");
										if (aa != null && aa.length == 2) {
											if (aa[0].trim().indexOf(element) != -1) {
												attribute = aa[1].trim();
											}
										}
									}
								}
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
					}

					if (attribute != null) {
						try {
							if (filter == null) {
								hasPassedFilter = true;
							} else {
								hasPassedFilter = hasPassedFilter
										|| filter.apply(attribute);
							}
							addAttribute(service.encode(attribute), feature,
									destProp);
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
				Geometry geom = (Geometry) f.getDefaultGeometry();
				try {
					this.setLatitude(geom.getCentroid().getY(), point);
					this.setLongitude(geom.getCentroid().getX(), point);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				contentHandler.addGeometryToFeature(feature, contentHandler
						.endPoint(point, service.getSRS(),
								DescribeService.DEFAULT_SRS, null));
				if (hasPassedFilter) {
					fillCategories(feature, service);
					fillService(feature, service);
					contentHandler.addFeatureToCollection(fc, feature);
				}
			}
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return (ArrayList) fc;
	}

	@Override
	public String getFormat() {
		return JPEParserFormatEnum.KML.format;
	}
} public enum AuthTypeEnum {

	none("simple", null, null),

	twitter_oauth("twitter_oauth", TwitterApi.class, OauthRequestService.class),

	forceJson("forceJson", SimpleHttpRequestServiceForceJson.class, SimpleHttpRequestServiceForceJson.class);

	public String type;
	public Class<? extends Api> _class;
	public Class<? extends RequestService> _downloader;

	private AuthTypeEnum(String type, Class<? extends Api> _class, Class<? extends RequestService> _downloader) {
		this.type = type;
		this._class = _class;
		this._downloader = _downloader;
	}

	public boolean isOauth() {
		return type.indexOf("oauth") != -1;
	}
} @ApiModel(description = "The configuration of a registered POI service in POIProxy")
public class DescribeService {

	private final static Logger logger = LoggerFactory.getLogger(DescribeService.class);

	public static final String UNICODE = "unicode";
	public static final String STRIP_ACCENTS = "stripaccents";
	public static final String CATEGORY_SEPARATOR = ",";

	public final static String SEARCH_TYPE = "search";
	public final static String BROWSE_TYPE = "browse";
	public static final String DEFAULT_ENCODING = "UTF-8";
	public static final String DEFAULT_SRS = "EPSG:4326";

	public static final String TIMESTAMP = "timestamp";

	@JsonIgnore
	private String apiKey;
	private HashMap<String, RequestType> requestTypes = new HashMap<String, RequestType>();
	private HashMap<String, FeatureType> featureTypes = new HashMap<String, FeatureType>();
	private List<String> categories = new ArrayList<String>();

	@JsonIgnore
	private Auth auth = new Auth();

	private String format;
	private String dateFormat = TIMESTAMP;
	private String csvSeparator;
	private String encoding;
	private String outputEncoding = DescribeService.DEFAULT_ENCODING;
	private String numberSeparator;
	private String decimalSeparator;
	private String compression;
	private String contentFile;
	private boolean useLocalFilter = false;
	private boolean encodeUrl = false;
	private String SRS = DEFAULT_SRS;

	private String type = BROWSE_TYPE;

	private String id;

	private String fillEmptyAttributes;

	/**
	 * 
	 * @return The apiKey
	 */
	@JsonIgnore
	public String getApiKey() {
		return apiKey;
	}

	/**
	 * Sets the apiKey
	 * 
	 * @param apiKey
	 */
	@JsonProperty
	public void setApiKey(String apiKey) {
		this.apiKey = apiKey;
	}

	/**
	 * The current type selected to parse a file, one of {@link #SEARCH_TYPE} or
	 * {@link #BROWSE_TYPE}
	 * 
	 * @return
	 */
	public String getType() {
		return type;
	}

	/**
	 * The current type depending on the request made by {@link POIProxy} one of
	 * {@link #SEARCH_TYPE} or {@link #BROWSE_TYPE}
	 * 
	 * @param type
	 */
	public void setType(String type) {
		this.type = type;
	}

	/**
	 * The format of the source response of the service to parse.
	 * 
	 * @return {@link JPEParser#FORMAT_JSON} or {@link JPEParser#FORMAT_XML}
	 */
	public String getFormat() {
		return format;
	}

	/**
	 * Sets the format of the source response of the service to parse
	 * 
	 * @param format
	 *            {@link JPEParser#FORMAT_JSON} or {@link JPEParser#FORMAT_XML}
	 */
	public void setFormat(String format) {
		this.format = format;
	}

	/**
	 * A map of {@link #BROWSE_TYPE}, {@link #SEARCH_TYPE} with
	 * {@link RequestType}
	 * 
	 * @return
	 */
	public HashMap<String, RequestType> getRequestTypes() {
		return requestTypes;
	}

	/**
	 * sets the request types
	 * 
	 * @param requestTypes
	 *            A map of {@link #BROWSE_TYPE}, {@link #SEARCH_TYPE} with
	 *            {@link RequestType}
	 */
	public void setRequestTypes(HashMap<String, RequestType> requestTypes) {
		this.requestTypes = requestTypes;
	}

	/**
	 * A map of {@link #BROWSE_TYPE}, {@link #SEARCH_TYPE} with
	 * {@link FeatureType}
	 * 
	 * @return
	 */
	public HashMap<String, FeatureType> getFeatureTypes() {
		return featureTypes;
	}

	/**
	 * sets the feature types
	 * 
	 * @param featureTypes
	 *            A map of {@link #BROWSE_TYPE}, {@link #SEARCH_TYPE} with
	 *            {@link FeatureType}
	 */
	public void setFeatureTypes(HashMap<String, FeatureType> featureTypes) {
		this.featureTypes = featureTypes;
	}

	/**
	 * returns the URL to request given an array of optional params. Usually if
	 * optionalParam contains a {@link Param} of type {@link Param#QUERY} then
	 * this method will return the url of the {@link RequestType} of type
	 * {@link DescribeService#SEARCH_TYPE} otherwise will return the url of
	 * {@link DescribeService#BROWSE_TYPE}
	 * 
	 * @param optionalParam
	 *            An array of {@link Param}
	 * @param params
	 *            A {@link ServiceParams} instance
	 * @return The url to request
	 */
	public String getRequestForParam(List<Param> optionalParam, ServiceParams params) {
		String url;
		ArrayList<String> requestParams;
		RequestType requestType;

		this.setType(DescribeService.BROWSE_TYPE);
		requestType = getRequestTypes().get(DescribeService.BROWSE_TYPE);

		if (optionalParam == null) {
			this.setType(DescribeService.BROWSE_TYPE);
			requestType = getRequestTypes().get(DescribeService.BROWSE_TYPE);
		}

		if (optionalParam.size() == 0) {
			this.setType(DescribeService.BROWSE_TYPE);
			requestType = getRequestTypes().get(DescribeService.BROWSE_TYPE);
		}

		for (Param optParam : optionalParam) {
			if (optParam.getType() == ParamEnum.QUERY.name) {
				this.setType(DescribeService.SEARCH_TYPE);
				requestType = getRequestTypes().get(DescribeService.SEARCH_TYPE);
			}
		}

		url = requestType.getUrl();
		requestParams = requestType.getParams();

		String optionalUrl = processRequestParams(requestParams, params);
		return url + optionalUrl;
	}

	public String encode(String element) {
		try {
			if (this.getOutputEncoding() != null && this.getOutputEncoding().equals(STRIP_ACCENTS)) {
				return StringUtils.stripAccents(element);
			}

			if (this.getOutputEncoding() != null && this.getOutputEncoding().equals(UNICODE)) {
				return StringEscapeUtils.escapeJava(element);
			}

			if (this.getFormat().equals(FormatEnum.JSON.name) || this.getFormat().equals(FormatEnum.CSV.name)) {
				return element;
			}

			return new String(element.getBytes(this.getEncoding()), this.getOutputEncoding());
		} catch (UnsupportedEncodingException e) {
			return element;
		}
	}

	private String processRequestParams(ArrayList<String> requestParams, ServiceParams params) {
		String p;
		String value;
		StringBuffer optionalUrl = new StringBuffer();
		for (String requestParam : requestParams) {
			try {
				p = requestParam.split("=")[1];
				value = params.getValueForParam(p);
				if (value != null) {
					optionalUrl.append("&" + requestParam);
				}
			} catch (Exception ignore) {
				ignore.printStackTrace();
			}
		}

		addOriginalParams(params, optionalUrl);

		return optionalUrl.toString();
	}

	public String encodeParam(String param) {
		return param;
		// try {
		// return URLEncoder.encode(param, "UTF-8");
		// } catch (UnsupportedEncodingException e) {
		// return param;
		// }
	}

	protected void addOriginalParams(ServiceParams params, StringBuffer optionalUrl) {
		for (String key : params.getParams().keySet()) {
			if (!ParamEnum.from(key) && !isSpecialParam(key)) {
				optionalUrl.append("&" + key + "=" + (params.getParams().get(key)));
			}
		}
	}

	public boolean isSpecialParam(String key) {
		return key.startsWith("__");
	}

	/**
	 * Gets the column separator if the format is
	 * {@link JPEParserFormatEnum#CSV}
	 * 
	 * @return
	 */
	public String getCsvSeparator() {
		return csvSeparator;
	}

	/**
	 * Sets the column separator if the format is
	 * {@link JPEParserFormatEnum#CSV}
	 * 
	 * @return
	 */
	public void setCsvSeparator(String csvSeparator) {
		this.csvSeparator = csvSeparator;
	}

	/**
	 * Gets the encoding of the result document of the service. This is
	 * specially usefull for CSV
	 * 
	 * @return
	 */
	public String getEncoding() {
		if (encoding == null) {
			return DEFAULT_ENCODING;
		}
		return encoding;
	}

	/**
	 * Sets the encoding of the result document of the service. This is
	 * specially usefull for CSV
	 * 
	 * @param encoding
	 */
	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	/**
	 * Gets the number separator. This is useful for CSV resources
	 * 
	 * @return
	 */
	public String getNumberSeparator() {
		return numberSeparator;
	}

	/**
	 * Sets the number separator. This is useful for CSV resources
	 * 
	 * @param numberSeparator
	 */
	public void setNumberSeparator(String numberSeparator) {
		this.numberSeparator = numberSeparator;
	}

	/**
	 * Gets the decimal separator. This is useful for CSV resources
	 * 
	 * @return
	 */
	public String getDecimalSeparator() {
		return decimalSeparator;
	}

	/**
	 * Sets the decimal separator. This is useful for CSV resources
	 * 
	 * @param decimalSeparator
	 */
	public void setDecimalSeparator(String decimalSeparator) {
		this.decimalSeparator = decimalSeparator;
	}

	/**
	 * Indicates the compression format of the resource
	 * 
	 * @return @see {@link CompressionEnum}
	 */
	public String getCompression() {
		return compression;
	}

	/**
	 * Sets the compression format of the resource
	 * 
	 * @see {@link CompressionEnum}
	 */
	public void setCompression(String compression) {
		this.compression = compression;
	}

	/**
	 * This is used when the {@link #compression} is used. Gets the file name of
	 * the file compressed
	 * 
	 * @return
	 */
	public String getContentFile() {
		return contentFile;
	}

	/**
	 * This is used when the {@link #compression} is used. Sets the file name of
	 * the file compressed
	 * 
	 * @param contentFile
	 */
	public void setContentFile(String contentFile) {
		this.contentFile = contentFile;
	}

	/**
	 * Gets the array of categories configured in the {@link DescribeService}
	 * document
	 * 
	 * @return
	 */
	public List<String> getCategories() {
		return categories;
	}

	/**
	 * Sets the array of categories configured in the {@link DescribeService}
	 * document
	 * 
	 * @param categories
	 */
	public void setCategories(List<String> categories) {
		this.categories = categories;
	}

	/**
	 * Check if the category is in the {@link #categories} supported by the
	 * service
	 * 
	 * @param category
	 * @return
	 */
	public boolean containsCategory(String category) {
		if (this.categories == null || this.categories.isEmpty()) {
			return false;
		}

		for (String cat : categories) {
			if (category.compareToIgnoreCase(cat) == 0) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Gets the SRS (EPSG code) of the origin service
	 * 
	 * @return
	 */
	public String getSRS() {
		if (SRS == null) {
			return DEFAULT_SRS;
		}
		return SRS;
	}

	/**
	 * Sets the SRS (EPSG code) of the origin service
	 * 
	 * @param sRS
	 */
	public void setSRS(String sRS) {
		SRS = sRS;
	}

	/**
	 * Concatenates the categories as a String separated by
	 * {@link DescribeService#CATEGORY_SEPARATOR}
	 * 
	 * @return
	 */
	public String getCategoriesAsString() {
		List<String> categories = getCategories();
		String categoriesStr = "";
		if (categories != null && !categories.isEmpty()) {
			categoriesStr = StringUtils.join(categories.toArray(), CATEGORY_SEPARATOR);
		}

		return categoriesStr;
	}

	/**
	 * Sets the identifier of the service
	 * 
	 * @param id
	 */
	public void setId(String id) {
		this.id = id;
	}

	/**
	 * Gets the identifier of the service
	 * 
	 * @param id
	 */
	public String getId() {
		return this.id;
	}

	/**
	 * Converts a comma separated list of categories into a {@link List}
	 * 
	 * @param categoriesList
	 * @return
	 */
	public static List<String> categoriesFromString(String categoriesList) {
		List<String> result = new ArrayList<String>();
		try {
			if (categoriesList != null) {
				String[] categories = categoriesList.split(DescribeService.CATEGORY_SEPARATOR);
				if (categories != null && categories.length != 0) {
					for (String cat : categories) {
						result.add(cat);
					}
				}
			}
		} catch (Exception ignore) {
			logger.warn("DescribeService", ignore);
		}

		return result;
	}

	/**
	 * When the data comes from a static file such a CSV, if this is set to True
	 * then the {@link Param#QUERY} is used to filter the data in memory
	 * 
	 * @return
	 */
	public boolean isUseLocalFilter() {
		return useLocalFilter;
	}

	/**
	 * When the data comes from a static file such a CSV, if this is set to True
	 * then the {@link Param#QUERY} is used to filter the data in memory
	 * 
	 * @param useLocalFilter
	 */
	public void setUseLocalFilter(boolean useLocalFilter) {
		this.useLocalFilter = useLocalFilter;
	}

	/**
	 * 
	 * @param optionalParams
	 * @return
	 */
	public LocalFilter getLocalFilter(List<Param> optionalParams) {
		LocalFilter localFilter = null;
		if (isUseLocalFilter()) {
			localFilter = LocalFilter.fromOptionalParams(optionalParams);
		}
		return localFilter;
	}

	public String getDateFormat() {
		return dateFormat;
	}

	public void setDateFormat(String dateFormat) {
		this.dateFormat = dateFormat;
	}

	public String getAuthType() {
		return auth.getType();
	}

	@JsonIgnore
	public Auth getAuth() {
		return auth;
	}

	@JsonProperty
	public void setAuth(Auth auth) {
		this.auth = auth;
	}

	public boolean isEncodeUrl() {
		return encodeUrl;
	}

	public void setEncodeUrl(boolean encodeUrl) {
		this.encodeUrl = encodeUrl;
	}

	public String getOutputEncoding() {
		return outputEncoding;
	}

	public void setOutputEncoding(String outputEncoding) {
		this.outputEncoding = outputEncoding;
	}

	public String getFillEmptyAttributes() {
		return this.fillEmptyAttributes;
	}

	public void setFillEmptyAttributes(String a) {
		this.fillEmptyAttributes = a;
	}
} @ApiModel(value = "DescribeServices", description = "Contains the available services to use in the browse API operations of POIProxy")
public class DescribeServices {

	private Map<String, DescribeService> services = new HashMap<String, DescribeService>();
	private ObjectMapper mapper = new ObjectMapper();

	@ApiModelProperty(value = "Services registered in POIProxy to use in the browse API operations", notes = "key-value pairs, where key is the name of the service and value its configuration. Use the name of the service as the service parameter in browse API operations")
	public Map<String, DescribeService> getServices() {
		return services;
	}

	public void setServices(Map<String, DescribeService> services) {
		this.services.clear();
		for (String key : services.keySet()) {
			this.put(key, services.get(key));
		}
	}

	public void put(String id, DescribeService service) {
		service.setId(id);
		services.put(id, service);
	}

	public String asJSON() throws JsonGenerationException,
			JsonMappingException, IOException {
		return mapper.defaultPrettyPrintingWriter().writeValueAsString(this);
	}

	public DescribeServices fromJSON(String src) throws JsonParseException,
			JsonMappingException, IOException {
		return mapper.readValue(src, DescribeServices.class);
	}

	/**
	 * Iterates the {@link DescribeService} registered and returns a list of
	 * available categories
	 * 
	 * @return
	 */
	@ApiModelProperty(value = "Categories of services registered. Not used at the moment")
	public List<String> getCategories() {
		Set<String> keySet = this.services.keySet();
		Iterator<String> it = keySet.iterator();

		List<String> categories = new ArrayList<String>();

		DescribeService service;
		while (it.hasNext()) {
			service = this.services.get(it.next());
			for (String category : service.getCategories()) {
				if (!categories.contains(category)) {
					categories.add(category);
				}
			}
		}

		return categories;
	}

	/**
	 * Iterates the list of registered services and for those that support the
	 * category passed as a parameter return their ID
	 * 
	 * @param category
	 * @return
	 */
	public List<String> getServicesIDByCategory(String category) {
		Set<String> servicesID = services.keySet();
		Iterator<String> it = servicesID.iterator();
		List<String> ids = new ArrayList<String>();

		if (category == null) {
			return ids;
		}

		String key;
		while (it.hasNext()) {
			key = it.next();
			DescribeService service = services.get(key);
			if (service.containsCategory(category)) {
				ids.add(key);
			}
		}

		return ids;
	}

	/**
	 * Returns true if there is any service registered that supports the
	 * category parameter
	 * 
	 * @param category
	 * @return
	 */
	public boolean supportsCategory(String category) {
		if (category == null) {
			return false;
		}

		List<String> categories = this.getCategories();
		for (String cat : categories) {
			if (cat.compareToIgnoreCase(category) == 0) {
				return true;
			}
		}

		return false;
	}
} public class Param {

	private String type;
	private String value;

	/**
	 * Constructor
	 * 
	 * @param type
	 *            A {@link ParamEnum} name
	 * @param value
	 */
	public Param(String type, String value) {
		this.type = type;
		this.value = value;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getValue() {
		return value;
	}

	public void setValue(String value) {
		this.value = value;
	}

} public class ServiceParams {

	public static final String MINX = "__MINX__";
	public static final String MAXX = "__MAXX__";
	public static final String MINY = "__MINY__";
	public static final String MAXY = "__MAXY__";

	public static final String LON = "__LON__";
	public static final String LAT = "__LAT__";
	public static final String DIST = "__DIST__";
	public static final String DISTKM = "__DISTKM__";

	public static final String KEY = "__KEY__";
	public static final String FORMAT = "__FORMAT__";

	public static final String QUERY = "__QUERY__";

	public static final String FROMDATE = "__FROMDATE__";
	public static final String TODATE = "__TODATE__";

	public static final String LIMIT = "__LIMIT__";
	public static final String OFFSET = "__OFFSET__";

	private HashMap<String, String> params = new HashMap<String, String>();
	private static HashMap<String, String> optParams = new HashMap<String, String>();

	static {
		optParams.put(ParamEnum.QUERY.name, QUERY);
		optParams.put(ParamEnum.APIKEY.name, KEY);
		optParams.put(ParamEnum.FROMDATE.name, FROMDATE);
		optParams.put(ParamEnum.TODATE.name, TODATE);
		optParams.put(ParamEnum.LIMIT.name, LIMIT);
		optParams.put(ParamEnum.OFFSET.name, OFFSET);
	}

	public void putParam(String param, String value) {
		this.params.put(param, value);
	}

	public String getValueForParam(String param) {
		return this.params.get(param);
	}

	public HashMap<String, String> getParams() {
		return this.params;
	}

	public String getServiceParamFromURLParam(String urlParam) {
		return optParams.get(urlParam);
	}

	/**
	 * Decides if the parameter name is of date type
	 * 
	 * @param key
	 * @return
	 */
	public boolean isDate(String key) {
		return key.toLowerCase().indexOf("date") != -1;
	}
} public class POIProxyEvent {

	private Extent extent;
	private POIProxyEventEnum type;
	private Integer z;
	private Integer y;
	private Integer x;
	private Double lon;
	private Double lat;
	private Double distance;
	private String query;

	private DescribeService describeService;

	private String parsedData;
	private List<JTSFeature> features;

	public POIProxyEvent(POIProxyEventEnum type, DescribeService service,
			Extent extent, Integer z, Integer y, Integer x, Double lon,
			Double lat, Double distance, String query, String parsedData,
			List<JTSFeature> features) {
		super();
		this.extent = extent;
		this.describeService = service;
		this.type = type;
		this.z = z;
		this.y = y;
		this.x = x;
		this.lon = lon;
		this.lat = lat;
		this.distance = distance;
		this.query = query;
		this.parsedData = parsedData;
		this.setFeatures(features);
	}

	public DescribeService getDescribeService() {
		return describeService;
	}

	public Extent getExtent() {
		return extent;
	}

	public POIProxyEventEnum getType() {
		return type;
	}

	public Integer getZ() {
		return z;
	}

	public Integer getY() {
		return y;
	}

	public Integer getX() {
		return x;
	}

	public Double getLon() {
		return lon;
	}

	public Double getLat() {
		return lat;
	}

	public Double getDistance() {
		return distance;
	}

	public String getQuery() {
		return query;
	}

	public String getParsedData() {
		return parsedData;
	}

	public List<JTSFeature> getFeatures() {
		return features;
	}

	public void setFeatures(List<JTSFeature> features) {
		this.features = features;
	}
} public enum POIProxyEventEnum {
	
	BeforeBrowseZXY(10, "BeforeBrowseZXY"),
	AfterBrowseZXY(11, "AfterBrowseZXY"),
	BeforeBrowseExtent(20, "BeforeBrowseExtent"),
	AfterBrowseExtent(21, "AfterBrowseExtent"),
	BeforeBrowseLonLat(30, "BeforeBrowseLonLat"),
	AfterBrowseLonLat(31, "AfterBrowseLonLat"),
	BeforeSearchZXY(40, "BeforeSearchZXY"),
	AfterSearchZXY(41, "AfterSearchZXY"),
	BeforeSearchExtent(50, "BeforeSearchExtent"),
	AfterSearchExtent(51, "AfterSearchExtent"),
	BeforeSearchLonLat(60, "BeforeSearchLonLat"),
	AfterSearchLonLat(61, "AfterSearchLonLat"),
	BeforeDescribeService(70, "BeforeDescribeService"),
	AfterDescribeService(71, "AfterDescribeService"),
	ReadLonLat(72, "ReadLonLat"),
	FeaturesParsedZXY(80, "FeaturesParsedZXY"),
	FeaturesParsedLonLat(81, "FeaturesParsedLonLat");
	
	public int id;
	public String description;
	
	private POIProxyEventEnum(int id, String description) {
		this.id = id;
		this.description = description;
	}
} public interface RequestService {

	/**
	 * 
	 * @param URL
	 *            The URL where download data from
	 * @param fileName
	 *            The name of the file to store the data in
	 * @param downloadPath
	 *            The path where to store the file
	 * @param authElement
	 *            The {@link Auth} configuration that can have, apiKeys,
	 *            credentials, etc.
	 * @return An array of bytes with the data
	 * @throws Exception
	 */
	public byte[] download(String URL, String fileName, String downloadPath,
			Auth authElement) throws Exception;
} public class RequestServices {

	private Map<AuthTypeEnum, RequestService> requestServices = new HashMap<AuthTypeEnum, RequestService>();

	private static RequestServices instance;

	public static RequestServices getInstance() {
		if (instance == null) {
			instance = new RequestServices();
		}

		return instance;
	}

	public void addRequestService(AuthTypeEnum authType,
			RequestService requestService) {
		this.requestServices.put(authType, requestService);
	}

	public RequestService getRequestService(AuthTypeEnum authTypeEnum) {
		return this.requestServices.get(authTypeEnum);
	}

	public RequestService getRequestService(String authType) {
		return this.requestServices.get(AuthTypeEnum.valueOf(authType));
	}
} public class SimpleHttpRequestService implements RequestService {

	private Downloader d = new Downloader();

	@Override
	public byte[] download(String URL, String fileName, String downloadPath,
			Auth authElem) throws Exception {
		return d.downloadFromUrl(URL, fileName, downloadPath, null);
	}
} public class Downloader {

	private final static Logger logger = Logger.getLogger(Downloader.class);

	private byte[] xmldata;
	private DownloadListener listener;

	public void downloadFromUrl(String mapURL, Cancellable cancellable)
			throws Exception {
		xmldata = null;
		InputStream in = null;
		OutputStream out = null;
		BufferedInputStream bis = null;
		if (mapURL == null)
			throw new Exception("Error null url");
		try {

			long startTime = System.currentTimeMillis();
			logger.debug("download starts");
			logger.debug("download url:" + (mapURL));

			in = new BufferedInputStream(openConnection(mapURL),
					Constants.IO_BUFFER_SIZE);
			final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
			out = new BufferedOutputStream(dataStream, Constants.IO_BUFFER_SIZE);
			byte[] b = new byte[8 * 1024];
			int read;
			int total = 0;
			while ((read = in.read(b)) != -1) {
				total += read;
				out.write(b, 0, read);
				if (listener != null) {
					listener.onBytesDownloaded(read);
				}
				if (cancellable != null && cancellable.getCanceled()) {
					logger.debug("Download cancelled");

					return;
				}

				// System.gc();
			}
			out.flush();
			xmldata = dataStream.toByteArray();

		} catch (IOException e) {
			logger.debug(e);
			throw new Exception(e);
		} finally {
			Constants.closeStream(in);
			Constants.closeStream(out);
		}
	}

	public byte[] downloadFromUrl(String mapURL, String fileName,
			String downloadPath, Cancellable cancellable) throws Exception {
		xmldata = null;
		InputStream in = null;
		OutputStream out = null;
		BufferedInputStream bis = null;
		if (mapURL == null)
			throw new Exception("Error null url");
		if (fileName == null)
			throw new Exception("Error null name");
		try {

			File directory = new File(downloadPath);
			if (!directory.exists()) {
				directory.mkdirs();
			}

			File file = new File(downloadPath + fileName);
			file.createNewFile();
			long startTime = System.currentTimeMillis();
			logger.debug("download starts");
			logger.debug("download url:" + (mapURL));
			logger.debug("downloading file:" + fileName);

			in = new BufferedInputStream(openConnection(mapURL),
					Constants.IO_BUFFER_SIZE);
			final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
			out = new BufferedOutputStream(dataStream, Constants.IO_BUFFER_SIZE);
			byte[] b = new byte[8 * 1024];
			int read;
			int total = 0;
			while ((read = in.read(b)) != -1) {
				total += read;
				out.write(b, 0, read);
				if (listener != null) {
					listener.onBytesDownloaded(read);
				}
				if (cancellable != null && cancellable.getCanceled()) {
					logger.debug("Download cancelled");
					file.delete();
					return null;
				}

				// System.gc();
			}
			out.flush();
			xmldata = dataStream.toByteArray();

			FileOutputStream fileout = new FileOutputStream(file);
			fileout.write(xmldata);
			fileout.close();
			logger.debug("download ready in"
					+ ((System.currentTimeMillis() - startTime) / 1000)
					+ " sec");

		} catch (IOException e) {
			logger.debug(e);
			throw new Exception(e);
		} finally {
			Constants.closeStream(in);
			Constants.closeStream(out);
		}
		return xmldata;
	}

	public void downloadFromUrlBigFile(String mapURL, String fileName,
			String downloadPath, Cancellable cancellable) throws Exception {
		InputStream in = null;
		// OutputStream out = null;
		BufferedInputStream bis = null;
		if (mapURL == null)
			throw new Exception("Error null url");
		if (fileName == null)
			throw new Exception("Error null name");
		try {

			File directory = new File(downloadPath);
			if (!directory.exists()) {
				directory.mkdirs();
			}

			File file = new File(downloadPath + fileName);
			file.createNewFile();
			long startTime = System.currentTimeMillis();
			logger.debug("download starts");
			logger.debug("download url:" + mapURL);
			logger.debug("downloading file:" + fileName);

			in = new BufferedInputStream(openConnection(mapURL),
					Constants.IO_BUFFER_SIZE);
			// final ByteArrayOutputStream dataStream = new
			// ByteArrayOutputStream();
			// out = new BufferedOutputStream(dataStream,
			// Constants.IO_BUFFER_SIZE);
			FileOutputStream fileout = new FileOutputStream(file);
			byte[] b = new byte[8 * 1024];
			int read;
			int total = 0;
			while ((read = in.read(b)) != -1) {
				total += read;
				fileout.write(b, 0, read);
				fileout.flush();
				if (listener != null) {
					listener.onBytesDownloaded(read);
				}
				if (cancellable != null && cancellable.getCanceled()) {
					logger.debug("Download cancelled");
					file.delete();
					return;
				}

				// System.gc();
			}
			// out.flush();
			// xmldata = dataStream.toByteArray();

			// fileout.write(xmldata);
			fileout.close();
			logger.debug("download ready in"
					+ ((System.currentTimeMillis() - startTime) / 1000)
					+ " sec");

		} catch (IOException e) {
			logger.debug(e);
			throw new Exception(e);
		} finally {
			Constants.closeStream(in);
			// Constants.closeStream(out);
		}
	}

	public String openFile(String filePath) throws Exception {
		if (filePath == null)
			throw new Exception("Error null url");

		try {
			File file = new File(filePath);

			if (!file.exists()) {
				throw new Exception("File not exists");
			}

			BufferedReader reader = new BufferedReader(new FileReader(file));
			String line = null;
			StringBuilder stringBuilder = new StringBuilder();
			String ls = System.getProperty("line.separator");

			while ((line = reader.readLine()) != null) {
				stringBuilder.append(line);
				stringBuilder.append(ls);
			}

			return stringBuilder.toString();
		} catch (IOException e) {
			logger.debug(e);
			throw new Exception(e);
		} finally {

		}
	}

	public byte[] getData() {
		return xmldata;
	}

	public static InputStream openConnection(String query) throws IOException {
		final URL url = new URL(query.replace(" ", "%20"));
		URLConnection urlconnec = url.openConnection();
		urlconnec.setConnectTimeout(60000);
		urlconnec.setReadTimeout(60000);
		return urlconnec.getInputStream();
	}

	public void setDownloadListener(DownloadListener listener) {
		this.listener = listener;
	}
}
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import es.alrocar.jpe.parser.JPEParser;
import es.alrocar.jpe.parser.JPEParserManager;
import es.alrocar.jpe.parser.csv.CSVParser;
import es.alrocar.jpe.parser.exceptions.NoParserRegisteredException;
import es.alrocar.jpe.parser.json.JSONJPEParser;
import es.alrocar.jpe.parser.kml.KMLParser;
import es.alrocar.jpe.parser.xml.XMLJPEParser;
import es.alrocar.jpe.writer.GeoJSONWriter;
import es.alrocar.poiproxy.configuration.AuthTypeEnum;
import es.alrocar.poiproxy.configuration.DescribeService;
import es.alrocar.poiproxy.configuration.DescribeServices;
import es.alrocar.poiproxy.configuration.Param;
import es.alrocar.poiproxy.configuration.ServiceConfigurationManager;
import es.alrocar.poiproxy.configuration.ServiceParams;
import es.alrocar.poiproxy.exceptions.POIProxyException;
import es.alrocar.poiproxy.geotools.GeotoolsUtils;
import es.alrocar.poiproxy.proxy.event.POIProxyEvent;
import es.alrocar.poiproxy.proxy.event.POIProxyEventEnum;
import es.alrocar.poiproxy.proxy.utiles.Calculator;
import es.alrocar.poiproxy.request.RequestService;
import es.alrocar.poiproxy.request.RequestServices;
import es.alrocar.poiproxy.request.SimpleHttpRequestService;
import es.alrocar.utils.CompressionEnum;
import es.alrocar.utils.Downloader;
import es.alrocar.utils.PropertyLocator;
import es.alrocar.utils.Unzip;
import es.prodevelop.geodetic.utils.conversion.ConversionCoords;
import es.prodevelop.gvsig.mini.geom.Extent;
import es.prodevelop.gvsig.mini.geom.impl.jts.JTSFeature;
import es.prodevelop.gvsig.mini.projection.TileConversor;
import es.prodevelop.gvsig.mobile.fmap.proj.CRSFactory;
/*
 * Licensed to Prodevelop SL under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The Prodevelop SL licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 * For more information, contact:
 *
 *   Prodevelop, S.L.
 *   Pza. Don Juan de Villarrasa, 14 - 5
 *   46001 Valencia
 *   Spain
 *
 *   +34 963 510 612
 *   +34 963 510 968
 *   prode@prodevelop.es
 *   http://www.prodevelop.es
 * 
 * @author Alberto Romeu Carrasco http://www.albertoromeu.com
 */

package es.alrocar.poiproxy.proxy;




/**
 * The main entry point of the library. This class has methods to make a request
 * to a service, parse the response and return the GeoJSON result
 * 
 * @author albertoromeu
 * 
 */
public class POIProxy {

	private static final String MERCATOR_SRS = "EPSG:900913";

	private static final String GEODETIC_SRS = "EPSG:4326";

	private final static Logger logger = Logger.getLogger(POIProxy.class);

	public static String CACHE_DIR = "/var/lib/sp/cache";

	private static POIProxy proxy;
	private ServiceConfigurationManager serviceManager;

	private RequestServices requestServices;

	public final static String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

	private List<POIProxyListener> listeners = new ArrayList<POIProxyListener>();
	private CacheListener cache;

	public static POIProxy asSingleton() {
		if (proxy == null) {
			proxy = new POIProxy();
			proxy.initialize();
		}

		return proxy;
	}

	/**
	 * Instantiates the {@link ServiceConfigurationManager} that loads the
	 * services available at
	 * {@link ServiceConfigurationManager#CONFIGURATION_DIR}
	 * 
	 * Registers a {@link JSONJPEParser} and a {@link XMLJPEParser} to allow
	 * parsing either json or xml depending on the format response of the
	 * registered services
	 * 
	 * And finally instantiates a {@link GeoJSONWriter} that will return the
	 * GeoJSON document
	 */
	public void initialize() {
		// Registra todos los servicios disponibles
		serviceManager = new ServiceConfigurationManager();

		registerRequestServices();

		JPEParserManager.getInstance().registerJPEParser(new JSONJPEParser());
		JPEParserManager.getInstance().registerJPEParser(new XMLJPEParser());
		JPEParserManager.getInstance().registerJPEParser(new CSVParser());
		JPEParserManager.getInstance().registerJPEParser(new KMLParser());
	}

	protected void registerRequestServices() {
		requestServices = RequestServices.getInstance();

		for (AuthTypeEnum authType : AuthTypeEnum.values()) {
			if (authType._downloader != null) {
				try {
					requestServices.addRequestService(authType, authType._downloader.newInstance());
				} catch (InstantiationException e) {
					logger.error(e.getMessage(), e);
				} catch (IllegalAccessException e) {
					logger.error(e.getMessage(), e);
				}
			} else {
				requestServices.addRequestService(authType, new SimpleHttpRequestService());