/**
 * Copyright 2008-2009 DRIVER PROJECT (Bielefeld University)
 * Original author: Marek Imialek <marek.imialek at uni-bielefeld.de>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.dnetlib.data.sts.ds;

import java.io.StringReader;
import java.util.List;
import java.util.Set;

import javax.jws.WebService;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import eu.dnetlib.common.interfaces.nh.INotificationHandler;
import eu.dnetlib.data.sts.constants.DepotServiceConstants;
import eu.dnetlib.data.sts.ds.ws.def.IDepotServiceDef;
import eu.dnetlib.data.sts.ds.ws.nh.NotificationConstants;
import eu.dnetlib.data.sts.ds.ws.store.IDepotServiceStorage;

/**
 * Driver Meta Data Store Service Facade.
 * 
 * @author <a href="mailto:marek.imialek at uni-bielefeld.de">Marek Imialek</a>
 * @version
 */
@WebService
public class DepotServiceFacade implements IDepotService {

	protected static final Logger log = Logger
	.getLogger(DepotServiceFacade.class);

	/** default store data structure size (MB). */
	private long defaultStDsSize;

	/** max store data structure size (MB). */
	private long maxStDsSize;

	/**
	 * Enables/disables checking notification producer.
	 */
	private boolean checkingNotificationProducersEnabled = false;

	/**
	 * Set of allowed notification producers.
	 */
	private Set<String> allowedNotificationProducers;

	/**
	 * Current Store service version.
	 */
	private String serviceVersion;

	/**
	 * Result Set service location.
	 */
	private String resultSetLocation;

	/**
	 * Notification handler module.
	 */
	private INotificationHandler storeNotificationHandler;

	/**
	 * Data Storage interface
	 */
	private IDepotServiceStorage dataStorage;

	/**
	 * StoreDef interface
	 */
	private IDepotServiceDef stsDef;

	/** Storing Iterator queue size. */
	private int rsQueueSize;

	/** Storing Iterator single call result size. */
	private int rsPackageSize;

	/** Maximum timeout (in seconds) 
	 * for retrieving single result from 
	 * ResultSet while storing records. 
	 */
	long maxRSIteratorTimeout;

	private boolean useIS;
	
	/**
	 * Spring init method.
	 */
	public void init() {}

	/*
	 * (non-Javadoc)
	 * 
	 * @see eu.dnetlib.data.sts.ds.IDepotService#identify()
	 */
	public String identify() {
		return getServiceVersion();
	}

	/* StS-DEF FUNCTIONALITY */

