001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Florent Guillaume
011 */
012
013package org.nuxeo.ecm.core.storage.sql.db;
014
015import java.io.Serializable;
016import java.sql.Connection;
017import java.sql.DatabaseMetaData;
018import java.sql.PreparedStatement;
019import java.sql.ResultSet;
020import java.sql.SQLException;
021import java.sql.Statement;
022import java.sql.Types;
023import java.util.HashSet;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Set;
027
028import org.h2.tools.SimpleResultSet;
029
030/**
031 * Functions used as stored procedures for H2.
032 *
033 * @author Florent Guillaume
034 */
035public class H2Functions extends EmbeddedFunctions {
036
037    // for debug
038    private static boolean isLogEnabled() {
039        return false;
040        // return log.isTraceEnabled();
041    }
042
043    // for debug
044    private static void logDebug(String message, Object... args) {
045        // log.trace("SQL: " + String.format(message.replace("?", "%s"), args));
046    }
047
048    public static boolean isInTreeString(Connection conn, String id, String baseId) throws SQLException {
049        return isInTree(conn, id, baseId);
050    }
051
052    public static boolean isInTreeLong(Connection conn, Long id, Long baseId) throws SQLException {
053        return isInTree(conn, id, baseId);
054    }
055
056    public static boolean isAccessAllowedString(Connection conn, String id, String principals, String permissions)
057            throws SQLException {
058        return isAccessAllowed(conn, id, split(principals), split(permissions));
059    }
060
061    public static boolean isAccessAllowedLong(Connection conn, Long id, String principals, String permissions)
062            throws SQLException {
063        return isAccessAllowed(conn, id, split(principals), split(permissions));
064    }
065
066    /**
067     * Adds an invalidation from this cluster node to the invalidations list.
068     */
069    @SuppressWarnings("boxing")
070    public static void clusterInvalidateString(Connection conn, long nodeId, String id, String fragments, int kind)
071            throws SQLException {
072        PreparedStatement ps = null;
073        try {
074            // find other node ids
075            String sql = "SELECT \"NODEID\" FROM \"CLUSTER_NODES\" WHERE \"NODEID\" <> ?";
076            if (isLogEnabled()) {
077                logDebug(sql, nodeId);
078            }
079            ps = conn.prepareStatement(sql);
080            ps.setLong(1, nodeId);
081            List<Long> nodeIds = new LinkedList<Long>();
082            ResultSet rs = ps.executeQuery();
083            while (rs.next()) {
084                nodeIds.add(rs.getLong(1));
085            }
086            if (isLogEnabled()) {
087                logDebug("  -> " + nodeIds);
088            }
089            // invalidate
090            sql = "INSERT INTO \"CLUSTER_INVALS\" " + "(\"NODEID\", \"ID\", \"FRAGMENTS\", \"KIND\") "
091                    + "VALUES (?, ?, ?, ?)";
092            for (Long nid : nodeIds) {
093                if (isLogEnabled()) {
094                    logDebug(sql, nid, id, fragments, kind);
095                }
096                ps = conn.prepareStatement(sql);
097                ps.setLong(1, nid);
098                ps.setObject(2, id);
099                ps.setString(3, fragments);
100                ps.setInt(4, kind);
101                ps.execute();
102            }
103        } finally {
104            if (ps != null) {
105                ps.close();
106            }
107        }
108    }
109
110    /**
111     * Gets the invalidations for this cluster node.
112     *
113     * @return a result set with columns id, fragments, kind
114     */
115    public static ResultSet getClusterInvalidationsString(Connection conn, long nodeId) throws SQLException {
116        DatabaseMetaData meta = conn.getMetaData();
117        SimpleResultSet result = new SimpleResultSet();
118        result.addColumn("ID", Types.VARCHAR, 0, 0); // String id
119        result.addColumn("FRAGMENTS", Types.VARCHAR, 0, 0);
120        result.addColumn("KIND", Types.INTEGER, 0, 0);
121        if (meta.getURL().startsWith("jdbc:columnlist:")) {
122            // this is just to query the result set columns
123            return result;
124        }
125
126        PreparedStatement ps = null;
127        try {
128            String sql = "SELECT \"ID\", \"FRAGMENTS\", \"KIND\" FROM \"CLUSTER_INVALS\" "
129                    + "WHERE \"NODEID\" = ?";
130            if (isLogEnabled()) {
131                logDebug(sql, nodeId);
132            }
133            ps = conn.prepareStatement(sql);
134            ps.setLong(1, nodeId);
135            ResultSet rs = ps.executeQuery();
136            List<Serializable> debugValues = null;
137            if (isLogEnabled()) {
138                debugValues = new LinkedList<Serializable>();
139            }
140            while (rs.next()) {
141                String id = rs.getString(1);
142                String fragments = rs.getString(2);
143                long kind = rs.getLong(3);
144                result.addRow(new Object[] { id, fragments, Long.valueOf(kind) });
145                if (debugValues != null) {
146                    debugValues.add(id + ',' + fragments + ',' + kind);
147                }
148            }
149            if (debugValues != null) {
150                logDebug("  -> " + debugValues);
151            }
152
153            // remove processed invalidations
154            sql = "DELETE FROM \"CLUSTER_INVALS\" WHERE \"NODEID\" = ?";
155            if (isLogEnabled()) {
156                logDebug(sql);
157            }
158            ps.close();
159            ps = conn.prepareStatement(sql);
160            ps.setLong(1, nodeId);
161            ps.execute();
162
163            // return invalidations
164            return result;
165        } finally {
166            if (ps != null) {
167                ps.close();
168            }
169        }
170    }
171
172    public static ResultSet upgradeVersions(Connection conn) throws SQLException {
173        PreparedStatement ps1 = null;
174        PreparedStatement ps2 = null;
175        try {
176            String sql = "SELECT v.id, v.versionableid, h.majorversion, h.minorversion"
177                    + "  FROM versions v JOIN hierarchy h ON v.id = h.id"
178                    + "  ORDER BY v.versionableid, v.created DESC";
179            ps1 = conn.prepareStatement(sql);
180            ResultSet rs = ps1.executeQuery();
181            String series = null;
182            boolean isLatest = false;
183            boolean isLatestMajor = false;
184            while (rs.next()) {
185                String id = rs.getString("id");
186                String vid = rs.getString("versionableid");
187                long maj = rs.getLong("majorversion");
188                long min = rs.getLong("minorversion");
189                if (vid == null || !vid.equals(series)) {
190                    // restart
191                    isLatest = true;
192                    isLatestMajor = true;
193                    series = vid;
194                }
195                boolean isMajor = min == 0;
196                ps2 = conn.prepareStatement("UPDATE versions SET label = ?, islatest = ?, islatestmajor = ?"
197                        + " WHERE id = ?");
198                ps2.setString(1, maj + "." + min);
199                ps2.setBoolean(2, isLatest);
200                ps2.setBoolean(3, isMajor && isLatestMajor);
201                ps2.setString(4, id);
202                ps2.executeUpdate();
203                // next
204                isLatest = false;
205                if (isMajor) {
206                    isLatestMajor = false;
207                }
208            }
209        } finally {
210            if (ps1 != null) {
211                ps1.close();
212            }
213            if (ps2 != null) {
214                ps2.close();
215            }
216        }
217
218        return new SimpleResultSet();
219    }
220
221    public static ResultSet upgradeLastContributor(Connection conn) throws SQLException {
222        PreparedStatement ps1 = null;
223        PreparedStatement ps2 = null;
224        try {
225            String sql = "SELECT dc_c.id, dc_c.item"
226                    + "  FROM dublincore dc"
227                    + "    JOIN (SELECT id, max(pos) AS pos FROM dc_contributors GROUP BY id) AS tmp ON (dc.id = tmp.id)"
228                    + "    JOIN dc_contributors dc_c ON (tmp.id = dc_c.id AND tmp.pos = dc_c.pos)"
229                    + "  WHERE dc.lastContributor IS NULL;";
230            ps1 = conn.prepareStatement(sql);
231            ResultSet rs = ps1.executeQuery();
232            String series = null;
233            while (rs.next()) {
234                String id = rs.getString("id");
235                String lastContributor = rs.getString("item");
236
237                ps2 = conn.prepareStatement("UPDATE dublincore SET lastContributor = ? WHERE id = ?");
238                ps2.setString(1, lastContributor);
239                ps2.setString(2, id);
240
241                ps2.executeUpdate();
242            }
243        } finally {
244            if (ps1 != null) {
245                ps1.close();
246            }
247            if (ps2 != null) {
248                ps2.close();
249            }
250        }
251
252        return new SimpleResultSet();
253    }
254
255    public static ResultSet getAncestorsIds(Connection conn, String idsString) throws SQLException {
256        Set<String> ids = split(idsString);
257        DatabaseMetaData meta = conn.getMetaData();
258        SimpleResultSet result = new SimpleResultSet();
259        result.addColumn("ID", Types.VARCHAR, 0, 0); // String id
260        if (meta.getURL().startsWith("jdbc:columnlist:")) {
261            // this is just to query the result set columns
262            return result;
263        }
264
265        PreparedStatement ps = null;
266        try {
267            LinkedList<String> todo = new LinkedList<String>(ids);
268            Set<String> done = new HashSet<String>();
269            Set<String> res = new HashSet<String>();
270            while (!todo.isEmpty()) {
271                done.addAll(todo);
272                String sql = getSelectParentIdsByIdsSql(todo.size());
273                if (isLogEnabled()) {
274                    logDebug(sql, todo);
275                }
276                ps = conn.prepareStatement(sql);
277                int i = 1;
278                for (String id : todo) {
279                    ps.setString(i++, id);
280                }
281                todo = new LinkedList<String>();
282                List<String> debugIds = null;
283                if (isLogEnabled()) {
284                    debugIds = new LinkedList<String>();
285                }
286                ResultSet rs = ps.executeQuery();
287                while (rs.next()) {
288                    String id = rs.getString(1);
289                    if (id != null) {
290                        if (!res.contains(id)) {
291                            res.add(id);
292                            result.addRow(new Object[] { id });
293                        }
294                        if (!done.contains(id)) {
295                            todo.add(id);
296                        }
297                        if (isLogEnabled()) {
298                            debugIds.add(id);
299                        }
300                    }
301                }
302                if (isLogEnabled()) {
303                    logDebug("  -> " + debugIds);
304                }
305                ps.close();
306                ps = null;
307            }
308            return result;
309        } finally {
310            if (ps != null) {
311                ps.close();
312            }
313        }
314    }
315
316    protected static String getSelectParentIdsByIdsSql(int size) {
317        StringBuilder buf = new StringBuilder("SELECT DISTINCT \"PARENTID\" FROM \"HIERARCHY\" WHERE \"ID\" IN (");
318        for (int i = 0; i < size; i++) {
319            if (i != 0) {
320                buf.append(", ");
321            }
322            buf.append('?');
323        }
324        buf.append(')');
325        return buf.toString();
326    }
327
328}