br.gov.lexml.oaicat.LexMLOAICatalog.java Source code

Java tutorial

Introduction

Here is the source code for br.gov.lexml.oaicat.LexMLOAICatalog.java

Source

/**
 * this is a work based on OAICat Software,
 *
 *
 * Copyright 2006 OCLC Online Computer Library Center Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or
 * agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */

package br.gov.lexml.oaicat;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;

import ORG.oclc.oai.server.catalog.AbstractCatalog;
import ORG.oclc.oai.server.verb.BadResumptionTokenException;
import ORG.oclc.oai.server.verb.CannotDisseminateFormatException;
import ORG.oclc.oai.server.verb.IdDoesNotExistException;
import ORG.oclc.oai.server.verb.NoItemsMatchException;
import ORG.oclc.oai.server.verb.NoMetadataFormatsException;
import ORG.oclc.oai.server.verb.NoSetHierarchyException;
import ORG.oclc.oai.server.verb.OAIInternalServerError;
import ORG.oclc.oai.util.OAIUtil;

import br.gov.lexml.borda.dao.ConjuntoItemDAO;
import br.gov.lexml.borda.dao.LexMLDAO.CdStatus;
import br.gov.lexml.borda.dao.LexMLDAO.CdValidacao;
import br.gov.lexml.borda.dao.RegistroItemDAO;
import br.gov.lexml.borda.domain.ConjuntoItem;
import br.gov.lexml.borda.domain.RegistroItem;
import br.gov.lexml.borda.domain.RegistroItemErro;

/**
 * Catalogo LexML, baseado no trabalho de Jeff Young NewFileSystemOAICatalog
 */

public class LexMLOAICatalog extends AbstractCatalog implements LexMLOAI {

    public class InternalResumptionToken {

        public String from;
        public String until;
        public String set;
        public String format;
        public String lastId;
        public int offset;
        public int total;

        public InternalResumptionToken(final String p_from, final String p_until, final String p_set,
                final String p_format, final String p_lastId, final int p_offset, final int p_total) {
            from = p_from;
            until = p_until;
            set = p_set;
            lastId = p_lastId;
            offset = p_offset;
            format = p_format;
            total = p_total;
        }

        public InternalResumptionToken(final InternalResumptionToken oldIRT, final String p_lastId,
                final int p_offset) {
            from = oldIRT.from;
            until = oldIRT.until;
            set = oldIRT.set;
            lastId = p_lastId;
            offset = oldIRT.offset + p_offset;
            format = oldIRT.format;
            total = oldIRT.total;
        }

    }

    static boolean debug = false;

    /**
     * Use the current date as the basis for the resumptiontoken
     * <p/>
     * <b>LEXML ready</b>
     * 
     * @return a long integer version of the current time
     */
    private synchronized static String getRSName() {
        Date now = new Date();
        return Long.toString(now.getTime());
    }

    private final SimpleDateFormat sdfData = new SimpleDateFormat("yyyy-MM-dd");
    private final SimpleDateFormat sdfDataHora = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    private final HashMap resumptionTokens = new HashMap();
    private RegistroItemDAO m_ri_dao = null;
    private ConjuntoItemDAO m_ci_dao = null;
    private ArrayList sets = null;

    private final int maxListSize;

    public LexMLOAICatalog(final Properties properties) throws IOException {

        String temp;
        temp = properties.getProperty("LexMLOAICatalog.maxListSize");
        if (temp == null) {
            throw new IllegalArgumentException(
                    "LexMLOAICatalog." + "maxListSize no foi informado no arquivo de properties.");
        }
        maxListSize = Integer.parseInt(temp);

        // TimeZone tz = TimeZone.getTimeZone("UTC");
        // sdfData.setTimeZone(tz);
        // sdfDataHora.setTimeZone(tz);

        prepare();
    }

    /**
     * close the repository
     */
    @Override
    public void close() {
    }

