package eu.dnetlib.enabling.aas.ctx;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.apache.log4j.Logger;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;


import com.opensymphony.oscache.base.Cache;
import com.opensymphony.oscache.base.NeedsRefreshException;

import eu.dnetlib.enabling.aas.admin.ISecurityContextContainerAdministration;
import eu.dnetlib.enabling.aas.is.ISConstants;
import eu.dnetlib.enabling.aas.is.ISUtils;
import eu.dnetlib.enabling.aas.nh.INotificationSubscriber;
import eu.dnetlib.enabling.aas.nh.ISecurityContextNotificationManager;
import eu.dnetlib.enabling.aas.nh.NotificationConstants;
import eu.dnetlib.enabling.aas.rmi.Attribute;
import eu.dnetlib.enabling.aas.rmi.Obligation;
import eu.dnetlib.enabling.aas.rmi.TypedString;
import eu.dnetlib.enabling.aas.service.A2Constants;
import eu.dnetlib.enabling.aas.service.A2Exception;
import eu.dnetlib.enabling.aas.utils.QueryProvider;
import eu.dnetlib.enabling.aas.utils.SecurityUtils;
import eu.dnetlib.enabling.aas.wrappers.SecurityContextWrapper;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpDocumentNotFoundException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.registry.ISRegistryDocumentNotFoundException;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryException;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.resultset.rmi.ResultSetException;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;

/**
 * Container for SecCTXs. Using cache and IS services.
 * <p>
 * Important note: secCtxId is also profId.
 * @author mhorst
 *
 */
