/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Florent Guillaume */ package org.eclipse.ecr.core.storage.sql.extensions; import java.io.Serializable; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Functions used as stored procedures for Derby and H2. * * @author Florent Guillaume */ public class EmbeddedFunctions { private static final Log log = LogFactory.getLog(EmbeddedFunctions.class); private static void logDebug(String message) { // log.debug(message); } /** * Checks if an id is a (strict) descendant of a given base id. * * @param id the id to check for * @param baseId the base id */ public static boolean isInTree(Serializable id, Serializable baseId) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection"); try { return isInTree(conn, id, baseId); } finally { conn.close(); } } /** * Checks if an id is a (strict) descendant of a given base id. * * @param conn the connection to the database * @param id the id to check for * @param baseId the base id */ public static boolean isInTree(Connection conn, Serializable id, Serializable baseId) throws SQLException { if (baseId == null || id == null || baseId.equals(id)) { // containment check is strict return false; } PreparedStatement ps = null; try { ps = conn.prepareStatement("SELECT PARENTID FROM HIERARCHY WHERE ID = ?"); do { ps.setObject(1, id); ResultSet rs = ps.executeQuery(); if (!rs.next()) { // no such id return false; } if (id instanceof String) { id = rs.getString(1); } else { id = Long.valueOf(rs.getLong(1)); } if (rs.wasNull()) { id = null; } rs.close(); if (baseId.equals(id)) { // found a match return true; } } while (id != null); // got to the root return false; } finally { if (ps != null) { ps.close(); } } } /** * Checks if access to a document is allowed. * <p> * This implements in SQL the ACL-based security policy logic. * * @param id the id of the document * @param principals the allowed identities * @param permissions the allowed permissions */ public static boolean isAccessAllowed(Serializable id, Set<String> principals, Set<String> permissions) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection"); try { return isAccessAllowed(conn, id, principals, permissions); } finally { conn.close(); } } /** * Checks if access to a document is allowed. * <p> * This implements in SQL the ACL-based security policy logic. * * @param conn the database connection * @param id the id of the document * @param principals the allowed identities * @param permissions the allowed permissions */ public static boolean isAccessAllowed(Connection conn, Serializable id, Set<String> principals, Set<String> permissions) throws SQLException { if (log.isDebugEnabled()) { logDebug("isAccessAllowed " + id + " " + principals + " " + permissions); } PreparedStatement ps1 = null; PreparedStatement ps2 = null; PreparedStatement ps3 = null; try { ps1 = conn.prepareStatement( // "SELECT \"GRANT\", \"PERMISSION\", \"USER\" FROM \"ACLS\" " + "WHERE ID = ? ORDER BY POS"); ps2 = conn.prepareStatement("SELECT PARENTID FROM HIERARCHY WHERE ID = ?"); boolean first = true; do { /* * Check permissions at this level. */ ps1.setObject(1, id); ResultSet rs = ps1.executeQuery(); while (rs.next()) { boolean grant = rs.getShort(1) != 0; String permission = rs.getString(2); String user = rs.getString(3); if (log.isDebugEnabled()) { logDebug(" -> " + user + " " + permission + " " + grant); } if (principals.contains(user) && permissions.contains(permission)) { if (log.isDebugEnabled()) { logDebug(" => " + grant); } return grant; } } /* * Nothing conclusive found, repeat on the parent. */ ps2.setObject(1, id); rs = ps2.executeQuery(); Serializable newId; if (rs.next()) { newId = (Serializable) rs.getObject(1); if (rs.wasNull()) { newId = null; } } else { // no such id newId = null; } if (first && newId == null) { // there is no parent for the first level // we may have a version on our hands, find the live doc ps3 = conn.prepareStatement("SELECT VERSIONABLEID FROM VERSIONS WHERE ID = ?"); ps3.setObject(1, id); rs = ps3.executeQuery(); if (rs.next()) { newId = (Serializable) rs.getObject(1); if (rs.wasNull()) { newId = null; } } else { // no such id newId = null; } } first = false; id = newId; } while (id != null); /* * We reached the root, deny access. */ if (log.isDebugEnabled()) { logDebug(" => false (root)"); } return false; } finally { if (ps1 != null) { ps1.close(); } if (ps2 != null) { ps2.close(); } if (ps3 != null) { ps3.close(); } } } /** * Extracts the words from a string for simple fulltext indexing. * * @param string1 the first string * @param string2 the second string * @return a string with extracted words */ public static String parseFullText(String string1, String string2) { Set<String> set = new HashSet<String>(); set.addAll(parseFullText(string1)); set.addAll(parseFullText(string2)); List<String> words = new ArrayList<String>(set); Collections.sort(words); return join(words, ' '); } protected static Set<String> parseFullText(String string) { if (string == null) { return Collections.emptySet(); } Set<String> set = new HashSet<String>(); for (String word : wordPattern.split(string)) { String w = parseWord(word); if (w != null) { set.add(w); } } return set; } /** * Checks if the passed query expression matches the fulltext. * * @param fulltext the fulltext, space-separated words * @param query a list of space-separated words * @return {@code true} if all the words are in the fulltext */ protected static boolean matchesFullText(String fulltext, String query) { if (fulltext == null || query == null) { return false; } Set<String> words = split(query, ' '); if (words.isEmpty()) { return false; } Set<String> fulltextWords = split(fulltext, ' '); for (String word : words) { if (!fulltextWords.contains(word)) { return false; } } return true; } // ----- simple parsing, don't try to be exhaustive ----- private static final Pattern wordPattern = Pattern.compile("[\\s\\p{Punct}]+"); private static final String UNACCENTED = "aaaaaaaceeeeiiii\u00f0nooooo\u00f7ouuuuy\u00fey"; private static final String STOPWORDS = "a an are and as at be by for from how " + "i in is it of on or that the this to was what when where who will with " + "car donc est il ils je la le les mais ni nous or ou pour tu un une vous " + "www com net org"; private static final Set<String> stopWords = new HashSet<String>(split( STOPWORDS, ' ')); public static final String parseWord(String string) { int len = string.length(); if (len < 3) { return null; } StringBuilder buf = new StringBuilder(len); for (int i = 0; i < len; i++) { char c = Character.toLowerCase(string.charAt(i)); if (c == '\u00e6') { buf.append("ae"); } else if (c >= '\u00e0' && c <= '\u00ff') { buf.append(UNACCENTED.charAt((c) - 0xe0)); } else if (c == '\u0153') { buf.append("oe"); } else { buf.append(c); } } // simple heuristic to remove plurals int l = buf.length(); if (l > 3 && buf.charAt(l - 1) == 's') { buf.setLength(l - 1); } String word = buf.toString(); if (stopWords.contains(word)) { return null; } return word; } // ----- utility functions ----- public static Set<String> split(String string) { return split(string, '|'); } public static Set<String> split(String string, char sep) { int len = string.length(); if (len == 0) { return Collections.emptySet(); } int end = string.indexOf(sep); if (end == -1) { return Collections.singleton(string); } Set<String> set = new HashSet<String>(); int start = 0; do { String segment = string.substring(start, end); set.add(segment); start = end + 1; end = string.indexOf(sep, start); } while (end != -1); if (start < len) { set.add(string.substring(start)); } else { set.add(""); } return set; } private static final String join(Collection<String> strings, char sep) { if (strings == null || strings.isEmpty()) { return ""; } int size = 0; for (String word : strings) { size += word.length() + 1; } StringBuilder buf = new StringBuilder(size); for (String word : strings) { buf.append(word); buf.append(sep); } buf.setLength(size - 1); return buf.toString(); } }