    /**
     * Utility method to construct a Record object for a specified metadataFormat from a native
     * record
     * 
     * @param nativeItem native item from the dataase
     * @param metadataPrefix the desired metadataPrefix for performing the crosswalk
     * @return the <record/> String
     * @exception CannotDisseminateFormatException the record is not available for the specified
     *            metadataPrefix.
     */
    private String constructRecord(final HashMap nativeItem, final String metadataPrefix)
            throws CannotDisseminateFormatException {
        String schemaURL = null;
        Iterator setSpecs = getSetSpecs(nativeItem);
        Iterator abouts = getAbouts(nativeItem);

        if (metadataPrefix != null) {
            if ((schemaURL = getCrosswalks().getSchemaURL(metadataPrefix)) == null) {
                throw new CannotDisseminateFormatException(metadataPrefix);
            }
        }
        return getRecordFactory().create(nativeItem, schemaURL, metadataPrefix, setSpecs, abouts);
    }

    private String constructRecord(final RegistroItem ri, final String metadataPrefix)
            throws CannotDisseminateFormatException {
        return constructRecord(RegistroItem2NativeRecord(ri), metadataPrefix);
    }

    private String constructError(final RegistroItem ri) throws CannotDisseminateFormatException {

        StringBuffer xmlRec = new StringBuffer();

        xmlRec.append("<record><header");
        if (ri.getCdStatus() == CdStatus.DELETADO) {
            xmlRec.append(" status=\"deleted\"");
        }
        xmlRec.append("><identifier>");
        xmlRec.append(OAIUtil.xmlEncode(ri.getIdRegistroItem()));
        xmlRec.append("</identifier><datestamp>");
        xmlRec.append(dateToString(ri.getTsRegistroGmt()));
        xmlRec.append("</datestamp>");
        xmlRec.append("</header>");

        xmlRec.append("<metadata>");

        // Status
        String cdStatus = ri.getCdStatus();
        String status = cdStatus.equals(CdStatus.DELETADO) ? "Deletado" : "Novo";
        xmlRec.append("<Status>" + status + "</Status>");

        if (cdStatus.equals(CdStatus.NOVO)) {

            // Resultado da validao
            String cdValidacao = ri.getCdValidacao();
            String validacao = null;
            if (cdValidacao.equals(CdValidacao.ERRO)) {
                validacao = "Erro";
            } else if (cdValidacao.equals(CdValidacao.INDEFINIDO)) {
                validacao = "Ainda no validado";
            } else if (cdValidacao.equals(CdValidacao.OK)) {
                validacao = "Valiado com sucesso";
            } else if (cdValidacao.equals(CdValidacao.FALHA)) {
                validacao = "Falha";
            } else {
                validacao = "Cdigo de validao '" + cdValidacao + "' desconhecido.";
            }
            xmlRec.append("<Validacao>" + validacao + "</Validacao>");

            // Mensagem de erro
            Set<RegistroItemErro> ries = ri.getRegistroItemErroCollection();
            if (!ries.isEmpty()) {
                RegistroItemErro rie = ries.iterator().next();
                xmlRec.append(
                        "<Mensagem>" + StringEscapeUtils.escapeXml(rie.getDeDetalheItemErro()) + "</Mensagem>");
            }
        }
        xmlRec.append("</metadata>");

        xmlRec.append("</record>");

        return xmlRec.toString();
    }

    private String dateToString(final Date date) {
        return sdfDataHora.format(date);
    }

    /**
     * get an Iterator containing the abouts for the nativeItem
     * 
     * @param rs ResultSet containing the nativeItem
     * @return an Iterator containing the list of about values for this nativeItem
     */
    private Iterator getAbouts(final HashMap nativeItem) {
        return null;
    }

    /**
     * <b>LEXML ready</b>
     * 
     * @return
     */
    private HashMap getNativeHeader(final RegistroItem ri) {

        HashMap recordMap = null;
        ArrayList setSpecs = new ArrayList();

        recordMap = new HashMap();
        recordMap.put(NI.localIdentifier, ri.getIdRegistroItem());
        recordMap.put(NI.lastModified, dateToString(ri.getTsRegistroGmt()));
        ConjuntoItem ci = ri.getConjuntoItem();
        if (null != ci) {
            setSpecs.add(ci.getIdConjuntoItem());
            recordMap.put(NI.setSpecs, setSpecs.iterator());
        }
        if ("D".equalsIgnoreCase(ri.getCdStatus().substring(0, 1))) {
            recordMap.put(NI.status, "deleted");
        }

        return recordMap;
    }

