ch.dbs.actions.bestand.Stock.java Source code

Java tutorial

Introduction

Here is the source code for ch.dbs.actions.bestand.Stock.java

Source

//  Copyright (C) 2005 - 2010  Markus Fischer, Pascal Steiner
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; version 2 of the License.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
//  Contact: info@doctor-doc.com

package ch.dbs.actions.bestand;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.actions.DispatchAction;
import org.apache.struts.upload.FormFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import util.Auth;
import util.CSV;
import util.Check;
import util.XLSReader;
import ch.dbs.entity.Bestand;
import ch.dbs.entity.Holding;
import ch.dbs.entity.Issn;
import ch.dbs.entity.Text;
import ch.dbs.form.ActiveMenusForm;
import ch.dbs.form.ErrorMessage;
import ch.dbs.form.FileForm;
import ch.dbs.form.HoldingForm;
import ch.dbs.form.Message;
import ch.dbs.form.OrderForm;
import ch.dbs.form.UserInfo;
import enums.Result;
import enums.TextType;

/**
 * Class to manage holdings information. Import / Export functions.
 * 
 * @author Markus Fischer
 */
public class Stock extends DispatchAction {

    private static final Logger LOG = LoggerFactory.getLogger(Stock.class);
    private static final int COLUMNS = 21; // Number of columns per import line
    private static final int FILESIZELIMIT = 500000; // ca. 500 KB. Limits the file size for upload to avoid OutOfMemory errors
    private static final char DELIMITER_CSV = ';'; // Delimiter for CSV-Export

    //Delimiter for Tab delimited txt-Export (Excel can't read UTF-8 in CSV...)
    private static final char DELIMITER_TXT = '\t';

    /**
     * Access control for the holdings export page.
     */
    public ActionForward prepareExport(final ActionMapping mp, final ActionForm fm, final HttpServletRequest rq,
            final HttpServletResponse rp) {

        final Auth auth = new Auth();
        // make sure the user is logged in
        if (!auth.isLogin(rq)) {
            return mp.findForward(Result.ERROR_TIMEOUT.getValue());
        }
        // check access rights
        if (!auth.isBibliothekar(rq) && !auth.isAdmin(rq)) {
            return mp.findForward(Result.ERROR_MISSING_RIGHTS.getValue());
        }

        return mp.findForward(Result.SUCCESS.getValue());
    }

    /**
     * Access control for the holdings import page.
     */
    public ActionForward prepareImport(final ActionMapping mp, final ActionForm fm, final HttpServletRequest rq,
            final HttpServletResponse rp) {

        final Auth auth = new Auth();
        // make sure the user is logged in
        if (!auth.isLogin(rq)) {
            return mp.findForward(Result.ERROR_TIMEOUT.getValue());
        }
        // check access rights
        if (!auth.isBibliothekar(rq) && !auth.isAdmin(rq)) {
            return mp.findForward(Result.ERROR_MISSING_RIGHTS.getValue());
        }

        String forward = Result.FAILURE.getValue();
        final Text t = new Text();

        try {

            forward = Result.SUCCESS.getValue();
            final UserInfo ui = (UserInfo) rq.getSession().getAttribute("userinfo");
            final HoldingForm hf = (HoldingForm) fm;

            hf.setStandorte(t.getAllKontoText(TextType.LOCATION, ui.getKonto().getId(), t.getConnection()));

            rq.setAttribute("holdingform", hf);

        } finally {
            t.close();
        }

        return mp.findForward(forward);
    }

