All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.gov.nationalarchives.droid.profile.JDBCProfileDao Maven / Gradle / Ivy

There is a newer version: 6.8.1
Show newest version
/**
 * Copyright (c) 2016, The National Archives 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 *  * Neither the name of the The National Archives nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package uk.gov.nationalarchives.droid.profile;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.gov.nationalarchives.droid.core.interfaces.filter.Filter;
import uk.gov.nationalarchives.droid.core.interfaces.filter.expressions.QueryBuilder;
import uk.gov.nationalarchives.droid.profile.referencedata.Format;
import uk.gov.nationalarchives.droid.results.handlers.ResultHandlerDao;
import uk.gov.nationalarchives.droid.results.handlers.JDBCBatchResultHandlerDao;

/**
 * Created by Matt Palmer on 19/06/15.
 */
public class JDBCProfileDao implements ProfileDao {
    //CHECKSTYLE:OFF  Sql Statements can break the rules, e.g. commas quite legitimate...
    private static final String INSERT_FORMAT = "INSERT INTO FORMAT (PUID,MIME_TYPE,NAME,VERSION) VALUES (?,?,?,?)";
    private static final String SELECT_MAIN = "SELECT NODE_ID, EXTENSION_MISMATCH, FINISHED_TIMESTAMP, IDENTIFICATION_COUNT, EXTENSION, HASH, "
                                                   + "IDENTIFICATION_METHOD, LAST_MODIFIED_DATE, NAME, NODE_STATUS, RESOURCE_TYPE, FILE_SIZE, "
                                                   + "PARENT_ID, PREFIX, PREFIX_PLUS_ONE, TEXT_ENCODING, URI FROM PROFILE_RESOURCE_NODE ";
    private static final String FIND_CHILD_NODES         = SELECT_MAIN + "WHERE PARENT_ID=?";
    private static final String FIND_TOP_LEVEL_CHILDREN  = SELECT_MAIN +  "WHERE PARENT_ID IS NULL";
    private static final String FIND_CHILDREN            = "SELECT ID.NODE_ID, ID.PUID FROM IDENTIFICATION AS ID "
                                                           + "INNER JOIN PROFILE_RESOURCE_NODE AS PRN "
                                                           + "ON ID.NODE_ID = PRN.NODE_ID";
    private static final String FIND_CHILD_IDS           = FIND_CHILDREN + " AND PRN.PARENT_ID = ?";
    private static final String FIND_TOP_LEVEL_CHILD_IDS = FIND_CHILDREN + " AND PRN.PARENT_ID IS NULL";

    private static final String dummyPuid = "INSERT INTO FORMAT (PUID,MIME_TYPE,NAME,VERSION) VALUES ('','','','')";
    //CHECKSTYLE:ON
    private static final int FORMAT_PUID_INDEX = 1;
    private static final int FORMAT_MIME_TYPE_INDEX = 2;
    private static final int FORMAT_NAME_INDEX = 3;
    private static final int FORMAT_VERSION_INDEX = 4;

    private final Log log = LogFactory.getLog(getClass());

    private DataSource datasource;
    private ResultHandlerDao resultHandlerDao;

    /**
     *
     * @param datasource The SQL datasource to use.
     */
    public void setDatasource(DataSource datasource) {
        this.datasource = datasource;
    }