    /*
     * Retrieve a list of Identifiers that satisfy the criteria parameters
     *
     * @param from beginning date in the form of YYYY-MM-DD or null if earliest date is desired
     *
     * @param until ending date in the form of YYYY-MM-DD or null if latest date is desired
     *
     * @param set set name or null if no set is desired
     *
     * @return a Map object containing an optional "resumptionToken" key/value pair and an "identifiers" Map object. The "identifiers" Map contains OAI identifier keys
     * with corresponding values of "true" or null depending on whether the identifier is deleted or not.
     *
     * @exception OAIBadRequestException signals an http status code 400 problem
     */

    private HashMap getNativeRecord(final String identifier, final String metadataFormat) throws IOException {
        RegistroItem ri = m_ri_dao.load(identifier);

        if (null == ri) {
            return null;
        }
        return RegistroItem2NativeRecord(ri);

    }

    /**
     * Retrieve the specified metadata for the specified oaiIdentifier
     * 
     * @param oaiIdentifier the OAI identifier
     * @param metadataPrefix the OAI metadataPrefix
     * @return the Record object containing the result.
     * @exception CannotDisseminateFormatException signals an http status code 400 problem
     * @exception IdDoesNotExistException signals an http status code 404 problem
     * @exception OAIInternalServerError signals an http status code 500 problem
     */
    @Override
    public String getRecord(final String oaiIdentifier, final String metadataPrefix)
            throws IdDoesNotExistException, CannotDisseminateFormatException, OAIInternalServerError {
        HashMap nativeItem = null;
        try {
            String localIdentifier = getRecordFactory().fromOAIIdentifier(oaiIdentifier);

            nativeItem = getNativeRecord(localIdentifier, metadataPrefix);
            if (nativeItem == null) {
                throw new IdDoesNotExistException(oaiIdentifier);
            }
            return constructRecord(nativeItem, metadataPrefix);
        } catch (IOException e) {
            e.printStackTrace();
            throw new OAIInternalServerError("Database Failure");
        }
    }

    /**
     * Retrieve a list of schemaLocation values associated with the specified oaiIdentifier. We get
     * passed the ID for a record and are supposed to return a list of the formats that we can
     * deliver the record in. Since we are assuming that all the records in the directory have the
     * same format, the response to this is static;
     * 
     * @param oaiIdentifier the OAI identifier
     * @return a Vector containing schemaLocation Strings
     * @exception OAIBadRequestException signals an http status code 400 problem
     * @exception OAINotFoundException signals an http status code 404 problem
     */
    @Override
    public Vector getSchemaLocations(final String oaiIdentifier)
            throws IdDoesNotExistException, NoMetadataFormatsException {
        // ArrayList extensionList = null;

        String localIdentifier = getRecordFactory().fromOAIIdentifier(oaiIdentifier);
        // extensionList = getExtensionList(localIdentifier);

        ArrayList extensionList = new ArrayList();
        // extensionList.add("oai_dc");
        extensionList.add("oai_lexml");

        if (extensionList != null) {
            return getRecordFactory().getSchemaLocations(extensionList);
        } else {
            throw new IdDoesNotExistException(oaiIdentifier);
        }
    }

    /**
     * <b>LEXML ready</b>
     * 
     * @return lista dos sets da base lexml-db
     */
    private ArrayList getSets() {
        TreeMap treeMap = new TreeMap();
        String propertyPrefix = "Sets.";
        List<ConjuntoItem> lista = m_ci_dao.list();
        if (null != lista) {
            Iterator<ConjuntoItem> iter = lista.iterator();
            int i = 0;
            while (iter.hasNext()) {
                i++;
                treeMap.put(propertyPrefix + i, helper.ConjuntoItem2Sets(iter.next()));
            }
        }
        return new ArrayList(treeMap.values());
    }

