001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.db.generic;
029
030import org.opencms.db.CmsDbContext;
031import org.opencms.db.CmsDbSqlException;
032import org.opencms.db.storage.I_CmsDbStorage;
033import org.opencms.file.I_CmsFileContentStreamHandler;
034import org.opencms.util.CmsFileUtil;
035
036import java.io.ByteArrayInputStream;
037import java.io.InputStream;
038import java.io.OutputStream;
039import java.sql.Connection;
040import java.sql.PreparedStatement;
041import java.sql.ResultSet;
042import java.sql.SQLException;
043
044/**
045 * Database backend for the OpenCms deduplicating media storage engine (DMSE).<p>
046 *
047 * This implementation stores binary content in a specialized table (CMS_STORAGE),
048 * using a unique hash for deduplication. It ensures that identical files are only
049 * stored once physically.<p>
050 *
051 * @see org.opencms.db.storage.CmsStorageManager
052 */
053public class CmsDbStorage implements I_CmsDbStorage {
054
055    /** The SQL manager to execute database queries. */
056    protected final CmsSqlManager m_sqlManager;
057
058    /**
059     * Public constructor.<p>
060     *
061     * @param sqlManager the SQL manager to use
062     */
063    public CmsDbStorage(CmsSqlManager sqlManager) {
064
065        m_sqlManager = sqlManager;
066    }
067
068    /**
069     * @see org.opencms.db.storage.I_CmsStorage#deleteContent(org.opencms.db.CmsDbContext, java.lang.String)
070     */
071    @Override
072    public void deleteContent(CmsDbContext dbc, String hash) throws Exception {
073
074        if (hash == null) {
075            return;
076        }
077        Connection conn = null;
078        PreparedStatement stmt = null;
079        try {
080            conn = m_sqlManager.getConnection(dbc);
081            stmt = m_sqlManager.getPreparedStatement(conn, "C_STORAGE_DELETE");
082            stmt.setString(1, hash);
083            stmt.executeUpdate();
084        } catch (SQLException e) {
085            throw new CmsDbSqlException(
086                Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)),
087                e);
088        } finally {
089            m_sqlManager.closeAll(dbc, conn, stmt, null);
090        }
091    }
092
093    /**
094     * @see org.opencms.db.storage.I_CmsStorage#getStorageIdentifier()
095     */
096    @Override
097    public String getStorageIdentifier() {
098
099        return STORAGE_TYPE;
100    }
101
102    /**
103     * @see org.opencms.db.storage.I_CmsStorage#loadContent(org.opencms.db.CmsDbContext, java.lang.String)
104     */
105    @Override
106    public byte[] loadContent(CmsDbContext dbc, String hash) throws Exception {
107
108        if (hash == null) {
109            return null;
110        }
111        Connection conn = null;
112        PreparedStatement stmt = null;
113        ResultSet res = null;
114        byte[] content = null;
115        try {
116            conn = m_sqlManager.getConnection(dbc);
117            stmt = m_sqlManager.getPreparedStatement(conn, "C_STORAGE_FILE_CONTENT");
118            stmt.setString(1, hash);
119            res = stmt.executeQuery();
120            if (res.next()) {
121                content = m_sqlManager.getBytes(res, "FILE_CONTENT");
122            }
123        } catch (SQLException e) {
124            throw new CmsDbSqlException(
125                Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)),
126                e);
127        } finally {
128            m_sqlManager.closeAll(dbc, conn, stmt, res);
129        }
130        return content;
131    }
132
133    /**
134     * @see org.opencms.db.storage.I_CmsStorage#loadContentFrom(CmsDbContext, String, I_CmsFileContentStreamHandler)
135     */
136    @Override
137    public void loadContentFrom(CmsDbContext dbc, String hash, I_CmsFileContentStreamHandler handler) throws Exception {
138
139        if (hash == null) {
140            return;
141        }
142        Connection conn = null;
143        PreparedStatement stmt = null;
144        ResultSet res = null;
145        try {
146            conn = m_sqlManager.getConnection(dbc);
147            stmt = m_sqlManager.getPreparedStatement(conn, "C_STORAGE_FILE_CONTENT");
148            stmt.setString(1, hash);
149            res = stmt.executeQuery();
150            if (res.next()) {
151                try (InputStream in = res.getBinaryStream("FILE_CONTENT")) {
152                    if (in != null) {
153                        handler.read(in);
154                    }
155                }
156            }
157        } catch (SQLException e) {
158            throw new CmsDbSqlException(
159                Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)),
160                e);
161        } finally {
162            m_sqlManager.closeAll(dbc, conn, stmt, res);
163        }
164    }
165
166    /**
167     * @see org.opencms.db.storage.I_CmsStorage#loadContentTo(CmsDbContext, String, OutputStream)
168     */
169    @Override
170    public void loadContentTo(CmsDbContext dbc, String hash, OutputStream out) throws Exception {
171
172        if (hash == null) {
173            return;
174        }
175        Connection conn = null;
176        PreparedStatement stmt = null;
177        ResultSet res = null;
178        try {
179            conn = m_sqlManager.getConnection(dbc);
180            stmt = m_sqlManager.getPreparedStatement(conn, "C_STORAGE_FILE_CONTENT");
181            stmt.setString(1, hash);
182            res = stmt.executeQuery();
183            if (res.next()) {
184                try (InputStream in = res.getBinaryStream("FILE_CONTENT")) {
185                    if (in != null) {
186                        CmsFileUtil.copy(in, out);
187                    }
188                }
189            }
190        } catch (SQLException e) {
191            throw new CmsDbSqlException(
192                Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)),
193                e);
194        } finally {
195            m_sqlManager.closeAll(dbc, conn, stmt, res);
196        }
197    }
198
199    /**
200     * @see org.opencms.db.storage.I_CmsStorage#storeContent(org.opencms.db.CmsDbContext, java.lang.String, byte[])
201     */
202    @Override
203    public void storeContent(CmsDbContext dbc, String hash, byte[] content) throws Exception {
204
205        Connection conn = null;
206        PreparedStatement stmt = null;
207        ResultSet res = null;
208        try {
209            conn = m_sqlManager.getConnection(dbc);
210            // check for deduplication
211            stmt = m_sqlManager.getPreparedStatement(conn, "C_STORAGE_EXISTS");
212            stmt.setString(1, hash);
213            res = stmt.executeQuery();
214            if (res.next()) {
215                // content already in storage, skip insert
216                return;
217            }
218            m_sqlManager.closeAll(dbc, null, stmt, res);
219
220            // insert new storage entry
221            stmt = m_sqlManager.getPreparedStatement(conn, "C_STORAGE_WRITE");
222            stmt.setString(1, hash);
223            if (content.length < 2000) {
224                stmt.setBytes(2, content);
225            } else {
226                stmt.setBinaryStream(2, new ByteArrayInputStream(content), content.length);
227            }
228            stmt.executeUpdate();
229        } catch (SQLException e) {
230            throw new CmsDbSqlException(
231                Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)),
232                e);
233        } finally {
234            m_sqlManager.closeAll(dbc, conn, stmt, res);
235        }
236    }
237
238    /**
239     * @see org.opencms.db.storage.I_CmsStorage#validateAvailable(org.opencms.db.CmsDbContext)
240     */
241    @Override
242    public void validateAvailable(CmsDbContext dbc) throws Exception {
243
244        // The database connection is already validated during OpenCms startup.
245    }
246
247}