org.roda.core.index.utils.SolrUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.roda.core.index.utils.SolrUtils.java

Source

/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE file at the root of the source
 * tree and available online at
 *
 * https://github.com/keeps/roda
 */
package org.roda.core.index.utils;

import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.xpath.XPathExpressionException;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.SolrQuery.SortClause;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.util.DateUtil;
import org.apache.solr.handler.loader.XMLLoader;
import org.roda.core.common.MetadataFileUtils;
import org.roda.core.common.PremisV3Utils;
import org.roda.core.common.RodaUtils;
import org.roda.core.common.dips.DIPUtils;
import org.roda.core.data.common.RodaConstants;
import org.roda.core.data.common.RodaConstants.DateGranularity;
import org.roda.core.data.exceptions.AuthorizationDeniedException;
import org.roda.core.data.exceptions.GenericException;
import org.roda.core.data.exceptions.NotFoundException;
import org.roda.core.data.exceptions.NotSupportedException;
import org.roda.core.data.exceptions.RequestNotValidException;
import org.roda.core.data.utils.JsonUtils;
import org.roda.core.data.v2.LiteRODAObject;
import org.roda.core.data.v2.common.OptionalWithCause;
import org.roda.core.data.v2.formats.Format;
import org.roda.core.data.v2.index.IndexResult;
import org.roda.core.data.v2.index.IndexRunnable;
import org.roda.core.data.v2.index.IsIndexed;
import org.roda.core.data.v2.index.facet.FacetFieldResult;
import org.roda.core.data.v2.index.facet.FacetParameter;
import org.roda.core.data.v2.index.facet.FacetParameter.SORT;
import org.roda.core.data.v2.index.facet.Facets;
import org.roda.core.data.v2.index.facet.SimpleFacetParameter;
import org.roda.core.data.v2.index.filter.AndFiltersParameters;
import org.roda.core.data.v2.index.filter.BasicSearchFilterParameter;
import org.roda.core.data.v2.index.filter.DateIntervalFilterParameter;
import org.roda.core.data.v2.index.filter.DateRangeFilterParameter;
import org.roda.core.data.v2.index.filter.EmptyKeyFilterParameter;
import org.roda.core.data.v2.index.filter.Filter;
import org.roda.core.data.v2.index.filter.FilterParameter;
import org.roda.core.data.v2.index.filter.FiltersParameters;
import org.roda.core.data.v2.index.filter.LongRangeFilterParameter;
import org.roda.core.data.v2.index.filter.NotSimpleFilterParameter;
import org.roda.core.data.v2.index.filter.OneOfManyFilterParameter;
import org.roda.core.data.v2.index.filter.OrFiltersParameters;
import org.roda.core.data.v2.index.filter.SimpleFilterParameter;
import org.roda.core.data.v2.index.select.SelectedItems;
import org.roda.core.data.v2.index.sort.SortParameter;
import org.roda.core.data.v2.index.sort.Sorter;
import org.roda.core.data.v2.index.sublist.Sublist;
import org.roda.core.data.v2.ip.AIP;
import org.roda.core.data.v2.ip.AIPLink;
import org.roda.core.data.v2.ip.AIPState;
import org.roda.core.data.v2.ip.DIP;
import org.roda.core.data.v2.ip.DIPFile;
import org.roda.core.data.v2.ip.File;
import org.roda.core.data.v2.ip.FileLink;
import org.roda.core.data.v2.ip.HasPermissionFilters;
import org.roda.core.data.v2.ip.IndexedAIP;
import org.roda.core.data.v2.ip.IndexedDIP;
import org.roda.core.data.v2.ip.IndexedFile;
import org.roda.core.data.v2.ip.IndexedRepresentation;
import org.roda.core.data.v2.ip.Permissions;
import org.roda.core.data.v2.ip.Permissions.PermissionType;
import org.roda.core.data.v2.ip.Representation;
import org.roda.core.data.v2.ip.RepresentationLink;
import org.roda.core.data.v2.ip.StoragePath;
import org.roda.core.data.v2.ip.TransferredResource;
import org.roda.core.data.v2.ip.metadata.DescriptiveMetadata;
import org.roda.core.data.v2.ip.metadata.FileFormat;
import org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent;
import org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent;
import org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent.PreservationMetadataEventClass;
import org.roda.core.data.v2.ip.metadata.LinkingIdentifier;
import org.roda.core.data.v2.ip.metadata.OtherMetadata;
import org.roda.core.data.v2.ip.metadata.PreservationMetadata.PreservationMetadataType;
import org.roda.core.data.v2.jobs.IndexedReport;
import org.roda.core.data.v2.jobs.Job;
import org.roda.core.data.v2.jobs.Job.JOB_STATE;
import org.roda.core.data.v2.jobs.JobStats;
import org.roda.core.data.v2.jobs.PluginType;
import org.roda.core.data.v2.jobs.Report;
import org.roda.core.data.v2.jobs.Report.PluginState;
import org.roda.core.data.v2.log.LogEntry;
import org.roda.core.data.v2.log.LogEntry.LOG_ENTRY_STATE;
import org.roda.core.data.v2.log.LogEntryParameter;
import org.roda.core.data.v2.notifications.Notification;
import org.roda.core.data.v2.risks.IndexedRisk;
import org.roda.core.data.v2.risks.Risk;
import org.roda.core.data.v2.risks.RiskIncidence;
import org.roda.core.data.v2.user.Group;
import org.roda.core.data.v2.user.RODAMember;
import org.roda.core.data.v2.user.User;
import org.roda.core.model.ModelService;
import org.roda.core.model.utils.ModelUtils;
import org.roda.core.storage.Binary;
import org.roda.core.storage.Directory;
import org.roda.core.storage.StorageService;
import org.roda.core.storage.fs.FSUtils;
import org.roda.core.util.IdUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

/**
 * Utilities class related to Apache Solr
 * 
 * @author Hlder Silva <hsilva@keep.pt>
 * @author Lus Faria <lfaria@keep.pt>
 * @author Sbastien Leroux <sleroux@keep.pt>
 */
