/**
 * 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.lls;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.log4j.Logger;

import pl.edu.icm.yadda.service2.ArchiveContentPartFacade;
import pl.edu.icm.yadda.service2.YaddaObjectID;
import pl.edu.icm.yadda.service2.YaddaObjectMeta.STATUS;
import pl.edu.icm.yadda.service2.catalog.CountingIterator;
import pl.edu.icm.yadda.service2.exception.ServiceException;
import pl.edu.icm.yadda.service2.storage.IStorage.EXECUTION_MODE;
import pl.edu.icm.yadda.service2.storage.operation.DeleteOperation;
import pl.edu.icm.yadda.service2.storage.operation.StorageOperation;
import pl.edu.icm.yadda.service2.storage.operation.DeleteOperation.DeletePolicy;
import pl.edu.icm.yadda.service3.ArchiveObject2Meta;
import pl.edu.icm.yadda.service3.ArchiveObjectFacade;
import pl.edu.icm.yadda.service3.archive.IArchiveFacade2;
import pl.edu.icm.yadda.service3.storage.IStorageFacade2;
import eu.dnetlib.data.sts.constants.DepotServiceConstants;
import eu.dnetlib.common.profile.ProfileHeader;
import eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade;
import eu.dnetlib.data.sts.ds.lls.StoringStatusResponse;
import eu.dnetlib.data.sts.ds.lls.sdo.SimpleDigitalObject;
import eu.dnetlib.data.sts.ds.lls.sdo.SimpleDigitalObject.ObjectContentType;
import eu.dnetlib.data.sts.profile.utils.SDOProfileMarshaller;
import eu.dnetlib.data.sts.profile.sdo.SdoProfileBody;
import eu.dnetlib.data.sts.utils.OssUtils;
import eu.dnetlib.miscutils.datetime.DateUtils;

/**
 * The Class LLSStoreFacade.
 * 
 * @author <a href="mailto:marek.imialek at uni-bielefeld.de">Marek Imialek</a>
 */
public class LLSStoreFacade implements ILLSStoreFacade {

	/** The Constant log. */
	protected static final Logger log = Logger.getLogger(LLSStoreFacade.class);

	/** The Constant oss protocol. */
	public static final String protocol = "yar://";
	
	/** The Constant contentType. */
	public static final String contentType = "BasicContent";

	/** The lls storage facade. */
	private IStorageFacade2 llsStore;

	/** The lls access facade. */
	private IArchiveFacade2 llsAccess;

	/** The data distributor uri. */
	private String dataDistributorUri;
	
	/** The descriptor uri. */
	private String descriptorURI;

