package com.libreecm.input.repositoriesImpl;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.attribute.AclFileAttributeView;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import com.libreecm.commonAPI.auxiliars.exceptions.AlreadyExistingObjectException;
import com.libreecm.commonAPI.auxiliars.exceptions.EmptyDocumentException;
import com.libreecm.commonAPI.auxiliars.exceptions.ErrorCreatingObjectException;
import com.libreecm.commonAPI.auxiliars.exceptions.ErrorReadingDocumentException;
import com.libreecm.commonAPI.auxiliars.exceptions.ErrorReadingFolderException;
import com.libreecm.commonAPI.auxiliars.exceptions.ErrorReadingStreamException;
import com.libreecm.commonAPI.auxiliars.exceptions.ErrorUriDefinitionException;
import com.libreecm.commonAPI.auxiliars.exceptions.ErrorWrittingStreamException;
import com.libreecm.commonAPI.auxiliars.exceptions.NotAllowedObjectTypeException;
import com.libreecm.commonAPI.auxiliars.exceptions.NotExistingObjectException;
import com.libreecm.commonAPI.auxiliars.exceptions.NotExistingObjectStreamException;
import com.libreecm.commonAPI.auxiliars.exceptions.NotExistingRepositoryPathException;
import com.libreecm.commonAPI.model.AclCapabilities;
import com.libreecm.commonAPI.model.CreatablePropertyTypes;
import com.libreecm.commonAPI.model.NewTypeSettableAttributes;
import com.libreecm.commonAPI.model.PermissionDefinition;
import com.libreecm.commonAPI.model.PermissionMapping;
import com.libreecm.commonAPI.model.Repository;
import com.libreecm.commonAPI.model.RepositoryCapabilities;
import com.libreecm.commonAPI.model.enums.AclPropagation;
import com.libreecm.commonAPI.model.enums.Action;
import com.libreecm.commonAPI.model.enums.BaseTypeId;
import com.libreecm.commonAPI.model.enums.CapabilityAcl;
import com.libreecm.commonAPI.model.enums.CapabilityChanges;
import com.libreecm.commonAPI.model.enums.CapabilityContentStreamUpdates;
import com.libreecm.commonAPI.model.enums.CapabilityJoin;
import com.libreecm.commonAPI.model.enums.CapabilityOrderBy;
import com.libreecm.commonAPI.model.enums.CapabilityQuery;
import com.libreecm.commonAPI.model.enums.CapabilityRenditions;
import com.libreecm.commonAPI.model.enums.CmisVersion;
import com.libreecm.commonAPI.model.enums.PropertyType;
import com.libreecm.commonAPI.model.enums.SupportedPermissions;
import com.libreecm.commonAPI.model.objects.*;
import com.libreecm.commonAPI.model.objects.properties.PropertyDefinition;
import com.libreecm.commonAPI.model.users.Acl;
import com.libreecm.input.auxiliars.Mapper;

/**
 * This repository is a local File System implementation of Repository.
 * 
 * It's name is FSRepository
 * 
 * Documents are files Item are files without stream Folders are folders
 * Policies, Relationships are information stored at a XML file at root path.
 * It's read when object created, and saved with each modification.
 * 
 * An object has just one folder parent, always.
 * 
 * Root folder has no parent, it's id is cmis:folder://"FSRepositoryId"/
 * 
 * No metadata is supported in this implementation, but it could.
 * 
 * ACL it's supported
 * 
 * No versioning document it's supported
 * 
 * @author Jordi Pérez Bayerri
 *
 */
public class FSRepository extends RepositoryImpl {

	private HashMap<String, Relationship> relationships = new HashMap<>();
	private HashMap<String, Policy> policies = new HashMap<>();
	private HashMap<String, RenditionData> renditions = new HashMap<>();