public class SecurityContextContainer 
	implements ISecurityContextContainer, 
	ISecurityContextContainerAdministration, ISecurityContextNotificationManager {

	protected static final Logger log = Logger.getLogger(SecurityContextContainer.class);
	
	/**
	 * Global cache.
	 */
	private Cache cache;
	
	/**
	 * IS Registry Service
	 */
	private ISRegistryService registryService;

	/**
	 * IS LookUp Service
	 */
	private ISLookUpService lookUpService;

	/**
	 * ResultSet Service 
	 */
	private ResultSetService resultSetService;
	
	/**
	 * AAS notification subscriber
	 */
	private INotificationSubscriber notificationSubscriber;
	
	/**
	 * Bindings used for mapping xmls to SecurityProfile objects. 
	 */
	private List<String> bindings = new ArrayList<String>();

	/**
	 * Mapping xmls to SecurityContext objects.
	 */
	private Mapping mapping = new Mapping();


	/**
	 * Auxilary mapping resource ids to secCtxs (bidirectional).
	 * Should be accessed only by synchronized methods.
	 */
	private BidiMap auxResourceToSecCtxMap = new DualHashBidiMap();
	

	/**
	 * SecurityUtils for creating priv/publ keys.
	 */
	SecurityUtils securityUtils;

	
	/**
	 * Init method to setup and initialize SecurityContext container. 
	 */
	public void init() {
//		init cache
		cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, new HashMap<String,SecurityContext>());
		
//		Load mapping files
        if (bindings.size()>0) {
        	for (Iterator iter = bindings.iterator(); iter.hasNext();) {
        		String currentBinding = null;
        		try {
					currentBinding = (String) iter.next();
					mapping.loadMapping(currentBinding);
				} catch (IOException e) {
					log.error("Exception occured while reading binding definition for '"
							+currentBinding+"' file!",e);
				} catch (MappingException e) {
					log.error("Exception occured while reading binding definition for '"
							+currentBinding+"' file!",e);
				}
			}
        } else
        	log.error("No binding definitions found for xml mappings!");

	}
	

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.ctx.ISecurityContextContainer#createContext(java.lang.String, java.util.Map)
	 */
	@SuppressWarnings("unchecked")
	public SecurityContext createContext(String resourceId,
			Map<String, Object> properties) throws SecurityContextContainerException{
		if (resourceId == null || properties == null)
			return null;
		try {

		if (checkKeyInAuxResourceToSecCtxMap(resourceId)) {
			String profId = (String) getFromAuxResourceToSecCtxMap(resourceId);
			SecurityContext newSecCtx = buildSecurityContext(resourceId, properties);
			if (!updateInISRegistry(profId, newSecCtx)) {
				throw new SecurityContextContainerException("Problem occured when updating SecCtx in IS. ResourceId: "+resourceId);
			}
			newSecCtx.setSecCtxId(profId);
			log.info("SecCtx already created for resource: "+resourceId+". Overwriting with new SecCtx...");
			synchronized(cache) {
				Map<String,SecurityContext> securityContexts = (Map<String, SecurityContext>) cache.getFromCache(A2Constants.CACHE_SECURITY_CONTEXTS);
				securityContexts.put(profId, newSecCtx);
				cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, securityContexts);
			}
			return newSecCtx;
			
		} else {
//			if secCtx doesn't exist in bidiMap, searches in IS
			SecurityContext newSecCtx = buildSecurityContext(resourceId, properties);
			String query = QueryProvider.findSecurityContextForResourceQuery(resourceId);
			
			String resourceProf = null;
			try {
				resourceProf = lookUpService.getResourceProfileByQuery(query);
			} catch (ISLookUpDocumentNotFoundException e) {
				log.info("Couldn't find scurityContext in IS for resourceId: "+resourceId);
			} catch (ISLookUpException e) {
				throw new SecurityContextContainerException("Exception occured when trying to " +
						"find securityContext (for resourceId: "+resourceId+") by query!",e);
			}
			
			if (resourceProf!=null && resourceProf.length()>0) {
				//found SecCtx in ISStore
				SecurityContext foundSecCtx;
		        
//		      	preparing unmarshaller for SecurityContext
		        Unmarshaller unmarshaller = new Unmarshaller(SecurityContextWrapper.class);
		        try {
		            unmarshaller.setIgnoreExtraElements(true);
					unmarshaller.setMapping(mapping);
				} catch (MappingException e) {
					log.error("Exception occured when setting mappings for marshaller!",e);
				}
				try {
					SecurityContextWrapper foundSecCtxWrapper = (SecurityContextWrapper) unmarshaller.unmarshal(new StringReader(resourceProf));
					if (foundSecCtxWrapper!=null && foundSecCtxWrapper.getSecCtx()!=null) {
						foundSecCtx = foundSecCtxWrapper.getSecCtx();
						foundSecCtx.setSecCtxId(foundSecCtxWrapper.getHeader().getProfId());
					} else {
						throw new SecurityContextContainerException("Problem occured when unmarshalling SecCtx for resource: "+resourceId + ". " +
								"Couldn't find securityContext.");
					}
				} catch (MarshalException e) {
					throw new SecurityContextContainerException("Exception occured when unmarshalling SecCtx for resource: "+resourceId,e);
				} catch (ValidationException e) {
					throw new SecurityContextContainerException("Exception occured when unmarshalling SecCtx for resource: "+resourceId,e);
				}
				boolean check = updateInISRegistry(foundSecCtx.getSecCtxId(), newSecCtx);
				if (!check)
					throw new SecurityContextContainerException("Some problems occured while updating secCtx in ISRegistry for resource: "+resourceId);
				newSecCtx.setSecCtxId(foundSecCtx.getSecCtxId());
				synchronized(cache) {
					Map<String,SecurityContext> securityContexts = (Map<String, SecurityContext>) cache.getFromCache(A2Constants.CACHE_SECURITY_CONTEXTS);
					securityContexts.put(newSecCtx.getSecCtxId(), newSecCtx);
					cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, securityContexts);
				}

				putInAuxResourceToSecCtxMap(resourceId, foundSecCtx.getSecCtxId());
//				SecCtxId is also profId
				subscribe(foundSecCtx.getSecCtxId());
				return newSecCtx;
				
			} else {
				//SecCtx not found is IS, creating new
				String profId = createInISRegistry(newSecCtx);
				newSecCtx.setSecCtxId(profId);
				synchronized(cache) {
					Map<String,SecurityContext> securityContexts = (Map<String, SecurityContext>) cache.getFromCache(A2Constants.CACHE_SECURITY_CONTEXTS);
					securityContexts.put(newSecCtx.getSecCtxId(), newSecCtx);
					cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, securityContexts);
				}
				putInAuxResourceToSecCtxMap(resourceId, profId);
				subscribe(profId);
				return newSecCtx;
			} 
			
		}
		} catch (NeedsRefreshException e) {
			throw new SecurityContextContainerException("Exception occured while reading/writing SecurityContext from/to cache for resource "+resourceId+"!",e);
		}
	}
	
	/**
	 * Updates SecCtx in IS
	 * @param profId
	 * @param secCtx
	 * @return true if succesfully updated
	 * @throws SecurityContextContainerException
	 */
	private boolean updateInISRegistry(String profId, SecurityContext secCtx) throws SecurityContextContainerException {
		if (profId==null || secCtx==null)
			return false;
		String resourceProfile = null;
		try {
			SecurityContextWrapper secCtxWrapper = new SecurityContextWrapper(secCtx);
			StringWriter strWriter = new StringWriter();
			Marshaller marshaller = new Marshaller(strWriter);
			marshaller.setMapping(mapping);
			marshaller.marshal(secCtxWrapper);
			resourceProfile = strWriter.toString();
		
			return registryService.updateProfile(profId, resourceProfile, A2Constants.RESOURCE_TYPE_SECURITY_CONTEXT);
			
		} catch (IOException e) {
			throw new SecurityContextContainerException("Exception occured when creating marshaller for SecCtx. Resource id: "+secCtx.getResourceId());
		} catch (MarshalException e) {
			throw new SecurityContextContainerException("Exception occured during converting SecurityContext (resourceId:"+secCtx.getResourceId()+") to xml!",e);
		} catch (ValidationException e) {
			throw new SecurityContextContainerException("Exception occured during converting SecurityContext (resourceId:"+secCtx.getResourceId()+") to xml!",e);		
		} catch (MappingException e) {
			throw new SecurityContextContainerException("Exception occured during converting SecurityContext (resourceId:"+secCtx.getResourceId()+") to xml!",e);		
		} catch (ISRegistryException e) {
			throw new SecurityContextContainerException("Exception occured while updating profile for resourceId: "+secCtx.getResourceId(),e);		
		} 
	}
	

	/**
	 * Creates SecCtx in IS
	 * @param secCtx
	 * @return profId
	 * @throws SecurityContextContainerException
	 */
	private String createInISRegistry(SecurityContext secCtx) throws SecurityContextContainerException {
		if (secCtx==null)
			return null;
		String resourceProfile = null;
		try {
			SecurityContextWrapper secCtxWrapper = new SecurityContextWrapper(secCtx);
			StringWriter strWriter = new StringWriter();
			Marshaller marshaller = new Marshaller(strWriter);
			marshaller.setMapping(mapping);
			marshaller.marshal(secCtxWrapper);
			resourceProfile = strWriter.toString();
			return registryService.registerProfile(resourceProfile);
			
		} catch (IOException e) {
			throw new SecurityContextContainerException("Exception occured when creating marshaller for SecCtx. Resource id: "+secCtx.getResourceId());
		} catch (MarshalException e) {
			throw new SecurityContextContainerException("Exception occured during converting SecurityContext (resourceId:"+secCtx.getResourceId()+") to xml!",e);
		} catch (ValidationException e) {
			throw new SecurityContextContainerException("Exception occured during converting SecurityContext (resourceId:"+secCtx.getResourceId()+") to xml!",e);		
		} catch (MappingException e) {
			throw new SecurityContextContainerException("Exception occured during converting SecurityContext (resourceId:"+secCtx.getResourceId()+") to xml!",e);		
		} catch (ISRegistryException e) {
			throw new SecurityContextContainerException("Exception occured while registering profile for resourceId: "+secCtx.getResourceId(),e);		
		} 		
	}
	
	
	/**
	 * Creates SecurityContext instance.
	 * @param secCtxId
	 * @param resourceId
	 * @param properties
	 * @return SecurityContext
	 */
	private SecurityContext buildSecurityContext(String resourceId,
			Map<String, Object> properties) {
//		SecCtxId is unknown at the time of creating SecCtx. ISRegistry generates SecCtxId.
		SecurityContext secCtx = new SecurityContext(null,resourceId);
		try {
			KeyPair keyPair = securityUtils.generateKeyPair();
			secCtx.setPrivKey(keyPair.getPrivate().getEncoded());
			secCtx.setPubKey(keyPair.getPublic().getEncoded());
		} catch (NoSuchAlgorithmException e) {
			log.error("Error occured during generating private/public keys!",e);
		}
		secCtx.setCreationTime(System.currentTimeMillis());
		secCtx.setSecCtxType((String) properties
				.get(ISecurityContextContainer.SECCTX_TYPE_PROPERTY_KEY));
		secCtx.setAttributes((Attribute[]) properties
				.get(ISecurityContextContainer.ATTRIBUTES_PROPERTY_KEY));
		secCtx.setIdentities((TypedString[]) properties
				.get(ISecurityContextContainer.IDENTITIES_PROPERTY_KEY));
		secCtx.setObligations((Obligation[]) properties
				.get(ISecurityContextContainer.OBLIGATIONS_PROPERTY_KEY));
		return secCtx;
	}

	
	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.nh.rmi.ISecurityContextNotificationManager#deleteContextForResourceId(java.lang.String)
	 */
	public SecurityContext deleteContextForResourceId(String resourceId) throws SecurityContextContainerException {
		if (resourceId==null)
			return null;
		if (checkKeyInAuxResourceToSecCtxMap(resourceId)) {
			String secCtxId = (String) getFromAuxResourceToSecCtxMap(resourceId);
			return deleteContext(secCtxId);
		} else {
//			secCtx is not stored in cache, removing from IS
			String query = QueryProvider.findSecurityContextForResourceQuery(resourceId);
			String secCtxProf = null;
			try {
				secCtxProf = lookUpService.getResourceProfileByQuery(query);
			} catch (ISLookUpDocumentNotFoundException e) {
				log.info("Couldn't find scurityContext in IS for resourceId: "+resourceId);
				return null;
			} catch (ISLookUpException e) {
				throw new SecurityContextContainerException("Exception occured when trying to find " +
						"securityContext (for resourceId: "+resourceId+") by query!",e);
			} 
			
			if (secCtxProf!=null && secCtxProf.length()>0) {
				//found SecCtx in ISStore
				SecurityContext foundSecCtx = null;
//		      	preparing unmarshaller for SecurityContext
		        Unmarshaller unmarshaller = new Unmarshaller(SecurityContextWrapper.class);
		        try {
		            unmarshaller.setIgnoreExtraElements(true);
					unmarshaller.setMapping(mapping);
				} catch (MappingException e) {
					throw new SecurityContextContainerException("Exception occured when setting mappings for marshaller!",e);
				}
				try {
					SecurityContextWrapper foundSecCtxWrapper = (SecurityContextWrapper) unmarshaller.unmarshal(new StringReader(secCtxProf));
					if (foundSecCtxWrapper!=null && foundSecCtxWrapper.getSecCtx()!=null) {
						foundSecCtx = foundSecCtxWrapper.getSecCtx();
						foundSecCtx.setSecCtxId(foundSecCtxWrapper.getHeader().getProfId());
						try {
							boolean check = registryService.deleteProfile(foundSecCtx.getSecCtxId());
							if (!check) {
								log.error("Some problems occured while removing secCtx from ISRegistry! SecCtxId: "+foundSecCtx.getSecCtxId()+". " +
										"Possible cause: SecCtx doesn't exist in IS.");
							}
							return foundSecCtx;
						} catch (ISRegistryDocumentNotFoundException e) {
							log.info("Some problems occured while removing secCtx from ISRegistry! " +
									"SecCtxId: "+foundSecCtx.getSecCtxId()+". " +
									"Possible cause: SecCtx doesn't exist in IS.",e);
							return null;
						} catch (ISRegistryException e) {
							throw new SecurityContextContainerException("Exception occured while deleting " +
									"profile for profId: "+foundSecCtx.getSecCtxId(),e);
						} 
					} else {
						throw new SecurityContextContainerException("Problem occured when unmarshalling SecCtx for resource: "+resourceId + ". " +
								"Couldn't find securityContext.");
					}
				} catch (MarshalException e) {
					throw new SecurityContextContainerException("Exception occured when unmarshalling SecCtx for resource: "+resourceId,e);
				} catch (ValidationException e) {
					throw new SecurityContextContainerException("Exception occured when unmarshalling SecCtx for resource: "+resourceId,e);
				}
			} else {
//				didn't find secCtx in IS
				return null;
			}
		}
	}


	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.ctx.ISecurityContextContainer#deleteContext(java.lang.String)
	 */
	@SuppressWarnings("unchecked")
	public SecurityContext deleteContext(String secCtxId) throws SecurityContextContainerException {
		if (secCtxId == null)
			return null;
		else {
			String removedResourceId = (String) getKeyFromAuxResourceToSecCtxMap(secCtxId);
			if (removedResourceId!=null)
				log.info("Removing secCtx for resource: " + removedResourceId);
			else
				log.info("Removing secCtx for resource which doesn't exist in auxResourceToSecCtxMap!");

			Map<String, SecurityContext> securityContexts;
			try {
				SecurityContext removedSecCtx = null;
				synchronized(cache) {
					securityContexts = (Map<String, SecurityContext>) cache.getFromCache(A2Constants.CACHE_SECURITY_CONTEXTS);
					removedSecCtx = securityContexts.remove(secCtxId);
//						after IS integration secCtxId is not set in SecCtx.secCtxId
					if (removedSecCtx!=null)
						removedSecCtx.setSecCtxId(secCtxId);
					cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, securityContexts);
				}
				removeValueFromAuxResourceToSecCtxMap(secCtxId);
				if (removedSecCtx==null) {
//						even if doesn't exist in cache it might be stored in IS
					String storedSecCtxStr = null;
					try {
						storedSecCtxStr = lookUpService.getResourceProfile(secCtxId);
					} catch (ISLookUpDocumentNotFoundException e) {
						log.info("Couldn't find scurityContext in IS for secCtxId: "+secCtxId);
					} catch (ISLookUpException e) {
						throw new SecurityContextContainerException("Exception occured when trying " +
								"to find securityContext (for secCtxId: "+secCtxId+")!",e);
					}
					
					if (storedSecCtxStr==null || storedSecCtxStr.length()==0)
						return null;
			        
//			      	preparing unmarshaller for SecurityContext
			        Unmarshaller unmarshaller = new Unmarshaller(SecurityContextWrapper.class);
			        try {
			            unmarshaller.setIgnoreExtraElements(true);
						unmarshaller.setMapping(mapping);
					} catch (MappingException e) {
						log.error("Exception occured when setting mappings for marshaller!",e);
					}
					try {
						SecurityContextWrapper storedSecCtxWrapper = (SecurityContextWrapper) unmarshaller.unmarshal(new StringReader(storedSecCtxStr));
						if (storedSecCtxWrapper!=null && storedSecCtxWrapper.getSecCtx()!=null) {
							removedSecCtx = storedSecCtxWrapper.getSecCtx();
//								after IS integration secCtxId is not set in SecCtx.secCtxId
							removedSecCtx.setSecCtxId(secCtxId);
						}
					} catch (MarshalException e) {
						throw new SecurityContextContainerException("Exception occured while unmarshalling SecurityContext: "+secCtxId+"!",e);
					} catch (ValidationException e) {
						throw new SecurityContextContainerException("Exception occured while unmarshalling SecurityContext: "+secCtxId+"!",e);
					}
					
				}
				try {
					boolean check = registryService.deleteProfile(secCtxId);
					if (!check) {
						log.error("Some problems occured while removing secCtx from ISRegistry! " +
								"SecCtxId: "+secCtxId+". Possible cause: SecCtx doesn't exist in IS.");
					}
				} catch (ISRegistryDocumentNotFoundException e) {
					log.info("Some problems occured while removing secCtx from ISRegistry! " +
							"SecCtxId: "+secCtxId+". Possible cause: SecCtx doesn't exist in IS.",e);
				} catch (ISRegistryException e) {
					throw new SecurityContextContainerException("Exception occured while deleting " +
							"profile for profId: "+secCtxId,e);
				}
				return removedSecCtx;
			} catch (NeedsRefreshException e) {
				throw new SecurityContextContainerException("Exception occured while reading/writing SecurityContext from/to a cache. SecCtxId "+secCtxId+".",e);
			} 
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.ctx.ISecurityContextContainer#queryContexts(java.lang.String[])
	 */
	@SuppressWarnings("unchecked")
	public SecurityContext[] queryContexts(String[] secCtxIds) throws SecurityContextContainerException{
		if (secCtxIds == null)
			return null;
		SecurityContext[] secCtxs = new SecurityContext[secCtxIds.length];
		List<SecurityContext> secCtxsToStore = new ArrayList<SecurityContext>();
		
		try {
			Map<String, SecurityContext> securityContexts = null;
			synchronized(cache) {
				securityContexts = (Map<String, SecurityContext>) cache.getFromCache(A2Constants.CACHE_SECURITY_CONTEXTS);
			}
			for (int i=0; i<secCtxIds.length; i++) {
				if (secCtxIds[i]==null) continue;
				SecurityContext foundSecCtx = securityContexts.get(secCtxIds[i]);
				if (foundSecCtx==null) {
//						checking for secCtx inside IS
					String storedSecCtxStr = null;
					try {
						storedSecCtxStr = lookUpService.getResourceProfile(secCtxIds[i]);
					} catch (ISLookUpDocumentNotFoundException e) {
						log.info("Couldn't find scurityContext in IS for secCtxId: "+secCtxIds[i]);
					} catch (ISLookUpException e) {
						throw new SecurityContextContainerException("Exception occured when trying to " +
								"find securityContext (for secCtxId: "+secCtxIds[i]+")!",e);
					}
					
					if (storedSecCtxStr!=null && storedSecCtxStr.length()>0) {
//					      	preparing unmarshaller for SecurityContext
				        Unmarshaller unmarshaller = new Unmarshaller(SecurityContextWrapper.class);
				        try {
				            unmarshaller.setIgnoreExtraElements(true);
							unmarshaller.setMapping(mapping);
						} catch (MappingException e) {
							log.error("Exception occured when setting mappings for marshaller!",e);
						}
						try {
							SecurityContextWrapper foundSecCtxWrapper = (SecurityContextWrapper) unmarshaller.unmarshal(new StringReader(storedSecCtxStr));
							if (foundSecCtxWrapper!=null && foundSecCtxWrapper.getSecCtx()!=null) {
								foundSecCtx = foundSecCtxWrapper.getSecCtx();
								foundSecCtx.setSecCtxId(foundSecCtxWrapper.getHeader().getProfId());
							}
							if (foundSecCtx!=null) {
//								prepare secCtx for updating in cache
								secCtxsToStore.add(foundSecCtx);
							}
						} catch (MarshalException e) {
							throw new SecurityContextContainerException("Exception occured while unmarshalling SecurityContext: "+secCtxIds[i]+"!",e);
						} catch (ValidationException e) {
							throw new SecurityContextContainerException("Exception occured while unmarshalling SecurityContext: "+secCtxIds[i]+"!",e);
						}
					}
				}
				secCtxs[i] = foundSecCtx;
			}
			if (secCtxsToStore.size()>0) {
				synchronized(cache) {
//					store secCtx found in IS
					log.info("Found " +secCtxsToStore.size()+ " secCtxs in IS when performing queryContexts. Fetching into cache...");
					Map<String, SecurityContext> updatedSecurityContexts = (Map<String, SecurityContext>) cache.getFromCache(A2Constants.CACHE_SECURITY_CONTEXTS);
					Iterator<SecurityContext> itSecCtx = secCtxsToStore.iterator();
					while (itSecCtx.hasNext()) {
						SecurityContext secCtxToStore = itSecCtx.next();
						SecurityContext secCtxFromCache = updatedSecurityContexts.get(secCtxToStore.getSecCtxId());
						if (secCtxFromCache==null || secCtxFromCache.getCreationTime()<secCtxToStore.getCreationTime()) {
							updatedSecurityContexts.put(secCtxToStore.getSecCtxId(), secCtxToStore);
							putInAuxResourceToSecCtxMap(secCtxToStore.getResourceId(), secCtxToStore.getSecCtxId());
							subscribe(secCtxToStore.getSecCtxId());
						} else
							log.warn("SecurityContext " +secCtxToStore.getSecCtxId()+ " already exists in cache and its creation time is equal or grater than secCtx to store.");
						
					}
					cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, updatedSecurityContexts);
				}
			}

		} catch (NeedsRefreshException e) {
			throw new SecurityContextContainerException("Exception occured while reading/writing SecurityContext from/to a cache",e);
		}
		return secCtxs;
	}


	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.admin.ISecurityContextContainerAdministration#dropAllSecCtxs()
	 */
	public void dropAllSecCtxs() throws SecurityContextContainerException {
		clearAuxResourceToSecCtxMap();
		synchronized(cache) {
			cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, new HashMap<String,SecurityContext>());
		}
		removeAllSecCtxsFromIS();
	}

	
	void removeAllSecCtxsFromIS() throws SecurityContextContainerException {
//		TODO WARNING: operations on IS don't provide transactions, create flag variable!
		W3CEndpointReference epr = null;
		try {
			String query = QueryProvider.findAllSecurityContextProfIds(); 
			epr = lookUpService.searchProfile(query);
		} catch (ISLookUpException e) {
			throw new SecurityContextContainerException("Exception occured when listing security profiles!",e);
		} 
		List<String> array1Res = null;
		try {
			String rsId = ISUtils.extractResultSetId(epr);
			if (rsId==null)
				return;
//			TODO resultset service might be run via epr - might be slower but allows using multiple RS services
			int resultsCount = resultSetService.getNumberOfElements(rsId);
			array1Res = resultSetService.getResult(rsId, ISConstants.RESULT_SET_FIRST_ELEMENT, resultsCount, ISConstants.RESULT_SET_REQUEST_MODE_WAITING);
		} catch (ResultSetException e) {
			throw new SecurityContextContainerException("Exception occured when retrieving results from result service!",e);
		} 
		if (array1Res==null || array1Res.size()==0) {
			log.warn("Got empty result when looking for all security contexts in IS!");
			return;
		}
		try {
			registryService.deleteProfiles(prepareProfIds(array1Res));
		} catch (ISRegistryException e) {
			throw new SecurityContextContainerException("Exception occured when trying to delete all security contexts from IS!",e);
		} catch (Exception e) {
			throw new SecurityContextContainerException("Exception occured when trying to delete all security contexts from IS!",e);
		} 
	}

	/**
	 * Prepares suitable array of profIds for deleting.
	 * @param sourceArray
	 * @return Array1 containing profIds
	 * @throws ParserConfigurationException 
	 * @throws IOException 
	 * @throws SAXException 
	 */
	List<String> prepareProfIds(List<String> sourceArray) throws ParserConfigurationException, SAXException, IOException {
		List<String> results = new ArrayList<String>(sourceArray.size());
		DocumentBuilderFactory factory =
            DocumentBuilderFactory.newInstance();
        factory.setIgnoringComments(true);
        DocumentBuilder db = null;
        factory.setNamespaceAware(true);
        factory.setValidating(false);
        db = factory.newDocumentBuilder();
		for (int i=0; i<sourceArray.size(); i++) {
			results.add(convertProfId(sourceArray.get(i),db));
		}
		return results;
	}
	
	String convertProfId(String sourceProfId, DocumentBuilder db) throws SAXException, IOException {
		Document doc = db.parse(new InputSource(new StringReader(sourceProfId)));
		if (doc.getDocumentElement().hasAttribute("value"))
			return doc.getDocumentElement().getAttribute("value");
		else
			return null;
	}
	
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.admin.ISecurityContextContainerAdministration#dumpBufferedSecCtxs(java.io.OutputStream)
	 */
	@SuppressWarnings("unchecked")
	public void dumpBufferedSecCtxs(OutputStream out) throws SecurityContextContainerException {
		PrintStream output = new PrintStream(out);
		try {
			Marshaller marshaller = new Marshaller();
			marshaller.setMapping(mapping);
			Map<String,SecurityContext> securityContexts = null;
			synchronized(cache) {
				securityContexts = (Map<String, SecurityContext>) cache.getFromCache(A2Constants.CACHE_SECURITY_CONTEXTS);
			}
			Set<String> keys = securityContexts.keySet();
			Iterator<String> keysIt = keys.iterator();
			while (keysIt.hasNext()) {
				StringWriter strWriter = new StringWriter();
				marshaller.setWriter(strWriter);
				marshaller.marshal(securityContexts.get(keysIt.next()));
				output.println(strWriter.toString());
			}
			
		} catch (NeedsRefreshException e) {
			throw new SecurityContextContainerException("Exception occured when getting security contexts from cache!",e);
		} catch (IOException e) {
			throw new SecurityContextContainerException("Exception occured when marshalling security contexts!",e);
		} catch (MappingException e) {
			throw new SecurityContextContainerException("Exception occured when loading mappings for marshalling security contexts!",e);
		} catch (MarshalException e) {
			throw new SecurityContextContainerException("Exception occured when marshalling security contexts!",e);
		} catch (ValidationException e) {
			throw new SecurityContextContainerException("Exception occured when marshalling security contexts!",e);
		}
	}


	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.admin.ISecurityContextContainerAdministration#invalidateCache()
	 */
	public void invalidateCache() {
		synchronized(cache) {
			cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, new HashMap<String,SecurityContext>());
		}
		clearAuxResourceToSecCtxMap();
	}
	

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.nh.rmi.ISecurityContextNotificationManager#deleteSecurityContext(java.lang.String)
	 */
	@SuppressWarnings("unchecked")
	public void deleteSecurityContext(String profileContent) throws A2Exception {
		if (profileContent==null)
			throw new A2Exception("Null profileContent provided!");
		
		try {
//	      	preparing unmarshaller for SecurityContext
	        Unmarshaller unmarshaller = new Unmarshaller(SecurityContextWrapper.class);
	        try {
	            unmarshaller.setIgnoreExtraElements(true);
				unmarshaller.setMapping(mapping);
			} catch (MappingException e) {
				log.error("Exception occured when setting mappings for marshaller!",e);
			}
			SecurityContextWrapper secCtxWrapper = (SecurityContextWrapper) unmarshaller.unmarshal(new StringReader(profileContent));
			if (secCtxWrapper!=null && secCtxWrapper.getSecCtx()!=null 
					&& secCtxWrapper.getSecCtx().getResourceId()!=null
					&& secCtxWrapper.getHeader().getProfId()!=null) {
				boolean deleteAuxMap = false;
				synchronized(cache) {
					Map<String,SecurityContext> secCtxCache = null;
					secCtxCache = (Map<String,SecurityContext>) cache.getFromCache(A2Constants.CACHE_SECURITY_CONTEXTS);
					SecurityContext storedSecCtx = secCtxCache.get(secCtxWrapper.getHeader().getProfId());
					if (storedSecCtx != null) {
//						checking if the same version of secCtx
						if (storedSecCtx.getCreationTime()==secCtxWrapper.getSecCtx().getCreationTime()) {
							log.info("SecCtx found in cache for received notification, removing secCtx: " + secCtxWrapper.getHeader().getProfId());
							deleteAuxMap = true;
							SecurityContext removedContext = secCtxCache.remove(secCtxWrapper.getHeader().getProfId());
							if (removedContext!=null)
								cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, secCtxCache);
						} else {
							log.warn("Got DELETE SecurityContext notification but stored context lastAccessTime is different than the one from notification!");
						}
					}
				}
				if (deleteAuxMap)
					removeValueFromAuxResourceToSecCtxMap(secCtxWrapper.getHeader().getProfId());

			} else {
				throw new A2Exception("Invalid Security Context unmarshalled from profileContent: "+profileContent);
			}
		} catch (MarshalException e) {
			throw new A2Exception("Exception occured during converting xml to SecurityProfile!",e);
		} catch (ValidationException e) {
			throw new A2Exception("Exception occured during converting xml to SecurityProfile!",e);
		} catch (NeedsRefreshException e) {
			throw new A2Exception("Exception occured when getting security profiles from cache!",e);
		}

	}


	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.nh.rmi.ISecurityContextNotificationManager#updateSecurityContext(java.lang.String)
	 */
	@SuppressWarnings("unchecked")
	public void updateSecurityContext(String profileContent) throws A2Exception {
		if (profileContent==null)
			throw new A2Exception("Null profileContent provided!");
        
//      preparing unmarshaller for SecurityContext
        Unmarshaller unmarshaller = new Unmarshaller(SecurityContextWrapper.class);
        try {
            unmarshaller.setIgnoreExtraElements(true);
			unmarshaller.setMapping(mapping);
		} catch (MappingException e) {
			log.error("Exception occured when setting mappings for marshaller!",e);
		}
		try {
			SecurityContextWrapper secCtxWrapper = (SecurityContextWrapper) unmarshaller.unmarshal(new StringReader(profileContent));
			if (secCtxWrapper!=null && secCtxWrapper.getSecCtx()!=null 
					&& secCtxWrapper.getSecCtx().getResourceId()!=null
					&& secCtxWrapper.getHeader().getProfId()!=null) {
				boolean updateAuxMap = false;
				synchronized(cache) {
					Map<String,SecurityContext> secCtxCache = null;
					secCtxCache = (Map<String,SecurityContext>) cache.getFromCache(A2Constants.CACHE_SECURITY_CONTEXTS);
					SecurityContext storedSecCtx = secCtxCache.get(secCtxWrapper.getHeader().getProfId());
					if (storedSecCtx==null || secCtxWrapper.getSecCtx().getCreationTime() > storedSecCtx.getCreationTime()) {
						log.info("Notification received, updating secCtx: " + secCtxWrapper.getHeader().getProfId());
						secCtxWrapper.getSecCtx().setSecCtxId(secCtxWrapper.getHeader().getProfId());
						secCtxCache.put(secCtxWrapper.getHeader().getProfId(), secCtxWrapper.getSecCtx());
						cache.putInCache(A2Constants.CACHE_SECURITY_CONTEXTS, secCtxCache);
						updateAuxMap = true;
					} else {
						log.info("Got UPDATE SecurityContext notification but stored context lastAccessTime is equal or greater than the one from update!");
					}
				}
				if (updateAuxMap) {
					putInAuxResourceToSecCtxMap(secCtxWrapper.getSecCtx().getResourceId(), secCtxWrapper.getHeader().getProfId());
				}
			} else {
				throw new A2Exception("Invalid Security Context unmarshalled from profileContent: "+profileContent);
			}
		} catch (MarshalException e) {
			throw new A2Exception("Exception occured during converting xml to SecurityProfile!",e);
		} catch (ValidationException e) {
			throw new A2Exception("Exception occured during converting xml to SecurityProfile!",e);
		} catch (NeedsRefreshException e) {
			throw new A2Exception("Exception occured when getting security profiles from cache!",e);
		}

	}
	
	/**
	 * Subscribes to the UPDATE and DELETE topics with profId given as parameter.
	 * @param profId
	 */
	private void subscribe(String profId) {
		notificationSubscriber.subscribe(NotificationConstants.TOPIC_PREFIX_DELETE, 
				NotificationConstants.SEC_CONTEXT_RESOURCE_TYPE, profId);
		notificationSubscriber.subscribe(NotificationConstants.TOPIC_PREFIX_UPDATE, 
				NotificationConstants.SEC_CONTEXT_RESOURCE_TYPE, profId);
	}
	
	/**
	 * Sets securityUtils for SecurityContext container.
	 * @param securityUtils
	 */
	public void setSecurityUtils(SecurityUtils securityUtils) {
		this.securityUtils = securityUtils;
	}
	
	/**
	 * Returns binding mappings.
	 * @return binding mappings
	 */
	public List<String> getBindings() {
		return bindings;
	}

	/**
	 * Sets xml to SecurityProfile object binding mappings.
	 * @param bindings
	 */
	public void setBindings(List<String> bindings) {
		this.bindings = bindings;
	}

	/**
	 * Returns global cache.
	 * @return global cache
	 */
	public Cache getCache() {
		return cache;
	}

	/**
	 * Sets cache.
	 * @param cache
	 */
	public void setCache(Cache cache) {
		this.cache = cache;
	}


	/**
	 * Returns IS LookUp service.
	 * @return IS LookUp service
	 */
	public ISLookUpService getLookUpService() {
		return lookUpService;
	}


	/**
	 * Sets IS LookUp service.
	 * @param lookUpService
	 */
	public void setLookUpService(ISLookUpService lookUpService) {
		this.lookUpService = lookUpService;
	}


	/**
	 * Returns IS Registry service.
	 * @return IS Registry service
	 */
	public ISRegistryService getRegistryService() {
		return registryService;
	}


	/**
	 * Sets IS Registry service.
	 * @param registryService
	 */
	public void setRegistryService(ISRegistryService registryService) {
		this.registryService = registryService;
	}


	/**
	 * Returns ResultSet service.
	 * @return ResultSet service
	 */
	public ResultSetService getResultSetService() {
		return resultSetService;
	}


	/**
	 * Sets ResultSet service.
	 * @param resultSetService
	 */
	public void setResultSetService(ResultSetService resultSetService) {
		this.resultSetService = resultSetService;
	}


	/**
	 * Returns notification subscriber.
	 * @return notification subscriber
	 */
	public INotificationSubscriber getNotificationSubscriber() {
		return notificationSubscriber;
	}


	/**
	 * Sets  notification subscriber.
	 * @param notificationSubscriber
	 */
	public void setNotificationSubscriber(
			INotificationSubscriber notificationSubscriber) {
		this.notificationSubscriber = notificationSubscriber;
	}
	
	
//	section of synchronized methods for maintaning aux map
	
	protected synchronized Object getFromAuxResourceToSecCtxMap(String key) {
		return auxResourceToSecCtxMap.get(key);
	}
	
	protected synchronized Object getKeyFromAuxResourceToSecCtxMap(String value) {
		return auxResourceToSecCtxMap.getKey(value);
	}
	
	protected synchronized boolean checkKeyInAuxResourceToSecCtxMap(String key) {
		return auxResourceToSecCtxMap.containsKey(key);
	}

	protected synchronized Object putInAuxResourceToSecCtxMap(String key, String value) 
		throws SecurityContextContainerException {
		if (key==null || key.length()==0
				|| value==null || value.length()==0) {
			throw new SecurityContextContainerException("neither key nor value can be " +
					"null or empty! Got key: " + key + ", value: " + value);
		} else {
			return auxResourceToSecCtxMap.put(key,value);
		}
	}
	
	protected synchronized Object removeValueFromAuxResourceToSecCtxMap(String value) {
		return auxResourceToSecCtxMap.removeValue(value);
	}
	
	protected synchronized void clearAuxResourceToSecCtxMap() {
		auxResourceToSecCtxMap.clear();
	}

}