    /**
     * Import a file with holdings information.
     */
    public ActionForward importHoldings(final ActionMapping mp, final ActionForm fm, final HttpServletRequest rq,
            final HttpServletResponse rp) {

        final Auth auth = new Auth();
        // make sure the user is logged in
        if (!auth.isLogin(rq)) {
            return mp.findForward(Result.ERROR_TIMEOUT.getValue());
        }
        // check access rights
        if (!auth.isBibliothekar(rq) && !auth.isAdmin(rq)) {
            return mp.findForward(Result.ERROR_MISSING_RIGHTS.getValue());
        }

        // Prepare classes
        String forward = Result.FAILURE.getValue();
        final Check ck = new Check();
        final Text cn = new Text();
        final FileForm fileForm = (FileForm) fm;
        final UserInfo ui = (UserInfo) rq.getSession().getAttribute("userinfo");

        try {
            // get uploaded File
            final FormFile upload = fileForm.getFile();
            final String fileName = upload.getFileName();

            if (fileForm.isCondition()) { // conditions for upload must be accepted
                // must be tab delimited or csv file
                if (ck.isFiletypeExtension(fileName, ".txt") || ck.isFiletypeExtension(fileName, ".csv")
                        || ck.isFiletypeExtension(fileName, ".xls") || ck.isFiletypeExtension(fileName, ".xlsx")) {
                    if (upload.getFileSize() < FILESIZELIMIT) { // limit file size to avoid OutOfMemory errors

                        // Excel is able to read UTF-8 encoded tab delimited files. But after editing the file,
                        // it will save the file as ANSI CP1250. There is no option to save the file as UTF-8.
                        // So we have to switch encoding if there has been uploaded a TXT file.
                        String encoding = "UTF-8";
                        char delimiter = DELIMITER_CSV; // default value
                        if (ck.isFiletypeExtension(fileName, ".txt")) {
                            delimiter = DELIMITER_TXT; // tab delimited file
                            encoding = "CP1250"; // Windows ANSI encoding...
                        }

                        List<List<String>> stockList = new ArrayList<List<String>>();
                        List<Message> messageList = new ArrayList<Message>();

                        try {
                            // Get an List<List<String>> representation of the file
                            if (ck.isFiletypeExtension(fileName, ".txt")
                                    || ck.isFiletypeExtension(fileName, ".csv")) {
                                stockList = readCSVImport(upload, delimiter, encoding);
                            } else if (ck.isFiletypeExtension(fileName, ".xls")) {
                                stockList = readXLSImport(upload);
                            } else {
                                final Message msg = new Message("error.import.failed",
                                        "Filetype XLSX not supported! Use XLS...", "");
                                messageList.add(msg);
                            }
                        } catch (final Exception e) {
                            final Message msg = new Message("error.import.failed", e.toString(), "");
                            messageList.add(msg);
                        }

                        // Check for errors reading file
                        if (messageList.isEmpty()) {

                            // Check if the file contains the correct number of columns
                            messageList = checkColumns(stockList);
                            if (messageList.isEmpty()) {

                                // Basic checks and make sure all entries in stockList are parsable
                                messageList = checkBasicParsability(stockList);
                                if (messageList.isEmpty()) {

                                    // Convert to ArrayList<Bestand>
                                    final List<Bestand> bestandList = convertToBestand(stockList, ui);

                                    // Check integrity of Bestand()
                                    messageList = checkBestandIntegrity(bestandList, ui, cn.getConnection());
                                    if (messageList.isEmpty()) {

                                        // save or update holdings, delete all other holdings
                                        final String successMessage = update(bestandList, ui, cn.getConnection());

                                        forward = Result.SUCCESS.getValue();

                                        final Message msg = new Message("import.success", successMessage,
                                                "allstock.do?method=prepareExport&activemenu=stock");

                                        rq.setAttribute("message", msg);

                                    } else { // detailed errors while checking integrity of Bestand() objects
                                        forward = "importError";
                                        final ActiveMenusForm mf = new ActiveMenusForm();
                                        mf.setActivemenu("stock");
                                        rq.setAttribute(Result.ACTIVEMENUS.getValue(), mf);
                                        rq.setAttribute("messageList", messageList);
                                        final Message em = new Message("error.import.heading",
                                                "stock.do?method=prepareImport&activemenu=stock");
                                        rq.setAttribute("singleMessage", em);
                                    }
                                } else { // basic errors before parsing to Bestand() objects
                                    forward = "importError";
                                    final ActiveMenusForm mf = new ActiveMenusForm();
                                    mf.setActivemenu("stock");
                                    rq.setAttribute(Result.ACTIVEMENUS.getValue(), mf);
                                    rq.setAttribute("messageList", messageList);
                                    final Message em = new Message("error.import.heading",
                                            "stock.do?method=prepareImport&activemenu=stock");
                                    rq.setAttribute("singleMessage", em);
                                }
                            } else { // Wrong number of columns
                                forward = "importError";
                                final ActiveMenusForm mf = new ActiveMenusForm();
                                mf.setActivemenu("stock");
                                rq.setAttribute(Result.ACTIVEMENUS.getValue(), mf);
                                rq.setAttribute("messageList", messageList);
                                final Message em = new Message("error.import.heading",
                                        "stock.do?method=prepareImport&activemenu=stock");
                                rq.setAttribute("singleMessage", em);
                            }
                        } else { // Error reading file
                            forward = "importError";
                            final ActiveMenusForm mf = new ActiveMenusForm();
                            mf.setActivemenu("stock");
                            rq.setAttribute(Result.ACTIVEMENUS.getValue(), mf);
                            rq.setAttribute("messageList", messageList);
                            final Message em = new Message("error.import.heading",
                                    "stock.do?method=prepareImport&activemenu=stock");
                            rq.setAttribute("singleMessage", em);
                        }
                    } else { // Filesize limit
                        final ActiveMenusForm mf = new ActiveMenusForm();
                        mf.setActivemenu("stock");
                        rq.setAttribute(Result.ACTIVEMENUS.getValue(), mf);
                        final ErrorMessage em = new ErrorMessage("error.filesize",
                                "stock.do?method=prepareImport&activemenu=stock");
                        rq.setAttribute(Result.ERRORMESSAGE.getValue(), em);
                    }
                } else { // Filetype error
                    final ActiveMenusForm mf = new ActiveMenusForm();
                    mf.setActivemenu("stock");
                    rq.setAttribute(Result.ACTIVEMENUS.getValue(), mf);
                    final ErrorMessage em = new ErrorMessage("error.filetype",
                            "stock.do?method=prepareImport&activemenu=stock");
                    rq.setAttribute(Result.ERRORMESSAGE.getValue(), em);
                }
            } else { // Did not accept conditions
                final ActiveMenusForm mf = new ActiveMenusForm();
                mf.setActivemenu("stock");
                rq.setAttribute(Result.ACTIVEMENUS.getValue(), mf);
                final ErrorMessage em = new ErrorMessage("error.import.condition",
                        "stock.do?method=prepareImport&activemenu=stock");
                rq.setAttribute(Result.ERRORMESSAGE.getValue(), em);
            }

        } finally {
            cn.close();
        }

        return mp.findForward(forward);
    }