public class SolrUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(SolrUtils.class);

    private static final String DEFAULT_QUERY_PARSER_OPERATOR = "AND";

    private static final Set<String> NON_REPEATABLE_FIELDS = new HashSet<>(Arrays.asList(RodaConstants.AIP_TITLE,
            RodaConstants.AIP_LEVEL, RodaConstants.AIP_DATE_INITIAL, RodaConstants.AIP_DATE_FINAL));

    private static Map<String, List<String>> liteFieldsForEachClass = new HashMap<>();

    /** Private empty constructor */
    private SolrUtils() {
        // do nothing
    }

    /*
     * Search & Retrieval
     * ____________________________________________________________________________________________________________________
     */

    public static <T extends IsIndexed> Long count(SolrClient index, Class<T> classToRetrieve, Filter filter)
            throws GenericException, RequestNotValidException {
        return find(index, classToRetrieve, filter, null, new Sublist(0, 0), new ArrayList<>()).getTotalCount();
    }

    public static <T extends IsIndexed> Long count(SolrClient index, Class<T> classToRetrieve, Filter filter,
            User user, boolean justActive) throws GenericException, RequestNotValidException {
        return find(index, classToRetrieve, filter, null, new Sublist(0, 0), null, user, justActive,
                new ArrayList<>()).getTotalCount();
    }

    public static <T extends IsIndexed> T retrieve(SolrClient index, Class<T> classToRetrieve, String id,
            List<String> fieldsToReturn) throws NotFoundException, GenericException {
        if (id == null) {
            throw new GenericException("Could not retrieve object from a null id");
        }

        T ret;
        try {
            SolrDocument doc = index.getById(getIndexName(classToRetrieve).get(0), id);
            if (doc != null) {
                ret = solrDocumentTo(classToRetrieve, doc, fieldsToReturn);
            } else {
                throw new NotFoundException("Could not find document " + id);
            }
        } catch (SolrServerException | IOException e) {
            throw new GenericException("Could not retrieve object from index", e);
        }
        return ret;
    }

    public static <T extends IsIndexed> List<T> retrieve(SolrClient index, Class<T> classToRetrieve,
            List<String> id, List<String> fieldsToReturn) throws NotFoundException, GenericException {
        List<T> ret = new ArrayList<>();
        try {
            int block = RodaConstants.DEFAULT_PAGINATION_VALUE;
            for (int i = 0; i < id.size(); i += block) {
                List<String> subList = id.subList(i, (i + block <= id.size() ? i + block : id.size()));
                SolrDocumentList docs = index.getById(getIndexName(classToRetrieve).get(0), subList);
                for (SolrDocument doc : docs) {
                    ret.add(solrDocumentTo(classToRetrieve, doc, fieldsToReturn));
                }
            }
        } catch (SolrServerException | IOException e) {
            throw new GenericException("Could not retrieve object from index", e);
        }
        return ret;
    }

    public static <T extends IsIndexed> IndexResult<T> find(SolrClient index, Class<T> classToRetrieve,
            Filter filter, Sorter sorter, Sublist sublist, List<String> fieldsToReturn)
            throws GenericException, RequestNotValidException {
        return find(index, classToRetrieve, filter, sorter, sublist, null, fieldsToReturn);
    }

    public static <T extends IsIndexed> IndexResult<T> find(SolrClient index, Class<T> classToRetrieve,
            Filter filter, Sorter sorter, Sublist sublist, Facets facets, List<String> fieldsToReturn)
            throws GenericException, RequestNotValidException {
        IndexResult<T> ret;
        SolrQuery query = new SolrQuery();
        query.setParam("q.op", DEFAULT_QUERY_PARSER_OPERATOR);
        query.setQuery(parseFilter(filter));
        query.setSorts(parseSorter(sorter));
        query.setStart(sublist.getFirstElementIndex());
        query.setRows(sublist.getMaximumElementCount());
        if (!fieldsToReturn.isEmpty()) {
            query.setFields(fieldsToReturn.toArray(new String[fieldsToReturn.size()]));
        }
        parseAndConfigureFacets(facets, query);

        try {
            QueryResponse response = index.query(getIndexName(classToRetrieve).get(0), query);
            ret = queryResponseToIndexResult(response, classToRetrieve, facets, fieldsToReturn);
        } catch (SolrServerException | IOException e) {
            throw new GenericException("Could not query index", e);
        } catch (SolrException e) {
            throw new RequestNotValidException(e);
        } catch (RuntimeException e) {
            throw new GenericException("Unexpected exception while querying index", e);
        }

        return ret;
    }

    public static <T extends IsIndexed> List<String> getClassLiteFields(Class<T> classToRetrieve) {
        List<String> ret;
        if (liteFieldsForEachClass.containsKey(classToRetrieve.getName())) {
            ret = liteFieldsForEachClass.get(classToRetrieve.getName());
        } else {
            try {
                ret = classToRetrieve.newInstance().liteFields();
                liteFieldsForEachClass.put(classToRetrieve.getName(), ret);
            } catch (InstantiationException | IllegalAccessException e) {
                LOGGER.error("Error instantiating object of type {}", classToRetrieve.getName(), e);
                ret = new ArrayList<>();
            }
        }
        return ret;
    }

    public static <T extends IsIndexed> IndexResult<T> find(SolrClient index, Class<T> classToRetrieve,
            Filter filter, Sorter sorter, Sublist sublist, Facets facets, User user, boolean justActive,
            List<String> fieldsToReturn) throws GenericException, RequestNotValidException {
        IndexResult<T> ret;
        SolrQuery query = new SolrQuery();
        query.setParam("q.op", DEFAULT_QUERY_PARSER_OPERATOR);
        query.setQuery(parseFilter(filter));
        query.setSorts(parseSorter(sorter));
        query.setStart(sublist.getFirstElementIndex());
        query.setRows(sublist.getMaximumElementCount());
        query.setFields(fieldsToReturn.toArray(new String[fieldsToReturn.size()]));
        parseAndConfigureFacets(facets, query);
        if (hasPermissionFilters(classToRetrieve)) {
            query.addFilterQuery(getFilterQueries(user, justActive, classToRetrieve));
        }

        try {
            QueryResponse response = index.query(getIndexName(classToRetrieve).get(0), query);
            ret = queryResponseToIndexResult(response, classToRetrieve, facets, fieldsToReturn);
        } catch (SolrServerException | IOException e) {
            throw new GenericException("Could not query index", e);
        } catch (SolrException e) {
            throw new RequestNotValidException(e.getMessage());
        } catch (RuntimeException e) {
            throw new GenericException("Unexpected exception while querying index", e);
        }

        return ret;
    }

    /*
     * "Internal" helper methods
     * ____________________________________________________________________________________________________________________
     */

    private static <T> T solrDocumentTo(Class<T> resultClass, SolrDocument doc, List<String> fieldsToReturn)
            throws GenericException {
        T ret;
        if (resultClass.equals(IndexedAIP.class)) {
            ret = resultClass.cast(solrDocumentToIndexedAIP(doc));
        } else if (resultClass.equals(IndexedRepresentation.class) || resultClass.equals(Representation.class)) {
            ret = resultClass.cast(solrDocumentToRepresentation(doc));
        } else if (resultClass.equals(LogEntry.class)) {
            ret = resultClass.cast(solrDocumentToLogEntry(doc, fieldsToReturn));
        } else if (resultClass.equals(Report.class) || resultClass.equals(IndexedReport.class)) {
            ret = resultClass.cast(solrDocumentToJobReport(doc, fieldsToReturn));
        } else if (resultClass.equals(RODAMember.class) || resultClass.equals(User.class)
                || resultClass.equals(Group.class)) {
            ret = resultClass.cast(solrDocumentToRodaMember(doc));
        } else if (resultClass.equals(TransferredResource.class)) {
            ret = resultClass.cast(solrDocumentToTransferredResource(doc));
        } else if (resultClass.equals(Job.class)) {
            ret = resultClass.cast(solrDocumentToJob(doc, fieldsToReturn));
        } else if (resultClass.equals(Risk.class) || resultClass.equals(IndexedRisk.class)) {
            ret = resultClass.cast(solrDocumentToRisk(doc));
        } else if (resultClass.equals(Format.class)) {
            ret = resultClass.cast(solrDocumentToFormat(doc));
        } else if (resultClass.equals(Notification.class)) {
            ret = resultClass.cast(solrDocumentToNotification(doc, fieldsToReturn));
        } else if (resultClass.equals(RiskIncidence.class)) {
            ret = resultClass.cast(solrDocumentToRiskIncidence(doc));
        } else if (resultClass.equals(DIP.class) || resultClass.equals(IndexedDIP.class)) {
            ret = resultClass.cast(solrDocumentToIndexedDIP(doc, fieldsToReturn));
        } else if (resultClass.equals(DIPFile.class)) {
            ret = resultClass.cast(solrDocumentToDIPFile(doc));
        } else if (resultClass.equals(IndexedFile.class)) {
            ret = resultClass.cast(solrDocumentToIndexedFile(doc));
        } else if (resultClass.equals(IndexedPreservationEvent.class)) {
            ret = resultClass.cast(solrDocumentToIndexedPreservationEvent(doc));
        } else if (resultClass.equals(IndexedPreservationAgent.class)) {
            ret = resultClass.cast(solrDocumentToIndexedPreservationAgent(doc));
        } else {
            throw new GenericException("Cannot find class index name: " + resultClass.getName());
        }
        return ret;
    }

    private static <T> SolrInputDocument toSolrDocument(Class<T> resultClass, T object)
            throws GenericException, NotSupportedException {

        SolrInputDocument ret;
        if (resultClass.equals(IndexedAIP.class)) {
            throw new NotSupportedException();
        } else if (resultClass.equals(IndexedRepresentation.class) || resultClass.equals(Representation.class)) {
            throw new NotSupportedException();
        } else if (resultClass.equals(LogEntry.class)) {
            ret = logEntryToSolrDocument((LogEntry) object);
        } else if (resultClass.equals(Report.class) || resultClass.equals(IndexedReport.class)) {
            throw new NotSupportedException();
        } else if (resultClass.equals(RODAMember.class) || resultClass.equals(User.class)
                || resultClass.equals(Group.class)) {
            ret = rodaMemberToSolrDocument((RODAMember) object);
        } else if (resultClass.equals(TransferredResource.class)) {
            ret = transferredResourceToSolrDocument((TransferredResource) object);
        } else if (resultClass.equals(Job.class)) {
            ret = jobToSolrDocument((Job) object);
        } else if (resultClass.equals(Risk.class) || resultClass.equals(IndexedRisk.class)) {
            ret = riskToSolrDocument((Risk) object, 0);
        } else if (resultClass.equals(Format.class)) {
            ret = formatToSolrDocument((Format) object);
        } else if (resultClass.equals(Notification.class)) {
            ret = notificationToSolrDocument((Notification) object);
        } else if (resultClass.equals(RiskIncidence.class)) {
            ret = riskIncidenceToSolrDocument((RiskIncidence) object);
        } else if (resultClass.equals(DIP.class) || resultClass.equals(IndexedDIP.class)) {
            ret = dipToSolrDocument((DIP) object);
        } else if (resultClass.equals(IndexedFile.class) || resultClass.equals(DIPFile.class)) {
            throw new NotSupportedException();
        } else if (resultClass.equals(IndexedPreservationEvent.class)) {
            throw new NotSupportedException();
        } else if (resultClass.equals(IndexedPreservationAgent.class)) {
            throw new NotSupportedException();
        } else {
            throw new GenericException("Cannot find class index name: " + resultClass.getName());
        }
        return ret;
    }

    public static <T extends Serializable> List<String> getIndexName(Class<T> resultClass) throws GenericException {
        List<String> indexNames = new ArrayList<>();

        // INFO 201608 nvieira: the first index name must be the "main" one
        if (resultClass.equals(AIP.class) || resultClass.equals(IndexedAIP.class)) {
            indexNames.add(RodaConstants.INDEX_AIP);
            indexNames.add(RodaConstants.INDEX_REPRESENTATION);
            indexNames.add(RodaConstants.INDEX_FILE);
            // INFO nvieira 20170207 events should be cleared on specific plugin
        } else if (resultClass.equals(Representation.class) || resultClass.equals(IndexedRepresentation.class)) {
            indexNames.add(RodaConstants.INDEX_REPRESENTATION);
        } else if (resultClass.equals(IndexedPreservationEvent.class)) {
            indexNames.add(RodaConstants.INDEX_PRESERVATION_EVENTS);
        } else if (resultClass.equals(IndexedPreservationAgent.class)) {
            indexNames.add(RodaConstants.INDEX_PRESERVATION_AGENTS);
        } else if (resultClass.equals(LogEntry.class)) {
            indexNames.add(RodaConstants.INDEX_ACTION_LOG);
        } else if (resultClass.equals(Report.class) || resultClass.equals(IndexedReport.class)) {
            indexNames.add(RodaConstants.INDEX_JOB_REPORT);
        } else if (resultClass.equals(User.class) || resultClass.equals(Group.class)
                || resultClass.equals(RODAMember.class)) {
            indexNames.add(RodaConstants.INDEX_MEMBERS);
        } else if (resultClass.equals(TransferredResource.class)) {
            indexNames.add(RodaConstants.INDEX_TRANSFERRED_RESOURCE);
        } else if (resultClass.equals(Job.class)) {
            indexNames.add(RodaConstants.INDEX_JOB);
            indexNames.add(RodaConstants.INDEX_JOB_REPORT);
        } else if (resultClass.equals(IndexedFile.class) || resultClass.equals(File.class)) {
            indexNames.add(RodaConstants.INDEX_FILE);
        } else if (resultClass.equals(Risk.class) || resultClass.equals(IndexedRisk.class)) {
            indexNames.add(RodaConstants.INDEX_RISK);
        } else if (resultClass.equals(Format.class)) {
            indexNames.add(RodaConstants.INDEX_FORMAT);
        } else if (resultClass.equals(Notification.class)) {
            indexNames.add(RodaConstants.INDEX_NOTIFICATION);
        } else if (resultClass.equals(RiskIncidence.class)) {
            indexNames.add(RodaConstants.INDEX_RISK_INCIDENCE);
        } else if (resultClass.equals(DIP.class) || resultClass.equals(IndexedDIP.class)) {
            indexNames.add(RodaConstants.INDEX_DIP);
            indexNames.add(RodaConstants.INDEX_DIP_FILE);
        } else if (resultClass.equals(DIPFile.class)) {
            indexNames.add(RodaConstants.INDEX_DIP_FILE);
        } else {
            throw new GenericException("Cannot find class index name: " + resultClass.getName());
        }

        return indexNames;
    }

    private static <T> boolean hasPermissionFilters(Class<T> resultClass) throws GenericException {
        return HasPermissionFilters.class.isAssignableFrom(resultClass);
    }

    /**
     * Method that knows how to escape characters for Solr
     * <p>
     * <code>+ - && || ! ( ) { } [ ] ^ " ~ * ? : /</code>
     * </p>
     * <p>
     * Note: chars <code>'*'</code> are not being escaped on purpose
     * </p>
     * 
     * @return a string with special characters escaped
     */
    // FIXME perhaps && and || are not being properly escaped: see how to do it
    public static String escapeSolrSpecialChars(String string) {
        return string.replaceAll("([+&|!(){}\\[\\-\\]\\^\\\\~?:\"/])", "\\\\$1");
    }

    private static List<String> objectToListString(Object object) {
        List<String> ret;
        if (object == null) {
            ret = new ArrayList<>();
        } else if (object instanceof String) {
            List<String> l = new ArrayList<>();
            l.add((String) object);
            return l;
        } else if (object instanceof List<?>) {
            List<?> l = (List<?>) object;
            ret = new ArrayList<>();
            for (Object o : l) {
                ret.add(o.toString());
            }
        } else {
            LOGGER.error("Could not convert Solr object to List<String> ({})", object.getClass().getName());
            ret = new ArrayList<>();
        }
        return ret;
    }

    public static Integer objectToInteger(Object object, Integer defaultValue) {
        Integer ret = defaultValue;
        if (object != null) {
            if (object instanceof Integer) {
                ret = (Integer) object;
            } else if (object instanceof String) {
                try {
                    ret = Integer.parseInt((String) object);
                } catch (NumberFormatException e) {
                    LOGGER.error("Could not convert Solr object to integer", e);
                }
            } else {
                LOGGER.error("Could not convert Solr object to integer ({})", object.getClass().getName());
            }
        }

        return ret;
    }

    public static Long objectToLong(Object object, Long defaultValue) {
        Long ret = defaultValue;
        if (object != null) {
            if (object instanceof Long) {
                ret = (Long) object;
            } else if (object instanceof String) {
                try {
                    ret = Long.parseLong((String) object);
                } catch (NumberFormatException e) {
                    LOGGER.error("Could not convert Solr object to long", e);
                }
            } else {
                LOGGER.error("Could not convert Solr object to long ({})", object.getClass().getName());
            }
        }
        return ret;
    }

    @SuppressWarnings("unused")
    private static Float objectToFloat(Object object) {
        Float ret;
        if (object instanceof Float) {
            ret = (Float) object;
        } else if (object instanceof String) {
            try {
                ret = Float.parseFloat((String) object);
            } catch (NumberFormatException e) {
                LOGGER.error("Could not convert Solr object to float", e);
                ret = null;
            }
        } else {
            LOGGER.error("Could not convert Solr object to float ({})", object.getClass().getName());
            ret = null;
        }
        return ret;
    }

    private static Date objectToDate(Object object) {
        Date ret;
        if (object == null) {
            ret = null;
        } else if (object instanceof Date) {
            ret = (Date) object;
        } else if (object instanceof String) {
            try {
                LOGGER.trace("Parsing date ({}) from string", object);
                ret = RodaUtils.parseDate((String) object);
            } catch (ParseException e) {
                LOGGER.error("Could not convert Solr object to date", e);
                ret = null;
            }
        } else {
            LOGGER.error("Could not convert Solr object to date, unsupported class: {}",
                    object.getClass().getName());
            ret = null;
        }

        return ret;
    }

    private static Boolean objectToBoolean(Object object, Boolean defaultValue) {
        Boolean ret = defaultValue;
        if (object != null) {
            if (object instanceof Boolean) {
                ret = (Boolean) object;
            } else if (object instanceof String) {
                ret = Boolean.parseBoolean((String) object);
            } else {
                LOGGER.error("Could not convert Solr object to Boolean ({})", object.getClass().getName());
            }
        }
        return ret;
    }

    private static String objectToString(Object object, String defaultValue) {
        String ret = defaultValue;
        if (object != null) {
            if (object instanceof String) {
                ret = (String) object;
            } else {
                LOGGER.warn("Could not convert Solr object to string, unsupported class: {}",
                        object.getClass().getName());
            }
        }
        return ret;
    }

    private static <E extends Enum<E>> E objectToEnum(Object object, Class<E> enumeration, E defaultValue) {
        E ret = defaultValue;
        if (object != null) {
            if (object instanceof String) {
                String name = (String) object;
                try {
                    ret = Enum.valueOf(enumeration, name);
                } catch (IllegalArgumentException e) {
                    LOGGER.warn("Invalid name for enumeration: {}, name: {}", enumeration.getName(), name);
                } catch (NullPointerException e) {
                    LOGGER.warn("Error parsing enumeration: {}, name: {}", enumeration.getName(), name);
                }
            } else {
                LOGGER.warn("Could not convert Solr object to enumeration: {}, unsupported class: {}",
                        enumeration.getName(), object.getClass().getName());
            }
        }
        return ret;
    }

    /**
     * @deprecated use {@link #objectToString(Object, String)} instead
     */
    @Deprecated
    private static String objectToString(Object object) {
        String ret;
        if (object == null) {
            ret = null;
        } else if (object instanceof String) {
            ret = (String) object;
        } else {
            LOGGER.warn("Could not convert Solr object to string, unsupported class: {}",
                    object.getClass().getName());
            ret = object.toString();
        }
        return ret;
    }

    private static <T extends Serializable> IndexResult<T> queryResponseToIndexResult(QueryResponse response,
            Class<T> responseClass, Facets facets, List<String> liteFields) throws GenericException {
        final SolrDocumentList docList = response.getResults();
        final List<FacetFieldResult> facetResults = processFacetFields(facets, response.getFacetFields());
        final long offset = docList.getStart();
        final long limit = docList.size();
        final long totalCount = docList.getNumFound();
        final List<T> docs = new ArrayList<>();

        for (SolrDocument doc : docList) {
            T result = solrDocumentTo(responseClass, doc, liteFields);
            docs.add(result);
        }

        return new IndexResult<>(offset, limit, totalCount, docs, facetResults);
    }

    private static List<FacetFieldResult> processFacetFields(Facets facets, List<FacetField> facetFields) {
        List<FacetFieldResult> ret = new ArrayList<>();
        FacetFieldResult facetResult;
        if (facetFields != null) {
            for (FacetField facet : facetFields) {
                LOGGER.trace("facet:{} count:{}", facet.getName(), facet.getValueCount());
                facetResult = new FacetFieldResult(facet.getName(), facet.getValueCount(),
                        facets.getParameters().get(facet.getName()).getValues());
                for (Count count : facet.getValues()) {
                    LOGGER.trace("   value:{} value:{}", count.getName(), count.getCount());
                    facetResult.addFacetValue(count.getName(), count.getName(), count.getCount());
                }
                ret.add(facetResult);
            }
        }
        return ret;

    }

    public static SolrInputDocument getDescriptiveMetadataFields(Binary binary, String metadataType,
            String metadataVersion) throws GenericException {
        SolrInputDocument doc;

        Map<String, String> parameters = new HashMap<>();
        parameters.put("prefix", RodaConstants.INDEX_OTHER_DESCRIPTIVE_DATA_PREFIX);
        Reader transformationResult = RodaUtils.applyMetadataStylesheet(binary,
                RodaConstants.CORE_CROSSWALKS_INGEST, metadataType, metadataVersion, parameters);

        try {
            XMLLoader loader = new XMLLoader();
            XMLStreamReader parser = XMLInputFactory.newInstance().createXMLStreamReader(transformationResult);

            boolean parsing = true;
            doc = null;
            while (parsing) {
                int event = parser.next();

                if (event == XMLStreamConstants.END_DOCUMENT) {
                    parser.close();
                    parsing = false;
                } else if (event == XMLStreamConstants.START_ELEMENT) {
                    String currTag = parser.getLocalName();
                    if ("doc".equals(currTag)) {
                        doc = loader.readDoc(parser);
                    }
                }
            }

        } catch (XMLStreamException | FactoryConfigurationError e) {
            throw new GenericException("Could not process descriptive metadata binary " + binary.getStoragePath(),
                    e);
        } finally {
            IOUtils.closeQuietly(transformationResult);
        }

        return doc == null ? new SolrInputDocument() : validateDescriptiveMetadataFields(doc);
    }

    private static SolrInputDocument validateDescriptiveMetadataFields(SolrInputDocument doc) {
        if (doc.get(RodaConstants.AIP_DATE_INITIAL) != null) {
            Object value = doc.get(RodaConstants.AIP_DATE_INITIAL).getValue();
            if (value instanceof String) {
                try {
                    Date d = DateUtil.parseDate((String) value);
                    doc.setField(RodaConstants.AIP_DATE_INITIAL, d);
                } catch (ParseException pe) {
                    doc.remove(RodaConstants.AIP_DATE_INITIAL);
                    doc.setField(RodaConstants.AIP_DATE_INITIAL + "_txt", value);
                }
            }
        }

        if (doc.get(RodaConstants.AIP_DATE_FINAL) != null) {
            Object value = doc.get(RodaConstants.AIP_DATE_FINAL).getValue();
            if (value instanceof String) {
                try {
                    Date d = DateUtil.parseDate((String) value);
                    doc.setField(RodaConstants.AIP_DATE_FINAL, d);
                } catch (ParseException pe) {
                    doc.remove(RodaConstants.AIP_DATE_FINAL);
                    doc.setField(RodaConstants.AIP_DATE_FINAL + "_txt", value);
                }
            }
        }

        return doc;
    }

    /*
     * Roda Filter > Apache Solr query
     * ____________________________________________________________________________________________________________________
     */

    public static String parseFilter(Filter filter) throws RequestNotValidException {
        StringBuilder ret = new StringBuilder();

        if (filter == null || filter.getParameters().isEmpty()) {
            ret.append("*:*");
        } else {
            for (FilterParameter parameter : filter.getParameters()) {
                parseFilterParameter(ret, parameter, true);
            }

            if (ret.length() == 0) {
                ret.append("*:*");
            }
        }

        LOGGER.trace("Converting filter {} to query {}", filter, ret);
        return ret.toString();
    }

    private static void parseFilterParameter(StringBuilder ret, FilterParameter parameter,
            boolean prefixWithANDOperatorIfBuilderNotEmpty) throws RequestNotValidException {
        if (parameter instanceof SimpleFilterParameter) {
            SimpleFilterParameter simplePar = (SimpleFilterParameter) parameter;
            appendExactMatch(ret, simplePar.getName(), simplePar.getValue(), true,
                    prefixWithANDOperatorIfBuilderNotEmpty);
        } else if (parameter instanceof OneOfManyFilterParameter) {
            OneOfManyFilterParameter param = (OneOfManyFilterParameter) parameter;
            appendValuesUsingOROperator(ret, param.getName(), param.getValues(),
                    prefixWithANDOperatorIfBuilderNotEmpty);
        } else if (parameter instanceof BasicSearchFilterParameter) {
            BasicSearchFilterParameter param = (BasicSearchFilterParameter) parameter;
            appendBasicSearch(ret, param.getName(), param.getValue(), "AND",
                    prefixWithANDOperatorIfBuilderNotEmpty);
        } else if (parameter instanceof EmptyKeyFilterParameter) {
            EmptyKeyFilterParameter param = (EmptyKeyFilterParameter) parameter;
            appendANDOperator(ret, true);
            ret.append("(*:* NOT " + param.getName() + ":*)");
        } else if (parameter instanceof DateRangeFilterParameter) {
            DateRangeFilterParameter param = (DateRangeFilterParameter) parameter;
            appendRange(ret, param.getName(), Date.class, param.getFromValue(), String.class,
                    processToDate(param.getToValue(), param.getGranularity(), false),
                    prefixWithANDOperatorIfBuilderNotEmpty);
        } else if (parameter instanceof DateIntervalFilterParameter) {
            DateIntervalFilterParameter param = (DateIntervalFilterParameter) parameter;
            appendRangeInterval(ret, param.getFromName(), param.getToName(), param.getFromValue(),
                    param.getToValue(), param.getGranularity(), prefixWithANDOperatorIfBuilderNotEmpty);
        } else if (parameter instanceof LongRangeFilterParameter) {
            LongRangeFilterParameter param = (LongRangeFilterParameter) parameter;
            appendRange(ret, param.getName(), Long.class, param.getFromValue(), Long.class, param.getToValue(),
                    prefixWithANDOperatorIfBuilderNotEmpty);
        } else if (parameter instanceof NotSimpleFilterParameter) {
            NotSimpleFilterParameter notSimplePar = (NotSimpleFilterParameter) parameter;
            appendNotExactMatch(ret, notSimplePar.getName(), notSimplePar.getValue(), true,
                    prefixWithANDOperatorIfBuilderNotEmpty);
        } else if (parameter instanceof OrFiltersParameters || parameter instanceof AndFiltersParameters) {
            FiltersParameters filters = (FiltersParameters) parameter;
            appendFiltersWithOperator(ret, parameter instanceof OrFiltersParameters ? "OR" : "AND",
                    filters.getValues(), prefixWithANDOperatorIfBuilderNotEmpty);
        } else {
            LOGGER.error("Unsupported filter parameter class: {}", parameter.getClass().getName());
            throw new RequestNotValidException(
                    "Unsupported filter parameter class: " + parameter.getClass().getName());
        }
    }

    private static void appendANDOperator(StringBuilder ret, boolean prefixWithANDOperatorIfBuilderNotEmpty) {
        if (prefixWithANDOperatorIfBuilderNotEmpty && ret.length() > 0) {
            ret.append(" AND ");
        }
    }

    private static void appendOROperator(StringBuilder ret, boolean prefixWithOROperatorIfBuilderNotEmpty) {
        if (prefixWithOROperatorIfBuilderNotEmpty && ret.length() > 0) {
            ret.append(" OR ");
        }
    }

    private static void appendExactMatch(StringBuilder ret, String key, String value, boolean appendDoubleQuotes,
            boolean prefixWithANDOperatorIfBuilderNotEmpty) {
        appendANDOperator(ret, prefixWithANDOperatorIfBuilderNotEmpty);
        ret.append("(").append(key).append(": ");
        if (appendDoubleQuotes) {
            ret.append("\"");
        }
        ret.append(value.replaceAll("(\")", "\\\\$1"));
        if (appendDoubleQuotes) {
            ret.append("\"");
        }
        ret.append(")");
    }

    private static void appendValuesUsingOROperator(StringBuilder ret, String key, List<String> values,
            boolean prefixWithANDOperatorIfBuilderNotEmpty) {
        if (!values.isEmpty()) {
            appendANDOperator(ret, prefixWithANDOperatorIfBuilderNotEmpty);

            ret.append("(");
            for (int i = 0; i < values.size(); i++) {
                if (i != 0) {
                    ret.append(" OR ");
                }
                appendExactMatch(ret, key, values.get(i), true, false);
            }
            ret.append(")");
        }
    }

    private static void appendBasicSearch(StringBuilder ret, String key, String value, String operator,
            boolean prefixWithANDOperatorIfBuilderNotEmpty) {
        if (StringUtils.isBlank(value)) {
            appendExactMatch(ret, key, "*", false, prefixWithANDOperatorIfBuilderNotEmpty);
        } else if (value.matches("^\".+\"$")) {
            appendExactMatch(ret, key, value.substring(1, value.length() - 1), true,
                    prefixWithANDOperatorIfBuilderNotEmpty);
        } else {
            appendWhiteSpaceTokenizedString(ret, key, value, operator, prefixWithANDOperatorIfBuilderNotEmpty);
        }
    }

    private static void appendKeyValue(StringBuilder ret, String key, String value) {
        ret.append(key).append(":").append("(").append(value).append(")");
    }

    private static void appendFiltersWithOperator(StringBuilder ret, String operator, List<FilterParameter> values,
            boolean prefixWithANDOperatorIfBuilderNotEmpty) throws RequestNotValidException {
        if (!values.isEmpty()) {
            appendANDOperator(ret, prefixWithANDOperatorIfBuilderNotEmpty);

            ret.append("(");
            for (int i = 0; i < values.size(); i++) {
                if (i != 0) {
                    ret.append(" ").append(operator).append(" ");
                }
                parseFilterParameter(ret, values.get(i), false);
            }
            ret.append(")");
        }
    }

    private static void appendWhiteSpaceTokenizedString(StringBuilder ret, String key, String value,
            String operator, boolean prefixWithANDOperatorIfBuilderNotEmpty) {
        appendANDOperator(ret, prefixWithANDOperatorIfBuilderNotEmpty);

        String[] split = value.trim().split("\\s+");
        ret.append("(");
        for (int i = 0; i < split.length; i++) {
            if (i != 0 && operator != null) {
                ret.append(" " + operator + " ");
            }
            if (split[i].matches("(AND|OR|NOT)")) {
                ret.append(key).append(": \"").append(split[i]).append("\"");
            } else {
                ret.append(key).append(": (").append(escapeSolrSpecialChars(split[i])).append(")");
            }
        }
        ret.append(")");
    }

    private static <T extends Serializable, T1 extends Serializable> void appendRange(StringBuilder ret, String key,
            Class<T> fromClass, T fromValue, Class<T1> toClass, T1 toValue,
            boolean prefixWithANDOperatorIfBuilderNotEmpty) {
        if (fromValue != null || toValue != null) {
            appendANDOperator(ret, prefixWithANDOperatorIfBuilderNotEmpty);

            ret.append("(").append(key).append(":[");
            generateRangeValue(ret, fromClass, fromValue);
            ret.append(" TO ");
            generateRangeValue(ret, toClass, toValue);
            ret.append("])");
        }
    }

    private static <T extends Serializable> void generateRangeValue(StringBuilder ret, Class<T> valueClass,
            T value) {
        if (value != null) {
            if (valueClass.equals(Date.class)) {
                String date = DateUtil.getThreadLocalDateFormat().format(Date.class.cast(value));
                LOGGER.trace("Appending date value \"{}\" to range", date);
                ret.append(date);
            } else if (valueClass.equals(Long.class)) {
                ret.append(Long.class.cast(value));
            } else if (valueClass.equals(String.class)) {
                ret.append(String.class.cast(value));
            } else {
                LOGGER.error("Cannot process range of the type {}", valueClass);
            }
        } else {
            ret.append("*");
        }
    }

    private static void appendRangeInterval(StringBuilder ret, String fromKey, String toKey, Date fromValue,
            Date toValue, DateGranularity granularity, boolean prefixWithANDOperatorIfBuilderNotEmpty) {
        if (fromValue != null || toValue != null) {
            appendANDOperator(ret, prefixWithANDOperatorIfBuilderNotEmpty);
            ret.append("(");

            ret.append(fromKey).append(":[");
            ret.append(processFromDate(fromValue));
            ret.append(" TO ");
            ret.append(processToDate(toValue, granularity));
            ret.append("]").append(" OR ");

            ret.append(toKey).append(":[");
            ret.append(processFromDate(fromValue));
            ret.append(" TO ");
            ret.append(processToDate(toValue, granularity));
            ret.append("]");

            if (fromValue != null && toValue != null) {
                ret.append(" OR ").append("(").append(fromKey).append(":[* TO ")
                        .append(processToDate(fromValue, granularity)).append("]");
                ret.append(" AND ").append(toKey).append(":[").append(processFromDate(toValue)).append(" TO *]")
                        .append(")");
            }

            ret.append(")");
        }
    }

    private static String processFromDate(Date fromValue) {
        final String ret;

        if (fromValue != null) {
            return DateUtil.getThreadLocalDateFormat().format(fromValue);
        } else {
            ret = "*";
        }

        return ret;
    }

    private static String processToDate(Date toValue, DateGranularity granularity) {
        return processToDate(toValue, granularity, true);
    }

    private static String processToDate(Date toValue, DateGranularity granularity, boolean returnAsteriskOnNull) {
        final String ret;
        StringBuilder sb = new StringBuilder();
        if (toValue != null) {
            sb.append(DateUtil.getThreadLocalDateFormat().format(toValue));
            switch (granularity) {
            case YEAR:
                sb.append("+1YEAR-1MILLISECOND");
                break;
            case MONTH:
                sb.append("+1MONTH-1MILLISECOND");
                break;
            case DAY:
                sb.append("+1DAY-1MILLISECOND");
                break;
            case HOUR:
                sb.append("+1HOUR-1MILLISECOND");
                break;
            case MINUTE:
                sb.append("+1MINUTE-1MILLISECOND");
                break;
            case SECOND:
                sb.append("+1SECOND-1MILLISECOND");
                break;
            default:
                // do nothing
                break;
            }
            ret = sb.toString();
        } else {
            ret = returnAsteriskOnNull ? "*" : null;
        }
        return ret;
    }

    private static void appendNotExactMatch(StringBuilder ret, String key, String value, boolean appendDoubleQuotes,
            boolean prefixWithANDOperatorIfBuilderNotEmpty) {
        appendExactMatch(ret, "*:* -" + key, value, appendDoubleQuotes, prefixWithANDOperatorIfBuilderNotEmpty);
    }

    public static String getLastScanDate(Date scanDate) {
        return DateUtil.getThreadLocalDateFormat().format(scanDate);
    }

    /*
     * Roda Sorter > Apache Solr Sort clauses
     * ____________________________________________________________________________________________________________________
     */
    public static List<SortClause> parseSorter(Sorter sorter) {
        List<SortClause> ret = new ArrayList<>();
        if (sorter != null) {
            for (SortParameter sortParameter : sorter.getParameters()) {
                ret.add(new SortClause(sortParameter.getName(),
                        sortParameter.isDescending() ? ORDER.desc : ORDER.asc));
            }
        }
        return ret;
    }

    /*
     * Roda Facets > Apache Solr Facets
     * ____________________________________________________________________________________________________________________
     */
    private static void parseAndConfigureFacets(Facets facets, SolrQuery query) {
        if (facets != null) {
            query.setFacetSort(getSolrFacetParameterSortValue(FacetParameter.DEFAULT_SORT));
            if (!"".equals(facets.getQuery())) {
                query.addFacetQuery(facets.getQuery());
            }
            StringBuilder filterQuery = new StringBuilder();
            for (Entry<String, FacetParameter> parameter : facets.getParameters().entrySet()) {
                FacetParameter facetParameter = parameter.getValue();
                setSolrFacetParameterSort(query, facetParameter);

                if (facetParameter instanceof SimpleFacetParameter) {
                    setQueryFacetParameter(query, (SimpleFacetParameter) facetParameter);
                    appendValuesUsingOROperator(filterQuery, facetParameter.getName(),
                            ((SimpleFacetParameter) facetParameter).getValues(), true);
                } else {
                    LOGGER.error("Unsupported facet parameter class: {}", facetParameter.getClass().getName());
                }
            }
            if (filterQuery.length() > 0) {
                query.addFilterQuery(filterQuery.toString());
                LOGGER.trace("Query after defining facets: {}", query);
            }
        }
    }

    private static void setSolrFacetParameterSort(SolrQuery query, FacetParameter facetParameter) {
        if (FacetParameter.DEFAULT_SORT != facetParameter.getSort()) {
            query.add(String.format("f.%s.facet.sort", facetParameter.getName()),
                    getSolrFacetParameterSortValue(facetParameter.getSort()));
        }
    }

    private static String getSolrFacetParameterSortValue(SORT facetSort) {
        return facetSort == SORT.INDEX ? FacetParams.FACET_SORT_INDEX : FacetParams.FACET_SORT_COUNT;
    }

    private static void setQueryFacetParameter(SolrQuery query, SimpleFacetParameter facetParameter) {
        query.addFacetField(facetParameter.getName());

        query.add(String.format("f.%s.facet.mincount", facetParameter.getName()),
                String.valueOf(facetParameter.getMinCount()));
        query.add(String.format("f.%s.facet.limit", facetParameter.getName()),
                String.valueOf(facetParameter.getLimit()));

    }

    /*
     * Roda user > Apache Solr filter query
     * ____________________________________________________________________________________________________________________
     */
    private static <T extends IsIndexed> String getFilterQueries(User user, boolean justActive,
            Class<T> classToRetrieve) {

        StringBuilder fq = new StringBuilder();

        // TODO find a better way to define admin super powers
        if (user != null && !RodaConstants.ADMIN.equals(user.getName())) {
            fq.append("(");
            String usersKey = RodaConstants.INDEX_PERMISSION_USERS_PREFIX + PermissionType.READ;
            appendExactMatch(fq, usersKey, user.getId(), true, false);

            String groupsKey = RodaConstants.INDEX_PERMISSION_GROUPS_PREFIX + PermissionType.READ;
            appendValuesUsingOROperatorForQuery(fq, groupsKey, new ArrayList<>(user.getGroups()), true);

            fq.append(")");
        }

        if (justActive && !IndexedDIP.class.equals(classToRetrieve) && !DIPFile.class.equals(classToRetrieve)) {
            appendExactMatch(fq, RodaConstants.STATE, AIPState.ACTIVE.toString(), true, true);
        }

        return fq.toString();
    }

    private static void appendValuesUsingOROperatorForQuery(StringBuilder ret, String key, List<String> values,
            boolean prependWithOrIfNeeded) {
        if (!values.isEmpty()) {
            if (prependWithOrIfNeeded) {
                appendOROperator(ret, true);
            } else {
                appendANDOperator(ret, true);
            }

            ret.append("(");
            for (int i = 0; i < values.size(); i++) {
                if (i != 0) {
                    ret.append(" OR ");
                }
                appendExactMatch(ret, key, values.get(i), true, false);
            }
            ret.append(")");
        }
    }

    /*
     * Apache Solr helper methods
     * ____________________________________________________________________________________________________________________
     */
    private static void commit(SolrClient index, String... collections) {

        boolean waitFlush = false;
        boolean waitSearcher = true;
        boolean softCommit = true;

        for (String collection : collections) {
            try {
                index.commit(collection, waitFlush, waitSearcher, softCommit);
            } catch (SolrServerException | IOException e) {
                LOGGER.error("Error commiting into collection: {}", collection, e);
            }
        }
    }

    public static void commit(SolrClient index, List<Class<? extends IsIndexed>> resultClasses)
            throws GenericException {
        List<String> collections = new ArrayList<>();
        for (Class<? extends IsIndexed> resultClass : resultClasses) {
            collections.add(getIndexName(resultClass).get(0));
        }

        commit(index, collections.toArray(new String[] {}));
    }

    @SafeVarargs
    public static void commit(SolrClient index, Class<? extends IsIndexed>... resultClasses)
            throws GenericException {
        commit(index, Arrays.asList(resultClasses));
    }

    public static <T extends IsIndexed> void create(SolrClient index, Class<T> classToCreate, T instance)
            throws GenericException {
        try {
            index.add(getIndexName(classToCreate).get(0), toSolrDocument(classToCreate, instance));
        } catch (SolrServerException | IOException | NotSupportedException e) {
            throw new GenericException("Error adding instance to index", e);
        }
    }

    /*
     * Crosswalks: RODA Objects <-> Apache Solr documents
     * ____________________________________________________________________________________________________________________
     */

    public static IndexedAIP solrDocumentToIndexedAIP(SolrDocument doc) {
        final String id = objectToString(doc.get(RodaConstants.INDEX_UUID), null);
        AIPState state = null;

        if (doc.containsKey(RodaConstants.STATE)) {
            state = AIPState
                    .valueOf(objectToString(doc.get(RodaConstants.STATE), AIPState.getDefault().toString()));
        }

        final String parentId = objectToString(doc.get(RodaConstants.AIP_PARENT_ID), null);
        final List<String> ingestSIPIds = objectToListString(doc.get(RodaConstants.INGEST_SIP_IDS));
        final String ingestJobId = objectToString(doc.get(RodaConstants.INGEST_JOB_ID), "");
        final List<String> ingestUpdateJobIds = objectToListString(doc.get(RodaConstants.INGEST_UPDATE_JOB_IDS));
        final List<String> ancestors = objectToListString(doc.get(RodaConstants.AIP_ANCESTORS));
        final List<String> levels = objectToListString(doc.get(RodaConstants.AIP_LEVEL));
        final List<String> titles = objectToListString(doc.get(RodaConstants.AIP_TITLE));
        final List<String> descriptions = objectToListString(doc.get(RodaConstants.AIP_DESCRIPTION));
        final Date dateInitial = objectToDate(doc.get(RodaConstants.AIP_DATE_INITIAL));
        final Date dateFinal = objectToDate(doc.get(RodaConstants.AIP_DATE_FINAL));
        final Long numberOfSubmissionFiles = objectToLong(doc.get(RodaConstants.AIP_NUMBER_OF_SUBMISSION_FILES),
                0L);
        final Long numberOfDocumentationFiles = objectToLong(
                doc.get(RodaConstants.AIP_NUMBER_OF_DOCUMENTATION_FILES), 0L);
        final Long numberOfSchemaFiles = objectToLong(doc.get(RodaConstants.AIP_NUMBER_OF_SCHEMA_FILES), 0L);

        final Boolean hasRepresentations = objectToBoolean(doc.get(RodaConstants.AIP_HAS_REPRESENTATIONS),
                Boolean.FALSE);
        final Boolean ghost = objectToBoolean(doc.get(RodaConstants.AIP_GHOST), Boolean.FALSE);

        Permissions permissions = getPermissions(doc);
        final String title = titles.isEmpty() ? null : titles.get(0);
        final String description = descriptions.isEmpty() ? null : descriptions.get(0);

        String level;
        if (ghost) {
            level = RodaConstants.AIP_GHOST;
        } else {
            level = levels.isEmpty() ? null : levels.get(0);
        }

        return new IndexedAIP(id, state, level, title, dateInitial, dateFinal, description, parentId, ancestors,
                permissions, numberOfSubmissionFiles, numberOfDocumentationFiles, numberOfSchemaFiles,
                hasRepresentations, ghost).setIngestSIPIds(ingestSIPIds).setIngestJobId(ingestJobId)
                        .setIngestUpdateJobIds(ingestUpdateJobIds);
    }

    public static SolrInputDocument aipToSolrInputDocument(AIP aip, List<String> ancestors, ModelService model,
            boolean safemode)
            throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException {
        SolrInputDocument ret = new SolrInputDocument();

        ret.addField(RodaConstants.INDEX_UUID, aip.getId());
        ret.addField(RodaConstants.AIP_ID, aip.getId());
        ret.addField(RodaConstants.AIP_PARENT_ID, aip.getParentId());
        ret.addField(RodaConstants.STATE, aip.getState().toString());

        ret.addField(RodaConstants.INGEST_SIP_IDS, aip.getIngestSIPIds());
        ret.addField(RodaConstants.INGEST_JOB_ID, aip.getIngestJobId());
        ret.addField(RodaConstants.INGEST_UPDATE_JOB_IDS, aip.getIngestUpdateJobIds());

        ret.addField(RodaConstants.AIP_ANCESTORS, ancestors);

        List<String> descriptiveMetadataIds = aip.getDescriptiveMetadata().stream().map(dm -> dm.getId())
                .collect(Collectors.toList());

        ret.addField(RodaConstants.AIP_DESCRIPTIVE_METADATA_ID, descriptiveMetadataIds);

        List<String> representationIds = aip.getRepresentations().stream().map(r -> r.getId())
                .collect(Collectors.toList());
        ret.addField(RodaConstants.AIP_REPRESENTATION_ID, representationIds);
        ret.addField(RodaConstants.AIP_HAS_REPRESENTATIONS, !representationIds.isEmpty());

        setPermissions(aip.getPermissions(), ret);

        ret.addField(RodaConstants.AIP_GHOST, aip.getGhost() != null ? aip.getGhost() : false);

        if (!safemode) {
            // guarding against repeated fields
            Set<String> usedNonRepeatableFields = new HashSet<>();

            for (DescriptiveMetadata metadata : aip.getDescriptiveMetadata()) {
                StoragePath storagePath = ModelUtils.getDescriptiveMetadataStoragePath(aip.getId(),
                        metadata.getId());
                Binary binary = model.getStorage().getBinary(storagePath);
                try {
                    SolrInputDocument fields = getDescriptiveMetadataFields(binary, metadata.getType(),
                            metadata.getVersion());
                    for (SolrInputField field : fields) {
                        if (NON_REPEATABLE_FIELDS.contains(field.getName())) {
                            boolean added = usedNonRepeatableFields.add(field.getName());
                            if (added) {
                                ret.addField(field.getName(), field.getValue(), field.getBoost());
                            }
                        } else {
                            ret.addField(field.getName(), field.getValue(), field.getBoost());
                        }
                    }
                } catch (GenericException e) {
                    LOGGER.info("Problem processing descriptive metadata: {}", e.getMessage());
                } catch (Exception e) {
                    LOGGER.error("Error processing descriptive metadata: {}", metadata, e);
                }
            }
        }

        // Calculate number of documentation and schema files
        StorageService storage = model.getStorage();

        Long numberOfSubmissionFiles;
        try {
            Directory submissionDirectory = model.getSubmissionDirectory(aip.getId());
            numberOfSubmissionFiles = storage.countResourcesUnderDirectory(submissionDirectory.getStoragePath(),
                    true);
        } catch (NotFoundException e) {
            numberOfSubmissionFiles = 0L;
        }

        Long numberOfDocumentationFiles;
        try {
            Directory documentationDirectory = model.getDocumentationDirectory(aip.getId());
            numberOfDocumentationFiles = storage
                    .countResourcesUnderDirectory(documentationDirectory.getStoragePath(), true);
        } catch (NotFoundException e) {
            numberOfDocumentationFiles = 0L;
        }

        Long numberOfSchemaFiles;
        try {
            Directory schemasDirectory = model.getSchemasDirectory(aip.getId());
            numberOfSchemaFiles = storage.countResourcesUnderDirectory(schemasDirectory.getStoragePath(), true);
        } catch (NotFoundException e) {
            numberOfSchemaFiles = 0L;
        }

        ret.addField(RodaConstants.AIP_NUMBER_OF_SUBMISSION_FILES, numberOfSubmissionFiles);
        ret.addField(RodaConstants.AIP_NUMBER_OF_DOCUMENTATION_FILES, numberOfDocumentationFiles);
        ret.addField(RodaConstants.AIP_NUMBER_OF_SCHEMA_FILES, numberOfSchemaFiles);

        return ret;
    }

    public static IndexedRepresentation solrDocumentToRepresentation(SolrDocument doc) {
        final String uuid = objectToString(doc.get(RodaConstants.INDEX_UUID), null);
        final String id = objectToString(doc.get(RodaConstants.REPRESENTATION_ID), null);
        final String aipId = objectToString(doc.get(RodaConstants.REPRESENTATION_AIP_ID), null);
        final Boolean original = objectToBoolean(doc.get(RodaConstants.REPRESENTATION_ORIGINAL), Boolean.FALSE);
        final String type = objectToString(doc.get(RodaConstants.REPRESENTATION_TYPE), null);

        final Long sizeInBytes = objectToLong(doc.get(RodaConstants.REPRESENTATION_SIZE_IN_BYTES), 0L);
        final Long totalNumberOfFiles = objectToLong(doc.get(RodaConstants.REPRESENTATION_NUMBER_OF_DATA_FILES),
                0L);

        final Long numberOfDocumentationFiles = objectToLong(
                doc.get(RodaConstants.REPRESENTATION_NUMBER_OF_DOCUMENTATION_FILES), 0L);
        final Long numberOfSchemaFiles = objectToLong(doc.get(RodaConstants.REPRESENTATION_NUMBER_OF_SCHEMA_FILES),
                0L);

        final List<String> ancestors = objectToListString(doc.get(RodaConstants.AIP_ANCESTORS));

        return new IndexedRepresentation(uuid, id, aipId, Boolean.TRUE.equals(original), type, sizeInBytes,
                totalNumberOfFiles, numberOfDocumentationFiles, numberOfSchemaFiles, ancestors);
    }

    public static SolrInputDocument representationToSolrDocument(AIP aip, Representation rep, Long sizeInBytes,
            Long numberOfDataFiles, Long numberOfDocumentationFiles, Long numberOfSchemaFiles,
            List<String> ancestors) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, IdUtils.getRepresentationId(rep));
        doc.addField(RodaConstants.REPRESENTATION_ID, rep.getId());
        doc.addField(RodaConstants.REPRESENTATION_AIP_ID, rep.getAipId());
        doc.addField(RodaConstants.REPRESENTATION_ORIGINAL, rep.isOriginal());
        doc.addField(RodaConstants.REPRESENTATION_TYPE, rep.getType());

        doc.addField(RodaConstants.REPRESENTATION_SIZE_IN_BYTES, sizeInBytes);
        doc.addField(RodaConstants.REPRESENTATION_NUMBER_OF_DATA_FILES, numberOfDataFiles);
        doc.addField(RodaConstants.REPRESENTATION_NUMBER_OF_DOCUMENTATION_FILES, numberOfDocumentationFiles);
        doc.addField(RodaConstants.REPRESENTATION_NUMBER_OF_SCHEMA_FILES, numberOfSchemaFiles);

        // indexing active state and permissions
        doc.addField(RodaConstants.STATE, aip.getState().toString());
        doc.addField(RodaConstants.INGEST_SIP_IDS, aip.getIngestSIPIds());
        doc.addField(RodaConstants.INGEST_JOB_ID, aip.getIngestJobId());
        doc.addField(RodaConstants.INGEST_UPDATE_JOB_IDS, aip.getIngestUpdateJobIds());
        doc.addField(RodaConstants.REPRESENTATION_ANCESTORS, ancestors);
        setPermissions(aip.getPermissions(), doc);

        return doc;
    }

    public static SolrInputDocument fileToSolrDocument(AIP aip, File file, List<String> ancestors) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, IdUtils.getFileId(file));
        List<String> path = file.getPath();
        doc.addField(RodaConstants.FILE_PATH, path);
        if (path != null && !path.isEmpty()) {
            List<String> ancestorsPath = getFileAncestorsPath(file.getAipId(), file.getRepresentationId(), path);
            if (!ancestorsPath.isEmpty()) {
                doc.addField(RodaConstants.FILE_PARENT_UUID, ancestorsPath.get(ancestorsPath.size() - 1));
                doc.addField(RodaConstants.FILE_ANCESTORS_PATH, ancestorsPath);
            }
        }
        doc.addField(RodaConstants.FILE_AIP_ID, file.getAipId());
        doc.addField(RodaConstants.FILE_FILE_ID, file.getId());
        doc.addField(RodaConstants.FILE_REPRESENTATION_ID, file.getRepresentationId());
        doc.addField(RodaConstants.FILE_REPRESENTATION_UUID,
                IdUtils.getRepresentationId(file.getAipId(), file.getRepresentationId()));
        doc.addField(RodaConstants.FILE_ISDIRECTORY, file.isDirectory());

        // extra-fields
        try {
            StoragePath filePath = ModelUtils.getFileStoragePath(file);
            doc.addField(RodaConstants.FILE_STORAGEPATH, FSUtils.getStoragePathAsString(filePath, false));
        } catch (RequestNotValidException e) {
            LOGGER.warn("Could not index file storage path", e);
        }

        String fileId = file.getId();
        if (!file.isDirectory() && fileId.contains(".") && !fileId.startsWith(".")) {
            String extension = fileId.substring(fileId.lastIndexOf('.') + 1);
            doc.addField(RodaConstants.FILE_EXTENSION, extension);
        }

        // indexing AIP inherited info
        doc.addField(RodaConstants.STATE, aip.getState().toString());
        doc.addField(RodaConstants.INGEST_SIP_IDS, aip.getIngestSIPIds());
        doc.addField(RodaConstants.INGEST_JOB_ID, aip.getIngestJobId());
        doc.addField(RodaConstants.INGEST_UPDATE_JOB_IDS, aip.getIngestUpdateJobIds());
        doc.addField(RodaConstants.FILE_ANCESTORS, ancestors);
        setPermissions(aip.getPermissions(), doc);

        return doc;
    }

    private static List<String> getFileAncestorsPath(String aipId, String representationId, List<String> path) {
        List<String> parentFileDirectoryPath = new ArrayList<>();
        List<String> ancestorsPath = new ArrayList<>();

        parentFileDirectoryPath.addAll(path);

        while (!parentFileDirectoryPath.isEmpty()) {
            int lastElementIndex = parentFileDirectoryPath.size() - 1;
            String parentFileId = parentFileDirectoryPath.get(lastElementIndex);
            parentFileDirectoryPath.remove(lastElementIndex);
            ancestorsPath.add(0, IdUtils.getFileId(aipId, representationId, parentFileDirectoryPath, parentFileId));
        }

        return ancestorsPath;
    }

    public static IndexedFile solrDocumentToIndexedFile(SolrDocument doc) {
        String uuid = objectToString(doc.get(RodaConstants.INDEX_UUID), null);
        String aipId = objectToString(doc.get(RodaConstants.FILE_AIP_ID), null);
        String representationId = objectToString(doc.get(RodaConstants.FILE_REPRESENTATION_ID), null);
        String fileId = objectToString(doc.get(RodaConstants.FILE_FILE_ID), null);
        List<String> path = objectToListString(doc.get(RodaConstants.FILE_PATH));

        String representationUUID = objectToString(doc.get(RodaConstants.FILE_REPRESENTATION_UUID), null);
        String parentUUID = objectToString(doc.get(RodaConstants.FILE_PARENT_UUID), null);
        List<String> ancestorsPath = objectToListString(doc.get(RodaConstants.FILE_ANCESTORS_PATH));

        String originalName = objectToString(doc.get(RodaConstants.FILE_ORIGINALNAME), null);
        List<String> hash = objectToListString(doc.get(RodaConstants.FILE_HASH));
        long size = objectToLong(doc.get(RodaConstants.FILE_SIZE), 0L);
        boolean isDirectory = objectToBoolean(doc.get(RodaConstants.FILE_ISDIRECTORY), Boolean.FALSE);
        String storagePath = objectToString(doc.get(RodaConstants.FILE_STORAGEPATH), null);

        // format
        String formatDesignationName = objectToString(doc.get(RodaConstants.FILE_FILEFORMAT), null);
        String formatDesignationVersion = objectToString(doc.get(RodaConstants.FILE_FORMAT_VERSION), null);
        String mimetype = objectToString(doc.get(RodaConstants.FILE_FORMAT_MIMETYPE), null);
        String pronom = objectToString(doc.get(RodaConstants.FILE_PRONOM), null);
        String extension = objectToString(doc.get(RodaConstants.FILE_EXTENSION), null);
        // FIXME how to restore format registries
        Map<String, String> formatRegistries = new HashMap<>();

        // technical features
        String creatingApplicationName = objectToString(doc.get(RodaConstants.FILE_CREATING_APPLICATION_NAME),
                null);
        String creatingApplicationVersion = objectToString(doc.get(RodaConstants.FILE_CREATING_APPLICATION_VERSION),
                null);
        String dateCreatedByApplication = objectToString(doc.get(RodaConstants.FILE_DATE_CREATED_BY_APPLICATION),
                null);
        final List<String> ancestors = objectToListString(doc.get(RodaConstants.AIP_ANCESTORS));

        // handle other properties
        Map<String, List<String>> otherProperties = new HashMap<>();
        for (String fieldName : doc.getFieldNames()) {
            if (fieldName.endsWith("_txt")) {
                List<String> otherProperty = objectToListString(doc.get(fieldName));
                otherProperties.put(fieldName, otherProperty);
            }

        }

        FileFormat fileFormat = new FileFormat(formatDesignationName, formatDesignationVersion, mimetype, pronom,
                extension, formatRegistries);

        return new IndexedFile(uuid, parentUUID, aipId, representationId, representationUUID, path, ancestorsPath,
                fileId, fileFormat, originalName, size, isDirectory, creatingApplicationName,
                creatingApplicationVersion, dateCreatedByApplication, hash, storagePath, ancestors,
                otherProperties);
    }

    public static SolrInputDocument addOtherPropertiesToIndexedFile(String prefix,
            OtherMetadata otherMetadataBinary, ModelService model, SolrClient index)
            throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException,
            ParserConfigurationException, SAXException, IOException, XPathExpressionException, SolrServerException {
        SolrDocument solrDocument = index.getById(RodaConstants.INDEX_FILE,
                IdUtils.getFileId(otherMetadataBinary.getAipId(), otherMetadataBinary.getRepresentationId(),
                        otherMetadataBinary.getFileDirectoryPath(), otherMetadataBinary.getFileId()));

        Binary binary = model.retrieveOtherMetadataBinary(otherMetadataBinary);
        Map<String, List<String>> otherProperties = MetadataFileUtils.parseBinary(binary);

        for (Map.Entry<String, List<String>> entry : otherProperties.entrySet()) {
            solrDocument.setField(prefix + entry.getKey(), entry.getValue());
        }
        return solrDocumentToSolrInputDocument(solrDocument);

    }

    private static LogEntry solrDocumentToLogEntry(SolrDocument doc, List<String> fieldsToReturn) {
        final String id = objectToString(doc.get(RodaConstants.LOG_ID), null);
        final String actionComponent = objectToString(doc.get(RodaConstants.LOG_ACTION_COMPONENT), null);
        final String actionMethod = objectToString(doc.get(RodaConstants.LOG_ACTION_METHOD), null);
        final String address = objectToString(doc.get(RodaConstants.LOG_ADDRESS), null);
        final Date datetime = objectToDate(doc.get(RodaConstants.LOG_DATETIME));
        final long duration = objectToLong(doc.get(RodaConstants.LOG_DURATION), 0L);

        final String parameters = objectToString(doc.get(RodaConstants.LOG_PARAMETERS), null);
        final String relatedObjectId = objectToString(doc.get(RodaConstants.LOG_RELATED_OBJECT_ID), null);
        final String username = objectToString(doc.get(RodaConstants.LOG_USERNAME), null);
        LOG_ENTRY_STATE state = null;

        if (doc.containsKey(RodaConstants.LOG_STATE)) {
            state = LOG_ENTRY_STATE
                    .valueOf(objectToString(doc.get(RodaConstants.LOG_STATE), LOG_ENTRY_STATE.UNKNOWN.toString()));
        }

        LogEntry entry = new LogEntry();
        entry.setId(id);
        entry.setActionComponent(actionComponent);
        entry.setActionMethod(actionMethod);
        entry.setAddress(address);
        entry.setDatetime(datetime);
        entry.setDuration(duration);
        entry.setState(state);
        try {
            if (fieldsToReturn.isEmpty() || fieldsToReturn.contains(RodaConstants.LOG_PARAMETERS)) {
                entry.setParameters(
                        JsonUtils.getListFromJson(parameters == null ? "" : parameters, LogEntryParameter.class));
            }
        } catch (GenericException e) {
            LOGGER.error("Error parsing log entry parameters", e);
        }

        entry.setRelatedObjectID(relatedObjectId);
        entry.setUsername(username);
        return entry;
    }

    public static SolrInputDocument logEntryToSolrDocument(LogEntry logEntry) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, logEntry.getId());
        doc.addField(RodaConstants.LOG_ID, logEntry.getId());
        doc.addField(RodaConstants.LOG_ACTION_COMPONENT, logEntry.getActionComponent());
        doc.addField(RodaConstants.LOG_ACTION_METHOD, logEntry.getActionMethod());
        doc.addField(RodaConstants.LOG_ADDRESS, logEntry.getAddress());
        doc.addField(RodaConstants.LOG_DATETIME, logEntry.getDatetime());
        doc.addField(RodaConstants.LOG_DURATION, logEntry.getDuration());
        doc.addField(RodaConstants.LOG_PARAMETERS, JsonUtils.getJsonFromObject(logEntry.getParameters()));
        doc.addField(RodaConstants.LOG_RELATED_OBJECT_ID, logEntry.getRelatedObjectID());
        doc.addField(RodaConstants.LOG_USERNAME, logEntry.getUsername());
        doc.addField(RodaConstants.LOG_STATE, logEntry.getState().toString());

        return doc;
    }

    public static SolrInputDocument rodaMemberToSolrDocument(RODAMember member) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, member.getId());
        doc.addField(RodaConstants.MEMBERS_ID, member.getId());
        doc.addField(RodaConstants.MEMBERS_IS_ACTIVE, member.isActive());
        doc.addField(RodaConstants.MEMBERS_IS_USER, member.isUser());
        doc.addField(RodaConstants.MEMBERS_NAME, member.getName());

        if (member.getDirectRoles() != null) {
            doc.addField(RodaConstants.MEMBERS_ROLES_DIRECT, new ArrayList<String>(member.getDirectRoles()));
        }
        if (member.getAllRoles() != null) {
            doc.addField(RodaConstants.MEMBERS_ROLES_ALL, new ArrayList<String>(member.getAllRoles()));
        }

        if (StringUtils.isNotBlank(member.getFullName())) {
            doc.addField(RodaConstants.MEMBERS_FULLNAME, member.getFullName());
        }

        // Add user specific fields
        if (member instanceof User) {
            User user = (User) member;
            doc.addField(RodaConstants.MEMBERS_EMAIL, user.getEmail());
            if (user.getGroups() != null) {
                doc.addField(RodaConstants.MEMBERS_GROUPS, new ArrayList<String>(user.getGroups()));
            }
        }

        // Add group specific fields
        if (member instanceof Group) {
            Group group = (Group) member;
            if (group.getUsers() != null) {
                doc.addField(RodaConstants.MEMBERS_USERS, new ArrayList<String>(group.getUsers()));
            }
        }

        return doc;
    }

    private static RODAMember solrDocumentToRodaMember(SolrDocument doc) {
        final String id = objectToString(doc.get(RodaConstants.INDEX_UUID), null);
        final String name = objectToString(doc.get(RodaConstants.MEMBERS_NAME), null);

        final boolean isActive = objectToBoolean(doc.get(RodaConstants.MEMBERS_IS_ACTIVE), Boolean.FALSE);
        final boolean isUser = objectToBoolean(doc.get(RodaConstants.MEMBERS_IS_USER), Boolean.FALSE);
        final String fullName = objectToString(doc.get(RodaConstants.MEMBERS_FULLNAME), null);

        final String email = objectToString(doc.get(RodaConstants.MEMBERS_EMAIL), null);
        final Set<String> groups = new HashSet<>(objectToListString(doc.get(RodaConstants.MEMBERS_GROUPS)));
        final Set<String> users = new HashSet<>(objectToListString(doc.get(RodaConstants.MEMBERS_USERS)));
        final Set<String> directRoles = new HashSet<>(
                objectToListString(doc.get(RodaConstants.MEMBERS_ROLES_DIRECT)));
        final Set<String> allRoles = new HashSet<>(objectToListString(doc.get(RodaConstants.MEMBERS_ROLES_ALL)));
        if (isUser) {
            User user = new User();
            user.setId(id);
            user.setName(name);

            user.setActive(isActive);
            user.setFullName(fullName);
            user.setDirectRoles(directRoles);
            user.setAllRoles(allRoles);

            user.setEmail(email);
            user.setGroups(groups);

            return user;
        } else {
            Group group = new Group();
            group.setId(id);
            group.setName(name);

            group.setActive(isActive);
            group.setFullName(fullName);
            group.setDirectRoles(directRoles);
            group.setAllRoles(allRoles);

            group.setUsers(users);

            return group;
        }
    }

    private static IndexedPreservationEvent solrDocumentToIndexedPreservationEvent(SolrDocument doc) {
        final String id = objectToString(doc.get(RodaConstants.INDEX_UUID), null);
        final String aipId = objectToString(doc.get(RodaConstants.PRESERVATION_EVENT_AIP_ID), null);
        final String representationUUID = objectToString(
                doc.get(RodaConstants.PRESERVATION_EVENT_REPRESENTATION_UUID), null);
        final String fileUUID = objectToString(doc.get(RodaConstants.PRESERVATION_EVENT_FILE_UUID), null);

        IndexedPreservationEvent ipe = new IndexedPreservationEvent();
        ipe.setId(id);
        ipe.setAipID(aipId);
        ipe.setRepresentationUUID(representationUUID);
        ipe.setFileUUID(fileUUID);

        String objectClass = objectToString(doc.get(RodaConstants.PRESERVATION_EVENT_OBJECT_CLASS), null);
        if (StringUtils.isNotBlank(objectClass)) {
            ipe.setObjectClass(PreservationMetadataEventClass.valueOf(objectClass.toUpperCase()));
        }

        final Date eventDateTime = objectToDate(doc.get(RodaConstants.PRESERVATION_EVENT_DATETIME));
        final String eventDetail = objectToString(doc.get(RodaConstants.PRESERVATION_EVENT_DETAIL), "");
        final String eventType = objectToString(doc.get(RodaConstants.PRESERVATION_EVENT_TYPE), "");
        final String eventOutcome = objectToString(doc.get(RodaConstants.PRESERVATION_EVENT_OUTCOME), "");

        final List<String> agents = objectToListString(
                doc.get(RodaConstants.PRESERVATION_EVENT_LINKING_AGENT_IDENTIFIER));
        final List<String> outcomes = objectToListString(
                doc.get(RodaConstants.PRESERVATION_EVENT_LINKING_OUTCOME_OBJECT_IDENTIFIER));
        final List<String> sources = objectToListString(
                doc.get(RodaConstants.PRESERVATION_EVENT_LINKING_SOURCE_OBJECT_IDENTIFIER));

        ipe.setEventDateTime(eventDateTime);
        ipe.setEventDetail(eventDetail);
        ipe.setEventType(eventType);
        ipe.setEventOutcome(eventOutcome);

        try {
            List<LinkingIdentifier> ids = new ArrayList<>();
            for (String source : sources) {
                ids.add(JsonUtils.getObjectFromJson(source, LinkingIdentifier.class));
            }
            ipe.setSourcesObjectIds(ids);
        } catch (GenericException | RuntimeException e) {
            LOGGER.error("Error setting event linking source", e);
        }
        try {
            List<LinkingIdentifier> ids = new ArrayList<>();
            for (String outcome : outcomes) {
                ids.add(JsonUtils.getObjectFromJson(outcome, LinkingIdentifier.class));
            }
            ipe.setOutcomeObjectIds(ids);
        } catch (GenericException | RuntimeException e) {
            LOGGER.error("Error setting event linking outcome", e);
        }
        try {
            List<LinkingIdentifier> ids = new ArrayList<>();
            for (String agent : agents) {
                ids.add(JsonUtils.getObjectFromJson(agent, LinkingIdentifier.class));
            }
            ipe.setLinkingAgentIds(ids);
        } catch (GenericException | RuntimeException e) {
            LOGGER.error("Error setting event linking agents", e);
        }
        return ipe;
    }

    private static IndexedPreservationAgent solrDocumentToIndexedPreservationAgent(SolrDocument doc) {
        final String id = objectToString(doc.get(RodaConstants.INDEX_UUID), null);
        final String name = objectToString(doc.get(RodaConstants.PRESERVATION_AGENT_NAME), null);
        final String type = objectToString(doc.get(RodaConstants.PRESERVATION_AGENT_TYPE), null);
        final String extension = objectToString(doc.get(RodaConstants.PRESERVATION_AGENT_EXTENSION), null);
        final String version = objectToString(doc.get(RodaConstants.PRESERVATION_AGENT_VERSION), null);
        final String note = objectToString(doc.get(RodaConstants.PRESERVATION_AGENT_NOTE), null);
        final List<String> roles = objectToListString(doc.get(RodaConstants.PRESERVATION_AGENT_ROLES));

        IndexedPreservationAgent ipa = new IndexedPreservationAgent();
        ipa.setId(id);
        ipa.setName(name);
        ipa.setType(type);
        ipa.setExtension(extension);
        ipa.setVersion(version);
        ipa.setNote(note);
        ipa.setRoles(roles);
        return ipa;
    }

    private static TransferredResource solrDocumentToTransferredResource(SolrDocument doc) {
        String uuid = objectToString(doc.get(RodaConstants.INDEX_UUID), null);
        String fullPath = objectToString(doc.get(RodaConstants.TRANSFERRED_RESOURCE_FULLPATH), null);

        String id = objectToString(doc.get(RodaConstants.TRANSFERRED_RESOURCE_ID), null);
        String parentId = null;
        String parentUUID = null;
        if (doc.containsKey(RodaConstants.TRANSFERRED_RESOURCE_PARENT_ID)) {
            parentId = objectToString(doc.get(RodaConstants.TRANSFERRED_RESOURCE_PARENT_ID), null);
            parentUUID = objectToString(doc.get(RodaConstants.TRANSFERRED_RESOURCE_PARENT_UUID), null);
        }
        String relativePath = objectToString(doc.get(RodaConstants.TRANSFERRED_RESOURCE_RELATIVEPATH), null);

        Date d = objectToDate(doc.get(RodaConstants.TRANSFERRED_RESOURCE_DATE));
        if (d == null) {
            LOGGER.warn("Error parsing transferred resource date. Setting date to current date.");
            d = new Date();
        }

        boolean isFile = objectToBoolean(doc.get(RodaConstants.TRANSFERRED_RESOURCE_ISFILE), Boolean.FALSE);
        long size = objectToLong(doc.get(RodaConstants.TRANSFERRED_RESOURCE_SIZE), 0L);
        String name = objectToString(doc.get(RodaConstants.TRANSFERRED_RESOURCE_NAME), null);

        List<String> ancestorsPath = objectToListString(doc.get(RodaConstants.TRANSFERRED_RESOURCE_ANCESTORS));

        Date lastScanDate = objectToDate(doc.get(RodaConstants.TRANSFERRED_RESOURCE_LAST_SCAN_DATE));

        TransferredResource tr = new TransferredResource();
        tr.setUUID(uuid);
        tr.setFullPath(fullPath);
        tr.setId(id);
        tr.setCreationDate(d);
        tr.setName(name);
        tr.setRelativePath(relativePath);
        tr.setSize(size);
        tr.setParentId(parentId);
        tr.setParentUUID(parentUUID);
        tr.setFile(isFile);
        tr.setAncestorsPaths(ancestorsPath);
        tr.setLastScanDate(lastScanDate);
        return tr;
    }

    public static SolrInputDocument transferredResourceToSolrDocument(TransferredResource resource) {
        SolrInputDocument transferredResource = new SolrInputDocument();

        transferredResource.addField(RodaConstants.INDEX_UUID, resource.getUUID());
        transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_ID, resource.getId());
        transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_FULLPATH, resource.getFullPath());
        if (resource.getParentId() != null) {
            transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_PARENT_ID, resource.getParentId());
            transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_PARENT_UUID,
                    IdUtils.createUUID(resource.getParentId()));
        }
        if (resource.getRelativePath() != null) {
            transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_RELATIVEPATH,
                    resource.getRelativePath());
        }
        transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_DATE, resource.getCreationDate());
        transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_ISFILE, resource.isFile());
        transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_SIZE, resource.getSize());
        transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_NAME, resource.getName());
        if (resource.getAncestorsPaths() != null && !resource.getAncestorsPaths().isEmpty()) {
            transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_ANCESTORS,
                    resource.getAncestorsPaths());
        }
        transferredResource.addField(RodaConstants.TRANSFERRED_RESOURCE_LAST_SCAN_DATE, resource.getLastScanDate());

        return transferredResource;
    }

    public static SolrInputDocument jobToSolrDocument(Job job) {
        SolrInputDocument doc = new SolrInputDocument();

        doc.addField(RodaConstants.INDEX_UUID, job.getId());
        doc.addField(RodaConstants.JOB_ID, job.getId());
        doc.addField(RodaConstants.JOB_NAME, job.getName());
        doc.addField(RodaConstants.JOB_USERNAME, job.getUsername());
        doc.addField(RodaConstants.JOB_START_DATE, job.getStartDate());
        doc.addField(RodaConstants.JOB_END_DATE, job.getEndDate());
        doc.addField(RodaConstants.JOB_STATE, job.getState().toString());
        doc.addField(RodaConstants.JOB_STATE_DETAILS, job.getStateDetails());
        JobStats jobStats = job.getJobStats();
        doc.addField(RodaConstants.JOB_COMPLETION_PERCENTAGE, jobStats.getCompletionPercentage());
        doc.addField(RodaConstants.JOB_SOURCE_OBJECTS_COUNT, jobStats.getSourceObjectsCount());
        doc.addField(RodaConstants.JOB_SOURCE_OBJECTS_WAITING_TO_BE_PROCESSED,
                jobStats.getSourceObjectsWaitingToBeProcessed());
        doc.addField(RodaConstants.JOB_SOURCE_OBJECTS_BEING_PROCESSED, jobStats.getSourceObjectsBeingProcessed());
        doc.addField(RodaConstants.JOB_SOURCE_OBJECTS_PROCESSED_WITH_SUCCESS,
                jobStats.getSourceObjectsProcessedWithSuccess());
        doc.addField(RodaConstants.JOB_SOURCE_OBJECTS_PROCESSED_WITH_FAILURE,
                jobStats.getSourceObjectsProcessedWithFailure());
        doc.addField(RodaConstants.JOB_OUTCOME_OBJECTS_WITH_MANUAL_INTERVENTION,
                jobStats.getOutcomeObjectsWithManualIntervention());
        doc.addField(RodaConstants.JOB_PLUGIN_TYPE, job.getPluginType().toString());
        doc.addField(RodaConstants.JOB_PLUGIN, job.getPlugin());
        doc.addField(RodaConstants.JOB_PLUGIN_PARAMETERS, JsonUtils.getJsonFromObject(job.getPluginParameters()));
        doc.addField(RodaConstants.JOB_SOURCE_OBJECTS, JsonUtils.getJsonFromObject(job.getSourceObjects()));
        doc.addField(RodaConstants.JOB_OUTCOME_OBJECTS_CLASS, job.getOutcomeObjectsClass());

        return doc;
    }

    public static Job solrDocumentToJob(SolrDocument doc, List<String> fieldsToReturn) {
        Job job = new Job();

        job.setId(objectToString(doc.get(RodaConstants.INDEX_UUID), null));
        job.setName(objectToString(doc.get(RodaConstants.JOB_NAME), null));
        job.setUsername(objectToString(doc.get(RodaConstants.JOB_USERNAME), null));
        job.setStartDate(objectToDate(doc.get(RodaConstants.JOB_START_DATE)));
        job.setEndDate(objectToDate(doc.get(RodaConstants.JOB_END_DATE)));
        if (doc.containsKey(RodaConstants.JOB_STATE)) {
            job.setState(JOB_STATE.valueOf(objectToString(doc.get(RodaConstants.JOB_STATE), null)));
        }
        job.setStateDetails(objectToString(doc.get(RodaConstants.JOB_STATE_DETAILS), null));
        JobStats jobStats = job.getJobStats();
        jobStats.setCompletionPercentage(objectToInteger(doc.get(RodaConstants.JOB_COMPLETION_PERCENTAGE), 0));
        jobStats.setSourceObjectsCount(objectToInteger(doc.get(RodaConstants.JOB_SOURCE_OBJECTS_COUNT), 0));
        jobStats.setSourceObjectsWaitingToBeProcessed(
                objectToInteger(doc.get(RodaConstants.JOB_SOURCE_OBJECTS_WAITING_TO_BE_PROCESSED), 0));
        jobStats.setSourceObjectsBeingProcessed(
                objectToInteger(doc.get(RodaConstants.JOB_SOURCE_OBJECTS_BEING_PROCESSED), 0));
        jobStats.setSourceObjectsProcessedWithSuccess(
                objectToInteger(doc.get(RodaConstants.JOB_SOURCE_OBJECTS_PROCESSED_WITH_SUCCESS), 0));
        jobStats.setSourceObjectsProcessedWithFailure(
                objectToInteger(doc.get(RodaConstants.JOB_SOURCE_OBJECTS_PROCESSED_WITH_FAILURE), 0));
        jobStats.setOutcomeObjectsWithManualIntervention(
                objectToInteger(doc.get(RodaConstants.JOB_OUTCOME_OBJECTS_WITH_MANUAL_INTERVENTION), 0));
        if (doc.containsKey(RodaConstants.JOB_PLUGIN_TYPE)) {
            job.setPluginType(PluginType.valueOf(objectToString(doc.get(RodaConstants.JOB_PLUGIN_TYPE), null)));
        }
        job.setPlugin(objectToString(doc.get(RodaConstants.JOB_PLUGIN), null));
        if (fieldsToReturn.isEmpty() || fieldsToReturn.contains(RodaConstants.JOB_PLUGIN_PARAMETERS)) {
            job.setPluginParameters(
                    JsonUtils.getMapFromJson(objectToString(doc.get(RodaConstants.JOB_PLUGIN_PARAMETERS), "")));
        }

        try {
            if (fieldsToReturn.isEmpty() || fieldsToReturn.contains(RodaConstants.JOB_SOURCE_OBJECTS)) {
                job.setSourceObjects(JsonUtils.getObjectFromJson(
                        objectToString(doc.get(RodaConstants.JOB_SOURCE_OBJECTS), ""), SelectedItems.class));
            }
        } catch (GenericException e) {
            LOGGER.error("Error parsing report in job objects", e);
        }
        job.setOutcomeObjectsClass(objectToString(doc.get(RodaConstants.JOB_OUTCOME_OBJECTS_CLASS), null));

        return job;
    }

    public static <T extends Serializable> SolrInputDocument jobReportToSolrDocument(Report jobReport, Job job,
            SolrClient index) {
        SolrInputDocument doc = new SolrInputDocument();

        doc.addField(RodaConstants.INDEX_UUID, jobReport.getId());
        doc.addField(RodaConstants.JOB_REPORT_ID, jobReport.getId());
        doc.addField(RodaConstants.JOB_REPORT_JOB_ID, jobReport.getJobId());
        doc.addField(RodaConstants.JOB_REPORT_SOURCE_OBJECT_ID, jobReport.getSourceObjectId());
        doc.addField(RodaConstants.JOB_REPORT_SOURCE_OBJECT_ORIGINAL_IDS, jobReport.getSourceObjectOriginalIds());
        doc.addField(RodaConstants.JOB_REPORT_SOURCE_OBJECT_ORIGINAL_NAME, jobReport.getSourceObjectOriginalName());
        doc.addField(RodaConstants.JOB_REPORT_OUTCOME_OBJECT_ID, jobReport.getOutcomeObjectId());
        doc.addField(RodaConstants.JOB_REPORT_OUTCOME_OBJECT_STATE, jobReport.getOutcomeObjectState().toString());
        doc.addField(RodaConstants.JOB_REPORT_TITLE, jobReport.getTitle());
        doc.addField(RodaConstants.JOB_REPORT_DATE_CREATED, jobReport.getDateCreated());
        doc.addField(RodaConstants.JOB_REPORT_DATE_UPDATED, jobReport.getDateUpdated());
        doc.addField(RodaConstants.JOB_REPORT_COMPLETION_PERCENTAGE, jobReport.getCompletionPercentage());
        doc.addField(RodaConstants.JOB_REPORT_STEPS_COMPLETED, jobReport.getStepsCompleted());
        doc.addField(RodaConstants.JOB_REPORT_TOTAL_STEPS, jobReport.getTotalSteps());
        doc.addField(RodaConstants.JOB_REPORT_PLUGIN, jobReport.getPlugin());
        doc.addField(RodaConstants.JOB_REPORT_PLUGIN_NAME, jobReport.getPluginName());
        doc.addField(RodaConstants.JOB_REPORT_PLUGIN_VERSION, jobReport.getPluginVersion());
        doc.addField(RodaConstants.JOB_REPORT_PLUGIN_STATE, jobReport.getPluginState().toString());
        doc.addField(RodaConstants.JOB_REPORT_PLUGIN_DETAILS, jobReport.getPluginDetails());
        doc.addField(RodaConstants.JOB_REPORT_HTML_PLUGIN_DETAILS, jobReport.isHtmlPluginDetails());
        doc.addField(RodaConstants.JOB_REPORT_REPORTS, JsonUtils.getJsonFromObject(jobReport.getReports()));
        doc.addField(RodaConstants.JOB_REPORT_SOURCE_OBJECT_CLASS, jobReport.getSourceObjectClass());
        doc.addField(RodaConstants.JOB_REPORT_OUTCOME_OBJECT_CLASS, jobReport.getOutcomeObjectClass());
        doc.addField(RodaConstants.JOB_REPORT_JOB_NAME, job.getName());

        doc.addField(RodaConstants.JOB_REPORT_SOURCE_OBJECT_LABEL,
                getObjectLabel(index, jobReport.getSourceObjectClass(), jobReport.getSourceObjectId()));
        doc.addField(RodaConstants.JOB_REPORT_OUTCOME_OBJECT_LABEL,
                getObjectLabel(index, jobReport.getOutcomeObjectClass(), jobReport.getOutcomeObjectId()));

        return doc;
    }

    private static <T extends Serializable> String getObjectLabel(SolrClient index, String className, String id) {
        if (StringUtils.isNotBlank(className) && StringUtils.isNotBlank(id)) {
            try {
                Class<T> objectClass = (Class<T>) Class.forName(className);
                if (objectClass.equals(LiteRODAObject.class)) {
                    return null;
                }
                String field = getIndexName(objectClass).get(0);
                SolrDocument doc = index.getById(field, id);
                if (doc != null) {
                    if (objectClass.equals(AIP.class)) {
                        return objectToString(doc.get(RodaConstants.AIP_TITLE), null);
                    } else if (objectClass.equals(Representation.class)) {
                        return objectToString(doc.get(RodaConstants.REPRESENTATION_ID), null);
                    } else if (objectClass.equals(File.class)) {
                        return objectToString(doc.get(RodaConstants.FILE_FILE_ID), null);
                    } else if (objectClass.equals(TransferredResource.class)) {
                        return objectToString(doc.get(RodaConstants.TRANSFERRED_RESOURCE_ID), null);
                    } else if (objectClass.equals(DIP.class)) {
                        return objectToString(doc.get(RodaConstants.DIP_TITLE), null);
                    } else if (objectClass.equals(DIPFile.class)) {
                        return objectToString(doc.get(RodaConstants.DIPFILE_ID), null);
                    } else {
                        return objectToString(doc.get(RodaConstants.INDEX_UUID), null);
                    }
                }
            } catch (ClassNotFoundException | GenericException | SolrServerException | IOException e) {
                LOGGER.error("Could not return object label of {} {}", className, id, e);
            }
        }

        return null;
    }

    private static IndexedReport solrDocumentToJobReport(SolrDocument doc, List<String> fieldsToReturn) {
        IndexedReport jobReport = new IndexedReport();
        jobReport.setId(objectToString(doc.get(RodaConstants.INDEX_UUID), null));
        jobReport.setJobId(objectToString(doc.get(RodaConstants.JOB_REPORT_JOB_ID), null));
        jobReport.setSourceObjectId(objectToString(doc.get(RodaConstants.JOB_REPORT_SOURCE_OBJECT_ID), null));
        jobReport.setSourceObjectClass(objectToString(doc.get(RodaConstants.JOB_REPORT_SOURCE_OBJECT_CLASS), null));
        jobReport.setSourceObjectOriginalIds(
                objectToListString(doc.get(RodaConstants.JOB_REPORT_SOURCE_OBJECT_ORIGINAL_IDS)));
        jobReport.setSourceObjectOriginalName(
                objectToString(doc.get(RodaConstants.JOB_REPORT_SOURCE_OBJECT_ORIGINAL_NAME), null));
        jobReport.setOutcomeObjectId(objectToString(doc.get(RodaConstants.JOB_REPORT_OUTCOME_OBJECT_ID), null));
        jobReport.setOutcomeObjectClass(
                objectToString(doc.get(RodaConstants.JOB_REPORT_OUTCOME_OBJECT_CLASS), null));
        if (doc.containsKey(RodaConstants.JOB_REPORT_OUTCOME_OBJECT_STATE)) {
            jobReport.setOutcomeObjectState(AIPState.valueOf(objectToString(
                    doc.get(RodaConstants.JOB_REPORT_OUTCOME_OBJECT_STATE), AIPState.getDefault().toString())));
        }
        jobReport.setTitle(objectToString(doc.get(RodaConstants.JOB_REPORT_TITLE), null));
        jobReport.setDateCreated(objectToDate(doc.get(RodaConstants.JOB_REPORT_DATE_CREATED)));
        jobReport.setDateUpdated(objectToDate(doc.get(RodaConstants.JOB_REPORT_DATE_UPDATED)));
        jobReport.setCompletionPercentage(
                objectToInteger(doc.get(RodaConstants.JOB_REPORT_COMPLETION_PERCENTAGE), 0));
        jobReport.setStepsCompleted(objectToInteger(doc.get(RodaConstants.JOB_REPORT_STEPS_COMPLETED), 0));
        jobReport.setTotalSteps(objectToInteger(doc.get(RodaConstants.JOB_REPORT_TOTAL_STEPS), 0));
        jobReport.setPlugin(objectToString(doc.get(RodaConstants.JOB_REPORT_PLUGIN), null));
        jobReport.setPluginName(objectToString(doc.get(RodaConstants.JOB_REPORT_PLUGIN_NAME), null));
        jobReport.setPluginVersion(objectToString(doc.get(RodaConstants.JOB_REPORT_PLUGIN_VERSION), null));
        jobReport.setPluginState(
                PluginState.valueOf(objectToString(doc.get(RodaConstants.JOB_REPORT_PLUGIN_STATE), null)));
        jobReport.setPluginDetails(objectToString(doc.get(RodaConstants.JOB_REPORT_PLUGIN_DETAILS), null));
        jobReport.setHtmlPluginDetails(
                objectToBoolean(doc.get(RodaConstants.JOB_REPORT_HTML_PLUGIN_DETAILS), false));
        try {
            if (fieldsToReturn.isEmpty() || fieldsToReturn.contains(RodaConstants.JOB_REPORT_REPORTS)) {
                jobReport.setReports(JsonUtils.getListFromJson(
                        objectToString(doc.get(RodaConstants.JOB_REPORT_REPORTS), ""), Report.class));
            }
        } catch (GenericException e) {
            LOGGER.error("Error parsing report in job report", e);
        }

        jobReport.setJobName(objectToString(doc.get(RodaConstants.JOB_REPORT_JOB_NAME), null));
        jobReport.setSourceObjectLabel(objectToString(doc.get(RodaConstants.JOB_REPORT_SOURCE_OBJECT_LABEL), null));
        jobReport.setOutcomeObjectLabel(
                objectToString(doc.get(RodaConstants.JOB_REPORT_OUTCOME_OBJECT_LABEL), null));

        return jobReport;
    }

    public static SolrInputDocument riskToSolrDocument(Risk risk, int incidences) {
        SolrInputDocument doc = new SolrInputDocument();

        doc.addField(RodaConstants.INDEX_UUID, risk.getId());
        doc.addField(RodaConstants.RISK_ID, risk.getId());
        doc.addField(RodaConstants.RISK_NAME, risk.getName());
        doc.addField(RodaConstants.RISK_DESCRIPTION, risk.getDescription());
        doc.addField(RodaConstants.RISK_IDENTIFIED_ON, risk.getIdentifiedOn());
        doc.addField(RodaConstants.RISK_IDENTIFIED_BY, risk.getIdentifiedBy());
        doc.addField(RodaConstants.RISK_CATEGORY, risk.getCategory());
        doc.addField(RodaConstants.RISK_NOTES, risk.getNotes());

        doc.addField(RodaConstants.RISK_PRE_MITIGATION_PROBABILITY, risk.getPreMitigationProbability());
        doc.addField(RodaConstants.RISK_PRE_MITIGATION_IMPACT, risk.getPreMitigationImpact());
        doc.addField(RodaConstants.RISK_PRE_MITIGATION_SEVERITY, risk.getPreMitigationSeverity());
        doc.addField(RodaConstants.RISK_PRE_MITIGATION_SEVERITY_LEVEL,
                risk.getPreMitigationSeverityLevel().toString());
        doc.addField(RodaConstants.RISK_PRE_MITIGATION_NOTES, risk.getPreMitigationNotes());

        doc.addField(RodaConstants.RISK_POST_MITIGATION_PROBABILITY, risk.getPostMitigationProbability());
        doc.addField(RodaConstants.RISK_POST_MITIGATION_IMPACT, risk.getPostMitigationImpact());
        doc.addField(RodaConstants.RISK_POST_MITIGATION_SEVERITY, risk.getPostMitigationSeverity());

        if (risk.getPostMitigationSeverityLevel() != null) {
            doc.addField(RodaConstants.RISK_POST_MITIGATION_SEVERITY_LEVEL,
                    risk.getPostMitigationSeverityLevel().toString());
        }

        doc.addField(RodaConstants.RISK_CURRENT_SEVERITY_LEVEL, risk.getCurrentSeverityLevel());

        doc.addField(RodaConstants.RISK_POST_MITIGATION_NOTES, risk.getPostMitigationNotes());

        doc.addField(RodaConstants.RISK_MITIGATION_STRATEGY, risk.getMitigationStrategy());
        doc.addField(RodaConstants.RISK_MITIGATION_OWNER_TYPE, risk.getMitigationOwnerType());
        doc.addField(RodaConstants.RISK_MITIGATION_OWNER, risk.getMitigationOwner());
        doc.addField(RodaConstants.RISK_MITIGATION_RELATED_EVENT_IDENTIFIER_TYPE,
                risk.getMitigationRelatedEventIdentifierType());
        doc.addField(RodaConstants.RISK_MITIGATION_RELATED_EVENT_IDENTIFIER_VALUE,
                risk.getMitigationRelatedEventIdentifierValue());

        doc.addField(RodaConstants.RISK_CREATED_ON, risk.getCreatedOn());
        doc.addField(RodaConstants.RISK_CREATED_BY, risk.getCreatedBy());
        doc.addField(RodaConstants.RISK_UPDATED_ON, risk.getUpdatedOn());
        doc.addField(RodaConstants.RISK_UPDATED_BY, risk.getUpdatedBy());

        if (risk instanceof IndexedRisk) {
            doc.addField(RodaConstants.RISK_INCIDENCES_COUNT, ((IndexedRisk) risk).getIncidencesCount());
            doc.addField(RodaConstants.RISK_UNMITIGATED_INCIDENCES_COUNT,
                    ((IndexedRisk) risk).getUnmitigatedIncidencesCount());
        } else {
            doc.addField(RodaConstants.RISK_INCIDENCES_COUNT, incidences);
            doc.addField(RodaConstants.RISK_UNMITIGATED_INCIDENCES_COUNT, incidences);
        }

        return doc;
    }

    public static IndexedRisk solrDocumentToRisk(SolrDocument doc) {
        IndexedRisk risk = new IndexedRisk();

        risk.setId(objectToString(doc.get(RodaConstants.INDEX_UUID), null));
        risk.setName(objectToString(doc.get(RodaConstants.RISK_NAME), null));
        risk.setDescription(objectToString(doc.get(RodaConstants.RISK_DESCRIPTION), null));
        risk.setIdentifiedOn(objectToDate(doc.get(RodaConstants.RISK_IDENTIFIED_ON)));
        risk.setIdentifiedBy(objectToString(doc.get(RodaConstants.RISK_IDENTIFIED_BY), null));
        risk.setCategory(objectToString(doc.get(RodaConstants.RISK_CATEGORY), null));
        risk.setNotes(objectToString(doc.get(RodaConstants.RISK_NOTES), null));

        risk.setPreMitigationProbability(
                objectToInteger(doc.get(RodaConstants.RISK_PRE_MITIGATION_PROBABILITY), 0));
        risk.setPreMitigationImpact(objectToInteger(doc.get(RodaConstants.RISK_PRE_MITIGATION_IMPACT), 0));
        risk.setPreMitigationSeverity(objectToInteger(doc.get(RodaConstants.RISK_PRE_MITIGATION_SEVERITY), 0));
        if (doc.containsKey(RodaConstants.RISK_PRE_MITIGATION_SEVERITY_LEVEL)) {
            risk.setPreMitigationSeverityLevel(objectToEnum(
                    doc.get(RodaConstants.RISK_PRE_MITIGATION_SEVERITY_LEVEL), Risk.SEVERITY_LEVEL.class, null));
        }
        risk.setPreMitigationNotes(objectToString(doc.get(RodaConstants.RISK_PRE_MITIGATION_NOTES), null));

        risk.setPostMitigationProbability(
                objectToInteger(doc.get(RodaConstants.RISK_POST_MITIGATION_PROBABILITY), 0));
        risk.setPostMitigationImpact(objectToInteger(doc.get(RodaConstants.RISK_POST_MITIGATION_IMPACT), 0));
        risk.setPostMitigationSeverity(objectToInteger(doc.get(RodaConstants.RISK_POST_MITIGATION_SEVERITY), 0));
        if (doc.containsKey(RodaConstants.RISK_POST_MITIGATION_SEVERITY_LEVEL)) {
            risk.setPostMitigationSeverityLevel(objectToEnum(
                    doc.get(RodaConstants.RISK_POST_MITIGATION_SEVERITY_LEVEL), Risk.SEVERITY_LEVEL.class, null));
        }
        risk.setPostMitigationNotes(objectToString(doc.get(RodaConstants.RISK_POST_MITIGATION_NOTES), null));

        risk.setMitigationStrategy(objectToString(doc.get(RodaConstants.RISK_MITIGATION_STRATEGY), null));
        risk.setMitigationOwnerType(objectToString(doc.get(RodaConstants.RISK_MITIGATION_OWNER_TYPE), null));
        risk.setMitigationOwner(objectToString(doc.get(RodaConstants.RISK_MITIGATION_OWNER), null));
        risk.setMitigationRelatedEventIdentifierType(
                objectToString(doc.get(RodaConstants.RISK_MITIGATION_RELATED_EVENT_IDENTIFIER_TYPE), null));
        risk.setMitigationRelatedEventIdentifierValue(
                objectToString(doc.get(RodaConstants.RISK_MITIGATION_RELATED_EVENT_IDENTIFIER_VALUE), null));

        risk.setCreatedOn(objectToDate(doc.get(RodaConstants.RISK_CREATED_ON)));
        risk.setCreatedBy(objectToString(doc.get(RodaConstants.RISK_CREATED_BY), null));
        risk.setUpdatedOn(objectToDate(doc.get(RodaConstants.RISK_UPDATED_ON)));
        risk.setUpdatedBy(objectToString(doc.get(RodaConstants.RISK_UPDATED_BY), null));

        risk.setIncidencesCount(objectToInteger(doc.get(RodaConstants.RISK_INCIDENCES_COUNT), 0));
        risk.setUnmitigatedIncidencesCount(
                objectToInteger(doc.get(RodaConstants.RISK_UNMITIGATED_INCIDENCES_COUNT), 0));
        return risk;
    }

    public static SolrInputDocument formatToSolrDocument(Format format) {
        SolrInputDocument doc = new SolrInputDocument();

        doc.addField(RodaConstants.INDEX_UUID, format.getId());
        doc.addField(RodaConstants.FORMAT_ID, format.getId());
        doc.addField(RodaConstants.FORMAT_NAME, format.getName());
        doc.addField(RodaConstants.FORMAT_NAME_SORT, format.getName());
        doc.addField(RodaConstants.FORMAT_DEFINITION, format.getDefinition());
        doc.addField(RodaConstants.FORMAT_CATEGORY, format.getCategories());
        doc.addField(RodaConstants.FORMAT_CATEGORY_SORT,
                (format.getCategories() != null && !format.getCategories().isEmpty())
                        ? format.getCategories().get(0)
                        : null);
        doc.addField(RodaConstants.FORMAT_LATEST_VERSION, format.getLatestVersion());
        if (format.getPopularity() != null) {
            doc.addField(RodaConstants.FORMAT_POPULARITY, format.getPopularity());
        }
        doc.addField(RodaConstants.FORMAT_DEVELOPER, format.getDeveloper());
        doc.addField(RodaConstants.FORMAT_INITIAL_RELEASE, format.getInitialRelease());
        doc.addField(RodaConstants.FORMAT_STANDARD, format.getStandard());
        doc.addField(RodaConstants.FORMAT_IS_OPEN_FORMAT, format.isOpenFormat());
        doc.addField(RodaConstants.FORMAT_WEBSITE, format.getWebsites());
        doc.addField(RodaConstants.FORMAT_PROVENANCE_INFORMATION, format.getProvenanceInformation());
        doc.addField(RodaConstants.FORMAT_EXTENSIONS, format.getExtensions());
        doc.addField(RodaConstants.FORMAT_MIMETYPES, format.getMimetypes());
        doc.addField(RodaConstants.FORMAT_PRONOMS, format.getPronoms());
        doc.addField(RodaConstants.FORMAT_UTIS, format.getUtis());
        doc.addField(RodaConstants.FORMAT_ALTERNATIVE_DESIGNATIONS, format.getAlternativeDesignations());
        doc.addField(RodaConstants.FORMAT_VERSIONS, format.getVersions());

        return doc;
    }

    public static Format solrDocumentToFormat(SolrDocument doc) {
        Format format = new Format();
        format.setId(objectToString(doc.get(RodaConstants.INDEX_UUID), null));
        format.setName(objectToString(doc.get(RodaConstants.FORMAT_NAME), null));
        format.setDefinition(objectToString(doc.get(RodaConstants.FORMAT_DEFINITION), null));
        format.setCategories(objectToListString(doc.get(RodaConstants.FORMAT_CATEGORY)));
        format.setLatestVersion(objectToString(doc.get(RodaConstants.FORMAT_LATEST_VERSION), null));
        format.setPopularity(objectToInteger(doc.get(RodaConstants.FORMAT_POPULARITY), null));
        format.setDeveloper(objectToString(doc.get(RodaConstants.FORMAT_DEVELOPER), null));
        format.setInitialRelease(objectToDate(doc.get(RodaConstants.FORMAT_INITIAL_RELEASE)));
        format.setStandard(objectToString(doc.get(RodaConstants.FORMAT_STANDARD), null));
        format.setOpenFormat(objectToBoolean(doc.get(RodaConstants.FORMAT_IS_OPEN_FORMAT), Boolean.FALSE));
        format.setWebsites(objectToListString(doc.get(RodaConstants.FORMAT_WEBSITE)));
        format.setProvenanceInformation(objectToString(doc.get(RodaConstants.FORMAT_PROVENANCE_INFORMATION), null));
        format.setExtensions(objectToListString(doc.get(RodaConstants.FORMAT_EXTENSIONS)));
        format.setMimetypes(objectToListString(doc.get(RodaConstants.FORMAT_MIMETYPES)));
        format.setPronoms(objectToListString(doc.get(RodaConstants.FORMAT_PRONOMS)));
        format.setUtis(objectToListString(doc.get(RodaConstants.FORMAT_UTIS)));
        format.setAlternativeDesignations(
                objectToListString(doc.get(RodaConstants.FORMAT_ALTERNATIVE_DESIGNATIONS)));
        format.setVersions(objectToListString(doc.get(RodaConstants.FORMAT_VERSIONS)));
        return format;
    }

    public static SolrInputDocument notificationToSolrDocument(Notification notification) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, notification.getId());
        doc.addField(RodaConstants.NOTIFICATION_ID, notification.getId());
        doc.addField(RodaConstants.NOTIFICATION_SUBJECT, notification.getSubject());
        doc.addField(RodaConstants.NOTIFICATION_BODY, notification.getBody());
        doc.addField(RodaConstants.NOTIFICATION_SENT_ON, notification.getSentOn());
        doc.addField(RodaConstants.NOTIFICATION_FROM_USER, notification.getFromUser());
        doc.addField(RodaConstants.NOTIFICATION_RECIPIENT_USERS, notification.getRecipientUsers());
        doc.addField(RodaConstants.NOTIFICATION_ACKNOWLEDGE_TOKEN, notification.getAcknowledgeToken());
        doc.addField(RodaConstants.NOTIFICATION_IS_ACKNOWLEDGED, notification.isAcknowledged());
        doc.addField(RodaConstants.NOTIFICATION_ACKNOWLEDGED_USERS,
                JsonUtils.getJsonFromObject(notification.getAcknowledgedUsers()));
        doc.addField(RodaConstants.NOTIFICATION_STATE, notification.getState().toString());
        return doc;
    }

    public static Notification solrDocumentToNotification(SolrDocument doc, List<String> fieldsToReturn) {
        Notification notification = new Notification();

        notification.setId(objectToString(doc.get(RodaConstants.INDEX_UUID), null));
        notification.setSubject(objectToString(doc.get(RodaConstants.NOTIFICATION_SUBJECT), null));
        notification.setBody(objectToString(doc.get(RodaConstants.NOTIFICATION_BODY), null));
        notification.setSentOn(objectToDate(doc.get(RodaConstants.NOTIFICATION_SENT_ON)));
        notification.setFromUser(objectToString(doc.get(RodaConstants.NOTIFICATION_FROM_USER), null));
        notification.setRecipientUsers(objectToListString(doc.get(RodaConstants.NOTIFICATION_RECIPIENT_USERS)));
        notification
                .setAcknowledgeToken(objectToString(doc.get(RodaConstants.NOTIFICATION_ACKNOWLEDGE_TOKEN), null));
        notification.setAcknowledged(
                objectToBoolean(doc.get(RodaConstants.NOTIFICATION_IS_ACKNOWLEDGED), Boolean.FALSE));
        if (fieldsToReturn.isEmpty() || fieldsToReturn.contains(RodaConstants.NOTIFICATION_ACKNOWLEDGED_USERS)) {
            notification.setAcknowledgedUsers(JsonUtils
                    .getMapFromJson(objectToString(doc.get(RodaConstants.NOTIFICATION_ACKNOWLEDGED_USERS), "")));
        }

        if (doc.containsKey(RodaConstants.NOTIFICATION_STATE)) {
            String defaultState = Notification.NOTIFICATION_STATE.COMPLETED.toString();
            notification.setState(Notification.NOTIFICATION_STATE
                    .valueOf(objectToString(doc.get(RodaConstants.NOTIFICATION_STATE), defaultState)));
        }
        return notification;
    }

    public static SolrInputDocument riskIncidenceToSolrDocument(RiskIncidence incidence) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, incidence.getId());
        doc.addField(RodaConstants.RISK_INCIDENCE_ID, incidence.getId());
        doc.addField(RodaConstants.RISK_INCIDENCE_AIP_ID, incidence.getAipId());
        doc.addField(RodaConstants.RISK_INCIDENCE_REPRESENTATION_ID, incidence.getRepresentationId());
        doc.addField(RodaConstants.RISK_INCIDENCE_FILE_PATH, incidence.getFilePath());
        doc.addField(RodaConstants.RISK_INCIDENCE_FILE_ID, incidence.getFileId());
        doc.addField(RodaConstants.RISK_INCIDENCE_OBJECT_CLASS, incidence.getObjectClass());
        doc.addField(RodaConstants.RISK_INCIDENCE_RISK_ID, incidence.getRiskId());
        doc.addField(RodaConstants.RISK_INCIDENCE_DESCRIPTION, incidence.getDescription());
        doc.addField(RodaConstants.RISK_INCIDENCE_BYPLUGIN, incidence.isByPlugin());
        doc.addField(RodaConstants.RISK_INCIDENCE_STATUS, incidence.getStatus().toString());
        doc.addField(RodaConstants.RISK_INCIDENCE_SEVERITY, incidence.getSeverity().toString());
        doc.addField(RodaConstants.RISK_INCIDENCE_DETECTED_ON, incidence.getDetectedOn());
        doc.addField(RodaConstants.RISK_INCIDENCE_DETECTED_BY, incidence.getDetectedBy());
        doc.addField(RodaConstants.RISK_INCIDENCE_MITIGATED_ON, incidence.getMitigatedOn());
        doc.addField(RodaConstants.RISK_INCIDENCE_MITIGATED_BY, incidence.getMitigatedBy());
        doc.addField(RodaConstants.RISK_INCIDENCE_MITIGATED_DESCRIPTION, incidence.getMitigatedDescription());
        doc.addField(RodaConstants.RISK_INCIDENCE_FILE_PATH_COMPUTED, StringUtils.join(incidence.getFilePath(),
                RodaConstants.RISK_INCIDENCE_FILE_PATH_COMPUTED_SEPARATOR));
        return doc;
    }

    public static RiskIncidence solrDocumentToRiskIncidence(SolrDocument doc) {
        RiskIncidence incidence = new RiskIncidence();
        incidence.setId(objectToString(doc.get(RodaConstants.INDEX_UUID), null));
        incidence.setAipId(objectToString(doc.get(RodaConstants.RISK_INCIDENCE_AIP_ID), null));
        incidence
                .setRepresentationId(objectToString(doc.get(RodaConstants.RISK_INCIDENCE_REPRESENTATION_ID), null));
        incidence.setFilePath(objectToListString(doc.get(RodaConstants.RISK_INCIDENCE_FILE_PATH)));
        incidence.setFileId(objectToString(doc.get(RodaConstants.RISK_INCIDENCE_FILE_ID), null));
        incidence.setObjectClass(objectToString(doc.get(RodaConstants.RISK_INCIDENCE_OBJECT_CLASS), null));
        incidence.setRiskId(objectToString(doc.get(RodaConstants.RISK_INCIDENCE_RISK_ID), null));
        incidence.setDescription(objectToString(doc.get(RodaConstants.RISK_INCIDENCE_DESCRIPTION), null));
        if (doc.containsKey(RodaConstants.RISK_INCIDENCE_STATUS)) {
            incidence.setStatus(RiskIncidence.INCIDENCE_STATUS
                    .valueOf(objectToString(doc.get(RodaConstants.RISK_INCIDENCE_STATUS),
                            RiskIncidence.INCIDENCE_STATUS.UNMITIGATED.toString())));
        }
        if (doc.containsKey(RodaConstants.RISK_INCIDENCE_SEVERITY)) {
            incidence.setSeverity(Risk.SEVERITY_LEVEL.valueOf(objectToString(
                    doc.get(RodaConstants.RISK_INCIDENCE_SEVERITY), Risk.SEVERITY_LEVEL.MODERATE.toString())));
        }
        incidence.setDetectedOn(objectToDate(doc.get(RodaConstants.RISK_INCIDENCE_DETECTED_ON)));
        incidence.setDetectedBy(objectToString(doc.get(RodaConstants.RISK_INCIDENCE_DETECTED_BY), null));
        incidence.setMitigatedOn(objectToDate(doc.get(RodaConstants.RISK_INCIDENCE_MITIGATED_ON)));
        incidence.setMitigatedBy(objectToString(doc.get(RodaConstants.RISK_INCIDENCE_MITIGATED_BY), null));
        incidence.setMitigatedDescription(
                objectToString(doc.get(RodaConstants.RISK_INCIDENCE_MITIGATED_DESCRIPTION), null));
        return incidence;
    }

    public static SolrInputDocument dipToSolrDocument(DIP dip) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, dip.getId());
        doc.addField(RodaConstants.DIP_ID, dip.getId());
        doc.addField(RodaConstants.DIP_TITLE, dip.getTitle());
        doc.addField(RodaConstants.DIP_DESCRIPTION, dip.getDescription());
        doc.addField(RodaConstants.DIP_TYPE, dip.getType());
        doc.addField(RodaConstants.DIP_DATE_CREATED, dip.getDateCreated());
        doc.addField(RodaConstants.DIP_LAST_MODIFIED, dip.getLastModified());
        doc.addField(RodaConstants.DIP_IS_PERMANENT, dip.getIsPermanent());
        doc.addField(RodaConstants.DIP_PROPERTIES, JsonUtils.getJsonFromObject(dip.getProperties()));

        doc.addField(RodaConstants.DIP_AIP_IDS, JsonUtils.getJsonFromObject(dip.getAipIds()));
        doc.addField(RodaConstants.DIP_REPRESENTATION_IDS, JsonUtils.getJsonFromObject(dip.getRepresentationIds()));
        doc.addField(RodaConstants.DIP_FILE_IDS, JsonUtils.getJsonFromObject(dip.getFileIds()));

        List<String> allAipUUIDs = new ArrayList<>();
        List<String> allRepresentationUUIDs = new ArrayList<>();

        List<String> aipUUIDs = new ArrayList<>();
        for (AIPLink aip : dip.getAipIds()) {
            aipUUIDs.add(aip.getAipId());
        }

        allAipUUIDs.addAll(aipUUIDs);

        List<String> representationUUIDs = new ArrayList<>();
        for (RepresentationLink rep : dip.getRepresentationIds()) {
            representationUUIDs.add(IdUtils.getRepresentationId(rep));
            if (!allAipUUIDs.contains(rep.getAipId())) {
                allAipUUIDs.add(rep.getAipId());
            }
        }

        allRepresentationUUIDs.addAll(representationUUIDs);

        List<String> fileUUIDs = new ArrayList<>();
        for (FileLink file : dip.getFileIds()) {
            fileUUIDs.add(IdUtils.getFileId(file));
            if (!allAipUUIDs.contains(file.getAipId())) {
                allAipUUIDs.add(file.getAipId());
            }

            String repUUID = IdUtils.getRepresentationId(file.getAipId(), file.getRepresentationId());
            if (!allRepresentationUUIDs.contains(repUUID)) {
                allRepresentationUUIDs.add(repUUID);
            }
        }

        doc.addField(RodaConstants.DIP_AIP_UUIDS, aipUUIDs);
        doc.addField(RodaConstants.DIP_REPRESENTATION_UUIDS, representationUUIDs);
        doc.addField(RodaConstants.DIP_FILE_UUIDS, fileUUIDs);

        doc.addField(RodaConstants.DIP_ALL_AIP_UUIDS, allAipUUIDs);
        doc.addField(RodaConstants.DIP_ALL_REPRESENTATION_UUIDS, allRepresentationUUIDs);

        setPermissions(dip.getPermissions(), doc);

        OptionalWithCause<String> openURL = DIPUtils.getCompleteOpenExternalURL(dip);
        if (openURL.isPresent()) {
            doc.addField(RodaConstants.DIP_OPEN_EXTERNAL_URL, openURL.get());
        } else if (openURL.getCause() != null) {
            LOGGER.error("Error indexing DIP open external URL", openURL.getCause());
        }

        return doc;
    }

    public static IndexedDIP solrDocumentToIndexedDIP(SolrDocument doc, List<String> fieldsToReturn) {
        IndexedDIP dip = new IndexedDIP();
        dip.setId(objectToString(doc.get(RodaConstants.INDEX_UUID), null));
        dip.setTitle(objectToString(doc.get(RodaConstants.DIP_TITLE), null));
        dip.setDescription(objectToString(doc.get(RodaConstants.DIP_DESCRIPTION), null));
        dip.setType(objectToString(doc.get(RodaConstants.DIP_TYPE), null));
        dip.setDateCreated(objectToDate(doc.get(RodaConstants.DIP_DATE_CREATED)));
        dip.setLastModified(objectToDate(doc.get(RodaConstants.DIP_LAST_MODIFIED)));
        dip.setIsPermanent(objectToBoolean(doc.get(RodaConstants.DIP_IS_PERMANENT), Boolean.FALSE));

        boolean emptyFields = fieldsToReturn.isEmpty();

        if (emptyFields || fieldsToReturn.contains(RodaConstants.DIP_PROPERTIES)) {
            dip.setProperties(JsonUtils.getMapFromJson(objectToString(doc.get(RodaConstants.DIP_PROPERTIES), "")));
        }

        try {
            if (emptyFields || fieldsToReturn.contains(RodaConstants.DIP_AIP_IDS)) {
                String aipIds = objectToString(doc.get(RodaConstants.DIP_AIP_IDS), null);
                dip.setAipIds(JsonUtils.getListFromJson(aipIds == null ? "" : aipIds, AIPLink.class));
            }

            if (emptyFields || fieldsToReturn.contains(RodaConstants.DIP_REPRESENTATION_IDS)) {
                String representationIds = objectToString(doc.get(RodaConstants.DIP_REPRESENTATION_IDS), null);
                dip.setRepresentationIds(JsonUtils.getListFromJson(
                        representationIds == null ? "" : representationIds, RepresentationLink.class));
            }

            if (emptyFields || fieldsToReturn.contains(RodaConstants.DIP_FILE_IDS)) {
                String fileIds = objectToString(doc.get(RodaConstants.DIP_FILE_IDS), null);
                dip.setFileIds(JsonUtils.getListFromJson(fileIds == null ? "" : fileIds, FileLink.class));
            }
        } catch (GenericException e) {
            LOGGER.error("Error getting related ids from DIP index");
        }

        dip.setPermissions(getPermissions(doc));
        dip.setOpenExternalURL(objectToString(doc.get(RodaConstants.DIP_OPEN_EXTERNAL_URL), null));
        return dip;
    }

    public static SolrInputDocument dipFileToSolrDocument(DIP dip, DIPFile file) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, IdUtils.getDIPFileId(file));
        List<String> path = file.getPath();
        doc.addField(RodaConstants.DIPFILE_PATH, path);
        if (path != null && !path.isEmpty()) {
            List<String> ancestorsPath = getDIPFileAncestorsPath(file.getDipId(), path);
            if (!ancestorsPath.isEmpty()) {
                doc.addField(RodaConstants.DIPFILE_ANCESTORS_UUIDS, ancestorsPath);
            }

            doc.addField(RodaConstants.DIPFILE_PARENT_UUID, IdUtils.getDIPFileId(file.getDipId(),
                    path.subList(0, path.size() - 1), path.get(path.size() - 1)));
        }

        doc.addField(RodaConstants.DIPFILE_DIP_ID, file.getDipId());
        doc.addField(RodaConstants.DIPFILE_ID, file.getId());
        doc.addField(RodaConstants.DIPFILE_IS_DIRECTORY, file.isDirectory());
        doc.addField(RodaConstants.DIPFILE_SIZE, Long.toString(file.getSize()));

        // extra-fields
        try {
            StoragePath filePath = ModelUtils.getDIPFileStoragePath(file);
            doc.addField(RodaConstants.DIPFILE_STORAGE_PATH, FSUtils.getStoragePathAsString(filePath, false));
        } catch (RequestNotValidException e) {
            LOGGER.warn("Could not index DIP file storage path", e);
        }

        setPermissions(dip.getPermissions(), doc);
        return doc;
    }

    private static List<String> getDIPFileAncestorsPath(String dipId, List<String> path) {
        List<String> parentFileDirectoryPath = new ArrayList<>();
        List<String> ancestorsPath = new ArrayList<>();
        parentFileDirectoryPath.addAll(path);

        while (!parentFileDirectoryPath.isEmpty()) {
            int lastElementIndex = parentFileDirectoryPath.size() - 1;
            String parentFileId = parentFileDirectoryPath.get(lastElementIndex);
            parentFileDirectoryPath.remove(lastElementIndex);
            ancestorsPath.add(0, IdUtils.getDIPFileId(dipId, parentFileDirectoryPath, parentFileId));
        }

        return ancestorsPath;
    }

    public static DIPFile solrDocumentToDIPFile(SolrDocument doc) {
        DIPFile file = new DIPFile();
        file.setUUID(objectToString(doc.get(RodaConstants.INDEX_UUID), null));
        file.setId(objectToString(doc.get(RodaConstants.DIPFILE_ID), null));
        file.setDipId(objectToString(doc.get(RodaConstants.DIPFILE_DIP_ID), null));
        file.setPath(objectToListString(doc.get(RodaConstants.DIPFILE_PATH)));
        file.setAncestorsUUIDs(objectToListString(doc.get(RodaConstants.DIPFILE_ANCESTORS_UUIDS)));
        file.setDirectory(objectToBoolean(doc.get(RodaConstants.DIPFILE_IS_DIRECTORY), Boolean.FALSE));
        file.setStoragePath(objectToString(doc.get(RodaConstants.DIPFILE_STORAGE_PATH), null));
        file.setSize(objectToLong(doc.get(RodaConstants.DIPFILE_SIZE), 0L));
        return file;
    }

    /*
     * Partial updates of RODA objects
     * ____________________________________________________________________________________________________________________
     */

    public static SolrInputDocument aipStateUpdateToSolrDocument(AIP aip) {
        return stateUpdateToSolrDocument(aip.getId(), aip.getState());
    }

    public static SolrInputDocument representationStateUpdateToSolrDocument(Representation representation,
            AIPState state) {
        return stateUpdateToSolrDocument(IdUtils.getRepresentationId(representation), state);
    }

    public static SolrInputDocument fileStateUpdateToSolrDocument(File file, AIPState state) {
        return stateUpdateToSolrDocument(IdUtils.getFileId(file), state);
    }

    public static SolrInputDocument preservationEventStateUpdateToSolrDocument(String preservationEventID,
            String preservationEventAipId, AIPState state) {
        SolrInputDocument document = stateUpdateToSolrDocument(preservationEventID, state);
        document.addField(RodaConstants.PRESERVATION_EVENT_AIP_ID, preservationEventAipId);
        return document;

    }

    private static SolrInputDocument stateUpdateToSolrDocument(String uuid, AIPState state) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, uuid);
        doc.addField(RodaConstants.STATE, set(state.toString()));
        return doc;
    }

    public static SolrInputDocument aipPermissionsUpdateToSolrDocument(AIP aip) {
        SolrInputDocument document = new SolrInputDocument();
        document.addField(RodaConstants.INDEX_UUID, aip.getId());
        return permissionsUpdateToSolrDocument(document, aip.getPermissions());
    }

    public static SolrInputDocument dipPermissionsUpdateToSolrDocument(DIP dip) {
        SolrInputDocument document = new SolrInputDocument();
        document.addField(RodaConstants.INDEX_UUID, dip.getId());
        return permissionsUpdateToSolrDocument(document, dip.getPermissions());
    }

    public static SolrInputDocument representationPermissionsUpdateToSolrDocument(Representation representation,
            Permissions permissions) {
        SolrInputDocument document = new SolrInputDocument();
        document.addField(RodaConstants.INDEX_UUID, IdUtils.getRepresentationId(representation));
        return permissionsUpdateToSolrDocument(document, permissions);
    }

    public static SolrInputDocument filePermissionsUpdateToSolrDocument(File file, Permissions permissions) {
        SolrInputDocument document = new SolrInputDocument();
        document.addField(RodaConstants.INDEX_UUID, IdUtils.getFileId(file));
        return permissionsUpdateToSolrDocument(document, permissions);
    }

    public static SolrInputDocument preservationEventPermissionsUpdateToSolrDocument(String preservationEventID,
            String preservationEventAipId, Permissions permissions, AIPState state) {
        SolrInputDocument document = new SolrInputDocument();
        document.addField(RodaConstants.INDEX_UUID, preservationEventID);
        document = permissionsUpdateToSolrDocument(document, permissions);
        document.addField(RodaConstants.STATE, state.toString());
        document.addField(RodaConstants.PRESERVATION_EVENT_AIP_ID, preservationEventAipId);
        return document;
    }

    private static SolrInputDocument permissionsUpdateToSolrDocument(SolrInputDocument doc,
            Permissions permissions) {
        for (Entry<PermissionType, Set<String>> entry : permissions.getUsers().entrySet()) {
            String key = RodaConstants.INDEX_PERMISSION_USERS_PREFIX + entry.getKey();
            List<String> value = new ArrayList<>(entry.getValue());
            doc.addField(key, set(value));
        }

        for (Entry<PermissionType, Set<String>> entry : permissions.getGroups().entrySet()) {
            String key = RodaConstants.INDEX_PERMISSION_GROUPS_PREFIX + entry.getKey();
            List<String> value = new ArrayList<>(entry.getValue());
            doc.addField(key, set(value));
        }

        return doc;
    }

    public static SolrInputDocument updateAIPParentId(String aipId, String parentId, List<String> ancestors)
            throws RequestNotValidException, GenericException, AuthorizationDeniedException {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, aipId);
        doc.addField(RodaConstants.AIP_PARENT_ID, set(parentId));
        doc.addField(RodaConstants.AIP_ANCESTORS, set(ancestors));
        return doc;
    }

    public static SolrInputDocument updateAIPAncestors(String aipId, List<String> ancestors)
            throws RequestNotValidException, GenericException, AuthorizationDeniedException {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, aipId);
        doc.addField(RodaConstants.AIP_ANCESTORS, set(ancestors));
        return doc;
    }

    public static SolrInputDocument updateRepresentationAncestors(String representationUUID, List<String> ancestors)
            throws RequestNotValidException, GenericException, AuthorizationDeniedException {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, representationUUID);
        doc.addField(RodaConstants.REPRESENTATION_ANCESTORS, set(ancestors));
        return doc;
    }

    public static SolrInputDocument updateFileAncestors(String fileUUID, List<String> ancestors)
            throws RequestNotValidException, GenericException, AuthorizationDeniedException {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(RodaConstants.INDEX_UUID, fileUUID);
        doc.addField(RodaConstants.FILE_ANCESTORS, set(ancestors));
        return doc;
    }

    public static Map<String, Object> set(Object value) {
        Map<String, Object> fieldModifier = new HashMap<>(1);
        // 20160511 this workaround fixes solr wrong behaviour with partial update
        // of empty lists
        if (value instanceof List && ((List<?>) value).isEmpty()) {
            fieldModifier.put("set", null);
        } else {
            fieldModifier.put("set", value);
        }
        return fieldModifier;
    }

    /*
     * Crosswalks auxiliary methods: RODA Objects <-> Apache Solr documents
     * ____________________________________________________________________________________________________________________
     */
    private static Permissions getPermissions(SolrDocument doc) {
        Permissions permissions = new Permissions();

        EnumMap<PermissionType, Set<String>> userPermissions = new EnumMap<>(PermissionType.class);

        for (PermissionType type : PermissionType.values()) {
            String key = RodaConstants.INDEX_PERMISSION_USERS_PREFIX + type;
            Set<String> users = new HashSet<>();
            users.addAll(objectToListString(doc.get(key)));
            userPermissions.put(type, users);
        }

        EnumMap<PermissionType, Set<String>> groupPermissions = new EnumMap<>(PermissionType.class);

        for (PermissionType type : PermissionType.values()) {
            String key = RodaConstants.INDEX_PERMISSION_GROUPS_PREFIX + type;
            Set<String> groups = new HashSet<>();
            groups.addAll(objectToListString(doc.get(key)));
            groupPermissions.put(type, groups);
        }

        permissions.setUsers(userPermissions);
        permissions.setGroups(groupPermissions);
        return permissions;
    }

    private static void setPermissions(Permissions permissions, final SolrInputDocument ret) {

        for (Entry<PermissionType, Set<String>> entry : permissions.getUsers().entrySet()) {
            String key = RodaConstants.INDEX_PERMISSION_USERS_PREFIX + entry.getKey();
            List<String> value = new ArrayList<>(entry.getValue());
            ret.addField(key, value);
        }

        for (Entry<PermissionType, Set<String>> entry : permissions.getGroups().entrySet()) {
            String key = RodaConstants.INDEX_PERMISSION_GROUPS_PREFIX + entry.getKey();
            List<String> value = new ArrayList<>(entry.getValue());
            ret.addField(key, value);
        }
    }

    public static List<String> getAncestors(String parentId, ModelService model)
            throws RequestNotValidException, GenericException, AuthorizationDeniedException {
        List<String> ancestors = new ArrayList<>();
        String nextAncestorId = parentId;
        while (nextAncestorId != null) {
            try {
                AIP nextAncestor = model.retrieveAIP(nextAncestorId);
                if (ancestors.contains(nextAncestorId)) {
                    break;
                }
                ancestors.add(nextAncestorId);
                nextAncestorId = nextAncestor.getParentId();
            } catch (NotFoundException e) {
                LOGGER.warn("Could not find one AIP ancestor. Ancestor id: {}", nextAncestorId);
                nextAncestorId = null;
            }
        }
        return ancestors;
    }

    /**
     * WARNING: this should only be used to debug/tests only
     * 
     * @return
     * @throws IOException
     * @throws SolrServerException
     */
    public static QueryResponse executeSolrQuery(SolrClient index, String collection, String solrQueryString)
            throws SolrServerException, IOException {
        LOGGER.trace("query string: {}", solrQueryString);
        SolrQuery query = new SolrQuery();
        for (String string : solrQueryString.split("&")) {
            String[] split = string.split("=");
            query.add(split[0], split[1]);
        }
        LOGGER.trace("executeSolrQuery: {}", query);
        return index.query(collection, query);
    }

    public static SolrInputDocument premisToSolr(PreservationMetadataType preservationMetadataType, AIP aip,
            String representationUUID, String fileUUID, Binary binary) throws GenericException {
        SolrInputDocument doc;

        Map<String, String> stylesheetOpt = new HashMap<>();
        stylesheetOpt.put(RodaConstants.PRESERVATION_EVENT_OBJECT_CLASS,
                PreservationMetadataEventClass.REPOSITORY.toString());

        if (aip != null) {
            stylesheetOpt.put(RodaConstants.PRESERVATION_EVENT_OBJECT_CLASS,
                    PreservationMetadataEventClass.AIP.toString());
            stylesheetOpt.put(RodaConstants.PRESERVATION_EVENT_AIP_ID, aip.getId());

            if (representationUUID != null) {
                stylesheetOpt.put(RodaConstants.PRESERVATION_EVENT_REPRESENTATION_UUID, representationUUID);
                stylesheetOpt.put(RodaConstants.PRESERVATION_EVENT_OBJECT_CLASS,
                        PreservationMetadataEventClass.REPRESENTATION.toString());
            }

            if (fileUUID != null) {
                stylesheetOpt.put(RodaConstants.PRESERVATION_EVENT_FILE_UUID, fileUUID);
                stylesheetOpt.put(RodaConstants.PRESERVATION_EVENT_OBJECT_CLASS,
                        PreservationMetadataEventClass.FILE.toString());
            }
        }

        Reader reader = null;

        try {
            reader = RodaUtils.applyMetadataStylesheet(binary, RodaConstants.CORE_CROSSWALKS_INGEST_OTHER,
                    RodaConstants.PREMIS_METADATA_TYPE, RodaConstants.PREMIS_METADATA_VERSION, stylesheetOpt);

            XMLLoader loader = new XMLLoader();
            XMLStreamReader parser = XMLInputFactory.newInstance().createXMLStreamReader(reader);

            boolean parsing = true;
            doc = null;
            while (parsing) {
                int event = parser.next();

                if (event == XMLStreamConstants.END_DOCUMENT) {
                    parser.close();
                    parsing = false;
                } else if (event == XMLStreamConstants.START_ELEMENT && "doc".equals(parser.getLocalName())) {
                    doc = loader.readDoc(parser);
                }

            }

        } catch (XMLStreamException | FactoryConfigurationError e) {
            throw new GenericException("Could not process PREMIS " + binary.getStoragePath(), e);
        } finally {
            IOUtils.closeQuietly(reader);
        }

        if (preservationMetadataType == PreservationMetadataType.EVENT && doc != null) {
            try {
                List<LinkingIdentifier> agents = PremisV3Utils.extractAgentsFromEvent(binary);
                for (LinkingIdentifier id : agents) {
                    doc.addField(RodaConstants.PRESERVATION_EVENT_LINKING_AGENT_IDENTIFIER,
                            JsonUtils.getJsonFromObject(id));
                }
            } catch (org.roda.core.data.v2.validation.ValidationException e) {
                LOGGER.warn("Error setting linking agent field: {}", e.getMessage());
            }
            try {
                List<LinkingIdentifier> sources = PremisV3Utils.extractObjectFromEvent(binary);
                for (LinkingIdentifier id : sources) {
                    doc.addField(RodaConstants.PRESERVATION_EVENT_LINKING_SOURCE_OBJECT_IDENTIFIER,
                            JsonUtils.getJsonFromObject(id));
                }
            } catch (org.roda.core.data.v2.validation.ValidationException e) {
                LOGGER.warn("Error setting linking source field: {}", e.getMessage());
            }
            try {
                List<LinkingIdentifier> outcomes = PremisV3Utils.extractObjectFromEvent(binary);
                for (LinkingIdentifier id : outcomes) {
                    doc.addField(RodaConstants.PRESERVATION_EVENT_LINKING_OUTCOME_OBJECT_IDENTIFIER,
                            JsonUtils.getJsonFromObject(id));
                }
            } catch (org.roda.core.data.v2.validation.ValidationException e) {
                LOGGER.warn("Error setting linking outcome field: {}", e.getMessage());
            }

            // indexing active state and permissions
            if (aip != null) {
                doc.addField(RodaConstants.STATE, aip.getState().toString());
                setPermissions(aip.getPermissions(), doc);
            } else {
                doc.addField(RodaConstants.STATE, AIPState.ACTIVE);
            }
        }

        // set uuid from id defined in xslt
        if (doc != null) {
            doc.addField(RodaConstants.INDEX_UUID, doc.getFieldValue(RodaConstants.PRESERVATION_EVENT_ID));
        } else {
            doc = new SolrInputDocument();
        }

        return doc;
    }

    public static <T extends IsIndexed> List<String> suggest(SolrClient index, Class<T> classToRetrieve,
            String field, String queryString, boolean justActive, User user, boolean allowPartial)
            throws GenericException {
        StringBuilder queryBuilder = new StringBuilder();
        appendKeyValue(queryBuilder, field + RodaConstants.INDEX_SEARCH_SUFFIX, queryString + "*");
        SolrQuery query = new SolrQuery();
        query.setParam("q.op", DEFAULT_QUERY_PARSER_OPERATOR);
        query.setQuery(queryBuilder.toString());
        if (hasPermissionFilters(classToRetrieve)) {
            query.addFilterQuery(getFilterQueries(user, justActive, classToRetrieve));
        }
        parseAndConfigureFacets(new Facets(new SimpleFacetParameter(field)), query);
        List<String> suggestions = new ArrayList<>();
        try {
            QueryResponse response = index.query(getIndexName(classToRetrieve).get(0), query);
            response.getFacetField(field).getValues().forEach(count -> suggestions.add(count.getName()));
        } catch (SolrServerException | IOException | SolrException e) {
            throw new GenericException("Could not get suggestions", e);
        } catch (RuntimeException e) {
            throw new GenericException("Unexpected exception while querying index", e);
        }

        return suggestions;
    }

    public static <T extends IsIndexed> void execute(SolrClient index, Class<T> classToRetrieve, Filter filter,
            List<String> fieldsToReturn, IndexRunnable<T> indexRunnable)
            throws GenericException, RequestNotValidException, AuthorizationDeniedException {

        Sorter sorter = null;
        int offset = 0;
        int pagesize = RodaConstants.DEFAULT_PAGINATION_VALUE;
        boolean done;

        do {
            Sublist sublist = new Sublist(offset, pagesize);
            IndexResult<T> find = SolrUtils.find(index, classToRetrieve, filter, sorter, sublist, fieldsToReturn);
            for (T target : find.getResults()) {
                indexRunnable.run(target);
            }
            done = find.getResults().isEmpty();
            offset += pagesize;
        } while (!done);
    }

    private static SolrInputDocument solrDocumentToSolrInputDocument(SolrDocument d) {
        SolrInputDocument doc = new SolrInputDocument();
        for (String name : d.getFieldNames()) {
            doc.addField(name, d.getFieldValue(name));
        }
        return doc;
    }

    public static <T extends IsIndexed> void delete(SolrClient index, Class<T> classToDelete, List<String> ids)
            throws GenericException {
        try {
            index.deleteById(getIndexName(classToDelete).get(0), ids);
        } catch (SolrServerException | IOException e) {
            throw new GenericException("Could not delete items", e);
        }
    }

    public static <T extends IsIndexed> void delete(SolrClient index, Class<T> classToDelete, Filter filter)
            throws GenericException, RequestNotValidException {
        try {
            index.deleteByQuery(getIndexName(classToDelete).get(0), parseFilter(filter));
        } catch (SolrServerException | IOException e) {
            throw new GenericException("Could not delete items", e);
        }
    }
}