    /**
     * get an Iterator containing the setSpecs for the nativeItem
     * 
     * @param rs ResultSet containing the nativeItem
     * @return an Iterator containing the list of setSpec values for this nativeItem
     */
    private Iterator getSetSpecs(final HashMap nativeItem) {
        return (Iterator) nativeItem.get(NI.setSpecs);
    }

    // #############################################################################################################################

    @Override
    public Map listIdentifiers(final String from, final String until, final String set, final String metadataPrefix)
            throws NoItemsMatchException, CannotDisseminateFormatException {
        List<RegistroItem> ri_list = m_ri_dao.listByTimeWindowAndSet(toDate(from), toDate(until), set, null,
                maxListSize);
        int total = m_ri_dao.countListByTimeWindowAndSet(toDate(from), toDate(until), set);

        String lastId = getLastId(ri_list);
        InternalResumptionToken irt = new InternalResumptionToken(from, until, set, metadataPrefix, lastId,
                ri_list.size(), total);
        return internalListIdentifiers(ri_list, irt);
    }

    @Override
    public Map listIdentifiers(final String resumptionId) throws BadResumptionTokenException {
        InternalResumptionToken irt = (InternalResumptionToken) resumptionTokens.get(resumptionId);
        if (null == irt) {
            throw new BadResumptionTokenException();
        }

        List<RegistroItem> ri_list = m_ri_dao.listByTimeWindowAndSet(toDate(irt.from), toDate(irt.until), irt.set,
                irt.lastId, maxListSize);

        try {
            return internalListIdentifiers(ri_list, irt);
        } catch (CannotDisseminateFormatException e) {
            throw new BadResumptionTokenException();
        } catch (NoItemsMatchException e) {
            // Se a lista retornar vazia  s o fim da paginacao.
        }
        return null;
    }

    private Map internalListIdentifiers(final List<RegistroItem> ri_list, final InternalResumptionToken irt)
            throws CannotDisseminateFormatException, NoItemsMatchException {
        purge(); // clean out old resumptionTokens

        Map listIdentifiersMap = new HashMap();
        ArrayList headers = new ArrayList();
        ArrayList identifiers = new ArrayList();

        int numRows = ri_list.size();
        Iterator<RegistroItem> ri_iter = ri_list.iterator();

        if (numRows == 0) {
            throw new NoItemsMatchException();
        }

        while (ri_iter.hasNext()) {
            String[] header = getRecordFactory().createHeader(getNativeHeader(ri_iter.next()));
            headers.add(header[0]);
            identifiers.add(header[1]);
        }

        // Verifica se temos que informar um ResumptionToken
        if (numRows >= maxListSize) {
            String resumptionId = LexMLOAICatalog.getRSName();
            String lastId = getLastId(ri_list);
            InternalResumptionToken novoIRT = new InternalResumptionToken(irt, lastId, numRows);
            resumptionTokens.put(resumptionId, novoIRT);
            listIdentifiersMap.put("resumptionMap", getResumptionMap(resumptionId, irt.total, irt.offset));
        }

        listIdentifiersMap.put("headers", headers.iterator());
        listIdentifiersMap.put("identifiers", identifiers.iterator());

        return listIdentifiersMap;

    }

    // ###########################################################################################################################
    @Override
    public Map<String, Object> listErrors(final String from, final String until, final String identifier,
            final String set) throws NoItemsMatchException, CannotDisseminateFormatException {

        List<RegistroItem> ri_list;
        InternalResumptionToken irt = null;

        if (StringUtils.isEmpty(identifier)) {
            ri_list = m_ri_dao.listErrorsByTimeWindowAndSet(toDate(from), toDate(until), set, null, maxListSize);
            int total = m_ri_dao.countListErrorsByTimeWindowAndSet(toDate(from), toDate(until), set);
            String lastId = getLastId(ri_list);
            irt = new InternalResumptionToken(from, until, set, null, lastId, ri_list.size(), total);
        } else {
            RegistroItem ri = m_ri_dao.load(identifier);
            if (ri == null) {
                throw new NoItemsMatchException();
            }
            ri_list = new ArrayList<RegistroItem>();
            ri_list.add(ri);
        }

        return internalErrors(ri_list, irt);
    }