    /**
     * Gets all holding locations for a given library.
     */
    public ActionForward listStockplaces(final ActionMapping mp, final ActionForm fm, final HttpServletRequest rq,
            final HttpServletResponse rp) {

        final Auth auth = new Auth();
        // make sure the user is logged in
        if (!auth.isLogin(rq)) {
            return mp.findForward(Result.ERROR_TIMEOUT.getValue());
        }
        // check access rights
        if (!auth.isBibliothekar(rq) && !auth.isAdmin(rq)) {
            return mp.findForward(Result.ERROR_MISSING_RIGHTS.getValue());
        }

        String forward = Result.FAILURE.getValue();
        final Text t = new Text();

        try {

            forward = Result.SUCCESS.getValue();
            final UserInfo ui = (UserInfo) rq.getSession().getAttribute("userinfo");
            final HoldingForm hf = (HoldingForm) fm;

            hf.setStandorte(t.getAllKontoText(TextType.LOCATION, ui.getKonto().getId(), t.getConnection()));

            if (hf.getStandorte().size() == 0) { //
                hf.setMessage("error.stockplacesmodify.nolocations");
            }

            rq.setAttribute("holdingform", hf);

        } finally {
            t.close();
        }

        return mp.findForward(forward);
    }

    /**
     * Prepares and changes a given holding location.
     */
    public ActionForward changeStockplace(final ActionMapping mp, final ActionForm fm, final HttpServletRequest rq,
            final HttpServletResponse rp) {

        final Auth auth = new Auth();
        // make sure the user is logged in
        if (!auth.isLogin(rq)) {
            return mp.findForward(Result.ERROR_TIMEOUT.getValue());
        }
        // check access rights
        if (!auth.isBibliothekar(rq) && !auth.isAdmin(rq)) {
            return mp.findForward(Result.ERROR_MISSING_RIGHTS.getValue());
        }

        String forward = Result.FAILURE.getValue();
        final Text t = new Text();

        try {

            final UserInfo ui = (UserInfo) rq.getSession().getAttribute("userinfo");
            final HoldingForm hf = (HoldingForm) fm;

            // Make sure the Text() and the location belongs to the given account
            final Text txt = new Text(t.getConnection(), hf.getStid(), ui.getKonto().getId(), TextType.LOCATION);
            if (txt.getId() != null) {
                forward = Result.SUCCESS.getValue();

                if (!hf.isMod() && !hf.isDel()) { // Prepares for changing a location
                    hf.setMod(true); // flags a location to be changed for the JSP
                    final ArrayList<Text> sl = new ArrayList<Text>();
                    sl.add(txt);
                    hf.setStandorte(sl);
                } else if (hf.isMod()) { // update the given Text() / location
                    txt.setInhalt(hf.getStandortid());
                    txt.updateText(t.getConnection(), txt);
                    hf.setMod(false); // back to the location list
                    hf.setStandorte(t.getAllKontoText(TextType.LOCATION, ui.getKonto().getId(), t.getConnection()));
                } else if (hf.isDel()) { // delete the given Text() / location
                    // Check if there still exist holdings for this location
                    final Bestand bestand = new Bestand();
                    final ArrayList<Bestand> sl = new ArrayList<Bestand>(
                            bestand.getAllBestandForStandortId(txt.getId(), t.getConnection()));
                    if (sl == null || sl.isEmpty()) {
                        txt.deleteText(t.getConnection(), txt);
                        hf.setMod(false);
                        hf.setDel(false);
                    } else {
                        // there are still holdings for this location!
                        hf.setMessage("error.stockplacesmodify.notdeletable");
                    }
                    hf.setStandorte(t.getAllKontoText(TextType.LOCATION, ui.getKonto().getId(), t.getConnection()));
                }

                rq.setAttribute("holdingform", hf);

            } else { // URL-hacking
                final ErrorMessage em = new ErrorMessage("error.hack", "login.do");
                rq.setAttribute(Result.ERRORMESSAGE.getValue(), em);
                LOG.info("changeStockplace: prevented URL-hacking! " + ui.getBenutzer().getEmail());
            }

        } finally {
            t.close();
        }

        return mp.findForward(forward);
    }

