001 package hirondelle.web4j.database; 002 003 import hirondelle.web4j.util.Util; 004 import static hirondelle.web4j.util.Consts.NEW_LINE; 005 006 /** 007 Dynamic SQL statement created in code. The SQL statement can be either: 008 <ul> 009 <li>a complete statement 010 <li>a fragment of a statement, to be appended to the end of a static base statement 011 </ul> 012 013 <P>This class is intended for two use cases: 014 <ul> 015 <li>creating a statement entirely in code 016 <li>creating <tt>WHERE</tt> and <tt>ORDER BY</tt> clauses dynamically, by building sort and filter criteria from 017 user input 018 </ul> 019 020 <P><b>The creation of SQL in code is dangerous. 021 You have to exercise care that your code will not be subject to 022 <a href='http://en.wikipedia.org/wiki/SQL_injection'>SQL Injection attacks</a>. 023 If you don't know what such attacks are all about, then you are in danger of creating 024 very large, dangerous security flaws in your application.</b> 025 026 <P>The main means of protecting yourself from these attacks is to ensure that the sql strings you pass to 027 this class never contain data that has come directly from the user, in an unescaped form. 028 You achieve this by <tt>parameterizing</tt> user input, and proceeding in 2 steps: 029 <ol> 030 <li>create an SQL statement that always uses a <tt>?</tt> placeholder for data entered by the user 031 <li>pass all user-entered data as parameters to the above statement 032 </ol> 033 034 The above corresponds to the correct use of a <tt>PreparedStatement</tt>. 035 After you have built your dynamic SQL, you will usually pass it to 036 {@link hirondelle.web4j.database.Db#search(Class, SqlId, DynamicSql, Object[])} to retrieve the data. 037 038 <h3>Entries in .sql Files</h3> 039 <P>The SQL string you pass to this class is always <em>appended</em> (using {@link #toString}) to 040 a (possibly-empty) base SQL statement already defined (as usual), in your <tt>.sql</tt> file. 041 That entry can take several forms. The criteria on the entry are: 042 <ul> 043 <li>it can be precompiled by WEB4J upon startup, if desired. 044 <li>it contains only <i>static</i> elements of the final SQL statement 045 </ul> 046 047 <em>It's important to note that the static base SQL can be completely empty.</em> 048 For example, the entry in your <tt>.sql file</tt> can look something like this: 049 <pre>MY_DYNAMIC_REPORT { 050 -- this sql is generated in code 051 }</pre> 052 As you can see, there's only a comment here; there's no real SQL. 053 In this case, you will need to build the entire SQL statement in code. 054 (Even though the above entry is empty, it's still necessary, since it's where you specify any 055 non-default target database name. It also ensures that the same mechanism web4j applies 056 to processing <tt>SqlId</tt> objects will remain in effect, which is useful.) 057 058 <P>You are encouraged to implement joins between tables using the <tt>JOIN</tt> syntax. 059 The alternative is to implement joins using expressions in the <tt>WHERE</tt> clause. 060 This usually isn't desirable, since it mixes up two distinct items - joins and actual criteria. 061 Using <tt>JOIN</tt> allows these items to remain separate and distinct. 062 063 <h3>See Also</h3> 064 Other items closely related to this class are : 065 <ul> 066 <li> {@link hirondelle.web4j.action.ActionImpl#getOrderBy(hirondelle.web4j.request.RequestParameter,hirondelle.web4j.request.RequestParameter, String)} - 067 convenience method for constructing an <tt>ORDER BY</tt> clause from request parameters. 068 <li> {@link hirondelle.web4j.database.Db#search(Class, SqlId, DynamicSql, Object[])} 069 <li> the {@link hirondelle.web4j.database.Report} class. 070 </ul> 071 072 <h3>Constants</h3> 073 The {@link #WHERE}, {@link #AND}, and other constants are included in this class as a simple 074 convenience. Note that each value includes a leading a trailing space, to avoid trivial spacing errors. 075 076 <P>This class is non-final, and can be overridden, if desired. The reason is that some applications may wish to try to 077 validate that the SQL passed to this class has been properly parameterized. 078 */ 079 public class DynamicSql {; 080 081 /** Value - {@value}, convenience value for building a <tt>WHERE</tt> clause.*/ 082 public static final String WHERE = " WHERE "; 083 084 /** Value - {@value}, convenience value for building a <tt>WHERE</tt> clause. */ 085 public static final String AND = " AND "; 086 087 /** Value - {@value}, convenience value for building a <tt>WHERE</tt> clause. */ 088 public static final String OR = " OR "; 089 090 /** Value - {@value}, convenience value for building an <tt>ORDER BY</tt> clause. */ 091 public static final String ORDER_BY = " ORDER BY "; 092 093 /** Value - {@value}, convenience value for building an <tt>ORDER BY</tt> clause. */ 094 public static final String ASC = " ASC "; 095 096 /** Value - {@value}, convenience value for building an <tt>ORDER BY</tt> clause. */ 097 public static final String DESC = " DESC "; 098 099 /** 100 Represents the absence of any criteria. The value of this item is simply <tt>null</tt>. 101 <P>If a method allows a <tt>null</tt> object to indicate the absence of any criteria, 102 then it is recommended that this reference be used instead of <tt>null</tt>. 103 */ 104 public static final DynamicSql NONE = null; 105 106 /** 107 Constructor. 108 <P>This constructor will slightly modify the given parameter: it will trim it, and prepend a new line to the result. 109 @param aSql must have content; it will be trimmed by this method. 110 */ 111 public DynamicSql(String aSql){ 112 if( ! Util.textHasContent(aSql) ){ 113 throw new IllegalArgumentException("The SQL text has no content."); 114 } 115 fSql = NEW_LINE + aSql.trim(); 116 } 117 118 119 /** Convenience constructor, forwards to {@link #DynamicSql(String)}. */ 120 public DynamicSql(StringBuilder aSql){ 121 this(aSql.toString()); 122 } 123 124 /** 125 Return the String passed to the constructor, trimmed. 126 127 <P>The returned value is appended by the framework to an existing (possibly empty) entry in an <tt>.sql</tt> file. 128 */ 129 @Override final public String toString(){ 130 return fSql; 131 } 132 133 // PRIVATE 134 135 private String fSql = ""; 136 137 }