	public List<Relationship> getObjectRelationships(LibreECMDataObject element) {
		ArrayList<Relationship> result = new ArrayList<>();
		for (Entry<String, Relationship> rel : this.relationships.entrySet()) {
			if (rel.getValue().getSourceId().equals(element.getId())) {
				result.add(rel.getValue());
			}
		}
		return result;
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Creates a new Repository pointing to a file system path Uri should have
	 * to be formatted:
	 * "FSRepository://repositoryId/rootFolder/secondRootFolder/.../parentFolder/baseFolder"
	 * Object pointed from Uri should be null or empty It could have any folder
	 * tree size. Attributes are setted to default to this repository This is
	 * the usual constructor when a new Repository is created, so it's not at
	 * Persistence yet.
	 * 
	 * @param url
	 *            Uri to locate fisically the repository
	 */
	public FSRepository(Uri uri) {
		super(uri);
		File f = new File(File.separatorChar + uri.getFolderTree() + uri.getObject());
		if (!f.exists() && !f.isDirectory()) {
			throw new NotExistingRepositoryPathException(uri.getRepositoryName(), uri.toString());
		}
	}

	/**
	 * Initializes Repository to defaults
	 */
	protected void setDefaultCapabilities() {

		// Other Properties
		this.setTypeId(RepositoryType.FSREPOSITORY.value());
		this.setChangesIncomplete(true);
		this.setChangesOnType(new ArrayList<BaseTypeId>(
				Arrays.asList(BaseTypeId.CMIS_FOLDER, BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_ITEM)));
		this.setLatestChangeLogToken("token-24");
		this.setPrincipalIdAnonymous("anonymous");
		this.setPrincipalIdAnyone("anyone");
		this.setVendorName("Jordi Pérez Bayerri");
		this.setProductName("LibreECM/1.0.0");
		this.setProductVersion("1.0.0");

		Uri rootFolderUri = new Uri();
		rootFolderUri.setObject("");
		rootFolderUri.setRepositoryName(this.getName());
		rootFolderUri.setType(BaseTypeId.CMIS_FOLDER.value());
		rootFolderUri.setFolderTree(new ArrayList<String>());
		this.setRootFolderId(rootFolderUri.toString());
		this.setThinClientUri("");
		this.setCmisVersionSupported(CmisVersion.CMIS_1_1.value());
		this.setExtensionFeatures(null);
		this.setExtensions(null);

		Set<PropertyType> propertyTypeSet = new HashSet<>();
		for (PropertyType property : PropertyType.values()) {
			propertyTypeSet.add(property);
		}
		CreatablePropertyTypes creatablePropertyTypes = new CreatablePropertyTypes(propertyTypeSet, null);

		/*
		 * Attributes in order are: canSetId, localName, localNamespace,
		 * displayName, queryName, description, creatable, fileable, queryable,
		 * fulltextIndexed, includedInSupertypeQuery, controllablePolicy,
		 * controllableACL
		 * 
		 * All attributes are true except: queryable, fulltextIndexed,
		 * includedInSupertypeQuery, controllablePolicy, controllableACL So, any
		 * change on this repository will not launch events, nor be ACL tested
		 */
		NewTypeSettableAttributes newTypeAttributes = new NewTypeSettableAttributes(true, true, true, true, true, true,
				true, true, false, false, false, false, false);

		/*
		 * Capabilities in order are (Capital where objects):
		 * allVersionsSearchable, ispwcSearchable, isPwcUpdatable,
		 * supportsGetDescendants, supportsGetFolderTree, CapabilityOrderBy
		 * supportsMultifiling, supportsUnfiling, supportsVersionSpecificFiling,
		 * NewTypeSettableAttributes, CapabilityRenditions, CapabilityAcl,
		 * CapabilityJoin, CapabilityChanges, CapabilityContentStreamUpdates,
		 * CapabilityQuery, CreatablePropertyTypes, Set<PropertyType>,
		 * List<CmisExtensionElement>
		 */
		RepositoryCapabilities caps = new RepositoryCapabilities(false, false, false, true, true,
				CapabilityOrderBy.NONE, false, false, false, newTypeAttributes, CapabilityRenditions.NONE,
				CapabilityAcl.DISCOVER, CapabilityJoin.NONE, CapabilityChanges.NONE,
				CapabilityContentStreamUpdates.ANYTIME, CapabilityQuery.NONE, creatablePropertyTypes, propertyTypeSet,
				null);

		// Now capabilities are set, set how ACL respond to each capability
		AclCapabilities capabilities = new AclCapabilities();

		capabilities.setAclPropagation(AclPropagation.OBJECTONLY);
		capabilities.setSupportedPermissions(SupportedPermissions.BASIC);

		List<PermissionDefinition> permissions = new ArrayList<>();
		permissions.add(new PermissionDefinition("cmis:read", "Read"));
		permissions.add(new PermissionDefinition("cmis:write", "Write"));
		permissions.add(new PermissionDefinition("cmis:all", "All"));
		capabilities.setPermissions(permissions);

		HashMap<String, PermissionMapping> mappings = new HashMap<>();

		// Setting all objects, permissions. Reading to read, modifying to
		// write, applying to all.
		for (String value : PermissionMapping.getPermissionActions().values()) {
			List<String> perms = new ArrayList<>();
			if (value.startsWith("canGet") || value.startsWith("canView")) {
				perms.add("cmis:read");
			}
			if (value.startsWith("canCreate") || value.startsWith("canUpdate") || value.startsWith("canMove")
					|| value.startsWith("canDelete") || value.startsWith("canRemove") || value.startsWith("canCheckOut")
					|| value.startsWith("canCheckIn") || value.startsWith("canSet") || value.startsWith("canAdd")) {
				perms.add("cmis:write");
			}
			if (value.startsWith("canApply")) {
				perms.add("cmis:all");
			}
			mappings.put(value, new PermissionMapping(null, perms, "value"));
		}
		// Two exceptions cause our Repository can't create relationships:
		List<String> perms = new ArrayList<>();
		perms.add("cmis:read");
		mappings.put("canCreateRelationship.Source",
				new PermissionMapping(null, perms, "canCreateRelationship.Source"));

		perms = new ArrayList<>();
		perms.add("cmis:read");
		mappings.put("canCreateRelationship.Target",
				new PermissionMapping(null, perms, "canCreateRelationship.Target"));

		capabilities.setPermissionMappings(mappings);

		this.setAclCapabilities(capabilities);

	}

	/**
	 * Creates a new Repository pointing to a file system path Uri should have
	 * to be formatted:
	 * "FSRepository://repositoryId/rootFolder/secondRootFolder/.../parentFolder/baseFolder"
	 * Object pointed from Uri should be null or empty It could have any folder
	 * tree size. Attributes are setted to default to this repository This is
	 * the usual constructor when a repository is at the persistence and want to
	 * construct
	 * 
	 * @param repository
	 *            read from persistence and with some data (the part no
	 *            storable)
	 */
	public FSRepository(Repository repo) {
		super(repo);

		File f = new File(this.getPath());
		if (!f.exists() && !f.isDirectory()) {
			throw new NotExistingRepositoryPathException(repo.getName(), repo.getPath());
		}
	}

	public Uri getUri() {
		Uri result = new Uri();
		result.setRepositoryName(this.getName());
		result.setType(this.getTypeId());
		result.setObject("");
		for (String folder : this.getPath().split(File.pathSeparator)) {
			result.getFolderTreeList().add(folder);
		}
		return result;
	}

	/**
	 * Setting default objects and it's capabilities
	 */
	protected void setAllowedObjectTypes() {

		List<ObjectType> result = new ArrayList<>();

		DocumentObjectType document = new DocumentObjectType();
		document.setBaseTypeId(BaseTypeId.CMIS_DOCUMENT);
		document.setDisplayName(BaseTypeId.CMIS_DOCUMENT.value());
		document.setId(document.getBaseTypeId().value());
		document.setLocalName(document.getBaseTypeId().toString());
		document.setLocalNamespace(this.getName() + "Object");
		document.setParentTypeId(document.getBaseTypeId().value());
		document.setDescription("Object from repository " + this.getName());
		document.setControllableAcl(true);
		document.setControllablePolicy(true);
		document.setContentStreamAllowed(ContentStreamAllowed.ALLOWED);
		document.setCreatable(true);
		document.setExtensions(this.getExtensions());
		document.setFileable(true);
		document.setFulltextIndexed(false);
		document.setIncludedInSupertypeQuery(true);
		document.setQueryable(true);
		document.setRepositoryId(this.getName());
		// Couldn't mutate types, nor create
		TypeMutability mutability = new TypeMutability();
		mutability.setCanCreate(true);
		mutability.setCanDelete(true);
		mutability.setCanUpdate(true);
		document.setTypeMutability(mutability);

		result.add(document);

		FolderObjectType folder = new FolderObjectType();
		folder.setBaseTypeId(BaseTypeId.CMIS_FOLDER);
		folder.setDisplayName(BaseTypeId.CMIS_FOLDER.value());
		folder.setId(folder.getBaseTypeId().value());
		folder.setLocalName(folder.getBaseTypeId().toString());
		folder.setLocalNamespace(this.getName() + " Object");
		folder.setParentTypeId(folder.getBaseTypeId().value());
		folder.setDescription("Object from repository " + this.getName());
		folder.setControllableAcl(true);
		folder.setControllablePolicy(true);
		folder.setCreatable(true);
		folder.setExtensions(this.getExtensions());
		folder.setFileable(true);
		folder.setFulltextIndexed(false);
		folder.setIncludedInSupertypeQuery(true);
		folder.setQueryable(true);
		folder.setRepositoryId(this.getName());
		// Couldn't mutate types, nor create
		mutability = new TypeMutability();
		mutability.setCanCreate(true);
		mutability.setCanDelete(true);
		mutability.setCanUpdate(true);
		folder.setTypeMutability(mutability);

		result.add(folder);

		ItemObjectType item = new ItemObjectType();
		item.setBaseTypeId(BaseTypeId.CMIS_ITEM);
		item.setDisplayName(BaseTypeId.CMIS_ITEM.value());
		item.setId(item.getBaseTypeId().value());
		item.setLocalName(item.getBaseTypeId().toString());
		item.setLocalNamespace(this.getName() + " Object");
		item.setParentTypeId(item.getBaseTypeId().value());

		item.setDescription("Object from repository " + this.getName());
		item.setControllableAcl(true);
		item.setControllablePolicy(true);
		item.setCreatable(true);
		item.setExtensions(this.getExtensions());
		item.setFileable(true);
		item.setFulltextIndexed(false);
		item.setIncludedInSupertypeQuery(true);
		item.setQueryable(true);
		item.setRepositoryId(this.getName());
		// Couldn't mutate types, nor create
		mutability = new TypeMutability();
		mutability.setCanCreate(false);
		mutability.setCanDelete(false);
		mutability.setCanUpdate(false);
		item.setTypeMutability(mutability);

		result.add(item);

		this.allowedObjectTypeId = (result);
	}

	public Item getItem(Uri uri) {
		ObjectType type = this.getObjectType(BaseTypeId.CMIS_ITEM.value());
		Item result = new Item(uri, type);
		result.setPath(this.getPath() + uri.getFolderTree() + uri.getObject());
		File file = new File(result.getPath());
		try {
			if (file.exists()) {

				result.setAccessible(true);

				result.setAccessible(true);
				result.setActive(true);
				result.setAcl(this.getAcl(file));

				// Depending on the file system
				result.setCreatedBy(Files.getOwner(file.toPath()).getName());
				result.setCreationDate(
				Mapper.map(Files.getLastModifiedTime(file.toPath(), java.nio.file.LinkOption.NOFOLLOW_LINKS)));
				result.setDescription("File");
				result.setDisplayName(uri.getObject());

				// Common to all file systems
				result.setRelationships(this.getObjectRelationships(result));
				result.setImmutable(false);
				result.setLastModifiedBy(Files.getOwner(file.toPath()).getName());
				result.setLocalName(uri.getObject());
				result.setLocalNamespace(uri.getRepositoryName());

				// TODO
				// result.setExactAcl(null);
				result.setPolicyIds(this.getObjectPolicies(result));
				
				
			} else
				throw new NotExistingObjectException(uri.toString());
		} catch (IOException e) {
			throw new ErrorReadingDocumentException(uri.getObject());
		}

		result.setRelationships(this.getObjectRelationships(result));
		result.setPolicyIds(this.getObjectPolicies(result));

		result.setId(uri.toString());

		return result;
	}

	/**
	 * Creates a new Document read from the meta data of the FS
	 * 
	 * @throws IOException
	 */
	public Document getDocument(Uri uri) {
		ObjectType type = this.getObjectType(BaseTypeId.CMIS_DOCUMENT.value());
		Document result = new Document(uri, type);
		result.setPath(this.getPath() + uri.getFolderTree() + uri.getObject());
		File file = new File(result.getPath());
		try {
			if (file.exists()) {

				result.setAccessible(true);
				result.setActive(true);
				result.setAcl(this.getAcl(file));

				// Depending on the file system
				result.setContentStreamFileName(uri.getObject());
				result.setContentStreamId(uri.getObject());
				result.setContentStreamLength(BigInteger.valueOf(file.length()));
				result.setContentStreamMimeType(Files.probeContentType(file.toPath()));
				result.setCreatedBy(Files.getOwner(file.toPath()).getName());
				result.setCreationDate(
						Mapper.map(Files.getLastModifiedTime(file.toPath(), java.nio.file.LinkOption.NOFOLLOW_LINKS)));
				result.setDescription("File");
				result.setDisplayName(uri.getObject());

				// Common to all file systems
				result.setRelationships(this.getObjectRelationships(result));
				result.setImmutable(false);
				result.setLastModifiedBy(Files.getOwner(file.toPath()).getName());
				result.setLocalName(uri.getObject());
				result.setLocalNamespace(uri.getRepositoryName());

				// TODO
				// result.setExactAcl(null);
				result.setPolicyIds(this.getObjectPolicies(result));
				// result.setChangeToken(null);

				// result.setPropertyDefinitions(new HashMap<String,
				// PropertyDefinition<?>>());
				// result.setChangeEventInfo(null);

				// Relationships and renditions
				result.setRelationships(this.getObjectRelationships(result));
				// result.setRenditions(this.getObjectRenditions(result));

				// Versioning not allowed with FSRepository
				// result.setCheckinComment("");
				result.setMajorVersion(true);
				result.setLatestMajorVersion(true);
				result.setLatestVersion(true);
				result.setVersionLabel("1.0.0");
				result.setVersionSeriesCheckedOut(true);
				result.setVersionSeriesCheckedOutBy(result.getLastModifiedBy());
				// result.setVersionSeriesCheckedOutId("");
				// result.setVersionSeriesId("");
			} else {
				throw new NotExistingObjectException(uri.toString());
			}
		} catch (IOException e) {
			throw new ErrorReadingDocumentException(uri.getObject());
		}

		result.setRelationships(this.getObjectRelationships(result));
		result.setPolicyIds(this.getObjectPolicies(result));

		result.setId(uri.toString());

		return result;
	}

	private List<RenditionData> getObjectRenditions(Folder element) {
		ArrayList<RenditionData> result = new ArrayList<>();
		for (Entry<String, RenditionData> rel : this.renditions.entrySet()) {
			if (rel.getValue().getRenditionDocumentId().equals(element.getId())) {
				result.add(rel.getValue());
			}
		}
		return result;
	}

	private Acl getAcl(File file) throws IOException {
		// Security and access
		AclFileAttributeView view = (Files.getFileAttributeView(file.toPath(), AclFileAttributeView.class));
		return Mapper.map(view);
	}

	public void addPolici(Policy policy) {
		policies.put(policy.getId(), policy);
	}

	private Folder getParentFolder(Uri uri) {
		//If it's root folder return
		if (uri.getFolderTree()==null||uri.getFolderTree().equals("") ||uri.getFolderTree().equals(""+File.separatorChar) ) {
			return null;
		}

		File file = new File(this.getPath());
		if (file.exists() && file.isDirectory()) {
			File fileParent = file.getParentFile();
			if (fileParent != null && fileParent.exists() && fileParent.isDirectory()) {
				Uri parentUri = new Uri();
				parentUri.setRepositoryName(uri.getRepositoryName());
				parentUri.setType(BaseTypeId.CMIS_FOLDER.value());
				parentUri.setObject("");
				List<String> list = new ArrayList<>();
				list.addAll(uri.getFolderTreeList());
				parentUri.setFolderTree(list);
				return this.getFolder(parentUri);
			}
			return null;
		} else
			return null;
	}

	public Folder getFolder(Uri uri) {
		//An uri folder class, should have it's object as null or ""
		if (uri.getObject()!=null && uri.getObject()!="")
			throw new ErrorUriDefinitionException(uri.toString());
		ObjectType folderType = this.getObjectType(BaseTypeId.CMIS_FOLDER.value());
		Folder result = new Folder(uri, folderType);
		result.setPath(this.getPath() + uri.getFolderTree() + uri.getObject());
		File file = new File(result.getPath());
		if (file != null && file.exists() && file.isDirectory()) {
			try {
				//Setting the folder name as the last of the folders tree
				if(uri.getFolderTreeList().size()>0)
					result.setDisplayName(uri.getFolderTreeList().get(uri.getFolderTreeList().size()-1));
				result.setCreatedBy(Files.getOwner(file.toPath()).getName());
				result.setCreationDate(
						Mapper.map(Files.getLastModifiedTime(file.toPath(), java.nio.file.LinkOption.NOFOLLOW_LINKS)));
				result.setLastModificationDate(
						Mapper.map(Files.getLastModifiedTime(file.toPath(), java.nio.file.LinkOption.NOFOLLOW_LINKS)));

				result.setAccessible(true);
				result.setActive(true);
				result.setAcl(this.getAcl(file));

				// Depending on the file system
				result.setCreatedBy(Files.getOwner(file.toPath()).getName());
				result.setCreationDate(
						Mapper.map(Files.getLastModifiedTime(file.toPath(), java.nio.file.LinkOption.NOFOLLOW_LINKS)));
				result.setDescription("File");
				
				// Common to all file systems
				result.setRelationships(this.getObjectRelationships(result));
				result.setLastModifiedBy(Files.getOwner(file.toPath()).getName());
				result.setLocalNamespace(uri.getRepositoryName());
				// TODO
				// result.setExactAcl(null);
				result.setPolicyIds(this.getObjectPolicies(result));
			
			} catch (IOException e) {
				throw new ErrorReadingFolderException(uri.getObject());
			}
		} else
			throw new NotExistingObjectException(uri.toString());

		result.setRelationships(this.getObjectRelationships(result));
		result.setPolicyIds(this.getObjectPolicies(result));
		result.setRenditions(this.getObjectRenditions(result));

		result.setAllowableActions(this.getAllowedActions());
		result.setAllowedChildObjectTypeIds(this.getAllowedObjectTypeId());

		// TODO
		// result.setChangeEventInfo(new ChangeEventInfo());
		// result.setExactAcl(true);
		// result.setChangeToken("ABCDEFG0123456789");

		// result.setId(uri.toString());

		return result;
	}

	public PolicyIdList getObjectPolicies(LibreECMDataObject element) {
		PolicyIdList list = new PolicyIdList();
		for (Entry<String, Policy> policy : policies.entrySet()) {
			if (policy.getValue().getOrigen().toString().equals(element.toString())) {
				list.getPoliciesIds().add(policy.getValue().getId());
			}
		}
		return list;
	}

	public Relationship getRelationship(Uri uri) {
		ObjectType type = this.getObjectType(BaseTypeId.CMIS_RELATIONSHIP.value());
		Relationship result = new Relationship(uri, type);
		result.setPath(this.getPath() + uri.getFolderTree() + uri.getObject());
		File file = new File(result.getPath());
		if (file.exists()) {
			result = this.getRelationship(uri);
			result.setAccessible(true);
		} else
			throw new NotExistingObjectException(uri.toString());

		result.setPath(this.getPath() + uri.getFolderTree() + uri.getObject());
		result.setRelationships(this.getObjectRelationships(result));
		return result;
	}

	public Policy getPolicy(Uri uri) {
		ObjectType type = this.getObjectType(BaseTypeId.CMIS_POLICY.value());
		Policy result = new Policy(uri, type);
		result.setPath(this.getPath() + uri.getFolderTree() + uri.getObject());
		File file = new File(result.getPath());
		if (file.exists()) {
			result = policies.get(uri.toString());
			result.setAccessible(true);
		} else
			throw new NotExistingObjectException(uri.toString());

		result.setRelationships(this.getObjectRelationships(result));

		return result;
	}

	public void setItem(Item element, Uri newPath) {
		File file = new File(newPath.getFolderTree() + File.separatorChar + newPath.getObject());
		if (!file.exists()) {
			if (!file.mkdir())
				throw new ErrorCreatingObjectException(element.getPath().toString());
		} else
			throw new AlreadyExistingObjectException(element.getId());
	}

	public void setDocument(Document element, Uri newPath) {
		InputStream is = null;
		try {
			is = (this.getObjectStream(element.getPath()));
			this.setObjectStream(newPath, is);
		} catch (Exception e) {
			throw new ErrorWrittingStreamException(newPath.toString());
		}
	}

	public void setFolder(Folder element, Uri newPath) {
		File file = new File(newPath.getFolderTree() + File.separatorChar + newPath.getObject());
		if (!file.exists()) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				throw new ErrorCreatingObjectException(element.getPath().toString());
			}
		} else
			throw new AlreadyExistingObjectException(element.getId());
	}

	/**
	 * Relationships are saved at the root of the repository, by its name as
	 * file name plus .relationship Internally, data is mapped as XML object
	 */
	public void setRelationship(Relationship element) {
		File file = new File(this.getRootFolderId() + "/." + element.getLocalName() + ".relationship");
		if (!file.exists()) {
			try {
				file.createNewFile();
				this.relationships.put(element.getId(), element);
			} catch (IOException e) {
				throw new ErrorCreatingObjectException(element.getPath().toString());
			}
		} else
			throw new AlreadyExistingObjectException(element.getId());
	}

	/**
	 * Policiess are saved at the root of the repository, by its name as file
	 * name plus .policy Internally, data is mapped as XML object
	 */
	public void setPolicy(Policy element) {
		File file = new File(this.getRootFolderId() + "/." + element.getLocalName() + ".policy");
		if (!file.exists()) {
			try {
				file.createNewFile();
				this.policies.put(element.getId(), element);
			} catch (IOException e) {
				throw new ErrorCreatingObjectException(element.getPath().toString());
			}
		} else
			throw new AlreadyExistingObjectException(element.getId());
	}

	private void copyStreams(InputStream is, OutputStream os) throws IOException {
		try {
			byte[] buffer = new byte[1024];
			int length;
			while ((length = is.read(buffer)) > 0) {
				os.write(buffer, 0, length);
			}
		} finally {
			is.close();
			os.close();
		}
	}

	@Override
	/**
	 * These are the default actions on objects on this repository
	 */
	public AllowableActions getAllowedActions() {
		AllowableActions result = new AllowableActions();
		Set<Action> actions = new HashSet<Action>();
		actions.add(Action.CAN_ADD_OBJECT_TO_FOLDER);
		actions.add(Action.CAN_APPLY_ACL);
		actions.add(Action.CAN_APPLY_POLICY);
		actions.add(Action.CAN_CREATE_DOCUMENT);
		actions.add(Action.CAN_CREATE_FOLDER);
		actions.add(Action.CAN_CREATE_ITEM);
		actions.add(Action.CAN_CREATE_RELATIONSHIP);
		actions.add(Action.CAN_DELETE_CONTENT_STREAM);
		actions.add(Action.CAN_DELETE_OBJECT);
		actions.add(Action.CAN_DELETE_TREE);
		actions.add(Action.CAN_GET_ACL);
		actions.add(Action.CAN_GET_APPLIED_POLICIES);
		actions.add(Action.CAN_GET_CHILDREN);
		actions.add(Action.CAN_GET_CONTENT_STREAM);
		actions.add(Action.CAN_GET_DESCENDANTS);
		actions.add(Action.CAN_GET_FOLDER_PARENT);
		actions.add(Action.CAN_GET_FOLDER_TREE);
		actions.add(Action.CAN_GET_OBJECT_PARENTS);
		actions.add(Action.CAN_GET_OBJECT_RELATIONSHIPS);
		actions.add(Action.CAN_GET_PROPERTIES);
		actions.add(Action.CAN_GET_RENDITIONS);
		actions.add(Action.CAN_MOVE_OBJECT);
		actions.add(Action.CAN_REMOVE_OBJECT_FROM_FOLDER);
		actions.add(Action.CAN_REMOVE_POLICY);
		actions.add(Action.CAN_SET_CONTENT_STREAM);
		actions.add(Action.CAN_UPDATE_PROPERTIES);
		result.setAllowableActions(actions);

		return result;
	}

	/**
	 * Returns an inputstream read from the Document
	 * 
	 * @param path
	 *            file of the object
	 */
	public InputStream getObjectStream(String path) {
		File file = new File(path);
		// File file = new File(path.getFolderTree() + File.separatorChar +
		// path.getObject());
		try {
			return new FileInputStream(file);
		} catch (FileNotFoundException e) {
			throw new ErrorReadingStreamException(path);
		}
	}

	@Override
	public void setObjectStream(Uri documentPath, InputStream content) {
		try {
			Document document = this.getDocument(documentPath);
			if (content.available() != 0) {
				try {
					BufferedOutputStream os = new BufferedOutputStream(
							new FileOutputStream(new File(document.getPath())));

					MessageDigest md = MessageDigest.getInstance("MD5");
					try (DigestInputStream dis = new DigestInputStream(content, md)) {
						/* Read decorated stream (dis) to EOF as normal... */
						copyStreams(dis, os);
					}
					byte[] digest = md.digest();
					try {
						os.close();
					} catch (Exception e) {

					}
					document.setContentStreamHash(digest.toString());

				} catch (FileNotFoundException e1) {
					throw new NotExistingObjectStreamException(this.getPath().toString());
				} catch (Exception e) {
					throw new ErrorWrittingStreamException(this.getId());
				}
			} else {
				throw new EmptyDocumentException(this.getId());
			}
		} catch (IOException e) {
			throw new ErrorReadingStreamException(this.getId());
		}

	}

	@Override
	public List<LibreECMDataObject> getChildren(String folderId) {
		List<LibreECMDataObject> result = new ArrayList<LibreECMDataObject>();
		Folder parent = this.getFolder(Uri.parse(folderId));
		File parentFile = new File(parent.getPath());
		// System.out.println("Path of parent file is
		// "+parentFile.getAbsolutePath());
		File[] files = parentFile.listFiles();
		for (File file : files) {
			if (file.isDirectory()) {
				Uri uri = new Uri();
				uri.setRepositoryName(this.getName());
				uri.setObject("");
				uri.setType(BaseTypeId.CMIS_FOLDER.value());
				// Folder tree is the same as parent's, adding the child folder
				uri.setFolderTree(Uri.parse(parent.getId()).getFolderTreeList());
				uri.getFolderTreeList().add(file.getName());
				result.add(this.getFolder(uri));
			} else {
				if (file.length() > 0) {
					Uri uri = new Uri();
					uri.setRepositoryName(this.getName());
					uri.setType(BaseTypeId.CMIS_DOCUMENT.value());
					uri.setFolderTree(Uri.parse(parent.getId()).getFolderTreeList());
					uri.setObject(file.getName());
					result.add(this.getDocument(uri));
				} else {
					Uri uri = new Uri();
					uri.setRepositoryName(this.getName());
					uri.setType(BaseTypeId.CMIS_ITEM.value());
					uri.setFolderTree(Uri.parse(parent.getId()).getFolderTreeList());
					uri.setObject(file.getName());
					result.add(this.getItem(uri));
				}
			}
		}
		// System.out.println("Found " + result.size() + " objects: " + result);
		return result;
	}

	@Override
	public LibreECMDataObject getCustomized(Uri uri, String type) {
		ObjectType objectType = this.getObjectType(type);
		LibreECMDataObject result = null;
		if (type.equals(BaseTypeId.CMIS_DOCUMENT.value()))
			new Document(uri, objectType);
		else if (type.equals(BaseTypeId.CMIS_FOLDER.value()))
			new Folder(uri, objectType);
		else if (type.equals(BaseTypeId.CMIS_ITEM.value()))
			new Item(uri, objectType);
		result.setPath(this.getPath() + uri.getFolderTree() + uri.getObject());
		File file = new File(result.getPath());

		if (file.exists())
			result.setAccessible(true);
		else
			throw new NotExistingObjectException(uri.toString());
		return result;

	}

}
