public abstract class AuthenticationManager {

    public boolean status;
    private String server;
    
    public AuthenticationManager() { }
    
    public String isValidUser(String username, String password, String sessionid) {
        String status = "";
        return status;
    }
    public boolean createNewUser(String fname, String lname, String mail, String lang, String pw, String userOrganization, String userGroup, Vector<String> usrFedoraRoles){
    	boolean status = false;
    	return status;
    }
    public boolean updateUser(String dn, String organization, String userGroup, Vector<String> fedoraRoles) {
        return false;
    }
    public boolean resetUserPassword(String usrDn, String pw) {
        return false;
    }
    public boolean createGroup(String groupName, String groupOrganization){
    	boolean status = false;
    	return status;
    }
    public boolean createRole(String roleName, String roleOrganization, Vector<AccessRight> accessRights){
        boolean status = false;
        return status;
    }
    public boolean updateRole(String dn, String roleName, Vector<AccessRight> accessrights) {
        return false;
    }
    public boolean createOrganization(String oName, String oDesc, String confFile, String afName, String alName, String aMail, String contact, String displayName){
    	boolean status = false;
    	return status;  
    }
    public List<User> getUsers(String organization){
        return null;
    }
    public String modifyAttributes(String dn, String jsonmod){
    	String result = "";
    	return result;
    }
    public void deleteEntry(String dn, EntryType entryType) {
    	
    }
    public HashMap<String,String> getAttributes(String dn){
    	HashMap<String,String> values = new HashMap<String,String>();
    	return values;
    }
    public String createUserDatatable() {
    	return "";
    }
    public Vector<String> getOrganizations(){
    	Vector<String> values =  new Vector<String>();
    	return values;
    }
    public Vector<String>getGroups(String organization){
    	Vector<String> values =  new Vector<String>();
    	return values;
    }
	public String getServer() {
		return server;
	}
	public void setServer(String server) {
		this.server = server;
	}
	public Vector<String> getOrganizationRoles(String organization) {
	    Vector<String> values =  new Vector<String>();
        return values;
	}
	public Role getRole(String roleName, String organization) {
	    return null;
	}
	public Role getPublicRole(String organization) {
	    return null;
	}
	public List<Role> getRoles(String organization) {
        return null;
	}
	public Vector<Role> getUserRoles(String user, String organization) {
	    Vector<Role> values =  new Vector<Role>();
        return values;
	}
	public User getUser(String userDn) {
		User user = new User();
		return user;
	}
	public String getDefaultDn(String defaultOrg) {
        return "";
	}
	public String getDomainComponents() {
        return "";
    }
	public String getInstanceAdminDn() {
	    return "";
	}
} public class LdapManager extends AuthenticationManager {
    
    private static final Logger logger = Logger.getLogger(LdapManager.class);
    public static enum EntryType {
    	USER,
    	ROLE
    }
    
    // Ldap attributes for organization
    public static final String ORG_NAME        = "o";
    public static final String ORG_VISIBLENAME = "displayName";
    public static final String ORG_DESC        = "description";
    // Ldap attributes for group
    public static final String GROUP_NAME = "ou";
    // Ldap attributes for person
    public static final String PERSON_FIRSTNAME      = "givenName";
    public static final String PERSON_LASTNAME       = "sn";
    public static final String PERSON_COMMONNAME     = "cn";
    public static final String PERSON_PASSWORD       = "userPassword";
    public static final String PERSON_EMAIL          = "mail";
    public static final String PERSON_LANGUAGE       = "preferredLanguage";
    // Ldap attributes for role
    public static final String ROLE_NAME             = "cn";
    public static final String ROLE_ACCESSRIGHTPATH  = "destinationIndicator";
    public static final String ROLE_ACCESSRIGHTLEVEL = "l";
    public static final String ROLE_ACCESSRIGHTPUBLICITYLEVEL = "ou";
    public static final String ROLE_USER             = "roleOccupant";
    
    /** LDAPManager status */
    public boolean status = false;
    
    /** Host:port where connected to */
    private String host;

    /** connection to LDAP */
    private DirContext context;
    private String admindn;
    private String adminpw;
    // domain components (dc=xx,dc=yy)
    private String domainComponents;
    // dn for instance admin
    private String instanceAdminDn;
    
    /**
     * Constructor
     */
    public LdapManager(String host, String AdminDn, String Adminpw, String baseDn, String adminDn) {
        this.host = host;
        this.admindn = AdminDn;
        this.adminpw = Adminpw;
        setDomainComponents(baseDn);
        setInstanceAdminDn(adminDn);
        context = getContext();
        // save host if connected
        if (context != null) {
            this.status = true;
            
            try {
                context.close();
            } catch (NamingException e) {
                logger.error("LdapManager initialization error.");
            }
        }
    }
    
    public static String hash256(String data) {
    	MessageDigest md = null;
    	
    	try {
    		
    		md = MessageDigest.getInstance("SHA-256");
    		md.update(data.getBytes());
    	
    	} catch (NoSuchAlgorithmException e){
    		logger.error("LdapManager error: Hashing error");
    	}
    	
    	return bytesToHex(md.digest());
    }
    
    public static String bytesToHex(byte[] bytes) {
    	StringBuffer result = new StringBuffer();
    	for (byte byt : bytes) result.append(Integer.toString((byt & 0xff) + 0x100, 16).substring(1));
    	return result.toString();
    }
    
    public String getDefaultDn(String defaultOrg) {
        return "o="+defaultOrg+","+getDomainComponents();
    }
    
    /**
     * Check user
     * @param username      email-address
     * @param password      password
     * @return
     */
    public String isValidUser(String username, String password, String sessionid) {
        
    	DirContext authContext = null;
        boolean status = false;
        String usrdn = "";
        String cnValue = "";
        String hashedPw = hash256(password);
        String retValue = "";
        
        try {
        	authContext = getContext();
        	SearchControls ctls = new SearchControls();
        	
        	// Check e-mail and password
        	String filter = "(&(mail="+username+")(userPassword="+hashedPw+"))";
        	
          	NamingEnumeration<SearchResult> answer3 = authContext.search(getDomainComponents(), filter, ctls);
           	if (answer3.hasMore()) {
           	    status = true;

           	    SearchResult result = answer3.next();
                cnValue = result.getAttributes().get("cn").get().toString();
                usrdn = "cn="+cnValue+","+getDomainComponents();
           	}
           	
        	NamingEnumeration<NameClassPair> e = authContext.list(getDomainComponents());
        	while (e.hasMore()) {
        		NameClassPair nc = e.next();
        		String organization = nc.getName();
        		
              	NamingEnumeration<SearchResult> answer = authContext.search(organization+","+getDomainComponents(), filter, ctls);
	           	if (answer.hasMore()) {
	           	    status = true;
	           	    
                    SearchResult result = answer.next();
                    cnValue = result.getAttributes().get("cn").get().toString();
                    usrdn = "cn="+cnValue+","+organization+","+getDomainComponents();
	           	}
	           	
        		NamingEnumeration<NameClassPair> ae = authContext.list(organization +","+getDomainComponents());
        		while (ae.hasMore()) {
                    NameClassPair ne = ae.next();
                    String group = ne.getName();
                    NamingEnumeration<SearchResult> answer2 = authContext.search(group+","+organization+","+getDomainComponents(), filter, ctls);
                    if (answer2.hasMore()) {
                        status = true;
                        
                        SearchResult result = answer2.next();
                        cnValue = result.getAttributes().get("cn").get().toString();
                        usrdn = "cn="+cnValue+","+group+","+organization+","+getDomainComponents();
                    }
        		}        		
        	}
        	
        	authContext.close();
        	
        } catch (Exception e) {
        	logger.error("LdapManager error: user check failed, "+e);
        }
        
        if (usrdn != "") {
            // check if user already logged in        
            status = Osa.dbManager.get("sql").setCurrentuser(usrdn, sessionid);
            if (status == false) {
                retValue = "alreadyloggedin;"+usrdn;
            }
            
        } else {
            retValue = "notfound";
        }
        
        if (status) {
        	return usrdn;      	
        } else {
        	return retValue;
        }
        
    }
    
    /**
     * Get the directory service interface, containing methods for examining and 
     * updating attributes associated with objects, 
     * and for searching the directory. 
     * @return
     */
    public DirContext getContext() {
        DirContext authContext = null;
        try {
        	
            Properties props = new Properties();
            props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            props.put(Context.PROVIDER_URL, this.host);
            props.put(Context.SECURITY_AUTHENTICATION, "simple");
            props.put(Context.SECURITY_PRINCIPAL, this.admindn);
            props.put(Context.SECURITY_CREDENTIALS, this.adminpw);
                
            authContext = new InitialDirContext(props);
            
        } catch (Exception e) {
            logger.error("LdapManager error: "+e);
        }
        
        return authContext;
    	
    }
    
    /**
     * Creates new user and adds user to specified roles
     */
    public boolean createNewUser(String fname, 
                                 String lname, 
                                 String mail, 
                                 String lang, 
                                 String pw, 
                                 String userOrganization, 
                                 String userGroup, 
                                 Vector<String> fedoraRoles) {
    	
    	DirContext context = null;
    	boolean status = false;
    	
    	String newdn = "cn="+fname+" "+lname+",ou="+userGroup+",o="+userOrganization+","+getDomainComponents();
    	
    	Attribute cn = new BasicAttribute(LdapManager.PERSON_COMMONNAME, fname+" "+lname);
    	Attribute gn = new BasicAttribute(LdapManager.PERSON_FIRSTNAME, fname);
    	Attribute sn = new BasicAttribute(LdapManager.PERSON_LASTNAME, lname);
    	Attribute email = new BasicAttribute(LdapManager.PERSON_EMAIL, mail);
    	Attribute preferredLanguage = new BasicAttribute(LdapManager.PERSON_LANGUAGE, lang);
    	Attribute userPassword = new BasicAttribute(LdapManager.PERSON_PASSWORD, hash256(pw));
    	
    	Attribute oc = new BasicAttribute("objectClass");
    	oc.add("person");
    	oc.add("inetOrgPerson");
    	    	
    	try {
    		context = getContext();
    		Attributes entry = new BasicAttributes();
    		entry.put(cn);
    		entry.put(gn);
    		entry.put(sn);
    		entry.put(email);
    		entry.put(preferredLanguage);
    		entry.put(userPassword);
    		entry.put(oc);
    		
    		context.createSubcontext(newdn, entry);
    		logger.debug("LdapManager: Created user: " + newdn);
    		
    		// Add default role to user (access to public data)
    		addUserToRole(newdn, userOrganization, Role.ROLE_PUBLIC);
    		
    		// Add fedora role(s) to user
    		for (String fedoraRole : fedoraRoles) {
    			if (fedoraRole != null) { // Null-check in case vector has null indexes
    				addUserToRole(newdn, userOrganization, fedoraRole);
    			}
    		}
    		context.close();
    		
    	} catch (NamingException e) {
    		logger.error("LdapManager error: Error creating user, "+e);
    		return status;
    	}

    	status = true;
		return status;
    }
    
    /**
     * Updates user related data
     */
    public boolean updateUser(String usrDn, 
                              String organization, 
                              String userGroup, 
                              Vector<String> fedoraRoles) {
        
        DirContext context = null;
        String newDn = null;
        
        try {
            
            context = getContext();
            
            // get userGroup
            String currentUserGroup = "";
            String[] parts = usrDn.split(",");
            for (int i=0; i<parts.length; i++) {
                if (parts[i].startsWith("ou=")) {
                    currentUserGroup = parts[i].replaceFirst("ou=", "");
                    break;
                }
            }
            // check userGroup if changed
            if (!currentUserGroup.equals(userGroup)) {
                newDn = usrDn.replace("ou="+currentUserGroup, "ou="+userGroup);
                // change dn 
                context.rename(usrDn, newDn);
            }
            context.close();
            
            // Handle fedora roles, remove old roles
            this.removeUserFromRole(usrDn);
            
            // use newDn if changed
            if (newDn != null) {
                usrDn = newDn;
            }
            
            // Handle fedora roles, add modified
            for (String fedoraRole : fedoraRoles) {
                addUserToRole(usrDn, organization, fedoraRole);
            }
            
            context.close();
            
        } catch (NamingException e) {
            logger.error("LdapManager error: Error updating user, "+e);
            return false;
        }
        return true;
    }
    
    /**
     * Function for instance_admin to reset user password 
     */
    public boolean resetUserPassword(String usrDn, String pw) {
        DirContext context = null;
        
        try {
            context = getContext();
            Attribute userPassword = new BasicAttribute(LdapManager.PERSON_PASSWORD, hash256(pw));
            ModificationItem[] mods = new ModificationItem[1];
            mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, userPassword);
            context.modifyAttributes(usrDn, mods);
            logger.info("LdapManager info: User '"+usrDn+"' password reseted by instance_admin.");
            context.close();
            
        } catch (NamingException e) {
            logger.error("LdapManager error: Error reseting user password, "+e);
            return false;
        }
        
        return true;
    }
    
    /**
     * Creates new group to the organization
     */
    public boolean createGroup(String groupName, String groupOrganization) {
    	
    	DirContext context = null;
    	boolean status = false;
    	
    	String newdn = "ou="+ groupName +",o="+ groupOrganization + "," + getDomainComponents();
    	
    	Attribute ou = new BasicAttribute(LdapManager.GROUP_NAME, groupName);
    	Attribute oc = new BasicAttribute("objectClass");
    	oc.add("organizationalUnit");
    	
    	try {
    		
    		context = getContext();
    		Attributes entry = new BasicAttributes();
    		entry.put(ou);
    		entry.put(oc);
    		
    		context.createSubcontext(newdn, entry);
    		logger.debug("LdapManager: Created group: "+newdn);
    		context.close();
    		
    	} catch (NamingException e) {
    		logger.error("LdapManager error: Error creating group, "+e);
    		return status;
    	}
    	
    	status = true;
		return status;
    	
    }
    
    /**
     * Creates new organization
     */
    public boolean createOrganization(String oName, String oDesc, String confFile, String afName, String alName, String aMail, String contact, String displayName) {
    	
    	DirContext context = null;
    	boolean status = false;
    	Attribute description = null;
    	Attribute dN = null;
    	
    	String newdn = "o=" + oName + ","+ getDomainComponents();
    	
    	Attribute o = new BasicAttribute(LdapManager.ORG_NAME, oName);
    	if (oDesc != null) {
    	    description = new BasicAttribute(LdapManager.ORG_DESC, oDesc);
    	}
    	if (displayName != null) {
    	    dN = new BasicAttribute(LdapManager.ORG_VISIBLENAME, displayName);
    	}
    	Attribute oc = new BasicAttribute("objectClass");
    	oc.add("OpenLDAPorg");
    	
    	try {
    		
    		context = getContext();
    		Attributes entry = new BasicAttributes();
    		entry.put(o);
    		if (dN != null) {
    		    entry.put(dN);
    		}
    		if (description != null) {
    		    entry.put(description);
    		}
    		entry.put(oc);
    		context.createSubcontext(newdn, entry);
    		logger.debug("LdapManager: Created organization: "+newdn);
    		context.close();
    		
    	} catch (NamingException e) {
    		logger.error("LdapManager error: Error creating organization, "+e);
    		return status;
    	}
    	
    	status = true;
		return status;
    	
    }

    /**
     * Creates role to selected organization
     * @param roleName              role name
     * @param roleOrganization      role organization
     * @param roleAccessRightLevel  public/restricted/confidential
     * @param roleDirectories       path to directory or document, where role has in level roleAccessRightLevel
     */
    public boolean createRole(String roleName, String roleOrganization, Vector<AccessRight> accessrights) {
        
        DirContext context = null;
        boolean status = false;
        String newdn = "cn="+roleName+",o="+roleOrganization+","+getDomainComponents();
        
        try {

        	Attributes entry = new BasicAttributes();
            Attribute oc = new BasicAttribute("objectClass", "organizationalRole");
            Attribute cn = new BasicAttribute(LdapManager.ROLE_NAME, roleName);
            Attribute ou = new BasicAttribute(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL);
            Attribute l = new BasicAttribute(LdapManager.ROLE_ACCESSRIGHTLEVEL);
            Attribute di = new BasicAttribute(LdapManager.ROLE_ACCESSRIGHTPATH);
            entry.put(oc);
            entry.put(cn);
            int i=0;
            
            for (AccessRight accessRight : accessrights) {
            	if (accessRight != null) {
	                String premark = Integer.toString(i)+"-";
	            	ou.add(premark+accessRight.getPublicityLevel());
	            	l.add(premark+Integer.toString(accessRight.getAccessRightLevel()));
	            	di.add(premark+accessRight.getAccessRightPath());
	            	entry.put(ou);
	            	entry.put(l);
	                entry.put(di);
	                i++;
            	}
            }
            
            context = getContext();
            context.createSubcontext(newdn, entry);
            context.close();
            logger.debug("LdapManager: Created role: "+newdn);
            
        } catch (NamingException e) {
            logger.error("LdapManager error: Error creating role, "+e);
            return status;
        }
        
        status = true;
        return status;
    }
    
    /**
     * Updates role related data
     */
    public boolean updateRole(String roleDn, String roleName, Vector<AccessRight> accessrights) {
        
        //role: cn=name,o=organization,dc=nn,dc=fi
        DirContext context = null;
        String newDn = null;
        
        try {
            
            context = getContext();
            
            // get name
            String currentRoleName = "";
            String[] parts = roleDn.split(",");
            for (int i=0; i<parts.length; i++) {
                if (parts[i].startsWith("cn=")) {
                    currentRoleName = parts[i].replaceFirst("cn=", "");
                    break;
                }
            }
            
            // modify role, remove accessrights
            ModificationItem[] mods = new ModificationItem[3];
            mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL));
            mods[1] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(LdapManager.ROLE_ACCESSRIGHTLEVEL));
            mods[2] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(LdapManager.ROLE_ACCESSRIGHTPATH));
            context.modifyAttributes(roleDn, mods);

            // check if roleName changed
            if (roleName != null && !currentRoleName.equals(roleName)) {
                newDn = roleDn.replace("cn="+currentRoleName, "cn="+roleName);
                // change dn 
                context.rename(roleDn, newDn);
                roleDn = newDn;
            }
            
            // modify role, add new accessrights
            int i=0;
            
            for (AccessRight accessRight : accessrights) {
                mods = new ModificationItem[3];
                if (accessRight != null) {
                    String premark = Integer.toString(i)+"-";
                    String publicityLevel = premark+accessRight.getPublicityLevel();
                    String acLevel = premark+Integer.toString(accessRight.getAccessRightLevel());
                    String di = premark+accessRight.getAccessRightPath();
                    
                    mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL, publicityLevel));
                    mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute(LdapManager.ROLE_ACCESSRIGHTLEVEL, acLevel));
                    mods[2] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute(LdapManager.ROLE_ACCESSRIGHTPATH, di));
                    
                    context.modifyAttributes(roleDn, mods);
                    i++;
                }
            }

            logger.debug("LdapManager:role '"+roleDn+"' updated.");
            context.close();
            
        } catch (NamingException e) {
            logger.error("LdapManager error: Error updating role, "+e);
            return false;
        }
        
        return true;
    }
    
    public Vector<String> getOrganizations() {
    	
    	DirContext context = null;
    	Vector<String> values = new Vector<String>();
    	
    	try {
    		context = getContext();
            SearchControls ctls = new SearchControls();
            String[] attrIDs = {"o"};
            ctls.setReturningAttributes(attrIDs);
            ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE);

            NamingEnumeration<SearchResult> answer = context.search( getDomainComponents(), "(objectclass=OpenLDAPorg)",ctls );
            while (answer.hasMore()) {
                SearchResult rslt = answer.next();
                Attributes attrs = rslt.getAttributes();
                
                if (attrs.get("o") != null) {
                	String test = attrs.get("o").toString();
                	test = test.replace("o: ","");
                	values.addElement(test);
                }
            }
            
            context.close();

        } catch (NamingException e) {
        	logger.error("LdapManager error: Error searching organizations, "+e);
        }
    	
    	return values;
    }
    
    public Vector<String> getGroups(String organization) {
    	
    	Vector<String> values = new Vector<String>();
    	if (organization == null) {
    		return values;
    	}
    	DirContext context = null;
    	String usersContainer = "o="+ organization +","+getDomainComponents();
    	
    	try {
    		context = getContext();
            SearchControls ctls = new SearchControls();
            String[] attrIDs = {"ou"};
            ctls.setReturningAttributes(attrIDs);
            ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE);

            NamingEnumeration<SearchResult> answer = context.search( usersContainer, "(objectclass=organizationalUnit)",ctls );
            while (answer.hasMore()) {
                SearchResult rslt = answer.next();
                Attributes attrs = rslt.getAttributes();
                
                if (attrs.get("ou") != null) {
                	String test = attrs.get("ou").toString();
                	test = test.replace("ou: ","");
                	values.addElement(test);
                }
            }
            
            context.close();

        } catch (NamingException e) {
        	logger.error("LdapManager error: Error searching organizations, "+e);
        }
    	
    	return values;
    }
    
    /**
     * Returns all users for organization
     */
    public List<User> getUsers(String organization) {
        List<User> users = new ArrayList<User>();
        DirContext context = null;

        if (organization == null || organization == "") {
            return users;
        }

        String GROUP_CTX = "o="+organization+","+getDomainComponents();
        String ORGANIZATION_ROLES_QUERY = "(&(objectClass=organizationalUnit))";
        String ORGANIZATION_USERS_QUERY = "(&(objectClass=inetOrgPerson))";
        User user = null;
        String group = "";
        SearchControls ctls = new SearchControls();
        
        try {
            context = getContext();
            NamingEnumeration<SearchResult> groupResults = context.search(GROUP_CTX, ORGANIZATION_ROLES_QUERY, ctls);
            // groups
            while (groupResults.hasMore()) {
                SearchResult result = groupResults.next();
                group = result.getAttributes().get("ou").get().toString();
                String USER_CTX = "ou="+group+","+GROUP_CTX;
                NamingEnumeration<SearchResult> userResults = context.search(USER_CTX, ORGANIZATION_USERS_QUERY, ctls);
                // users
                while (userResults.hasMore()) {
                    SearchResult userResult = userResults.next();
                    user = new User();
                    user.setGroup(group);
                    
                    if (userResult.getAttributes().get(LdapManager.PERSON_COMMONNAME) != null) {
                        user.setCn(userResult.getAttributes().get(LdapManager.PERSON_COMMONNAME).get().toString());
                        user.setDn("cn="+user.getCn()+","+USER_CTX);
                    }
                    if (userResult.getAttributes().get(LdapManager.PERSON_FIRSTNAME) != null) {
                        user.setFirstName(userResult.getAttributes().get(LdapManager.PERSON_FIRSTNAME).get().toString());
                    }
                    if (userResult.getAttributes().get(LdapManager.PERSON_LASTNAME) != null) {
                        user.setLastName(userResult.getAttributes().get(LdapManager.PERSON_LASTNAME).get().toString());
                    }
                    if (userResult.getAttributes().get(LdapManager.PERSON_EMAIL) != null) {
                        user.setMail(userResult.getAttributes().get(LdapManager.PERSON_EMAIL).get().toString());
                    }
                    if (userResult.getAttributes().get(LdapManager.PERSON_LANGUAGE) != null) {
                        user.setPreferredLanguage(userResult.getAttributes().get(LdapManager.PERSON_LANGUAGE).get().toString());
                    }
                    users.add(user);
                    
                }
                userResults.close();
            }
            groupResults.close();
            
            // Find Fedora roles from ldap and set roles to users
            for (User currentUser : users) {
                currentUser.setFedoraRoles(Osa.authManager.getUserRoles(currentUser.getDn(), organization));
            }
            
            context.close();
            
        } catch (NamingException e) {
            logger.error("LdapManager error: User query failed, "+e);
        }
        
        return users;
    }
    
    /** Get user's info from LDAP
     * 
     * @param dn	User dn
     * @return		User object
     */
    public User getUser(String userDn) {
    	DirContext context 	= null;
    	User userInfo 		= new User();
    	String dn 			= "";
    	String org			= "";
    	String ou			= "";
    	
    	if (!userDn.endsWith(getDomainComponents())) {
    		dn = userDn +","+ getDomainComponents();
    	} else {
    		dn = userDn;
    	}
    	
    	try {
    		String[] parts = dn.split(",");
    		for (int i = 0; i < parts.length; i++) {
    			if (parts[i].startsWith("ou=")) {
    				ou = parts[i].split("=")[1];
    			}
    			if (parts[i].startsWith("o=")) {
    				org = parts[i].split("=")[1];
    			}
    		}
    		userInfo.setDn(dn);
    		userInfo.setGroup(ou);
    		
    		context = getContext();
    		
    		Attributes attrs = context.getAttributes(dn);
    		NamingEnumeration<?> i = attrs.getAll();
    		while (i.hasMore()) {
    			Attribute attr = (Attribute) i.next();
    			String values = "";
    			
    			NamingEnumeration<?> e = attr.getAll();

    			while (e != null && e.hasMore()) {
    			    String value = e.next().toString();
    			    if (attr.getID().equals(LdapManager.ROLE_ACCESSRIGHTLEVEL) 
    			        || attr.getID().equals(LdapManager.ROLE_ACCESSRIGHTPATH) 
    			        || attr.getID().equals(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL)) {

    			        if (value.contains("-")) {
    			            value = value.substring(value.indexOf("-")+1);
    			        }
    			    } 
    			    
    			    if (values == "") {
    			        values += value;
    			    } else {
    			        // separate values
    			        values = values+";"+value;
    			    }
    			}
    			
    			if (LdapManager.PERSON_COMMONNAME.equals(attr.getID())) {
    				if (!values.isEmpty()) {userInfo.setCn(values);}
    			}
    			if (LdapManager.PERSON_FIRSTNAME.equals(attr.getID())) {
    				if (!values.isEmpty()) {userInfo.setFirstName(values);}
    			}
    			if (LdapManager.PERSON_LASTNAME.equals(attr.getID())) {
    				if (!values.isEmpty()) {userInfo.setLastName(values);}
    			}
    			if (LdapManager.PERSON_EMAIL.equals(attr.getID())) {
    				if (!values.isEmpty()) {userInfo.setMail(values);}
    			}
    			if (LdapManager.PERSON_LANGUAGE.equals(attr.getID())) {
    				if (!values.isEmpty()) {userInfo.setPreferredLanguage(values);}
    			}
    		}
    		
    		userInfo.setFedoraRoles(Osa.authManager.getUserRoles(userInfo.getDn(), org));
    		context.close();
    		
    	} catch (Exception e) {
            logger.error("LdapManager error: user query failed, "+e);
    	}
    	
    	return userInfo;
    }
    
    /**
     * Returns public role (f.ex. anonymous user)
     */
    public Role getPublicRole(String organization) {
        Role role = getRole(Role.ROLE_PUBLIC, organization);
        return role;
    }
    
    /**
     * Returns all roles for organization
     */
    public List<Role> getRoles(String organization) {
        List<Role> roles = new ArrayList<Role>();
        DirContext context = getContext();
        SearchControls ctls = new SearchControls();
        Role role = null;

        if (organization == null || organization == "") {
            return roles;
        }
        
        String CURRENT_CTX = "o="+organization+","+getDomainComponents();
        String ORGANIZATION_ROLE_QUERY = "(&(objectClass=organizationalRole))";
        
        try {
            NamingEnumeration<SearchResult> results = context.search(CURRENT_CTX, ORGANIZATION_ROLE_QUERY, ctls);
            while (results.hasMore()) {
                role = new Role();
                int groups = 0;
                int roleOccupants = 0;
                
                SearchResult result = results.next();
                
                if (result.getAttributes().get(LdapManager.ROLE_NAME) != null) {
                    role.setName(result.getAttributes().get(LdapManager.ROLE_NAME).get().toString());
                }
                
                // Handle several attribute-groups (publicityLevel-accessRightLevel-destination)
                if (result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL) != null) {
                    groups = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL).size();
                }
                
                for (int i=0; i<groups; i++) {
                    String premark = Integer.toString(i)+"-";
                    String publicityLevel = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL).get(i).toString();
                    String accessRightLevel = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTLEVEL).get(i).toString();
                    String destinationIndicator = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPATH).get(i).toString();
                    
                    publicityLevel = publicityLevel.replaceFirst(premark, "");
                    accessRightLevel = accessRightLevel.replaceFirst(premark, "");
                    destinationIndicator = destinationIndicator.replaceFirst(premark, "");
                    
                    AccessRight right = new AccessRight(publicityLevel, Integer.parseInt(accessRightLevel), destinationIndicator);
                    role.setRoleAccessright(right);
                }
                
                if (result.getAttributes().get(LdapManager.ROLE_USER) != null) {
                    roleOccupants = result.getAttributes().get(LdapManager.ROLE_USER).size();
                }

                for (int i=0; i<roleOccupants; i++) {
                    // userdns, who has this role
                    role.setRoleOccupant(result.getAttributes().get(LdapManager.ROLE_USER).get(i).toString());
                }
                
                roles.add(role);
            }
            results.close();
            context.close();
            
        } catch (NamingException e) {
            logger.error("LdapManager error: Role query failed, "+e);
        }
        
        return roles;
    }
    
    public HashMap<String,String> getAttributes(String objectdn) {
    	DirContext context = null;
    	HashMap<String,String> datatable = new HashMap<String,String>();
    	
    	String dn = "";
    	
    	if (!objectdn.endsWith(getDomainComponents())) {
    		dn = objectdn +","+ getDomainComponents();
    	} else {
    		dn = objectdn;
    	}
    	
    	try {

    		context = getContext();    		
    		Attributes attrs = context.getAttributes(dn);
    		
    		NamingEnumeration<?> i = attrs.getAll();
    		
    		while (i.hasMore()) {
    			Attribute attr = (Attribute) i.next();
    			String values = "";
    			NamingEnumeration<?> e = attr.getAll();

    			while (e != null && e.hasMore()) {
    			    String value = e.next().toString();
    			    
    			    if (attr.getID().equals(LdapManager.ROLE_ACCESSRIGHTLEVEL) 
    			        || attr.getID().equals(LdapManager.ROLE_ACCESSRIGHTPATH) 
    			        || attr.getID().equals(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL)) {

    			        if (value.contains("-")) {
    			            value = value.substring(value.indexOf("-")+1);
    			        }
    			    } 
    			    
    			    if (values == "") {
    			        values += value;
    			    } else {
    			        // separate values
    			        values = values+";"+value;
    			    }
    			}
    			datatable.put(attr.getID(), values);
    		}
    		
    		context.close();
    		
    	} catch (Exception e) {
            logger.error("LdapManager error: attributes not found for "+dn+", "+e);
    	}
    	
    	return datatable;
    	
    }
    
    public Vector<String> getOrganizationRoles(String organization) {
        
        if (organization == null) {
            return null;
        }
        
        String CURRENT_CTX = "o="+organization+","+getDomainComponents();
        String ORGANIZATION_ROLE_QUERY = "(&(objectClass=organizationalRole))";
        DirContext context = getContext();
        SearchControls ctls = new SearchControls();
        Vector<String> roles = new Vector<String>();
        
        try {
            
            NamingEnumeration<SearchResult> results = context.search(CURRENT_CTX, ORGANIZATION_ROLE_QUERY, ctls);
            while (results.hasMore()) {
                SearchResult result = results.next();
                String roleName = result.getAttributes().get(LdapManager.ROLE_NAME).get().toString();
                roles.add(roleName);
            }
            results.close();
            context.close();
            
        } catch(NamingException e) {
            logger.error("LdapManager error: Role query failed, "+e);
        }

        return roles;
    }
    
    /**
     * Get role details
     */
    public Role getRole(String roleName, String organization) {
        if (organization == null) {
            return null;
        }
        
        String CURRENT_CTX 	= "o="+organization+","+getDomainComponents();
        String ROLE_QUERY 	= "(&(objectClass=organizationalRole)(cn="+roleName+"))";
        DirContext context 	= getContext();
        SearchControls ctls = new SearchControls();
        Role role 			= null;
        
        try {
            
            NamingEnumeration<SearchResult> results = context.search(CURRENT_CTX, ROLE_QUERY, ctls);
            while (results.hasMore()) {
                SearchResult result = results.next();
                String name = result.getAttributes().get(LdapManager.ROLE_NAME).get().toString();
                role = new Role(name);
                // Handle several attribute-groups (publicityLevel-accessRightLevel-destination)
                int groups = 0;
                int roleOccupants = 0;
                
                if (result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL) != null) {
                    groups = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL).size();
                }
                
                for (int i=0; i<groups; i++) {
                    String premark = Integer.toString(i)+"-";
                    String publicityLevel = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL).get(i).toString();
                    String accessRightLevel = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTLEVEL).get(i).toString();
                    String destinationIndicator = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPATH).get(i).toString();
                    
                    publicityLevel = publicityLevel.replaceFirst(premark, "");
                    accessRightLevel = accessRightLevel.replaceFirst(premark, "");
                    destinationIndicator = destinationIndicator.replaceFirst(premark, "");
                    
                    AccessRight right = new AccessRight(publicityLevel, Integer.parseInt(accessRightLevel), destinationIndicator);
                    role.setRoleAccessright(right);
                }
                
                if (result.getAttributes().get(LdapManager.ROLE_USER) != null) {
                    roleOccupants = result.getAttributes().get(LdapManager.ROLE_USER).size();
                }

                for (int i=0; i<roleOccupants; i++) {
                    // userdns, who has this role
                    role.setRoleOccupant(result.getAttributes().get(LdapManager.ROLE_USER).get(i).toString());
                }
            }
            results.close();
            context.close();
            
        } catch(NamingException e) {
            logger.error("LdapManager error: Role query failed, "+e);
        }
        
        return role;
    }
    
    /**
     * Get roles for user according to dn and organization name
     * @param user          user dn (f.ex. "cn=name,ou=group,o=organization,dc=nn,dc=fi")
     * @param organization  name of user organization
     */
    public Vector<Role> getUserRoles(String user, String organization) {
        if (organization == null) {
            return null;
        }
        
        String CURRENT_CTX 		= "o="+organization+","+getDomainComponents();        
        String USER_ROLE_QUERY 	= "(&(objectClass=organizationalRole)(roleOccupant={0}))";
        DirContext context 		= getContext();
        SearchControls ctls 	= new SearchControls();
        Vector<Role> roles 		= new Vector<Role>();
        
        try {
            
            NamingEnumeration<SearchResult> results = context.search(CURRENT_CTX, USER_ROLE_QUERY, new Object[] { user }, ctls);
            while (results.hasMore()) {
                SearchResult result = results.next();
                String name = result.getAttributes().get(LdapManager.ROLE_NAME).get().toString();
                Role role = new Role(name);
                // Handle several attribute-groups (publicityLevel-accessRightLevel-destination)
                int groups = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL).size();
                
                for (int i=0; i<groups; i++) {
                    String premark = Integer.toString(i)+"-";
                    String publicityLevel = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPUBLICITYLEVEL).get(i).toString();
                    String accessRightLevel = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTLEVEL).get(i).toString();
                    String destinationIndicator = result.getAttributes().get(LdapManager.ROLE_ACCESSRIGHTPATH).get(i).toString();
                    publicityLevel = publicityLevel.replaceFirst(premark, "");
                    accessRightLevel = accessRightLevel.replaceFirst(premark, "");
                    destinationIndicator = destinationIndicator.replaceFirst(premark, "");
                    
                    AccessRight right = new AccessRight(publicityLevel, Integer.parseInt(accessRightLevel), destinationIndicator);
                    role.setRoleAccessright(right);
                }
                
                roles.add(role);
            }
            results.close();
            context.close();
            
        } catch (NamingException e) {
            logger.error("LdapManager error: Role query for user failed, "+e);
        }

        return roles;
    }
    
    /**
     * Add user to ldap role
     * @param userdn
     * @param organization
     * @param role
     */
    public void addUserToRole(String userdn, String organization, String role) {
        
        DirContext context = null;
        //role: cn=name,o=organization,dc=nn,dc=fi
        String ROLE_CTX = "cn="+role+",o="+organization+","+getDomainComponents();
        
        if (role.equals(Role.ROLE_PUBLIC)) {
            Vector<String> orgRoles = getOrganizationRoles(organization);
            // check if public role exists
            if (orgRoles == null || orgRoles.isEmpty() || !orgRoles.contains(Role.ROLE_PUBLIC)) {
                // create public role
                this.createRole(Role.ROLE_PUBLIC, organization, Role.getPublicRoleAccessrights(organization));
                logger.info("LdapManager: "+Role.ROLE_PUBLIC+" role created");
            }
        }
        
        try {
            
            context = getContext();
            
            ModificationItem[] mods = new ModificationItem[1];
            //user: cn=name,ou=group,o=organization,dc=nn,dc=fi
            Attribute mod0 = new BasicAttribute(LdapManager.ROLE_USER, userdn);
            mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod0);
            
            context.modifyAttributes(ROLE_CTX, mods);
            logger.debug("LdapManager: role '"+role+"' modified.");
            context.close();
                        
        } catch (NamingException e) {
            logger.error("LdapManager error: Error adding user to role, "+e);
        }
    }
    
    public void removeUserFromRole(String userdn) {
        try {
            context = getContext();

            String organization = "";
            // get organization
            String[] parts = userdn.split(",");
            for (int i=0; i<parts.length; i++) {
                if (parts[i].startsWith("o=")) {
                    organization = parts[i].substring(2, parts[i].length());
                    break;
                }
            }
            
            Vector<Role> roles = getUserRoles(userdn, organization);
            
            for (int i=0; i<roles.size(); i++) {
                String roleName = roles.get(i).name;
                String role_ctx = "cn="+roleName+",o="+organization+","+getDomainComponents();
                
                ModificationItem[] mods = new ModificationItem[1];
                Attribute mod0 = new BasicAttribute(LdapManager.ROLE_USER, userdn);
                mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, mod0);

                context.modifyAttributes(role_ctx, mods);
            }
            context.close();
            
        } catch (NamingException e) {
            logger.error("LdapManager error: Error removing user from role, "+e);
        }
    }
    
    public String modifyAttributes(String dn, String jsonMod) {
    	
    	String usrDn = "";
    	
    	if (!dn.endsWith(getDomainComponents())) {
    		usrDn = dn +","+ getDomainComponents();
    	} else {
    		usrDn = dn;
    	}
    	String newDn = usrDn;
    	
    	DirContext context = null;
    	HashMap values = (HashMap) new JSONDeserializer().deserialize(jsonMod);
    	HashMap modifiers = new HashMap();
    	
    	Iterator iterEntries = values.entrySet().iterator();
    	int i = 0;
    	
    	try {
    		context = getContext();
    		
	    	while (iterEntries.hasNext()) {
	    	    Map.Entry entry = (Map.Entry) iterEntries.next();
	    		String attr = entry.getKey().toString();
	    		String value = "";
	    		
	    		if (attr.equalsIgnoreCase(LdapManager.PERSON_PASSWORD)) {
	    		    value = hash256(entry.getValue().toString());
	    		} else {
	    		    value = entry.getValue().toString();
	    		}
	    		    		
	    		if (attr.equals("cn")) {
	    			String[] parts = newDn.split(",");
	    			int j = 0;
	    			while (parts[j].indexOf("cn=") != 0) {
	    				j++;
	    			}
	    			parts[j] = "cn="+entry.getValue();
	    			
	    			newDn = "";
	    			for (int n = 0;n < parts.length;n++) {
	    				if (n == 0) {
	    					newDn += parts[n];
	    				} else {
	    					newDn += ","+parts[n];
	    				}
	    			}	
	    			
	    		} else {
	    			modifiers.put(attr, value);
	    		}

	    	}
	    	
	    	if (!newDn.equals(usrDn)) {
	    		context.rename(usrDn, newDn);
	    	}
	    	
	    	ModificationItem[] mods = new ModificationItem[modifiers.size()];
	    	Iterator iterModentries = modifiers.entrySet().iterator();
	    	int ae = 0;
	    	while (iterModentries.hasNext()) {
	    		Map.Entry modentry = (Map.Entry) iterModentries.next();
	    		
	    		if ((modentry.getKey().toString().equals(LdapManager.ROLE_ACCESSRIGHTPATH) || modentry.getKey().toString().equals(LdapManager.ROLE_USER))
	    		    && modentry.getValue().toString().contains("[")) {
	    		    
	    		    // handle multivalues
    		        String multipleValues = modentry.getValue().toString();
    		        multipleValues = multipleValues.substring(1, multipleValues.length()-1);
    		        String[] arrayValues = multipleValues.split(", ");
    		        
    		        BasicAttribute attribute = new BasicAttribute(modentry.getKey().toString());
    		        for (int index=0; index<arrayValues.length; index++) {
    		            attribute.add(arrayValues[index]);
    		        }
    		        
    		        mods[ae] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute);
    		        
	    		} else {
	    		
	    		    mods[ae] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(modentry.getKey().toString(), modentry.getValue().toString()));
	    		}
	    		ae++;
	    	} 		
    		context.modifyAttributes(newDn, mods);
    		context.close();
    		
    	} catch (Exception e) {
    	    logger.error("LdapManager error: modify attributes failed, "+e);
    	}
    	
    	String result = "fail";
    	return result;
    }
    
    public void deleteEntry(String dn, EntryType entryType) {
    	DirContext context = null;
    	String usrDn = dn+","+getDomainComponents();
    	
    	try {
    		context = getContext();
    		context.destroySubcontext(usrDn);
    		
    		// Delete user from role
    		if (entryType.equals(EntryType.USER)) {
    			removeUserFromRole(usrDn);
    		}
    		
    		context.close();
    		
    	} catch (Exception e) {
    	    logger.error("LdapManager error: deleteEntry failed, "+e);
    	}	
    }
    
    private void setDomainComponents(String baseDomainComponents) {
        this.domainComponents = baseDomainComponents;
    }
    
    public String getDomainComponents() {
        return this.domainComponents;
    }
    
    private void setInstanceAdminDn(String instanceAdmin) {
        // Dn for instance admin
        if (!this.domainComponents.isEmpty() && instanceAdmin.endsWith(this.domainComponents)) {
            this.instanceAdminDn = instanceAdmin;
        }
    }
    
    public String getInstanceAdminDn() {
        return this.instanceAdminDn;
    }

} public abstract class DatabaseManager {
	
	private String host;
	private int port;
	private String username;
	private String password;
		
	public boolean testConnection(){
		return true;
	}
	
	public void close(){
    }
	
	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public String listItems() {
		return "";
		// TODO Auto-generated method stub
	}

	public void insertObject(String jsonData) {
		// TODO Auto-generated method stub
	}

	public void removeObject(String jsonData) {

	}

	public long getCtxTime(String organization) {
		// TODO Auto-generated method stub
		return 0;
	}

	public void setCtxTime(String organization) {
		// TODO Auto-generated method stub
	}
	
	public String getNextId(String parentPid) {
	    return null;
	}
	
    public void initIdGeneration(String parentPid, int id) {
        
    }

	/*
	 * BASKET METHODS
	 */
	public String insertToBasket(User user, String pid) {
		return "";
	}
	
	public String updateBasket(User user, String pid, String objName) {
		return "";
	}
	
	public List<BasketItem> getBasketItems(User user){
		return null;
	}
	
	public String listBasketContent(User user) {
		return "";
	}
	
	public void deleteFromBasket(User user, String pid) {
		
	}
	
	/** Syncs users' baskets if object is removed from fedora or renamed
	 * 
	 * @param pid		Pid of an object that has been deleted/disposed/renamed
	 * @param event		Name of database modification event <b>[delete|rename]</b> 
	 * @param newName	Objects new name, leave null if deleting.
	 */
	public void syncBaskets(String pid, String event, String newName) {
		
	}
	
	public boolean checkIfExists(User user, String pid) {
		return true;
	}
	
	public int getBasketSize(User user) {
		return 0;
	}
	
	public void clearTable(String tablename) {
		
	}
	
	/*
	 * DISPOSAL LIST METHODS
	 */
	/** Inserts item to disposal list
	 * 
	 * @param user	User object of user that has requested the disposal of the item
	 * @param pid	Pid of item
	 * @param date	Disposal date as a string, format yyyy-MM-dd HH:mm:ss	
	 * @return		Boolean success
	 */
	public boolean insertToDisposalList(User user, String pid, String date, String title) {
	    return true;
	}
	public void deleteFromDisposalList(String pid, String organization) {
        
    }
	public String listDisposalListContent() {
        return "";
    }
	public List<DisposalListItem> getDisposalListItems(String organization) {
		return null;
	}
	public int getDisposalListSize(String organization){
		return 0;
	}
	
	public boolean setCurrentuser(String user, String sessionid) {
	    return true;
	}
	
	public String removeCurrentuser(String user) {
        return "";
    }
	
	/*
	 * LATEST EDITED LIST METHODS
	 */
	public void addToLatestEdited(User user, String pid, String objectName, String objectId, String eventType) {
		
	}
	public String getLatestEdited(User user) {
		return "";
	}
	public int getLatestEditedCount(User user){
		return 0;
	}
	
	/*
	 * METADATA FILE METHODS
	 */
	/**	Stores metadata to database for given file
	 * 
	 * @param username	Username
	 * @param filename	Filename
	 * @param map		Metadata fields & values from form
	 */
	public void addMetadataFile(User user, String filename, LinkedHashMap<String, MetaDataElement> map, boolean massEdit){
		
	}
	
	/** Gets metadata from database for given file
	 * 
	 * @param username		Username (cn)
	 * @param filename		File name
	 * @return				LinkedHashMap containing the metadata fields and their values
	 */
	public LinkedHashMap<String, MetaDataElement> getMetadataFile(User user, String filename) {
    	return null;
    }
	
	public boolean checkIfHasMetadata(User user, String filename) {
		return false;
	}
	
	public void removeMetadataFile(User user, String filename) {
	    
	}

	public String getFeedbackSize() {
		return "";
	}

} public class MongoManager extends DatabaseManager {
    
    private static final Logger logger = Logger.getLogger(MongoManager.class);
	public static String EVENT_REMOVE = "remove";
	public static String EVENT_RENAME = "rename";
	public static String EVENT_ADD 	  = "add";
	public static String EVENT_UPDATE = "update";
	
	// Collections names
	private static final String DB_BASKET	   = "basket";
	private static final String DB_DISPOSAL	   = "disposal";
	private static final String DB_LATESTEDITS = "latestEdits";
	private static final String DB_METADATA	   = "metadatafiles";
	
	// Names of arrays holding the documents in collections
	private static final String ARRAY_BASKET	  = "pids";
	private static final String ARRAY_DISPOSAL	  = "disposalItems";
	private static final String ARRAY_LATESTEDITS = "pids";
	private static final String ARRAY_METADATA	  = "metadatafiles";
	
	//Only for testing purposes, for final implementation these should be specified in conf.
	private String Host;
	private int port;
	private String defaultDB;
	private String defaultCollection;
	
	private MongoClient mongo;
	private DB db;
	private DBCollection table;
	
	public MongoManager(String host, int port, String dbname, String collection) {
		this.Host = host;
		this.port = port;
		this.defaultDB = dbname;
		this.defaultCollection = collection;
		try {
			this.mongo = new MongoClient(this.Host,this.port);
			this.db = mongo.getDB(this.defaultDB);
			this.table = db.getCollection(this.defaultCollection);
			
		} catch (UnknownHostException e) {
		    logger.error("MongoManager initializing error, "+e);
		}
		
	}
	
	public void close() {
	    this.mongo.close();
	}
	
	@Override
	public boolean testConnection() {
		// TODO Auto-generated method stub
		return super.testConnection();
	}
	
	public void selectDB(String db) {
		this.db = mongo.getDB(db);	
	}
	
	public void selectCollection(String collection) {
		this.table = db.getCollection(collection);
	}
	
	public void insertObject(String json) {
		this.selectCollection(defaultCollection);
		DBObject dbocject = (DBObject) JSON.parse(json);
		this.table.insert(dbocject);
	}
	
	public void removeObject(String objectid) {
		this.selectCollection(defaultCollection);
	    BasicDBObject query = new BasicDBObject();
	    query.put("_id", new ObjectId(objectid));
	    table.remove(query);
	}
	
	public String getFeedbackSize(){
		this.selectCollection(defaultCollection);	
		long feedbackSize = table.count();		
		return String.valueOf(feedbackSize+1);
	}
	
	public String listItems() {
		
		this.selectCollection(defaultCollection);
		DBCursor cursor = table.find();
		String jsonResult = "[";	
		while (cursor.hasNext()) {
			jsonResult += cursor.next().toString() +",";
		}

		cursor.close();		
		jsonResult += "]";			
		return jsonResult;
	}
	
	public String insertToBasket(User user, String pid) {
		this.selectCollection(DB_BASKET);
		
		String success = "";
		String username = user.getOrganization().getName()+":"+user.getMail();
		String objName = Osa.searchManager.getItemByPid(pid, user, user.getLocale()).replace("[", "").replace("]", "");
		String objType = Osa.searchManager.getObjectType(pid, user);
		
		Vector<HashMap<String, String>> pidVector = new Vector<HashMap<String, String>>();
		HashMap<String, String> pidDatamap = new HashMap<String, String>();
		
		BasicDBObject query = new BasicDBObject();
		DBCursor cursor = table.find(new BasicDBObject("username", username), new BasicDBObject(ARRAY_BASKET,1).append("_id",false));
		// If basket already has items
		if (cursor.size() != 0) {
			success = this.updateBasket(user, pid, objName);
		} else {
			// If basket is empty, create new
			pidDatamap.put("pid", pid);
			pidDatamap.put("name", objName);
			pidDatamap.put("type", objType);
			pidVector.add(pidDatamap);
			
			query.put("username", username);
			query.put(ARRAY_BASKET, pidVector);
			
			this.table.insert(query);
			success = "success";
		}
		cursor.close();
		return success;
	}
	
	public String updateBasket(User user, String newpid, String objname) {
		this.selectCollection(DB_BASKET);
		String username = user.getOrganization().getName()+":"+user.getMail();
		String success = "";
		String objType = Osa.searchManager.getObjectType(newpid, user);
		
		if (!checkIfExists(user, newpid)) {
			BasicDBObject updateQuery = new BasicDBObject("username", username);
			BasicDBObject updateCommand = new BasicDBObject();
			
			updateCommand.put("$push", 
							new BasicDBObject(ARRAY_BASKET, new BasicDBObject("pid",newpid)
							.append("name", objname)
							.append("type", objType)));
			this.table.update(updateQuery, updateCommand);
			success = "success";
		} else {
			success = "alreadyexists";
		}
		return success;
	}
	
	/**
     * @param user      name of the user (cn from ldap)
     * @param pid       pid of the object to be deleted
     */
    public void deleteFromBasket(User user, String pid) {
        this.selectCollection(DB_BASKET);
        String username = user.getOrganization().getName()+":"+user.getMail();
        // Delete from this user
        BasicDBObject updateQuery = new BasicDBObject("username", username);
        // from pids array where pid = given pid
        BasicDBObject updateCommand = new BasicDBObject(ARRAY_BASKET, new BasicDBObject("pid", pid));
        this.table.update(updateQuery, new BasicDBObject("$pull", updateCommand));
    }
    
    public void syncBaskets(String pid, String event, String newName) {
        this.selectCollection(DB_BASKET);
        BasicDBObject updateQuery = new BasicDBObject();
        // Deletes all entries with given pid. The second (multi) true flag enables multi update
        if (event.equals(EVENT_REMOVE)) {
	        BasicDBObject updateCommand = new BasicDBObject(ARRAY_BASKET, new BasicDBObject("pid", pid));
	        this.table.update(updateQuery, new BasicDBObject("$pull", updateCommand), false, true);
        }
        else if (event.equals(EVENT_RENAME)) {
        	updateQuery = new BasicDBObject("pids.pid", pid);
        	BasicDBObject updateCommand = new BasicDBObject("pids.$.name", newName);
	        this.table.update(updateQuery, new BasicDBObject("$set", updateCommand), false, true);
        }
    }
    
	/**
	 * @param user		User object
	 * @param pid		pid of the object
	 * @return true or false
	 */
	public boolean checkIfExists(User user, String pid) {
		this.selectCollection(DB_BASKET);
		String username = user.getOrganization().getName()+":"+user.getMail();
		boolean exists = false;		
		DBCursor cursor = table.find(new BasicDBObject("username", username), new BasicDBObject(ARRAY_BASKET,1).append("_id",false));
		
		while (cursor.hasNext()) {
			if (cursor.next().get(ARRAY_BASKET).toString().contains(pid)) {
				exists = true;
			}
		}
		cursor.close();
		return exists;
	}
	
	public List<BasketItem> getBasketItems(User user) {
		this.selectCollection(DB_BASKET);
		String username = user.getOrganization().getName()+":"+user.getMail();
		DBCursor cursor = table.find(new BasicDBObject("username", username), new BasicDBObject(ARRAY_BASKET,1).append("_id",false));
		List<BasketItem> basketItems = new ArrayList<BasketItem>();
		
		if (cursor.size() > 0) {
			while (cursor.hasNext()) {
				List<Map<String,String>> result = new JSONDeserializer<List<Map<String,String>>>().deserialize(cursor.next().get(ARRAY_BASKET).toString());
		        for (int i = 0; i < result.size(); i++) {
			        BasketItem basketItem = new BasketItem();
			        basketItem.setPid(result.get(i).get("pid"));
			        basketItem.setType(result.get(i).get("type"));
			        basketItem.setName(result.get(i).get("name"));
			        basketItems.add(basketItem);
		        }
			}
		}
		cursor.close();
		return basketItems;
	}
	
	/**
	 * @param user		name of the user (cn from ldap)
	 * 
	 * @return Content of the basket in json format
	 */
	public String listBasketContent(User user) {
		this.selectCollection(DB_BASKET);
		String username = user.getOrganization().getName()+":"+user.getMail();
		String jsonresult = "";
		// Exclude database row id from results with .append("_id",false)
		DBCursor cursor = table.find(new BasicDBObject("username", username), new BasicDBObject(ARRAY_BASKET,1).append("_id",false));
    	
		if (cursor.size() > 0) {
			while (cursor.hasNext()) {
				jsonresult = "["+cursor.next().toString();
			}
		}

		if (cursor.size() > 0) {
			jsonresult += "]";
		}
		cursor.close();
		return jsonresult;
	}
	
	/** Counts the amount of objects in given user's basket.
	 * 
	 * @param	user	Username, ldap cn
	 * @return	Integer number of items in basket
	 */
	public int getBasketSize(User user) {
	    this.selectCollection(DB_BASKET);
	    String username = user.getOrganization().getName()+":"+user.getMail();
	    DBCursor cursor = table.find(new BasicDBObject("username", username), new BasicDBObject(ARRAY_BASKET,1).append("_id",false));
	    int size = 0;
	    
	    while (cursor.hasNext()) {
	        Map<String,List<Map<String,String>>> result = new JSONDeserializer<Map<String,List<Map<String,String>>>>().deserialize(cursor.next().toString());
	        List<Map<String,String>> map = result.get(ARRAY_BASKET);
	        size += map.size();
	    }
	    cursor.close();
		return size;
	}
	
	public void clearTable(String tablename) {
		this.selectCollection(tablename);
		this.table.drop();
		logger.info("MongoManager: Table "+table.getName()+ " dropped.");
	}

	//************************************************************
	// METHODS FOR DISPOSAL LIST
	//************************************************************
	public boolean insertToDisposalList(User user, String pid, String date, String title) {
        this.selectCollection(DB_DISPOSAL);
        
        boolean success = false;
        long timestamp = 0;
        String username = "";
        String objName = "";
        String objType = "";
        String organization = "";

        if (user != null) {
            objName = Osa.searchManager.getItemByPid(pid, user, user.getLocale()).replace("[", "").replace("]", "");
            objType = Osa.searchManager.getObjectType(pid, user);
            username = user.getCn();
            organization = user.getOrganization().getName();
            
        } else {
            // when scheduled task executed, user not defined
            objName = title;
            // get type from pid
            String pidType = pid.split(":")[1].split("-")[0];
            if (RepositoryManager.DATATYPES_MAP.containsKey(pidType)) {
                objType = RepositoryManager.DATATYPES_MAP.get(pidType);
            }
            // automatic
            username = DestructionPlan.DESTRUCTIONREASON_Scheduled;
            // get organization from pid
            organization = pid.split(":")[0].toLowerCase();
        }
        
        Vector<HashMap<String, String>> pidVector = new Vector<HashMap<String, String>>();
		HashMap<String, String> pidDatamap = new HashMap<String, String>();
        
        BasicDBObject query = new BasicDBObject();
        DBCursor cursor = table.find(new BasicDBObject("organization", organization), new BasicDBObject(ARRAY_DISPOSAL,1).append("_id",false));
        
        if (cursor.size() != 0) {
            success = this.updateDisposalList(pid, objName, date, user);
            
        } else {
        	try {
    			timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(date).getTime();
    		} catch (ParseException e1) {
    			e1.printStackTrace();
    		}
        	pidDatamap.put("pid", pid);
			pidDatamap.put("objname", objName);
			pidDatamap.put("objtype", objType);
			pidDatamap.put("username", username);
			pidDatamap.put("date", date);
			pidDatamap.put("timestamp", String.valueOf(timestamp));
			pidVector.add(pidDatamap);
			
			query.put("organization", organization);
			query.put(ARRAY_DISPOSAL, pidVector);
			
            this.table.insert(query);
            success = true;
        }
        cursor.close();
        return success;
    }
	
	public boolean updateDisposalList(String pid, String objectName, String disposalDate, User user) {
		this.selectCollection(DB_DISPOSAL);
		
		boolean success = false;
		boolean exists 	= false;
		long timestamp 	= 0;
		String organization = "";
		String username = "";
		String objType = "";
		
		if (user != null) {
		    organization = user.getOrganization().getName();
		    username = user.getCn();
		    objType = Osa.searchManager.getObjectType(pid, user);
		} else {
            // get organization from pid
            organization = pid.split(":")[0].toLowerCase();
            //
            username = DestructionPlan.DESTRUCTIONREASON_Scheduled;
            // get type from pid
            String pidType = pid.split(":")[1].split("-")[0];
            if (RepositoryManager.DATATYPES_MAP.containsKey(pidType)) {
                objType = RepositoryManager.DATATYPES_MAP.get(pidType);
            }
		}
				
		DBCursor existsCursor = table.find(new BasicDBObject("organization", organization), new BasicDBObject(ARRAY_DISPOSAL,1).append("_id",false));
		
		while (existsCursor.hasNext()) {
			if (existsCursor.next().get(ARRAY_DISPOSAL).toString().contains(pid)){
				exists = true;
			}
		}
		existsCursor.close();
		
		if (!exists) {
			try {
				timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(disposalDate).getTime();
			} catch (ParseException e1) {
				e1.printStackTrace();
			}
			
			BasicDBObject updateQuery = new BasicDBObject("organization", organization);
			BasicDBObject updateCommand = new BasicDBObject();

			updateCommand.put("$push", 
							new BasicDBObject(ARRAY_DISPOSAL, new BasicDBObject("pid",pid)
							.append("objname", objectName)
							.append("objtype", objType)
							.append("username", username)
							.append("date", disposalDate)
							.append("timestamp", String.valueOf(timestamp))));
			
			this.table.update(updateQuery, updateCommand);
			success = true;
		}
		
		return success;
	}
	
    /**
     * Remove object from disposal list
     * @param pid       	pid of the object to be deleted
     * @param organization  Name of the user organization
     */
    public void deleteFromDisposalList(String pid, String organization) {
        this.selectCollection(DB_DISPOSAL);
        BasicDBObject updateQuery = new BasicDBObject("organization", organization);
        BasicDBObject updateCommand = new BasicDBObject(ARRAY_DISPOSAL, new BasicDBObject("pid", pid));
        this.table.update(updateQuery, new BasicDBObject("$pull", updateCommand));
    }
        
    public List<DisposalListItem> getDisposalListItems(String organization){
    	this.selectCollection(DB_DISPOSAL);
    	List<DisposalListItem> disposalListItems = new ArrayList<DisposalListItem>();
        DBCursor cursor = table.find(new BasicDBObject("organization", organization), new BasicDBObject("_id", 0));
        
        if (cursor.size() > 0) {
            while (cursor.hasNext()) {
            	List<Map<String,String>> results = new JSONDeserializer<List<Map<String,String>>>().deserialize(cursor.next().get(ARRAY_DISPOSAL).toString());
	            for (int i = 0; i < results.size(); i++) {
	            	Map<String, String> item = results.get(i);
	            	DisposalListItem disposalItem = new DisposalListItem();
	            	
	            	disposalItem.setPid(item.get("pid"));
	            	disposalItem.setObjectName(item.get("objname"));
	            	disposalItem.setObjectType(item.get("objtype"));
	            	disposalItem.setDeleter(item.get("username"));
	            	disposalItem.setDisposalDateString(item.get("date"));
	            	disposalItem.setDisposalDateTimestamp(Long.parseLong(item.get("timestamp")));
	            	
					disposalListItems.add(disposalItem);
	            }
            }
        }
        cursor.close();
        return disposalListItems;
    }
    
    public int getDisposalListSize(String organization) {
    	this.selectCollection(DB_DISPOSAL);
    	//TODO Update mongo jar to 2.2 and use aggregation framework
    	DBCursor cursor = table.find(new BasicDBObject("organization", organization), new BasicDBObject(ARRAY_DISPOSAL,1).append("_id",false));
	    int size = 0;
	    
	    while (cursor.hasNext()) {
	        Map<String,List<Map<String,String>>> result = new JSONDeserializer<Map<String,List<Map<String,String>>>>().deserialize(cursor.next().toString());
	        List<Map<String,String>> map = result.get(ARRAY_DISPOSAL);
	        size += map.size();
	    }
	    cursor.close();
    	return size;
    }
    
	//************************************************************
	// METHODS FOR LATEST EDITED OBJECTS
	//************************************************************
    
    public void addToLatestEdited(User user, String pid, String objectName, String objectId, String eventType) {
    	this.selectCollection(DB_LATESTEDITS);
    	String username = user.getOrganization().getName()+":"+user.getMail();
    	String objName = objectName;
    	
    	Vector<HashMap<String, String>> pidVector = new Vector<HashMap<String, String>>();
		HashMap<String, String> pidDatamap = new HashMap<String, String>();
		
		BasicDBObject query = new BasicDBObject();
		DBCursor cursor = table.find(new BasicDBObject("username", username), new BasicDBObject(ARRAY_LATESTEDITS,1).append("_id",false)).sort(new BasicDBObject("editDate",1));
		if (cursor.size() != 0) {
			BasicDBObject updateQuery = new BasicDBObject("username", username);
			BasicDBObject updateCommand = new BasicDBObject();
			while (cursor.hasNext()) {
				// If updated object is not already on the list, update list
				if (!cursor.next().get(ARRAY_LATESTEDITS).toString().contains(pid)) {
							
					updateCommand.put("$push", new BasicDBObject(ARRAY_LATESTEDITS, 
							new BasicDBObject("pid",pid)
								.append("name", objName)
								.append("id", objectId)
								.append("event", eventType)
								.append("eventLocal", new LocalizableMessage("general."+eventType).getMessage(user.getLocale()))
								.append("editDate", String.valueOf(System.currentTimeMillis()))));
					// If the list has 12 items, remove last one (oldest)
					if (getLatestEditedCount(user) == 12) {
						BasicDBObject removeLastCommand = new BasicDBObject();
						removeLastCommand.put("$pop", new BasicDBObject(ARRAY_LATESTEDITS, -1));
						this.table.update(updateQuery, removeLastCommand);
					}
			
					this.table.update(updateQuery, updateCommand);
				} else {
					//TODO Refactor and use $set to update...
					updateCommand.put("$pull", new BasicDBObject(ARRAY_LATESTEDITS, new BasicDBObject("pid",pid)));
					this.table.update(updateQuery, updateCommand);
					
					updateCommand = new BasicDBObject();
					updateCommand.put("$push", new BasicDBObject(ARRAY_LATESTEDITS, 
							new BasicDBObject("pid",pid)
								.append("name", objName)
								.append("id", objectId)
								.append("event", eventType)
								.append("eventLocal", new LocalizableMessage("general."+eventType).getMessage(user.getLocale()))
								.append("editDate", String.valueOf(System.currentTimeMillis()))
								));
					this.table.update(updateQuery, updateCommand);
				}
			}
		// If list is empty, create new vector of objects
		} else {
			pidDatamap.put("pid", pid);
			pidDatamap.put("name", objName);
			pidDatamap.put("id", objectId);
			pidDatamap.put("event", eventType);
			pidDatamap.put("eventLocal", new LocalizableMessage("general."+eventType).getMessage(user.getLocale()));
			pidDatamap.put("editDate", String.valueOf(System.currentTimeMillis()));
			pidVector.add(pidDatamap);
			
			query.put("username", username);
			query.put(ARRAY_LATESTEDITS, pidVector);
			
			this.table.insert(query);
		}
		
		cursor.close();
    }
    
    /** Gets latest objects user has edited
     * @param	user	Username
     * @return	Object info in JSON
     */
    public String getLatestEdited(User user) {
    	String jsonresult 	= "";
    	String username 	= user.getOrganization().getName()+":"+user.getMail();
		this.selectCollection(DB_LATESTEDITS);
		// Exclude database row id from results with .append("_id",false)
		DBCursor cursor = table.find(new BasicDBObject("username", username), new BasicDBObject(ARRAY_LATESTEDITS,1).append("_id",false)).sort(new BasicDBObject("editDate", 1));
		
		if (cursor.size() > 0) {
			while (cursor.hasNext()) {
				jsonresult = "["+cursor.next().toString();
			}
		}

		if (cursor.size() > 0) {
			jsonresult += "]";
		}
		
		cursor.close();
		return jsonresult;
    }
    
    public int getLatestEditedCount(User user) {
	    this.selectCollection(DB_LATESTEDITS);
	    String username = user.getOrganization().getName()+":"+user.getMail();
	    DBCursor cursor = table.find(new BasicDBObject("username", username), new BasicDBObject(ARRAY_LATESTEDITS,1).append("_id",false));
	    int size = 0;
	    
	    while (cursor.hasNext()) {
	        Map<String,List<Map<String,String>>> result = new JSONDeserializer<Map<String,List<Map<String,String>>>>().deserialize(cursor.next().toString());
	        List<Map<String,String>> map = result.get(ARRAY_LATESTEDITS);
	        size += map.size();
	    }
	    
	    cursor.close();
		return size;
	}
    
    //************************************************************
    // METHODS FOR METADATA FILES
    //************************************************************
    
    public void addMetadataFile(User user, String filename, LinkedHashMap<String, MetaDataElement> map, boolean massEdit) {
        String username = user.getOrganization().getName()+":"+user.getMail();
    	this.selectCollection(DB_METADATA);
    	
    	BasicDBObject updateQuery = new BasicDBObject("username", username);
    	BasicDBObject updateCommand = new BasicDBObject();
    	BasicDBObject query = new BasicDBObject();
    	
    	DBCursor cursor = table.find(updateQuery, new BasicDBObject(ARRAY_METADATA,1).append("_id",false));
    
    	Vector<Object> metadatafiles = new Vector<Object>();
    	
    	MetaDataElement mdElem = new MetaDataElement();
    	mdElem.setName("filename");
    	mdElem.setValue(filename);
    	map.put("filename", mdElem);
    
    	Object serializedMap = null;
    
    	// User already has at least one file added to metadata list
    	if (cursor.size() != 0) {
    	    // Given file is not yet on the list, update db with the file
    	    if (!cursor.next().get(ARRAY_METADATA).toString().contains(filename)) {
                serializedMap = JSON.parse(new flexjson.JSONSerializer().deepSerialize(map));
                updateCommand.put("$push", new BasicDBObject(ARRAY_METADATA, serializedMap));
                this.table.update(updateQuery, updateCommand);
    	    } 
    	    // Given file is already on the list, update it's metadatas
    	    else {
    	        
                LinkedHashMap<String, MetaDataElement> oldMap = getMetadataFile(user, filename);
                // file has already data in mongo
                if (oldMap.size() > 0 && massEdit) {
                    // keep not modified metadata in database
                    for (Entry<String, MetaDataElement> entry : oldMap.entrySet()) {
                        if (!map.containsKey(entry.getKey())) {
                            map.put(entry.getKey(), entry.getValue());
                        }
                    }
                }
    			
                serializedMap = JSON.parse(new flexjson.JSONSerializer().deepSerialize(map));
                updateCommand = new BasicDBObject();
                // Appends filename to filter so query is "get from this user where filename is this"
                updateQuery.append(ARRAY_METADATA+".filename.value", filename);
                updateCommand.put("$set", new BasicDBObject(ARRAY_METADATA+".$", serializedMap));
                  
                this.table.update(updateQuery, updateCommand, true, false);
            }
    	} 
    	// User does not have any files added to metadata list
    	else {
            serializedMap = JSON.parse(new flexjson.JSONSerializer().deepSerialize(map));
            metadatafiles.add(serializedMap);
            query.put("username", username);
            query.put(ARRAY_METADATA, metadatafiles);
            this.table.insert(query);
    	}
    	
    	cursor.close();
    	
    }
    
    public LinkedHashMap<String, MetaDataElement> getMetadataFile(User user, String filename) {
        String username = user.getOrganization().getName()+":"+user.getMail();
    	this.selectCollection(DB_METADATA);
    	DBCursor cursor = table.find(new BasicDBObject("username", username), 
    									new BasicDBObject(ARRAY_METADATA, 
											new BasicDBObject("$elemMatch", 
												new BasicDBObject("filename.value", filename))));
    	
    	List<LinkedHashMap<String, MetaDataElement>> resultSet = new ArrayList<LinkedHashMap<String, MetaDataElement>>();
    	
    	ObjectMapper objMapper = new ObjectMapper();
    	// Prevents mapper from throwing exception on fields that are not present in given TypeReference
    	// In this case returned JSON has field "class" that is not found in MetaDataElement class
    	// Note: This is for jackson version 1.x.x. For 2.x use DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
    	objMapper.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    	
    	while (cursor.hasNext()) {
    		// Deserializes returned JSON to given data structure
    		resultSet = objMapper.convertValue(cursor.next().get(ARRAY_METADATA), new TypeReference<List<LinkedHashMap<String,MetaDataElement>>>() { });
    	}
    	if (resultSet == null || resultSet.isEmpty()) {
    		return new LinkedHashMap<String, MetaDataElement>();
    	}
    	// Remove filename entry. Can be left in if needed.
    	resultSet.get(0).remove("filename");
    	cursor.close();
    	return resultSet.get(0);
    	
    }
    
    public void removeMetadataFile(User user, String filename) {
        String username = user.getOrganization().getName()+":"+user.getMail();
        this.selectCollection(DB_METADATA);
        
        // remove file data
        BasicDBObject updateObject = new BasicDBObject().append("$pull", new BasicDBObject(ARRAY_METADATA,new BasicDBObject("filename.value",filename)));
        BasicDBObject updateQuery = new BasicDBObject("username", username);
        table.update(updateQuery, updateObject);
    }
    
    public boolean checkIfHasMetadata(User user, String filename) {
        String username = user.getOrganization().getName()+":"+user.getMail();
    	this.selectCollection(DB_METADATA);
    	boolean hasMetadata = false;
    	
    	DBCursor cursor = table.find(new BasicDBObject("username", username), new BasicDBObject(ARRAY_METADATA,1).append("_id",false));
    	
    	while (cursor.hasNext()) {
    		if (cursor.next().get(ARRAY_METADATA).toString().contains(filename)) {
    		    hasMetadata = true;
    		}
    	}
    	cursor.close();
    	return hasMetadata;
    }    
} public class DatabaseConfiguration {
    
    private String dbDriverName = null;
    private String dbUser = null;
    private String dbPassword = null;
    private String dbURI = null;
    
    private int dbPoolMinSize = 0;
    private int dbPoolMaxSize = 0;
    private int dbTimeout = 0;

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

    /**
     * Default constructor
     */
    public DatabaseConfiguration() {
        
    }
    
    /**
     * Constructor
     * @param xmlFile
     */
    public DatabaseConfiguration(String xmlFile) {
        SAXBuilder builder = new SAXBuilder();

        try {

            InputStream is = new FileInputStream( xmlFile );

            Document doc = builder.build ( is );
            Element root = doc.getRootElement();

            dbDriverName = root.getChild("dbDriverName").getTextTrim();
            dbUser = root.getChild("dbUser").getTextTrim();
            dbPassword = root.getChild("dbPassword").getTextTrim();
            dbURI = root.getChild("dbURI").getTextTrim();
            dbPoolMinSize = Integer.parseInt( root.getChild("dbPoolMinSize").getTextTrim() );
            dbPoolMaxSize = Integer.parseInt( root.getChild("dbPoolMaxSize").getTextTrim() );
            dbTimeout = Integer.parseInt( root.getChild("dbTimeout").getTextTrim() );
            logger.info("XXXXXXXXXXXXXX "+dbUser+"\n");

        }   catch ( Exception ex ) {
            logger.error( "Could not read configuration file: ", ex );
        }

    }

    public String getDbDriverName() {
        return dbDriverName;
    }

    public String getDbUser() {
        return dbUser;
    }

    public String getDbPassword() {
        return dbPassword;
    }

    public String getDbURI() {
        return dbURI;
    }

    public int getDbPoolMinSize() {
        return dbPoolMinSize;
    }

    public int getDbPoolMaxSize() {
        return dbPoolMaxSize;
    }

    public int getDbTimeout() {
        return dbTimeout;
    }

    /**
     * Function: setDbDriverName
     * @param dbDriverName the dbDriverName to set
     */
    public void setDbDriverName(String dbDriverName) {
        this.dbDriverName = dbDriverName;
    }

    /**
     * @param dbUser the dbUser to set
     */
    public void setDbUser(String dbUser) {
        this.dbUser = dbUser;
    }

    /**
     * @param dbPassword the dbPassword to set
     */
    public void setDbPassword(String dbPassword) {
        this.dbPassword = dbPassword;
    }

    /**
     * @param dbURI the dbURI to set
     */
    public void setDbURI(String dbURI) {
        this.dbURI = dbURI;
    }

    /**
     * @param dbPoolMinSize the dbPoolMinSize to set
     */
    public void setDbPoolMinSize(int dbPoolMinSize) {
        this.dbPoolMinSize = dbPoolMinSize;
    }

    /**
     * @param dbPoolMaxSize the dbPoolMaxSize to set
     */
    public void setDbPoolMaxSize(int dbPoolMaxSize) {
        this.dbPoolMaxSize = dbPoolMaxSize;
    }

    /**
     * @param dbTimeout the dbTimeout to set
     */
    public void setDbTimeout(int dbTimeout) {
        this.dbTimeout = dbTimeout;
    }

    @Override
    public String toString() {
        
        ReflectionToStringBuilder tsb = 
            new ReflectionToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
        return tsb.toString();
    }
} public class ConnectionManager {

    private static final Logger logger = Logger.getLogger(ConnectionManager.class);
    public static DataSource ds = null;
    private static GenericObjectPool _pool = null;

    /**
     * Constructor
    *  @param config configuration from an XML file.
    */
    public ConnectionManager(DatabaseConfiguration config)
    {
        try
        {
            connectToDB( config );
        }
        catch(Exception e)
        {
            logger.error( "Failed to construct ConnectionManager", e );
        }
    }

    /**
    *  destructor
    */
    protected void finalize()
    {
        logger.debug("Finalizing ConnectionManager");
        try
        {
            super.finalize();
        }
        catch(Throwable ex)
        {
            logger.error( "ConnectionManager finalize failed to disconnect from mysql: ", ex );
        }
    }


    /**
    *  connectToDB - Connect to the MySql DB!
    */
    private void connectToDB( DatabaseConfiguration config ) {

        try
        {
            java.lang.Class.forName( config.getDbDriverName() ).newInstance();
        }
        catch(Exception e)
        {
            logger.error("Error when attempting to obtain DB Driver: "
                    + config.getDbDriverName() + " on "
                    + new Date().toString(), e);
        }

        logger.debug("Trying to connect to database...");
        //System.out.println(config.getDbURI());
        try
        {
            SQLManager.dataSource = setupDataSource(
                    config.getDbURI(),
                    config.getDbUser(),
                    config.getDbPassword(),
                    config.getDbPoolMinSize(),
                    config.getDbPoolMaxSize(),
                    config.getDbTimeout());
            
            logger.debug("Connection attempt to database succeeded.");
            printDriverStats();
        }
        catch(Exception e)
        {
            logger.error("Error when attempting to connect to DB ", e);
        }
    }

    /**
     *
     * @param connectURI - JDBC Connection URI
     * @param username - JDBC Connection username
     * @param password - JDBC Connection password
     * @param minIdle - Minimum number of idel connection in the connection pool
     * @param maxActive - Connection Pool Maximum Capacity (Size)
     * @param timeout - timeout
     * @throws Exception
     */
    public static DataSource setupDataSource(String connectURI, 
        String username, 
        String password,
        int minIdle, int maxActive, int timeout
        ) throws Exception {

        //
        // First, we'll need a ObjectPool that serves as the
        // actual pool of connections.
        //
        // We'll use a GenericObjectPool instance, although
        // any ObjectPool implementation will suffice.
        //
        GenericObjectPool connectionPool = new GenericObjectPool(null);

        connectionPool.setMinIdle( minIdle );
        connectionPool.setMaxActive( maxActive );

        ConnectionManager._pool = connectionPool; 
        // we keep it for two reasons
      // #1 We need it for statistics/debugging
      // #2 PoolingDataSource does not have getPool()
      // method, for some obscure, weird reason.

        //
        // Next, we'll create a ConnectionFactory that the
        // pool will use to create Connections.
        // We'll use the DriverManagerConnectionFactory,
        // using the connect string from configuration
        //
        ConnectionFactory connectionFactory = 
            new DriverManagerConnectionFactory(connectURI,username, password);

        //
        // Now we'll create the PoolableConnectionFactory, which wraps
        // the "real" Connections created by the ConnectionFactory with
        // the classes that implement the pooling functionality.
        //
        PoolableConnectionFactory poolableConnectionFactory =
                new PoolableConnectionFactory(
            connectionFactory,connectionPool,null,null,false,true);

        PoolingDataSource dataSource = 
            new PoolingDataSource(connectionPool);

        return dataSource;
    }

    public static void printDriverStats() throws Exception {
        ObjectPool connectionPool = ConnectionManager._pool;
        logger.info("NumActive: " + connectionPool.getNumActive());
        logger.info("NumIdle: " + connectionPool.getNumIdle());
    }

    /**
    *  getNumLockedProcesses - gets the 
    *  number of currently locked processes on the MySQL db
    *
    *  @return Number of locked processes
    */
    public int getNumLockedProcesses()
    {
        int num_locked_connections = 0;
        Connection con = null; 
        PreparedStatement p_stmt = null;  ResultSet rs = null;
        try
        {
            con = ConnectionManager.ds.getConnection();
            p_stmt = con.prepareStatement("SHOW PROCESSLIST");
            rs = p_stmt.executeQuery();
            while(rs.next())
            {
                if(rs.getString("State") != 
                        null && rs.getString("State").equals("Locked"))
                {
                    num_locked_connections++;
                }
            }
        }
        catch(Exception e)
        {
            logger.debug("Failed to get get Locked Connections - Exception: " + e.toString());
        } finally {
            try {
                rs.close();
                p_stmt.close();
                con.close();
            }  catch ( java.sql.SQLException ex) {
                logger.error ( ex.toString() );
            }
        }
        return num_locked_connections;
    }

} public class MySQLManager extends SQLManager {

    private static final Logger logger = Logger.getLogger(MySQLManager.class);
    
    public MySQLManager() {
        super();
        testDatabase();
    }
    
    @Override
    public boolean testDatabase() {
        
        try {
            // testing osa-db, clears now currentusers-table
            Connection conn = SQLManager.getConnection();
            String sql = "TRUNCATE TABLE osa.currentusers";
            Statement st = conn.createStatement();
            st.executeUpdate(sql);
            conn.close();
            
        } catch (Exception e) {
            logger.error("Database check failed."+e);
            return false;
        }
        return true;     
    }
    
    public long getCtxTime(String organization){
    	
    	long contextTime = 0;
    	
        try {     

            Connection conn = SQLManager.getConnection();
            String sql = "SELECT * FROM contexttime WHERE organization='"+organization+"'";
            Statement st = conn.createStatement();
            ResultSet rs = st.executeQuery(sql);                   

            while (rs.next()){
            	contextTime = rs.getLong("contexttime");
            }
            conn.close();
            
        } catch (Exception e) {
            logger.error("Database check failed."+e);
        }
        
        return contextTime;
    	
    }
    
    public void setCtxTime(String organization){

	    long ctxtime = System.currentTimeMillis();
	    	
	    try {
	    		
	        Connection conn = SQLManager.getConnection();
	        String sql = "insert into contexttime (organization, contexttime) values('"+organization+"', "+ctxtime+") on duplicate key update contexttime=values(contexttime)";
	        Statement st = conn.createStatement();
	        st.executeUpdate(sql);
	        conn.close();
	        
	    } catch (Exception e) {
	        logger.error("Database check failed. "+e);
	    }

    }
    
    /**
     * Add user into logged in users -list in database
     */
    public boolean setCurrentuser(String user, String sessionid) {
        
        boolean status = true;
        
        try {
            Connection conn = SQLManager.getConnection();
            String sql = "INSERT INTO currentusers (user, sessionid) VALUES ('"+user+"', '"+sessionid+"')";
            Statement st = conn.createStatement();
            st.executeUpdate(sql);
            conn.close();
            
        } catch (Exception e) {
            status = false;
            logger.error("Insert into database table currentusers failed. "+e);
        }
        
        return status;
    }
    
    /**
     * Remove user from logged in users -list in database
     */
    public String removeCurrentuser(String user) {
        
        String sessionid = "";
        
        try {
            Connection conn = SQLManager.getConnection();
            String sql = "SELECT sessionid FROM currentusers WHERE user='"+user+"';";
            Statement st = conn.createStatement();
            ResultSet result = st.executeQuery(sql);
            while (result.next()) {
                sessionid = result.getString("sessionid");
            }
            
            sql = "DELETE FROM currentusers WHERE user='"+user+"';";
            st = conn.createStatement();
            st.executeUpdate(sql);                   
            conn.close();
            
        } catch (Exception e) {
            logger.error("Database check failed."+e);
        }
        
        return sessionid;
    }
    
    /**
     * Function for generating next id below parent pid
     */
    public String getNextId(String parentPid) {
        long id = 0;
        
        try {
            Connection conn = SQLManager.getConnection();
            String sql = "SELECT highestID FROM idGen WHERE pid='"+parentPid+"'";
            Statement st = conn.createStatement();
            ResultSet result = st.executeQuery(sql);

            while (result.next()) {
                id = result.getLong("highestID");
            }
            conn.close();
            
            id = id+1;
            
            conn = SQLManager.getConnection();
            sql = "UPDATE idGen SET highestID="+id+" WHERE pid='"+parentPid+"'";
            st = conn.createStatement();
            st.executeUpdate(sql);                   
            conn.close();
            
        } catch (Exception e) {
            logger.error("Id generation failed."+e);
        }
        return Long.toString(id);
    }
    
    /**
     * Initialise collection pid
     * @param parentId      pid of parent
     * @param id            initial id
     */
    public void initIdGeneration(String parentPid, int id) {
        try {
            Connection conn = SQLManager.getConnection();
            String sql = "INSERT INTO idGen VALUES('"+parentPid+"', "+Integer.toString(id)+") ON DUPLICATE KEY UPDATE pid=pid";
            Statement st = conn.createStatement();
            st.executeUpdate(sql);
            conn.close();
            
        } catch (Exception e) {
            logger.error("Id initialisation failed."+e);
        } 
    }
        
} public class Fedora3Manager extends RepositoryManager {

    public static final String DATASTREAM         = "datastream";
    public static final String DATASTREAM_CONTENT = "datastream_content";
    public static final String OBJECT             = "object";
    
    String previousTime;
    int pidcount = 0;
    public String schemaPath;
    
    private static final Logger logger = Logger.getLogger(Fedora3Manager.class);
    private FedoraCredentials fedoracredential = null;
    private FedoraClient fedoraClient = null;
    
    /**
     * Constructor
     */
    public Fedora3Manager(String host, String port, String uname, String pword, String path) {
        baseUrl = "http://"+host+":"+port+"/fedora/";
        username = uname;
        password = pword;
        schemaPath = path;
        
        try {
            
            fedoracredential = new FedoraCredentials(baseUrl, username, password);
            fedoraClient = new FedoraClient(fedoracredential);
            
            if (fedoraClient != null) {
                // set this instance of as the default for all requests
                FedoraRequest.setDefaultClient(fedoraClient);
            }
            
        } catch (Exception e) {
            logger.error("Fedora3Manager error: Failed to construct Fedora3Manager, "+e );
        }
    }
    
    /**
     * Create datastream for attachment
     * @param file    attachment
     * @param pid     A persistent, unique identifier for the object
     * @param fObject
     * @param user    user name
     */
    public void createAttachmentDatastream(File file, String pid, FedoraBean fObject, String user) {
        
        if (file != null) {
            // FILE
            try {
                
                String dataStreamName = FedoraBean.DATASTREAM_ATTACHMENT;
                String strIndex = "";
                
                if (fObject != null) {
                    
                    List<String> dsList = listDataStreams(pid);
                    String currentDataStreamId = null;
                                        
                    // get latest attachment datastream
                    for (int i=0; i<dsList.size(); i++) {
                        currentDataStreamId = dsList.get(i).toString();
                        if (currentDataStreamId.equalsIgnoreCase(FedoraBean.DATASTREAM_ATTACHMENT)) {
                            int index = 2;
                            strIndex = Integer.toString(index);
                            dataStreamName = currentDataStreamId + strIndex;
                            
                        } else if (currentDataStreamId.startsWith(FedoraBean.DATASTREAM_ATTACHMENT)) {
                            int number = Integer.parseInt(currentDataStreamId.substring(FedoraBean.DATASTREAM_ATTACHMENT.length(),currentDataStreamId.length()));
                            number++;
                            strIndex = Integer.toString(number);
                            dataStreamName = FedoraBean.DATASTREAM_ATTACHMENT + strIndex;
                        }
                    }
                }

                FedoraClient.addDatastream(pid, dataStreamName)
                            .versionable(false)
                            .dsLabel(pid + FedoraBean.DATASTREAM_ATTACHMENT_LABEL + strIndex + "." + FilenameUtils.getExtension(file.getName())+" ("+user+")")
                            .controlGroup("M")
                            .content(file)
                            .execute(fedoraClient);
         
                createPreviewDatastream(file.getAbsolutePath(), pid, dataStreamName, user);
                
                // add RELS-INT relationship
                String subject = String.format("info:fedora/%s/%s", pid, dataStreamName);
                // rdf-about/predicate
                String rdfAbout = "info:fedora/fedora-system:def/relations-internal#isAttachmentOf";
                // rdf-resource/object
                String rdfResource = "info:fedora/"+pid+"/"+FedoraBean.DATASTREAM_ORIGINAL;
                FedoraClient.addRelationship(subject).predicate(rdfAbout).object(rdfResource).execute(fedoraClient);
                
            } catch (FedoraClientException e) {
                logger.error("Fedora3Manager error: creating datastream for attachment: "+e);
            }
            
            //logger.info("Fedora3Manager: attachment "+file.getAbsolutePath()+" added");
        }
    }
    
    /**
     * createPreview for documents
     * @param pdfPathAndFileName    absolute path 
     * @param pid                   A persistent, unique identifier for the object
     * @throws IOException
     * @throws PdfException
     */
    public void createPreviewDatastream(String filePath, String pid, String datastreamName, String user) {
        
        // Creating a preview is an optional task.
        // Failing to create one is not a critical error.
        try {
            // create thumb file
        	File thumbFile = null;
            String filename = FilenameUtils.getName(filePath);
            filename = FilenameUtils.removeExtension(filename);
            String path = FilenameUtils.getPath(filePath);
            
            thumbFile = new File("/"+path + ThumbBean.THUMB_DSID+"_"+filename+"."+ThumbBean.THUMB_EXTENSION);
	        
	        if (!thumbFile.exists()) {
	            path = path.replace("ingest", "upload");
	            thumbFile = new File("/"+path + ThumbBean.THUMB_DSID+"_"+filename+"."+ThumbBean.THUMB_EXTENSION);
	        }
	        
	        // create label: pid + -PA | -PA1
	        String label = pid + FedoraBean.DATASTREAM_THUMB_LABEL;
	        if (datastreamName.equals(FedoraBean.DATASTREAM_ORIGINAL)) {
	            label += "O";
	        } else if (datastreamName.equals(FedoraBean.DATASTREAM_ATTACHMENT)) {
	            label += "A";
	        } else if (datastreamName.startsWith(FedoraBean.DATASTREAM_ATTACHMENT)) {
	            label += "A" + datastreamName.charAt(datastreamName.length()-1);
	        }
	        label += "." + FilenameUtils.getExtension(thumbFile.getName());
	        
            // add to datastream and remove
            if (thumbFile.canRead()) {
                try {
                    // NOTE: Ensure DC, RELS-EXT and RELS-INT are versionable if using Managed Content
                    FedoraClient.addDatastream(pid, FedoraBean.DATASTREAM_THUMB+"_"+datastreamName)
                                .versionable(false)
                                .dsLabel(label+" ("+user+")")
                                .controlGroup("M")
                                .content(thumbFile)
                                .execute(fedoraClient);
                    
                    // add RELS-INT relationship
                    String subject = String.format("info:fedora/%s/%s", pid, FedoraBean.DATASTREAM_THUMB+"_"+datastreamName);
                    // rdf-about/predicate
                    String rdfAbout = "info:fedora/fedora-system:def/relations-internal#isThumbnailOf";
                    // rdf-resource/object
                    String rdfResource = "info:fedora/"+pid+"/"+datastreamName;
                    FedoraClient.addRelationship(subject).predicate(rdfAbout).object(rdfResource).execute(fedoraClient);
                    
                } catch (FedoraClientException e) {
                    logger.error("Fedora3Manager error: createPreviewDatastream() "+e.getMessage());
                }
                
            	thumbFile.delete();
            	//logger.info("Fedora3Manager: Thumbnail file for "+filePath+" added");
            }
        } catch (Exception ex) {
            logger.error("Fedora3Manager: Failed to create thumbnail preview. " + ex.getMessage());
        }
    }
    
    /**
     * Creates FedoraBean object from metadata elements from GUI
     * @param bIngest       ingest vs. update
     * @param pid           A persistent, unique identifier for the object
     * @param type          object type
     * @param lhmElements   metadata elements in linked hashmap
     * @return
     */
    public FedoraBean createFedoraBean(boolean bIngest, User user, Gui userGui, String pid, String type, LinkedHashMap<String, MetaDataElement> lhmElements) {
      
        FedoraBean fedoraBean = new FedoraBean(pid, type);
        
        if (lhmElements.containsKey(CaptureBean.TITLE)) {
            String title = lhmElements.get(CaptureBean.TITLE).getValue();
            fedoraBean.setObjectData(FedoraBean.OBJ_LABEL, title);
        }
                
        Iterator<Entry<String, MetaDataElement>> iter = lhmElements.entrySet().iterator();
        
        // Go through metadata elements from GUI
        while (iter.hasNext()) {
            
            Map.Entry<String, MetaDataElement> entry = (Map.Entry<String, MetaDataElement>) iter.next();
            MetaDataElement element = entry.getValue();
            
            // TODO: check this
            if (element.getValue() == null && element.getValues().size() > 0) {
                String value = element.getValues().get(0);
                if (value != null) {
                    element.setValue(value);
                }
            }
            
            if (element != null && element.getName() != null 
                && (element.getValue() != null 
                    || !element.getNestedElements().isEmpty()) 
                    || element.getGenerated() == true) {
                
                String name = element.getName();
                MetaDataType eType = element.getMetaDataType();
                
                if (element.getGenerated()) {
                    // value set to be generated automatically, if empty
                    if (name.equalsIgnoreCase(CaptureBean.IDENTIFIER)) {
                        String parentPid = lhmElements.get(CaptureBean.RELATION_IsPartOf).getValue();
                        
                        if (bIngest && type.endsWith(Fedora3Manager.COLLECTION)) {
                            Osa.dbManager.get("sql").initIdGeneration(pid, 0);
                        }

                        if (element.getValue() == null) {
                            String idValue = "";
                            // get id of parent object
                            String parentId = Osa.searchManager.getObjectValue(parentPid, SolrManager.SOLRFIELD_Id, user);
                            if (parentId != "") {
                                idValue = parentId+".";
                            }
                            // get next available id below parent object
                            String nextId = Osa.dbManager.get("sql").getNextId(parentPid);
                            idValue = idValue.concat(nextId);
                            element.setValue(idValue);
                            
                        }
                    } else {
                        continue;
                    }
                }
                
                // Get datastream(s) to metadata element from configuration
                List<String> datastreams = userGui.getDataStreamMappings(name);
                
                // One metadata may belong to several datastreams
                for (int j=0; j<datastreams.size(); j++) {
                    String datastream = datastreams.get(j).toString();
                    
                    // Get visible name for attribute in the current datastream from configuration (osa-system:contentmodel-*)
                    String visibleName = userGui.getVisibleName(name, datastream);
                    element.setVisibleName(visibleName);
                    // set metadata element to the datastream of fedoraBean
                    fedoraBean.getDataStream(datastream).setMetaDataElement(element);
                    
                    if (eType.equals(MetaDataType.relation)) {
                        // update relation to its datastream
                        fedoraBean.getDataStream(FedoraBean.DATASTREAM_RELS_EXT).setMetaDataElement(element);
                    }
                }
            }
        }
        
        // set ancestor metadata to management-datastream
        CaptureBean captureDatastream = (CaptureBean)fedoraBean.getDataStream(FedoraBean.DATASTREAM_CAPTURE);
        String parent = captureDatastream.getParent();
        ManagementBean managementDatastream = (ManagementBean)fedoraBean.getDataStream(FedoraBean.DATASTREAM_MANAGEMENT);
        
        // set ancestor
        if (parent == null || parent.contains(":root")) {
            // object is ancestor itself
            managementDatastream.setAncestor(pid);
            
        } else {
            String newAncestor = this.getAncestor(parent);
            managementDatastream.setAncestor(newAncestor); 
        }
        
        // set path metadata to management-datastream
        String path = this.getPath(parent);
        if (path.endsWith("/")) {
            path = path + pid;
        } else {
            path = path+"/"+pid;
        }
        managementDatastream.setPath(path);
        
        return fedoraBean;
    }

    /**
     * Create root object and static collections for context objects (actions, agents, events, places)
     * @param root      organization name
     * @param userGui   holds information of datastream mappings
     */
    public void createRootObject(String rootName, Gui userGui, User user) {
        // f.ex. mamk:root
        rootName = rootName.toUpperCase();
        String rootPid = rootName+":root";
        String contextsPid = rootName+RepositoryManager.PID_COLLECTION_CONTEXTS;
        String agentsPid = rootName+RepositoryManager.PID_COLLECTION_AGENTS;
        String actionsPid = rootName+RepositoryManager.PID_COLLECTION_ACTIONS;
        String tol2008ActionsPid = rootName+RepositoryManager.PID_COLLECTION_TOL2008ACTIONS;
        String lyActionsPid = rootName+RepositoryManager.PID_COLLECTION_LYACTIONS;
        String eventsPid = rootName+RepositoryManager.PID_COLLECTION_EVENTS;
        String placesPid = rootName+RepositoryManager.PID_COLLECTION_PLACES;
        IngestResponse response = null;
        LinkedHashMap<String, MetaDataElement> lhmElements = null;
        MetaDataElement mdElement = null;
        
        // Initialize id for basecollection in idGen-table in MariaDB
        Osa.dbManager.get("sql").initIdGeneration(rootPid, userGui.getInitialBasecollectionId());
        
        try {
            
            if (this.findObject(rootPid) == null) {
                response = new Ingest(rootPid).label(rootName).execute(fedoraClient);
                logger.info("Fedora3Manager: root object "+rootPid+" created.");
            }

            // get datastream mappings
            userGui.getGUIElements(RepositoryManager.BASECOLLECTION);
            
            // Collection for Contexts-object
            if (this.findObject(contextsPid) == null) {
                lhmElements = new LinkedHashMap<String, MetaDataElement>();
                // pid
                mdElement = new MetaDataElement("pid", "pid", contextsPid, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                lhmElements.put("pid", mdElement);
                // type
                mdElement = new MetaDataElement(CaptureBean.TYPE, CaptureBean.TYPE, RepositoryManager.BASECOLLECTION, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                lhmElements.put(CaptureBean.TYPE, mdElement);
                // title
                mdElement = new MetaDataElement(CaptureBean.TITLE, CaptureBean.TITLE, "Kontekstiobjektit", MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                lhmElements.put(CaptureBean.TITLE, mdElement);
                // accessRights
                mdElement = new MetaDataElement(CaptureBean.PUBLICITY_LEVEL, CaptureBean.PUBLICITY_LEVEL, AccessRight.PUBLICITYLEVEL_Public, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                lhmElements.put(CaptureBean.PUBLICITY_LEVEL, mdElement);
                // relation
                mdElement = new MetaDataElement(CaptureBean.RELATION_IsPartOf, CaptureBean.RELATION_IsPartOf, rootPid, MetaDataType.relation, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                lhmElements.put(CaptureBean.RELATION_IsPartOf, mdElement);
                this.ingest(userGui, user, rootName, null, null, RepositoryManager.BASECOLLECTION, contextsPid, lhmElements, null);
                logger.info("Fedora3Manager: object "+contextsPid+" created.");
            }
            
            if (this.findObject(contextsPid) != null) {
                // Collection for Agent-objects
                if (this.findObject(agentsPid) == null) {
                    lhmElements = new LinkedHashMap<String, MetaDataElement>();
                    // pid
                    mdElement = new MetaDataElement("pid", "pid", agentsPid, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put("pid", mdElement);
                    // type
                    mdElement = new MetaDataElement(CaptureBean.TYPE, CaptureBean.TYPE, RepositoryManager.GROUPCOLLECTION, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TYPE, mdElement);
                    // title
                    mdElement = new MetaDataElement(CaptureBean.TITLE, CaptureBean.TITLE, "Toimijat", MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TITLE, mdElement);
                    // accessRights
                    mdElement = new MetaDataElement(CaptureBean.PUBLICITY_LEVEL, CaptureBean.PUBLICITY_LEVEL, AccessRight.PUBLICITYLEVEL_Public, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.PUBLICITY_LEVEL, mdElement);
                    // relation          
                    mdElement = new MetaDataElement(CaptureBean.RELATION_IsPartOf, CaptureBean.RELATION_IsPartOf, contextsPid, MetaDataType.relation, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.RELATION_IsPartOf, mdElement);
                    this.ingest(userGui, user, rootName, null, null, RepositoryManager.GROUPCOLLECTION, agentsPid, lhmElements, null);
                    logger.info("Fedora3Manager: object "+agentsPid+" created.");
                }
                
                // Collection for Action-objects
                if (this.findObject(actionsPid) == null) {
                    lhmElements = new LinkedHashMap<String, MetaDataElement>();
                    // pid
                    mdElement = new MetaDataElement("pid", "pid", actionsPid, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put("pid", mdElement);
                    // type
                    mdElement = new MetaDataElement(CaptureBean.TYPE, CaptureBean.TYPE, RepositoryManager.GROUPCOLLECTION, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TYPE, mdElement);
                    // title
                    mdElement = new MetaDataElement(CaptureBean.TITLE, CaptureBean.TITLE, "Toiminnat", MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TITLE, mdElement);
                    // accessRights
                    mdElement = new MetaDataElement(CaptureBean.PUBLICITY_LEVEL, CaptureBean.PUBLICITY_LEVEL, AccessRight.PUBLICITYLEVEL_Public, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.PUBLICITY_LEVEL, mdElement);
                    // relation           
                    mdElement = new MetaDataElement(CaptureBean.RELATION_IsPartOf, CaptureBean.RELATION_IsPartOf, contextsPid, MetaDataType.relation, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.RELATION_IsPartOf, mdElement);
                    this.ingest(userGui, user, rootName, null, null, RepositoryManager.GROUPCOLLECTION, actionsPid, lhmElements, null);
                    logger.info("Fedora3Manager: object "+actionsPid+" created.");
                }
                
                // Collection for TOL 2008 actions (used for basecollection metadata)
                if (this.findObject(tol2008ActionsPid) == null) {
                    lhmElements = new LinkedHashMap<String, MetaDataElement>();
                    // pid
                    mdElement = new MetaDataElement("pid", "pid", tol2008ActionsPid, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put("pid", mdElement);
                    // type
                    mdElement = new MetaDataElement(CaptureBean.TYPE, CaptureBean.TYPE, RepositoryManager.GROUPCOLLECTION, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TYPE, mdElement);
                    // title
                    mdElement = new MetaDataElement(CaptureBean.TITLE, CaptureBean.TITLE, "TOL 2008", MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TITLE, mdElement);
                    // accessRights
                    mdElement = new MetaDataElement(CaptureBean.PUBLICITY_LEVEL, CaptureBean.PUBLICITY_LEVEL, AccessRight.PUBLICITYLEVEL_Public, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.PUBLICITY_LEVEL, mdElement);
                    // relation           
                    mdElement = new MetaDataElement(CaptureBean.RELATION_IsPartOf, CaptureBean.RELATION_IsPartOf, actionsPid, MetaDataType.relation, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.RELATION_IsPartOf, mdElement);
                    this.ingest(userGui, user, rootName, null, null, RepositoryManager.GROUPCOLLECTION, tol2008ActionsPid, lhmElements, null);
                    logger.info("Fedora3Manager: object "+tol2008ActionsPid+" created.");
                }
                
                // Ingest TOL 2008 objects for actions
                if (this.findObject(tol2008ActionsPid) != null && this.findObject(rootName+":F-tol2008actionA") == null) {
                    String path = schemaPath+"wip/actionobjectsTOL";
                    File folder = new File(path);
                    File[] listOfFiles = folder.listFiles();
                    for (int i=0; i<listOfFiles.length; i++) {
                        File file = listOfFiles[i];
                        if (file.isFile() && file.getName().endsWith(".xml")) {
                            String fileLocation = path+"/"+file.getName();
                            this.ingestFile(rootName, fileLocation);
                        } 
                    }
                }
                
                // Collection for Liikearkistoyhdistys actions (used for group/unitcollection metadata)
                if (this.findObject(lyActionsPid) == null) {
                    lhmElements = new LinkedHashMap<String, MetaDataElement>();
                    // pid
                    mdElement = new MetaDataElement("pid", "pid", lyActionsPid, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put("pid", mdElement);
                    // type
                    mdElement = new MetaDataElement(CaptureBean.TYPE, CaptureBean.TYPE, RepositoryManager.GROUPCOLLECTION, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TYPE, mdElement);
                    // title
                    mdElement = new MetaDataElement(CaptureBean.TITLE, CaptureBean.TITLE, "Liikearkistoyhdistys", MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TITLE, mdElement);
                    // accessRights
                    mdElement = new MetaDataElement(CaptureBean.PUBLICITY_LEVEL, CaptureBean.PUBLICITY_LEVEL, AccessRight.PUBLICITYLEVEL_Public, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.PUBLICITY_LEVEL, mdElement);
                    // relation           
                    mdElement = new MetaDataElement(CaptureBean.RELATION_IsPartOf, CaptureBean.RELATION_IsPartOf, actionsPid, MetaDataType.relation, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.RELATION_IsPartOf, mdElement);
                    this.ingest(userGui, user, rootName, null, null, RepositoryManager.GROUPCOLLECTION, lyActionsPid, lhmElements, null);
                    logger.info("Fedora3Manager: object "+lyActionsPid+" created.");
                }
                    
                // Ingest Liikearkistoyhdistys objects for actions
                if (this.findObject(lyActionsPid) != null && this.findObject(rootName+":F-lyaction00") == null) {
                    String path = schemaPath+"wip/actionobjectsLY";
                    File folder = new File(path);
                    File[] listOfFiles = folder.listFiles();

                    for (int i=0; i<listOfFiles.length; i++) {
                        File file = listOfFiles[i];
                        if (file.isFile() && file.getName().endsWith(".xml")) {
                            String fileLocation = path+"/"+file.getName();
                            this.ingestFile(rootName, fileLocation);
                        } 
                    }                   
                }
                
                // Collection for Event-objects
                if (this.findObject(eventsPid) == null) {
                    lhmElements = new LinkedHashMap<String, MetaDataElement>();
                    // pid
                    mdElement = new MetaDataElement("pid", "pid", eventsPid, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put("pid", mdElement);
                    // type
                    mdElement = new MetaDataElement(CaptureBean.TYPE, CaptureBean.TYPE, RepositoryManager.GROUPCOLLECTION, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TYPE, mdElement);
                    // title
                    mdElement = new MetaDataElement(CaptureBean.TITLE, CaptureBean.TITLE, "Tapahtumat", MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TITLE, mdElement);
                    // accessRights
                    mdElement = new MetaDataElement(CaptureBean.PUBLICITY_LEVEL, CaptureBean.PUBLICITY_LEVEL, AccessRight.PUBLICITYLEVEL_Public, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.PUBLICITY_LEVEL, mdElement);
                    // relation
                    mdElement = new MetaDataElement(CaptureBean.RELATION_IsPartOf, CaptureBean.RELATION_IsPartOf, contextsPid, MetaDataType.relation, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.RELATION_IsPartOf, mdElement);
                    this.ingest(userGui, user, rootName, null, null, RepositoryManager.GROUPCOLLECTION, eventsPid, lhmElements, null);
                    logger.info("Fedora3Manager: object "+eventsPid+" created.");
                }
                
                // Collection for Place-objects
                if (this.findObject(placesPid) == null) {
                    lhmElements = new LinkedHashMap<String, MetaDataElement>();
                    // pid
                    mdElement = new MetaDataElement("pid", "pid", placesPid, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put("pid", mdElement);
                    // type
                    mdElement = new MetaDataElement(CaptureBean.TYPE, CaptureBean.TYPE, RepositoryManager.GROUPCOLLECTION, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TYPE, mdElement);
                    // title
                    mdElement = new MetaDataElement(CaptureBean.TITLE, CaptureBean.TITLE, "Paikat", MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.TITLE, mdElement);
                    // accessRights
                    mdElement = new MetaDataElement(CaptureBean.PUBLICITY_LEVEL, CaptureBean.PUBLICITY_LEVEL, AccessRight.PUBLICITYLEVEL_Public, MetaDataType.string, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.PUBLICITY_LEVEL, mdElement);
                    // relation           
                    mdElement = new MetaDataElement(CaptureBean.RELATION_IsPartOf, CaptureBean.RELATION_IsPartOf, contextsPid, MetaDataType.relation, FormElement.MULTIVALUE_NONE, null, null, CaptureBean.CAPTURE_DSID);
                    lhmElements.put(CaptureBean.RELATION_IsPartOf, mdElement);
                    this.ingest(userGui, user, rootName, null, null, RepositoryManager.GROUPCOLLECTION, placesPid, lhmElements, null);
                    logger.info("Fedora3Manager: object "+placesPid+" created.");
                }
            }
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager error: "+e);
        } catch (SAXException e) {
            logger.error("Fedora3Manager error: "+e);
        }
        
        if (response != null && response.getStatus() == 200) {
            logger.info("Fedora3Manager: rootObject '"+rootPid+"' created.");
        }
    }
    
    public String getAuditTrail(String pid) {
    	String auditTrail = "";
    	int tabs = 0;
    	
    	try {
			FedoraResponse resp = FedoraClient.getObjectXML(pid).execute(fedoraClient);
			String response = resp.getEntity(String.class);
			String[] lines = response.split("\n");
			
			for (int i=0; i<lines.length; i++) {
				String line = lines[i];
				if (line.startsWith("<audit:auditTrail")) {
					
					while (!line.contains("</audit:auditTrail>")) {
						
						if (line.startsWith("</")) {
							tabs--;
						}
						
						for (int j=0; j<tabs; j++) {
							auditTrail += "\t";
						}

						if (line.startsWith("<") && !line.contains("</") && !line.contains("/>")) {
							tabs++;
						}
						
						auditTrail += line+"\n";
						i++;
						line = lines[i];
						
					}
					auditTrail += line;
					i = lines.length;
				}
			}			
		} catch (FedoraClientException e) {
		    logger.error("Fedora3Manager:getAuditTrail error: "+e);
		}
    	
    	return auditTrail;
    }
    
    /**
     * Delete object from Fedora repository
     * @param pid       PID
     * @return
     */
    public void deleteObject(String pid) {
        try {
            // Purge object
            FedoraClient.purgeObject(pid).execute(fedoraClient);
            logger.info("Fedora3Manager: object "+pid+" removed.");
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager error: "+e);
        }
    }
    
    /**
     * Dispose object from Fedora repository
     * @param pid         PID
     * @param plan        destruction plan for disposing
     * @return
     */
    public void createResidualObject(String pid, DestructionPlan plan) {

        List<String> datastreams = plan.getResidualDatastreams();
        List<String> objectDatastreams = this.listDataStreams(pid);
        
        // remove datastreams, that are not in DestructionPlan
        for (String objectDatastream : objectDatastreams) {
            if (!datastreams.contains(objectDatastream)) {
                this.deleteDataStream(pid, objectDatastream);
            }
        }
        
        // set object state
        this.setObjectState(pid, State.D);
        logger.info("Fedora3Manager: object "+pid+" removed and residual object created.");
    }
    
    /**
     * Delete root object 
     * @param rootName      organization name
     */
    public void deleteRootObject(String rootName) {
        try {
            String pid = rootName+":root";
            FedoraClient.purgeObject(pid).execute(fedoraClient);
            logger.info("Fedora3Manager: root object "+pid+" removed.");
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager error: "+e);
        }
    }
    
    public void deleteDataStream(String pid, String dsid) {
    	
    	try {
			FedoraClient.purgeDatastream(pid, dsid).execute(fedoraClient);
						
		} catch (FedoraClientException e) {
			logger.error("Fedora3Manager error: Cannot remove datastream: "+dsid+" from "+pid);
			e.printStackTrace();
		}
    }
    
    /**
     * Delete all objects
     * @param orgContext
     */
    public void deleteAllobjects(String orgContext) {
    	
    	List<String> pids = findAllObjects(orgContext);
    	
    	while (pids.size() > 1) {
			for (int i=0; i<pids.size(); i++) {
				if (!pids.get(i).equals(orgContext+":root")) {
		            try {
		                FedoraClient.purgeObject(pids.get(i)).execute(fedoraClient);
					} catch (FedoraClientException e) {
						logger.error("Fedora3Manager error: deleteAllobjects() "+e);
					}
		            logger.info("Fedora3Manager: object "+pids.get(i)+" removed.");
				}
			}
			pids = findAllObjects(orgContext);
    	}
    }
    
    /**
     * Deletes all objects below TOL 2008 Actions
     * @param orgContext        organization name
     * @param type              pid type of action object (":F-tol2008action*"/":F-lyaction*")
     */
    public void deleteActionObjects(String orgContext, String type) {

        List<String> pids = null;
        String queryString = "pid~"+orgContext+type;
        FindObjectsResponse response = null;
        
        try {
            response = FedoraClient.findObjects().pid().maxResults(200).query(queryString).execute(fedoraClient);
            pids = response.getPids();
            
            while (pids.size() > 0) {
                logger.info("Fedora3Manager: Removing "+pids.size()+" objects.");
                for (int i=0; i<pids.size(); i++) {
                    FedoraClient.purgeObject(pids.get(i)).execute(fedoraClient);
                    logger.info("Fedora3Manager: object "+pids.get(i)+" removed.");
                }
                response = FedoraClient.findObjects().pid().maxResults(200).query(queryString).execute(fedoraClient);
                pids = response.getPids();
                break;
            }
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:deleteActionObjects error: "+e);
        }
    }
       
    /**
     * Function to search all objects by organization
     * @param orgContext
     * @return
     */
    public List<String> findAllObjects(String orgContext) {
    	
    	List<String> pids = null;
    	FindObjectsResponse response = null;

        try {
        	response = FedoraClient.findObjects().pid().query("pid~"+orgContext+"*").execute(fedoraClient);
        	pids = response.getPids();
        	
        } catch (FedoraClientException e) {
        	logger.error("Fedora3Manager:findAllObjects error: "+e);
        }
    		
        return pids;
    }
    
    /**
     * Search object from Fedora repository
     * @param pid       A persistent, unique identifier for the object
     */
    public FedoraBean findObject(String pid) {
        
        //logger.info("Fedora3Manager: searching object "+pid+" ");
        boolean bFound = false;
        FedoraBean fObject = new FedoraBean(pid);
        
        // get object related common data
        bFound = getData(Fedora3Manager.OBJECT, pid, "", fObject);
        
        if (!bFound) {
            // object not found
            return null;
        }
        
        List<String> dsList = listDataStreams(pid);
        String currentDataStreamId = null;

        // handle all datastreams for pid
        for (int i=0; i<dsList.size(); i++) {
            currentDataStreamId = dsList.get(i).toString();
            // read datastream profile data
            getData(Fedora3Manager.DATASTREAM, pid, currentDataStreamId, fObject);
            // read datastream content
            getData(Fedora3Manager.DATASTREAM_CONTENT, pid, currentDataStreamId, fObject);
        }
        
        // add object to vector
        vectFedoraObjects.add(fObject);
        return fObject;
    }
    
    /**
     * 
     * @param pid
     * @return
     */
    public String getChildren(String pid) {
    	
        String query = String.format("select $child from <#ri> where $child <fedora-rels-ext:isPartOf> <info:fedora/"+pid+">");        	
        String collectionObjs = "";
        
        try {
            RiSearchResponse response = null;
            response = FedoraClient.riSearch(query).lang("itql").format("json").execute(fedoraClient);
            String result = response.getEntity(String.class);
            collectionObjs = result;

        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager error: "+e);
        }
        return collectionObjs;
    }
    
    /**
     * Gets all sub children of an object
     * @param pid	Pid of an parent object
     * @return Arraylist of children pids
     */
	public ArrayList<HashMap<String, String>> getAllChildren(String pid) {
	    
		String initChildren = this.getChildren(pid);
	    HashMap<String, ArrayList<HashMap<String, String>>> jsonElement = new flexjson.JSONDeserializer<HashMap<String, ArrayList<HashMap<String, String>>>>().deserialize(initChildren); 
	    ArrayList<HashMap<String, String>> resultlist = jsonElement.get("results");
	
		for (int i=0; i<resultlist.size(); i++) {
			String child = cleanEntry(resultlist.get(i).get("child"));
			HashMap<String, ArrayList<HashMap<String, String>>> jsonChildren = new flexjson.JSONDeserializer<HashMap<String, ArrayList<HashMap<String, String>>>>().deserialize(this.getChildren(child)); 
	        ArrayList<HashMap<String, String>> resultlist2 = jsonChildren.get("results");
	        
	        if (resultlist2.size() > 0) {
	        	resultlist.addAll(resultlist2);
	        }
		}
		return resultlist;
	}
	
    /**
     * Get root and all descendents
     * @param organization      user organization name
     */
    public String getCollectionObjects(String organization) {
        String query = String.format("select $parent $child from <#ri> " +
        		"where walk ($child <fedora-rels-ext:isPartOf> <info:fedora/"+organization+":root> and " +
        		"$child <fedora-rels-ext:isPartOf> $parent)");
        	
        String collectionObjs = "";
        
        try {
            RiSearchResponse response = null;
            response = FedoraClient.riSearch(query).lang("itql").format("json").execute(fedoraClient); 
            String result = response.getEntity(String.class);
            collectionObjs = result;
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager error: "+e);
        }
        
        return collectionObjs;
    }
    
    /**
     * Get schema from Fedora
     * schema is in datastream of object's contentmodel
     * @param type      object type (document|image|...)
     * @return          schema for type
     */
    public Document getSchema(String type, String organization) {

        Document doc = null;
        InputStream inputstream = null;
        FedoraResponse response = null;
        String pid = RepositoryManager.CONTENTMODEL + type;
        String dsID = "OSA-SCHEMA";
        
        try {
            // organization specific schema
            String dsID_org = dsID+"-"+organization;
            List<String> dsList = listDataStreams(pid);
            
            if (dsList.contains(dsID_org)) {
                // organization specific schema
                response = FedoraClient.getDatastreamDissemination(pid, dsID_org).execute(fedoraClient);
                
            } else {
                // default schema
                response = FedoraClient.getDatastreamDissemination(pid, dsID).execute(fedoraClient);
            }
            
            inputstream = response.getEntityInputStream();
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.parse(inputstream);
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:getSchema error: "+e);
        } catch (Exception e) {
            logger.error("Fedora3Manager:getSchema error: "+e);
        }
        
        return doc;
    }
    
    /**
     * Function to get object and datastream content
     * @param pid       A persistent, unique identifier for the object
     * @param dsID      An identifier for the datastream that is unique within the digital object
     */
    private boolean getData(String dataType, String pid, String dsID, FedoraBean fObject) {
        boolean retValue = false;
        InputStream inputstream = null;
        
        if (pid == null) {
            return retValue;
        }
        
        try {
            
            if (dataType.equalsIgnoreCase(Fedora3Manager.OBJECT)) {
                FedoraResponse response = FedoraClient.getObjectProfile(pid).execute(fedoraClient);
                inputstream = response.getEntityInputStream();
                
            } else if (dataType.equalsIgnoreCase(Fedora3Manager.DATASTREAM)) {
                GetDatastreamResponse gdr = FedoraClient.getDatastream(pid, dsID).execute(fedoraClient);
                inputstream = gdr.getEntityInputStream();
                
            } else if (dataType.equalsIgnoreCase(Fedora3Manager.DATASTREAM_CONTENT)) {
                
                if (dsID.equalsIgnoreCase(FedoraBean.DATASTREAM_ORIGINAL)
                    || dsID.startsWith(FedoraBean.DATASTREAM_THUMB)
                    || dsID.startsWith(FedoraBean.DATASTREAM_ATTACHMENT)
                    ) {
                    
                    String value = baseUrl+"objects/"+pid+"/datastreams/"+dsID+"/content";
                    InputStream is = null;
                    String credentials = username + ":" + password;

                    try {
                        byte[] encoded = new Base64().encode(credentials.getBytes("UTF-8"));
                        String encoding = new String(encoded);
                        URL url = new URL(value);
                        URLConnection conn = url.openConnection();
                        conn.setDoInput(true);
                        conn.setRequestProperty("Authorization", String.format("Basic %s", encoding));
                        is = conn.getInputStream();
                    
                        if (dsID.startsWith(FedoraBean.DATASTREAM_ATTACHMENT) || dsID.equals(FedoraBean.DATASTREAM_ATTACHMENT)) {
                            fObject.getDataStream(FedoraBean.DATASTREAM_ATTACHMENT).setMetaDataElement(dsID, dsID, value, MetaDataType.string, null, null, is);
                        } else if (dsID.startsWith(FedoraBean.DATASTREAM_THUMB) || dsID.equals(FedoraBean.DATASTREAM_THUMB)) {
                            fObject.getDataStream(FedoraBean.DATASTREAM_THUMB).setMetaDataElement(dsID, dsID, value, MetaDataType.string, null, null, is);                            
                        } else {
                            fObject.getDataStream(FedoraBean.DATASTREAM_ORIGINAL).setMetaDataElement(dsID, dsID, value, MetaDataType.string, null, null, is);
                        }
                    
                    } catch (UnsupportedEncodingException e) {
                        logger.error("Fedora3Manager:getData error: "+e);
                    } catch (MalformedURLException e) {
                        logger.error("Fedora3Manager:getData error: "+e);
                    } catch (IOException e) {
                        logger.error("Fedora3Manager:getData error: "+e);
                    }
                    return true;
                    
                } else if (dsID.equalsIgnoreCase(FedoraBean.DATASTREAM_RELS_EXT)) {
                    // Handle RELS_EXT datastream
                    FedoraResponse response = FedoraClient.getRelationships(pid).execute(fedoraClient);
                    Model model = ModelFactory.createDefaultModel();
                    model.read(response.getEntityInputStream(), null, FileUtils.langXML);
                    
                    StmtIterator it = model.listStatements();
                    Statement s;
                    while (it.hasNext()) {
                        
                        s = (Statement) it.next();
                        // name of relation
                        String name = s.getPredicate().toString();
                        String[] nameParts = name.split("/");
                        name = nameParts[nameParts.length-1];
                        name = name.substring(name.indexOf("#")+1);
                        // relation to object
                        String value = s.getObject().toString();
                        String[] valueParts = value.split("/");
                        value = valueParts[valueParts.length-1];
                        
                        // store data to fedora object
                        fObject.getDataStream(dsID).setMetaDataElement(name, name, value, MetaDataType.relation);
                    }
                    return true;
                } else {
                    // Handle other datastreams
                    FedoraResponse response = FedoraClient.getDatastreamDissemination(pid, dsID).execute(fedoraClient);
                    inputstream = response.getEntityInputStream();
                }
            }
                            
            XMLStreamReader reader = null;
            String name = "";
            String value = "";
            String parent = "";

            try {
                reader = XMLInputFactory.newInstance().createXMLStreamReader(inputstream);    
                while (reader.hasNext()) {
                    value = "";
                    int eventType = reader.next();
                    
                    if (reader.isStartElement()) {
                        // node name
                        name = reader.getLocalName();                      
                        if (parent.equals("") && !reader.getLocalName().equalsIgnoreCase(FedoraBean.DATASTREAM_CAPTURE)) {
                            // set parent node name
                            parent = reader.getLocalName();
                        }
                        
                    } else if (reader.isEndElement()) { 
                        if (reader.getLocalName().equals(parent)) {
                            // clear parent node name
                            parent = "";
                        }
                    }
                    
                    if (eventType == XMLStreamConstants.CHARACTERS) {
                        if (!reader.getText().trim().isEmpty()) {
                            // node value
                            value = reader.getText().trim();
                        }
                    }
                    
                    if (!value.isEmpty()) {
                        
                        if (dataType.equalsIgnoreCase(Fedora3Manager.OBJECT)) {
                            fObject.setObjectData(name, value);
                            
                        } else if (dataType.equalsIgnoreCase(Fedora3Manager.DATASTREAM)) {
                            if (dsID.startsWith(FedoraBean.DATASTREAM_ATTACHMENT) || dsID.equals(FedoraBean.DATASTREAM_ATTACHMENT)) {
                                // set propertydata to several attachments
                                AttachmentBean currentAttachment = (AttachmentBean)fObject.getDataStream(FedoraBean.DATASTREAM_ATTACHMENT);
                                currentAttachment.setElementProperty(dsID, name, value);
                                
                            } else if (dsID.startsWith(FedoraBean.DATASTREAM_THUMB) || dsID.equals(FedoraBean.DATASTREAM_THUMB)) {
                                // set propertydata to several thumbs
                                ThumbBean currentThumb = (ThumbBean)fObject.getDataStream(FedoraBean.DATASTREAM_THUMB);
                                currentThumb.setElementProperty(dsID, name, value);
                                
                            } else if (fObject.getDataStream(dsID) != null) {
                                // store data to fedora object
                                fObject.getDataStream(dsID).setProperty(name, value);
                            }
                            
                        } else if (dataType.equalsIgnoreCase(Fedora3Manager.DATASTREAM_CONTENT)) {
                            
                            if (fObject.getDataStream(dsID) != null) {
                                
                                if (dsID.equalsIgnoreCase(FedoraBean.DATASTREAM_CAPTURE)
                                    && !parent.equals(name)) {
                                    // if parent node exists, add values to nested elements vector
                                    fObject.getDataStream(dsID).setNestedMetaDataElement(parent, name, name, value, MetaDataType.string);
                                } else {
                                    // store data to fedora object                                
                                    fObject.getDataStream(dsID).setMetaDataElement(name, name, value, MetaDataType.string);
                                }
                            }
                        }
                    }
                }
                    
                // no more elements...
                reader.close();
                retValue = true;
                
            } catch (XMLStreamException e) {
                logger.error("Fedora3Manager:getDataFromDataStream XMLError: "+e);
            }

        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:getDataStream error: "+e);
        }
        
        return retValue;
    }
    
    /**
     * Generate PID (organization + mimetype + timestamp)
     * @param organizationName      organization of the user
     * @param mime                  object type
     * @return                      pid
     */
    public String getNextPID(String organizationName, String mime) {
        String PID = organizationName.toUpperCase() + ":";
        mime = mime.toLowerCase();
        boolean bFound = false;
        
        // get abbreviation for mime from HashMap
        for (Entry<String, String> entry : DATATYPES_MAP.entrySet()) {
            if (entry.getValue().equals(mime)) {
                PID = PID + entry.getKey();
                bFound = true;
                break;
            }
        }

        // if not found, check if collection
        if (!bFound && mime.endsWith(RepositoryManager.COLLECTION)) {
            PID = PID + RepositoryManager.DATATYPES_MAP_REVERSE.get(RepositoryManager.COLLECTION);
            bFound = true;
        }
        
        // if not found, use default value
        if (!bFound) {
            for (Entry<String, String> entry : DATATYPES_MAP.entrySet()) {
                if (entry.getValue().equals(RepositoryManager.DEFAULT_TYPE)) {
                    PID = PID + entry.getKey();
                    break;
                }
            }
        }
        
        String tstamp = String.valueOf(System.currentTimeMillis());
        
        if (tstamp.equals(this.previousTime)) {
            this.pidcount++;
            PID = PID +"-"+tstamp+"-"+pidcount;
        } else {
            this.previousTime = tstamp;
            this.pidcount = 0;
            PID = PID +"-"+tstamp+"-"+pidcount;
        }
        // add timestamp to PID
        
        return PID;
    }
    
    /**
     * Get history of the object
     * @param pid       The PID of the object.
     */
    public List<MetaDataElement> getObjectHistory(String pid) {
        FedoraObjectHistory history = null;
        List<MetaDataElement> listHistory = new ArrayList<MetaDataElement>();
        // get existing datastreams for pid
        List<String> existingDatastreams = listDataStreams(pid);
        int size = existingDatastreams.size();
        
        try {
            GetObjectHistoryResponse response = FedoraClient.getObjectHistory(pid).execute(fedoraClient);
            history = response.getObjectHistory();
            
            // Get all datastream profiles
            List<GetDatastreamHistoryResponse> listDsResponses = new ArrayList<GetDatastreamHistoryResponse>();
            GetDatastreamHistoryResponse dsResponse = null;

            for (int i=0; i<size; i++) {
                String currentDatastream = existingDatastreams.get(i);
                dsResponse = FedoraClient.getDatastreamHistory(pid, currentDatastream).execute(fedoraClient);
                listDsResponses.add(dsResponse);
            }
            
            // Get change dates
            List<String> changeDates = history.getObjectChangeDate();
            Iterator<String> iterChangeDates = changeDates.iterator();
            
            while (iterChangeDates.hasNext()) {
                String changeDate = (String) iterChangeDates.next();
                Boolean bFound = false;
                
                for (int i=0; i<listDsResponses.size(); i++) {
                    dsResponse = listDsResponses.get(i);
                    List<DatastreamProfile> listDsProfile = dsResponse.getDatastreamProfile().getDatastreamProfile();
                    Iterator<DatastreamProfile> iterDsProfile = listDsProfile.iterator();
                                        
                    if (dsResponse != null) {
                        
                        if (iterDsProfile != null) {
                            
                            while (iterDsProfile.hasNext()) {
                                DatastreamProfile profile = (DatastreamProfile) iterDsProfile.next();

                                // check if date of version history matches to datastream version
                                if (profile.getDsCreateDate().toString().matches(changeDate)) {
                                    // Link to current datastream version, read baseUrl in IngestAction
                                    // http://127.0.0.1:8080/fedora/objects/ORG:T-1234567890/datastreams/DC/content?asOfDateTime=2014-01-01T10:00:00.000Z
                                    String link = "objects/"+pid+"/datastreams/";
                                    link = link + profile.getDsID() + "/";
                                    link = link + "content?asOfDateTime=";
                                    link = link + changeDate;
                                    
                                    // date and label (modifier)
                                    String value = changeDate+"<br>"+profile.getDsLabel();
                                    MetaDataElement dateElement = new MetaDataElement("date", null, value, MetaDataType.date);
                                    listHistory.add(dateElement);
                                    
                                    // datastream version info and link
                                    MetaDataElement dsVersionElement = new MetaDataElement(profile.getDsVersionID(), null, link, MetaDataType.link);
                                    listHistory.add(dsVersionElement);
                                    
                                    bFound = true;
                                    
                                    break;
                                }
                            }
                        }
                        if (bFound == true) break;
                    }
                }
            }
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:getObjectHistory error: "+e);
        }
        return listHistory;
    }
    
    /**
     * Ingest/update to the Fedora repository.
     * @param userGui             gui
     * @param user                user
     * @param organization        user organization
     * @param file                file to ingest
     * @param attachment          attachment to ingest
     * @param type                collection/doc/image etc.
     * @param pid                 identifier
     * @param lhmCaptureElements  metadata
     * @param oldFedoraBean       current object in repository
     */
    public String ingest(Gui userGui, 
                       User user, 
                       String organization, 
                       File file, 
                       List<File> attachment, 
                       String type, 
                       String pid, 
                       LinkedHashMap<String, MetaDataElement> lhmCaptureElements, 
                       FedoraBean oldFedoraBean) throws SAXException 
    {
        boolean bIngest = true;
        
        if (pid == null) {
            // generate new pid if the first ingest
            pid = getNextPID(organization, type);
        } else {
            if (pid.endsWith(PID_COLLECTION_ACTIONS) 
                || pid.endsWith(PID_COLLECTION_TOL2008ACTIONS)
                || pid.endsWith(PID_COLLECTION_LYACTIONS)
                || pid.endsWith(PID_COLLECTION_AGENTS)
                || pid.endsWith(PID_COLLECTION_EVENTS)
                || pid.endsWith(PID_COLLECTION_PLACES)
                || pid.endsWith(PID_COLLECTION_CONTEXTS)) {
                    bIngest = true;
                } else {
                    bIngest = false;
                }
            
            if (type == null || type == "") {
                type = Osa.searchManager.getObjectType(pid, user);
            } else if (type.equals(RepositoryManager.COLLECTION)) {
                // get exact type
                if (lhmCaptureElements.containsKey(CaptureBean.TYPE)) {
                    type = lhmCaptureElements.get(CaptureBean.TYPE).getValue();
                }
            }
        }
        
        // check isPartOf relation exist
        boolean bFound = false;
        
        if (lhmCaptureElements.get(CaptureBean.RELATION_IsPartOf) != null) {
            String value = lhmCaptureElements.get(CaptureBean.RELATION_IsPartOf).getValue();
            
            if (value != null) {
                bFound = true;
            } else if (value == null) {
                lhmCaptureElements.get(CaptureBean.RELATION_IsPartOf).setValue(organization+":root");
                bFound = true;
            }
        }
        
        if (!bFound) {
            // relation not found, add relation to 'organization:root'
            String name = CaptureBean.RELATION_IsPartOf;
            String value = organization+":root";//"info:fedora/"+organization+":root";
            MetaDataType eType = MetaDataType.relation;
            
            MetaDataElement mdElement = new MetaDataElement(name, name, value, eType);
            lhmCaptureElements.put(name, mdElement);
        }
        
        // Create new or update existing Fedora object
        FedoraBean newFedoraBean = this.createFedoraBean(bIngest, user, userGui, pid, type, lhmCaptureElements);
        
        // In case of update update check, which datastreams have been changed
        if (!bIngest) {
            newFedoraBean = checkIfDatastreamsModified(oldFedoraBean, newFedoraBean);
        }
        
        ingestObject(bIngest, user, organization, type, file, attachment, newFedoraBean);
        
        return pid;
    }
    
    /**
     * Ingest/update to the Fedora repository.
     * @param bIngest
     * @param userGui
     * @param organization
     * @param file
     * @param type
     * @param lhmElements
     */
    public void ingestWithPid(boolean bIngest, User user, Gui userGui, String organization, File file, String type, LinkedHashMap<String, MetaDataElement> lhmElements) {
        String pid = null;
        
        pid = lhmElements.get("pid").getValue();
        
        if (pid == null) {
            logger.error("Fedora3Manager error: ingest update without pid.");
            return;
        }
        
        // Create new or update existing Fedora object
        FedoraBean fedoraBean = this.createFedoraBean(bIngest, user, userGui, pid, type, lhmElements);
        try {
            ingestObject(bIngest, user, organization, type, file, null, fedoraBean);
        } catch (SAXException e) {
            logger.error("Fedora3Manager:ingestWithPid error: "+e);
        }
    }
    
    /**
     * Function to ingest file to Fedora repository
     * @param organization      organization name
     * @param path              file path
     * @return
     */
    public boolean ingestFile(String organization, String path) {
        IngestResponse response;
        File file = new File(path);
        File organizationFile = null;
        BufferedReader reader;
        
        try {
            reader = new BufferedReader(new FileReader(file));
            String line = "";
            String text = "";
            String orgText = "XXXX";
            // files has PID f.ex. 'XXXX:F-tol2008actionA' to be replaced with organization name
            while ((line = reader.readLine()) != null) {
                if (line.contains(orgText)) {
                    line = line.replaceAll(orgText, organization);
                }
                text += line + "\r\n";
            }
            reader.close();
            
            // create temporary file for ingesting object to Fedora
            // file has now organization specific PID (f.ex. ORG:F-tol2008actionA)
            path = path.replace(".xml", "temp.xml");
            organizationFile = new File(path);
            FileWriter writer = new FileWriter(organizationFile);
            writer.write(text);
            writer.close();
            
            response = new Ingest().content(organizationFile).execute(fedoraClient);
            
        } catch (FileNotFoundException e) {
            logger.error("Fedora3Manager:ingestFile error: "+e);
        } catch (IOException e) {
            logger.error("Fedora3Manager:ingestFile error: "+e);
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:ingestFile error: "+e);
        }
        
        organizationFile.delete();
        return true;
    }
    
    /**
     * Ingest/update object to the Fedora repository.
     * @param bIngest       ingest vs. update
     * @param user          user name
     * @param organization  name of the user organization
     * @param type          object type
     * @param file          object file
     * @param attachment    attachment to ingest
     * @param fedoraBean    fedora object to hold metadata
     * @return
     */
    public boolean ingestObject(boolean bIngest, User user, String organization, String type, File file, List<File> attachment, FedoraBean fedoraBean) throws SAXException {
        
        IngestResponse response;
        String userName = user.getCn();
        
        try {
            
            String pid = fedoraBean.getPID();
            String label = fedoraBean.getObjectValue(FedoraBean.OBJ_LABEL);
            
            // Get schema for object type
            Document schemaDocument = this.getSchema(type, organization);
            
            // Get capture datastream
            // getXMLDataStream() function validates datastream as well
            String captureContent = fedoraBean.getXMLDataStream(FedoraBean.DATASTREAM_CAPTURE, schemaDocument);
            
            if (bIngest) {
                response = new Ingest(pid).label(label).execute(fedoraClient);
                //logger.info("Fedora3Manager: ingestObject done with pid:"+pid+", status "+response.getStatus());
            }
                        
            if (file != null) {
                // FILE
                FedoraClient.addDatastream(pid, FedoraBean.DATASTREAM_ORIGINAL)
                            .versionable(false)
                            .dsLabel(pid+FedoraBean.DATASTREAM_ORIGINAL_LABEL+"."+FilenameUtils.getExtension(file.getName())+" ("+userName+")")
                            .controlGroup("M")
                            .content(file)
                            .execute(fedoraClient);
                
                //logger.info("Fedora3Manager: file: "+file.getAbsolutePath()+" added");
                
                // Create thumbnail for preview
                createPreviewDatastream(file.getAbsolutePath(), pid, FedoraBean.DATASTREAM_ORIGINAL, userName);
            }
            
            if (attachment != null && attachment.size() > 0) {
                // ATTACHMENT
            	for (File item : attachment) {
            		createAttachmentDatastream(item, pid, fedoraBean, userName);
            	}
            }
            
            // DC
            if (bIngest || fedoraBean.bUpdateDcDatastream) {
                String dcContent = fedoraBean.getXMLDataStream(FedoraBean.DATASTREAM_DC, null);
                FedoraClient.addDatastream(pid, FedoraBean.DATASTREAM_DC)
                            .versionable(true)
                            .dsLabel(DCBean.DC_LABEL+" ("+userName+")")
                            .content(dcContent)
                            .mimeType("text/xml")
                            .execute(fedoraClient);
                //logger.info("Fedora3Manager: DC datastream updated");
            }

            // CAPTURE
            if (bIngest || fedoraBean.bUpdateCaptureDatastream) {
                FedoraClient.addDatastream(pid, FedoraBean.DATASTREAM_CAPTURE)
                            .versionable(true)
                            .dsLabel(CaptureBean.CAPTURE_LABEL+" ("+userName+")")
                            .content(captureContent)
                            .execute(fedoraClient);
                //logger.info("Fedora3Manager: CAPTURE datastream updated");
            }
                               
            // RELATIONSHIPS
            if (bIngest || fedoraBean.bUpdateReslExtDatastream) {
                // Set model
                String modelRelation = "info:fedora/fedora-system:def/model#hasModel";
                if (fedoraBean.bUpdateReslExtDatastream) {
                    FedoraClient.purgeRelationship(modelRelation);
                }
                FedoraClient.addRelationship(pid).predicate(modelRelation).object("info:fedora/"+fedoraBean.getContentModel()).execute(fedoraClient);
                
                // Set capture relationships to RELS-EXT
                LinkedHashMap<String, MetaDataElement> captureElements = fedoraBean.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements();
                Iterator<Entry<String, MetaDataElement>> iter = captureElements.entrySet().iterator();
                while (iter.hasNext()) {
                    Map.Entry<String, MetaDataElement> entry = (Map.Entry<String, MetaDataElement>) iter.next();
                    MetaDataElement element = entry.getValue();
                    
                    if (element.getMetaDataType().equals(MetaDataElement.MetaDataType.relation)) {
                        FedoraClient.addRelationship("info:fedora/"+ pid)
                                    .predicate("info:fedora/fedora-system:def/relations-external#" +element.getName())
                                    .object("info:fedora/"+ element.getValue())
                                    .execute(fedoraClient);
                    }
                }
                
                //logger.info("Fedora3Manager: RELS-EXT datastream updated");
            }
            
            // MANAGEMENT
            if (bIngest || fedoraBean.bUpdateManagementDatastream) {
                String managementContent = fedoraBean.getXMLDataStream(FedoraBean.DATASTREAM_MANAGEMENT, null);
                FedoraClient.addDatastream(pid, FedoraBean.DATASTREAM_MANAGEMENT)
                            .versionable(false)
                            .dsLabel(ManagementBean.MANAGEMENT_LABEL+" ("+userName+")")
                            .content(managementContent).execute(fedoraClient);
                //logger.info("Fedora3Manager: MANAGEMENT datastream updated");
                
                // if updating collection, update management datastream of child objects as well
                if (type.endsWith(RepositoryManager.COLLECTION) && fedoraBean.bUpdateManagementDatastream) {
                    String newParent = fedoraBean.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements().get(CaptureBean.RELATION_IsPartOf).getValue();
                    ArrayList<HashMap<String, String>> pidList = this.getAllChildren(pid);
                    for (int i=0; i<pidList.size(); i++)
                    {
                        String childPid = cleanEntry(pidList.get(i).get("child"));
                        updateObjectPosition(childPid, true, fedoraBean.getOldParent(), newParent, user);
                    }
                }
            }
            
            logger.info("Fedora3Manager: ingest/update for "+pid+" done.");

        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:ingestObject error: ",e);
        }
        
        Osa.dbManager.get("sql").setCtxTime(organization); //update timestamp for the organization context
        
        return true;
    }
    
    /**
     * Move object in hierarchy
     * @param pid
     * @param oldParent
     * @param newParent
     * @param user
     * @throws FedoraClientException
     */
    public void moveObject(String pid, String oldParent, String newParent, User user) throws FedoraClientException {
        boolean isChild = false;
        ArrayList<HashMap<String, String>> pidList = this.getAllChildren(pid);
    	logger.info("Fedora3Manager: moving object "+pid);
    	
    	updateObjectPosition(pid, isChild, oldParent, newParent, user);
        
        // If has children
    	if (!pidList.isEmpty()) {
    	    isChild = true;
    		for (int i=0; i<pidList.size(); i++) {
        		String childPid = cleanEntry(pidList.get(i).get("child"));
        		updateObjectPosition(childPid, isChild, oldParent, newParent, user);
        	}
    	}
    }
    
    /**
     * Update object metadata, when moving it below another collection
     * @param pid           pid of object
     * @param isChild       indicates if child object of object, that is to be moved
     * @param oldParent     original parent pid
     * @param newParent     new parent pid
     * @param user          user
     * @throws FedoraClientException
     */
    public void updateObjectPosition(String pid, boolean isChild, String oldParent, String newParent, User user) throws FedoraClientException {
        boolean ancestorUpdated = false;
        DataStream captureStream = null;
        DataStream managementStream = null;
        String newParentPath = this.getPath(newParent);
        String organization = user.getOrganization().getName().toUpperCase();
        
        if (oldParent != null && newParent != null && oldParent.equals(newParent)) {
            // no need to update
            return;
        }
        
        if (oldParent != null) {
            purgeRelationship(pid, CaptureBean.RELATION_IsPartOf, oldParent, false);
        }
        
        FedoraBean fObject = findObject(pid);
        captureStream = fObject.getDataStream(FedoraBean.DATASTREAM_CAPTURE);
        managementStream = fObject.getDataStream(FedoraBean.DATASTREAM_MANAGEMENT);
        MetaDataElement ancestorElement = (MetaDataElement)managementStream.getMetaDataElements().get(ManagementBean.DATA_isAncestor);
        MetaDataElement pathElement = (MetaDataElement)managementStream.getMetaDataElements().get(ManagementBean.DATA_objectPath);
        
        if (!isChild) {
            // remove oldParent
            captureStream.getMetaDataElements().get(CaptureBean.RELATION_IsPartOf).removeValue();
            // set new parent
            captureStream.getMetaDataElements().get(CaptureBean.RELATION_IsPartOf).setValue(newParent);
        }
        
        // update objectPath-metadata
        if (pathElement != null) {
            // remove old
            managementStream.getMetaDataElements().get(ManagementBean.DATA_objectPath).removeValue();
            
            // handle child objects
            if (isChild) {
                String childPath = this.getPath(pid);
                
                if (oldParent != null) {
                    // when moving collections and childobjects in browse.jsp
                    // objects have not been removed yet
                    String oldParentPath = this.getPath(oldParent);
                    String newChildPath = childPath.replace(oldParentPath, newParentPath);
                    pathElement.setValue(newChildPath);
                    
                } 

            } else {
                pathElement.setValue(newParentPath+"/"+pid);
            }
        }
        
        // update ancestor-metadata
        if (ancestorElement != null) {
            boolean updateAncestor = true;
            
            if (isChild) {
                // check if new parent needs to be updated
                String[] pids = newParentPath.split("/");
                String oldAncestor = managementStream.getMetaDataElements().get(ManagementBean.DATA_isAncestor).getValue(); 
                String newAncestor = "";
                
                if (pids.length >= 2) {
                    newAncestor = pids[1];
                    if (oldAncestor.equals(newAncestor)) {
                        updateAncestor = false;
                    }
                }
            }
            
            if (updateAncestor) {
                // remove oldParent
                managementStream.getMetaDataElements().get(ManagementBean.DATA_isAncestor).removeValue();
                if (newParent.contains(":root")) {
                    ancestorElement.setValue(pid);
                    ancestorUpdated = true;
                } else {
                    ancestorElement.setValue(this.getAncestor(newParent));
                    ancestorUpdated = true;
                }
            }
        }
        
        // if ancestor field has not been updated, it doesn't exist and needs to be created
        // Not needed for child objects
        if (!ancestorUpdated && !isChild) {
            if (newParent.contains(":root")) {
                managementStream.getMetaDataElements().put(ManagementBean.DATA_isAncestor, 
                                                           new MetaDataElement(ManagementBean.DATA_isAncestor, 
                                                                               ManagementBean.DATA_isAncestor, 
                                                                               pid, 
                                                                               MetaDataElement.MetaDataType.getEnum("string")));
            } else {
                managementStream.getMetaDataElements().put(ManagementBean.DATA_isAncestor,
                                                           new MetaDataElement(ManagementBean.DATA_isAncestor, 
                                                                               ManagementBean.DATA_isAncestor, 
                                                                               this.getAncestor(newParent), 
                                                                               MetaDataElement.MetaDataType.getEnum("string")));
            }
        }
        
        try {

            if (!isChild) {
                String pidType = pid.split(":")[1].split("-")[0];
                String objType = RepositoryManager.DATATYPES_MAP.get(pidType);
                // Get schema for object type
                Document schemaDocument = this.getSchema(objType, organization);
                String captureContent = fObject.getXMLDataStream(FedoraBean.DATASTREAM_CAPTURE, schemaDocument);
                FedoraClient.addDatastream(pid, FedoraBean.DATASTREAM_CAPTURE)
                            .versionable(true)
                            .dsLabel(CaptureBean.CAPTURE_LABEL+" ("+user.getCn()+")")
                            .content(captureContent)
                            .execute(fedoraClient);
                
                FedoraClient.addRelationship("info:fedora/"+ pid)
                            .predicate("info:fedora/fedora-system:def/relations-external#isPartOf")
                            .object("info:fedora/"+newParent)
                            .execute(fedoraClient);
            }
        
            String managementContent = fObject.getXMLDataStream(FedoraBean.DATASTREAM_MANAGEMENT, null);
            FedoraClient.addDatastream(pid, FedoraBean.DATASTREAM_MANAGEMENT)
                        .versionable(false)
                        .dsLabel(ManagementBean.MANAGEMENT_LABEL+" ("+user.getCn()+")")
                        .content(managementContent)
                        .execute(fedoraClient);
          
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:moveObject error: "+e);
        } catch (SAXException e) {
            logger.error("Fedora3Manager:moveObject error: "+e);
        }

    }
    
    private String cleanResponse(String response) {    	
    	String[] responseparts = response.split("/");
    	String objectpid = responseparts[(responseparts.length - 1)].split(">")[0];
    	return objectpid;
    }
    
    /**
     * RISearch query to find ancestor value (the first collection below root object)
     * @param parent
     * @return
     */
    private String getAncestor(String parentpid) {
    	Boolean ancestor = false;
    	
    	while (!ancestor) {
    		
            try {
            	String query = String.format("select $parent from <#ri> where <info:fedora/"+parentpid+"> <fedora-rels-ext:isPartOf> $parent");
            	
                RiSearchResponse response = null;
                response = FedoraClient.riSearch(query).lang("itql").format("simple").execute(fedoraClient);
                String result = response.getEntity(String.class);
                
                if (result.contains(":root") || result.isEmpty()) {
                	ancestor = true;
                } else {
                	parentpid = cleanResponse(result);
                }
                
            } catch (FedoraClientException e) {
                logger.error("Fedora3Manager error: "+e);
            }
    	}
    	
        return parentpid;
	}
    
    /**
     * RISearch query to find object loaction (path)
     * @param pid
     * @return
     */
    public String getPath(String pid) {
        String path = "";
        String organization = "";
        String parentpid = pid;
        Boolean rootFound = false;
        
        if (pid == null) {
            return path;
        } else {
            // get organization from pid
            String[] parts = pid.split(":");
            organization = parts[0];
        }
        
        if (pid.contains(":root")) {
            path = pid + "/";
            // object is below root
            return path;
        } else {
            path = pid;
        }
        
        while (!rootFound) {
            
            try {
                String query = String.format("select $parent from <#ri> where <info:fedora/"+parentpid+"> <fedora-rels-ext:isPartOf> $parent");
                
                RiSearchResponse response = null;
                response = FedoraClient.riSearch(query).lang("itql").format("simple").execute(fedoraClient);
                
                if (response.getStatus() != 200) { 
                    return null;
                }
                
                String result = response.getEntity(String.class);
                
                if (result.contains(":root") || result.isEmpty()) {
                    parentpid = cleanResponse(result);
                    // if empty, set below root object
                    if (parentpid == null || parentpid.isEmpty()) {
                        parentpid = organization+":root";
                    }
                    
                    path = parentpid+"/"+path;
                    rootFound = true;
                } else {
                    parentpid = cleanResponse(result);
                    path = parentpid+"/"+path;
                }
                
            } catch (FedoraClientException e) {
                logger.error("Fedora3Manager error: "+e);
            }
        }
        return path;
    }
    
    /**
     * RISearch query to calculate level of collection object in hierarchy
     * @param   collectionpid
     * @return  level
     */
    public int getObjLevel(String collectionpid) {
    	if (collectionpid == null) {
    		return 1;
    	}
    	int level = 1;
    	String parentpid = collectionpid;
    	boolean root = false;
    	
    	if (parentpid.contains(":root")) {
    		level = 0;
    	} else {
    	    
        	while (!root) {
    			
    	        try {
    	        	String query = String.format("select $parent from <#ri> where <info:fedora/"+parentpid+"> <fedora-rels-ext:isPartOf> $parent");
    	            RiSearchResponse response = null;
    	            response = FedoraClient.riSearch(query).lang("itql").format("simple").execute(fedoraClient);
    	            String result = response.getEntity(String.class);
    	
    	            if (result.contains(":root")) {
    	            	root = true;
    	            } else {
    	            	parentpid = cleanResponse(result);
    	            	level++;
    	            }

    	        } catch (FedoraClientException e) {
    	            logger.error("Fedora3Manager error: "+e);
    	        }
        	}
    	}
    	
    	return level;
    }
    
	/**
     * Function to find all datastreams for PID
     * @param pid       A persistent, unique identifier for the object
     * @return          list of datastream ids
     */
    public List<String> listDataStreams(String pid) {
        ListDatastreamsResponse response;
        List<DatastreamType> dsList = new ArrayList<DatastreamType>();
        List<String> dataStreamIDsList = new ArrayList<String>();
        
        if (pid == null || pid == "") {
            return dataStreamIDsList;
        }
        
        try {
            response = new ListDatastreams(pid).execute(fedoraClient);
            dsList = response.getDatastreams();
            String contentaction = null;
            
            for (int i=0;i<dsList.size();i++) {
                contentaction = dsList.get(i).getDsid();
                dataStreamIDsList.add(contentaction);
            }
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:listDataStreams error: "+e);
        }
        
        return dataStreamIDsList;
    }
    
    public List<DatastreamType> listRawDataStreams(String pid) {
        ListDatastreamsResponse response;
        List<DatastreamType> dsList = new ArrayList<DatastreamType>();
  
        try {
            response = new ListDatastreams(pid).execute(fedoraClient);
            dsList = response.getDatastreams();
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:listDataStreams error"+e);
        }
        
        return dsList;
    }
    
    /**
     * Remove relation from fedora object
     * @param pid           The PID of the object.
     * @param relationship  The predicate, null matches any predicate.
     * @param object        The object, null matches any object.
     * @param isLiteral     A boolean value indicating whether the object is a literal.
     */
    public void purgeRelationship(String pid, String relationship, String object, boolean isLiteral) {
        
        try {
            FedoraResponse response = FedoraClient.purgeRelationship("info:fedora/"+ pid)
                                                  .predicate("info:fedora/fedora-system:def/relations-external#" +relationship)
                                                  .object("info:fedora/"+ object, isLiteral)
                                                  .execute(fedoraClient);
            
            if (response != null && response.getStatus() ==  200) {
                logger.info("Fedora3Manager:purgeRelationship "+relationship+" for "+object+" done.");
            }
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:purgeRelationship error: "+e);
        }
    }

    /**
     * Cleans given string of fedora-specific strings
     * 
     * @param entry
     *            String to be cleaned
     * @return Cleaned string (if applicable)
     */
    public String cleanEntry(String entry) {
    	if (entry.contains("info:fedora/")) {
    		return entry.replace("info:fedora/", "");
    	} else {
    		return entry;
    	} 	
    }
    
    /**
     * Purge all relationships from Fedora object
     * @param pid       The PID of the object.
     * @throws FedoraClientException 
     */
    public void purgeRelationshipsFromObjects(String pid) {

        String organization = pid.split(":")[0];
        HashMap<String, ArrayList<HashMap<String, String>>> relations = new flexjson.JSONDeserializer<HashMap<String, ArrayList<HashMap<String, String>>>>().deserialize(this.getChildren(pid)); 
        ArrayList<HashMap<String, String>> relationlist = relations.get("results");
        ArrayList<String> pids = new ArrayList<String>();
        
        for (int i = 0; i < relationlist.size();i++) {
        	pids.add(this.cleanEntry(relationlist.get(i).get("child")));	
        }
        
        for (int j = 0;j<pids.size();j++) {
        	
        	FedoraBean fbean = this.findObject(pids.get(j));
        	this.purgeRelationship(pids.get(j), "isPartOf", pid, false);       
        	fbean.bUpdateCaptureDatastream = true;
        	fbean.bUpdateReslExtDatastream = true;
        }
    }

    /**
     * Change object state in Fedora repository
     * @param pid
     * @param state     active/inactive
     */
    public void setObjectState(String pid, State state) {
        try {
            if (state.equals("I")) {
                // state inactive
                this.purgeRelationshipsFromObjects(pid);
            }
            FedoraClient.modifyObject(pid).state(state.toString()).execute(fedoraClient);
            
        } catch (FedoraClientException e) {
            logger.error("Fedora3Manager:setObjectState error: "+e);
        }
    }
        
    public void writeDatastreamToFile(String uploadDir, String pid, String datastreamName, String origExt) throws UnsupportedEncodingException, IOException {
        
    	String objectUrl = this.baseUrl+"objects/"+pid+"/datastreams/"+datastreamName+"/content";
    	String datastreamNameLower = datastreamName.toLowerCase();

    	FedoraObjectHistory history = null;
    	
		String username = this.username;
        String password = this.password;
        String credentials = username + ":" + password;
        byte[] encoded = new Base64().encode(credentials.getBytes("UTF-8"));
        String encoding = new String(encoded);
    	
		OutputStream outputStream = null;
        File contentFile = new File("");
        
        URL url = new URL(objectUrl);
        URLConnection conn = url.openConnection();
        conn.setDoInput(true);
        conn.setRequestProperty("Authorization", String.format("Basic %s", encoding));
        InputStream is = conn.getInputStream();
        
	    int read = 0;
	    byte[] bytes = new byte[1024];
	    
    	if (FedoraBean.DATASTREAM_DC.equalsIgnoreCase(datastreamName) 
    	     || FedoraBean.DATASTREAM_CAPTURE.equalsIgnoreCase(datastreamName) 
    	     || FedoraBean.DATASTREAM_RELS_EXT.equalsIgnoreCase(datastreamName) 
    	     || FedoraBean.DATASTREAM_RELS_INT.equalsIgnoreCase(datastreamName) 
    	     || FedoraBean.DATASTREAM_MANAGEMENT.equalsIgnoreCase(datastreamName)
    	     )
    	{
    		File xmlfile = new File(uploadDir+pid+"/metadata/"+datastreamNameLower+"/"+datastreamNameLower+".xml");	
    		xmlfile.getParentFile().mkdirs();
        	outputStream = new FileOutputStream(xmlfile);
    	    while ((read = is.read(bytes)) != -1) {
    	    	outputStream.write(bytes, 0, read);
    	    }
    	    logger.info("Fedora3Manager: added "+datastreamNameLower+" xml file to package");
    	    
    	    try {
                GetObjectHistoryResponse response = FedoraClient.getObjectHistory(pid).execute(fedoraClient);
                history = response.getObjectHistory();
                
                List<String> changeDates = history.getObjectChangeDate();
                Iterator<String> iterChangeDates = changeDates.iterator();
                
                while (iterChangeDates.hasNext()) {
	                String changeDate = (String) iterChangeDates.next();
                    GetDatastreamHistoryResponse dsResponse = null;
                    List<DatastreamProfile> listDsProfile = null;
                    Iterator<DatastreamProfile> iterDsProfile = null;
                    
                    dsResponse = FedoraClient.getDatastreamHistory(pid, datastreamName).execute(fedoraClient);
                    listDsProfile = dsResponse.getDatastreamProfile().getDatastreamProfile();
                    iterDsProfile = listDsProfile.iterator();
                    
                    if (iterDsProfile != null) {                        
                        while (iterDsProfile.hasNext()) {
                            DatastreamProfile profile = (DatastreamProfile) iterDsProfile.next();
                            // check if date of version history matches to datastream version
                            if (profile.getDsCreateDate().toString().matches(changeDate)) {
                                objectUrl = this.baseUrl+"objects/"+pid+"/datastreams/"+datastreamName+"/content?asOfDateTime="+changeDate;
                                File historyxml = new File(uploadDir+pid+"/metadata/history/"+datastreamNameLower+"-history/"+profile.getDsVersionID()+"-"+changeDate+".xml");
                                historyxml.getParentFile().mkdirs();
                                
                                url = new URL(objectUrl);
                                conn = url.openConnection();
                                conn.setDoInput(true);
                                conn.setRequestProperty("Authorization", String.format("Basic %s", encoding));
                                is = conn.getInputStream();
                                
                            	outputStream = new FileOutputStream(historyxml);
                        	    while ((read = is.read(bytes)) != -1) {
                        	    	outputStream.write(bytes, 0, read);
                        	    }
                            }
                        }
                    }
                }
                logger.info("Fedora3Manager: added "+datastreamNameLower+" xml history files to package");
                
            } catch (FedoraClientException e) {
            	e.printStackTrace();
            }
    	    outputStream.close();
    	    
    	} else if (FedoraBean.DATASTREAM_ORIGINAL.equalsIgnoreCase(datastreamName) 
    	           || datastreamName.startsWith(FedoraBean.DATASTREAM_ATTACHMENT) 
    	           ||  datastreamName.startsWith(FedoraBean.DATASTREAM_THUMB)) {

    		if (FedoraBean.DATASTREAM_ORIGINAL.equalsIgnoreCase(datastreamName)) {
    			if (origExt != null) {
    				contentFile = new File(uploadDir+pid+"/"+datastreamNameLower+"/"+datastreamNameLower+"."+origExt);
    				logger.info("Fedora3Manager: added "+datastreamNameLower+" "+origExt+" file to package");
    			}
    		}

    		if (datastreamName.startsWith(FedoraBean.DATASTREAM_ATTACHMENT)) {
            	List<DatastreamType> dslist = this.listRawDataStreams(pid);
            	String format = "";
            	
            	for (DatastreamType ds : dslist) {
            		if (datastreamName.equals(ds.getDsid())) {
            			format = ds.getMimeType();	
            		}
            	}
         
                String attExt = format.substring(format.indexOf("/")+1, format.length());
    			contentFile = new File(uploadDir+pid+"/attachments/"+datastreamNameLower+"."+attExt);
    			logger.info("Fedora3Manager: added "+datastreamNameLower+" "+attExt+" file to package");
    		}

    		if (datastreamName.startsWith(FedoraBean.DATASTREAM_THUMB)) {
    			List<DatastreamType> dslist = this.listRawDataStreams(pid);
            	String format = "";
            	
            	for (DatastreamType ds : dslist) {
            		if (datastreamName.equals(ds.getDsid())) {
            			format = ds.getMimeType();	
            		}
            	}
         
                String thumbExt = format.substring(format.indexOf("/")+1, format.length());
				contentFile = new File(uploadDir+pid+"/thumbnails/"+datastreamNameLower+"."+thumbExt);
				logger.info("Fedora3Manager: added "+datastreamNameLower+" "+thumbExt+" file to package");
    		}
    		
    		contentFile.getParentFile().mkdirs();
    		outputStream = new FileOutputStream(contentFile);
    		while ((read = is.read(bytes)) != -1) {
    			outputStream.write(bytes, 0, read);
    		}
    		outputStream.close();
    	}

		File auditContentFile = new File(uploadDir+pid+"/metadata/history/audit/audit.xml");
		if (!auditContentFile.exists()) {
			auditContentFile.getParentFile().mkdirs();
			outputStream = new FileOutputStream(auditContentFile);
			PrintStream printStream = new PrintStream(outputStream);
			printStream.print(this.getAuditTrail(pid));
			logger.info("Fedora3Manager: added audit trail xml file to package");
			
			printStream.close();
			outputStream.close();
		}
        is.close();
    }
    
} public abstract class RepositoryManager {
    
    // Fedora versions
    public static final String FEDORA3 = "fedora3";
    public static final String FEDORA4 = "fedora4";
    
    // content model prefix
    public static final String CONTENTMODEL    = "osa-system:contentmodel-";
    
    // data types
    public static final String ACTION          = "action";
    public static final String AGENT           = "agent";
    public static final String APPLICATION     = "application";
    public static final String AUDIO           = "audio";
    public static final String BASECOLLECTION  = "basecollection";
    public static final String COLLECTION      = "collection";
    public static final String DEFAULT_TYPE    = "default";
    public static final String DOCUMENT        = "document";
    public static final String DRAWING         = "drawing";
    public static final String EVENT           = "event";    
    public static final String GROUPCOLLECTION = "groupcollection";
    public static final String IMAGE           = "image";
    public static final String MAP             = "map";
    public static final String MOVINGIMAGE     = "movingimage";
    public static final String PHYSICALOBJECT  = "physicalobject";
    public static final String PLACE           = "place";
    public static final String ROOT            = "root";    
    public static final String UNITCOLLECTION  = "unitcollection";
    
    public static final String PID_COLLECTION_AGENTS         = ":C-agents";
    public static final String PID_COLLECTION_ACTIONS        = ":C-actions";
    public static final String PID_COLLECTION_CONTEXTS       = ":C-contexts";
    public static final String PID_COLLECTION_EVENTS         = ":C-events";
    public static final String PID_COLLECTION_LYACTIONS      = ":C-lyactions";
    public static final String PID_COLLECTION_PLACES         = ":C-places";
    public static final String PID_COLLECTION_TOL2008ACTIONS = ":C-tol2008actions";
    
    /** Abbreviations for object types. Lookup with PID's object type's identifier.
     * 
     */
    public static final Map<String, String> DATATYPES_MAP = 
            Collections.unmodifiableMap(new HashMap<String, String>() {
                private static final long serialVersionUID = 1L;
            { 
                put("F", RepositoryManager.ACTION);
                put("W", RepositoryManager.AGENT);
                put("S", RepositoryManager.APPLICATION);
                put("A", RepositoryManager.AUDIO);
                put("C", RepositoryManager.COLLECTION);
                put("T", RepositoryManager.DOCUMENT);
                put("D", RepositoryManager.DRAWING);
                put("E", RepositoryManager.EVENT);
                put("P", RepositoryManager.IMAGE);
                put("M", RepositoryManager.MAP);
                put("V", RepositoryManager.MOVINGIMAGE);
                put("O", RepositoryManager.PHYSICALOBJECT);
                put("L", RepositoryManager.PLACE);
                put("X", RepositoryManager.DEFAULT_TYPE);
            }});

    /** Letters for different kind of object types. Lookup with object type as plain, normal text.
     * 
     */
    public static final Map<String, String> DATATYPES_MAP_REVERSE = 
            Collections.unmodifiableMap(new HashMap<String, String>() {
                private static final long serialVersionUID = 1L;
            { 
                put(RepositoryManager.ACTION,        "F");
                put(RepositoryManager.AGENT,         "W");
                put(RepositoryManager.APPLICATION,   "S");
                put(RepositoryManager.AUDIO,         "A");
                put(RepositoryManager.COLLECTION,    "C");
                put(RepositoryManager.DOCUMENT,      "T");
                put(RepositoryManager.DRAWING,       "D");
                put(RepositoryManager.EVENT,         "E");
                put(RepositoryManager.IMAGE,         "P");
                put(RepositoryManager.MAP,           "M");
                put(RepositoryManager.MOVINGIMAGE,   "V");
                put(RepositoryManager.PHYSICALOBJECT,"O");
                put(RepositoryManager.PLACE,         "L");
                put(RepositoryManager.DEFAULT_TYPE,  "X");
            }});

    public static final Map<String, String> CONTEXTOBJECT_COLLECTIONS = 
            Collections.unmodifiableMap(new HashMap<String, String>() {
                private static final long serialVersionUID = 1L;
            { 
                put(RepositoryManager.ACTION, RepositoryManager.PID_COLLECTION_ACTIONS);
                put(RepositoryManager.AGENT, RepositoryManager.PID_COLLECTION_AGENTS);
                put(RepositoryManager.EVENT, RepositoryManager.PID_COLLECTION_EVENTS);
                put(RepositoryManager.PLACE, RepositoryManager.PID_COLLECTION_PLACES);
            }});
    
    private static final Logger logger = Logger.getLogger(RepositoryManager.class);
    
    public String username;
    public String password;
    public String baseUrl;
    // content data
    Vector<FedoraBean> vectFedoraObjects = new Vector<FedoraBean>();
    
    public abstract void createAttachmentDatastream(File file, String pid, FedoraBean fObject, String user);
    public abstract void createPreviewDatastream(String filePath, String pid, String datastreamName, String user);
    public abstract FedoraBean createFedoraBean(boolean bIngest, User user, Gui userGui, String pid, String type, LinkedHashMap<String, MetaDataElement> lhmElements);
    public abstract void createRootObject(String rootName, Gui userGui, User user);
    public abstract String getAuditTrail(String pid);
    public abstract void deleteObject(String pid);
    public abstract void createResidualObject(String pid, DestructionPlan plan);
    public abstract void deleteRootObject(String rootName);
    public abstract void deleteDataStream(String pid, String dsid);
    public abstract void deleteAllobjects(String orgContext);
    public abstract void deleteActionObjects(String orgContext, String type);
    public abstract List<String> findAllObjects(String orgContext);
    public abstract FedoraBean findObject(String pid);
    public abstract String getChildren(String pid);
    public abstract ArrayList<HashMap<String, String>> getAllChildren(String pid);
    public abstract String getCollectionObjects(String organization);
    public abstract Document getSchema(String type, String organization);
    public abstract String getNextPID(String organizationName, String mime);
    public abstract List<MetaDataElement> getObjectHistory(String pid);
    public abstract String ingest(Gui userGui, User user, String organization, File file, List<File> attachment, 
        String type, String pid, LinkedHashMap<String, MetaDataElement> lhmCaptureElements, FedoraBean oldFedoraBean) throws SAXException;
    public abstract void ingestWithPid(boolean bIngest, User user, Gui userGui, String organization, File file, String type, LinkedHashMap<String, MetaDataElement> lhmElements);
    public abstract boolean ingestFile(String organization, String path);
    public abstract boolean ingestObject(boolean bIngest, User user, String organization, String type, File file, List<File> attachment, FedoraBean fedoraBean) throws SAXException;
    public abstract void moveObject(String pid, String oldParent, String newParent, User user) throws FedoraClientException;
    public abstract void updateObjectPosition(String pid, boolean isChild, String oldParent, String newParent, User user) throws FedoraClientException;
    public abstract String getPath(String pid);
    public abstract int getObjLevel(String collectionpid);
    public abstract List<String> listDataStreams(String pid);
    public abstract List<DatastreamType> listRawDataStreams(String pid);
    public abstract void purgeRelationship(String pid, String relationship, String object, boolean isLiteral);
    public abstract String cleanEntry(String entry);
    public abstract void purgeRelationshipsFromObjects(String pid);
    public abstract void setObjectState(String pid, State state);
    public abstract void writeDatastreamToFile(String uploadDir, String pid, String datastreamName, String origExt) throws UnsupportedEncodingException, IOException;
    
    /**
     * Check if datastreams have been modified before updating to Fedora repository
     * @param oldObject     current object in Fedora Repository
     * @param newObject     new object to be updated to Fedora Repository
     * @return              object updated with boolean values, which tells if datastreams modified
     */
    public FedoraBean checkIfDatastreamsModified(FedoraBean oldObject, FedoraBean newObject) {
        
        if (oldObject == null) {
            return newObject;
        }
        
        // get existing datastreams for pid
        List<String> existingDatastreams = listDataStreams(oldObject.getPID());
        int size = existingDatastreams.size();
        
        // Handle all datastreams
        for (int i=0; i<size; i++) {
            
            String currentDatastream = existingDatastreams.get(i);
            String newDS = null;
            String oldDS = null;
            
            if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_CAPTURE)) {
                newDS = newObject.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements().toString();                
                oldDS = oldObject.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements().toString();
                
            } else if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_DC)) {
                
                // sort by key
                List<String> keysNew = new ArrayList<String>(newObject.getDataStream(FedoraBean.DATASTREAM_DC).getMetaDataElements().keySet());
                Collections.sort(keysNew);
             
                Map<String, MetaDataElement> tempNew = new LinkedHashMap<String, MetaDataElement>();
                for (String key: keysNew) {
                    tempNew.put(key, newObject.getDataStream(FedoraBean.DATASTREAM_DC).getMetaDataElements().get(key));
                }
                
                List<String> keysOld = new ArrayList<String>(oldObject.getDataStream(FedoraBean.DATASTREAM_DC).getMetaDataElements().keySet());
                Collections.sort(keysOld);
             
                Map<String, MetaDataElement> tempOld = new LinkedHashMap<String, MetaDataElement>();
                for (String key: keysOld) {
                    tempOld.put(key, oldObject.getDataStream(FedoraBean.DATASTREAM_DC).getMetaDataElements().get(key));
                }

                // remove name=identified before comparing datastreams
                tempOld.remove("identifier");
                
                newDS = tempNew.toString();
                oldDS = tempOld.toString();
                
            } else if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_RELS_EXT)) {
                
                LinkedHashMap<String, MetaDataElement> tempNew = newObject.getDataStream(FedoraBean.DATASTREAM_RELS_EXT).getMetaDataElements();
                LinkedHashMap<String, MetaDataElement> tempOld = oldObject.getDataStream(FedoraBean.DATASTREAM_RELS_EXT).getMetaDataElements();
                
                // remove name=hasModel before comparing datastreams
                tempOld.remove("hasModel");
                
                newDS = tempNew.toString();
                oldDS = tempOld.toString();
                
            } else if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_MANAGEMENT)) {
                
                newDS = newObject.getDataStream(FedoraBean.DATASTREAM_MANAGEMENT).getMetaDataElements().toString();
                oldDS = oldObject.getDataStream(FedoraBean.DATASTREAM_MANAGEMENT).getMetaDataElements().toString();
                
            } else if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_RELS_INT)) {
                // TODO: check this
            }
            
            MessageDigest m;
            String newChecksum = null;
            String oldChecksum = null;
            
            try {
                
                m = MessageDigest.getInstance("MD5");
                
                if (oldDS != null) {
                    m.update(oldDS.getBytes(), 0, oldDS.length());
                    oldChecksum = new BigInteger(1,m.digest()).toString(16);
                    m.reset();
                }
                
                if (newDS != null) {
                    m.update(newDS.getBytes(), 0, newDS.length());
                    newChecksum = new BigInteger(1,m.digest()).toString(16);
                }
                
                if ((newChecksum != null && oldChecksum != null && !oldChecksum.equalsIgnoreCase(newChecksum))
                    || newChecksum != null && oldChecksum == null
                   )
                {
                    if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_CAPTURE)) {
                        // Datastream modified
                        
                        // Check if title has been edited, update it to basket items
                        if (newObject.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements().containsKey(CaptureBean.TITLE) && 
                            oldObject.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements().containsKey(CaptureBean.TITLE)) {
                            if (!newObject.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements().get(CaptureBean.TITLE).getValue().equals(
                                 oldObject.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements().get(CaptureBean.TITLE).getValue())) {
                                Osa.dbManager.get("mongo").syncBaskets(newObject.getPID(), 
                                                                       MongoManager.EVENT_RENAME, 
                                                                       newObject.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements().get(CaptureBean.TITLE).getValue());
                            }
                        }
                        
                        newObject.bUpdateCaptureDatastream = true;
                        // store old parent (value is used when updating path, if object and child objects moved)
                        String oldParent = oldObject.getDataStream(FedoraBean.DATASTREAM_CAPTURE).getMetaDataElements().get(CaptureBean.RELATION_IsPartOf).getValue();
                        newObject.setOldParent(oldParent);
                        
                    } else if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_DC)) {
                        // datastream modified
                        newObject.bUpdateDcDatastream = true;
                    } else if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_RELS_EXT)) {
                        // datastream modified
                        newObject.bUpdateReslExtDatastream = true;
                    } else if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_RELS_INT)) {
                        // datastream modified
                        newObject.bUpdateReslIntDatastream = true;
                    } else if (currentDatastream.equalsIgnoreCase(FedoraBean.DATASTREAM_MANAGEMENT)) {
                        // datastream modified
                        newObject.bUpdateManagementDatastream = true;
                    }
                }
                
            } catch (NoSuchAlgorithmException e) {
                logger.error("RepositoryManager error: "+e);
            }
        }
        
        return newObject;
    }
    
    /**
     * Get fedora object from FedoraManager
     * @param pid       The PID of the object.
     * @return          fedora object
     */
    public FedoraBean getObject(String pid) {
        Iterator<FedoraBean> iterFedoraObjects = this.vectFedoraObjects.iterator();
        FedoraBean fedoraObject = null;        
        
        while (iterFedoraObjects.hasNext()) {
            FedoraBean currentObject = (FedoraBean) iterFedoraObjects.next();
            
            if (currentObject.getPID().equalsIgnoreCase(pid)) {
                fedoraObject = currentObject;
            }
        }
        return fedoraObject;
    }
    
    /**
     * Check if object is allowed in storage level
     * @param plan          Preservation plan
     * @param objectType    document/image/base collection/group collection/ etc.
     * @param order         storage level if available (default -1)
     * @param objParent     pid of parent object
     * @param isCollection  parameter to define if object is collection
     * @return              validation error text if object can not be stored
     */
    public String isObjectPermitted(PreservationPlan plan, String objectType, int order, String objParent, boolean isCollection) {
        String validationErrorText = "";
        if (objectType == null && objParent == null) {
            return "error.preservation";
        } else if (objParent == null && order == -1) {
            return "error.preservation";
        } else if (objParent != null && order == -1) {
            order = this.getObjLevel(objParent);
        }
        
        if (isCollection) {
            // collection itself has information in preservation plan
            order = order+1;
        }
        
        // check preservation plan
        if (order <= plan.getMaxLevel()) {
            for (ArchiveLevel level : plan.getHierarchy()) {
                if (order == level.getOrder() || order < level.getMaxOrder()) {
                    if (isCollection) {
                        // adding collection
                        // check type of collection, and check if it is allowed to this level
                        if (!level.getAllowCollectionsForType(objectType)) {
                            validationErrorText = "error.preservation.collection";
                        }
                        break;
                        
                    } else {
                        // adding other type
                        // check type of objParent, and getAllowDocuments according to this value                     
                        if (!level.getAllowDocumentsForType(objectType)) {
                            validationErrorText = "error.preservation";
                        }
                        break;
                    }
                }
            }
        } else {
            validationErrorText = "max level is "+ plan.getMaxLevel();
        }
        return validationErrorText;
    }
} public class TaskScheduler {
    
    private String jobname;
    private String triggername;
    private String logDirectory;
    private String configPath;
    private static final Logger logger = Logger.getLogger(TaskScheduler.class);
    private Scheduler scheduler;
    
    public TaskScheduler(String jobname, String triggername, String logDirectory, String configPath) {
        this.jobname = jobname;
        this.triggername = triggername;
        this.logDirectory = logDirectory;
        this.configPath = configPath;
        this.scheduler = null;
    }
    
    public void start() throws SchedulerException {
        
        JobDetail job = JobBuilder.newJob(ScheduleExecutor.class)
                  .withIdentity(jobname, "group1")
                  .requestRecovery()
                  .usingJobData("configPath", configPath)
                  .usingJobData("logDirectory", logDirectory)
                  .build();
        
        // schedule
        int timeInterval = 1;
        SimpleScheduleBuilder simpleScheduleBuilder = null;
        simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
        simpleScheduleBuilder.withIntervalInHours(timeInterval * 24).repeatForever();
        
        Trigger trigger = TriggerBuilder
                .newTrigger()
                .withIdentity(triggername, "group1")
                .withSchedule(simpleScheduleBuilder)
                .startAt(DateBuilder.dateOf(23, 55, 0))
                .forJob(job)
                .build();
        
        scheduler = new StdSchedulerFactory(configPath+"config/quartz.properties").getScheduler();
        scheduler.start();
        scheduler.scheduleJob(job, trigger);
        
    }
    
    public String getConfigPath() {
        return this.configPath;
    }
    
    public String getLogDirectory() {
        return this.logDirectory;
    }
    
    /**
     * Stop scheduler
     */
    public void stop() {
        try {
            
            scheduler.unscheduleJob(TriggerKey.triggerKey(triggername, "group1"));                       
            scheduler.pauseAll();
            scheduler.shutdown(true);
            
            int ct = 0;

            // waiting 30 seconds for the scheduler to shutdown
            while(ct < 30) {
                ct++;
                Thread.sleep(1000);
                if (scheduler.isShutdown()) {
                    break;
                }
            }
            
        } catch (SchedulerException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

} public abstract class SearchManager {
	
	public static enum ProviderType  {solr};
	
	private ProviderType provider;
	private SearchConfiguration configuration;
	
    
	public abstract Search search(Search search, User user, Organization organization) throws Exception;
	public abstract boolean testSearch();

	public ProviderType getProvider() {
		return provider;
	}
	public String getItemByPid(String pid, User user, Locale locale){
		String title = "";
		return title;
	}
	public List<TreeNode> getChildren(String identifier, String pidType, User user, HashMap<String, String> filters, String accessRights){
		List<TreeNode> nodes = new ArrayList<TreeNode>();
		return nodes;
	}
    public HashMap<String, String> getDisposableItems(String dateTime) {
        HashMap<String, String> items = new HashMap<String, String>();
        return items;
    }
	public void setProvider(ProviderType provider) {
		this.provider = provider;
	}

	public SearchConfiguration getConfiguration() {
		return configuration;
	}

	public void setConfiguration(SearchConfiguration configuration) {
		this.configuration = configuration;
	}
	public String getObjectPath(String identifier, User user, boolean viewOnly) {
		return null;
	}
	public String getObjectType(String identifier, User user) {
	    return null;
	}
	public String getObjectValue(String identifier, String mdName, User user) {
        return null;
    }
	public String parseAccessRights(User user) {
	    return null;
	}
} public class SolrManager extends SearchManager
{
    private static final Logger logger = Logger.getLogger(SolrManager.class);

    public static final String SOLRFIELD_Pid           = "PID";
    public static final String SOLRFIELD_DestrDate     = "c.destructionDate";
    public static final String SOLRFIELD_Id            = "c.id";
    public static final String SOLRFIELD_IsPartOf      = "c.isPartOf";
    public static final String SOLRFIELD_PreferredName = "c.preferredName";
    public static final String SOLRFIELD_Title         = "c.title";
    public static final String SOLRFIELD_Type          = "c.type";

    private Solr solr;
    
    public SolrManager() {
    	this.solr = new Solr();
    }

    @Override
    public boolean testSearch() {
        boolean status = false;

        QueryResponse response = solr.query("*:*", new Vector<String>(), new HashMap<String, String>(), null, null, -1, -1, " ");
        if (response != null) {
            SolrDocumentList results = response.getResults();
            
            if (!results.isEmpty()) {
                status = true;
            }
        }
        
        return status;
    }    
    
    @Override
    public List<TreeNode> getChildren(String identifier, String pidType, User user, HashMap<String, String> filters, String accessRights) {
    	HashMap<String, String> searchFilters = (filters == null) ? new HashMap<String, String>() : filters;
    	String queryString = "\""+identifier+"\"";
        ArrayList<TreeNode> nodes = new ArrayList<TreeNode>();
        
        if (accessRights == null || accessRights.isEmpty() || accessRights.equals("")) {
            accessRights = parseAccessRights(user);
        }
        
    	QueryResponse response = solr.query(SOLRFIELD_IsPartOf+":"+queryString, 
    	                                    new Vector<String>(), 
    	                                    searchFilters, 
    	                                    accessRights, 
    	                                    new Vector<String>(), 
    	                                    -1, 
    	                                    Integer.MAX_VALUE, 
    	                                    SOLRFIELD_Id+" asc");
    	
    	if (response != null) {

            SolrDocumentList results = response.getResults();
                        
            for (SolrDocument result : results) {

                TreeNode cNode = new TreeNode();
            	String pid = null;
            	
            	if (result.getFieldValue(SOLRFIELD_Pid) != null) {
                    pid = result.getFieldValue(SOLRFIELD_Pid).toString();
                }
            	
            	if (pid != null) {
    			    
                    if (pidType == null // show all
                        || (pidType.equals("C") && pid.contains(":"+pidType+"-")) // show collections
                        || (!pidType.equals("C") && (pid.contains(RepositoryManager.PID_COLLECTION_CONTEXTS) // show agents, actions, events, places
                                                     || pid.contains(RepositoryManager.PID_COLLECTION_ACTIONS)
                                                     || pid.contains(RepositoryManager.PID_COLLECTION_TOL2008ACTIONS)
                                                     || pid.contains(RepositoryManager.PID_COLLECTION_LYACTIONS)
                                                     || pid.contains(RepositoryManager.PID_COLLECTION_AGENTS)
                                                     || pid.contains(RepositoryManager.PID_COLLECTION_EVENTS)
                                                     || pid.contains(RepositoryManager.PID_COLLECTION_PLACES)
                                                     || pid.contains(":"+pidType)))
                        ) {
                        cNode.setId(result.getFieldValue(SOLRFIELD_Pid).toString());
                    } else {
                        // if pidType is determined (L/E/F/W), 
                        // only PIDs containing pidType value will be shown
                        continue;
                    }
                }

            	// if c.title is not available use c.preferredName
            	if (result.containsKey(SOLRFIELD_Title)) {
            	    String name = convertToString(result.get(SOLRFIELD_Title), SOLRFIELD_Title);
                    cNode.setName(name);
            	} else if (result.containsKey(SOLRFIELD_PreferredName)) {
            	    String name = convertToString(result.get(SOLRFIELD_PreferredName), SOLRFIELD_PreferredName);
                    cNode.setName(name);
                } else {
                    cNode.setName(new LocalizableMessage("header.notitlejs").getMessage(user.getLocale()));
                }
    			
            	if (result.containsKey(SOLRFIELD_Type)) {
            	    String type = convertToString(result.get(SOLRFIELD_Type), SOLRFIELD_Type);
    			    // use collection for all collection types
    			    if (type.contains(RepositoryManager.COLLECTION)) {
    			        type = RepositoryManager.COLLECTION;
    			    }
    			    
    			    String key = RepositoryManager.DATATYPES_MAP_REVERSE.get(type);
    			    cNode.setType(key);
    			}
    			                
                nodes.add(cNode);
            }
    	}  	
    	
    	return nodes;
    }
    
    @Override
    public HashMap<String, String> getDisposableItems(String dateTime) {
        
        HashMap<String, String> searchFilters = new HashMap<String, String>();
        String queryString = "\""+dateTime+"\"";
        // map for key:pid and value:title
        HashMap<String, String> items = new HashMap<String, String>();
        
        QueryResponse response = solr.query(SOLRFIELD_DestrDate+":"+queryString, 
                                            new Vector<String>(), 
                                            searchFilters, 
                                            "", 
                                            new Vector<String>(), 
                                            -1, 
                                            Integer.MAX_VALUE, 
                                            "");
        
        if (response != null) {

            SolrDocumentList results = response.getResults();
            
            for (int i=0; i<results.size(); i++) {

                String pid = null;
                String title = "";
                if (results.get(i).getFieldValue(SOLRFIELD_Pid) != null) {
                    pid = results.get(i).getFieldValue(SOLRFIELD_Pid).toString();
                }
                
                if (results.get(i).containsKey(SOLRFIELD_Title)) {
                    title = convertToString(results.get(i).get(SOLRFIELD_Title), SOLRFIELD_Title);
                } else if (results.get(i).containsKey(SOLRFIELD_PreferredName) && title.equals("")) {
                    title = convertToString(results.get(i).get(SOLRFIELD_PreferredName), SOLRFIELD_PreferredName);
                }
                
                items.put(pid, title);
            }
        }
        
        return items;
    }
    
    @Override
    public String getItemByPid(String identifier, User user, Locale locale){
    	String title = "";
    	
    	QueryResponse response = solr.query(SOLRFIELD_Pid+":"+identifier, 
    	                                    new Vector<String>(), 
    	                                    new HashMap<String, String>(), 
    	                                    parseAccessRights(user), 
    	                                    new Vector<String>(), 
    	                                    -1, -1, "score asc");
    	if (response != null) {
    		SolrDocumentList results = response.getResults();
    		
    		for (int i=0; i<results.size();i++) {
    		    if (results.get(i).containsKey(SOLRFIELD_Title)) {
    				title = convertToString(results.get(i).get(SOLRFIELD_Title), SOLRFIELD_Title);
    			} else if (results.get(i).containsKey(SOLRFIELD_PreferredName) && title.equals("")) {
    			    title = convertToString(results.get(i).get(SOLRFIELD_PreferredName), SOLRFIELD_PreferredName);
    			}
    		}
    	}
    	
    	if (title.isEmpty() || title.equals("")) {
    		title = new LocalizableMessage("header.notitlejs").getMessage(locale);
    	}
    	
    	return title;
    	
    }
        
    @Override
    public String getObjectPath(String identifier, User user, boolean viewOnly){
        
        String org = user.getOrganization().getName().toUpperCase();
    	String path = "<a href='browse.jsp' title='"+org+"'>"+org+"</a> &raquo; ";
    	String origPid = identifier;
    	ArrayList<String> names = new ArrayList<String>();
    	boolean isRoot = false;

    	while (!isRoot && identifier != "") {
    		QueryResponse response = solr.query(SOLRFIELD_Pid+":"+identifier, 
    		                                    new Vector<String>(), 
    		                                    new HashMap<String, String>(), 
    		                                    parseAccessRights(user), 
    		                                    new Vector<String>(), 
    		                                    -1, -1, "score asc");
    		if (response != null) {
        		SolrDocumentList results = response.getResults();
        		String isPartOf = "";
        		String title = "";
        		String pid = "";
        		
        		if (results.size() == 0) {
        		    identifier = isPartOf;
        		}
        		
        		for (int i=0; i<results.size(); i++) {
                    
        		    if (results.get(i).containsKey(SOLRFIELD_IsPartOf)) {
                        isPartOf = convertToString(results.get(i).get(SOLRFIELD_IsPartOf), SOLRFIELD_IsPartOf);
                        if (isPartOf.contains(":root") || user.isAnonymous()) {
                            // show anonymous user only name
                            isRoot = true;
                        }
                    }
        		    
        		    if (results.get(i).containsKey(SOLRFIELD_Title)) {
                        title = convertToString(results.get(i).get(SOLRFIELD_Title), SOLRFIELD_Title);
                    } else if (results.get(i).containsKey(SOLRFIELD_PreferredName)) {
                        title = convertToString(results.get(i).get(SOLRFIELD_PreferredName), SOLRFIELD_PreferredName);
                    }
                    
        		    if (results.get(i).containsKey(SOLRFIELD_Pid)) {
                        pid = results.get(i).getFieldValue(SOLRFIELD_Pid).toString();
                    }
                    
                    identifier = isPartOf;
                    
                    // If found pid equals object that user opened, do not create link or arrow
                    if (pid.equalsIgnoreCase(origPid)) {
                        names.add("<span class='italic'>"+title+"</span>");
                    } else {
                        names.add("<a href='Ingest.action?pid="+pid+"&plainView="+viewOnly+"&view=' title='"+title+"'>"+title+"</a> &raquo; ");
                    }
        		}
        	}
    	}
    	
    	for (int x =(names.size()-1); x>-1; x--) {
    		path += names.get(x);
    	}
    	
    	return path;
    }
    
    @Override
    public String getObjectType(String identifier, User user){
        String type = "";
        
        QueryResponse response = solr.query(SOLRFIELD_Pid+":"+identifier, 
                                            new Vector<String>(), 
                                            new HashMap<String, String>(), 
                                            parseAccessRights(user), 
                                            new Vector<String>(), 
                                            -1, -1, "score asc");
        if (response != null) {
            SolrDocumentList results = response.getResults();
            
            for (SolrDocument doc : results) {
            	type = convertToString(doc.get(SOLRFIELD_Type), SOLRFIELD_Type);
            }
        }
        
        if (type == null || type.isEmpty() || type.equals("")) {
            type = "null";
        }
        return type;
    }
    
    @Override
    /**
     * Get metadata value by object pid and metadata name
     */
    public String getObjectValue(String identifier, String mdName, User user){
        String mdValue = "";
        QueryResponse response = solr.query(SOLRFIELD_Pid+":"+identifier, 
                                            new Vector<String>(), 
                                            new HashMap<String, String>(), 
                                            parseAccessRights(user), 
                                            new Vector<String>(), 
                                            -1, -1, "score asc");
        
        if (response != null) {
            SolrDocumentList results = response.getResults();
            for (SolrDocument doc : results) {
                for (String key : doc.keySet()) {     
                    if (key.equals(mdName)) {
                        mdValue = convertToString(doc.get(key), key);
                        break;
                    }
                }
            }
        }
        
        if (mdValue == null || mdValue.isEmpty() || mdValue.equals("")) {
            mdValue = "";
        }
        return mdValue;
    }
    
    @Override
    public Search search(Search search, User user, Organization organization) throws Exception {
        if (search == null) {
            search = new Search();
        }
        
        if (user != null) {                 // Private search
            organization = user.getOrganization();
        } else if (organization != null) {  // Public search
            // do something
        } else {                            // No known organization or user
            throw new Exception("Search error. Unknown user and organization.");
        }
            
        // TODO Parse query
        String freetext = "";
        if (search.getFreetext() == null || search.getFreetext().equals("")) {
            freetext = "*:*";
        } else {
            freetext = search.getFreetext();
        }
        String query = freetext;

        for (SearchFormField searchFormField : search.getFieldValues()) {
          
            // Generate tokens from field values
        	if (searchFormField.getValue().get(0) != null){
        	
        		String searchTypes = "";
        		for (int i = 0; i < searchFormField.getValue().size(); i++) {
        	        if (i != 0) { searchTypes += " "; }
        	        searchTypes += searchFormField.getValue().get(i);
        	    }
        		
	            String[] tokens = new StrTokenizer(searchTypes, StrMatcher.spaceMatcher(), StrMatcher.quoteMatcher()).getTokenArray();
	          
	            // If there is more than one Solr field bound to form field
	            // include the term as AND (field:value OR field:value ...)
	            // otherwise just include it as simple AND statement.
	            
	            // Multiple Solr fields bound
	            if (searchFormField.getSolrFields().size() > 1) {
	                String innerQuery = " AND (";
	                for (SolrField solrField : searchFormField.getSolrFields()) {
	                    // Multiple tokens: add each token as separate OR-condition unless they're quoted
	                    if (tokens.length > 1) {
	                        for (String token : tokens) {
	                            innerQuery += " OR " + solrField.getName() + ":" + token;
	                        }
	                  
	                        // Single token 
	                    } else {
	                        innerQuery += " OR " + solrField.getName() + ":" + tokens[0];
	                    }
	                }
	                innerQuery = innerQuery.replaceFirst(" OR", "");
	                innerQuery += ")";
	                query += innerQuery;
	          
	                // Single Solr field bound
	            } else {
	              
	                // Multiple tokens: add each token as separate OR-condition unless they're quoted
	                if (tokens.length > 1 
	                    && searchFormField.getType() != SearchFormField.Type.date ) {
	                  
	                	switch (searchFormField.getName()) {
	                		case "archiveClass":
		                        String innerQuery = " AND " + searchFormField.getName() + ":("; 
		                        for (String token : tokens) {               
		                            innerQuery += " " + token;
		                        } 
		                        innerQuery += ")";
		                        query += innerQuery;	                			
	                			break; 
	                			
	                		case SOLRFIELD_Type:
	                			query += " AND (";
	                			int tempCounter = 0;
		                        for (String token : tokens) {
		                        	if (tempCounter == 0) { 
		                        		query += searchFormField.getName() + ":" + token;
		                        	} else {
		                        		query += " OR " + searchFormField.getName() + ":" + token;
		                        	}
		                        	tempCounter++;
		                        }
		                        query += ")";
	                			break;
	                	
	                		default:
		                        for (String token : tokens) {               
		                            query += " AND " + searchFormField.getName() + ":" + token;
		                        } 	                			
	                			break;
	                	}
	              
	                    // Single token
	                } else if (searchFormField.getType() != SearchFormField.Type.date ) {
	                    query += " AND " + searchFormField.getName() + ":" + tokens[0];
	              
	                    // Date range: special case with multiple token
	                } else if (searchFormField.getType() == SearchFormField.Type.date) {
	                    // Create range query for date fields
	                    SimpleDateFormat simpleFormat = new SimpleDateFormat("d.M.yyyy");
	                    Date low = null;
	                    Date high = null;
	                    Calendar calendar = Calendar.getInstance();
	                    try {
	                        low = simpleFormat.parse(searchFormField.getValue().get(0));
	                        calendar.setTime(low);
	                        low = calendar.getTime();
	                        high = simpleFormat.parse(searchFormField.getValue().get(1));
	                        calendar.setTime(high);
	                        calendar.add(Calendar.HOUR, +23); 
	                        calendar.add(Calendar.MINUTE, +59);
	                        calendar.add(Calendar.SECOND, +59);
	                        high = calendar.getTime();                      
	                    } catch (ParseException e) {
	                        logger.error("Date parse error", e);
	                    }
	                    simpleFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
	                    
	                    query += " AND " + searchFormField.getName() + ":[" + simpleFormat.format(low) + " TO " + simpleFormat.format(high) + "]";
	                }
	            }
        	}
        }

        // TODO Query fields  
        Vector<String> queryFields = new Vector<String>();
        for(SolrField searchFormField : search.getFreetextSolrFields())
        {
            queryFields.add(searchFormField.getName());
        }
        
        if (false) {  // TODO partial matches
            queryFields.add("*_ngram");          
        }
        if (false) {  // TODO content search
            queryFields.add("content");
        }        
        
        Vector<String> facetFields = new Vector<String>();
        facetFields = search.getFacetFields();
        
        // Filters
        HashMap<String, String> filters = search.getFilters();
     
        // Facets
        for (String facetName : search.getFacetFilters().keySet()) {
            String values = "(";
            String prefix = "+";
            for (String facetValue : search.getFacetFilters().get(facetName)) {
                if ("empty".equals(facetValue)) {
                    values += "[\"\" TO *]";
                    filters.remove("+" + facetName);
                    prefix = "-";
                    break;
                } else {
                    values +=  " AND \"" + facetValue + "\"" ;                  
                }
              
            }
            values += ")";
            if (!values.equals("()")) {
                filters.put(prefix + facetName, values.replaceFirst(" AND ", ""));
            }
        } 

        // Fetch only objects with a type (to exclude content models).
        if (!filters.containsKey(SOLRFIELD_Type)) {
            filters.put(SOLRFIELD_Type, "*");        
        }
        
        // Add context
        filters.put(SOLRFIELD_Pid, organization.getName().toUpperCase()+"\\:*");
        
        // Access rights
        String accessRights = parseAccessRights(user);        
        
        // Query with Solr
        QueryResponse response = solr.query(query, queryFields, filters, accessRights, facetFields, search.getStart(), search.getRows(), search.getSorting());
        SolrDocumentList results = response.getResults();
        search.setQueryTime(response.getQTime());
        search.setTotalSize(results.getNumFound());
	    
        // Parse SolrDocumentList into an ArrayList of HashMaps.
        HashMap<String, String> ancestorPids = new HashMap<String, String>();
        for (SolrDocument doc : results) {
            HashMap<String, Object> row = new HashMap<String, Object>();
                
            // Add columns to row
            for (String key : doc.keySet()) {                               
                row.put(key, doc.get(key));
            }
            SearchResult result = new SearchResult(row);
            search.getResults().add(result);
            if (doc.get("m.isAncestor") != null ) { 
                ancestorPids.put((String) doc.get("m.isAncestor"), null); 
            }
        }

        // Add ancestor titles from pids        
        if (!ancestorPids.isEmpty()) {
            ancestorPids = getAncestorTitles(ancestorPids);
            for (SearchResult result : search.getResults()) {
                result.setAncestorTitle(ancestorPids.get(result.getMetadataFields().get("m.isAncestor")));
            }
        }             
        
		// Parse facets
		search.getFacets().clear();
		Iterator<FacetField> facetsIterator = response.getFacetFields().iterator();
		while (facetsIterator.hasNext()) {
			FacetField facetField = facetsIterator.next();
			if (facetField.getValues() == null) {
				throw new NullPointerException("FacetField values are null");
			}

			// Add facet content
			Iterator<Count> facetValueIterator = facetField.getValues().iterator();
			
			while (facetValueIterator.hasNext()) {
				Count count = facetValueIterator.next();
				if (count.getName() == null) {
					// cases where 'empty' is not allowed
					if ("archiveClass".equals(facetField.getName()) 
							|| "format".equals(facetField.getName())
							|| "type".equals(facetField.getName())) {
					// Add special facet values
					} else if (count.getCount() > 0) {							
						LinkedHashMap<String, Integer> facet = search.getFacets().get(facetField.getName());
						if (facet == null) {
							facet = new LinkedHashMap<String, Integer>();
						}
						facet.put("empty", new Integer((int) count.getCount()));
						search.getFacets().put(facetField.getName(), facet);
					}
				} else  {
					LinkedHashMap<String, Integer> facet = search.getFacets().get(facetField.getName());
					if (facet == null) {
						facet = new LinkedHashMap<String, Integer>();
					}
					facet.put(count.getName(), new Integer((int) count.getCount()));
					search.getFacets().put(facetField.getName(), facet);					
				}
			}        
		}
        return search;
	} 
    
    private HashMap<String, String> getAncestorTitles(HashMap<String, String> pids) {
        if (pids == null || pids.isEmpty()) {
            return null;
        }
        
        String pidList = "";
        for (String pid : pids.keySet()) {
            pidList += " PID:" + pid;
        }
        
        QueryResponse response = solr.query(pidList, new Vector<String>(), new HashMap<String, String>(), "", new Vector<String>(), -1, Integer.MAX_VALUE, "score desc");
        SolrDocumentList results = response.getResults();
        
        // Parse SolrDocumentList into an ArrayList of HashMaps.
        for (SolrDocument doc : results) {          
            pids.put((String)doc.get(SOLRFIELD_Pid),(String) doc.get(SOLRFIELD_Title));
        }

        return pids;
    }       
    
    // Combine filters with OR. Combine rules inside a filter with AND.
    public String parseAccessRights(User user) {
        String accessRights = "";
        Vector<HashMap<String, String>> acRightFilters = user.getAccessRightFilters();
        if (acRightFilters.size() == 0) { // This is an error case. Deny all access rights.
            accessRights = "-*:*";
        } 
        for (int i = 0; i < acRightFilters.size(); i++) {
            if (i == 0 && acRightFilters.size() > 1) {
                accessRights += "(";
            }
            
            HashMap<String, String> acFilter = acRightFilters.get(i);
            if (i >= 1) {
                accessRights += " OR (";
            }
    
            int index = 0;
            for (String key : acFilter.keySet()) {
                accessRights += key + ":" + acFilter.get(key).replace(":", "\\:");
                if (index < (acFilter.keySet().size()-1)) {
                    accessRights += " AND ";
                }
                index++;
            }
            
            if (acRightFilters.size() > 1) {
                accessRights += ")";
            }
        }
        
        return accessRights;
    }

    static public String convertToString(Object value, String solrFieldName) {
        String result = null;
        
        if (value == null) {
            result = null;
        } else if (value instanceof String) {
            result = (String)value;
        } else  if (value instanceof String[]) {
            String[] values = (String[])value;
            if (values.length == 0) {
                result = null;
            } else if (values.length == 1) {
                result = values[0];
            } else {
                logger.error("SolrManager:convertToString() "+solrFieldName+", "+values.length);
            }
        } else if (value instanceof Collection) {
            Collection c = (Collection)value;
            if (c.isEmpty()) {
                result = null;
            } else if (c.size() == 1) {
                Object o = c.iterator().next();
                result = convertToString(o, solrFieldName);
            } else {
                logger.error("SolrManager:convertToString() "+solrFieldName+", "+c.size());                
            }
        } else {
            logger.error("SolrManager:convertToString() "+solrFieldName+", "+value.getClass().getName());
        }
        return result;
    }
    
} public class WorkflowManager {
    
    SosweClient sosweClient = null;
    
    private static final Logger logger = Logger.getLogger(WorkflowManager.class);
    
    public static final String WORKFLOW_PREINGEST = "Pre-ingest";
    public static final String WORKFLOW_BATCHINGEST = "Batch-ingest";
    public static final String WORKFLOW_ZIP = "Zip";
    
    public static final String OPT_FILENAME  = "filename";
    public static final String OPT_USERNAME  = "username";
    public static final String OPT_USERMAIL  = "usermail";
    public static final String OPT_ORG       = "organization";
    public static final String OPT_IMPORTDIR = "importdirectory";
    public static final String OPT_UPLOADDIR = "uploaddirectory";
    public static final String OPT_INGESTDIR = "ingestdirectory";
    public static final String OPT_FAILEDDIR = "faileddirectory";
    
    public WorkflowManager(String host, String port) throws UnknownHostException {
        int iPort = Integer.parseInt(port);
        sosweClient = new SosweClient(host, iPort);            
    }
    
    public boolean startWorkflow(String workflowname, Map<String, Object> opts) throws ClientHandlerException {
        boolean retVal = true;
        String answer = sosweClient.startWorkflow(workflowname, opts);      
        return retVal;
    }
    
    public String getStatus(String workflow, String fileName) {
        String statusMap = sosweClient.getStatus();
        String status = "";
        HashMap<String, String> values = new JSONDeserializer<HashMap<String, String>>().deserialize(statusMap);
        status = values.get(fileName);

        return status;
    }

    public String getWorkflows() {
        String workflows = "";
        try {
            workflows = sosweClient.getWorkflows("application/json");

        } catch (TypeNotSupportedException e) {
            logger.error("WorkflowManager:getWorkflows error: "+e);
        }
        return workflows;
    }
    
    public static String mapToString(Map<String, MetaDataElement> map) {
        StringBuilder stringBuilder = new StringBuilder();
         
        for (String key : map.keySet()) {
            if (stringBuilder.length() > 0) {
                stringBuilder.append("&");
            }
            String value = map.get(key).toString();
            try {
                stringBuilder.append((key != null ? URLEncoder.encode(key, "UTF-8") : ""));
                stringBuilder.append("=");
                stringBuilder.append(value != null ? URLEncoder.encode(value, "UTF-8") : "");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("This method requires UTF-8 encoding support", e);
            }
        }
        return stringBuilder.toString();
    }
    
}
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import fi.mamk.osa.auth.AuthenticationManager;
import fi.mamk.osa.auth.LdapManager;
import fi.mamk.osa.database.DatabaseManager;
import fi.mamk.osa.database.mongo.MongoManager;
import fi.mamk.osa.database.sql.DatabaseConfiguration;
import fi.mamk.osa.database.sql.mysql.ConnectionManager;
import fi.mamk.osa.database.sql.mysql.MySQLManager;
import fi.mamk.osa.fedora.Fedora3Manager;
import fi.mamk.osa.fedora.RepositoryManager;
import fi.mamk.osa.scheduler.TaskScheduler;
import fi.mamk.osa.search.SearchManager;
import fi.mamk.osa.solr.SolrManager;
import fi.mamk.osa.workflow.WorkflowManager;
package fi.mamk.osa.core;





/**
 * osa servlet class
 */
public class Osa extends GenericServlet
{
    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(Osa.class);

    public static AuthenticationManager authManager;

    public static HashMap<String, DatabaseManager> dbManager = new HashMap<String, DatabaseManager>();
    public static SearchManager searchManager;
    public static RepositoryManager fedoraManager;
    public static WorkflowManager workflowManager;
    public static TaskScheduler taskScheduler;
    
    public static String persistence;
    public static String systemDatabase;
    public static String documentDatabase;
    public static String authentication;
    public static String search;
    public static String logging;
    public static String defaultConfiguration;
    public static String defaultRoot;
    public static String configFolder;
    public static String onkiAccessKey;
    
    public static String tempDirectory;
    public static String dataDirectory;
    public static String logDirectory;
    public static String importDirectory; 
    public static String ingestDirectory;
    public static String failedDirectory;
    public static String uploadDirectory;
    
    // TODO: move to database manager class
    public static DatabaseConfiguration dbConfiguration;