    /**
     * Gets a list of all holdings from all libraries.
     * 
     * @param OrderForm
     * @param boolean
     * @return list<Bestand>
     */
    public List<Bestand> checkGeneralStockAvailability(final OrderForm pageForm, final boolean internal) {

        List<Bestand> bestaende = new ArrayList<Bestand>();
        final Text cn = new Text();
        final Holding ho = new Holding();
        final Bestand be = new Bestand();

        try {
            // get Holdings from ISSN
            if (pageForm.getIssn() != null && !"".equals(pageForm.getIssn())) {

                final List<String> setIssn = getRelatedIssn(pageForm.getIssn(), cn.getConnection());
                final List<String> hoids = ho.getAllHOIDs(setIssn, cn.getConnection());
                bestaende = be.getAllBestandForHoldings(hoids, pageForm, internal, cn.getConnection());

                // get Holdings from title
            } else if (pageForm.getZeitschriftentitel() != null && !"".equals(pageForm.getZeitschriftentitel())) {

                final List<String> hoids = ho.getAllHOIDs(pageForm.getZeitschriftentitel(), cn.getConnection());
                bestaende = be.getAllBestandForHoldings(hoids, pageForm, internal, cn.getConnection());
            }

        } finally {
            cn.close();
        }

        return bestaende;

    }

    /**
     * Gets a list of all holdings for a given library from an IP.
     * 
     * @param OrderForm
     * @param Text
     * @param boolean
     * @param Connection
     * @return list<Bestand>
     */
    public List<Bestand> checkStockAvailabilityForIP(final OrderForm pageForm, final Text tip,
            final boolean internal, final Connection cn) {

        List<Bestand> bestaende = new ArrayList<Bestand>();

        final Bestand be = new Bestand();
        final Holding ho = new Holding();

        // Do only check if we have an account (Konto)
        if (tip.getKonto() != null && tip.getKonto().getId() != null) {

            // get Holdings from ISSN
            if (pageForm.getIssn() != null && !"".equals(pageForm.getIssn())) {

                final List<String> setIssn = getRelatedIssn(pageForm.getIssn(), cn);
                final List<String> hoids = ho.getAllHOIDsForKonto(setIssn, tip.getKonto().getId(), cn);
                bestaende = be.getAllBestandForHoldings(hoids, pageForm, internal, cn);

                // get Holdings from title
            } else if (pageForm.getZeitschriftentitel() != null && !"".equals(pageForm.getZeitschriftentitel())) {

                final List<String> hoids = ho.getAllHOIDsForKonto(pageForm.getZeitschriftentitel(),
                        tip.getKonto().getId(), cn);
                bestaende = be.getAllBestandForHoldings(hoids, pageForm, internal, cn);

            }
        }

        return bestaende;

    }

