001/*
002 * #%L
003 * Netarchivesuite - harvester
004 * %%
005 * Copyright (C) 2005 - 2018 The Royal Danish Library, 
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.harvester.datamodel.extendedfield;
025
026import java.sql.Connection;
027import java.sql.PreparedStatement;
028import java.sql.ResultSet;
029import java.sql.SQLException;
030import java.util.LinkedList;
031import java.util.List;
032
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import dk.netarkivet.common.exceptions.ArgumentNotValid;
037import dk.netarkivet.common.exceptions.IOFailure;
038import dk.netarkivet.common.exceptions.UnknownID;
039import dk.netarkivet.common.utils.DBUtils;
040import dk.netarkivet.harvester.datamodel.HarvestDBConnection;
041import dk.netarkivet.harvester.datamodel.HarvesterDatabaseTables;
042
043/**
044 * A database-based implementation of the ExtendedFieldDBDAO class.
045 */
046public class ExtendedFieldDBDAO extends ExtendedFieldDAO {
047
048    /** The logger for this class. */
049    private static final Logger log = LoggerFactory.getLogger(ExtendedFieldDBDAO.class);
050
051    /**
052     * Constructor for the ExtendedFieldDBDAO object.
053     */
054    public ExtendedFieldDBDAO() {
055        Connection connection = HarvestDBConnection.get();
056        try {
057            HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.EXTENDEDFIELD);
058            HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.EXTENDEDFIELDVALUE);
059        } finally {
060            HarvestDBConnection.release(connection);
061        }
062    }
063
064    @Override
065    public synchronized void create(ExtendedField aExtendedField) {
066        ArgumentNotValid.checkNotNull(aExtendedField, "aExtendedField");
067
068        Connection connection = HarvestDBConnection.get();
069        if (aExtendedField.getExtendedFieldID() != null) {
070            log.warn("The extendedFieldID for this extended Field is already set. This should probably never happen.");
071        } else {
072            aExtendedField.setExtendedFieldID(generateNextID(connection));
073        }
074
075        log.debug("Creating {}", aExtendedField.toString());
076
077        PreparedStatement statement = null;
078        try {
079            connection.setAutoCommit(false);
080            statement = connection.prepareStatement("" + "INSERT INTO extendedfield "
081                    + "            (extendedfield_id, " + "             extendedfieldtype_id, " + "             name, "
082                    + "             format, " + "             defaultvalue, " + "             options, "
083                    + "             datatype, " + "             mandatory, " + "             sequencenr, "
084                    + "             maxlen) " + "VALUES      (?, " + "             ?, " + "             ?, "
085                    + "             ?, " + "             ?, " + "             ?, " + "             ?, "
086                    + "             ?, " + "             ?, " + "             ?) ");
087
088            statement.setLong(1, aExtendedField.getExtendedFieldID());
089            statement.setLong(2, aExtendedField.getExtendedFieldTypeID());
090            statement.setString(3, aExtendedField.getName());
091            statement.setString(4, aExtendedField.getFormattingPattern());
092            statement.setString(5, aExtendedField.getDefaultValue());
093            statement.setString(6, aExtendedField.getOptions());
094            statement.setInt(7, aExtendedField.getDatatype());
095            // the following conversion from boolean to int is necessary,
096            // because the database column 'mandatory' is a integer field
097            // and not a boolean (NAS-2127)
098            statement.setInt(8, aExtendedField.isMandatory() ? 1 : 0);
099            statement.setInt(9, aExtendedField.getSequencenr());
100            statement.setInt(10, aExtendedField.getMaxlen());
101
102            // TODO replace this toString by something better
103            log.debug(statement.toString());
104
105            statement.executeUpdate();
106            connection.commit();
107        } catch (SQLException e) {
108            String message = "SQL error creating extended field " + aExtendedField + " in database" + "\n";
109            log.warn(message, e);
110            throw new IOFailure(message, e);
111        } finally {
112            DBUtils.closeStatementIfOpen(statement);
113            DBUtils.rollbackIfNeeded(connection, "create extended field", aExtendedField);
114            HarvestDBConnection.release(connection);
115        }
116    }
117
118    /**
119     * Generates the next id of a extended field. this implementation retrieves the maximum value of extendedfield_id in
120     * the DB, and returns this value + 1.
121     *
122     * @param c an open connection to the HarvestDatabase
123     * @return The next available ID
124     */
125    private Long generateNextID(Connection c) {
126        // FIXME Synchronization problem, this is why one should always use identity rows or generators.
127        Long maxVal = DBUtils.selectLongValue(c, "SELECT max(extendedfield_id) FROM extendedfield");
128        if (maxVal == null) {
129            maxVal = 0L;
130        }
131        return maxVal + 1L;
132    }
133
134    /**
135     * Check whether a particular extended Field exists.
136     *
137     * @param aExtendedfieldId Id of the extended field.
138     * @return true if the extended field exists.
139     */
140    public boolean exists(Long aExtendedfieldId) {
141        ArgumentNotValid.checkNotNull(aExtendedfieldId, "Long aExtendedfieldId");
142
143        Connection c = HarvestDBConnection.get();
144        try {
145            return exists(c, aExtendedfieldId);
146        } finally {
147            HarvestDBConnection.release(c);
148        }
149    }
150
151    /**
152     * Check, if there exists an ExtendedField with a given ID.
153     *
154     * @param c An open connection to the HarvestDatabase
155     * @param aExtendedfieldId An Id for a given Extended Field.
156     * @return true, if the extended field with the Id exists; otherwise false
157     */
158    private synchronized boolean exists(Connection c, Long aExtendedfieldId) {
159        return 1 == DBUtils.selectLongValue(c, "SELECT COUNT(*) FROM extendedfield WHERE extendedfield_id = ?",
160                aExtendedfieldId);
161    }
162
163    @Override
164    public synchronized void update(ExtendedField aExtendedField) {
165        ArgumentNotValid.checkNotNull(aExtendedField, "aExtendedField");
166
167        Connection connection = HarvestDBConnection.get();
168
169        PreparedStatement statement = null;
170        try {
171            final Long extendedfieldId = aExtendedField.getExtendedFieldID();
172            if (!exists(connection, extendedfieldId)) {
173                throw new UnknownID("Extended Field id " + extendedfieldId + " is not known in persistent storage");
174            }
175
176            connection.setAutoCommit(false);
177
178            statement = connection.prepareStatement("" + "UPDATE extendedfield " + "SET    extendedfield_id = ?, "
179                    + "       extendedfieldtype_id = ?, " + "       name = ?, " + "       format = ?, "
180                    + "       defaultvalue = ?, " + "       options = ?, " + "       datatype = ?, "
181                    + "       mandatory = ?, " + "       sequencenr = ?, " + "       maxlen = ? "
182                    + "WHERE  extendedfield_id = ? ");
183
184            statement.setLong(1, aExtendedField.getExtendedFieldID());
185            statement.setLong(2, aExtendedField.getExtendedFieldTypeID());
186            statement.setString(3, aExtendedField.getName());
187            statement.setString(4, aExtendedField.getFormattingPattern());
188            statement.setString(5, aExtendedField.getDefaultValue());
189            statement.setString(6, aExtendedField.getOptions());
190            statement.setInt(7, aExtendedField.getDatatype());
191            // the following conversion from boolean to int is necessary,
192            // because the database column 'mandatory' is a int field
193            // and not a boolean (NAS-2127)
194            statement.setInt(8, aExtendedField.isMandatory() ? 1 : 0);
195            statement.setInt(9, aExtendedField.getSequencenr());
196            statement.setInt(10, aExtendedField.getMaxlen());
197            statement.setLong(11, aExtendedField.getExtendedFieldID());
198
199            // TODO replace this toString by something better
200            log.debug(statement.toString());
201
202            statement.executeUpdate();
203
204            connection.commit();
205        } catch (SQLException e) {
206            String message = "SQL error updating extendedfield " + aExtendedField + " in database" + "\n";
207            log.warn(message, e);
208            throw new IOFailure(message, e);
209        } finally {
210            DBUtils.closeStatementIfOpen(statement);
211            DBUtils.rollbackIfNeeded(connection, "update extendedfield", aExtendedField);
212            HarvestDBConnection.release(connection);
213        }
214    }
215
216    @Override
217    public synchronized ExtendedField read(Long aExtendedfieldId) {
218        ArgumentNotValid.checkNotNull(aExtendedfieldId, "aExtendedfieldId");
219        Connection connection = HarvestDBConnection.get();
220        try {
221            return read(connection, aExtendedfieldId);
222        } finally {
223            HarvestDBConnection.release(connection);
224        }
225    }
226
227    /**
228     * Read an ExtendedField from database.
229     *
230     * @param connection A connection to the harvestDatabase
231     * @param aExtendedfieldId The ID for a given ExtendedField
232     * @return An ExtendedField object for the given ID.
233     */
234    private synchronized ExtendedField read(Connection connection, Long aExtendedfieldId) {
235        if (!exists(connection, aExtendedfieldId)) {
236            throw new UnknownID("Extended Field id " + aExtendedfieldId + " is not known in persistent storage");
237        }
238
239        ExtendedField extendedField = null;
240        PreparedStatement statement = null;
241        try {
242            statement = connection.prepareStatement("" + "SELECT extendedfieldtype_id, " + "       name, "
243                    + "       format, " + "       defaultvalue, " + "       options, " + "       datatype, "
244                    + "       mandatory, " + "       sequencenr, " + "       maxlen " + "FROM   extendedfield "
245                    + "WHERE  extendedfield_id = ? ");
246
247            statement.setLong(1, aExtendedfieldId);
248            ResultSet result = statement.executeQuery();
249            result.next();
250
251            long extendedfieldtypeId = result.getLong(1);
252            String name = result.getString(2);
253            String format = result.getString(3);
254            String defaultvalue = result.getString(4);
255            String options = result.getString(5);
256            int datatype = result.getInt(6);
257            boolean mandatory = result.getInt(7) != 0;
258            int sequencenr = result.getInt(8);
259            int maxlen = result.getInt(9);
260
261            extendedField = new ExtendedField(aExtendedfieldId, extendedfieldtypeId, name, format, datatype, mandatory,
262                    sequencenr, defaultvalue, options, maxlen);
263
264            return extendedField;
265        } catch (SQLException e) {
266            String message = "SQL error reading extended Field " + aExtendedfieldId + " in database" + "\n";
267            log.warn(message, e);
268            throw new IOFailure(message, e);
269        }
270    }
271
272    @Override
273    public List<ExtendedField> getAll(long aExtendedFieldTypeId) {
274        Connection c = HarvestDBConnection.get();
275        try {
276            List<Long> idList = DBUtils.selectLongList(c, "SELECT extendedfield_id FROM extendedfield "
277                    + "WHERE extendedfieldtype_id = ? ORDER BY sequencenr ASC", aExtendedFieldTypeId);
278            List<ExtendedField> extendedFields = new LinkedList<ExtendedField>();
279            for (Long extendedfieldId : idList) {
280                extendedFields.add(read(c, extendedfieldId));
281            }
282            return extendedFields;
283        } finally {
284            HarvestDBConnection.release(c);
285        }
286    }
287
288    @Override
289    public void delete(long aExtendedfieldId) throws IOFailure {
290        ArgumentNotValid.checkNotNull(aExtendedfieldId, "aExtendedfieldId");
291
292        Connection c = HarvestDBConnection.get();
293        PreparedStatement stm = null;
294        try {
295            c.setAutoCommit(false);
296
297            stm = c.prepareStatement("DELETE FROM extendedfieldvalue WHERE extendedfield_id = ?");
298            stm.setLong(1, aExtendedfieldId);
299            stm.executeUpdate();
300            stm.close();
301            stm = c.prepareStatement("DELETE FROM extendedfield WHERE extendedfield_id = ?");
302            stm.setLong(1, aExtendedfieldId);
303            stm.executeUpdate();
304            c.commit();
305        } catch (SQLException e) {
306            String message = "SQL error deleting extended fields for ID " + aExtendedfieldId + "\n";
307            log.warn(message, e);
308        } finally {
309            DBUtils.closeStatementIfOpen(stm);
310            DBUtils.rollbackIfNeeded(c, "delete extended field", aExtendedfieldId);
311            HarvestDBConnection.release(c);
312        }
313    }
314
315}