You have a privilege to create a quiz (QnA) related to this subject and obtain creativity score...
DataService framework by ITS conceptually is similar to Spring. The framework uses the same principles of Inversion of Control and Dependency Injection. The base class is the DataService class (versus JdbcTemplate in Spring). User injects dependencies describing desirable objects and functions into this class and DataService handles data management for developers, saving a lot of coding efforts.
There are two major differences making DataService easier than other frameworks.
a) DataService simplifies configuration, providing most of dependencies as Java arguments.
b) SQL statements are not provided with Java code but stored separately in the {project}/config/sql/ - directory as small SQL files. DataService takes the name of the file as an argument to DataService, for example, insertAccounts. This allows to separate two different languages and makes easy testing SQL statements, which could be very big and sophisticated, with existing SQL tools.
In this section we will create JDBC utilities that provide abbreviated examples of how these principles are implemented. You will be able to use these utilities while working with databases. Later you will be able to load DataService library and use real implementations that add more functions and take care about the flavors of different DB vendors. Your code will not change as the names and signature of the utilities will be mostly the same as in the real DataService implementation.
Before creating the utilities read again 2.2. JDBC section inside the level 2. Java and Databases. Then shorten your sleeves and start following coding instructions.
1) Create a new project and
2) Name it week4db.
3) Press the NEXT control to see the project structure and press Finish
4) Create a new folder by right mouse click on the project
5) Name the new folder as lib. You created the lib directory in the project structure and can place there the Oracle JDBC driver library ojdbc6.jar and ucp.jar.
6) Download the driver from these URLs: http://ITUniversity.us/downloads/jars/ojdbc6.jar and http://ITUniversity.us/downloads/jars/ucp.jar and store both on your PC in the c:\downloads\jars – folder (create this folder if not thee yet).
7) Then copy the jar files from the c:\downloads\jars – folder to the lib folder in Eclipse, in the week4db project.
8) You can see the jar files under the lib folder. But the compiler does not know about this library yet. You need to add this this file to the classpath for the compiler. In Eclipse terms this means Build Path – Add to Build Path. How do you do this? Right mouse click on each jar file, select Build Path – Add to Build Path. Now it is done! The magic happened and you can see this jar file as part of the project above the lib folder.
Ready for the fun part? We start with a clear plan placed in a header of the DataService class.
/**
* The DataService class is the core class of DataService framework by ITS.
* The framework allows developers describe dependencies and desirable objects
* as configuration parameters saving a lot of coding efforts.
* DataService expects a simple configuration file with DB properties.
* SQL statements are not provided with Java code but stored separately in the {project}/config/sql/ - directory as small SQL files.
* DataService works with other framework classes, such as IOMaster, Parser, Stringer and Stats to read configuration details and SQL statements,
* the store them in a static Hashtable called appDetails.
* At runtime DataService takes the name of an SQL file as an argument and retrieves the statement from the Hashtable.
* This allows to separate two different languages and makes easy testing SQL statements with existing SQL tools.
* Preferred way of database access is via PreparedStatement.
* PreparedStatement is safer than Statement objects from security prospects, often accelerate performance,
* and provides better handling for data with unexpected characters,
* such as single quote, which can confuse a regular Statement processing.
Recommended usage:
* 1. Prepare a configuration file describing your databases (can be more than one)
Example of a configuration file, its-ds.xml:
its jdbc:oracle:thin:@localhost:1521:xe oracle.jdbc.driver.OracleDriver its its
* 2. Create SQL statements and store them in the SQL location with the extension ".sql"
Example:
select * from users where userName = ? and job = ?
* Store the line above in the file {project}/config/sql/selectUsersByNameAndJob.sql
* using PreparedStatement
*
* 3. At application start read configuration and associate a Database with its data source name (dsName) to
* prepare for work with the pool of connections. The method below will also read all SQL files into a Hashtable.
DataService.init(applicationName, dataSourceLogicalName, pathToConfigurationPropertyFile);
*
* 4. In the application at runtime use the method below, which will find a proper SQL statement and replace runtime variables
Example:
List listOfRecords = DataService.getPrepDataBySqlName("selectUsersByNameAndJob", new String[] {userName,job}, dsName);
*/
Several hints on Data Services Implementation
Data Services is a small library collected in a single com.its.util.jar file.
A complete API is provided at http://ITUniversity.us/downloads/html/api
There is no need to know internals to use this library.
But we find it useful for you to have some hints on how this is done.
So you can see no magic involved and you can do similar or better, when it comes to more complex matters.
According to the plan, we should have at least the following utilities.
A) Read/write utilities in IOMaster – we did this before and we can copy or better re-type this class in Eclipse.
package its.day11.db;
import java.io.*;
/**
* IOMaster provides a set of IO utilities to read and write text and binary files
*
*/
public class IOMaster {
/**
* The readTextFile() method connects to an existing file and reads from the file
*
* @param name
* @return text or error message
*/
public static String readTextFile(String name) {
String text = ""; // the String object to collect the file
BufferedReader br = null;
try {
FileReader fr = new FileReader(name);
br = new BufferedReader(fr);
// prepare for the loop
String line = ""; // a single line to be filled by readLine()
for (; (line = br.readLine()) != null;) {
text += line + System.lineSeparator(); // collecting all lines
}
} catch (IOException e) {
text = "ERROR: e=" + e.getMessage(); // return error message
} finally { // the finally will close the stream even in the case of exception
if (br != null) {
try {
br.close();
} catch (Exception e) {
System.out.println("ERROR: " + e.getMessage());
}
}
}
return text;
}
/**
* The writeTextFile() method creates a new file and writes a set of lines
* to the file
*
* @param name
* @param lines
* @returns true if success or false in the case of any errors
*/
public static boolean writeTextFile(String name, String[] lines) {
boolean success = true;
PrintWriter pw = null;
try {
FileWriter fw = new FileWriter(name); // create a new file
pw = new PrintWriter(fw); // wrap a convenient stream for lines
for (int i = 0; i < lines.length; i++) {
pw.println(lines[i]); // write line by line into the file
}
// flush OS buffers, otherwise OS can wait till the buffers are full
// before writing to a file
} catch (IOException e) {
success = false;
} finally {
pw.flush();
pw.close();
}
return success;
}
/**
* The readBinFile() method connects to an existing file and reads from the
* file
*
* @param name
* @return bytes
* @throws IOException
*/
public static byte[] readBinFile(String name) {
byte[] bytes = null;
try {
FileInputStream fis = new FileInputStream(name);
BufferedInputStream bis = new BufferedInputStream(fis);
// get a size of the file to read
File file = new File(name);
int size = (int) file.length();
// allocate a byte array for reading assuming it is not a huge file
bytes = new byte[size];
int numberOfBytesToRead = 4096; // reading by chunks
int i = 0; // byte index to start reading from the file into the array
for (; i < bytes.length; i = i + numberOfBytesToRead) {
bis.read(bytes, i, numberOfBytesToRead); // read numberOfBytesToRead
}
int moreBytesToRead = bytes.length - i;
bis.read(bytes, i, moreBytesToRead); // read the remaining bytes
bis.close();
} catch(Exception e) {
System.out.println("ERROR: "+e.getMessage());
return null;
}
return bytes;
}
/**
* The writeBinFile() method writes array of bytes into a binary file
* @param filename
* @param bytes
*/
public static void writeBinFile(String filename, byte[] bytes) {
try {
FileOutputStream fos = new FileOutputStream(filename); // create a new file
BufferedOutputStream bos=new BufferedOutputStream (fos); // wrap a convenient stream for writing lines
int numberOfBytesToWrite = 4096; // prepare to write by 4096 bytes at once
int i=0; // byte index to start writing from the array of bytes
for(; i < bytes.length; i = i + numberOfBytesToWrite) {
bos.write(bytes, i, numberOfBytesToWrite); // write numberOfBytesToWrite from index i
}
int moreBytesToWrite = bytes.length - i;
bos.write(bytes, i, moreBytesToWrite); // write the remaining bytes
// flush OS buffers, otherwise OS can wait till the buffers are full before writing to a file
bos.flush();
bos.close(); // and close the stream
} catch(Exception e) {
System.out.println("ERROR: "+e.getMessage());
}
}
/**
* Test all methods in the main method
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
testTextMethods();
testBinMethods();
}
public static void testTextMethods() throws IOException {
String[] lines = { "Jeff", "Alicia", "Karen" };
String name = "c:/its-resources/day6.txt";
// test writing text file
IOMaster.writeTextFile(name, lines);
// test reading text file
String text = IOMaster.readTextFile(name);
System.out.println(text);
// Check with Windows Explorer if the file was written and open to see
// its content
}
public static void testBinMethods() throws IOException {
byte[] data = { 1, 2, 3, 4, 5, 6 };
String name = "c:/its-resources/day6.bin";
// test writing text file
IOMaster.writeBinFile(name, data);
// test reading text file
byte[] bytes = IOMaster.readBinFile(name);
System.out.println("Number of bytes: " + bytes.length);
// Check with Windows Explorer if the file was written
}
}
Was it clear so far?
B) To parse configuration properties we need our Parser utilities and we add an extract from the Stringer class that retrieves the value between XML tags.
package its.day11.db;
/**
* This is an extract from the class Parser, part of DataService framework, which includes text manipulation utilities
*/
public class Parser {
/**
* The parseWithPatterns() search the text according to the patterns
* @param text
* @param patterns, such as {"after","div", "after", "table", "before", "/table"}
* @return result
*/
public static String parseWithPatterns(String text, String patternsInString) {
return parseWithPatterns(text, patternsInString, false); // look at case by default
}
/**
* The parseWithPatterns() search the text according to the pattern
* @param text
* @param patternsInString pairs of keywords and patterns, separated by the comma characters,
* such as "after,div,after,table,before,/table"
* - three patterns with three keywords (after, after, before) as one string
* @param ignoreCase
* @return result
*/
public static String parseWithPatterns(String text, String patternsInString, boolean ignoreCase) {
if(patternsInString == null || text == null) {
return text;
}
String[] patterns = patternsInString.split(",");
return parseWithPatterns(text, patterns, ignoreCase);
}
/**
* This version of the parseWithPatterns() method takes an array of patterns
* In the array the first value is a key, such as "before" or "after" or "before last" or "after ignorecase" etc.
* and the next value is the pattern to look for
* @param text
* @param patterns in pairs (key, pattern)
* @return result
*/
public static String parseWithPatterns(String text, String[] patterns) {
return parseWithPatterns(text, patterns, false); // case sensitive by default
}
public static String parseWithPatterns(String originText, String[] patterns, boolean ignoreCase) {
if(patterns == null || originText == null) {
return originText;
}
int foundIndex = -1;
String text = originText;
if(ignoreCase) {
// make all text lower case
text = originText.toLowerCase();
}
for(int i=0; i < patterns.length; i+= 2) {
String key = patterns[i];
String pattern = patterns[i+1];
if(ignoreCase || key.toLowerCase().indexOf("ignorecase") > 0) {
// make lower case only for this particular pattern
pattern = pattern.toLowerCase();
text = text.toLowerCase();
}
// check for the pattern looking forward
int indexOfPattern = text.indexOf(pattern);
// check for the pattern looking back (reverse)
int rindexOfPattern = text.lastIndexOf(pattern);
// the key can be "after last" or "afterlast" producing same results
if(key.equalsIgnoreCase("after last") || key.equalsIgnoreCase("afterLast")) {
// for the last pattern, look reverse (from the end of the text)
if(rindexOfPattern >= 0) {
text = text.substring(rindexOfPattern + pattern.length());
originText = originText.substring(rindexOfPattern + pattern.length());
} else {
return "";
}
} else if(key.startsWith("after")) {
// looking forward and using the index found while looking forward
if(indexOfPattern >= 0) {
text = text.substring(indexOfPattern + pattern.length());
originText = originText.substring(indexOfPattern + pattern.length());
} else {
return "";
}
} else if(key.equalsIgnoreCase("before last") || key.equalsIgnoreCase("beforeLast")) {
if(rindexOfPattern >= 0) {
text = text.substring(0, rindexOfPattern);
originText = originText.substring(0, rindexOfPattern);
} else {
return "";
}
} else if(key.startsWith("before")) {
if(indexOfPattern >= 0) {
text = text.substring(0, indexOfPattern);
originText = originText.substring(0, indexOfPattern);
} else {
return "";
}
} else if(key.equalsIgnoreCase("find")) {
if(indexOfPattern >= 0) {
foundIndex = indexOfPattern;
} else {
return "";
}
} else if(key.equalsIgnoreCase("backFromFound")) {
indexOfPattern = text.lastIndexOf(pattern, foundIndex);
if(indexOfPattern >= 0) {
text = text.substring(indexOfPattern + pattern.length());
originText = originText.substring(indexOfPattern + pattern.length());
} else {
return "";
}
}
}
return originText;
}
}
Here is a small part of the Stringer class that is actively used by the Parser.
package its.day11.db;
/**
* This is an extract from the class Stringer, part of DataService framework, which includes text manipulation utilities
*/
public class Stringer {
/**
* getStringBetweenTags() returns a string between "" and ""
* @param source
* @param tag
* @return stringBetweenTags
*/
public static String getStringBetweenTags(String origSource, String tag) {
String startTag = "<" + tag + ">";
String endTag = "" + tag + ">";
String result = Parser.parseWithPatterns(origSource, "after,"+startTag+",before,"+endTag);
return result;
}
}
So much troubles to retrieve several values from the file! But we are not done yet.
C) Once we retrieve the values, we need to store them and make available for quick and easy retrieval by many users. We arrange this storage in a static Hashtable, which lives in the Stats class.
This Hashtable can serve as a singleton (a single object in JVM) to store application variables. Potentially a single JVM can run more than one application. To accommodate this possibility the Hashtable appDetails can include more than one internal Hashtables, one for each application. Each internal Hashtable stores key-value pairs.
Two methods: setAppDetails() and getAppDetailsByKeyName() provide access to data.
package its.day11.db;
import java.util.Hashtable;
/**
* The Stats is the static singleton storage of application properties
*/
public class Stats {
// thread-safe singleton storage for application details
// contains a Hashtable for each application, may serve to more than one apps
// each internal Hashtable includes key-value pairs
private static Hashtable> appDetails = new Hashtable>();
/**
* The setAppDetails() method will set key-value pair in a proper app table
* If table does not exist, will create one
* @param appName
* @param key
* @param value
*/
public static void setAppDetails(String appName, String key, String value) {
Hashtable table = null;
try {
table = appDetails.get(appName);
if(table == null) {
table = new Hashtable();
appDetails.put(appName, table);
}
} catch(Exception e) {
// table is not there yet, create one!
table = new Hashtable();
appDetails.put(appName, table);
}
table.put(key, value);
}
/**
* The getAppDetailsByKeyName() method retrieves a value by a key from a proper app table
* @param appName
* @param key
* @return value
*/
public static String getAppDetailsByKeyName(String appName, String key) {
Hashtable table = appDetails.get(appName);
if(table == null) {
return null;
}
String value = table.get(key);
return value;
}
}
With this ammunition we can write the init() method in the DataService class to read configuration files, including database configuration and SQL statement files.
For each data source we will have a Hashtable with key-value pairs, where SQL file name will serve as the key and the statement in the file as the value. There could be more than one data source, so we will associate such Hashtable with the name of a data source and store this Hashtable in the umbrella Hashtable appDetails.
Note: Each method in the DataService class throw Exception. This is understandable as DB operations are not always end up successfully.
A database can be down, an SQL statement might not be found, the table or a column name are not spelled correctly in an SQL statement, etc.
The most of these cases will not allow the program continue working and will require a fix.
But there the following common cases that the program should catch and still continue performance:
a) The program tries create the table, but the table is already existing object, created some time ago. Keep in mind that running the program second time, for example, after a test, will cause this problem. The program should catch this situation and allow to continue performance.
b) The program tries to insert the record with a duplicated primary key, when the table was created with the condition unique primary key. It is recommended to catch this exception and allow program to continue working on other records.
Fulfill the Assignments below and continue reading in the section DataSource class simplified.
Assignments: 1. Open Eclipse and navigate to the project week4db.
2. Add to the package its.day11.db a new class IOMaster.
3. One by one type the headers and methods from this section to the class
4. Get rid of red underscore lines provided by Eclipse to indicate errors.
5. Do same thing with the Stringer and then Parser classes.
6. Finish this work by creating the Stats class.
* 2. Create SQL statements and store them in the SQL location with the extension ".sql"
Example:
select * from users where userName = ? and job = ?
* Store the line above in the file {project}/config/sql/selectUsersByNameAndJob.sql
* using PreparedStatement
*
* 3. At application start read configuration and associate a Database with its data source name (dsName) to
* prepare for work with the pool of connections. The method below will also read all SQL files into a Hashtable.
DataService.init(applicationName, dataSourceLogicalName, pathToConfigurationPropertyFile);
*
* 4. In the application at runtime use the method below, which will find a proper SQL statement and replace runtime variables
Example:
List listOfRecords = DataService.getPrepDataBySqlName("selectUsersByNameAndJob", new String[] {userName,job}, dsName);
*/
Several hints on Data Services Implementation
Data Services is a small library collected in a single com.its.util.jar file.
A complete API is provided at http://ITUniversity.us/downloads/html/api
There is no need to know internals to use this library.
But we find it useful for you to have some hints on how this is done.
So you can see no magic involved and you can do similar or better, when it comes to more complex matters.
According to the plan, we should have at least the following utilities.
A) Read/write utilities in IOMaster – we did this before and we can copy or better re-type this class in Eclipse.
<br/>package its.day11.db;
<br/>
<br/>import java.io.*;
<br/>
<br/>/**
<br/> * IOMaster provides a set of IO utilities to read and write text and binary files
<br/> *
<br/> */
<br/>public class IOMaster {
<br/> /**
<br/> * The readTextFile() method connects to an existing file and reads from the file
<br/> *
<br/> * @param name
<br/> * @return text or error message
<br/> */
<br/> public static String readTextFile(String name) {
<br/> String text = ""; // the String object to collect the file
<br/> BufferedReader br = null;
<br/> try {
<br/> FileReader fr = new FileReader(name);
<br/> br = new BufferedReader(fr);
<br/> // prepare for the loop
<br/> String line = ""; // a single line to be filled by readLine()
<br/>
<br/> for (; (line = br.readLine()) != null;) {
<br/> text += line + System.lineSeparator(); // collecting all lines
<br/> }
<br/> } catch (IOException e) {
<br/> text = "ERROR: e=" + e.getMessage(); // return error message
<br/> } finally { // the finally will close the stream even in the case of exception
<br/> if (br != null) {
<br/> try {
<br/> br.close();
<br/> } catch (Exception e) {
<br/> System.out.println("ERROR: " + e.getMessage());
<br/> }
<br/> }
<br/> }
<br/>
<br/> return text;
<br/> }
<br/>
<br/> /**
<br/> * The writeTextFile() method creates a new file and writes a set of lines
<br/> * to the file
<br/> *
<br/> * @param name
<br/> * @param lines
<br/> * @returns true if success or false in the case of any errors
<br/> */
<br/> public static boolean writeTextFile(String name, String[] lines) {
<br/> boolean success = true;
<br/> PrintWriter pw = null;
<br/> try {
<br/> FileWriter fw = new FileWriter(name); // create a new file
<br/> pw = new PrintWriter(fw); // wrap a convenient stream for lines
<br/> for (int i = 0; i < lines.length; i++) {
<br/> pw.println(lines[i]); // write line by line into the file
<br/> }
<br/> // flush OS buffers, otherwise OS can wait till the buffers are full
<br/> // before writing to a file
<br/> } catch (IOException e) {
<br/> success = false;
<br/> } finally {
<br/> pw.flush();
<br/> pw.close();
<br/> }
<br/> return success;
<br/> }
<br/>
<br/> /**
<br/> * The readBinFile() method connects to an existing file and reads from the
<br/> * file
<br/> *
<br/> * @param name
<br/> * @return bytes
<br/> * @throws IOException
<br/> */
<br/> public static byte[] readBinFile(String name) {
<br/> byte[] bytes = null;
<br/> try {
<br/> FileInputStream fis = new FileInputStream(name);
<br/> BufferedInputStream bis = new BufferedInputStream(fis);
<br/> // get a size of the file to read
<br/> File file = new File(name);
<br/> int size = (int) file.length();
<br/> // allocate a byte array for reading assuming it is not a huge file
<br/> bytes = new byte[size];
<br/> int numberOfBytesToRead = 4096; // reading by chunks
<br/> int i = 0; // byte index to start reading from the file into the array
<br/> for (; i < bytes.length; i = i + numberOfBytesToRead) {
<br/> bis.read(bytes, i, numberOfBytesToRead); // read numberOfBytesToRead
<br/> }
<br/> int moreBytesToRead = bytes.length - i;
<br/> bis.read(bytes, i, moreBytesToRead); // read the remaining bytes
<br/> bis.close();
<br/> } catch(Exception e) {
<br/> System.out.println("ERROR: "+e.getMessage());
<br/> return null;
<br/> }
<br/> return bytes;
<br/> }
<br/> /**
<br/> * The writeBinFile() method writes array of bytes into a binary file
<br/> * @param filename
<br/> * @param bytes
<br/> */
<br/> public static void writeBinFile(String filename, byte[] bytes) {
<br/> try {
<br/> FileOutputStream fos = new FileOutputStream(filename); // create a new file
<br/> BufferedOutputStream bos=new BufferedOutputStream (fos); // wrap a convenient stream for writing lines
<br/> int numberOfBytesToWrite = 4096; // prepare to write by 4096 bytes at once
<br/> int i=0; // byte index to start writing from the array of bytes
<br/> for(; i < bytes.length; i = i + numberOfBytesToWrite) {
<br/> bos.write(bytes, i, numberOfBytesToWrite); // write numberOfBytesToWrite from index i
<br/> }
<br/> int moreBytesToWrite = bytes.length - i;
<br/> bos.write(bytes, i, moreBytesToWrite); // write the remaining bytes
<br/> // flush OS buffers, otherwise OS can wait till the buffers are full before writing to a file
<br/> bos.flush();
<br/> bos.close(); // and close the stream
<br/> } catch(Exception e) {
<br/> System.out.println("ERROR: "+e.getMessage());
<br/> }
<br/> }
<br/> /**
<br/> * Test all methods in the main method
<br/> *
<br/> * @param args
<br/> * @throws IOException
<br/> */
<br/> public static void main(String[] args) throws IOException {
<br/> testTextMethods();
<br/> testBinMethods();
<br/> }
<br/>
<br/> public static void testTextMethods() throws IOException {
<br/> String[] lines = { "Jeff", "Alicia", "Karen" };
<br/> String name = "c:/its-resources/day6.txt";
<br/> // test writing text file
<br/> IOMaster.writeTextFile(name, lines);
<br/> // test reading text file
<br/> String text = IOMaster.readTextFile(name);
<br/> System.out.println(text);
<br/> // Check with Windows Explorer if the file was written and open to see
<br/> // its content
<br/> }
<br/>
<br/> public static void testBinMethods() throws IOException {
<br/> byte[] data = { 1, 2, 3, 4, 5, 6 };
<br/> String name = "c:/its-resources/day6.bin";
<br/> // test writing text file
<br/> IOMaster.writeBinFile(name, data);
<br/> // test reading text file
<br/> byte[] bytes = IOMaster.readBinFile(name);
<br/> System.out.println("Number of bytes: " + bytes.length);
<br/> // Check with Windows Explorer if the file was written
<br/> }
<br/>}
<br/>
Was it clear so far?
onclick="window.location.href='/BASE/jsp/demo.jsp?checkFlavor=itsp&issueID=58&intro=general&group=aitu&ur=f'">
B) To parse configuration properties we need our Parser utilities and we add an extract from the Stringer class that retrieves the value between XML tags.
<br/>package its.day11.db;
<br/>
<br/>/**
<br/> * This is an extract from the class Parser, part of DataService framework, which includes text manipulation utilities
<br/> */
<br/>
<br/>public class Parser {
<br/> /**
<br/> * The parseWithPatterns() search the text according to the patterns
<br/> * @param text
<br/> * @param patterns, such as {"after","div", "after", "table", "before", "/table"}
<br/> * @return result
<br/> */
<br/> public static String parseWithPatterns(String text, String patternsInString) {
<br/> return parseWithPatterns(text, patternsInString, false); // look at case by default
<br/> }
<br/> /**
<br/> * The parseWithPatterns() search the text according to the pattern
<br/> * @param text
<br/> * @param patternsInString pairs of keywords and patterns, separated by the comma characters,
<br/> * such as "after,div,after,table,before,/table"
<br/> * - three patterns with three keywords (after, after, before) as one string
<br/> * @param ignoreCase
<br/> * @return result
<br/> */
<br/> public static String parseWithPatterns(String text, String patternsInString, boolean ignoreCase) {
<br/> if(patternsInString == null || text == null) {
<br/> return text;
<br/> }
<br/> String[] patterns = patternsInString.split(",");
<br/> return parseWithPatterns(text, patterns, ignoreCase);
<br/> }
<br/> /**
<br/> * This version of the parseWithPatterns() method takes an array of patterns
<br/> * In the array the first value is a key, such as "before" or "after" or "before last" or "after ignorecase" etc.
<br/> * and the next value is the pattern to look for
<br/> * @param text
<br/> * @param patterns in pairs (key, pattern)
<br/> * @return result
<br/> */
<br/> public static String parseWithPatterns(String text, String[] patterns) {
<br/> return parseWithPatterns(text, patterns, false); // case sensitive by default
<br/> }
<br/> public static String parseWithPatterns(String originText, String[] patterns, boolean ignoreCase) {
<br/> if(patterns == null || originText == null) {
<br/> return originText;
<br/> }
<br/> int foundIndex = -1;
<br/> String text = originText;
<br/> if(ignoreCase) {
<br/> // make all text lower case
<br/> text = originText.toLowerCase();
<br/> }
<br/> for(int i=0; i < patterns.length; i+= 2) {
<br/> String key = patterns[i];
<br/> String pattern = patterns[i+1];
<br/> if(ignoreCase || key.toLowerCase().indexOf("ignorecase") > 0) {
<br/> // make lower case only for this particular pattern
<br/> pattern = pattern.toLowerCase();
<br/> text = text.toLowerCase();
<br/> }
<br/> // check for the pattern looking forward
<br/> int indexOfPattern = text.indexOf(pattern);
<br/> // check for the pattern looking back (reverse)
<br/> int rindexOfPattern = text.lastIndexOf(pattern);
<br/> // the key can be "after last" or "afterlast" producing same results
<br/> if(key.equalsIgnoreCase("after last") || key.equalsIgnoreCase("afterLast")) {
<br/> // for the last pattern, look reverse (from the end of the text)
<br/> if(rindexOfPattern >= 0) {
<br/> text = text.substring(rindexOfPattern + pattern.length());
<br/> originText = originText.substring(rindexOfPattern + pattern.length());
<br/> } else {
<br/> return "";
<br/> }
<br/> } else if(key.startsWith("after")) {
<br/> // looking forward and using the index found while looking forward
<br/> if(indexOfPattern >= 0) {
<br/> text = text.substring(indexOfPattern + pattern.length());
<br/> originText = originText.substring(indexOfPattern + pattern.length());
<br/> } else {
<br/> return "";
<br/> }
<br/> } else if(key.equalsIgnoreCase("before last") || key.equalsIgnoreCase("beforeLast")) {
<br/> if(rindexOfPattern >= 0) {
<br/> text = text.substring(0, rindexOfPattern);
<br/> originText = originText.substring(0, rindexOfPattern);
<br/> } else {
<br/> return "";
<br/> }
<br/> } else if(key.startsWith("before")) {
<br/> if(indexOfPattern >= 0) {
<br/> text = text.substring(0, indexOfPattern);
<br/> originText = originText.substring(0, indexOfPattern);
<br/> } else {
<br/> return "";
<br/> }
<br/> } else if(key.equalsIgnoreCase("find")) {
<br/> if(indexOfPattern >= 0) {
<br/> foundIndex = indexOfPattern;
<br/> } else {
<br/> return "";
<br/> }
<br/> } else if(key.equalsIgnoreCase("backFromFound")) {
<br/> indexOfPattern = text.lastIndexOf(pattern, foundIndex);
<br/> if(indexOfPattern >= 0) {
<br/> text = text.substring(indexOfPattern + pattern.length());
<br/> originText = originText.substring(indexOfPattern + pattern.length());
<br/> } else {
<br/> return "";
<br/> }
<br/> }
<br/> }
<br/> return originText;
<br/> }
<br/>}
<br/>
Here is a small part of the Stringer class that is actively used by the Parser.
<br/>
<br/>package its.day11.db;
<br/>
<br/>/**
<br/> * This is an extract from the class Stringer, part of DataService framework, which includes text manipulation utilities
<br/> */
<br/>
<br/>public class Stringer {
<br/> /**
<br/> * getStringBetweenTags() returns a string between "<tag>" and "</tag>"
<br/> * @param source
<br/> * @param tag
<br/> * @return stringBetweenTags
<br/> */
<br/> public static String getStringBetweenTags(String origSource, String tag) {
<br/> String startTag = "<" + tag + ">";
<br/> String endTag = "</" + tag + ">";
<br/> String result = Parser.parseWithPatterns(origSource, "after,"+startTag+",before,"+endTag);
<br/> return result;
<br/> }
<br/>}
<br/>
So much troubles to retrieve several values from the file! But we are not done yet.
C) Once we retrieve the values, we need to store them and make available for quick and easy retrieval by many users. We arrange this storage in a static Hashtable, which lives in the Stats class.
This Hashtable can serve as a singleton (a single object in JVM) to store application variables. Potentially a single JVM can run more than one application. To accommodate this possibility the Hashtable appDetails can include more than one internal Hashtables, one for each application. Each internal Hashtable stores key-value pairs.
Two methods: setAppDetails() and getAppDetailsByKeyName() provide access to data.
<br/>package its.day11.db;
<br/>
<br/>import java.util.Hashtable;
<br/>
<br/>/**
<br/> * The Stats is the static singleton storage of application properties
<br/> */
<br/>public class Stats {
<br/> // thread-safe singleton storage for application details
<br/> // contains a Hashtable for each application, may serve to more than one apps
<br/> // each internal Hashtable includes key-value pairs
<br/> private static Hashtable<String, Hashtable<String,String>> appDetails = new Hashtable<String,Hashtable<String,String>>();
<br/> /**
<br/> * The setAppDetails() method will set key-value pair in a proper app table
<br/> * If table does not exist, will create one
<br/> * @param appName
<br/> * @param key
<br/> * @param value
<br/> */
<br/> public static void setAppDetails(String appName, String key, String value) {
<br/> Hashtable<String, String> table = null;
<br/> try {
<br/> table = appDetails.get(appName);
<br/> if(table == null) {
<br/> table = new Hashtable<String, String>();
<br/> appDetails.put(appName, table);
<br/> }
<br/> } catch(Exception e) {
<br/> // table is not there yet, create one!
<br/> table = new Hashtable<String, String>();
<br/> appDetails.put(appName, table);
<br/> }
<br/> table.put(key, value);
<br/> }
<br/> /**
<br/> * The getAppDetailsByKeyName() method retrieves a value by a key from a proper app table
<br/> * @param appName
<br/> * @param key
<br/> * @return value
<br/> */
<br/> public static String getAppDetailsByKeyName(String appName, String key) {
<br/> Hashtable<String, String> table = appDetails.get(appName);
<br/> if(table == null) {
<br/> return null;
<br/> }
<br/> String value = table.get(key);
<br/> return value;
<br/> }
<br/>}
<br/>
With this ammunition we can write the init() method in the DataService class to read configuration files, including database configuration and SQL statement files.
For each data source we will have a Hashtable with key-value pairs, where SQL file name will serve as the key and the statement in the file as the value. There could be more than one data source, so we will associate such Hashtable with the name of a data source and store this Hashtable in the umbrella Hashtable appDetails.
Note: Each method in the DataService class throw Exception. This is understandable as DB operations are not always end up successfully.
A database can be down, an SQL statement might not be found, the table or a column name are not spelled correctly in an SQL statement, etc.
The most of these cases will not allow the program continue working and will require a fix.
But there the following common cases that the program should catch and still continue performance:
a) The program tries create the table, but the table is already existing object, created some time ago. Keep in mind that running the program second time, for example, after a test, will cause this problem. The program should catch this situation and allow to continue performance.
b) The program tries to insert the record with a duplicated primary key, when the table was created with the condition unique primary key. It is recommended to catch this exception and allow program to continue working on other records.
Fulfill the Assignments below and continue reading in the section DataSource class simplified.
Assignments: 1. Open Eclipse and navigate to the project week4db.
2. Add to the package its.day11.db a new class IOMaster.
3. One by one type the headers and methods from this section to the class
4. Get rid of red underscore lines provided by Eclipse to indicate errors.
5. Do same thing with the Stringer and then Parser classes.
6. Finish this work by creating the Stats class.