    /**
     * Runs basic checks and makes sure that the ArrayList<Bestand> is parsable
     * to Bestand().
     * 
     * @param List <List<String>>
     * @return List<Message>
     */
    private List<Message> checkBasicParsability(final List<List<String>> stockList) {

        final List<Message> messageList = new ArrayList<Message>();

        final int max = stockList.size();
        for (int i = 1; i < max; i++) { // start at position 1, thus ignoring the header
            final List<String> importLines = stockList.get(i);
            int column = 0; // column number of element to check
            for (String line : importLines) {
                Message msg;
                column++;
                if (line == null) {
                    line = "";
                }
                switch (column) {
                case 1: // Stock-ID
                    msg = checkStockID(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                case 2: // Holding-ID
                    msg = checkHoldingID(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                case 3: // Location-ID
                    msg = checkLocationID(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                case 4: // Location
                    break;
                case 5: // Shelfmark
                    break;
                case 6: // Title
                    msg = checkTitle(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                case 7: // Coden
                    break;
                case 8: // Publisher
                    break;
                case 9: // Place
                    break;
                case 10: // ISSN
                    msg = checkISSN(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                case 11: // ZDB-ID
                    break;
                case 12: // Startyear
                    msg = checkStartyear(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                case 13: // Startvolume
                    break;
                case 14: // Startissue
                    break;
                case 15: // Endyear
                    msg = checkEndyear(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                case 16: // Endvolume
                    break;
                case 17: // Endissue
                    break;
                case 18: // Supplement
                    msg = checkSuppl(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                case 19: // remarks
                    break;
                case 20: // eissue
                    msg = checkBoolean(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                case 21: // internal
                    msg = checkBoolean(i + 1, line);
                    if (msg.getMessage() != null) {
                        messageList.add(msg);
                    }
                    break;
                default:
                    LOG.error("Stock ckeck BasicParsability - Unpredicted switch case in default: " + column);
                }
            }
        }

        return messageList;
    }

    /**
     * Checks if the import file has the correct format, by counting the columns
     * per line.
     * 
     * @param List <List<String>>
     * @return List<Message>
     */
    private List<Message> checkColumns(final List<List<String>> stockList) {

        final List<Message> messageList = new ArrayList<Message>();
        int lineCount = 0;

        for (final List<String> importLine : stockList) {
            lineCount++; // Lines start at position 1
            if (importLine.size() != COLUMNS) {
                final Message msg = new Message("error.import.separators", composeSystemMessage(lineCount, ""), "");
                messageList.add(msg);
            }
        }

        return messageList;
    }

    /**
     * Checks that each Bestand() has all necessary entries and belongs to the
     * account uploading the file.
     * 
     * @param List <Bestand>
     * @param UserInfo
     * @param Connection
     * @return List<Message>
     */
    private List<Message> checkBestandIntegrity(final List<Bestand> bestandList, final UserInfo ui,
            final Connection cn) {

        final List<Message> messageList = new ArrayList<Message>();
        final HashSet<Long> uniqueSetStockID = new HashSet<Long>();
        int lineCount = 1; // the header of the ArrayList<Bestand> is already omitted

        for (final Bestand b : bestandList) {
            lineCount++;

            if (b.getId() != null) {

                // check for unique Stock-ID
                if (uniqueSetStockID.contains(b.getId())) {
                    final Message msg = new Message();
                    msg.setMessage("error.import.stid.notUnique");
                    msg.setSystemMessage(composeSystemMessage(lineCount, b.getId()));
                    messageList.add(msg);
                } else { // if not found add Stock-ID
                    uniqueSetStockID.add(b.getId());
                }

                // check if Bestand is from account
                // internal holdings are visible
                final Bestand control = new Bestand(b.getId(), true, cn); // get control Bestand from specified ID

                // check if KID from Bestand matches KID from ui
                if (control.getHolding() != null && control.getHolding().getKid().equals(ui.getKonto().getId())) {

                    // check if Holding-ID from control matches Holding-ID specified
                    if (!control.getHolding().getId().equals(b.getHolding().getId())) {
                        final Message msg = new Message();
                        msg.setMessage("error.import.hoid.doesNotMatchStid");
                        msg.setSystemMessage(composeSystemMessage(lineCount, b.getHolding().getId()));
                        messageList.add(msg);
                    }
                } else { // KID from Bestand does not match KID from ui!
                    final Message msg = new Message();
                    msg.setMessage("error.import.stid.notFromAccount");
                    msg.setSystemMessage(composeSystemMessage(lineCount, b.getId()));
                    messageList.add(msg);
                }
            }

            // check location-ID and location
            if (b.getStandort().getId() != null) {
                // check if location-ID is from account
                // get control location from specified ID
                final Text control = new Text(cn, b.getStandort().getId(), ui.getKonto().getId(),
                        TextType.LOCATION);

                if (control.getId() == null) { // Location-ID does not belong to account
                    final Message msg = new Message();
                    msg.setMessage("error.import.locationid");
                    msg.setSystemMessage(composeSystemMessage(lineCount, b.getStandort().getId()));
                    messageList.add(msg);

                    // Location-ID belongs to account, but do the locations match?
                } else if (!b.getStandort().getInhalt().equals("")
                        && !b.getStandort().getInhalt().equals(control.getInhalt())) { // locations do not match...
                    final Message msg = new Message();
                    msg.setMessage("error.import.locationsDoNotMatch");
                    if (b.getStandort().getId() != null) {
                        msg.setSystemMessage(composeSystemMessage(lineCount,
                                b.getStandort().getId().toString() + "/" + b.getStandort().getInhalt()));
                    } else {
                        msg.setSystemMessage(
                                composeSystemMessage(lineCount, "null/" + b.getStandort().getInhalt()));
                    }
                    messageList.add(msg);
                }
            } else if (b.getStandort().getInhalt().equals("")) { // check if location is present
                final Message msg = new Message();
                msg.setMessage("error.import.location");
                msg.setSystemMessage(composeSystemMessage(lineCount, b.getStandort().getInhalt()));
                messageList.add(msg);
            }

            // if there is an endvolume or and endissue => there must be an endyear
            if ((!"".equals(b.getEndvolume()) || !"".equals(b.getEndissue())) && "".equals(b.getEndyear())) {
                final Message msg = new Message();
                msg.setMessage("error.import.endyear");
                msg.setSystemMessage(composeSystemMessage(lineCount, ""));
                messageList.add(msg);
            }

        }

        return messageList;
    }

    /**
     * Converts an CSV/TXT import file into an ArrayList<List<String>> with all
     * the text line elements.
     * 
     * @param FormFile
     * @param char
     * @param String
     * @return List<List<String>>
     */
    private List<List<String>> readCSVImport(final FormFile upload, final char delimiter, final String encoding)
            throws Exception {

        final List<List<String>> result = new ArrayList<List<String>>();
        String line = "";
        BufferedInputStream fileStream = null;
        BufferedReader br = null;

        try {

            fileStream = new BufferedInputStream(upload.getInputStream());
            br = new BufferedReader(new InputStreamReader(fileStream, encoding));

            while ((line = br.readLine()) != null && !line.equals("")) {
                final CSV importFile = new CSV(delimiter);
                result.add(importFile.parse(line));
            }

        } finally {
            try {
                br.close();
                fileStream.close();
            } catch (final IOException e) {
                LOG.error(e.toString());
            }
        }

        return result;
    }

    /**
     * Converts an XLS/XLSX import file into an ArrayList<List<String>> with all
     * the text line elements.
     * 
     * @param FormFile
     * @return List<List<String>>
     */
    private List<List<String>> readXLSImport(final FormFile upload) throws Exception {

        List<List<String>> result = new ArrayList<List<String>>();
        BufferedInputStream fileStream = null;

        try {

            final XLSReader xlsReader = new XLSReader();
            fileStream = new BufferedInputStream(upload.getInputStream());
            result = xlsReader.read(fileStream);

        } finally {
            try {
                fileStream.close();
            } catch (final IOException e) {
                LOG.error(e.toString());
            }
        }

        return result;
    }

    /**
     * Converts an ArrayList<List<String>> with import elements into an
     * ArrayList<Bestand>.
     * 
     * @param List<List<String>>
     * @param UserInfo
     * @return List<Bestand>
     */
    private List<Bestand> convertToBestand(final List<List<String>> stockList, final UserInfo ui) {

        final List<Bestand> bestandList = new ArrayList<Bestand>();

        final int max = stockList.size();
        for (int i = 1; i < max; i++) { // start at position 1, thus ignoring the header
            bestandList.add(getBestand(stockList.get(i), ui.getKonto().getId()));
        }

        return bestandList;
    }

    /**
     * Converts a List<String> with elements of one import line into a
     * Bestand(). It relies on the assumption, that all integrity checks for
     * formatting etc. have been run before!
     * 
     * @param List<String>
     * @param long
     * @return Bestand
     */
    private Bestand getBestand(final List<String> list, final long kid) {
        final Bestand b = new Bestand();

        int column = 0; // column number of CSV entry to check
        for (String line : list) {
            column++;
            if (line == null) {
                line = "";
            }

            switch (column) {
            case 1: // Stock-ID
                if (!"".equals(line)) { // If ID unknown, it should be null
                    b.setId(Long.valueOf(line));
                }
                break;
            case 2: // Holding-ID
                b.getHolding().setKid(kid); // set the kid from UserInfo
                if (!"".equals(line)) { // If ID unknown, it should be null
                    b.getHolding().setId(Long.valueOf(line));
                }
                break;
            case 3: // Location-ID
                if (!"".equals(line)) { // If ID unknown, it should be null
                    b.getStandort().setId(Long.valueOf(line));
                }
                break;
            case 4: // Location
                b.getStandort().setInhalt(line);
                break;
            case 5: // Shelfmark
                b.setShelfmark(line);
                break;
            case 6: // Title
                b.getHolding().setTitel(line);
                break;
            case 7: // Coden
                if ("".equals(line)) {
                    b.getHolding().setCoden(null);
                } else {
                    b.getHolding().setCoden(line);
                }
                break;
            case 8: // Publisher
                b.getHolding().setVerlag(line);
                break;
            case 9: // Place
                b.getHolding().setOrt(line);
                break;
            case 10: // ISSN
                b.getHolding().setIssn(line);
                break;
            case 11: // ZDB-ID
                if ("".equals(line)) {
                    b.getHolding().setZdbid(null);
                } else {
                    b.getHolding().setZdbid(line);
                }
                break;
            case 12: // Startyear
                b.setStartyear(line);
                break;
            case 13: // Startvolume
                b.setStartvolume(line);
                break;
            case 14: // Startissue
                b.setStartissue(line);
                break;
            case 15: // Endyear
                b.setEndyear(line);
                break;
            case 16: // Endvolume
                b.setEndvolume(line);
                break;
            case 17: // Endissue
                b.setEndissue(line);
                break;
            case 18: // Supplement
                if ("".equals(line)) {
                    b.setSuppl(1); // Defaultvalue
                } else {
                    b.setSuppl(Integer.valueOf(line));
                }
                break;
            case 19: // remarks
                b.setBemerkungen(line);
                break;
            case 20: // eissue
                if (!"".equals(line)) { // Defaultvalue remains false
                    b.setEissue(Boolean.valueOf(line));
                }
                break;
            case 21: // internal
                if (!"".equals(line)) { // Defaultvalue remains false
                    b.setInternal(Boolean.valueOf(line));
                }
                break;
            default:
                LOG.error("Stock getBestand - Unpredicted switch case in default: " + column);
            }
        }

        return b;
    }

    /**
     * Gets from an ISSN a List<String> of all 'related' ISSNs to map them.
     * 
     * @param String
     * @param Connection
     * @return List<String>
     */
    private List<String> getRelatedIssn(final String issn, final Connection cn) {

        final Issn is = new Issn();

        final List<String> issns = is.getAllIssnsFromOneIssn(issn, cn);

        if (issns.isEmpty()) {
            issns.add(issn);
        } // if there has been no hit, return the ISSN from the input

        return issns;
    }

    /**
     * Checks if the string is a parsable Stock-ID.
     * 
     * @param int
     * @param String
     * @return Message
     */
    private Message checkStockID(final int lineCount, final String content) {

        final Message msg = new Message();

        // the ID may be empty && if not empty it must be a valid number
        if (!"".equals(content) && !org.apache.commons.lang.StringUtils.isNumeric(content)) {
            msg.setMessage("error.import.stid");
            msg.setSystemMessage(composeSystemMessage(lineCount, content));
        }

        return msg;
    }

    /**
     * Checks if the string is a parsable Holding-ID.
     * 
     * @param int
     * @param String
     * @return Message
     */
    private Message checkHoldingID(final int lineCount, final String content) {

        final Message msg = new Message();

        // the ID may be empty && if not empty it must be a valid number
        if (!"".equals(content) && !org.apache.commons.lang.StringUtils.isNumeric(content)) {
            msg.setMessage("error.import.hoid");
            msg.setSystemMessage(composeSystemMessage(lineCount, content));
        }

        return msg;
    }

    /**
     * Checks if the string is a parsable Location-ID.
     * 
     * @param int
     * @param String
     * @return Message
     */
    private Message checkLocationID(final int lineCount, final String content) {

        final Message msg = new Message();

        // the ID may be empty && if not empty it must be a valid number
        if (!"".equals(content) && !org.apache.commons.lang.StringUtils.isNumeric(content)) {
            msg.setMessage("error.import.locationid");
            msg.setSystemMessage(composeSystemMessage(lineCount, content));
        }

        return msg;
    }

    /**
     * Checks if there has been specified a title.
     * 
     * @param int
     * @param String
     * @return Message
     */
    private Message checkTitle(final int lineCount, final String content) {

        final Message msg = new Message();
        final Check ck = new Check();

        // if not empty it must be a valid number
        if (!ck.isMinLength(content, 1)) {
            msg.setMessage("error.import.title");
            msg.setSystemMessage(composeSystemMessage(lineCount, content));
        }

        return msg;
    }

    /**
     * If an ISSN has been specified, checks that the string specified is a
     * valid ISSN.
     * 
     * @param int
     * @param String
     * @return Message
     */
    private Message checkISSN(final int lineCount, final String content) {

        final Message msg = new Message();
        final Check ck = new Check();

        if (!"".equals(content)) { // the ISSN may be empty
            // if not empty it must be a valid ISSN
            if (!ck.isExactLength(content, 9)) { // length must be 9 characters
                msg.setMessage("error.import.issn.length");
                msg.setSystemMessage(composeSystemMessage(lineCount, content));
            } else if (!ck.isValidIssn(content)) { // check for typos...
                msg.setMessage("error.import.issn.notvalid");
                msg.setSystemMessage(composeSystemMessage(lineCount, content));
            }
        }

        return msg;
    }

    /**
     * Checks if there has been specified a startyear and the string is a valid
     * year.
     * 
     * @param int
     * @param String
     * @return Message
     */
    private Message checkStartyear(final int lineCount, final String content) {

        final Message msg = new Message();
        final Check ck = new Check();

        if (!ck.isYear(content)) {
            msg.setMessage("error.import.startyear");
            msg.setSystemMessage(composeSystemMessage(lineCount, content));
        }

        return msg;
    }

    /**
     * Checks if the string is a valid (end)year.
     * 
     * @param int
     * @param String
     * @return Message
     */
    private Message checkEndyear(final int lineCount, final String content) {

        final Message msg = new Message();
        final Check ck = new Check();

        // the endyear may be empty && if not empty it must be a valid year
        if (!"".equals(content) && !ck.isYear(content)) {
            msg.setMessage("error.import.endyear");
            msg.setSystemMessage(composeSystemMessage(lineCount, content));
        }

        return msg;
    }

    /**
     * Checks if the string is a valid Supplement (0 / 1 / 2).
     * 
     * @param int
     * @param String
     * @return Message
     */
    private Message checkSuppl(final int lineCount, final String content) {

        final Message msg = new Message();

        if (!"".equals(content) // may be empty => we will use default value
                && !"0".equals(content) && !"1".equals(content) && !"2".equals(content)) {
            msg.setMessage("error.import.suppl");
            msg.setSystemMessage(composeSystemMessage(lineCount, content));
        }

        return msg;
    }

    /**
     * Checks if the string is a valid boolean value.
     * 
     * @param int
     * @param String
     * @return Message
     */
    private Message checkBoolean(final int lineCount, final String content) {

        final Message msg = new Message();

        if (!"".equals(content) // may be empty => we will use default value
                && !"true".equals(content) && !"false".equals(content)) {
            msg.setMessage("error.import.boolean");
            msg.setSystemMessage(composeSystemMessage(lineCount, content));
        }

        return msg;
    }

    /**
     * Uses a StringBuffer to compose a String in the form: Row x: text...
     * 
     * @param int
     * @param Long
     * @return String
     */
    private String composeSystemMessage(final int lineCount, final Long element) {

        final StringBuffer bf = new StringBuffer();

        bf.append("Row ");
        bf.append(lineCount);
        bf.append(": ");
        bf.append(element);

        return bf.toString();
    }

    /**
     * Uses a StringBuffer to compose a String in the form: Row x: text...
     * 
     * @param int
     * @param String
     * @return String
     */
    private String composeSystemMessage(final int lineCount, final String element) {

        final StringBuffer bf = new StringBuffer();

        bf.append("Row ");
        bf.append(lineCount);
        bf.append(": ");
        bf.append(element);

        return bf.toString();
    }

    /**
     * Handles the update, save and delete process for the import file into the
     * DB. It relies on the assumption, that all integrity checks for formatting
     * etc. have been run before!
     * 
     * @param List<Bestand>
     * @param UserInfo
     * @param Connection
     * @return String
     */
    private String update(final List<Bestand> bestandList, final UserInfo ui, final Connection cn) {

        final StringBuffer bf = new StringBuffer(32);
        int countUpdate = 0;
        int countInsert = 0;

        final Holding hold = new Holding();
        final Bestand best = new Bestand();

        // delete all existing stock entries for this account
        best.deleteAllKontoBestand(ui.getKonto(), cn);

        for (final Bestand b : bestandList) {

            // try to get an existing Holding for this account with
            // the values specified in the Bestand, but without an ev. ID
            final Holding h = new Holding(b.getHolding(), cn);

            // here we replace the holding from Bestand with the holding found in the DB
            // this will deduplicate and centralize the holdings for each account
            if (h.getId() != null) {
                b.setHolding(h);
            } else { // the holdings needs to be updated! Make sure it will...
                b.getHolding().setId(null);
            }

            // create a location if we do not have a location ID
            if (b.getStandort().getId() == null) {
                // try to get an existing entry for this account
                Text loc = new Text(cn, TextType.LOCATION, ui.getKonto().getId(), b.getStandort().getInhalt());
                if (loc.getId() == null) { // save a new location for this account
                    b.getStandort().setKonto(ui.getKonto());
                    b.getStandort().setTexttype(TextType.LOCATION);
                    loc.saveNewText(cn, b.getStandort());
                    // get back the saved location
                    loc = new Text(cn, TextType.LOCATION, ui.getKonto().getId(), b.getStandort().getInhalt());
                }
                // set the location with ID
                b.setStandort(loc);
            }

            if (b.getId() == null) { // save as new Bestand
                b.saveWithoutID(b, cn);
                countInsert++;
            } else { // update Bestand
                b.saveWithID(b, cn);
                countUpdate++;
            }
        }

        // delete all not used Holdings for this account
        hold.purgeNotUsedKontoHoldings(ui.getKonto(), cn);

        bf.append("Updated: ");
        bf.append(countUpdate);
        bf.append("\nInserted: ");
        bf.append(countInsert);

        return bf.toString();
    }

    /**
     * Gets the delimiter csv.
     * 
     * @return char
     */
    public static char getDelimiterCsv() {
        return DELIMITER_CSV;
    }

    /**
     * Gets the delimiter txt.
     * 
     * @return char
     */
    public static char getDelimiterTxt() {
        return DELIMITER_TXT;
    }

}