    @Override
    public Map<String, Object> listErrors(final String resumptionId) throws BadResumptionTokenException {
        InternalResumptionToken irt = (InternalResumptionToken) resumptionTokens.get(resumptionId);
        if (null == irt) {
            throw new BadResumptionTokenException();
        }

        List<RegistroItem> ri_list = m_ri_dao.listErrorsByTimeWindowAndSet(toDate(irt.from), toDate(irt.until),
                irt.set, irt.lastId, maxListSize);

        try {
            return internalErrors(ri_list, irt);
        } catch (CannotDisseminateFormatException e) {
            throw new BadResumptionTokenException();
        } catch (NoItemsMatchException e) {
            // Se a lista retornar vazia  s o fim da paginacao.
        }
        return null;
    }

    private Map internalErrors(final List<RegistroItem> ri_list, final InternalResumptionToken irt)
            throws CannotDisseminateFormatException, NoItemsMatchException {

        purge(); // clean out old resumptionTokens

        Map listIdentifiersMap = new HashMap();
        ArrayList errors = new ArrayList();

        int numRows = ri_list.size();
        Iterator<RegistroItem> ri_iter = ri_list.iterator();

        if (numRows == 0) {
            throw new NoItemsMatchException();
        }

        while (ri_iter.hasNext()) {
            errors.add(constructError(ri_iter.next()));
            // String[] header = getRecordFactory().createHeader(getNativeHeader(ri_iter.next()));
            // errors.add(header[0]);
            // identifiers.add(header[1]);
        }

        // Verifica se temos que informar um ResumptionToken
        if (numRows >= maxListSize) {
            String resumptionId = LexMLOAICatalog.getRSName();
            String lastId = getLastId(ri_list);
            InternalResumptionToken novoIRT = new InternalResumptionToken(irt, lastId, numRows);
            resumptionTokens.put(resumptionId, novoIRT);
            listIdentifiersMap.put("resumptionMap", getResumptionMap(resumptionId, irt.total, irt.offset));
        }

        listIdentifiersMap.put("errors", errors.iterator());

        return listIdentifiersMap;

    }

    // ###########################################################################################################################

    @Override
    public Map listRecords(final String from, final String until, final String set, final String metadataPrefix)
            throws CannotDisseminateFormatException, OAIInternalServerError, NoItemsMatchException {
        List<RegistroItem> ri_list = m_ri_dao.listByTimeWindowAndSet(toDate(from), toDate(until), set, null,
                maxListSize);
        int total = m_ri_dao.countListByTimeWindowAndSet(toDate(from), toDate(until), set);

        String lastId = getLastId(ri_list);
        InternalResumptionToken irt = new InternalResumptionToken(from, until, set, metadataPrefix, lastId,
                ri_list.size(), total);
        return internalListRecords(ri_list, irt);
    }

    private String getLastId(final List<RegistroItem> list) {
        if (!list.isEmpty()) {
            return list.get(list.size() - 1).getIdRegistroItem();
        }
        return null;
    }

    @Override
    public Map listRecords(final String resumptionId) throws BadResumptionTokenException {
        InternalResumptionToken irt = (InternalResumptionToken) resumptionTokens.get(resumptionId);
        if (null == irt) {
            throw new BadResumptionTokenException();
        }

        List<RegistroItem> ri_list = m_ri_dao.listByTimeWindowAndSet(toDate(irt.from), toDate(irt.until), irt.set,
                irt.lastId, maxListSize);

        try {
            return internalListRecords(ri_list, irt);
        } catch (CannotDisseminateFormatException e) {
            throw new BadResumptionTokenException();
        } catch (NoItemsMatchException e) {
            // A falta de itens indica o fim da paginao e no um erro.
        }
        return null;
    }

