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