	/**
	 * Main storage initialization.
	 * 
	 * @throws Exception the exception
	 */
	public void init() throws Exception {}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#createStoreDataStructure(java
	 * .lang.String)
	 */
	public String createStoreDataStructure(String storeDataStructId)
		throws Exception {

		try {
			YaddaObjectID yaddaStoredDataStructerId = new YaddaObjectID(
					storeDataStructId);
			
			ArchiveObjectFacade checkStoreDataStructure = 
				llsAccess.getOptionalObject(yaddaStoredDataStructerId, null, false,
					false);

			if (checkStoreDataStructure != null
					&& !checkStoreDataStructure.getStatus().isDeleted()) {

				yaddaStoredDataStructerId = checkStoreDataStructure.getId();
				String msg = "The Store Data Structure is already present: "
					+ yaddaStoredDataStructerId +"(STATUS:"+
					checkStoreDataStructure.getStatus()+")";
				log.warn(msg);
			} else {
				if (checkStoreDataStructure != null && 
						!("".equals(checkStoreDataStructure)) &&
							checkStoreDataStructure.getStatus().isDeleted()){
					log.info("Recreating Store Data Structure: "
							+ storeDataStructId);
				}else {
					log.info("Creating new Store Data Structure: "
						+ storeDataStructId);
				}
				
				ArchiveObjectFacade stds = new ArchiveObjectFacade();
				stds.setId(new YaddaObjectID(storeDataStructId));
				stds.setType("ROOT");
				stds.setStructureType("stdsDirectory");
				stds.setTags(new String[] {"stds"});
				yaddaStoredDataStructerId = llsStore.saveObject(protocol
						+ storeDataStructId, stds,
						null, null);

				ArchiveObjectFacade stdsProfiles = new ArchiveObjectFacade();
				stdsProfiles.setId(new YaddaObjectID(UUID.randomUUID()
						.toString()));
				stdsProfiles.setType("DIRECTORY");
				stdsProfiles.setStructureType(
						DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_PROFILES
						+ "Directory");
				
				llsStore.saveObject(protocol+ storeDataStructId + "/"
						+ DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_PROFILES,
						stdsProfiles, null, null);

				ArchiveObjectFacade stdsObjects = new ArchiveObjectFacade();
				stdsObjects.setId(new YaddaObjectID(UUID.randomUUID()
						.toString()));
				stdsObjects.setType("DIRECTORY");
				stdsObjects.setStructureType(
						DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_OBJECTS
						+ "Directory");

				llsStore.saveObject(protocol+ storeDataStructId + "/"
						+ DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_OBJECTS,
						stdsObjects, null, null);

				ArchiveObjectFacade stdsSDOs = new ArchiveObjectFacade();
				stdsSDOs.setId(new YaddaObjectID(UUID.randomUUID().toString()));
				stdsSDOs.setType("DIRECTORY");
				stdsSDOs.setStructureType(DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS
						+ "Directory");

				llsStore.saveObject(protocol+ storeDataStructId+ "/"
						+ DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS,
						stdsSDOs, null, null);
			}

			return yaddaStoredDataStructerId.getId();

		} catch (ServiceException e) {
			log.error("Problem by creating " + "Store Data Structure "
					+ storeDataStructId + " Exception: " + e.getMessage());
			throw (ServiceException) new Exception(
					"Problem by creating " + "Store Data Structure "
					+ storeDataStructId).initCause(e);
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#deleteStoreDataStructure(java
	 * .lang.String)
	 */
	public boolean removeStoreDataStructure(String storeDataStructId)
		throws Exception {

		try {
			ArchiveObjectFacade objectToDelete = 
				llsAccess.getOptionalObject(new YaddaObjectID(
						storeDataStructId),null, false, false);
			
			if (objectToDelete == null) {
				log.warn("Store Data Structure could not be deleted. "
						+ storeDataStructId + " not found");
				return false;
			} else {
				log.debug("Deleting Store Data Structure "+ 
						objectToDelete.getId());
				DeleteOperation deleteOperation = 
					new DeleteOperation(objectToDelete.getId(), 
							DeletePolicy.CASCADE_DELETE);
				llsStore.executeOperation(deleteOperation);
				log.debug("The Store Data Structure " + storeDataStructId
						+ " deleted");
				return true;
			}
		} catch (ServiceException e) {
			log.error("Problem by deleting " + "Store Data Structure "
					+ storeDataStructId + " Exception: " + e.getMessage());
			throw (ServiceException) new Exception(
					"Problem by deleting " + "Store Data Structure "
					+ storeDataStructId).initCause(e);
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#storeObject(java.lang.String,
	 * eu.dnetlib.data.sts.ds.lls.SimpleDigitalObject)
	 */
	public String storeObject(String storeDataStructId, SimpleDigitalObject sdo)
		throws Exception {
		synchronized (this){
		String basicURI = protocol + storeDataStructId 
				+ "/" + sdo.getDataStructureType();
		String objectURI = null;

		try {
			
			String internObjectID = UUID.randomUUID().toString();
			String externObjectID = sdo.getObjectId();
			
			if (externObjectID == null || "".equals(externObjectID))
				externObjectID = sdo.getObjectName();
			
			externObjectID = OssUtils.convertObjectIdentifier(externObjectID);
			
			objectURI = basicURI + "/" + externObjectID;

			ArchiveObjectFacade object = new ArchiveObjectFacade();
			object.setId(new YaddaObjectID(internObjectID));
			object.setType("FILE");
			object.setStructureType(sdo.getDataStructureType());
			
			if (sdo.getObjectContentType() == ObjectContentType.PATH) {
				object.addPart(contentType, 
						sdo.getObjectMimeType(), 
						new FileInputStream(sdo.getObjectLocation()));
			} else {
				object.addPart(contentType, 
						sdo.getObjectMimeType(),sdo.getObjectContent());	
			}
				
			if (sdo.getObjectTags() == null) {
				object.setTags(new String[] { sdo.getObjectMimeType(),
						sdo.getDataStructureType(), storeDataStructId
						+"-"+ sdo.getDataStructureType()});
			} else {
				object.setTags(sdo.getObjectTags());
			}

			if (DepotServiceConstants.
					STORE_DATA_STRUCTURE_SUBTYPE_OBJECTS.
					equals(sdo.getDataStructureType()))  {
			
				//CREATE SDO XML PROFILE
				ArchiveObjectFacade sdoProfileObject = 
					createSDOProfile(sdo, sdo.getObjectId(), objectURI, 
							storeDataStructId);
				
				if (sdoProfileObject == null){
					log.error("Object not stored, " +
						"could not create SDO XML Profile");
					return null;
					
				} else {
					
					String sdoProfileURI = protocol + storeDataStructId + "/" + 
							DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS + "/"
							+ externObjectID + "-" + 
							DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS;

					llsStore.saveObject(sdoProfileURI, sdoProfileObject,
							null, null);
					
				}
				
			}
			
			llsStore.saveObject(objectURI, object, null, null);

			log.debug("The Object '" + objectURI + "' stored in "
					+ "Store Data Structure " + storeDataStructId);
			
			return externObjectID;
				
		} catch (ServiceException e) {
			log.error("Problem by storing object '" + objectURI + "' in "
					+ "Store Data Structure " + storeDataStructId
					+ " ;Exception: " + e);
			throw (ServiceException) new Exception(
					"Problem by storing object '" + objectURI
					+ "'in Store Data Structure " + storeDataStructId +": " + e);
		}
		}

	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#storeObjectsBatch(java.lang
	 * .String, java.util.List)
	 */
	@Override
	public StoringStatusResponse storeObjectsBatch(String storeDataStructureId,
			Iterator<SimpleDigitalObject> listRecordsStoringIterator) 
				throws Exception {
		synchronized (this){
		log.debug("Preparing objects for storing");
		String objectURI = null;
		String sdoProfileURI = null;
		StoringStatusResponse storingResponse = new StoringStatusResponse();
		int processedObjects = 0;
		int ignoreNotAllowedMimeType = 0;
		int inserted = 0;
		int updated = 0;
		int maxObjectsInMem = 20;
		int storedObjects = 0;
		
		try {
			List<StorageOperation> ops = new LinkedList<StorageOperation>();
			HashMap<String,Long> storedObjectTypes = 
				new HashMap<String,Long>();
			
			while (listRecordsStoringIterator.hasNext()){
				
				SimpleDigitalObject sdo = listRecordsStoringIterator.next();
			
				if (sdo == null || (sdo.getObjectName() != null 
						&& "ignorethisobject".equals(sdo.getObjectName())))
					continue;
				
				processedObjects++;
				
				if (sdo.getObjectName() != null 
					&& "NotAllowedMimeType".equals(sdo.getObjectName())) {
					ignoreNotAllowedMimeType++;
					continue;
				}
				
				ArchiveObjectFacade object = new ArchiveObjectFacade();
				String internObjectID = UUID.randomUUID().toString();
				String externObjectID = OssUtils.convertObjectIdentifier(sdo.getObjectId());
				
				objectURI = protocol 
					+ storeDataStructureId + "/" + sdo.getDataStructureType() + "/"
					+ externObjectID;
				
				object.setId(new YaddaObjectID(internObjectID));
				
				object.setType("FILE");
				object.setStructureType(sdo.getDataStructureType());

				if (sdo.getObjectContentType() == ObjectContentType.PATH) {
					object.addPart(contentType, 
							sdo.getObjectMimeType(), 
							new FileInputStream(sdo.getObjectLocation()));
				} else {
					object.addPart(contentType, 
							sdo.getObjectMimeType(),sdo.getObjectContent());	
				}
				
				object.setTags(new String[] { sdo.getObjectMimeType(),
						sdo.getDataStructureType(), storeDataStructureId
						+"-"+ sdo.getDataStructureType()});
				object.setStatus(STATUS.READY);
				
				//CREATE SDO XML PROFILE
				ArchiveObjectFacade sdoProfileObject = 
					createSDOProfile(sdo, sdo.getObjectId(), objectURI,
							storeDataStructureId);

				if (sdoProfileObject == null){
					log.error("Object not stored, " +
						"could not create SDO XML Profile");
				} else {
					ops.add(llsStore.buildSaveOperation(objectURI, object, null, null));
					
					sdoProfileURI = protocol 
						+ storeDataStructureId + "/" + 
						DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS + "/"
						+ externObjectID + "-" + 
						DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS;

					ops.add(llsStore.buildSaveOperation(sdoProfileURI, sdoProfileObject, null, null));
				}
				
				if (checkIfObjectAlreadyStored(objectURI)){
					updated++;
				} else {
					inserted++;
					
					Long currentValue = storedObjectTypes.get(sdo.getObjectMimeType());
					if (currentValue == null)
						currentValue = Long.valueOf("1");
					else
						currentValue++;
					
					storedObjectTypes.put(sdo.getObjectMimeType(), currentValue);	
				}
				
				
				//inserted++;//DELME
				//log.debug("The Object '" + objectURI + "' prepared for "
				//		+ "storing in " + storeDataStructureId);
				
				if (ops.size() >= maxObjectsInMem) {
					log.debug("Sending objects to the LLS");
					Collection<YaddaObjectID> status = 
						this.llsStore.batch(ops, EXECUTION_MODE.TRANSACTIONAL);
					log.debug(status.size() + " elements processed by LLS ("
							+status.size()/2+ " objects and "
							+status.size()/2 +" SDO profiles)");
					storedObjects = storedObjects + status.size();
					ops = new LinkedList<StorageOperation>();
				}
			}
			
			if (ops != null & ops.size()>0) {
				log.debug("Sending objects to the LLS");
				Collection<YaddaObjectID> status = 
					this.llsStore.batch(ops, EXECUTION_MODE.TRANSACTIONAL);
				log.debug(status.size() + " elements processed by LLS ("
						+status.size()/2+ " objects and "
						+status.size()/2 +" SDO profiles)");
				storedObjects = storedObjects + status.size();
			}
			
			storedObjects = storedObjects/2;
			storingResponse.setProcessedObjects(processedObjects);
			storingResponse.setInsertedObjects(inserted);
			storingResponse.setUpdatedObjects(updated);
			storingResponse.setIgnorredObjects(processedObjects-storedObjects);
			storingResponse.setStoredObjectTypes(storedObjectTypes);
			storingResponse.setIgnoreNotAllowedMimeTypes(ignoreNotAllowedMimeType);
			
			return storingResponse;				

		} catch (ServiceException e) {
			String errMsg = "Problem by storing object '" + objectURI + "' in "
					+ "Store Data Structure " + storeDataStructureId
					+ " ;Exception: " + e.getMessage();
			log.error(errMsg);
			
			StringWriter sw = new StringWriter();
	        PrintWriter pw = new PrintWriter(sw, true);
	        e.printStackTrace(pw);
	        pw.flush();
	        sw.flush();
	        log.error(sw.toString());
			throw new Exception (errMsg);
			//throw (ServiceException) new Exception(
			//				errMsg).initCause(e);			
		} catch (Exception e) {
			String errMsg = "Problem by storing object '" + objectURI + "' in "
				+ "Store Data Structure " + storeDataStructureId
				+ " ;Exception: " + e.getMessage();
			log.error(errMsg);
	
			StringWriter sw = new StringWriter();
	        PrintWriter pw = new PrintWriter(sw, true);
	        e.printStackTrace(pw);
	        pw.flush();
	        sw.flush();
	        
	        log.error(sw.toString());
	        throw new Exception (errMsg);
			//throw (ServiceException) new Exception(
				//			sw.toString()).initCause(e);
		}
		}
	}

	/**
	 * Creates the sdo profile.
	 * 
	 * @param sdo the sdo
	 * @param objectID the object id
	 * @param objectUri the object uri
	 * @param storeDataStructId the store data struct id
	 * 
	 * @return the archive object facade
	 * 
	 * @throws Exception the interrupted exception
	 */
	private ArchiveObjectFacade createSDOProfile (
			SimpleDigitalObject sdo, String objectID,
			String objectUri, String storeDataStructId) 
			throws Exception {

		ArchiveObjectFacade sdoObject = new ArchiveObjectFacade();
		
		ProfileHeader header = new ProfileHeader(
				objectID, DepotServiceConstants.SDO_DS_RESOURCE_TYPE, 
				DepotServiceConstants.STORE_DS_RESOURCE_KIND);
		header.setResourceURI(this.getLlsDescriptorUri());
		
		DateUtils date = new DateUtils();
		header.setDateOfCreation(date.getDateAsISO8601String());
		
		SdoProfileBody body = new SdoProfileBody ();
		body.setSdoMimeType(sdo.getObjectMimeType());
		body.setLastModificationDate("");
		
		String externalObjectUri = 
			OssUtils.createExternalObjectUri(objectUri, getLlsDataDistributorUri());
		
		body.setSdoUri(externalObjectUri);		
		log.debug("Object's URI for external access: "+ externalObjectUri);

		String sdoXml = SDOProfileMarshaller.generateSDOProfile(header, body);

		InputStream sdoXmlIS = null;
		try {
			byte[] buffer = sdoXml.getBytes("UTF-8");

			sdoXmlIS = new ByteArrayInputStream(
					buffer);
			
			sdoObject.setId(new YaddaObjectID(UUID.randomUUID().toString()));
			
			sdoObject.setType("FILE");

			sdoObject.setStructureType(
					DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS);

			sdoObject.addPart(contentType, "xml", sdoXmlIS);

			sdoObject.setTags(new String[] { "xml",
					DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS,
					storeDataStructId+"-"+
					DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS});

			sdoObject.setStatus(STATUS.READY);
			
			sdoXmlIS.close(); 
			
			return sdoObject;
	
		} catch (IOException e) {
			String msg = "The following problem occured by creating" +
				" SDO profile " + e.getMessage();
			log.error(msg);
			throw new Exception(msg);
		}
		finally {
			try {
				sdoXmlIS.close();
			}
			catch(IOException e) {
				log.error("Unable to close input " +
					"stream for SDO profile!"+ e.getMessage());
			}
		}
		
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#removeObject(java.lang.String,
	 * eu.dnetlib.data.sts.ds.lls.SimpleDigitalObject)
	 */
	public boolean removeObject(String storeDataStructId,
			SimpleDigitalObject sdo) throws Exception {

		String objectURI = protocol
		+ storeDataStructId + "/" + sdo.getDataStructureType() + "/"
		+ sdo.getObjectName();

		try {
			ArchiveObject2Meta objectToDelete = llsAccess.queryUniqueObject(
					objectURI, true);

			if (objectToDelete == null) {
				log.warn("The Object '" + objectURI + "' can not be deleted. "
						+ "Not found in Store Data Structure "
						+ storeDataStructId);
				return false;
			} else {
				llsStore.deleteObject(objectToDelete.getId());
				log.debug("The Object '" + objectURI + "' deleted from "
						+ "Store Data Structure " + storeDataStructId);
				return true;
			}

		} catch (ServiceException e) {
			log.error("Problem by removing object '" + objectURI + "' from "
					+ "Store Data Structure " + storeDataStructId
					+ " Exception: " + e.getMessage());
			throw (ServiceException) new Exception(
					"Problem by removing '" + objectURI
					+ "' object from Store Data Structure "
					+ storeDataStructId).initCause(e);
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#cleanStoreDataStructure(java
	 * .lang.String)
	 */
	public boolean cleanStoreDataStructure(String storeDataStructureId)
		throws ServiceException {

		log.debug("Cleaning Store Data Structure content: "
				+ storeDataStructureId);

		String objectsURI = protocol + storeDataStructureId + "/"
			+ DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_OBJECTS;

		String sdosURI = protocol +storeDataStructureId + "/"
			+ DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS;

		try {
			ArchiveObject2Meta objectToDelete = llsAccess.queryUniqueObject(
					objectsURI, true);

			if (objectToDelete == null || objectToDelete.getId() == null) {
				log.warn("Object '" + objectsURI + "' can not be deleted. "
						+ "Not found in Store Data Structure "
						+ storeDataStructureId);
			} else {
				DeleteOperation deleteOperation = 
					new DeleteOperation(objectToDelete.getId(), 
							DeletePolicy.CASCADE_DELETE);
				llsStore.executeOperation(deleteOperation);
				log.debug("Object '" + objectsURI + "' deleted from "
						+ "Store Data Structure " + storeDataStructureId);

				ArchiveObjectFacade stdsObjects = new ArchiveObjectFacade();
				stdsObjects.setId(new YaddaObjectID(
						DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_OBJECTS));
				stdsObjects.setType("DIRECTORY");
				stdsObjects.setStructureType(
						DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_OBJECTS
						+ "Directory");
				llsStore.saveObject(protocol+ storeDataStructureId+ "/"
						+ DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_OBJECTS,
						stdsObjects, null, null);
			}

			ArchiveObject2Meta sdosToDelete = llsAccess.queryUniqueObject(
					sdosURI, true);

			if (sdosToDelete == null || sdosToDelete.getId() == null) {
				log.warn("Object '" + sdosURI + "' can not be deleted. "
						+ "Not found in Store Data Structure "
						+ storeDataStructureId);
			} else {
				DeleteOperation deleteSDOSOperation = 
					new DeleteOperation(sdosToDelete.getId(), 
							DeletePolicy.CASCADE_DELETE);
				llsStore.executeOperation(deleteSDOSOperation);
				log.debug("Object '" + sdosURI + "' deleted from "
						+ "Store Data Structure " + storeDataStructureId);

				ArchiveObjectFacade stdsSDOs = new ArchiveObjectFacade();
				stdsSDOs.setId(new YaddaObjectID(
						DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS));
				stdsSDOs.setType("DIRECTORY");
				stdsSDOs.setStructureType(
						DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS
						+ "Directory");
				llsStore.saveObject(protocol+ storeDataStructureId+ "/"
						+ DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS,
						stdsSDOs, null, null);
			}

			log.debug("Empty object containers ("
					+ DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_SDOS
					+ ","
					+ DepotServiceConstants.STORE_DATA_STRUCTURE_SUBTYPE_OBJECTS
					+ ") " + "created in the Store Data Structure");

			return true;

		} catch (ServiceException e) {
			String msg = "Problem by cleaning " + "Store Data Structure "
				+ storeDataStructureId ;
			log.error(msg);
			throw (ServiceException) new Exception(msg).initCause(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#performSimpleSearch(java.lang
	 * .String)
	 */
	public Iterator<String> performSimpleSearch(String[] searchedTags)
		throws Exception {
		
		CountingIterator<ArchiveObject2Meta> response = null;
		try {
			response = 
				llsAccess.listObjects(null, null, searchedTags, false);
		} catch (ServiceException e) {
			throw (ServiceException) new Exception(
			"Failed to search storage").initCause(e);
		}

		if (response == null){
			log.warn("No objects found in the storage!");
			return null;
		}
		
		List<String> results = new ArrayList<String>();
		
		while (response.hasNext()) {
			
			ArchiveObject2Meta object = response.next();
			if (!object.getStatus().isDeleted())
				results.add(object.getId().getId());
		}
		
		if (results == null){
			log.warn("No objects found in the storage!");
			return null;
		}
		else {
			return results.iterator();
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#getSingleObject(java.lang.String)
	 */
	public InputStream getSingleObject (String objectName)
		throws Exception {
		
		ArchiveObjectFacade singleObjectFacade = 
			getSingleObjectFacade(objectName);
		
		ArchiveContentPartFacade contentPartFacade = 
			singleObjectFacade.getPart(contentType);
				
		InputStream is = contentPartFacade.getData();
		
		return is; 
	}
	
	/* (non-Javadoc)1
	 * @see eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#getSingleObjectDetails(java.lang.String)
	 */
	public SimpleDigitalObject getSingleObjectDetails (String objectName)
		throws Exception {
		
		ArchiveObjectFacade singleObjectFacade = 
			getSingleObjectFacade(objectName);
		
		if (singleObjectFacade != null) {
			
			ArchiveContentPartFacade contentPartFacade = 
				singleObjectFacade.getPart(contentType);
		
			if (contentPartFacade != null) {
				SimpleDigitalObject sdo = new SimpleDigitalObject();
				sdo.setObjectMimeType(contentPartFacade.getMime());
				sdo.setDataStructureType(contentPartFacade.getType());
				sdo.setObjectURI(contentPartFacade.getPath());
				sdo.setObjectId(contentPartFacade.getPath());
		
				return sdo;
			} else {
				log.warn("Content of object "+ objectName+ " is empty!");
				return null;
			}	
		} else {
			log.warn("Object "+ objectName+ " not found!");
			return null;
		}	
	}
	
	/**
	 * Gets the single object facade.
	 * 
	 * @param objectName the object name
	 * 
	 * @return the single object facade
	 * 
	 * @throws Exception the exception
	 */
	private ArchiveObjectFacade getSingleObjectFacade (String objectName)
		throws Exception {

		if (!objectName.contains(protocol))
			objectName = protocol + objectName;
		log.debug("Searching for object: " + objectName);

		try {

			ArchiveObject2Meta searchObject = 
				llsAccess.queryUniqueObject(objectName, true);

			if (searchObject != null) {

				ArchiveObjectFacade foundObject = 
					llsAccess.getObject(searchObject.getId(), 
							new String[]{contentType}, false);

				
				return foundObject;
			} else {
				String message = "Object "+objectName+" " +
				"not found in the storage.";
				log.error(message);
				throw new Exception(message);
			}

		}  catch (Exception e1) {
			String msg = "Problem by getting object " +
			objectName + " from the storage: " +e1;
			log.error(msg);
			throw new Exception(e1);
		}	

	}
	

	/**
	 * Check if object exist.
	 * 
	 * @param objectName the object name
	 * 
	 * @return true, if successful
	 * 
	 * @throws Exception the exception
	 */
	@SuppressWarnings("unused")
	private boolean checkIfObjectAlreadyStored (String objectName)
		throws Exception {

		try {
			if (!objectName.contains(protocol))
				objectName = protocol + objectName;
		
			log.debug("Checking if  object exist: " + objectName);

			ArchiveObject2Meta searchObject = 
				llsAccess.queryUniqueObject(objectName, true);

			if (searchObject != null)
				return true;

		} catch (ServiceException e) {
			String msg = "Problem by getting object " +
				objectName + " from the storage: " +e;
			log.debug(msg);
			throw (ServiceException) new Exception(msg).initCause(e);
		} 
		return false;
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.data.sts.ds.lls.ILLSStoreFacade#countNumberOfElement(java.lang.String, java.lang.String)
	 */
	public long countNumberOfElement (String storeDataStructureId,
			String structureSubtype) throws Exception {
		
		String searchedPath = protocol+ storeDataStructureId + "/"
				+ structureSubtype;
		log.info("Checking number of elements in: " + searchedPath);
		
		CountingIterator<ArchiveObject2Meta> response = null;
	
		try {
			response = 
				llsAccess.queryObjects(searchedPath, true);
			
		} catch (ServiceException e) {
			String msg = "Failed to query object" + searchedPath;
			log.error(msg);
			throw (ServiceException) new Exception(
				msg).initCause(e);
		}
		
		if (response == null || response.count() == 0) {
			log.warn("No objects found in the storage!");
			return 0;
		}
		
		long counter = 0;

		if (response.hasNext()) {	
			
			ArchiveObject2Meta object = response.next();
			ArchiveObjectFacade foundObject = null;
			
			try {
				
				foundObject = 
					llsAccess.getObject(
							object.getId(), 
							new String[]{contentType}, 
							true);	
			} catch (ServiceException e) {
				String msg = " Exception occured when try get object " 
					+object.getId() +" from the storage : "+e;
				log.error(msg);
				throw (ServiceException) new Exception(
						msg).initCause(e);
			}
			
			if (foundObject != null) {
				
				Map<String, List<YaddaObjectID>> children = foundObject.getChildren();
				Iterator<String> chIter = children.keySet().iterator();
			
				while (chIter.hasNext()) {
	
					ArchiveObjectFacade foundSingleObject = null;
				
					try {
						String key = chIter.next();
						List<YaddaObjectID> values = children.get(key);
						Iterator<YaddaObjectID> iter = values.iterator();
						
						while (iter.hasNext()){
							YaddaObjectID value = iter.next();
					
							
							foundSingleObject = 
								llsAccess.getObject(
										value, 
										new String[]{contentType}, 
										false);
						}
						
					} catch (ServiceException e) {
						String msg = " Exception occured when try get object " 
							+object.getId() +" from the storage : "+e;
						log.error(msg);
						throw (ServiceException) new Exception(
								msg).initCause(e);
					}
					
					if (foundSingleObject == null || foundSingleObject.getStatus() == STATUS.DELETED)
						continue;
					
					counter++;
				}
				log.debug("Found "+counter+" elements");
			}
		}
		
		return counter;
	}

	/**
	 * Gets the lls storage facade.
	 * 
	 * @return the lls storage facade
	 */
	public IStorageFacade2 getLlsStorageFacade() {
		return llsStore;
	}

	/**
	 * Sets the lls storage facade.
	 * 
	 * @param llsStore the lls store
	 */
	public void setLlsStorageFacade(IStorageFacade2 llsStore) {
		this.llsStore = llsStore;
	}

	/**
	 * Gets the lls archive facade.
	 * 
	 * @return the lls archive facade
	 */
	public IArchiveFacade2 getLlsArchiveFacade() {
		return llsAccess;
	}

	/**
	 * Sets the lls archive facade.
	 * 
	 * @param llsAccess the lls access
	 */
	public void setLlsArchiveFacade(IArchiveFacade2 llsAccess) {
		this.llsAccess = llsAccess;
	}
	
	/**
	 * Sets the lls data distributor uri.
	 * 
	 * @param dataDistributorUri the new lls data distributor uri
	 */
	public void setLlsDataDistributorUri(String dataDistributorUri) {
		this.dataDistributorUri = dataDistributorUri;
	}
	
	/**
	 * Gets the lls data distributor uri.
	 * 
	 * @return the lls data distributor uri
	 */
	public String getLlsDataDistributorUri() {
		return this.dataDistributorUri;
	}

	/**
	 * Sets the lls descriptor uri.
	 * 
	 * @param descriptorURI the new lls descriptor uri
	 */
	public void setLlsDescriptorUri(String descriptorURI) {
		this.descriptorURI = descriptorURI;
	}
	
	/**
	 * Gets the lls descriptor uri.
	 * 
	 * @return the lls descriptor uri
	 */
	public String getLlsDescriptorUri() {
		return this.descriptorURI;
	}

}
