java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.java
author František Kučera <franta-hg@frantovo.cz>
Wed, 08 Feb 2012 13:15:23 +0100
changeset 78 3b4abb1ec5a3
parent 53 c06dfb1a2678
permissions -rw-r--r--
Limit počtu řádků (10000) a doby provádění SQL dotazu (3 vteřiny) v pískovišti,
nedokonalá ochrana proti DoS útoku (kartézský součin, náročný dotaz).
     1 package cz.frantovo.sql.vyuka.dao;
     2 
     3 import cz.frantovo.sql.vyuka.dao.VyukaSuperDAO.DATABAZE;
     4 import cz.frantovo.sql.vyuka.dto.Hlaska;
     5 import cz.frantovo.sql.vyuka.dto.Hlaska.Typ;
     6 import cz.frantovo.sql.vyuka.dto.Tabulka;
     7 import cz.frantovo.sql.vyuka.dto.Uzivatel;
     8 import cz.frantovo.sql.vyuka.dto.VysledekSQL;
     9 import java.sql.Connection;
    10 import java.sql.PreparedStatement;
    11 import java.sql.ResultSet;
    12 import java.sql.SQLException;
    13 import java.util.logging.Level;
    14 
    15 /**
    16  * Pro spouštění uživatelových příkazů.
    17  *
    18  * @author fiki
    19  */
    20 public class PiskovisteDAO extends VyukaSuperDAO {
    21 
    22 	/** maximální doba trvání SQL dotazu – vteřiny */
    23 	private static final int LIMIT_ČASU = 3;
    24 	/** maximální počet řádků */
    25 	private static final int LIMIT_POČTU = 10000;
    26 
    27 	private enum VLASTNOSTI {
    28 
    29 		VYCHOZI_CESTA,
    30 		LIMIT_ČASU
    31 	}
    32 	TipyDAO tipy = new TipyDAO();
    33 	HistorieDAO historie = new HistorieDAO();
    34 
    35 	public VysledekSQL vykonejSQL(String sql, Uzivatel uzivatel) {
    36 		VysledekSQL v = new VysledekSQL();
    37 		if (historie.ulozPrikaz(sql, uzivatel)) {
    38 
    39 			Connection db = getSpojeni(DATABAZE.PISKOVISTE);
    40 			if (db == null) {
    41 				v.getHlasky().add(new Hlaska("Došlo k chybě spojení.", Typ.Chyba));
    42 			} else {
    43 				PreparedStatement ps = null;
    44 				ResultSet rs = null;
    45 				try {
    46 					/**
    47 					 * Uživatelskému SQL příkazu předřadíme výchozí cestu (search_path).
    48 					 * Protože uživatelé si ji mohou měnit a kvůli recyklaci databázových zdrojů
    49 					 * by jeden uživatel mohl ovlivnit jiného.
    50 					 */
    51 					if (getVlastnost(VLASTNOSTI.VYCHOZI_CESTA) != null) {
    52 						sql = orizni(getVlastnost(VLASTNOSTI.VYCHOZI_CESTA)) + sql;
    53 					}
    54 					
    55 					/**
    56 					 * TODO:
    57 					 * použít ps.setQueryTimeout(LIMIT_ČASU);
    58 					 * až ho bude podporovat JDBC ovladač,
    59 					 * viz níže.
    60 					 * Uživatel ale stejně může zadat:
    61 					 * SET statement_timeout 0;
    62 					 * do svého SQL dotazu.
    63 					 */
    64 					if (getVlastnost(VLASTNOSTI.LIMIT_ČASU) != null) {
    65 						sql = orizni(getVlastnost(VLASTNOSTI.LIMIT_ČASU)) + sql;
    66 					}
    67 
    68 					long casPred = System.currentTimeMillis();
    69 					ps = db.prepareStatement(sql);
    70 					/**
    71 					 * Limit času bohužel není podporován JDBC ovladačem.
    72 					 * Alespoň ne v postgresql-9.1-901.jdbc4.jar
    73 					 * http://jdbc.postgresql.org/todo.html
    74 					 * 
    75 					 * TODO:
    76 					 * ps.setQueryTimeout(LIMIT_ČASU);
    77 					 */
    78 					ps.setMaxRows(LIMIT_POČTU);
    79 					boolean isRS = ps.execute();
    80 
    81 					if (isRS) {
    82 						rs = ps.getResultSet();
    83 						v.getTabulky().add(zpracujVysledek(rs));
    84 					}
    85 
    86 					/**
    87 					 * Ošetříme případ, kdy uživatel zadá SQL příkaz, který nevrací výsledkovou
    88 					 * sadu.
    89 					 * Typicky nastavení výchozího schématu: SET search_path = '…';
    90 					 * Poznámka: jeden „SET search_path TO "…"“ se obvykle předřazuje uživatelskému
    91 					 * SQL (viz PiskovisteDAO.xml).
    92 					 */
    93 					while (ps.getMoreResults() || ps.getUpdateCount() > -1) {
    94 						rs = ps.getResultSet();
    95 						if (rs == null) {
    96 							/** Jedná se o „update count“. */
    97 						} else {
    98 							v.getTabulky().add(zpracujVysledek(rs));
    99 						}
   100 					}
   101 					long dobaProvadeni = System.currentTimeMillis() - casPred;
   102 
   103 					/** Varování */
   104 					if (v.getHlasky().size() < 1 && v.getTabulky().size() < 1) {
   105 						v.getHlasky().add(new Hlaska("SQL příkaz proběhl, ale nevrátil žádná data.", Typ.Varovani));
   106 					}
   107 
   108 					/** Varování */
   109 					int pocitadloTabulek = 1;
   110 					for (Tabulka t : v.getTabulky()) {
   111 						if (t.getHodnoty().size() < 1) {
   112 							v.getHlasky().add(new Hlaska("Tabulka " + pocitadloTabulek + "  je prázdná.", Typ.Varovani));
   113 						}
   114 						pocitadloTabulek++;
   115 					}
   116 
   117 					v.getHlasky().add(new Hlaska("SQL příkaz byl proveden úspěšně, během " + dobaProvadeni + " ms.", Typ.OK));
   118 
   119 				} catch (SQLException e) {
   120 					log.log(Level.SEVERE, "SQL chyba při vykonávání uživatelského dotazu.", e);
   121 					v.getHlasky().add(new Hlaska("Chybné SQL: " + e.getMessage(), Typ.Chyba));
   122 				} catch (Exception e) {
   123 					log.log(Level.SEVERE, "Chyba při vykonávání uživatelského dotazu.", e);
   124 					v.getHlasky().add(new Hlaska("Došlo k chybě dotazu.", Typ.Chyba));
   125 				} finally {
   126 					zavri(db, ps, rs);
   127 				}
   128 			}
   129 
   130 			/** Tip pro uživatele */
   131 			String tip = tipy.getTip();
   132 			if (tip != null) {
   133 				v.getHlasky().add(new Hlaska(tip, Typ.Tip, false));
   134 			}
   135 
   136 		} else {
   137 			v.getHlasky().add(new Hlaska("Došlo k chybě historie.", Typ.Chyba));
   138 		}
   139 		return v;
   140 	}
   141 
   142 	private Tabulka zpracujVysledek(ResultSet rs) throws SQLException {
   143 		Tabulka t = new Tabulka();
   144 
   145 		int pocetSloupecku = rs.getMetaData().getColumnCount();
   146 		String[] zahlavi = new String[pocetSloupecku];
   147 		t.setZahlavi(zahlavi);
   148 		for (int i = 0; i < pocetSloupecku; i++) {
   149 			zahlavi[i] = rs.getMetaData().getColumnName(i + 1);
   150 		}
   151 
   152 		while (rs.next()) {
   153 			Object[] hodnoty = new Object[pocetSloupecku];
   154 			for (int i = 0; i < pocetSloupecku; i++) {
   155 				hodnoty[i] = rs.getObject(i + 1);
   156 			}
   157 			t.getHodnoty().add(hodnoty);
   158 		}
   159 
   160 		return t;
   161 	}
   162 }