    /**
     *
     * @param resultHandlerDao The resultHandlerDao to assign.
     */
    public void setResultHandlerDao(ResultHandlerDao resultHandlerDao) {
        this.resultHandlerDao = resultHandlerDao;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List getAllFormats() {
        return resultHandlerDao.getAllFormats();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void saveFormat(final Format format) {
        try {
            final Connection conn = datasource.getConnection();
            try {
                // BNO getInsertFormatStatement tries to convert the empty Puid to NULL, causing a failed
                // database insert. So as a workaround for now we specify the SQL directly for this case.
                final PreparedStatement insertFormat =
                        (format == Format.NULL)
                                ? conn.prepareStatement(dummyPuid) : getInsertFormatStatement(conn, format);
                insertFormat.execute();
                conn.commit();
            } finally {
                conn.close();
            }
        } catch (SQLException e) {
            log.error("A database exception occurred inserting a format " + format, e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List findProfileResourceNodes(final Long parentId) {
        try {
            final Connection conn = datasource.getConnection();
            try {
                // Get the nodes which are children of the parent id:
                final String query = parentId == null ? FIND_TOP_LEVEL_CHILDREN : FIND_CHILD_NODES;
                final PreparedStatement findChildren = conn.prepareStatement(query);
                if (parentId != null) {
                    SqlUtils.setNullableLong(1, parentId, findChildren);
                }
                final ResultSet children = findChildren.executeQuery();
                final List childNodes = buildNodes(children);
                loadIdentifications(parentId, conn, childNodes);
                return childNodes;
            } finally {
                conn.close();
            }
        } catch (SQLException e) {
            log.error("A database exception occurred finding nodes with parent id " + parentId, e);
        }
        return Collections.emptyList();
    }


    /**
     * {@inheritDoc}
     */

    @Override
    public List findProfileResourceNodes(final Long parentId,
                                                              final Filter filter) {
        final QueryBuilder queryBuilder = SqlUtils.getQueryBuilder(filter);
        final String ejbFilter = queryBuilder.toEjbQl();
        final String query = getSQLQueryString(ejbFilter, parentId);

        try {
            final Connection conn = datasource.getConnection();
            try {
                final PreparedStatement statement = conn.prepareStatement(query);
                setFilterParameters(statement, queryBuilder, parentId);
                final ResultSet results = statement.executeQuery();
                final List filteredNodes = buildNodes(results);
                //TODO: deal with filter status.
                loadIdentifications(parentId, conn, filteredNodes);
                return filteredNodes;
            } finally {
                conn.close();
            }
        } catch (SQLException e) {
            log.error("A database exception occurred finding filtered nodes with parent id " + parentId, e);
        }
        return Collections.emptyList();
    }

    @Override
    public void initialise() {
        populateResultHandlerReferenceData();
    }


    private void loadIdentifications(Long parentId, Connection conn, List childNodes)
        throws SQLException {
        if (childNodes.size() > 0) {
            final String childQuery = parentId == null ? FIND_TOP_LEVEL_CHILD_IDS : FIND_CHILD_IDS;
            final PreparedStatement findIdentifications = conn.prepareStatement(childQuery);
            if (parentId != null) {
                SqlUtils.setNullableLong(1, parentId, findIdentifications);
            }
            final ResultSet identifications = findIdentifications.executeQuery();
            addIdentificationsToNodes(identifications, childNodes, resultHandlerDao.getPUIDFormatMap());
        }
    }

    private void setFilterParameters(final PreparedStatement statement,
                                     final QueryBuilder queryBuilder,
                                     final Long parentId) throws SQLException {
        final Object[] parameters = queryBuilder.getValues();
        int paramCount = 1;
        String nullFilterMessage = "A null filter parameter was encountered at position ";

        // Set the filter parameters for the first part of the query.
        for (final Object parameter : parameters) {
            if (parameter != null) {
                SqlUtils.setNonNullableParameter(paramCount++, parameter, statement);
            } else {
                log.warn(nullFilterMessage + (paramCount - 1));
            }
        }
        // Set the same filter parameters for the second part of the query.
        for (final Object parameter : parameters) {
            if (parameter != null) {
                SqlUtils.setNonNullableParameter(paramCount++, parameter, statement);
            } else {
                log.warn(nullFilterMessage + (paramCount - 1));
            }
        }
        // Set the parent id of the children if not null.
        if (parentId != null) {
            statement.setLong(paramCount, parentId);
        }
    }

    private String getSQLQueryString(final String ejbFilter, final Long parentId) {
        boolean formatCriteriaExist = formatCriteriaExist(ejbFilter);
        boolean formatMetadataExist = formatCriteriaExist && formatMetadataExist(ejbFilter);
        String filterCriteriaDirect = SqlUtils.transformEJBtoSQLFields(ejbFilter,
                "profile", "form");
        String filterCriteriaChild = SqlUtils.transformEJBtoSQLFields(ejbFilter,
                "children", "child_form");

        String query = formatCriteriaExist ? "select distinct profile.*," : "select profile.*,";
        query += " case when (" + filterCriteriaDirect + ") then 1"
                + " else case when profile.resource_type <> 2 and exists ("
                + " select children.node_id from profile_resource_node as children";

        if (formatCriteriaExist) { // if we have filter conditions on the file formats:
            if (formatMetadataExist) { // join to the format table through identifications.
                query = query + " inner join identification as child_ident on child_ident.node_id = children.node_id"
                        + " inner join format as child_form on child_form.puid = child_ident.puid";
            } else { // a puid-only format query only needs to join to identifications.
                query = query + " inner join identification as child_form on child_form.node_id = children.node_id";
            }
        }

        query = query + " where children.prefix > profile.prefix and children.prefix < profile.prefix_plus_one"
                + " and (" + filterCriteriaChild + ")) then 2 else 0 end end as FilterStatus"
                + " from profile_resource_node as profile";

        if (formatCriteriaExist) { // if we have filter conditions on the file formats:
            if (formatMetadataExist) { // join to the format table through identifications.
                query = query + " inner join identification as ident on ident.node_id = profile.node_id"
                        + " inner join format as form on form.puid = ident.puid";
            } else { // // a puid-only format query only needs to join to identifications.
                query = query + " inner join identification as form on form.node_id = profile.node_id";
            }
        }

        return query + " where profile.parent_id " + getParentIdQuery(parentId);
    }

    private boolean formatCriteriaExist(final String filter) {
        return filter.contains("format.");
    }

    private boolean formatMetadataExist(final String filter) {
        return filter.contains("format.mimeType") || filter.contains("format.name");
    }

    private PreparedStatement getInsertFormatStatement(Connection conn, Format format) throws SQLException {
        final PreparedStatement insertFormat = conn.prepareStatement(INSERT_FORMAT);
        insertFormat.setString(FORMAT_PUID_INDEX, format.getPuid());
        SqlUtils.setNullableString(FORMAT_MIME_TYPE_INDEX, format.getMimeType(), insertFormat);
        SqlUtils.setNullableString(FORMAT_NAME_INDEX, format.getName(), insertFormat);
        SqlUtils.setNullableString(FORMAT_VERSION_INDEX, format.getVersion(), insertFormat);
        return insertFormat;
    }

    private List buildNodes(final ResultSet children) throws SQLException {
        final List childNodes = new ArrayList(32);
        while (children.next()) {
            final ProfileResourceNode child = SqlUtils.buildProfileResourceNode(children);
            if (child.getFilterStatus() > 0) {
                childNodes.add(child);
            }
        }
        return childNodes;
    }

    private void addIdentificationsToNodes(final ResultSet identifications,
                                           final List childNodes,
                                           final Map puidFormatMap) throws SQLException {
        final Map nodeIdMap = buildNodeIdMap(childNodes);
        while (identifications.next()) {
            final ProfileResourceNode node   = nodeIdMap.get(identifications.getLong(1));
            final Format              format = puidFormatMap.get(identifications.getString(2));
            if (node != null && format != null) {
                node.addFormatIdentification(format);
            }
        }
    }

    private Map buildNodeIdMap(List nodes) {
        final Map nodeIdMap = new HashMap(nodes.size() * 2);
        for (final ProfileResourceNode node : nodes) {
            nodeIdMap.put(node.getId(), node);
        }
        return nodeIdMap;
    }

    private String getParentIdQuery(Long parentId) {
        return parentId == null ? "is null" : " = ?";
    }

    private void populateResultHandlerReferenceData() {
        if (this.resultHandlerDao instanceof JDBCBatchResultHandlerDao) {
            //initalize the formats
            JDBCBatchResultHandlerDao batchResultHandler = (JDBCBatchResultHandlerDao) this.resultHandlerDao;
            batchResultHandler.initialiseForNewTemplate();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy