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 FormElement implements Serializable {

    private static final long serialVersionUID = -3756519340228830695L;

    // Field types
    public static enum FieldType 
    {
        undefined,
        checkbox,
        date,
        hidden,
        link,
        linked,
        nested,
        radio,
        relation,
        select,
        textarea,
        textfield,
        timerange,
        units;
        
        public static FieldType getEnum(String s)
        {
            if (checkbox.name().equalsIgnoreCase(s))
                return checkbox;
            else if (date.name().equalsIgnoreCase(s))
                return date;
            else if (link.name().equalsIgnoreCase(s))
                return link;
            else if (linked.name().equalsIgnoreCase(s))
                return linked;
            else if (radio.name().equalsIgnoreCase(s))
                return radio;
            else if (relation.name().equalsIgnoreCase(s))
                return relation;
            else if (select.name().equalsIgnoreCase(s))
                return select;
            else if (textarea.name().equalsIgnoreCase(s))
                return textarea;
            else if (textfield.name().equalsIgnoreCase(s))
                return textfield;
            else if (timerange.name().equalsIgnoreCase(s))
                return timerange;
            else if (nested.name().equalsIgnoreCase(s))
                return nested;
            else if (units.name().equalsIgnoreCase(s))
                return units;
            else if (hidden.name().equalsIgnoreCase(s))
                return hidden;
            throw new IllegalArgumentException("No Enum specified for this string");
        }
    }
    
    public static final String MULTIVALUE_DELIMITED  = "delimited";
    public static final String MULTIVALUE_MULTIFIELD = "multifield";
    public static final String MULTIVALUE_NONE 		 = "false";
    
    // Element name
    public String name;
    // Form where element belongs to
    private String form;
    // Tab of the form
    private String tab;
    
    // Value for element
    private String value;
    
    // Field type
    private FieldType fieldType;
    // Enumerated values for field
    List<String> enumValues;
    // Is value automatically generated
    public boolean generated;
    // Is element readonly
    public boolean readonly;
    // Is element value mandatory
    private boolean required;
    // Multivalue types: FormElement.MULTIVALUE_DELIMITED / FormElement.MULTIVALUE_MULTIFIELD / FormElement.MULTIVALUE_NONE
    private String multivalue;
    // Name of the ontology, if used in this field
    private String source;
    private String nestingStyle;
    
    private Vector<FormElement> nestedFormElements = new Vector<FormElement>();
    
    // Default constructor
    public FormElement()
    {
        this.name = "";
        this.form = "";
        this.tab = "";
        this.fieldType = FieldType.undefined;
        this.enumValues = null;
        this.generated = false;
        this.readonly = true;
        this.required = false;
        this.multivalue = FormElement.MULTIVALUE_NONE;
        this.source = null;
        this.nestingStyle = null;
    }
    
    // Constructor
    public FormElement(String name, 
            String form, 
            String tab, 
            FieldType fieldType, 
            List<String> enumValues, 
            boolean readonly, 
            boolean generated, 
            boolean required,
            String multivalue,
            String source,
            String nestingStyle,
            Vector<FormElement> nestedElements)
    {
        this.name = name;
        this.form = form;
        this.tab = tab;
        this.fieldType = fieldType;
        this.enumValues = enumValues;
        this.readonly = readonly;
        this.generated = generated;
        this.required = required;
        this.multivalue = multivalue;
        this.source = source;
        this.nestingStyle = nestingStyle;
        
        if (required == true) {
        	this.required = true;
        } else {
        	this.required = false;
        }
        
        this.nestedFormElements = nestedElements;
    }
    
    public FieldType getFieldType() {
        return this.fieldType;
    }
    
    public void setFieldType(FieldType value) {
        this.fieldType = value;
    }
    
    public List<String> getEnumValues() {
        return this.enumValues;
    }
    
    public void setEnumValues(List<String> value) {
        this.enumValues = value;
    }
    
    public String getForm() {
        return this.form;
    }
    
    public void setForm(String value) {
        this.form = value;
    }
    
    public boolean getGenerated() {
        return this.generated;
    }
    
    public void setGenerated(boolean value) {
        this.generated = value;
    } 

    public String getName() {
        return this.name;
    }
    
    public void setName(String value) {
        this.name = value;
    }
    
    public String getTab() {
        return this.tab;
    }
    
    public void setTab(String value) {
        this.tab = value;
    }
    
    public boolean getReadonly() {
        return this.readonly;
    }
    
    public void setReadonly(boolean value) {
        this.readonly = value;
    } 
    
    public String getValue() {
        return this.value;
    }
    
    public void setValue(String value) {
        this.value = value;
    }

	public boolean isRequired() {
		return required;
	}

	public void setRequired(boolean required) {
		this.required = required;
	}

    public String getMultivalue() {
        return multivalue;
    }

    public void setMultivalue(String multivalue) {
        this.multivalue = multivalue;
    }

    public Vector<FormElement> getNestedFormElements() {
        return nestedFormElements;
    }

    public void setNestedFormElements(Vector<FormElement> nestedFormElements) {
        this.nestedFormElements = nestedFormElements;
    }
    
    public String getSource() {
        return this.source;
    }
    
    public void setSource(String value) {
        this.source = value;
    }

	public String getNestingStyle() {
		return nestingStyle;
	}

	public void setNestingStyle(String nestingStyle) {
		this.nestingStyle = nestingStyle;
	}
} public class Gui implements Serializable {

    private static final long serialVersionUID = -6157579742585725399L;
    private static final Logger logger = Logger.getLogger(Gui.class);
    
    // Form variables
    public static final String FORM_SEARCH         = "search";
    public static final String DEFAULT_MAIN_HEADER = "/layout/osa_header_main.jsp";
    public static final String DEFAULT_SUB_HEADER  = "/layout/osa_header.jsp";
    public static final String DEFAULT_APP_TITLE   = "OSA Web Client";
    
    // Layout
    private String cssFile 	   = null;
    private String logoFile    = null;
    private String logoFileAlt = null;
    private String appTitle    = DEFAULT_APP_TITLE;
    private String mainHeader  = DEFAULT_MAIN_HEADER;
    private String subHeader   = DEFAULT_SUB_HEADER;
    
    private String resultType  = "";
    private int queueSize;
    private int initialBasecollectionId = 0;
    
    // map between FormElement and datastream
    public MultiValueMap mapDataStreams = null;
    
    private SearchConfiguration searchConfiguration = null;    
    private SearchResultConfiguration searchResults = null;
    // contains all FormElements to be shown in GUI
    private FormConfiguration formConfiguration 	= null;
    
    // Disposal
    private String disposeMethod = "";
    private boolean disposeChilds = false;
    
    public Gui(File xmlConfiguration, OsaActionBeanContext context) {
        
        mapDataStreams = new MultiValueMap();
        searchConfiguration = new SearchConfiguration();
        
        try {
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
            Document doc = docBuilder.parse(xmlConfiguration);
            doc.getDocumentElement().normalize();
            getIngestForms(doc);
            getSearchForm(doc);
            getResultLayout(doc);
            getLayout(doc, context);
            getUploadConfig(doc);
            getIdGenerationConfig(doc);
            getFacetFieldsFromConf(doc);
            getFacetFieldsforAdvBrowse(doc);
        } catch (Exception e) {
            logger.error("Failed to load configuration file: " + xmlConfiguration+", "+ e);
        }
    }

    /**
     * Returns all datastreams for the form element name
     * Reads <metadata> tags in osa-system:contentmodel-*
     * @param name      name of the form element
     * @return          list of datastreams
     */
    public List<String> getDataStreamMappings(String name) {
        List<String> retValue = new ArrayList<String>();
        List<MetaDataElement> list;
        Set entrySet = this.mapDataStreams.entrySet();
        Iterator it = entrySet.iterator();
               
        while (it.hasNext()) {
            Map.Entry mapEntry = (Map.Entry) it.next();
            list = (List<MetaDataElement>) this.mapDataStreams.get(mapEntry.getKey());
                        
            for (int i=0; i<list.size(); i++) {
                if (name.equalsIgnoreCase((String) mapEntry.getKey())) {
                    MetaDataElement el = (MetaDataElement)list.get(i);
                    retValue.add(el.getDatastream());
                }
            }
        }
        
        return retValue;
    }
    
    /**
     * Find right name for element according to name in form (keyName)
     * @param keyName           element visible name in form
     * @param elementName       element name
     * @param dataStream        datastream name
     * @return
     */
    public String getMetadataName(String elementName, String dataStream) {
        String retValue = null;
        List<MetaDataElement> list;
        Set entrySet = this.mapDataStreams.entrySet();
        Iterator it = entrySet.iterator();
               
        while (it.hasNext()) {
            Map.Entry mapEntry = (Map.Entry) it.next();
            list = (List<MetaDataElement>) this.mapDataStreams.get(mapEntry.getKey());
            
            for (int i=0; i<list.size(); i++) {
                MetaDataElement el = (MetaDataElement)list.get(i);
                if (el.getDatastream().equalsIgnoreCase(dataStream) && el.getVisibleName().equalsIgnoreCase(elementName)) {
                    retValue = (String) mapEntry.getKey();
                    break;
                }
            }
        }
        
        return retValue;
    }
    
    /**
     * Find attribute visible name used in datastream
     * @param name          attribute name
     * @param dataStream    datastream name
     * @return              visible name
     */
    public String getVisibleName(String name, String dataStream) {
        String retValue = null;
        List<MetaDataElement> list;
        Set entrySet = this.mapDataStreams.entrySet();
        Iterator it = entrySet.iterator();
        
        while (it.hasNext()) {
            Map.Entry mapEntry = (Map.Entry) it.next();
            list = (List<MetaDataElement>) this.mapDataStreams.get(mapEntry.getKey());
                        
            for (int i=0; i<list.size(); i++) {
                if (name.equalsIgnoreCase((String) mapEntry.getKey())) {
                    MetaDataElement el = (MetaDataElement)list.get(i);
                    
                    if (el.getDatastream().equalsIgnoreCase(FedoraBean.DATASTREAM_CAPTURE)) {
                        retValue = el.getVisibleName();
                    }
                }
            }
        }
        
        return retValue;
    }
    
    /**
     * Get GUI elements from xml configuration file
     * @param doc           document
     * @param elementName   name of an element
     */
    public void getElements(Document doc, String elementName, String form) {
        
        String formName = null;
        String tabName = null;
        
        if (form.equalsIgnoreCase(FORM_SEARCH)) {
            NodeList searchNodes = doc.getElementsByTagName("search");
            for (int i=0; i<searchNodes.getLength(); i++) {
                Node searchNode = searchNodes.item(i);
                if (searchNode.getNodeType() == Node.ELEMENT_NODE) {
                    NodeList formNodes = searchNode.getChildNodes();
                    for (int j=0; j<formNodes.getLength(); j++) {
                        Node formNode = formNodes.item(j);
                        if (formNode.getNodeType() == Node.ELEMENT_NODE) {
                            NodeList fieldNodes = formNode.getChildNodes();
                            for (int k=0; k<fieldNodes.getLength(); k++) {
                                Node fieldNode = fieldNodes.item(k);
                                if (fieldNode.getNodeType() == Node.ELEMENT_NODE) {
                                    if (fieldNode.getNodeName().equals("field")) {
                                        this.parseFieldNode(fieldNode, form, null, null);
                                    } else {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            
        } else {
            try {
                NodeList formsNodes = doc.getElementsByTagName("forms");
                for (int i=0; i<formsNodes.getLength(); i++) {
                    Node formsNode = formsNodes.item(i);
                    if (formsNode.getNodeType() == Node.ELEMENT_NODE) {
                        NodeList formNodes = formsNode.getChildNodes();
                        for (int j=0; j<formNodes.getLength(); j++) {
                            Node formNode = formNodes.item(j);
                            if (formNode.getNodeType() == Node.ELEMENT_NODE) {
                                formName = formNode.getAttributes().getNamedItem("name").getNodeValue();
                                // tabs-list
                                NodeList tabsNodes = formNode.getChildNodes();
                                for (int k=0; k<tabsNodes.getLength(); k++) {
                                    // tabs
                                    Node tabsNode = tabsNodes.item(k);
                                    if (tabsNode.getNodeType() == Node.ELEMENT_NODE) {
                                        // tab-list
                                        NodeList tabNodes = tabsNode.getChildNodes();
                                        for (int l=0; l<tabNodes.getLength(); l++) {
                                            // tab
                                            Node tabNode = tabNodes.item(l);
                                            if (tabNode.getNodeType() == Node.ELEMENT_NODE) {
                                                tabName = tabNode.getAttributes().getNamedItem("name").getNodeValue();
                                                formConfiguration.setTab(formName, tabName);
                                                // field-list
                                                NodeList fieldNodes = tabNode.getChildNodes();
                                                for (int m=0; m<fieldNodes.getLength(); m++) {
                                                    // field
                                                    Node fieldNode = fieldNodes.item(m);
                                                    if (fieldNode.getNodeType() == Node.ELEMENT_NODE) {
                                                        if (fieldNode.getNodeName().equals("field")) {
                                                            this.parseFieldNode(fieldNode, form, formName, tabName);
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                
            } catch (Exception e) {
                logger.error("Gui:getElements() "+e);
            }
        }
    }

    public void parseFieldNode(Node node, String form, String formName, String tabName) {
        
        if (node.getNodeType() != Node.ELEMENT_NODE) {
            return;
        }
        
        if (form.equalsIgnoreCase(FORM_SEARCH)) {
            // Search fields
            SearchFormField searchFormField = new SearchFormField();
            
            try {
                searchFormField.setName(node.getAttributes().getNamedItem("name").getNodeValue());
                searchFormField.setType(SearchFormField.Type.valueOf(node.getAttributes().getNamedItem("type").getNodeValue()));
            }
            catch (Exception ex) {
                logger.error(ex);
            }
            
            // Map visible search fields to backend search fields.
            // In this case Solr is the search provider, 
            // and fields from Solr schema are mapped via the organization configuration.
            NodeList subNodes = node.getChildNodes();
            for (int j=0; j<subNodes.getLength(); j++) {
                Node subNode = subNodes.item(j);
                if (subNode.getNodeType() == Node.ELEMENT_NODE) {
                    // Solr fields bound to the search field
                    if ("solr".equalsIgnoreCase(subNode.getNodeName())) {
                        NodeList solrFieldNodes = subNode.getChildNodes();
                        for (int k=0; k<solrFieldNodes.getLength(); k++) {
                            Node solrFieldNode = solrFieldNodes.item(k);
                            if (solrFieldNode.getNodeType() == Node.ELEMENT_NODE) {
                                SolrField solrField = new SolrField();
                                solrField.setName(solrFieldNode.getAttributes().getNamedItem("name").getNodeValue());
                                if (solrFieldNode.getAttributes().getNamedItem("boost") != null) {
                                    solrField.setBoost(Integer.parseInt(solrFieldNode.getAttributes().getNamedItem("boost").getNodeValue()));
                                }
                                if (solrFieldNode.getAttributes().getNamedItem("ngramBoost") != null) {
                                    solrField.setNgramBoost(Integer.parseInt(solrFieldNode.getAttributes().getNamedItem("ngramBoost").getNodeValue()));
                                }
                                // Read solr fields bound to freetext field in conf file 
                                if ("freetext".equalsIgnoreCase(subNode.getParentNode().getAttributes().getNamedItem("name").getNodeValue())) {
                                    this.searchConfiguration.getFreetextField().add(solrField);
                                } else {
                                    // Solr fields bound to rest of the fields
                                    searchFormField.getSolrFields().add(solrField);
                                }
                                
                            }
                        }
                    }
                    if ("subFields".equalsIgnoreCase(subNode.getNodeName())) {

                        NodeList subNodes2 = subNode.getChildNodes();
                        for (int l=0; l<subNodes2.getLength(); l++) {
                            Node subNode2 = subNodes2.item(l);
                            if (subNode2.getNodeType() == Node.ELEMENT_NODE) {
                                
                                SearchFormField subField = new SearchFormField();
                                subField.setName(subNode2.getAttributes().getNamedItem("name").getNodeValue());
                                subField.setType(SearchFormField.Type.valueOf(subNode2.getAttributes().getNamedItem("type").getNodeValue()));
                                
                                // Solr fields bound to the search field
                                if ("solr".equalsIgnoreCase(subNode2.getNodeName())) {
                                    NodeList solrFieldNodes = subNode2.getChildNodes();
                                    for (int m=0; m<solrFieldNodes.getLength(); m++) {
                                        Node solrFieldNode = solrFieldNodes.item(m);
                                        if (solrFieldNode.getNodeType() == Node.ELEMENT_NODE) {
                                            SolrField solrField = new SolrField();
                                            solrField.setName(solrFieldNode.getAttributes().getNamedItem("name").getNodeValue());
                                            if (solrFieldNode.getAttributes().getNamedItem("boost") != null) {
                                                solrField.setBoost(Integer.parseInt(solrFieldNode.getAttributes().getNamedItem("boost").getNodeValue()));
                                            }
                                            if (solrFieldNode.getAttributes().getNamedItem("ngramBoost") != null) {
                                                solrField.setNgramBoost(Integer.parseInt(solrFieldNode.getAttributes().getNamedItem("ngramBoost").getNodeValue()));
                                            }
                                            subField.getSolrFields().add(solrField);
                                        }
                                    }
                                }
                                searchFormField.getSubFields().put(subField.getName(), subField);
                            }
                        }
                    }
                }
            }
            // Add parsed field information to search configuration wrapper
            searchConfiguration.getSearchForm().put(searchFormField.getName(), searchFormField);
            
        } else {    // All other forms
            String name = null;
            FormElement.FieldType fieldType = FormElement.FieldType.undefined;
            List<String> enumValues = null;
            boolean isReadonly = false;
            boolean isGenereted = false;
            boolean isRequired = false;
            String multivalue = "false";
            String source = null;
            String nestingStyle = null;
            Vector<FormElement> nestedElements = new Vector<FormElement>();
            
            // current element name
            name = node.getAttributes().getNamedItem("element").getNodeValue();
            
            if (node.getAttributes().getNamedItem("readonly") != null) {
                isReadonly = Boolean.parseBoolean(node.getAttributes().getNamedItem("readonly").getNodeValue());
            }
            if (node.getAttributes().getNamedItem("generated") != null) {
                isGenereted = Boolean.parseBoolean(node.getAttributes().getNamedItem("generated").getNodeValue());
            }
            if (node.getAttributes().getNamedItem("required") != null) {
                isRequired = Boolean.parseBoolean(node.getAttributes().getNamedItem("required").getNodeValue());
            }
            if (node.getAttributes().getNamedItem("type") != null) {
                fieldType = FormElement.FieldType.getEnum(node.getAttributes().getNamedItem("type").getNodeValue());
                
                if (fieldType.equals(FormElement.FieldType.select) || fieldType.equals(FormElement.FieldType.units)) {
                    // read enum values
                    enumValues = getOptionsList(node, "item");
                    
                } else if (fieldType.equals(FormElement.FieldType.nested) 
                           || fieldType.equals(FormElement.FieldType.timerange)
                           || fieldType.equals(FormElement.FieldType.linked)) {
                    
                	if (node.getAttributes().getNamedItem("nestingStyle") != null) {
                		nestingStyle = node.getAttributes().getNamedItem("nestingStyle").getNodeValue();
                	}
                	
                    NodeList nestedFields = node.getChildNodes();
                    for (int j=0; j<nestedFields.getLength(); j++) {
                        Node nestedNode = nestedFields.item(j);
                        if (nestedNode.getNodeType() == Node.ELEMENT_NODE) {
                            FormElement nestedElement = new FormElement(); 
                            nestedElement.setName(nestedNode.getAttributes().getNamedItem("element").getNodeValue());
                            nestedElement.setReadonly(Boolean.parseBoolean(nestedNode.getAttributes().getNamedItem("readonly").getNodeValue()));
                            nestedElement.setRequired(Boolean.parseBoolean(nestedNode.getAttributes().getNamedItem("required").getNodeValue()));
                            nestedElement.setFieldType(FormElement.FieldType.getEnum(nestedNode.getAttributes().getNamedItem("type").getNodeValue()));
                            if (nestedNode.getAttributes().getNamedItem("type").getNodeValue().equalsIgnoreCase(FormElement.FieldType.select.toString())) {
                                nestedElement.setEnumValues(getOptionsList(nestedNode, "item"));
                            }
                            nestedElements.add(nestedElement);
                        }
                    }
                    
                }
            }
            if (node.getAttributes().getNamedItem("multivalue") != null) {
                
                switch (node.getAttributes().getNamedItem("multivalue").getNodeValue()) {
                    case FormElement.MULTIVALUE_DELIMITED:
                        multivalue = FormElement.MULTIVALUE_DELIMITED;
                        // do something
                        break;
                    case FormElement.MULTIVALUE_MULTIFIELD:
                        multivalue = FormElement.MULTIVALUE_MULTIFIELD;
                        // do something
                        break;
                    default:
                        multivalue = FormElement.MULTIVALUE_NONE; 
                        break;
                }
            }
            if (node.getAttributes().getNamedItem("source") != null) {
                source = node.getAttributes().getNamedItem("source").getNodeValue();
            }
            
            FormElement formElement = new FormElement(name, 
                                                      formName, 
                                                      tabName, 
                                                      fieldType, 
                                                      enumValues, 
                                                      isReadonly, 
                                                      isGenereted, 
                                                      isRequired, 
                                                      multivalue, 
                                                      source, 
                                                      nestingStyle,
                                                      nestedElements);

            formConfiguration.setFormElement(formName, formElement);
        }
    }
    
    /**
     * Get elements to be shown in the form
     * @param formName      name of the form
     * @return              vector of elements
     */
    public LinkedHashMap<String, FormElement> getGUIElements(String formName) {
        return this.formConfiguration.getForm(formName);
    }
    
    /**
     * Get tabs to be shown in the form
     * @param formName      name of the form
     * @return              vector of tab names
     */
    public Vector<String> getGUITabs(String formName) {
        return this.formConfiguration.getTabs(formName);
    }
    
    /**
     * Get enumerated items for field element
     * @param node      field element node
     * @return          list of enumerated items
     */
    private List<String> getOptionsList(Node node, String nodeName) {
        List<String> itemList = new ArrayList<String>();
        
        if (node != null) {
            NodeList nodes = node.getChildNodes();
            
            for (int i=0; i<nodes.getLength(); i++) {
                Node currentNode = nodes.item(i);
                if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
                    // check node name
                    if (currentNode.getNodeName().equalsIgnoreCase(nodeName)) {
                        if (!currentNode.getTextContent().isEmpty()) {
                            itemList.add(currentNode.getTextContent());
                        }
                    } else if (currentNode.getNodeName().equalsIgnoreCase("authoritative_list")) {
                        return getOptionsList(currentNode, nodeName);
                    }
                }
            }
            
        }
        return itemList;
    }

    /**
     * Find ParentNode by type
     * @param node      current node
     * @param nodeType  type of node (f.ex. form, tab, etc.)
     * @return
     * @throws Exception 
     */
    private Node getParentNode(Node node, String nodeType)
    {
        Node parentNode = null;
        if (node.getParentNode() != null) {
            parentNode = node.getParentNode();
            String parentNodeType = node.getParentNode().getNodeName();

            // check if types match
            if (nodeType != parentNodeType) {
                return getParentNode(parentNode, nodeType);
            }
            
        } else {
            parentNode = null;
        }

        return parentNode;
    }
    
    public void getLayout(Document doc, OsaActionBeanContext context) {
        try {
        	NodeList layoutSettings = doc.getElementsByTagName("layout");
        	for (int i=0; i<layoutSettings.getLength(); i++){
        		Node item = layoutSettings.item(i);
        		if (item.getNodeType() == Node.ELEMENT_NODE){
        			NodeList childNodes = item.getChildNodes();
        			for (int j=0; j<childNodes.getLength(); j++) {
        				Node subItem = childNodes.item(j);
        				if (subItem.getNodeName().equals("cssFile")) {
        					this.setCssFile(subItem.getFirstChild().getNodeValue());
        				}
        				if (subItem.getNodeName().equals("logoFile")) {
        					this.setLogoFile(subItem.getFirstChild().getNodeValue());
        					//TODO do empty check for alt attribute
        					this.setLogoFileAlt(subItem.getAttributes().getNamedItem("alt").getNodeValue());
        				}
        				if (subItem.getNodeName().equals("applicationTitle")) {
        					if (subItem.getFirstChild().getNodeValue() != null) {
        						this.setAppTitle(subItem.getFirstChild().getNodeValue());
        					}
        				}
        				
        				// Organization specific header files
        				if (subItem.getNodeName().equals("headerJsp")) {
        					NodeList headerFiles = subItem.getChildNodes();
                			for (int k = 0; k < headerFiles.getLength(); k++) {
                				Node headerFile = headerFiles.item(k);
                				if (headerFile.getNodeName().equals("mainPage")) {
                					// First check if config has setting for header file
                					if(headerFile.getFirstChild().getNodeValue() != null) {
                						File main = new File(context.getServletContext().getRealPath(headerFile.getFirstChild().getNodeValue()));
                						// Then check if file set in config actually exists
                						if (main.exists() && !main.isDirectory()) {
                							this.setMainHeader(headerFile.getFirstChild().getNodeValue());
                						}
                					}
                				}
                				if (headerFile.getNodeName().equals("subPage")) {
                					// First check if config has setting for header file
                					if(headerFile.getFirstChild().getNodeValue() != null) {
                						File sub = new File(context.getServletContext().getRealPath(headerFile.getFirstChild().getNodeValue()));
                						// Then check if file set in config actually exists
                						if (sub.exists() && !sub.isDirectory()) {
                							this.setSubHeader(headerFile.getFirstChild().getNodeValue());
                						}
                					}
                				}
                			}
        				}
        			}
        		}
        	}
        } catch (Exception e) {
        	
        }
    }
    
    /**
     * Reads upload settings from organization config xml
     * @param doc  document (config xml)
     * @return
     */
    public void getUploadConfig(Document doc) {
        try {
        	NodeList uploadSettings = doc.getElementsByTagName("upload");
        
        	for (int i=0; i<uploadSettings.getLength(); i++) {
        		Node item = uploadSettings.item(i);
        		if (item.getNodeType() == Node.ELEMENT_NODE) {
        			NodeList childNodes = item.getChildNodes();
        			for (int j=0; j<childNodes.getLength(); j++) {
        				Node subItem = childNodes.item(j);
        				if (subItem.getNodeName().equalsIgnoreCase("queue")) {
        					this.setQueueSize(Integer.parseInt(subItem.getAttributes().getNamedItem("size").getNodeValue()));
        				}
        			}			
        		}	
        	}
        	
        } catch(Exception e) {
        	logger.error(e);
        }
    }
    
    /**
     * Reads initial basecollection settings from organization config xml
     * @param doc  document (config xml)
     * @return
     */
    public void getIdGenerationConfig(Document doc) {
        try {
            NodeList uploadSettings = doc.getElementsByTagName("idgeneration");
        
            for (int i=0; i<uploadSettings.getLength(); i++) {
                Node item = uploadSettings.item(i);
                if (item.getNodeType() == Node.ELEMENT_NODE) {
                    NodeList childNodes = item.getChildNodes();
                    for (int j=0; j<childNodes.getLength(); j++) {
                        Node subItem = childNodes.item(j);
                        if (subItem.getNodeName().equalsIgnoreCase("basecollection")) {
                            this.setInitialBasecollectionId(Integer.parseInt(subItem.getAttributes().getNamedItem("id").getNodeValue()));
                        }
                    }           
                }   
            }
            
        } catch(Exception e) {
            logger.error(e);
        }
    }
    
    public void getFacetFieldsFromConf(Document doc) {
        
        Vector<String> facetfields = new Vector<String>();
        try {
        	NodeList facetSettings = doc.getElementsByTagName("facets");
        	
        	for (int i=0; i<facetSettings.getLength(); i++) {
        		Node item = facetSettings.item(i);
        		if (item.getNodeType() == Node.ELEMENT_NODE) {
        			NodeList childNodes = item.getChildNodes();
        			for (int j=0; j<childNodes.getLength(); j++) {
        				Node subItem = childNodes.item(j);
        				if (subItem.getNodeName().equalsIgnoreCase("facetField")) {
        					facetfields.add(subItem.getAttributes().getNamedItem("name").getNodeValue());
        				}
        			}			
        		}	
        	}
        	searchConfiguration.setFacetFields(facetfields);
        
        } catch (Exception e) {
        	logger.error(e);
        }
    }
    
    public void getFacetFieldsforAdvBrowse(Document doc) {
        
        Vector<String> facetfields = new Vector<String>();
        try {
        	NodeList facetSettings = doc.getElementsByTagName("enabledFacets");
        	
        	for (int i=0; i<facetSettings.getLength(); i++) {
        		Node item = facetSettings.item(i);
        		if (item.getNodeType() == Node.ELEMENT_NODE) {
        			NodeList childNodes = item.getChildNodes();
        			for (int j=0; j<childNodes.getLength(); j++) {
        				Node subItem = childNodes.item(j);
        				if (subItem.getNodeName().equalsIgnoreCase("facetField")) {
        					facetfields.add(subItem.getAttributes().getNamedItem("name").getNodeValue());
        				}
        			}			
        		}	
        	}
        	
        	searchConfiguration.setAdvBrowseFacetFields(facetfields);
        
        } catch (Exception e) {
        	logger.error(e);
        }
    }
    
    public List<String> getAllObjects() {
        return this.formConfiguration.getAllForms();
    }
    
    public void getIngestForms(Document doc) {
        try {

            formConfiguration = new FormConfiguration();
            getElements(doc, "field", "ingest");
            // map elements to datastreams
            this.mapMetadataElements(doc);
            
            // check that
            List<String> contentmodels = Osa.fedoraManager.findAllObjects("osa-system:contentmodel-*");
            for (String cm : contentmodels) {
                String formName = cm.replace(RepositoryManager.CONTENTMODEL, "");
                if (formConfiguration.getForm(formName) == null) {
                    logger.error("Gui:getIngestForms() missing definitions for form: "+formName+" in "+doc.getDocumentURI());
                }
            }

        } catch (Exception e) {
            logger.error("failed to read: "+doc.getDocumentURI()+", "+e);
        }
    }
    
    /**
     * Read elements to the search form
     * @param xmlFile   xml file for the organization
     */
    public void getSearchForm(Document doc) {
        try {
            getElements(doc, "field", Gui.FORM_SEARCH);

        } catch (Exception e) {
            logger.error("failed to read: "+doc.getDocumentURI()+", "+e);
        }
    }
    
    public void getResultLayout(Document doc) {
    	try {
    		NodeList nodes = doc.getElementsByTagName("results");
            for (int i=0; i<nodes.getLength(); i++) {
                Node node = nodes.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                	searchResults = new ColumnResult(SearchResultConfiguration.Type.columns);
                	
                	NodeList subNodes = node.getChildNodes(); // results-tag's children
                	for (int j=0; j<subNodes.getLength(); j++) {
                		Node subNode = subNodes.item(j);
                		if (subNode.getNodeType() == Node.ELEMENT_NODE) {
                			if ("resultLayout".equalsIgnoreCase(subNode.getNodeName())) {
                				searchResults.getLayoutTypelist().add(subNode.getAttributes().getNamedItem("type").getNodeValue());
                				if (subNode.getAttributes().getNamedItem("defaultSort") != null) {
                					searchResults.setDefaultSort(subNode.getAttributes().getNamedItem("defaultSort").getNodeValue());
                				}
                				
                				NodeList layoutSubnodes = subNode.getChildNodes(); // results children's children
                            	for (int k=0; k<layoutSubnodes.getLength(); k++) {
                            		Node layoutSubnode = layoutSubnodes.item(k);
                            		if (layoutSubnode.getNodeType() == Node.ELEMENT_NODE) {
                            			NamedNodeMap attributes = layoutSubnode.getAttributes();
                                        Vector<String> headerVector = new Vector<String>();
                                        
                                        String name 	 = attributes.getNamedItem("name").getNodeValue();
                                        String headerkey = attributes.getNamedItem("headerKey").getNodeValue();
                                        String sortable  = attributes.getNamedItem("sortable").getNodeValue();
                                        String width 	 = attributes.getNamedItem("width").getNodeValue();
                                        
                                        headerVector.add(name);
                                        headerVector.add(headerkey);
                                        headerVector.add(sortable);
                                        headerVector.add(width);
                                        
                                        searchResults.getHeaderKeys().add(headerVector);
                                        searchResults.getColumnNames().add(name);
                            		}             		
                            	}
                			}
                			
                  			if ("paging".equalsIgnoreCase(subNode.getNodeName())) {
                				NodeList layoutSubnodes = subNode.getChildNodes();
                            	for (int l=0; l<layoutSubnodes.getLength(); l++) {
                            		Node layoutSubnode = layoutSubnodes.item(l);
                            		if (layoutSubnode.getNodeType() == Node.ELEMENT_NODE) {
                            		    if (layoutSubnode.getNodeName().equalsIgnoreCase("rows")) {
                            		        searchResults.setRows(Integer.parseInt(layoutSubnode.getFirstChild().getNodeValue()));
                            			}
                            			
                            		    if (layoutSubnode.getNodeName().equalsIgnoreCase("images")) {
                            				
                            			}
                              		}
                            	}
                			}
                		}
                	}
                }
            }
            
    	} catch (Exception e) {
    		logger.error("ERROR reading xml: " + e.getMessage()); 
    	}
    }
    
    /**
     * Returns text content of the tag
     * @param doc           xml document
     * @param tagName       tag name
     * @return              text content of the tag
     */
    public String getTextContent(Document doc, String tagName) {
        String retValue = null;
        NodeList nodes = doc.getElementsByTagName(tagName);
        
        for (int i=0; i<nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node instanceof Element) {
                retValue = node.getTextContent();
            }
        }
        
        return retValue;
    }
    
    /**
     * Maps metadata elements to the datastreams
     */
    public void mapMetadataElements(Document doc) {
        try {
            NodeList nodes = null;
            NodeList metadataNodes = doc.getElementsByTagName("metadatamapping");
            for (int i=0; i<metadataNodes.getLength(); i++) {
                Node metadataNode = metadataNodes.item(i);
                if (metadataNode.getNodeType() == Node.ELEMENT_NODE) {
                    nodes = metadataNode.getChildNodes();
                }
            }
            
            for (int i=0; i<nodes.getLength(); i++) {
                Node node = nodes.item(i);
                
                String name = null;
                MetaDataElement.MetaDataType eType = null;
                // Enumerated values for field
                List<String> enumValues = null;
                
                if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("element")) { 
                    if (node.getAttributes().getNamedItem("name") != null) {
                        name = node.getAttributes().getNamedItem("name").getNodeValue();
                    }
                    
                    // Get datastreams
                    NodeList dataStreamNodes = node.getChildNodes();
                    for (int j=0; j<dataStreamNodes.getLength(); j++) {
                        Node dsNode = dataStreamNodes.item(j);
                        
                        if (dsNode.getNodeType() == Node.ELEMENT_NODE) {
                            // check node name
                            if (dsNode.getNodeName().equalsIgnoreCase("mapping")) {
                                if (dsNode.getAttributes().getNamedItem("schema") != null) {
                                    String dataStream = dsNode.getAttributes().getNamedItem("schema").getNodeValue();
                                    String displayName = dsNode.getAttributes().getNamedItem("name").getNodeValue();
                                    
                                    MetaDataElement metaDataElement = new MetaDataElement(name, displayName, null, eType, null, null, null, dataStream);
                                    metaDataElement.setEnumValues(enumValues);
                                    
                                    // add datastream for form element in GUI
                                    mapDataStreams.put(name, metaDataElement);
                                }
                            }
                        }
                    }
                }
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }        
    }
    
	/**
	 * @return the searchConfiguration
	 */
	public SearchConfiguration getSearchConfiguration() {
		return searchConfiguration;
	}

	/**
	 * @param searchConfiguration the searchConfiguration to set
	 */
	public void setSearchConfiguration(SearchConfiguration searchConfiguration) {
		this.searchConfiguration = searchConfiguration;
	}

	public String getCssFile() {
		return cssFile;
	}

	public void setCssFile(String cssFile) {
		this.cssFile = cssFile;
	}

	public String getLogoFile() {
		return logoFile;
	}

	public void setLogoFile(String logoFile) {
		this.logoFile = logoFile;
	}
	
	/**
	 * @return the searchResults
	 */
	public SearchResultConfiguration getSearchResults() {
		return searchResults;
	}

	public void setSearchResultType() {
		if (resultType.equals("columns")) {
			this.searchResults.setType(SearchResultConfiguration.Type.columns);
		} else if (resultType.equals("grid")) {
			this.searchResults.setType(SearchResultConfiguration.Type.grid);
		} else {
			this.searchResults.setType(SearchResultConfiguration.Type.columns);
		}
	}
	
	/**
	 * @param searchResults the searchResults to set
	 */
	public void setSearchResults(SearchResultConfiguration searchResults) {
		this.searchResults = searchResults;
	}

	public String getResultType() {
		return resultType;
	}

	public void setResultType(String resultType) {
		this.resultType = resultType;
	}
	
	// TODO Refactor to somewhere else
	public int getQueueSize() {
		return queueSize;
	}

	public void setQueueSize(int queueSize) {
		this.queueSize = queueSize;
	}
	
	public int getInitialBasecollectionId() {
        return initialBasecollectionId;
    }

    public void setInitialBasecollectionId(int value) {
        this.initialBasecollectionId = value;
    }

	public String getMethod() {
		return disposeMethod;
	}

	public void setMethod(String method) {
		this.disposeMethod = method;
	}

	public boolean isDisposeChilds() {
		return disposeChilds;
	}

	public void setDisposeChilds(boolean disposeChilds) {
		this.disposeChilds = disposeChilds;
	}

	public String getLogoFileAlt() {
		return logoFileAlt;
	}

	public void setLogoFileAlt(String logoFileAlt) {
		this.logoFileAlt = logoFileAlt;
	}

	public String getMainHeader() {
		return mainHeader;
	}

	public void setMainHeader(String mainHeader) {
		this.mainHeader = mainHeader;
	}

	public String getSubHeader() {
		return subHeader;
	}

	public void setSubHeader(String subHeader) {
		this.subHeader = subHeader;
	}

	public String getAppTitle() {
		return appTitle;
	}

	public void setAppTitle(String appTitle) {
		this.appTitle = appTitle;
	}	
} public class AccessRight implements Serializable {
	
    private static final long serialVersionUID = -8302724810061640050L;

    public static final int ACCESSRIGHTLEVEL_DENY_META         = -1;
    public static final int ACCESSRIGHTLEVEL_READ_META         = 10;
    public static final int ACCESSRIGHTLEVEL_READ_DOC          = 20;
    public static final int ACCESSRIGHTLEVEL_WRITE_META        = 30;
    public static final int ACCESSRIGHTLEVEL_ADD_DOC           = 40;
    public static final int ACCESSRIGHTLEVEL_RECORDSMANAGEMENT = 50;
    public static final int ACCESSRIGHTLEVEL_ADMIN             = 100;
    public static final int ACCESSRIGHTLEVEL_INSTANCEADMIN     = 200;

    public static final String PUBLICITYLEVEL_Confidential = "confidential";
    public static final String PUBLICITYLEVEL_Restricted   = "restricted";
    public static final String PUBLICITYLEVEL_Public       = "public";
    
    /**
     * publicityLevel
     * 0: access to public data
     * 1: access to restricted data
     * 2: access to confidential data
     */
    public String publicityLevel;
    private int accessRightLevel;
    private String accessRightPath;
    private boolean recursiveRule;
    
    // publicity level hierarchy
    public static final List<String> PUBLICITYLEVEL_LIST = 
    Collections.unmodifiableList(new Vector<String>() {
        private static final long serialVersionUID = 1L;
    { 
        add("public");
        add("restricted");
        add("confidential");
    }});
	
    /**
     * Constructor
     */
	public AccessRight() {
		this.publicityLevel = PUBLICITYLEVEL_Public;
		this.accessRightLevel = ACCESSRIGHTLEVEL_READ_META;
		this.accessRightPath = null;
		this.recursiveRule = false;
	}
	
	/**
     * Constructor
     */
    public AccessRight(String publicityLevel, int accessRightLevel, String accessRightPath) {
		this.publicityLevel = publicityLevel;
		this.accessRightLevel = accessRightLevel;
		this.accessRightPath = accessRightPath;
		
		if (this.accessRightPath.endsWith("+")) {
		    this.recursiveRule = true;
		} else {
		    this.recursiveRule = false;
		}
	}
    
    /**
     * Get all publicity levels below determined level
     * (public, restricted, confidential)
     * @return
     */
    public String getPublicityLevels() {

        String levels = "";
        int index = PUBLICITYLEVEL_LIST.indexOf(this.publicityLevel);
        
        if (this.getAccessRightLevel() == AccessRight.ACCESSRIGHTLEVEL_DENY_META) {
            for (int i=0; i<PUBLICITYLEVEL_LIST.size(); i++) {
                if (i >= index) {
                    if (levels != "") {
                        levels += " ";
                    }
                    levels += PUBLICITYLEVEL_LIST.get(i);
                }
            }
            
        } else {
            for (int i=0; i<PUBLICITYLEVEL_LIST.size(); i++) {
                if (i <= index) {
                    if (levels != "") {
                        levels += " ";
                    }
                    levels += PUBLICITYLEVEL_LIST.get(i);
                }
            }
        }
        return levels;
    }
    
    public String getPublicityLevel() {
		return publicityLevel;
	}
	public void setPublicityLevel(String publicityLevel) {
		this.publicityLevel = publicityLevel;
	}
	public String getAccessRightPath() {
		return accessRightPath;
	}
	public void setAccessRightPath(String accessRightPath) {
		this.accessRightPath = accessRightPath;
	}
	public int getAccessRightLevel() {
		return accessRightLevel;
	}
	public void setAccessRightLevel(int accessRightLevel) {
		this.accessRightLevel = accessRightLevel;
	}
    public boolean isRecursiveRule() {
        return recursiveRule;
    }
    public void setRecursiveRule(boolean recursiveRule) {
        this.recursiveRule = recursiveRule;
    }

} public class User implements Serializable {
    
    private static final long serialVersionUID = -9105054849954016390L;
    private static final Logger logger = Logger.getLogger(User.class);
    
	/** Distinguished name */
	private String dn = null;
	/** Common name */
	private String cn = null;
	/** Password of the User. Could be cleartext, encoded or hash depending on implementation. */
    private String password = null;
    /** Unique identifier */
    private String uid = null;
    
    /** First name (given name) */ 
    private String firstName = null;
    /** Last name (surname) */
    private String lastName = null;
    /** Description of the User */
    private String description = null;
    /** e-mail address of the User */
    private String mail = null;
    /** Preferred language of the User */
    private String preferredLanguage = null;
    /** Title  of the User */
    private String title = null;
    /** Employee number  of the User */
    private String employeeNumber = null;
    /** Jpeg photo  of the User. Could be URI or file address depenging on implementation */
	private String jpegPhoto = null;
	/** Role for fedora commons repository */
	private Vector<Role> fedoraRoles = null;
	/** User's organization */
    private Organization organization = null;
    /** User's group in organization */
    private String group = null;
	/** Locale of the User */
	private Locale locale = null;
	/** User is anonymous */
	private boolean anonymous = false;
	/** User is instance admin */
	private boolean instance_admin = false;
	
	private int highestAccessRight = 0;
	
	/**
     * Constructor
     */
	public User() {
	    
	}
	
	/**
     * Constructor
     */
	public User(String dn, Locale language) {
	    
        this.setDn(dn);
        
        // get user attributes from ldap
	    HashMap<String,String> usrAttrs = Osa.authManager.getAttributes(dn);
	   
	    if (dn.equals(Osa.authManager.getInstanceAdminDn())) {
	        this.setInstanceAdmin(true);
	    } 
	    
	    if (usrAttrs.containsKey("cn")) {
            this.setCn(usrAttrs.get("cn"));
            this.setAnonymous(false);
            logger.info("logged in with dn: "+dn);
        } else {
            this.setAnonymous(true);
            logger.info("logged in as anonymous user with dn: "+dn);
        }
	    
        if (usrAttrs.containsKey("preferredLanguage")) {
            String[] localeParts = usrAttrs.get("preferredLanguage").split("_");
            this.locale = new Locale(localeParts[0],localeParts[1]);
        }
        
        // TODO: find better way to do this
        // parse organization name from dn
	    String[] parts = dn.split(",");
	    String orgname = "";
	    for (int i=0; i<parts.length; i++) {
	    	if (parts[i].indexOf("o=") == 0) {
	    		orgname = parts[i].replace("o=", "");
	    		break;
	    	}
	    }
	    
	    Organization org = new Organization(dn.replaceAll("(cn=.*?,)", ""), orgname, null);
	    this.setOrganization(org);

	    if (usrAttrs.containsKey("mail")) {
	        this.setMail(usrAttrs.get("mail"));
	    }
	    
	    if (anonymous) {
	        Role anonymousRole = Osa.authManager.getPublicRole(orgname);
	        Vector<Role> anonymousRoles = new Vector<Role>();
	        anonymousRoles.add(anonymousRole);
	        this.setFedoraRoles(anonymousRoles);
	        logger.info("User role in Fedora: "+anonymousRole.name);
	        
	        if (language != null) {
	            this.locale = language;
	        }
	        
	    } else if (!this.isInstanceAdmin()) {
	        this.setFedoraRoles(Osa.authManager.getUserRoles(dn, org.getName()));
	        for (Role role : this.getFedoraRoles()) {
	            logger.info("User role in Fedora: "+role.name);
	        }
	    }
	    
	    logger.info("User rolelevel: "+this.getHighestAccessRight());	   
	    logger.info("User locale: "+this.locale);
	    logger.info("User organization: "+this.organization.getName());
	}
	
	/**
     * @return the application role
     */
	public String getRole() {
	    int acRight = this.getHighestAccessRight();
	    String role = "";
	    
	    if (this.isInstanceAdmin()) {
	        role = "instance_admin";
	    } else if (acRight == AccessRight.ACCESSRIGHTLEVEL_ADMIN) {
	        role = "admin";
	    } else if (acRight == AccessRight.ACCESSRIGHTLEVEL_RECORDSMANAGEMENT) {
	        role = "manager";
	    } else {
	        role = "reader";
	    }
	    return role;
	}

	
	/**
	 * @return the dn
	 */
	public String getDn() {
		return dn;
	}
	/**
	 * @param dn the dn to set
	 */
	public void setDn(String dn) {
		this.dn = dn;
	}
	/**
	 * @return the cn
	 */
	public String getCn() {
		return cn;
	}
	/**
	 * @param cn the cn to set
	 */
	public void setCn(String cn) {
		this.cn = cn;
	}
	/**
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}
	/**
	 * @param password the password to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}
	/**
	 * @return the uid
	 */
	public String getUid() {
		return uid;
	}
	/**
	 * @param uid the uid to set
	 */
	public void setUid(String uid) {
		this.uid = uid;
	}
	/**
	 * @return the firstName
	 */
	public String getFirstName() {
		return firstName;
	}
	/**
	 * @param firstName the firstName to set
	 */
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	/**
	 * @return the lastName
	 */
	public String getLastName() {
		return lastName;
	}
	/**
	 * @param lastName the lastName to set
	 */
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	/**
	 * @return the description
	 */
	public String getDescription() {
		return description;
	}
	/**
	 * @param description the description to set
	 */
	public void setDescription(String description) {
		this.description = description;
	}
	/**
	 * @return the mail
	 */
	public String getMail() {
		return mail;
	}
	/**
	 * @param mail the mail to set
	 */
	public void setMail(String mail) {
		this.mail = mail;
	}
	/**
	 * @return the preferredLanguage
	 */
	public String getPreferredLanguage() {
		return preferredLanguage;
	}
	/**
	 * @param preferredLanguage the preferredLanguage to set
	 */
	public void setPreferredLanguage(String preferredLanguage) {
		this.preferredLanguage = preferredLanguage;
	}
	/**
	 * @return the title
	 */
	public String getTitle() {
		return title;
	}
	/**
	 * @param title the title to set
	 */
	public void setTitle(String title) {
		this.title = title;
	}
	/**
	 * @return the employeeNumber
	 */
	public String getEmployeeNumber() {
		return employeeNumber;
	}
	/**
	 * @param employeeNumber the employeeNumber to set
	 */
	public void setEmployeeNumber(String employeeNumber) {
		this.employeeNumber = employeeNumber;
	}
	/**
	 * @return the jpegPhoto
	 */
	public String getJpegPhoto() {
		return jpegPhoto;
	}
	
	/**
	 * Get all access right filters from user roles
	 * @return
	 */
	public Vector< HashMap<String, String> > getAccessRightFilters() {
	    // Collect filters per role to vector
	    Vector< HashMap<String, String> > filters = new Vector< HashMap<String, String> >();
	    // vector to store paths, where access is denied
        Vector<String> nonAccessiblePaths = new Vector<String>();
        
        if (this.isInstanceAdmin()) {
            return filters;
        }
        
        // Check if access is denied
        for (Role role : this.getFedoraRoles()) {
            for (AccessRight acRight : role.getRoleAccessrights()) {
                int accessRightLevel = acRight.getAccessRightLevel();
                if (accessRightLevel == AccessRight.ACCESSRIGHTLEVEL_DENY_META) {
                    // access denied to this path
                    String accessRightPath = "("+acRight.getAccessRightPath()+"";
                    // replace '+' char with '*' for Solr. Query syntax: ("publicityLevel AND accessRightPath/"*)
                    if (accessRightPath.contains("+")) accessRightPath = accessRightPath.replace("+", "") + "*";
                    accessRightPath += ")";                    
                    nonAccessiblePaths.add(accessRightPath);
                }
            }
        }
        
        // Go through all roles
	    for (Role role : this.getFedoraRoles()) {
            // Go through all AccessRights of the Role
            for (AccessRight acRight : role.getRoleAccessrights()) {
                String publicityLevels = "(" + acRight.getPublicityLevels() + ")";
                String accessRightPath = "(" + acRight.getAccessRightPath() + "";
                // replace '+' char with '*' for Solr. Query syntax: ("publicityLevel AND accessRightPath/"*)
                if (accessRightPath.contains("+")) accessRightPath = accessRightPath.replace("+", "") + "*";
                accessRightPath += ")";
                int accessRightLevel = acRight.getAccessRightLevel();
                HashMap<String, String> filter = new HashMap<String, String>();

                if (accessRightLevel != AccessRight.ACCESSRIGHTLEVEL_DENY_META) {
                    filter.put("c.accessRights", publicityLevels);
                    filter.put("m.objectPath", accessRightPath);
                    
                    // add paths, where access is denied, to filter
                    for (String path : nonAccessiblePaths) {
                        filter.put("-m.objectPath", path);
                    }
                    filters.add(filter);
                }
            }
        }
        return filters;
    }
	
    /**
     * @param User's highest accessright level to set
     */
    public void setHighestAccessRight(int value) {
        this.highestAccessRight = value;
    }
    
	/**
	 * @return User's highest accessright
	 */
    public int getHighestAccessRight() {
        int value = 0;
        if (this.highestAccessRight == 0) {
            if (this.isInstanceAdmin()) {
                value = AccessRight.ACCESSRIGHTLEVEL_INSTANCEADMIN;
            } else  {
                for (Role role : this.getFedoraRoles()) {
                    for (AccessRight acRight : role.getRoleAccessrights()) {
                        if (acRight.getAccessRightLevel() > value) {
                            value = acRight.getAccessRightLevel();
                        }
                    }
                }
            }
            setHighestAccessRight(value);
        }
        return this.highestAccessRight;
    }
	
    /**
     * @return the roles determined to fedora
     */
    public Vector<Role> getFedoraRoles() {
    	return this.fedoraRoles;
    }
    /**
     * @param roles to the roles set
     */
    public void setFedoraRoles(Vector<Role> roles) {
    	this.fedoraRoles = roles;
    }
	/**
	 * @param jpegPhoto the jpegPhoto to set
	 */
	public void setJpegPhoto(String jpegPhoto) {
		this.jpegPhoto = jpegPhoto;
	}
	/**
	 * @return the organization
	 */
	public Organization getOrganization() {
		return organization;
	}
	/**
	 * @param organization the organization to set
	 */
	public void setOrganization(Organization organization) {
		this.organization = organization;
	}
	/**
     * @return group
     */
	public String getGroup() {
        return this.group;
    }
	/**
     * @param group the group to set
     */
	public void setGroup(String group) {
	    this.group = group;
	}
	/**
	 * @return the locale
	 */
	public Locale getLocale() {
		return locale;
	}
	/**
	 * @param locale the locale to set
	 */
	public void setLocale(Locale locale) {
		this.locale = locale;
	}

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

	public void setAnonymous(boolean anonymous) {
		this.anonymous = anonymous;
	}

	public boolean isAnonymous() {
		return anonymous;
	}
	
	public void setInstanceAdmin(boolean value) {
	    this.instance_admin = value;
	}
	
	public boolean isInstanceAdmin() {
        return this.instance_admin;
    }
	
	public boolean isAdmin() {
	    return this.getHighestAccessRight() >= AccessRight.ACCESSRIGHTLEVEL_ADMIN;
    }

} public class AttachmentBean extends DataStream {
    
    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(AttachmentBean.class);
    
    public static final String ATTACHMENT_DSID = "attachment";
    
    // container for thumb-objects property data
    LinkedHashMap<String, DataStream> lhmElementProperties = new LinkedHashMap<String, DataStream>();
    
    // Constructor
    public AttachmentBean(String PID) 
    {
        super(PID);
        dsID = AttachmentBean.ATTACHMENT_DSID;
    }
    
    public LinkedHashMap<String, DataStream> getElementProperties() { return this.lhmElementProperties; }
    public void setElementProperties(LinkedHashMap<String, DataStream> elements) { this.lhmElementProperties = elements; }
    
    public void setElementProperty(String dataStream, String propertyName, String propertyValue) {
        if (this.lhmElementProperties.containsKey(dataStream)) {
            this.lhmElementProperties.get(dataStream).setProperty(propertyName, propertyValue);
        } else {
            DataStream ds = new AttachmentBean(dataStream);
            ds.setProperty(propertyName, propertyValue);
            this.lhmElementProperties.put(dataStream, ds);
        }
    }
} public class CaptureBean extends DataStream {
    
    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(CaptureBean.class);
    
    public static final String CAPTURE_DSID 			= "CAPTURE";
    public static final String CAPTURE_LABEL 			= "Capture Record";
    
    public static final String RELATION_ConformsTo 		= "conformsTo";
    public static final String RELATION_HasAction 		= "hasAction";    
    public static final String RELATION_HasAgent 		= "hasAgent";
    public static final String RELATION_HasEvent 		= "hasEvent";
    public static final String RELATION_HasFormat 		= "hasFormat";
    public static final String RELATION_HasPart 		= "hasPart";
    public static final String RELATION_HasPlace 		= "hasPlace";
    public static final String RELATION_HasVersion 		= "hasVersion";
    public static final String RELATION_IsFormatOf 		= "isFormatOf";
    public static final String RELATION_IsChildOf 		= "isChildOf";
    public static final String RELATION_IsParentOf 		= "isParentOf";
    public static final String RELATION_IsPartOf 		= "isPartOf";
    public static final String RELATION_IsReferencedBy 	= "isReferencedBy";
    public static final String RELATION_IsReplacedBy 	= "isReplacedBy";
    public static final String RELATION_IsRequiredBy 	= "isRequiredBy";
    public static final String RELATION_IsVersionOf 	= "isVersionOf";
    public static final String RELATION_References 		= "references";
    public static final String RELATION_Replaces 		= "replaces";
    public static final String RELATION_Requires 		= "requires";
    public static final String RELATION_Source 			= "source";
    public static final String RELATION_HasRedaction 	= "hasRedaction";
    public static final String RELATION_IsRedactionOf 	= "isRedactionOf";
    public static final String RELATION_IsMemberOf 		= "isMemberOf";
    
    public static final String PUBLICITY_LEVEL          = "accessRights";
    public static final String IDENTIFIER               = "id";
    public static final String PREFERREDNAME            = "preferredName";
    public static final String TITLE                    = "title";
    public static final String TYPE 			        = "type";
    
    // Constructor
    public CaptureBean(String PID) {
        super(PID);
        dsID = CaptureBean.CAPTURE_DSID;
    }
    
    /**
     * Returns the value of isPartOf-relation
     * @return
     */
    public String getParent() {
        String parent = null;
        MetaDataElement element = this.getMetaDataElements().get(CaptureBean.RELATION_IsPartOf);
        
        if (element != null) {
            parent = element.getValue();
        }
        return parent;
    }
    
    public String getPublicityLevel() {
        String parent = null;
        MetaDataElement element = this.getMetaDataElements().get(CaptureBean.PUBLICITY_LEVEL);
        
        if (element != null) {
            parent = element.getValue();
        }
        return parent;
    }
        
    /**
     * set metadata to capture datastream
     * @param name      name of the relation
     * @param value     value  of the relation
     * @param eType     type of metadata 
     */
    public void setMetaDataElement(String name, String visibleName, String value, MetaDataType eType) {
        
        if (name.equalsIgnoreCase(CaptureBean.RELATION_ConformsTo)
            || name.equalsIgnoreCase(CaptureBean.RELATION_HasFormat)
            || name.equalsIgnoreCase(CaptureBean.RELATION_HasPart)
            || name.equalsIgnoreCase(CaptureBean.RELATION_HasRedaction)
            || name.equalsIgnoreCase(CaptureBean.RELATION_HasVersion)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsChildOf)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsFormatOf)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsMemberOf)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsParentOf)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsPartOf)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsRedactionOf)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsReferencedBy)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsReplacedBy)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsRequiredBy)
            || name.equalsIgnoreCase(CaptureBean.RELATION_IsVersionOf)
            || name.equalsIgnoreCase(CaptureBean.RELATION_References)
            || name.equalsIgnoreCase(CaptureBean.RELATION_Replaces)
            || name.equalsIgnoreCase(CaptureBean.RELATION_Requires)
            || name.equalsIgnoreCase(CaptureBean.RELATION_Source)
                )
            {
                super.setMetaDataElement(name, visibleName, value, MetaDataElement.MetaDataType.relation);
            } else {
                super.setMetaDataElement(name, visibleName, value, eType);
            }
    }
    
} public class DCBean extends DataStream {
    
    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(DCBean.class);
    
    public static final String DC_DSID  = "DC";
    public static final String DC_LABEL = "Dublin Core Record";

    // Resource types (The nature or genre of the resource)
    public static enum Type {
        Undefined,
        Collection,
        Dataset,
        Drawing,
        Email,
        Event,
        Folder,
        Image,
        InteractiveResource,
        Model,
        MovingImage,
        PhysicalObject,
        Service,
        Software,
        Sound,
        Text,
        other;
        
        public static Type getEnum(String s) {
            if (Collection.name().equals(s))
                return Collection;
            else if (Dataset.name().equals(s))
                return Dataset;
            else if (Drawing.name().equals(s))
                return Drawing;
            else if (Email.name().equals(s))
                return Email;
            else if (Event.name().equals(s))
                return Event;
            else if (Folder.name().equals(s))
                return Folder;
            else if (Image.name().equals(s))
                return Image;
            else if (InteractiveResource.name().equals(s))
                return InteractiveResource;
            else if (Model.name().equals(s))
                return Model;
            else if (MovingImage.name().equals(s))
                return MovingImage;
            else if (PhysicalObject.name().equals(s))
                return PhysicalObject;
            else if (Service.name().equals(s))
                return Service;
            else if (Software.name().equals(s))
                return Software;
            else if (Sound.name().equals(s))
                return Sound;
            else if (Text.name().equals(s))
                return Text;
            else if (other.name().equals(s))
                return other;
            throw new IllegalArgumentException("No Enum specified for this string");
        }
    }
    
    // Constructor
    public DCBean(String PID) 
    {
        super(PID);
        dsID = DCBean.DC_DSID;
    }
        
} public abstract class DataStream implements Serializable {
    
    private static final long serialVersionUID = 8605655186952919889L;
    private static final Logger logger = Logger.getLogger(DataStream.class);
    
    public static final String DS_LABEL         = "dsLabel";
    public static final String DS_CHECKSUM      = "dsChecksum";
    public static final String DS_CHECKSUMTYPE  = "dsChecksumType";
    public static final String DS_CONTROLGROUP  = "dsControlGroup";
    public static final String DS_CREATEDATE    = "dsCreateDate";
    public static final String DS_FORMATURI     = "dsFormatURI";
    public static final String DS_ID            = "sID";
    public static final String DS_INFOTYPE      = "dsInfoType";
    public static final String DS_LOCATION      = "dsLocation";
    public static final String DS_LOCTYPE       = "dsLocationType";
    public static final String DS_MIME          = "dsMIME";
    public static final String DS_PID           = "id";
    public static final String DS_SIZE          = "dsSize";
    public static final String DS_STATE         = "dsState";
    public static final String DS_VERSIONABLE   = "dsVersionable";
    public static final String DS_VERSIONID     = "dsVersionID";
    
    // The algorithm used to compute the checksum
    public static enum ChecksumType {
        DEFAULT("DEFAULT"),
        DISABLED("DISABLED"),
        MD5("MD5"),
        SHA1("SHA-1"),
        SHA256("SHA-256"),
        SHA385("SHA-385"),
        SHA512("SHA-512"),
        HAVAL("HAVAL"),
        TIGER("TIGER"),
        WHIRLPOOL("TIGER");
        
        private String name;       

        private ChecksumType(String s) {
            name = s;
        }
        
        public static ChecksumType getEnum(String s) {
            if (DEFAULT.name.equals(s))
                return DEFAULT;
            else if (DISABLED.name.equals(s))
                return DISABLED;
            else if (MD5.name.equals(s))
                return MD5;
            else if (SHA1.name.equals(s))
                return SHA1;
            else if (SHA256.name.equals(s))
                return SHA256;
            else if (SHA385.name.equals(s))
                return SHA385;
            else if (SHA512.name.equals(s))
                return SHA512;
            else if (HAVAL.name.equals(s))
                return HAVAL;
            else if (TIGER.name.equals(s))
                return TIGER;
            else if (WHIRLPOOL.name.equals(s))
                return WHIRLPOOL;
            throw new IllegalArgumentException("No Enum specified for this string");
        }
    }
    
    // indicates the kind of datastream
    public static enum ControlGroup {
        DEFAULT,
        E,          // ExternallyReferencedContent
        R,          // RedirectedContent
        M,          // ManagedContent
        X;          // InlineXML
        
        public static ControlGroup getEnum(String s) {
            if (E.name().equals(s))
                return E;
            else if (R.name().equals(s))
                return R;
            else if (M.name().equals(s))
                return M;
            else if (X.name().equals(s))
                return X;
            throw new IllegalArgumentException("No Enum specified for this string");
        }
    }
    
    // datastream state
    public static enum State {
        DEFAULT,
        A,          // Active
        I,          // Inactive
        D;          // Deleted
        
        public static State getEnum(String s) {
            if (A.name().equals(s))
                return A;
            else if (I.name().equals(s))
                return I;
            else if (D.name().equals(s))
                return D;
            throw new IllegalArgumentException("No Enum specified for this string");
        }
    }
    
    // Content location type
    public static enum LocationType {
        DEFAULT,
        URL,
        INTERNAL_ID;
        
        public static LocationType getEnum(String s) {
            if (URL.name().equals(s))
                return URL;
            else if (INTERNAL_ID.name().equals(s))
                return INTERNAL_ID;
            throw new IllegalArgumentException("No Enum specified for this string");
        }
    }
        
    // a true/false indication as to whether the datastream should be versioned by the Fedora repository service
    protected boolean bIsVersionable;
    // datastream size
    protected int size;
    // checksum type
    protected ChecksumType eChecksumType;
    // indicates the kind of datastream
    protected ControlGroup eControlGroup;
    // location type
    protected LocationType eLocationType;
    // MIME type
    protected String eMimeType;
    // datastream state
    protected State eState;
    // checksum
    protected String checksum;
    // creation date for the datastream version
    protected String createDate;
    // unique identifier for the datastream
    protected String dsID;
    // format uri
    protected String formatURI;
    // info type
    protected String infoType;
    // user-assigned descriptive label for the datastream
    protected String label;
    // latest modification date
    protected String lastModifiedDate;
    // points to content on a external web server or to an internal identifier instead of a URL
    protected String location; 
    // digital object identifier
    protected String PID;
    // version
    protected String versionID;
    // The owner(s) of the object
    protected Vector<String> vectOwnerId;
    
    // content data
    protected LinkedHashMap<String, MetaDataElement> lhmElements = new LinkedHashMap<String, MetaDataElement>();

    // Constructor
    public DataStream(String PID) {
        this.PID = PID;
        this.bIsVersionable = true;
        this.createDate = null;
        this.dsID = null;
        this.eChecksumType = ChecksumType.DEFAULT;
        this.eControlGroup = ControlGroup.DEFAULT;
        this.eLocationType = LocationType.DEFAULT;
        this.eMimeType = "";
        this.eState = State.DEFAULT;
        this.lastModifiedDate = null;
        this.versionID = null;
    }
    
    // Constructor
    public DataStream(String pid, ControlGroup cGroup, State state) {
        this.PID = pid;
        this.eControlGroup = cGroup;
        this.eState = state;
        this.bIsVersionable = true;
    }
    
    // Constructor
    public DataStream(LinkedHashMap<String, MetaDataElement> elements) {
        this.lhmElements = elements;
    }
    
    public ChecksumType getChecksumType() { return this.eChecksumType; }
    public void setChecksumType(ChecksumType value) { this.eChecksumType = value; }
    
    public ControlGroup getControlGroup() { return this.eControlGroup; }
    public void setControlGroup(ControlGroup value) { this.eControlGroup = value; }
    
    public String getCreateDate() { return this.createDate; }
    public void setCreateDate(String date) { this.createDate = date; }
    
    public String getDataStreamID() { return this.dsID; }
    public void setDataStreamID(String id) { this.dsID = id; }
    
    public LinkedHashMap<String, MetaDataElement> getMetaDataElements() { return this.lhmElements; }
    public void setMetaDataElements(LinkedHashMap<String, MetaDataElement> elements) { this.lhmElements = elements; }
    
    public void setMetaDataElement(MetaDataElement newElement) {
        if (lhmElements.containsKey(newElement.getName())) {
            MetaDataElement element = lhmElements.get(newElement.getName());
            element.getValues().add(newElement.getValue());
            lhmElements.put(newElement.getName(), element);
        } else {
            lhmElements.put(newElement.getName(), newElement);
        }
    }
    
    public void setMetaDataElement(String name, String visibleName, String value, MetaDataType eType) {
        if (lhmElements.containsKey(name)) {
            MetaDataElement element = lhmElements.get(name);
            element.getValues().add(value);
            lhmElements.put(name, element);
        } else {
            MetaDataElement mdElement = new MetaDataElement(name, visibleName, value, eType);
            lhmElements.put(name, mdElement);
        }
    }
    
    public void setMetaDataElement(String name, 
                                   String visibleName, 
                                   String value, 
                                   MetaDataType eType, 
                                   String multivalueType, 
                                   Vector<LinkedHashMap<String, MetaDataElement>> nestedElements, 
                                   InputStream is) {
        
        if (lhmElements.containsKey(name)) {
            MetaDataElement element = lhmElements.get(name);
            element.getValues().add(value);
            lhmElements.put(name, element);
        } else {
            MetaDataElement mdElement = new MetaDataElement(name, visibleName, value, eType, multivalueType, is, nestedElements, null);
            lhmElements.put(name, mdElement);
        }
    }

    public void setNestedMetaDataElement(String parent, String name, String visibleName, String value, MetaDataType eType) {
        MetaDataElement parentElement = null;
        if (lhmElements.containsKey(parent)) {
            parentElement = lhmElements.get(parent);
            
        } else {
            parentElement = new MetaDataElement(parent, parent, null, eType);
            lhmElements.put(parent, parentElement);
        }
        
        MetaDataElement nestedElement = new MetaDataElement(name, visibleName, value, eType);
        lhmElements.get(parent).setNestedElement(name, nestedElement);
    }
    
    public String getProperty(String name) {
        String retValue = "";
        
        if (DataStream.DS_LABEL.equals(name)) {
            retValue = this.getLabel();
        } else if (DataStream.DS_VERSIONID.equals(name)) {
            retValue = this.getVersionID();
        } else if (DataStream.DS_CREATEDATE.equals(name)) {
            retValue = this.getCreateDate();
        } else if (DataStream.DS_STATE.equals(name)) {
            retValue = this.getState().toString();
        } else if (DataStream.DS_FORMATURI.equals(name)) {
            retValue = this.getFormatURI();
        } else if (DataStream.DS_CONTROLGROUP.equals(name)) {
            retValue = this.getControlGroup().toString();
        } else if (DataStream.DS_INFOTYPE.equals(name)) {
            retValue = this.getInfoType();
        } else if (DataStream.DS_CHECKSUM.equals(name)) {
            retValue = this.getChecksum();
        } else if (DataStream.DS_CHECKSUMTYPE.equals(name)) {
            retValue = this.getChecksumType().toString();
        } else if (DataStream.DS_LOCATION.equals(name)) {
            retValue = this.getLocation();
        } else if (DataStream.DS_LOCTYPE.equals(name)) {
            retValue = this.getLocationType().toString();
        } else if (DataStream.DS_VERSIONABLE.equals(name)) {
            retValue = String.valueOf(this.getIsVersionable());
        } else if (DataStream.DS_SIZE.equals(name)) {
            retValue = Integer.toString(this.getDataStreamSize());
        } else if (DataStream.DS_MIME.equals(name)) {
            retValue = this.getMimeFormat();
        } else {
            logger.info("missing "+name);
        }
        
        return retValue;
    }
    
    public void setProperty(String name, String value) {
        if (DataStream.DS_LABEL.equals(name)) {
            this.setLabel(value);
        } else if (DataStream.DS_VERSIONID.equals(name)) {
            this.setVersionID(value);
        } else if (DataStream.DS_CREATEDATE.equals(name)) {
            this.setCreateDate(value);
        } else if (DataStream.DS_STATE.equals(name)) {
            this.setState(DataStream.State.getEnum(value.toString()));
        } else if (DataStream.DS_FORMATURI.equals(name)) {
            this.setFormatURI(value);
        } else if (DataStream.DS_CONTROLGROUP.equals(name)) {
            this.setControlGroup(DataStream.ControlGroup.getEnum(value.toString()));
        } else if (DataStream.DS_INFOTYPE.equals(name)) {
            this.setInfoType(value);
        } else if (DataStream.DS_CHECKSUM.equals(name)) {
            this.setChecksum(value);
        } else if (DataStream.DS_CHECKSUMTYPE.equals(name)) {
            this.setChecksumType(DataStream.ChecksumType.getEnum(value.toString()));
        } else if (DataStream.DS_LOCATION.equals(name)) {
            this.setLocation(value);
        } else if (DataStream.DS_LOCTYPE.equals(name)) {
            this.setLocationType(DataStream.LocationType.getEnum(value.toString()));
        } else if (DataStream.DS_VERSIONABLE.equals(name)) {
            this.setIsVersionable(Boolean.parseBoolean(value.toString()));
        } else if (DataStream.DS_SIZE.equals(name)) {
            this.setDataStreamSize(Integer.parseInt(value.toString()));
        } else if (DataStream.DS_MIME.equals(name)) {
            this.setMimeFormat(value);
        } else {
            logger.info("missing "+name);
        }

    }
    
    /**
     * Function is used for comparing datastream content
     * @return
     */
    public String getMetadataContentUsingVisibleName(String ignoreElement) {
        
        String content = "";
        LinkedHashMap<String, String> elements = new LinkedHashMap<String, String>();
        
        if (ignoreElement != null) {
            // f.ex. remove name=identifier before comparing DC datastreams
            lhmElements.remove(ignoreElement);
        }
        
        // go through metadata elements, and create new collection where only visible names are used with values
        Iterator<Entry<String, MetaDataElement>> iter = this.lhmElements.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<String, MetaDataElement> entry = (Map.Entry<String, MetaDataElement>) iter.next();
            MetaDataElement mdElement = entry.getValue();
            
            if (!mdElement.isEmpty()) {
                content = "{visiblename="+mdElement.getVisibleName()+
                          ", value="+mdElement.getValue()+
                          ", values="+mdElement.getValues().toString()+
                          ", type="+mdElement.getMetaDataType().toString()+
                          ", nestedElements="+mdElement.getNestedElements().toString()+"}";
                elements.put(mdElement.getVisibleName(), content);
            }
        }
        
        // sort by key
        Map<String, String> sorted = sortByKeys(elements);
        
        return sorted.toString();
        
    }
    
    public String getChecksum() { return this.checksum; }
    public void setChecksum(String value) { this.checksum = value; }
    
    public int getDataStreamSize() { return this.size; }
    public void setDataStreamSize(int value) { this.size = value; }
    
    public String getFormatURI() { return this.formatURI; }
    public void setFormatURI(String value) { this.formatURI = value; }
    
    public String getInfoType() { return this.infoType; }
    public void setInfoType(String value) { this.infoType = value; }
    
    public boolean getIsVersionable() { return this.bIsVersionable; }
    public void setIsVersionable(boolean bValue) { this.bIsVersionable = bValue; }
    
    public String getLabel() { return this.label; }
    public void setLabel(String value) { this.label = value; }
    
    public String getLastModDate() { return this.lastModifiedDate; }
    public void setLastModDate(String date) { this.lastModifiedDate = date; }
    
    public String getLocation() { return this.location; }
    public void setLocation(String value) { this.location = value; }
    
    public LocationType getLocationType() { return this.eLocationType; }
    public void setLocationType(LocationType value) { this.eLocationType = value; }
    
    public String getMimeFormat() { return this.eMimeType; }
    public void setMimeFormat(String value) { this.eMimeType = value; }
    
    public Vector<String> getOwnerID() { return this.vectOwnerId; }
    public void setOwnerID(Vector<String> owners) { this.vectOwnerId = owners; }
    
    public String getPID() { return this.PID; }
    public void setPID(String id) { this.PID = id; }
    
    public State getState() { return this.eState; }
    public void setState(State state) { this.eState = state; }
    
    public String getVersionID() { return this.versionID; }
    public void setVersionID(String value) { this.versionID = value; }
    
    public void removeMetaDataElement(String name) {
        this.getMetaDataElements().remove(name);
    }
    
    // Function to sort a map by key
    public <K extends Comparable,V extends Comparable> Map<K,V> sortByKeys(Map<K,V> map) {
        List<K> keys = new LinkedList<K>(map.keySet());
        Collections.sort(keys);
     
        Map<K,V> sortedMap = new LinkedHashMap<K,V>();
        for(K key: keys){
            sortedMap.put(key, map.get(key));
        }
     
        return sortedMap;
    }

} public static enum State {
    DEFAULT,
    A,          // Active
    I,          // Inactive
    D;          // Deleted
        
    public static State getEnum(String s) {
        if (A.name().equals(s))
            return A;
        else if (I.name().equals(s))
            return I;
        else if (D.name().equals(s))
            return D;
        throw new IllegalArgumentException("No Enum specified for this string");
    }
} public class FedoraBean {
    
    private static final Logger logger = Logger.getLogger(FedoraBean.class);
    public static final String DATASTREAM_DC = "DC";
    public static final String DATASTREAM_CAPTURE = "CAPTURE";
    public static final String DATASTREAM_CONTENT = "CONTENT";
    public static final String DATASTREAM_AUDIT_TRAIL = "AUDIT_TRAIL";
    public static final String DATASTREAM_RELS_EXT = "RELS-EXT";
    public static final String DATASTREAM_RELS_INT = "RELS-INT";
    public static final String DATASTREAM_THUMB = "THUMB";
    public static final String DATASTREAM_ATTACHMENT = "ATTACHMENT";
    public static final String DATASTREAM_MANAGEMENT = "MANAGEMENT";
    
    public static final String DATASTREAM_ORIGINAL = "ORIGINAL";
    public static final String DATASTREAM_ORIGINAL_LABEL = "-O";
    public static final String DATASTREAM_THUMB_LABEL = "-P";
    public static final String DATASTREAM_ATTACHMENT_LABEL = "-A";
    
    public static final String OBJ_LABEL        = "objLabel";
    public static final String OBJ_CONTENTMODEL = "model";
    public static final String OBJ_CREATIONDATE = "objCreateDate";
    public static final String OBJ_LASTMODIFIED = "objLastModDate";
    public static final String OBJ_OWNERID      = "objOwnerId";
    public static final String OBJ_STATE        = "objState";
    public static final String OBJ_DISSINDEX    = "objDissIndexViewURL";
    public static final String OBJ_ITEMINDEX    = "objItemIndexViewURL";

    // datastreams
    DCBean dublinCoreData = null;
    CaptureBean captureData = null;
    OriginalBean original = null;
    ContentBean content = null;
    RelationsBean relations = null;
    ThumbBean thumb = null;
    AttachmentBean attachment = null;
    ManagementBean management = null;
    
    // common object data
    private String objectLabel;
    private String objectContentModel;
    private String objectCreationDate;
    private String objectLastModified;
    private String objectOwnedId;
    private DataStream.State eObjectState;
    
    public String PID;    
    public String type; // document, image, etc.
    public String oldParent;
    
    public boolean bUpdateCaptureDatastream;
    public boolean bUpdateDcDatastream;
    public boolean bUpdateReslExtDatastream;
    public boolean bUpdateReslIntDatastream;
    public boolean bUpdateManagementDatastream;

    // Default constructor
    public FedoraBean() {
        PID = null;
        type = null;
        oldParent = null;
        dublinCoreData = new DCBean(PID);
        captureData = new CaptureBean(PID);
        original = new OriginalBean(PID);
        content = new ContentBean(PID);
        relations = new RelationsBean(PID);
        thumb = new ThumbBean(PID);
        attachment = new AttachmentBean(PID);
        management = new ManagementBean(PID);
        bUpdateCaptureDatastream = false;
        bUpdateDcDatastream = false;
        bUpdateReslExtDatastream = false;
        bUpdateReslIntDatastream = false;
        bUpdateManagementDatastream = false;
    }
    
    // Constructor
    public FedoraBean(String PID) {
        this.setPID(PID);
        this.setTypeFromPID(PID);
        oldParent = null;
        dublinCoreData = new DCBean(PID);
        captureData = new CaptureBean(PID);
        original = new OriginalBean(PID);
        content = new ContentBean(PID);
        relations = new RelationsBean(PID);
        thumb = new ThumbBean(PID);
        attachment = new AttachmentBean(PID);
        management = new ManagementBean(PID);
        bUpdateCaptureDatastream = false;
        bUpdateDcDatastream = false;
        bUpdateReslExtDatastream = false;
        bUpdateReslIntDatastream = false;
        bUpdateManagementDatastream = false;
    }
    
    // Constructor
    public FedoraBean(String PID, String type) {
        this.setPID(PID);
        this.type = type;
        oldParent = null;
        dublinCoreData = new DCBean(PID);
        captureData = new CaptureBean(PID);
        original = new OriginalBean(PID);
        content = new ContentBean(PID);
        relations = new RelationsBean(PID);
        thumb = new ThumbBean(PID);
        attachment = new AttachmentBean(PID);
        management = new ManagementBean(PID);
        bUpdateCaptureDatastream = false;
        bUpdateDcDatastream = false;
        bUpdateReslExtDatastream = false;
        bUpdateReslIntDatastream = false;
        bUpdateManagementDatastream = false;
    }
    
    /**
     * Get content model
     * @return      name of content model
     */
    public String getContentModel() {
        
        String model = null;
        
        if (this.type != null) {
            model = RepositoryManager.CONTENTMODEL + this.type;
        }
        
        return model;
    }
    
    /**
     * Find datastream according to datastream id
     * @param dataStreamID
     * @return
     */
    public DataStream getDataStream(String dataStreamID) {
        
        if (dataStreamID.equalsIgnoreCase(DATASTREAM_DC)) {
            return this.dublinCoreData;
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_CAPTURE)) {
            return this.captureData;
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_ORIGINAL)) {
            return this.original;
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_RELS_EXT)) {
            return this.relations;
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_THUMB)) {
            return this.thumb;
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_ATTACHMENT)) {
            return this.attachment;
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_MANAGEMENT)) {
            return this.management;
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_CONTENT)) {
            return this.content;
            
        }
        
        return null;
    }
    
    public String getObjectValue(String name) {
        
        String strValue = null;
        
        if (FedoraBean.OBJ_LABEL.equals(name)) {
            strValue = this.objectLabel;
            
        } else if (FedoraBean.OBJ_CONTENTMODEL.equals(name)) {
            strValue = this.objectContentModel;
            
        } else if (FedoraBean.OBJ_CREATIONDATE.equals(name)) {
            strValue = this.objectCreationDate;
            
        } else if (FedoraBean.OBJ_LASTMODIFIED.equals(name)) {
            strValue = this.objectLastModified;
            
        } else if (FedoraBean.OBJ_OWNERID.equals(name)) {
            strValue = this.objectOwnedId;
            
        } else if (FedoraBean.OBJ_STATE.equals(name)){
            strValue = this.eObjectState.toString();
            
        }
        
        return strValue;
    }
    
    /**
     * Set object profile related data
     * @param name      object variable name
     * @param value     object variable value
     */
    public void setObjectData(String name, String value) {
        
        if (FedoraBean.OBJ_LABEL.equals(name)) {
            this.objectLabel = value;
            
        } else if (FedoraBean.OBJ_CONTENTMODEL.equals(name)) {
            this.objectContentModel = value;
            
        } else if (FedoraBean.OBJ_CREATIONDATE.equals(name)) {
            this.objectCreationDate = value;
            
        } else if (FedoraBean.OBJ_LASTMODIFIED.equals(name)) {
            this.objectLastModified = value;
            
        } else if (FedoraBean.OBJ_OWNERID.equals(name)) {
            this.objectOwnedId = value;
            
        } else if (FedoraBean.OBJ_STATE.equals(name)) {
            this.eObjectState = DataStream.State.getEnum(value.toString());
            
        }
    }
    
    public String getPID() { return this.PID; }
    
    public void setPID(String value) { this.PID = value; }
    
    public String getType() { return this.type; }
    
    public void setType(String value) { 
        this.type = value; 
        // if type is general collection
        if (type.equals(RepositoryManager.COLLECTION)) {
            // set exact collection type (base-, group- or unitcollection)
            if (captureData.getMetaDataElements().containsKey(CaptureBean.TYPE)) {
                type = captureData.getMetaDataElements().get(CaptureBean.TYPE).getValue();
            }
        }
    }
    
    public String getOldParent() { return this.oldParent; }
    
    public void setOldParent(String value) { this.oldParent = value; }
       
    /**
     * Convert datastream to XML
     * @param dataStreamID      datastream id
     * @param schemaDocument    schema for object type
     * @return XML datastream as String
     */
    public String getXMLDataStream(String dataStreamID, Document schemaDocument) throws SAXException {

        String OSA_NS = "http://www.mamk.fi";
        String DS_NS = "";
        String ELEMENT_NS = "";
        String retValue = "";
        LinkedHashMap<String, MetaDataElement> elements = null;
        Element rootElement = null;
        String rootElementName = "";
        
        if (dataStreamID.equalsIgnoreCase(DATASTREAM_DC)) {
            
            elements = this.getDataStream(DATASTREAM_DC).getMetaDataElements();
            rootElementName = dataStreamID;
            DS_NS = "http://www.mamk.fi/dc";
            ELEMENT_NS = "dc:";
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_CAPTURE)) {
            
            // sort elements according to schema
            this.sortDataStream(DATASTREAM_CAPTURE, schemaDocument);
            
            elements = this.getDataStream(DATASTREAM_CAPTURE).getMetaDataElements();
            rootElementName = dataStreamID.toLowerCase();
            ELEMENT_NS = "c:";
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_MANAGEMENT)) {
            
            elements = this.getDataStream(DATASTREAM_MANAGEMENT).getMetaDataElements();
            rootElementName = dataStreamID.toLowerCase();
            ELEMENT_NS = "m:";
            
        } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_CONTENT)) {
            
            elements = this.getDataStream(DATASTREAM_CONTENT).getMetaDataElements();
            rootElementName = dataStreamID.toLowerCase();
            ELEMENT_NS = "ft:";
        }
        
        try {
            
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            docFactory.setNamespaceAware(true);
            docFactory.setValidating(true);
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
            Document doc = null;
            
            if (dataStreamID.equalsIgnoreCase(DATASTREAM_DC) 
                || dataStreamID.equalsIgnoreCase(DATASTREAM_RELS_EXT)
                )
            {
                // root element
                doc = docBuilder.newDocument();
                rootElement = doc.createElement(rootElementName);
                doc.appendChild(rootElement);
                
            } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_CAPTURE)
                       || dataStreamID.equalsIgnoreCase(DATASTREAM_MANAGEMENT)
                       || dataStreamID.equalsIgnoreCase(DATASTREAM_CONTENT)
                       ) 
            {
                // root element
                doc = docBuilder.newDocument();
                rootElement = doc.createElement("osa:"+rootElementName);
                doc.appendChild(rootElement);

                // add additional namespaces to the root element
                doc.getDocumentElement().setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xs", "http://www.w3.org/2001/XMLSchema");
                doc.getDocumentElement().setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:osa", OSA_NS);
                
                if (dataStreamID.equalsIgnoreCase(DATASTREAM_CAPTURE)) {
                    doc.getDocumentElement().setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:c", OSA_NS);
                } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_MANAGEMENT)) {
                    doc.getDocumentElement().setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:m", OSA_NS);
                } else if (dataStreamID.equalsIgnoreCase(DATASTREAM_CONTENT)) {
                    doc.getDocumentElement().setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:ft", OSA_NS);
                }
            }
            
            Iterator<Entry<String, MetaDataElement>> iter = elements.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry<String, MetaDataElement> entry = (Map.Entry<String, MetaDataElement>) iter.next();
                MetaDataElement element = entry.getValue();
                
                // Handle multivalues
                // ingest form
                if (element.getMultivalueType().equals(FormElement.MULTIVALUE_DELIMITED) && element.getSource() == null) {
                    String[] multivalues= element.getValue().split(",");
                    for (int i=0; i<multivalues.length; i++) {
                        Element xmlElement = doc.createElementNS(DS_NS, ELEMENT_NS+element.getVisibleName());
                        xmlElement.appendChild(doc.createTextNode(multivalues[i].trim())); // using trim() to remove possible leading and trailing spaces from splitted values
                        doc.getDocumentElement().appendChild(xmlElement);
                    }
                } else if (element.getMultivalueType().equals(FormElement.MULTIVALUE_MULTIFIELD)
                           || !element.getNestedElements().isEmpty()) {
                    // parent element
                    Element parentXmlElement = null;
                    // child elements in hashmap
                    Vector<LinkedHashMap<String, MetaDataElement>> nestedElements = element.getNestedElements();
                    
                    for (int i=0; i<nestedElements.size(); i++) {
                        LinkedHashMap<String, MetaDataElement> lhm = nestedElements.get(i);
                        
                        if (lhm != null) {
                            Iterator<Entry<String, MetaDataElement>> iterLhm = lhm.entrySet().iterator();
                            int index = 0;
                            
                            while (iterLhm.hasNext()) {
                                Map.Entry<String, MetaDataElement> entryLhm = (Map.Entry<String, MetaDataElement>) iterLhm.next();
                                MetaDataElement nestedElement = entryLhm.getValue();
                                
                                if (nestedElement.getName() != null && nestedElement.getValue() != null) {
                                    
                                    if (index == 0) {
                                        // parent element
                                        parentXmlElement = doc.createElementNS(DS_NS, ELEMENT_NS+element.getVisibleName());
                                        // add parent element
                                        doc.getDocumentElement().appendChild(parentXmlElement);
                                    }
                                    // add child elements
                                    Element childElement = doc.createElement(ELEMENT_NS+nestedElement.getName());
                                    childElement.appendChild(doc.createTextNode(nestedElement.getValue()));
                                    parentXmlElement.appendChild(childElement);
                                }
                                index++;
                            }
                        }
                    }
                // move object
                // create element
                } else if (element.getValues().size() > 0) {
                    for (int i=0; i<element.getValues().size(); i++) {
                        if (element.getValues().get(i) != null) {
                            Element xmlElement = doc.createElementNS(DS_NS, ELEMENT_NS+element.getVisibleName());
                            xmlElement.appendChild(doc.createTextNode(element.getValues().get(i)));
                            doc.getDocumentElement().appendChild(xmlElement);
                        }
                    }
                }
            }
                        
            // write the content
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource source = new DOMSource(doc);    
            StreamResult result = new StreamResult(new StringWriter());
            transformer.transform(source, result);
            
            retValue = result.getWriter().toString();
            
            // validate capture datastream
            if (dataStreamID.equalsIgnoreCase(DATASTREAM_CAPTURE) && type != null) {
                this.validateDatastream(retValue, schemaDocument);
            }
            
        } catch (ParserConfigurationException e) {
            logger.error("FedoraBean:getXMLDataStream error: "+e);
        } catch (TransformerException e) {
            logger.error("FedoraBean:getXMLDataStream error: "+e);
        } catch (DOMException e) {
            logger.error("FedoraBean:getXMLDataStream error: "+e);
        }
        return retValue;
    }
    
    /**
     * Set type from Pid
     * @param pid
     */
    public void setTypeFromPID(String pid) {
        
        if (pid != null && pid.contains(":")) {
            
            String pidType = pid.split(":")[1].split("-")[0];
            if (RepositoryManager.DATATYPES_MAP.containsKey(pidType)) {
                // get object type from HashMap
                this.type = RepositoryManager.DATATYPES_MAP.get(pidType);
            } else {
                this.type = RepositoryManager.DOCUMENT;
            }
            
            // handle default_types as document
            if (pidType.equalsIgnoreCase("X")) {
                this.type = RepositoryManager.DOCUMENT;
            }
        }
    }
    
    /**
     * Validate datastream according to schema
     * @param strDocument   datastream content
     * @param schemaDoc     schema for object type (document|image|etc.)
     */
    public void validateDatastream(String strDocument, Document schemaDoc) throws SAXException {
        
        try {
            SAXParserFactory spf = SAXParserFactory.newInstance(); 
            spf.setNamespaceAware(true);
            XMLReader reader = spf.newSAXParser().getXMLReader(); 
            
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            XMLSerializer serializer = new XMLSerializer(outputStream, new OutputFormat(schemaDoc));
            serializer.serialize(schemaDoc);
            InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
            SchemaFactory xsFact = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            Schema schema = xsFact.newSchema(new StreamSource(is));
            
            // Validator
            ValidatorHandler vh = schema.newValidatorHandler(); 
            reader.setContentHandler(vh);
            
            InputSource inputSource = new InputSource(new StringReader(strDocument));
            reader.parse(inputSource);
            
        } catch (IOException e) {
            // instance document is invalid!
            logger.error("FedoraBean:validateDatastream, capture datastream is invalid: "+e);
        } catch (ParserConfigurationException e) {
            // instance document is invalid!
            logger.error("FedoraBean:validateDatastream, capture datastream is invalid: "+e);
        }
    }
    
    public CaptureBean getCaptureData() {
        return captureData;
    }

    public void setCaptureData(CaptureBean captureData) {
        this.captureData = captureData;
    }
    
    public void removeOriginalDatastream() {
        this.original = null;
    }
    
    /**
     * Re-arranges datastream according to the order of elements in schema
     * @param ds                datastream
     * @param schemaDocument    schema for object type (groupcollection|document|image|etc.)
     */
    public void sortDataStream(String ds, Document schemaDocument) {
        
        LinkedHashMap<String, MetaDataElement> elements = this.getDataStream(ds).getMetaDataElements();
        LinkedHashMap<String, MetaDataElement> orderedElements = new LinkedHashMap<String, MetaDataElement>();
        
        XPath xPath = XPathFactory.newInstance().newXPath();
        String expression = "//element[@name='capture']//element";
        NodeList result = null;

        try {
            // Elements in schema
            result= (NodeList) xPath.compile(expression).evaluate(schemaDocument, XPathConstants.NODESET);
            
        } catch (Exception e) {
            logger.error("FedoraBean:sortDataStream error, "+e);
        }
        
        // re-arrange datastream
        for (int i=0; i<result.getLength(); i++) {
            Element e = (Element)result.item(i);
            String key = e.getAttribute("name");
            MetaDataElement element = null;
            element = elements.get(key);
            
            if (element == null) {
                // check if name and visible name differs
                Iterator<Entry<String, MetaDataElement>> iter = elements.entrySet().iterator();
                while (iter.hasNext()) {
                    Map.Entry<String, MetaDataElement> entry = (Map.Entry<String, MetaDataElement>) iter.next();
                    MetaDataElement mdElement = entry.getValue();
                    if (mdElement.getVisibleName() != null && key.equals(mdElement.getVisibleName())) {
                        element = mdElement;
                        break;
                    }
                }                
            }
            
            if (element != null) {
                orderedElements.put(key, element);
            }
        }
        
        // update datastream 
        this.captureData.setMetaDataElements(orderedElements);
    }
    
    /**
     * Extract metadata using Tika
     * @throws Exception
     */
    public static HashMap<String, Object> extractMetadata(File file, String contentType) {

        HashMap<String, Object> captureMetadata = new HashMap<String, Object>();
        String PATTERN_DATE_MODEL = "([0-9]{4}\\-[0-9]{2}\\-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z)|([0-9]{4}\\-[0-9]{2}\\-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2})";
        boolean addType = false;
        
        if (contentType == null) {
            String fileExtension = FilenameUtils.getExtension(file.getPath());
            addType = true;
            
            if (fileExtension.equalsIgnoreCase("tif") 
                || fileExtension.equalsIgnoreCase("gif") 
                || fileExtension.equalsIgnoreCase("jpg") 
                || fileExtension.equalsIgnoreCase("jpf")
                || fileExtension.equalsIgnoreCase("jp2")
                || fileExtension.equalsIgnoreCase("jpeg") 
                || fileExtension.equalsIgnoreCase("png")) {
                contentType = RepositoryManager.IMAGE;
                
            } else if (fileExtension.equalsIgnoreCase("wav")
                       || fileExtension.equalsIgnoreCase("wma")) {
                contentType = RepositoryManager.AUDIO;
                
            } else if (fileExtension.equalsIgnoreCase("wmv")
                       || fileExtension.equalsIgnoreCase("m4v")
                       || fileExtension.equalsIgnoreCase("mp4")) {
                contentType = RepositoryManager.MOVINGIMAGE;
                
            } else {
                contentType = RepositoryManager.DOCUMENT;
            }
        }
        
        try {
            InputStream input = new FileInputStream(file);
            ContentHandler textHandler = new BodyContentHandler();
            Metadata metadata = new Metadata();
            Parser parser = new AutoDetectParser();
            ParseContext context = new ParseContext();
            
            parser.parse(input, textHandler, metadata, context);
            input.close();
            
            HashMap<String, String> mappings = getMappings(contentType);
            
            // metadata found by Tika
            for (String key : metadata.names()) {
                String value = metadata.get(key);
                // check if found to be mapped with capture's metadata
                if (mappings.containsKey(key)) {
                    // check date format
                    if (value.matches(PATTERN_DATE_MODEL)) {
                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
                        Object o = simpleDateFormat.parse(value);
                        value = simpleDateFormat.format((Date)o);
                    }
                    
                    captureMetadata.put(mappings.get(key), value);
                }
            }
            
            // add file information
            if (file.isFile()) {
                // add file size
                double fileSize = (double) (file.length()/1024.0);
                String unit = " kB";
                
                if (fileSize > 1000) {
                    fileSize = fileSize/1024.0;
                    unit = " MB";
                }
                
                String strFileSize = String.format("%.1f", fileSize).concat(unit);               
                captureMetadata.put("extentsize", strFileSize);
                
                // add file name
                captureMetadata.put("fileName", file.getName());
                
            }            
            
            if (addType) {
                captureMetadata.put(CaptureBean.TYPE, contentType);
            }
            
        } catch (Exception e) {
            logger.error(e);
        }
        
        return captureMetadata;
    }
    
    /**
     * Extract content using Tika
     * @throws Exception
     */
    public static String extractContent(File file) {
        String content = null;
        if (file == null) {
            return content;
        }
        
        try {
            InputStream input = new FileInputStream(file);
            ContentHandler textHandler = new BodyContentHandler();
            Metadata metadata = new Metadata();
            Parser parser = new AutoDetectParser();
            ParseContext context = new ParseContext();
            
            parser.parse(input, textHandler, metadata, context);
            input.close();
            
            content = textHandler.toString();
            // remove spaces
            content = content.replaceAll("^\\s*", " ");
            
        } catch (Exception e) {
            logger.error(e);
        }
        
        return content;
    }
    
    /**
     * Get tika/capture parameter mappings
     * @param contenttype
     * @return
     * @throws SAXException
     * @throws IOException
     * @throws ParserConfigurationException
     */
    public static HashMap<String, String> getMappings(String contentType) throws SAXException, IOException, ParserConfigurationException {
        
        HashMap<String, String> xmlMappings = new HashMap<String,String>();
        File mappingFile = new File(Osa.configFolder+"tika_mappings.xml");
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(mappingFile);
        doc.getDocumentElement().normalize();
        NodeList nodes = doc.getElementsByTagName("content");
        for (int i=0; i<nodes.getLength();i++) {
            Node node = nodes.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                if (node.getAttributes().getNamedItem("type").getNodeValue().equals(contentType)) {
                    NodeList subNodes = node.getChildNodes();
                    for (int j = 0;j < subNodes.getLength();j++) {
                        Node subNode = subNodes.item(j);
                        if (subNode.getNodeType() == Node.ELEMENT_NODE) {
                            String osaVal = subNode.getAttributes().getNamedItem("osa").getNodeValue();
                            String tikaVal = subNode.getAttributes().getNamedItem("tika").getNodeValue();
                            xmlMappings.put(tikaVal, osaVal);
                        }
                    }
                }
                
            }
        }
        return xmlMappings;
    }
    
} public class ManagementBean extends DataStream {

    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(ManagementBean.class);
    
    public static final String MANAGEMENT_DSID = "MANAGEMENT";
    public static final String MANAGEMENT_LABEL = "Management Record";
    
    public static final String DATA_isAncestor = "isAncestor";
    public static final String DATA_objectPath = "objectPath";
    
    // Constructor
    public ManagementBean(String PID) {
        super(PID);
        dsID = ManagementBean.MANAGEMENT_DSID;
    }

    /**
     * Returns the value of isAncestor
     * @return
     */
    public String getAncestor() {
        String parent = null;
        MetaDataElement element = (MetaDataElement)this.getMetaDataElements().get(DATA_isAncestor);
        if (element != null) {
            parent = element.getValue();
        }
        return parent;
    }
    
    /**
     * Set isAncestor value (ancestor = next to root object)
     * @param pid
     */
    public void setAncestor(String pid) {
        boolean newValue = true;
        
        MetaDataElement element = (MetaDataElement)this.getMetaDataElements().get(DATA_isAncestor);
        if (element != null) {
            element.removeValue();
            element.setValue(pid);
            newValue = false;
        }
        
        // Metadata value not found, create new
        if (newValue) {
            this.getMetaDataElements().put(ManagementBean.DATA_isAncestor,
                                           new MetaDataElement(ManagementBean.DATA_isAncestor, 
                                                               ManagementBean.DATA_isAncestor, 
                                                               pid, 
                                                               MetaDataElement.MetaDataType.getEnum("string")));
        }
    }
    
    /**
     * Set path value
     * @param path      object location in hierarchy
     */
    public void setPath(String path) {
        boolean newValue = true;
        
        if (!path.endsWith("/")) {
            path = path.concat("/");
        }
        
        MetaDataElement element = (MetaDataElement)this.getMetaDataElements().get(DATA_objectPath);
        if (element != null) {
            element.removeValue();
            element.setValue(path);
            newValue = false;
        }
        
        if (path != null && path != "") {
            // Metadata value not found, create new
            if (newValue) {
                this.getMetaDataElements().put(ManagementBean.DATA_objectPath,
                                               new MetaDataElement(ManagementBean.DATA_objectPath, 
                                                                   ManagementBean.DATA_objectPath, 
                                                                   path, 
                                                                   MetaDataElement.MetaDataType.getEnum("string")));
            }
        }
    }
    
    public String getPath() {
        String path = "";
        MetaDataElement element = (MetaDataElement)this.getMetaDataElements().get(DATA_objectPath);
        if (element != null) {
            path = element.getValue();
        }
        return path;
    }
    
} public class MetaDataElement implements Serializable {

    private static final long serialVersionUID = -5496828257828486275L;
    private static final Logger logger = Logger.getLogger(MetaDataElement.class);
    
    // indicates the kind of metadata
    public static enum MetaDataType {
        string,     // String
        date,       // Date
        integer,    // Integer
        link,       //
        relation,   // Object relationships
        select;     // Enum value
        
        public static MetaDataType getEnum(String s) {
            if (string.name().equalsIgnoreCase(s))
                return string;
            else if (date.name().equalsIgnoreCase(s))
                return date;
            else if (integer.name().equalsIgnoreCase(s))
                return integer;
            else if (link.name().equalsIgnoreCase(s))
                return link;
            else if (relation.name().equalsIgnoreCase(s))
                return relation;
            else if (select.name().equalsIgnoreCase(s))
                return select;
            else if (s.startsWith("text") || s.startsWith("time"))
                return string;
            throw new IllegalArgumentException("No Enum specified for this string");
        }
    }
    
    private String name;
    private String visibleName;
    private String value;
    private Vector<String> values;
    private String visibleValue;
    private MetaDataType eMetaDataType;
    // Multivalue types: FormElement.MULTIVALUE_DELIMITED / FormElement.MULTIVALUE_MULTIFIELD / FormElement.MULTIVALUE_NONE
    private String multivalueType;
    // Name of the ontology, if used in this field
    private String source;
    // Is value automatically generated
    private boolean generated;
    // For original, attachment, preview content
    private InputStream inputStream;

    // list of datastreams, where metadata belongs to
    // used, when mapping metadata to datastreams determined in contentmodel
    private List<String> datastreams;
    String datastream;
    
    // Enumerated values for field
    private List<String> enumValues;

    private Vector<LinkedHashMap<String, MetaDataElement>> nestedElements;

    // Default constructor
    public MetaDataElement() {
        this.name = null;
        this.visibleName = null;
        this.value = null;
        this.values = new Vector<String>();
        this.generated = false;
        this.eMetaDataType = MetaDataType.string;
        this.multivalueType = FormElement.MULTIVALUE_NONE;
        this.source = null;
        this.inputStream = null;       
        this.nestedElements = new Vector<LinkedHashMap<String, MetaDataElement>>();
        this.datastream = null;
        this.datastreams = new ArrayList<String>();
    }
    
    // Constructor
    public MetaDataElement(String name, String visibleName, String value, MetaDataType eType) {
        this.name = name;
        this.visibleName = visibleName;
        this.value = value;
        this.values = new Vector<String>();
        this.values.add(value);
        
        if (isRelation(name)) {
            this.eMetaDataType = MetaDataType.relation;
        } else {
            this.eMetaDataType = eType;
        }
        
        this.generated = false;
        this.multivalueType = FormElement.MULTIVALUE_NONE;
        this.source = null;
        this.inputStream = null;
        this.nestedElements = new Vector<LinkedHashMap<String, MetaDataElement>>();
        this.datastream = null;
        this.datastreams = new ArrayList<String>();
    }
    
    // Constructor
    public MetaDataElement(MetaDataElement element) {
        this.name = element.getName();
        this.visibleName = element.getVisibleName();
        this.value = element.getValue();
        this.values = new Vector<String>();
        this.values = element.getValues();
        this.generated = element.getGenerated();
        this.eMetaDataType = element.getMetaDataType();
        this.multivalueType = element.getMultivalueType();
        this.source = element.getSource();
        this.inputStream = element.getInputStream();
        this.nestedElements = new Vector<LinkedHashMap<String, MetaDataElement>>();
        this.nestedElements = element.getNestedElements(); 
        this.datastream = element.getDatastream();
        this.datastreams = element.getDatastreams();
    }
    
    // Constructor
    public MetaDataElement(String name, 
                           String visibleName, 
                           String value, 
                           MetaDataType eType, 
                           String multivalue, 
                           InputStream is,
                           Vector<LinkedHashMap<String, MetaDataElement>> nestedElements,
                           String ds) {
        this.name = name;
        this.visibleName = visibleName;
        this.value = value;
        this.values = new Vector<String>();
        this.values.add(value);
        
        if (isRelation(name)) {
            this.eMetaDataType = MetaDataType.relation;
        } else {
            this.eMetaDataType = eType;
        }
        
        this.generated = false;
        this.multivalueType = multivalue;
        this.source = null;
        this.inputStream = is;
        this.nestedElements = new Vector<LinkedHashMap<String, MetaDataElement>>();
        if (nestedElements != null) {
            this.nestedElements = nestedElements;
        }
        this.datastream = ds;
        this.datastreams = new ArrayList<String>();
        this.setDatastream(ds);
    }
    
    @Override
    public boolean equals(Object object) {
        boolean isEqual = false;       

        if (object != null && object instanceof MetaDataElement) {
            String objectName = ((MetaDataElement) object).getName();
            isEqual = (this.name.equalsIgnoreCase(objectName));
        }

        return isEqual;
    }

    /**
     * Check if metadata element has value
     * @return
     */
    public boolean isEmpty() {
        boolean retValue = true;
        if (this.getValue() != null) {
            retValue = false;
        } else if (this.getValues().size() > 0 && this.getValues().get(0) != null) {
            retValue = false;
        } else if (this.getNestedElements().size() > 0) {
            Vector<LinkedHashMap<String, MetaDataElement>> nestedElements = this.getNestedElements();
            for (LinkedHashMap<String, MetaDataElement> lhm : nestedElements) {
                if (lhm != null) {
                    Iterator<Entry<String, MetaDataElement>> iterLhm = lhm.entrySet().iterator();
                    while (iterLhm.hasNext()) {
                        Map.Entry<String, MetaDataElement> entryLhm = (Map.Entry<String, MetaDataElement>) iterLhm.next();
                        MetaDataElement nestedElement = entryLhm.getValue();
                        if (nestedElement.getValue() != null) {
                            retValue = false;
                        } else if (nestedElement.getValues().size() > 0 && nestedElement.getValues().get(0) != null) {
                            retValue = false;
                        }
                        break;
                    }
                }
            }
        }
        return retValue;
    }
    
    @Override
    public String toString() {
        return "metadataelement [name="+name+", value="+value+", values="+values.toString()+", type="+eMetaDataType.toString()+", nestedElements="+nestedElements+"]";
    }
    
    public List<String> getEnumValues() { return this.enumValues; }
    
    public void setEnumValues(List<String> value) {
        if (value != null) {
            this.enumValues = value;
        }
    }
    
    public MetaDataType getMetaDataType() { return this.eMetaDataType; }
    
    public void setMetaDataType(MetaDataType value) { this.eMetaDataType = value; }
    
    public String getName() { return this.name; }
    
    public void setName(String value) {
        this.name = value;
        if (isRelation(this.name)) {
            this.eMetaDataType = MetaDataType.relation;
        }
    }
    
    public String getVisibleName() { return this.visibleName; }
    
    public void setVisibleName(String value) {
        this.visibleName = value;
        if (isRelation(this.visibleName)) {
            this.eMetaDataType = MetaDataType.relation;
        }
    }
    
    public List<String> getDatastreams() { return this.datastreams; }
    
    public String getDatastream() { return this.datastream; }
    
    public void setDatastream(String value) {
        this.datastream = value;
        if (value != null || value != "") {
            this.datastreams.add(value);
        }
    }
    
    public String getValue() {
        return this.value;
    }
    
    public void removeValue() { 
        this.value = "";
        this.values.removeAllElements();
    }
    
    public void setValue(String value) { 
        this.value = value;
        if (!this.values.contains(value)) {
            this.values.add(value);
        }
    }
    
    public Vector<String> getValues() { 
        return this.values;
    }
    
    public void setValues(Vector<String> values) { 
        this.values = values;
    }
    
    public String getVisibleValue() {
        return visibleValue;
    }

    public void setVisibleValue(String visibleValue) {
        this.visibleValue = visibleValue;
    }
    
    public String getMultivalueType() {
        return multivalueType;
    }

    public void setMultivalueType(String value) {
        this.multivalueType = value;
    }

    public String getSource() {
        return this.source;
    }
    
    public void setSource(String value) {
        this.source = value;
    }
    
    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream value) {
        this.inputStream = value;
    }
    
    public boolean getGenerated() {
        return this.generated;
    }
    
    public void setGenerated(boolean value) {
        this.generated = value;
    }
    
    public boolean isRelation(String name) {
        boolean retValue = false;
        
        if (this.name.equalsIgnoreCase(CaptureBean.RELATION_ConformsTo)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_HasFormat)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_HasPart)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_HasRedaction)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_HasVersion)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsChildOf)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsFormatOf)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsMemberOf)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsParentOf)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsPartOf)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsRedactionOf)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsReferencedBy)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsReplacedBy)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsRequiredBy)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_IsVersionOf)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_References)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_Replaces)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_Requires)
            || this.name.equalsIgnoreCase(CaptureBean.RELATION_Source)
            )
        {
            retValue = true;
        }
        
        return retValue;
    }

    public Vector<LinkedHashMap<String, MetaDataElement>> getNestedElements() {
        return nestedElements;
    }

    public void setNestedElements(Vector<LinkedHashMap<String, MetaDataElement>> nestedElements) {
        this.nestedElements = nestedElements;
    }
    
    public void setNestedElement(String name, MetaDataElement nestedElement) {

        boolean addNewHashMap = false;
        if (nestedElements.isEmpty()) {
            LinkedHashMap<String, MetaDataElement> lhm = new LinkedHashMap<String, MetaDataElement>();
            lhm.put(name, nestedElement);
            this.nestedElements.add(lhm);
            
        } else {
            
            for (int i=0; i<this.nestedElements.size(); i++) {
                LinkedHashMap<String, MetaDataElement> currentLhm = this.nestedElements.get(i);
                if (currentLhm.containsKey(name)) {
                    addNewHashMap = true;
                } else {
                    this.nestedElements.get(i).put(name, nestedElement);
                    addNewHashMap = false;
                }
            }
            
            if (addNewHashMap) {
                LinkedHashMap<String, MetaDataElement> lhm = new LinkedHashMap<String, MetaDataElement>();
                lhm.put(name, nestedElement);
                this.nestedElements.add(lhm);
                addNewHashMap = false;
            }
        }
    }

} public static enum MetaDataType {
    string,     // String
    date,       // Date
    integer,    // Integer
    link,       //
    relation,   // Object relationships
    select;     // Enum value
        
    public static MetaDataType getEnum(String s) {
        if (string.name().equalsIgnoreCase(s))
            return string;
        else if (date.name().equalsIgnoreCase(s))
            return date;
        else if (integer.name().equalsIgnoreCase(s))
            return integer;
        else if (link.name().equalsIgnoreCase(s))
            return link;
        else if (relation.name().equalsIgnoreCase(s))
            return relation;
        else if (select.name().equalsIgnoreCase(s))
            return select;
        else if (s.startsWith("text") || s.startsWith("time"))
            return string;
        throw new IllegalArgumentException("No Enum specified for this string");
    }
} public class ThumbBean extends DataStream {
    
    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(ThumbBean.class);
    
    public static final String THUMB_DSID       = "thumb";
    public static final String THUMB_EXTENSION  = "png";
    
    // container for thumb-objects property data
    LinkedHashMap<String, DataStream> lhmElementProperties = new LinkedHashMap<String, DataStream>();
    
    // Constructor
    public ThumbBean(String PID) {
        super(PID);
        dsID = ThumbBean.THUMB_DSID;
    }
    
    public LinkedHashMap<String, DataStream> getElementProperties() { return this.lhmElementProperties; }
    public void setElementProperties(LinkedHashMap<String, DataStream> elements) { this.lhmElementProperties = elements; }
    
    public void setElementProperty(String dataStream, String propertyName, String propertyValue) {
        if (this.lhmElementProperties.containsKey(dataStream)) {
            this.lhmElementProperties.get(dataStream).setProperty(propertyName, propertyValue);
        } else {
            DataStream ds = new ThumbBean(dataStream);
            ds.setProperty(propertyName, propertyValue);
            this.lhmElementProperties.put(dataStream, ds);
        }
    }
    
    /**
     * createPreview for documents
     * @param filePath    absolute path 
     * @param pid         A persistent, unique identifier for the object
     * @param format      file format
     */
    public static File createThumbFile(String filePath, String pid, String format) {
        String fileExtension                = filePath.substring(filePath.lastIndexOf('.'), filePath.length());
        File thumbFile                      = null;
        BufferedImage bufferedImage         = null;
        BufferedImage thumb                 = null;
        // use the same path, but change the extension to png
        String thumbnailPathAndName         = FilenameUtils.removeExtension(filePath)+"."+THUMB_EXTENSION;
        FileOutputStream fileOutputStream   = null;
           
        if (fileExtension.equalsIgnoreCase(".pdf")) {
            try {
                // open the PDF file
                PDDocument document = PDDocument.load(filePath);
                                                
                PDPage firstPage = (PDPage) document.getDocumentCatalog().getAllPages().get(0);
                // get page 1 as an image
                bufferedImage = firstPage.convertToImage();
                thumb = Scalr.resize(bufferedImage, 250);
                
                //File outputfile = new File(thumbnailPathAndName);
                fileOutputStream = new FileOutputStream(thumbnailPathAndName);
                ImageIO.write(thumb, THUMB_EXTENSION, fileOutputStream);
                
                // close the pdf file
                document.close();
                
            } catch (IOException e) {
                logger.error("Error creating thumbnail for pdf file: "+e.getMessage());
            }
            
        } else if (format.equalsIgnoreCase("image")) {
            
            try {
                // Decide if or not try to make an animated gif thumb for animated gif files
                // At the moment it does not and current solution (Scalr) does not support making animated gifs
                
                if (fileExtension.equalsIgnoreCase(".tif")) {
                    SeekableStream s = new FileSeekableStream(filePath);
                    TIFFDecodeParam param = null;
                    ImageDecoder dec = ImageCodec.createImageDecoder("tiff", s, param);
                    RenderedImage op = dec.decodeAsRenderedImage(0);
                    // RenderedImage to BufferedImage
                    ColorModel cm = op.getColorModel();
                    int width = op.getWidth();
                    int height = op.getHeight();
                    WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
                    boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
                    Hashtable properties = new Hashtable();
                    String[] keys = op.getPropertyNames();
                    if (keys!=null) {
                        for (int i = 0; i < keys.length; i++) {
                            properties.put(keys[i], op.getProperty(keys[i]));
                        }
                    }
                    bufferedImage = new BufferedImage(cm, raster, isAlphaPremultiplied, properties);
                    op.copyData(raster);
                    
                    thumb = Scalr.resize(bufferedImage, 200); // Scale the original image around the width and height of 150 px, maintaining original aspect ratio
                    fileOutputStream = new FileOutputStream(thumbnailPathAndName);
                    ImageIO.write(thumb, THUMB_EXTENSION, fileOutputStream);
                    
                } else {
                    bufferedImage = ImageIO.read(new File(filePath));
                    thumb = Scalr.resize(bufferedImage, 200); // Scale the original image around the width and height of 150 px, maintaining original aspect ratio
                    fileOutputStream = new FileOutputStream(thumbnailPathAndName);
                    ImageIO.write(thumb, THUMB_EXTENSION, fileOutputStream);
                    
                }
                
            } catch (IOException e) {
                logger.error("Error creating thumbnail for image file: "+e.getMessage());
            } 

        }
        
        thumbFile = new File(thumbnailPathAndName);
        
        if (thumbFile.canRead()) {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                logger.error("Error creating thumbnail file: "+e.getMessage());
            }
        }
                
        return thumbFile;
    }
    
    public static void createThumbFileAfterUpload(String mimeType, String filename, String dir, OsaActionBeanContext context) {
        
        // Images and pdf
        if (mimeType != null && 
                mimeType.contains("image") 
                || FilenameUtils.getExtension(filename).equalsIgnoreCase("pdf")
                || FilenameUtils.getExtension(filename).equalsIgnoreCase("jpf")
                || FilenameUtils.getExtension(filename).equalsIgnoreCase("jp2")
                ) {
            String copyFilename = "";
            try {
                // Make a copy of uploaded file which the thumbnail image will be generated from
                copyFilename = THUMB_DSID+"_"+filename;
                FileUtils.copyFile(new File(dir + filename), new File(dir + copyFilename));

            } catch (IOException e) {
                e.printStackTrace();
            }

            File thumbFile = createThumbFile(dir + copyFilename, null, "image");
            // If the copied file's name does not equal the generated thumbnail's name, delete the copy.
            // This is to delete the extra image copy if the copy does not have same extension as the thumbnail (not .png).
            if (!thumbFile.getName().equals(copyFilename)) {
                new File(dir + THUMB_DSID+"_"+filename).delete();
            }
        // Rest of the formats. Uses generic "no preview" image for thumbnail 
        } else {
            String copyFilename = filename;
            String copyFileExt  = FilenameUtils.getExtension(filename);
            try {
                if (copyFileExt != null && !copyFileExt.isEmpty()) {
                    copyFilename = copyFilename.replace(copyFileExt, THUMB_EXTENSION);
                } else {
                    copyFilename = copyFilename+"."+THUMB_EXTENSION;
                }
                FileUtils.copyFile(new File(context.getServletContext().getRealPath("/img/nopreview.png")), 
                                   new File(dir + THUMB_DSID+"_"+copyFilename));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
} public class DestructionPlan {
    
    private static final Logger logger = Logger.getLogger(DestructionPlan.class);
    
    public static final String DESTRUCTIONTYPE_Dispose = "dispose";
    public static final String DESTRUCTIONTYPE_Delete = "delete";
    public static final String DESTRUCTIONREASON_Scheduled = "scheduled";
    
    private String approvalPolicy = "";
    private String disposeMethod = "";
    private boolean allowOrphans = false;
    private boolean createResidualObjects = false;
    private boolean recursiveDestruction = false; // TODO: check if needed
    private boolean scheluded = false;
    private List<String> residualDatastreams = new ArrayList<String>();
    private List<String> residualMetadata = new ArrayList<String>();
    
    public DestructionPlan(File configuration) {
        
        try {
        	
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
            Document doc = docBuilder.parse(configuration);
            doc.getDocumentElement().normalize();
            
        	NodeList disposeSettings = doc.getElementsByTagName("destruction");
            
            for (int i=0; i<disposeSettings.getLength(); i++) {

                Node item = disposeSettings.item(i);
            	if (item.getNodeType() == Node.ELEMENT_NODE)	{

            	    NodeList childNodes = item.getChildNodes();
            		for (int j=0; j<childNodes.getLength(); j++) {
            			Node subItem = childNodes.item(j);
            			if (subItem.getNodeType() == Node.ELEMENT_NODE) {
            				if (subItem.getNodeName().equalsIgnoreCase("method")) {
            					setDisposeMethod(subItem.getTextContent());
            				}
            				
            				if (subItem.getNodeName().equalsIgnoreCase("approval")) {
                                setApprovalPolicy(subItem.getTextContent());
                            }
            				
            				if (subItem.getNodeName().equalsIgnoreCase("scheduled")) {
            				    setScheluded(Boolean.parseBoolean(subItem.getTextContent()));
                            }
            				// TODO: check if needed
            				if (subItem.getNodeName().equalsIgnoreCase("recursiveDestruction")) {
            					setRecursiveDestruction(Boolean.parseBoolean(subItem.getTextContent()));
            				}
            				
            				if (subItem.getNodeName().equalsIgnoreCase("allowOrphans")) {
            				    setAllowOrphans(Boolean.parseBoolean(subItem.getTextContent()));
            				}
            				
            				if (subItem.getNodeName().equalsIgnoreCase("createResidualObjects")) {
                                setCreateResidualObjects(Boolean.parseBoolean(subItem.getTextContent()));
                            }
            				
            				if (subItem.getNodeName().equalsIgnoreCase("residualObjectDefinition")) {
            				    
            				    NodeList residualObject_metadata = doc.getElementsByTagName("metadata").item(0).getChildNodes();
            				    // get metadata to be preserved
                                for (int x=0; x<residualObject_metadata.getLength(); x++) {
                                    Node metadata = residualObject_metadata.item(x);
                                    if (metadata.getNodeType() == Node.ELEMENT_NODE) {
                                        String mdName = metadata.getAttributes().getNamedItem("name").getNodeValue();
                                        residualMetadata.add(mdName);
                                    }
                                }
            				    
            				    NodeList residualObject_datastreams = doc.getElementsByTagName("dataStreams").item(0).getChildNodes();
            				    // get datastream names to be preserved
                                for (int x=0; x<residualObject_datastreams.getLength(); x++) {
                                    Node datastream = residualObject_datastreams.item(x);
                                    if (datastream.getNodeType() == Node.ELEMENT_NODE) {
                                        String dsName = datastream.getAttributes().getNamedItem("id").getNodeValue();
                                        residualDatastreams.add(dsName);
                                    }
                                }
                            }

            			}
            		}			
            	}	
            }
    		
        } catch(Exception e) {
        	logger.error(e);
        }
        // TODO Load the disposal plan
    }
    
    public String getApprovalPolicy() {
        return approvalPolicy;
    }
    
    public void setApprovalPolicy(String approvalPolicy) {
        this.approvalPolicy = approvalPolicy;
    }
    
    public void setCreateResidualObjects(boolean createResidualObjects) {
        this.createResidualObjects = createResidualObjects;
    }
    
    public boolean isCreateResidualObjects() {
        return createResidualObjects;
    }
    
    public String getDisposeMethod() {
    	return disposeMethod;
    }
    
    public void setDisposeMethod(String disposeMethod) {
    	this.disposeMethod = disposeMethod;
    }
    
    public boolean isRecursiveDestruction() {
    	return recursiveDestruction;
    }
    
    public void setRecursiveDestruction(boolean recursiveDestruction) {
    	this.recursiveDestruction = recursiveDestruction;
    }
    
    public boolean isAllowOrphans() {
        return allowOrphans;
    }
    
    public void setAllowOrphans(boolean allowOrphans) {
        this.allowOrphans = allowOrphans;
    }
    
    public void setScheluded(boolean scheluded) {
        this.scheluded = scheluded;
    }
    
    public boolean isScheluded() {
        return scheluded;
    }
    
    public List<String> getResidualMetadata() {
        return residualMetadata;
    }
	
    public List<String> getResidualDatastreams() {
        return residualDatastreams;
    }
        
} 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;
    
    // TODO: move to solr manager class
    public static String solrServerUrl;
	
    /** 
     * 	Initializes Osa instance
     */
    public void init() throws ServletException {
        
        Properties properties = new Properties();
        try {
            
            properties.load(new FileInputStream(getServletContext().getRealPath("/") + this.getInitParameter("config")));
            
            // Directories
            // TODO check if directories exist and are accessible
            tempDirectory = properties.getProperty("osa.directory.temp");
            dataDirectory = properties.getProperty("osa.directory.data");
            logDirectory = properties.getProperty("osa.directory.log");
            importDirectory = properties.getProperty("osa.directory.import"); 
            ingestDirectory = properties.getProperty("osa.directory.ingest");
            failedDirectory = properties.getProperty("osa.directory.failed");
            uploadDirectory = properties.getProperty("osa.directory.upload");            
                        
            // Persistence
            persistence = properties.getProperty("osa.persistence");
            
            switch (persistence) {
        	case RepositoryManager.FEDORA3:
        	    String host = properties.getProperty("osa.fedora.server");
        	    String port = properties.getProperty("osa.fedora.port");
        	    String username = properties.getProperty("osa.fedora.username");
        	    String password = properties.getProperty("osa.fedora.password");
        	    String schemaPath =  getServletContext().getRealPath("/") + "WEB-INF/";
        	    
        		Osa.fedoraManager = new Fedora3Manager(host, port, username, password, schemaPath);
        		
        		// TODO: Check Fedora server status
        		break;
        		
        	case RepositoryManager.FEDORA4:
        	    // TODO: version 4 support
        	    break;
        
        	default:
        		throw new Exception("OSA instance requires a persistence module.");
            }                
            
            // Database
            systemDatabase = properties.getProperty("osa.database.system");
            
            // TODO: Add support for multiple coexisting databases.  
            switch (systemDatabase) {
                case "mysql":
                case "mariadb":
	                Osa.dbConfiguration = new DatabaseConfiguration();			
	                Osa.dbConfiguration.setDbURI(properties.getProperty("osa.mysql.connectionString"));
	                Osa.dbConfiguration.setDbDriverName(properties.getProperty("osa.mysql.driver"));
	                Osa.dbConfiguration.setDbUser(properties.getProperty("osa.mysql.user"));
	                Osa.dbConfiguration.setDbPassword(properties.getProperty("osa.mysql.password"));
	                Osa.dbConfiguration.setDbPoolMinSize(Integer.parseInt(properties.getProperty("osa.mysql.poolMinSize".trim())));
	                Osa.dbConfiguration.setDbPoolMaxSize(Integer.parseInt(properties.getProperty("osa.mysql.poolMaxSize").trim()));
	                Osa.dbConfiguration.setDbTimeout( Integer.parseInt(properties.getProperty("osa.mysql.timeout").trim()) );
	                
	                new ConnectionManager(dbConfiguration);
	                Osa.dbManager.put("sql", new MySQLManager());
	                //logger.info("Database configuration loaded.");	                
					break;
	
				default:
					throw new Exception("OSA instance requires a system database module.");
			}
            
            documentDatabase = properties.getProperty("osa.database.document");
            
            switch (documentDatabase) {
				case "mongodb":
					String mongohost = properties.getProperty("osa.mongo.host");
					int mongoport = Integer.parseInt(properties.getProperty("osa.mongo.port"));
					String mongoDefaultdb = properties.getProperty("osa.mongo.defaultdb");
					String mongoDefautCollection = properties.getProperty("osa.mongo.defaultcollection");
					
	                Osa.dbManager.put("mongo", new MongoManager(mongohost, mongoport, mongoDefaultdb, mongoDefautCollection));
	                //logger.info("Mongo configuration loaded.");	                
					break;
	
				default:
					throw new Exception("OSA instance requires a document database module.");
			}
            
            
            // Authentication
            authentication = properties.getProperty("osa.authentication");
            switch (authentication) {
            	case "ldap":
                    Osa.authManager = new LdapManager(
                            properties.getProperty("osa.ldap.server"),
                            properties.getProperty("osa.ldap.admindn"),
                            properties.getProperty("osa.ldap.managepw"),
                            properties.getProperty("osa.ldap.basedn"),
                            properties.getProperty("osa.ldap.instanceadmin")
                            );
                    
                    // TODO: Check LDAP server status
            		break;
            
            	default:
            		throw new Exception("OSA instance requires an authentication module.");
            }
          
            
            // Search
            search = properties.getProperty("osa.search");
            switch (search) {
				case "solr":
					
					// TODO Refactor inside the manager implementation
	            	Osa.solrServerUrl = properties.getProperty("osa.solr.server");

	            	Osa.searchManager = new SolrManager();
	            	
					// TODO test sorl server status
					break;

				default:
					throw new Exception("OSA instance requires a search module.");
			}

            
            // Logger
            logging = properties.getProperty("osa.logger");
            switch (logging) {
            	case "log4j":
                    String configurationFile = this.getInitParameter("logger");
                    File file = new File(getServletContext().getRealPath("/") + configurationFile);
                    if(!file.canRead()) 
                    {
                        throw new Exception("Couldn't read logger configuration file at " + file.getAbsolutePath());
                    }
                    
                    PropertyConfigurator.configure(file.getAbsolutePath()); 
            		break;
            
            	default:
            		throw new Exception("OSA instance requires a logger module.");
            }            
            
            
            // Workflows
            String engineHost = properties.getProperty("osa.workflowengine.host");
            String enginePort = properties.getProperty("osa.workflowengine.port");
            try {
                Osa.workflowManager = new WorkflowManager(engineHost, enginePort);
            } catch (Exception e) {
                logger.info("WorkflowManager not available: "+e);
            }
           
            // Load default conf.
            Osa.defaultConfiguration = properties.getProperty("osa.default.configuration");
            Osa.configFolder = getServletContext().getRealPath("/") + "WEB-INF/config/";
            Osa.defaultRoot = properties.getProperty("osa.default.root");
            
            
            // ONKI access key according to external IP address
            final String prefix = "osa.onki.key.";
            String externalIP = getIP();
            char[] chArr = prefix.toCharArray();  
            chArr[chArr.length -1]++;  
            String endPrefix = new String(chArr);
            Set<String> allNames = properties.stringPropertyNames();  
            SortedSet<String> names = new TreeSet<String>(allNames).subSet(prefix, endPrefix);
            Iterator<String> it = names.iterator();  
            while (it.hasNext()) {
                String key = it.next();
                if (key.endsWith(externalIP)) {
                    Osa.onkiAccessKey = properties.getProperty(key);
                    break;
                }
            }
            
            if (Osa.onkiAccessKey == null) {
                // Default value
                Osa.onkiAccessKey = properties.getProperty("osa.onki.key.default");
            }
            
            // Scheduled tasks
            if (properties.getProperty("osa.scheduledtasks").equals("1")) {
                String configPath =  getServletContext().getRealPath("/") + "WEB-INF/";
                taskScheduler = new TaskScheduler("AutomaticDestruction", "triggername", logDirectory, configPath);
                taskScheduler.start();
            }
            
            logger.info("OSA version [" + getVersionString() + "] + started. See log for more information.");

        } catch (Exception e) {
            logger.error("Error initializing OSA instance. " + e);
            System.exit(0);
        }
    }
    
    public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
    	throw new UnsupportedOperationException("Not supported.");	
    }
    
    public static String getVersionString() {
    	return "release 1.0 - december 2014";
    }
    
    /**
     * Get external IP address
     */
    public String getIP() throws IOException {
        String ip = "";
        URL whatismyip = new URL("http://checkip.amazonaws.com");
        BufferedReader in = new BufferedReader(new InputStreamReader(whatismyip.openStream()));
        ip = in.readLine();
        return ip;
    }

}
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.util.FileUtils;
import com.yourmediashelf.fedora.client.FedoraClient;
import com.yourmediashelf.fedora.client.FedoraClientException;
import com.yourmediashelf.fedora.client.FedoraCredentials;
import com.yourmediashelf.fedora.client.request.FedoraRequest;
import com.yourmediashelf.fedora.client.request.Ingest;
import com.yourmediashelf.fedora.client.request.ListDatastreams;
import com.yourmediashelf.fedora.client.response.FedoraResponse;
import com.yourmediashelf.fedora.client.response.FindObjectsResponse;
import com.yourmediashelf.fedora.client.response.GetDatastreamHistoryResponse;
import com.yourmediashelf.fedora.client.response.GetDatastreamResponse;
import com.yourmediashelf.fedora.client.response.GetObjectHistoryResponse;
import com.yourmediashelf.fedora.client.response.IngestResponse;
import com.yourmediashelf.fedora.client.response.ListDatastreamsResponse;
import com.yourmediashelf.fedora.client.response.RiSearchResponse;
import com.yourmediashelf.fedora.generated.access.DatastreamType;
import com.yourmediashelf.fedora.generated.access.FedoraObjectHistory;
import com.yourmediashelf.fedora.generated.management.DatastreamProfile;
import fi.mamk.osa.solr.SolrManager;
import fi.mamk.osa.ui.FormElement;
import fi.mamk.osa.ui.Gui;
import fi.mamk.osa.auth.AccessRight;
import fi.mamk.osa.auth.User;
import fi.mamk.osa.bean.AttachmentBean;
import fi.mamk.osa.bean.CaptureBean;
import fi.mamk.osa.bean.DCBean;
import fi.mamk.osa.bean.DataStream;
import fi.mamk.osa.bean.DataStream.State;
import fi.mamk.osa.bean.FedoraBean;
import fi.mamk.osa.bean.ManagementBean;
import fi.mamk.osa.bean.MetaDataElement;
import fi.mamk.osa.bean.MetaDataElement.MetaDataType;
import fi.mamk.osa.bean.ThumbBean;
import fi.mamk.osa.core.DestructionPlan;
import fi.mamk.osa.core.Osa;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.jpedal.exception.PdfException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
                    // 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);