	/*
	 * (non-Javadoc)
	 * 
	 * @see eu.dnetlib.data.sts.ds.ws.def.IDepotServiceDef#createStore()
	 */
	public String createStore(List<String> predefinedObjectTypes, long sizeStDS)
		throws DepotServiceException {

		if (sizeStDS == 0) {
			sizeStDS = defaultStDsSize;
		}

		if (sizeStDS > maxStDsSize) {
			sizeStDS = maxStDsSize;
		}
		try {
			return stsDef.createStore(predefinedObjectTypes, sizeStDS);
		} catch (Exception e) {
			throw (DepotServiceException) new DepotServiceException(
				"Creating Store Data Strucrure:").initCause(e); 
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see eu.dnetlib.data.sts.ds.ws.def.IDepotServiceDef#updateStore()
	 */
	public boolean updateStore(String stId, long sizeStDS,
			List<String> predefinedObjectTypes) throws DepotServiceException {

		if (stId.equals(null)) {
			String errorContent = "Data structure identifier "
				+ "can not be null!";
			throw new DepotServiceException(errorContent);
		}

		if (sizeStDS == 0) {
			sizeStDS = defaultStDsSize;
		}

		if (sizeStDS > maxStDsSize) {
			sizeStDS = maxStDsSize;
		}

		try {
			return stsDef.updateStore(stId, sizeStDS, predefinedObjectTypes);
		} catch (Exception e) {
			throw (DepotServiceException) new DepotServiceException(
				"Updating Store Data Structure:").initCause(e); 
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see eu.dnetlib.data.sts.ds.ws.def.IDepotServiceDef#deleteStore()
	 */
	public boolean deleteStore(String stId) throws DepotServiceException {

		try {
			return stsDef.deleteStore(stId);
		} catch (Exception e) {
			throw (DepotServiceException) new DepotServiceException(
				"Deleting Store Data Structure:").initCause(e); 
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * eu.dnetlib.data.sts.ds.ws.def.IDepotServiceDef#deleteStoreObject(java
	 * .lang.String, java.util.List)
	 */
	public boolean deleteStoreObject(String stId, List<String> stObjectIds)
		throws DepotServiceException {

		try {
			return stsDef.deleteStoreObject(stId, stObjectIds);
		} catch (Exception e) {
			throw (DepotServiceException) new DepotServiceException(
					"Deleting Store Object:").initCause(e); 
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * eu.dnetlib.data.sts.ds.ws.def.IDepotServiceDef#manageDASReplicas(java
	 * .util.HashMap)
	 */
	public String updateDASReplica(String replicaUrl, String operation)
		throws DepotServiceException {
		
		if (useIS) {
			if (replicaUrl == null || operation == null)
				throw new DepotServiceException("Neither replica url nor operation " +
					"parameter can not be empty");
			log.info("Updating DAS replica entry in service profile: " +
					"operation:" + operation + " , url:" +replicaUrl);
			try {
				return stsDef.updateDASReplica(replicaUrl, operation);
			} catch (Exception e) {
				log.error(e);
				throw new DepotServiceException(e);
			}
		} else {
			throw new DepotServiceException ("This method is not supported in the individual mode." +
					" If you want to use this functionality, switch on the Information Service" +
					" communication mode in properties file (services.sts.ds.is.operations = true)");
		}
		
	}

	/* StS-STORE FUNCTIONALITY */

	/* (non-Javadoc)
	 * @see eu.dnetlib.data.sts.ds.ws.store.IDepotServiceStorage#storeObjectsFromRS(
	 * 	java.lang.String, javax.xml.ws.wsaddressing.W3CEndpointReference, java.lang.String, boolean)
	 */
	public String storeObjectsFromRS(String stId, W3CEndpointReference rsEPR,
			String storingType, boolean simpleDownload) throws DepotServiceException {

		if (stId == null)
			throw new DepotServiceException("Wrong parameter! " +
					"StDS identifier can not be null!");
		if (rsEPR == null)
			throw new DepotServiceException("Wrong parameter! " +
					"ResultSet EPR can not be null!");
		if (storingType == null)
			throw new DepotServiceException("Wrong parameter!" +
					"Storing type can not be null!");

		if (!storingType.equals(DepotServiceConstants.STORING_TYPE_REFRESH) 
				&& !storingType.equals(
						DepotServiceConstants.STORING_TYPE_INCREMENTAL))
			throw new DepotServiceException("Wrong storing type " +
					"parameter! Only '"+
					DepotServiceConstants.STORING_TYPE_REFRESH+
					"' or '"+
					DepotServiceConstants.STORING_TYPE_INCREMENTAL+
					"' " +"values are accepted.");

		log.info("METHOD storeObjectsFromRS has "
				+ "been issued with the following parameters: ");
		log.info("Store Id        : " + stId);
		log.info("Storing Type    : " + storingType);
		log.info("Simple Download : " + simpleDownload);
		log.info("ResultSet EPR   : " + rsEPR);
		
		return this.dataStorage.storeObjectsFromRS(stId,rsEPR,storingType, simpleDownload);
	}

	
	/* (non-Javadoc)
	 * @see eu.dnetlib.data.sts.ds.ws.store.IDepotServiceStorage#storeObjects(
	 * 	java.lang.String, java.util.List, java.lang.String, boolean)
	 */
	public String storeObjects(String stId, List<String> objectsForStoring,
			String storingType, boolean simpleDownload) throws DepotServiceException {

		if (stId == null)
			throw new DepotServiceException("Wrong parameter! "
					+ "StDS identifier can not be null!");
		if (objectsForStoring == null)
			throw new DepotServiceException("Wrong parameter! "
					+ "Storing objects list can not be null!");
		if (storingType == null)
			throw new DepotServiceException("Wrong parameter!"
					+ "Storing type can not be null!");

		if (!storingType.equals(DepotServiceConstants.STORING_TYPE_REFRESH)
				&& !storingType
				.equals(DepotServiceConstants.STORING_TYPE_INCREMENTAL))
			throw new DepotServiceException("Wrong storing type "
					+ "parameter! Only '"
					+ DepotServiceConstants.STORING_TYPE_REFRESH + "' or '"
					+ DepotServiceConstants.STORING_TYPE_INCREMENTAL + "' "
					+ "values are accepted.");

		log.info("METHOD storeObjects has "
				+ "been issued with the following parameters: ");
		log.info("Store Id        : " + stId);
		log.info("Objects number  : " + objectsForStoring.size());
		log.info("Storing Type    : " + storingType);
		log.info("Simple Download : " + simpleDownload);
		
		return this.dataStorage.storeObjects(stId,objectsForStoring,storingType, simpleDownload);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see eu.dnetlib.data.sts.ds.ws.store.IDataStorage#storeingCallback()
	 */
	public String storingCallback(String stId, String actionId,
			boolean completeStoringStatusProfile) throws DepotServiceException {

		return this.dataStorage.
			storingCallback(stId, actionId, completeStoringStatusProfile);
	}

	/* StS-NH FUNCTIONALITY */
	/*
	 * (non-Javadoc)
	 * 
	 * @see eu.dnetlib.data.sts.ds.ws.nh.INotificationHandler#notify()
	 */
	public boolean notify(String subscrId, String topic, String isId,
			String message) {

		log.info("Notification has been issued with the "
				+ "following parameters:\n");
		log.debug("subscrId: " + subscrId);
		log.debug("topic: " + topic);
		log.debug("isId: " + isId);
		log.debug("message: " + message + "\n ");

		if (isCheckingNotificationProducersEnabled()) {
			if (!checkNotificationProducer(isId))
				return false;
		} else
			log.info("Checking notification producer address is disabled");

		if (checkIfMDFormatNotification(topic))
			return true;// mdFormatNotificationHandler.notify(subscrId, topic,
		// isId, message);
		else
			return this.storeNotificationHandler.notify(subscrId, topic, isId,
					message);
	}

	/**
	 * Checks if notification producer is allowed.
	 * 
	 * @param isId
	 * @return true if notification producer is allowed
	 */
	private boolean checkNotificationProducer(String isId) {
		if (isId == null) {
			log.warn("Couldn't find notification producer address. Null isId!");
			return false;
		}
		String npAddress = extractAddressFromIsId(isId);
		if (npAddress == null) {
			log.error("No notification producer address found");
			return false;
		}
		if (this.allowedNotificationProducers.contains(npAddress))
			return true;
		else {
			log.warn("Notifications issued by " + npAddress
					+ " are not allowed!");
			return false;
		}
	}

	/**
	 * Extracts ISSN service address from isId notification part.
	 * 
	 * @param isId
	 * @return service address
	 */
	private String extractAddressFromIsId(String isId) {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
			.newInstance();
			factory.setIgnoringComments(true);
			DocumentBuilder db = null;
			factory.setNamespaceAware(true);
			factory.setValidating(false);
			db = factory.newDocumentBuilder();
			Document doc = db.parse(new InputSource(new StringReader(isId)));
			String addressTagName = "wsa:Address";
			NodeList nodeList = doc.getElementsByTagName(addressTagName);

			if (nodeList.getLength() != 1) {
				log
				.error("Invalid list of nodes for " + addressTagName
						+ " element. Expected 1, found:"
						+ nodeList.getLength());
				return null;
			}

			Element addressElement = (Element) nodeList.item(0);
			if (addressElement == null
					|| addressElement.getFirstChild() == null
					|| addressElement.getFirstChild().getTextContent() == null) {

				log.error("Couldn't find " + addressTagName + " element!");
				return null;
			}

			return addressElement.getFirstChild().getTextContent();

		} catch (Exception e) {
			log.error("Couldn't parse isId content!", e);
			return null;
		}
	}

	/**
	 * Checks if notification topic applies to MDFormatDSResourceType.
	 * 
	 * @param topic
	 * @return true if notification topic applies to MDFormatDSResourceType
	 */
	private boolean checkIfMDFormatNotification(String topic) {
		if (topic != null
				&& (topic
						.startsWith(NotificationConstants.TOPIC_DELETE_MD_FORMAT) || topic
						.startsWith(NotificationConstants.TOPIC_UPDATE_MD_FORMAT)))
			return true;
		else
			return false;
	}

	/**
	 * Returns current service version.
	 * 
	 * @return current service version
	 */
	public String getServiceVersion() {
		return this.serviceVersion;
	}

	/**
	 * Sets current service version.
	 * 
	 * @param serviceVersion
	 */
	public void setServiceVersion(String serviceVersion) {
		this.serviceVersion = serviceVersion;
	}	

	/**
	 * Returns allowed notification producers addresses.
	 * 
	 * @return allowed notification producers addresses
	 */
	public Set<String> getAllowedNotificationProducers() {
		return this.allowedNotificationProducers;
	}

	/**
	 * Sets allowed notification producers addresses.
	 * 
	 * @param allowedNotificationProducers
	 */
	public void setAllowedNotificationProducers(
			Set<String> allowedNotificationProducers) {
		this.allowedNotificationProducers = allowedNotificationProducers;
	}

	/**
	 * Returns true if checking notification producers is enabled.
	 * 
	 * @return true if checking notification producers is enabled
	 */
	public boolean isCheckingNotificationProducersEnabled() {
		return this.checkingNotificationProducersEnabled;
	}

	/**
	 * Sets checking notification producers.
	 * 
	 * @param checkingNotificationProducersEnabled
	 */
	public void setCheckingNotificationProducersEnabled(
			boolean checkingNotificationProducersEnabled) {
		this.checkingNotificationProducersEnabled = checkingNotificationProducersEnabled;
	}

	/**
	 * Returns target Result Set service location.
	 * 
	 * @return target Result Set service location
	 */
	public String getResultSetLocation() {
		return this.resultSetLocation;
	}

	/**
	 * Sets target Result Set service location.
	 * 
	 * @param resultSetLocation
	 */
	public void setResultSetLocation(String resultSetLocation) {
		this.resultSetLocation = resultSetLocation;
	}

	/**
	 * Returns notification handler.
	 * 
	 * @return storeNotificationHandler
	 */
	public INotificationHandler getStoreNotificationHandler() {
		return this.storeNotificationHandler;
	}

	/**
	 * Sets notification handler.
	 * 
	 * @param storeNotificationHandler
	 */
	public void setStoreNotificationHandler(
			INotificationHandler notificationHandler) {
		this.storeNotificationHandler = notificationHandler;
	}

	/**
	 * Returns data storage handler.
	 * 
	 * @return dataStorage
	 */
	public IDepotServiceStorage getDataStorage() {
		return this.dataStorage;
	}

	/**
	 * Sets data storage handler.
	 * 
	 * @param dataStorage
	 */
	public void setDataStorage(IDepotServiceStorage dataStorage) {
		this.dataStorage = dataStorage;
	}

	/**
	 * Returns 'define' interface reference.
	 * 
	 * @return stsDef
	 */
	public IDepotServiceDef getStoreDef() {
		return this.stsDef;
	}

	/**
	 * Sets 'define' interface reference.
	 * 
	 * @param stsDef
	 */
	public void setStoreDef(IDepotServiceDef stsDef) {
		this.stsDef = stsDef;
	}

	/**
	 * Get default data structure size
	 * 
	 * @return the defaultStDsSize
	 */
	public long getDefaultStDsSize() {
		return defaultStDsSize;
	}

	/**
	 * Set default data structure size
	 * 
	 * @param defaultStDsSize
	 *            the defaultStDsSize to set
	 */
	public void setDefaultStDsSize(long defaultStDsSize) {
		this.defaultStDsSize = defaultStDsSize;
	}

	/**
	 * Get max data structure size
	 * 
	 * @return the maxStDsSize
	 */
	public long getMaxStDsSize() {
		return maxStDsSize;
	}

	/**
	 * Set max data structure size
	 * 
	 * @param maxStDsSize
	 *            the maxStDsSize to set
	 */
	public void setMaxStDsSize(long maxStDsSize) {
		this.maxStDsSize = maxStDsSize;
	}
	
	/**
	 * Returns ResultSetIterator single call result size.
	 * 
	 * @return ResultSetIterator single call result size
	 */
	public int getRsPackageSize() {
		return this.rsPackageSize;
	}

	/**
	 * Sets ResultSetIterator single call result size.
	 * 
	 * @param rsPackageSize the rs package size
	 */
	public void setRsPackageSize(int rsPackageSize) {
		this.rsPackageSize = rsPackageSize;
	}

	/**
	 * Returns ResultSetIterator queue size.
	 * 
	 * @return ResultSetIterator queue size
	 */
	public int getRsQueueSize() {
		return this.rsQueueSize;
	}

	/**
	 * Sets ResultSetIterator queue size.
	 * 
	 * @param rsQueueSize the rs queue size
	 */
	public void setRsQueueSize(int rsQueueSize) {
		this.rsQueueSize = rsQueueSize;
	}

	/**
	 * Returns maximum timeout (in seconds) for retrieving single result from
	 * ResultSet while storing records.
	 * 
	 * @return maximum timeout (in seconds) for retrieving single result from
	 * ResultSet while storing records
	 */
	public long getMaxRSIteratorTimeout() {
		return this.maxRSIteratorTimeout;
	}

	/**
	 * Sets maximum timeout (in seconds) for retrieving single result from
	 * ResultSet while storing records.
	 * 
	 * @param maxRSIteratorTimeout the max rs iterator timeout
	 */
	public void setMaxRSIteratorTimeout(long maxRSIteratorTimeout) {
		this.maxRSIteratorTimeout = maxRSIteratorTimeout;
	}
	
	/**
	 * Checks if is use is.
	 * 
	 * @return true, if is use is
	 */
	public boolean getUseIS() {
		return useIS;
	}

	/**
	 * Sets the use is.
	 * 
	 * @param useIS the new use is
	 */
	public void setUseIS(boolean useIS) {
		this.useIS = useIS;
	}

}