    private Map internalListRecords(final List<RegistroItem> ri_list, final InternalResumptionToken irt)
            throws CannotDisseminateFormatException, NoItemsMatchException {
        purge(); // clean out old resumptionTokens
        Map listRecordsMap = new HashMap();
        ArrayList records = new ArrayList();

        int numRows = ri_list.size();
        Iterator<RegistroItem> ri_iter = ri_list.iterator();

        if (numRows == 0) {
            throw new NoItemsMatchException();
        }

        while (ri_iter.hasNext()) {
            records.add(constructRecord(ri_iter.next(), irt.format));
        }
        // Verifica se temos que informar um ResumptionToken
        if (numRows >= maxListSize) {
            String resumptionId = LexMLOAICatalog.getRSName();
            String lastId = getLastId(ri_list);
            InternalResumptionToken novoIRT = new InternalResumptionToken(irt, lastId, numRows);
            resumptionTokens.put(resumptionId, novoIRT);
            listRecordsMap.put("resumptionMap", getResumptionMap(resumptionId, irt.total, irt.offset));
        }
        listRecordsMap.put("records", records.iterator());
        return listRecordsMap;

    }

    /**
     * No faremos paginao de sets
     * <p/>
     * <b>LEXML ready</b>
     */
    @Override
    public Map listSets() throws NoSetHierarchyException {
        // throw new NoSetHierarchyException();
        if (sets.size() == 0) {
            throw new NoSetHierarchyException();
        }
        Map listSetsMap = new LinkedHashMap();
        listSetsMap.put("sets", sets.iterator());
        return listSetsMap;
    }

    /**
     * Nunca faremos paginao de sets
     * <p/>
     * <b>LEXML ready</b>
     */
    @Override
    public Map listSets(final String resumptionToken) throws BadResumptionTokenException {
        throw new BadResumptionTokenException();
    }

    /**
     * <b>LEXML ready</b>
     */
    private void prepare() {
        setHarvestable(false);
        if (null == m_ri_dao) {
            m_ri_dao = new RegistroItemDAO();
        }
        if (null == m_ci_dao) {
            m_ci_dao = new ConjuntoItemDAO();
        }
        if (null == sets) {
            sets = getSets();
        }
        if (null != m_ri_dao && null != m_ci_dao) {
            setHarvestable(true);
        }

    }

    /**
     * Purge tokens that are older than the time-to-live.
     * <p/>
     * <b>LEXML ready</b>
     */
    private void purge() {
        ArrayList old = new ArrayList();
        Date then, now = new Date();
        Iterator keySet = resumptionTokens.keySet().iterator();
        String key;

        while (keySet.hasNext()) {
            key = (String) keySet.next();
            then = new Date(Long.parseLong(key) + getMillisecondsToLive());
            if (now.after(then)) {
                old.add(key);
            }
        }
        Iterator iterator = old.iterator();
        while (iterator.hasNext()) {
            key = (String) iterator.next();
            resumptionTokens.remove(key);
        }
    }

    private HashMap RegistroItem2NativeRecord(final RegistroItem ri) {
        HashMap recordMap = getNativeHeader(ri);
        if (recordMap != null) {
            // Remove diretiva xml
            String metadado = ri.getTxMetadadoXml();
            if (metadado.startsWith("<?xml")) {
                int i = metadado.indexOf("?>");
                if (i != -1) {
                    metadado = metadado.substring(i + 2);
                }
            }
            recordMap.put(NI.recordBytes, metadado);
            return recordMap;
        }
        return null;
    }

    /**
     * <p/>
     * <b>LEXML ready</b>
     * 
     * @param strDate
     * @return
     * @throws ParseException
     */
    private Date toDate(final String strDate) {
        if (StringUtils.isEmpty(strDate)) {
            return null;
        }
        try {
            if (strDate.length() == 10) {
                return sdfData.parse(strDate);
            }
            // Data e hora
            return sdfDataHora.parse(strDate);
        } catch (ParseException e) {
            throw new RuntimeException("Falha ao converter a data '" + strDate + "'", e);
        }
    }
}